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] } }