chore: 同步本地 v9 整改与运营能力

This commit is contained in:
weijuesen
2026-08-17 21:43:26 +08:00
parent 9a426039b7
commit d205d4845b
58 changed files with 4042 additions and 115 deletions
+203
View File
@@ -0,0 +1,203 @@
package handler
import (
"net/http"
"time"
"silk-server-go/internal/middleware"
"silk-server-go/internal/model"
"silk-server-go/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// RegisterFarmRoutes 注册产量、死亡、淘汰、损失和成本记录路由。
func RegisterFarmRoutes(rg *gin.RouterGroup, db *gorm.DB) {
read := middleware.RequirePermission(db, "farm:read")
write := middleware.RequirePermission(db, "farm:write")
rg.GET("/production-loss-records", read, listProductionLossRecords(db))
rg.GET("/production-loss-records/stats", read, productionLossStats(db))
rg.POST("/production-loss-records", write, createProductionLossRecord(db))
rg.PATCH("/production-loss-records/:id", write, updateProductionLossRecord(db))
rg.DELETE("/production-loss-records/:id", write, deleteProductionLossRecord(db))
}
func listProductionLossRecords(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
q := db.Model(&model.ProductionLossRecord{}).
Joins("LEFT JOIN batches b ON b.id = production_loss_records.batch_id")
q = applyRoomScope(q, c, "COALESCE(production_loss_records.room_id, b.room_id)")
if roomID := c.Query("roomId"); roomID != "" {
q = q.Where("production_loss_records.room_id = ?", roomID)
}
if batchID := c.Query("batchId"); batchID != "" {
q = q.Where("production_loss_records.batch_id = ?", batchID)
}
var list []model.ProductionLossRecord
q.Order("production_loss_records.record_date DESC").Limit(200).Find(&list)
c.JSON(http.StatusOK, list)
}
}
func createProductionLossRecord(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var body struct {
RoomID *string `json:"roomId"`
BatchID *string `json:"batchId"`
RecordDate *time.Time `json:"recordDate"`
DeathCount *int `json:"deathCount"`
CulledCount *int `json:"culledCount"`
YieldKg *float64 `json:"yieldKg"`
LossKg *float64 `json:"lossKg"`
CostType *string `json:"costType"`
CostAmount *float64 `json:"costAmount"`
Note *string `json:"note"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if body.BatchID != nil {
if !requireObjectAccess(c, db, "batch", *body.BatchID) {
return
}
if body.RoomID == nil {
if roomID, ok := objectRoomID(db, "batch", *body.BatchID); ok {
body.RoomID = roomID
}
}
}
if body.RoomID != nil && !canAccessRoom(db, c, body.RoomID) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权在该蚕房下记录产量损失"})
return
}
record := model.ProductionLossRecord{
RoomID: body.RoomID, BatchID: body.BatchID,
DeathCount: body.DeathCount, CulledCount: body.CulledCount,
YieldKg: body.YieldKg, LossKg: body.LossKg,
CostType: body.CostType, CostAmount: body.CostAmount,
Note: body.Note, CreatedBy: currentUserID(c),
}
if body.RecordDate != nil {
record.RecordDate = *body.RecordDate
} else {
record.RecordDate = time.Now()
}
if err := model.ValidateProductionLossRecord(record); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Create(&record).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "创建产量损失记录失败"})
return
}
c.JSON(http.StatusCreated, record)
}
}
func updateProductionLossRecord(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
if !requireObjectAccess(c, db, "production_loss_record", id) {
return
}
var record model.ProductionLossRecord
if db.Where("id = ?", id).First(&record).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "production loss record not found"})
return
}
updates, err := bindUpdates(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if roomID, ok := updates["room_id"].(string); ok && !canAccessRoom(db, c, &roomID) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权迁移该记录到指定蚕房"})
return
}
if batchID, ok := updates["batch_id"].(string); ok && !requireObjectAccess(c, db, "batch", batchID) {
return
}
if len(updates) > 0 {
if err := db.Model(&model.ProductionLossRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "更新产量损失记录失败"})
return
}
}
db.Where("id = ?", id).First(&record)
c.JSON(http.StatusOK, record)
}
}
func deleteProductionLossRecord(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
if !requireObjectAccess(c, db, "production_loss_record", id) {
return
}
var record model.ProductionLossRecord
if db.Where("id = ?", id).First(&record).Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "production loss record not found"})
return
}
db.Where("id = ?", id).Delete(&model.ProductionLossRecord{})
c.JSON(http.StatusOK, gin.H{"id": id})
}
}
func productionLossStats(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
q := db.Table("production_loss_records pl").
Select(`
COALESCE(pl.room_id::text, '') AS room_id,
COALESCE(pl.batch_id::text, '') AS batch_id,
COALESCE(SUM(pl.death_count), 0) AS death_count,
COALESCE(SUM(pl.culled_count), 0) AS culled_count,
COALESCE(SUM(pl.yield_kg), 0) AS yield_kg,
COALESCE(SUM(pl.loss_kg), 0) AS loss_kg,
COALESCE(SUM(pl.cost_amount), 0) AS cost_amount,
COUNT(*) AS record_count
`).
Joins("LEFT JOIN batches b ON b.id = pl.batch_id")
q = applyRoomScope(q, c, "COALESCE(pl.room_id, b.room_id)")
if from := c.Query("from"); from != "" {
q = q.Where("pl.record_date >= ?", from)
}
if to := c.Query("to"); to != "" {
q = q.Where("pl.record_date <= ?", to)
}
var rows []service.ProductionLossRow
q.Group("pl.room_id, pl.batch_id").Scan(&rows)
stats := service.AggregateProductionLoss(rows)
fillProductionStatNames(db, stats)
c.JSON(http.StatusOK, stats)
}
}
func fillProductionStatNames(db *gorm.DB, stats []service.ProductionLossStat) {
roomNames := make(map[string]string)
batchNames := make(map[string]string)
for _, s := range stats {
if s.RoomID != "" {
if _, ok := roomNames[s.RoomID]; !ok {
var room model.Room
if db.Select("id", "name").Where("id = ?", s.RoomID).First(&room).Error == nil {
roomNames[s.RoomID] = room.Name
}
}
}
if s.BatchID != "" {
if _, ok := batchNames[s.BatchID]; !ok {
var batch model.Batch
if db.Select("id", "name").Where("id = ?", s.BatchID).First(&batch).Error == nil {
batchNames[s.BatchID] = batch.Name
}
}
}
}
for i := range stats {
stats[i].RoomName = roomNames[stats[i].RoomID]
stats[i].BatchName = batchNames[stats[i].BatchID]
}
}