diff --git a/internal/common/service/common.fifo.service.go b/internal/common/service/common.fifo.service.go index fd1812fb..100c8fcc 100644 --- a/internal/common/service/common.fifo.service.go +++ b/internal/common/service/common.fifo.service.go @@ -26,6 +26,7 @@ type FifoService interface { Consume(ctx context.Context, req StockConsumeRequest) (*StockConsumeResult, error) ReleaseUsage(ctx context.Context, req StockReleaseRequest) error AdjustStockableQuantity(ctx context.Context, req StockAdjustRequest) error + ResolvePending(ctx context.Context, req PendingResolveRequest) ([]PendingResolution, error) } type fifoService struct { @@ -111,6 +112,11 @@ type PendingResolution struct { Quantity float64 } +type PendingResolveRequest struct { + ProductWarehouseID uint + Tx *gorm.DB +} + type StockReplenishResult struct { AddedQuantity float64 PendingResolved []PendingResolution @@ -227,6 +233,23 @@ func (s *fifoService) Replenish(ctx context.Context, req StockReplenishRequest) return result, nil } +func (s *fifoService) ResolvePending(ctx context.Context, req PendingResolveRequest) ([]PendingResolution, error) { + if req.ProductWarehouseID == 0 { + return nil, errors.New("product warehouse id is required") + } + + var resolved []PendingResolution + err := s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error { + var err error + resolved, err = s.resolvePendingForWarehouse(ctx, tx, req.ProductWarehouseID) + return err + }) + if err != nil { + return nil, err + } + return resolved, nil +} + func (s *fifoService) Consume(ctx context.Context, req StockConsumeRequest) (*StockConsumeResult, error) { if req.UsableID == 0 || strings.TrimSpace(req.UsableKey.String()) == "" { return nil, errors.New("usable key and id are required") @@ -849,8 +872,8 @@ func (s *fifoService) fetchPendingCandidates(ctx context.Context, tx *gorm.DB, p if cfg.Columns.CreatedAt == cfg.Columns.ID { var rows []struct { ID uint - Pending float64 - CreatedAt int64 + Pending float64 `gorm:"column:pending_qty"` + CreatedAt int64 `gorm:"column:created_at"` } query := tx.Table(cfg.Table). @@ -867,27 +890,26 @@ func (s *fifoService) fetchPendingCandidates(ctx context.Context, tx *gorm.DB, p query = query.Order(order) } - if err := query.Find(&rows).Error; err != nil { - return nil, err + if err := query.Find(&rows).Error; err != nil { + return nil, err + } + for _, row := range rows { + if row.Pending <= 0 { + continue } - - for _, row := range rows { - if row.Pending <= 0 { - continue - } - candidates = append(candidates, pendingCandidate{ - UsableKey: key, - Config: cfg, - UsableID: row.ID, - Pending: row.Pending, - CreatedAt: time.Unix(0, row.CreatedAt), - }) - } - } else { + candidates = append(candidates, pendingCandidate{ + UsableKey: key, + Config: cfg, + UsableID: row.ID, + Pending: row.Pending, + CreatedAt: time.Unix(0, row.CreatedAt), + }) + } + } else { var rows []struct { ID uint - Pending float64 - CreatedAt time.Time + Pending float64 `gorm:"column:pending_qty"` + CreatedAt time.Time `gorm:"column:created_at"` } query := tx.Table(cfg.Table). diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index 4f666812..b4ccf36a 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -74,7 +74,7 @@ func seedUsers(tx *gorm.DB) (map[string]uint, error) { } func seedUoms(tx *gorm.DB, createdBy uint) (map[string]uint, error) { - names := []string{"Kilogram", "Gram", "Liter", "Unit", "Ekor"} + names := []string{"Kilogram", "Gram", "Liter", "Unit", "Ekor", "Butir"} result := make(map[string]uint, len(names)) for _, name := range names { @@ -235,7 +235,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Name: "Telur Utuh", Brand: "-", Sku: "4", - Uom: "Gram", + Uom: "Butir", Category: "Telur", Price: 1, Flags: []utils.FlagType{utils.FlagTelurUtuh}, @@ -245,7 +245,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Name: "Telur Pecah", Brand: "-", Sku: "5", - Uom: "Gram", + Uom: "Butir", Category: "Telur", Price: 1, Flags: []utils.FlagType{utils.FlagTelurPecah}, @@ -255,7 +255,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Name: "Telur Putih", Brand: "-", Sku: "6", - Uom: "Gram", + Uom: "Butir", Category: "Telur", Price: 1, Flags: []utils.FlagType{utils.FlagTelurPutih}, @@ -265,12 +265,32 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Name: "Telur Retak", Brand: "-", Sku: "7", - Uom: "Gram", + Uom: "Butir", Category: "Telur", Price: 1, Flags: []utils.FlagType{utils.FlagTelurRetak}, IsVisible: false, }, + { + Name: "Telur Papacal", + Brand: "-", + Sku: "8", + Uom: "Butir", + Category: "Telur", + Price: 1, + Flags: []utils.FlagType{utils.FlagTelur}, + IsVisible: false, + }, + { + Name: "Telur Jumbo", + Brand: "-", + Sku: "9", + Uom: "Butir", + Category: "Telur", + Price: 1, + Flags: []utils.FlagType{utils.FlagTelur}, + IsVisible: false, + }, } for _, seed := range seeds { diff --git a/internal/modules/marketing/controllers/deliveryorder.controller.go b/internal/modules/marketing/controllers/deliveryorder.controller.go index 73904cc3..3a6ca49b 100644 --- a/internal/modules/marketing/controllers/deliveryorder.controller.go +++ b/internal/modules/marketing/controllers/deliveryorder.controller.go @@ -3,6 +3,7 @@ package controller import ( "math" "strconv" + "strings" "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services" @@ -23,9 +24,38 @@ func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersSer } func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error { + parseUintListParam := func(param string) ([]uint, error) { + if param == "" { + return nil, nil + } + parts := strings.Split(param, ",") + ids := make([]uint, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed == "" { + return nil, strconv.ErrSyntax + } + parsed, err := strconv.ParseUint(trimmed, 10, 64) + if err != nil { + return nil, err + } + ids = append(ids, uint(parsed)) + } + return ids, nil + } + + productIDs, err := parseUintListParam(c.Query("product_ids", "")) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid product_ids") + } + query := &validation.DeliveryOrderQuery{ Page: c.QueryInt("page", 1), Limit: c.QueryInt("limit", 10), + Search: strings.TrimSpace(c.Query("search", "")), + ProductIDs: productIDs, + Status: strings.ReplaceAll(strings.TrimSpace(c.Query("status", "")), "_", " "), + CustomerId: uint(c.QueryInt("customer_id", 0)), MarketingId: uint(c.QueryInt("marketing_id", 0)), } diff --git a/internal/modules/marketing/dto/deliveryorder.dto.go b/internal/modules/marketing/dto/deliveryorder.dto.go index bd4b2a0b..20b3e42b 100644 --- a/internal/modules/marketing/dto/deliveryorder.dto.go +++ b/internal/modules/marketing/dto/deliveryorder.dto.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "strings" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -17,6 +18,7 @@ import ( type MarketingRelationDTO struct { Id uint `json:"id"` SoNumber string `json:"so_number"` + DoNumber *string `json:"do_number"` SoDate time.Time `json:"so_date"` Notes string `json:"notes,omitempty"` } @@ -95,9 +97,16 @@ type DeliveryMarketingProductDTO struct { } func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO { + var doNumber *string + if doNumbers := collectDoNumbers(marketing); len(doNumbers) > 0 { + value := doNumbers[0] + doNumber = &value + } + return MarketingRelationDTO{ Id: marketing.Id, SoNumber: marketing.SoNumber, + DoNumber: doNumber, SoDate: marketing.SoDate, Notes: marketing.Notes, } @@ -182,7 +191,6 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType) } } - return MarketingListDTO{ MarketingRelationDTO: ToMarketingRelationDTO(marketing), Customer: customer, @@ -239,7 +247,6 @@ func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval) latestApproval = mapped } - return MarketingDetailDTO{ MarketingRelationDTO: ToMarketingRelationDTO(marketing), SoDocs: marketing.SoDocs, @@ -346,11 +353,46 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri } func GenerateDeliveryOrderNumber(soNumber string, deliveryDate *time.Time, warehouseId uint) string { - dateStr := "" - if deliveryDate != nil { - dateStr = deliveryDate.Format("20060102") + numberPrefix := soNumber + if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(soNumber)), "SO-") { + numberPrefix = "DO-" + soNumber[3:] } - return fmt.Sprintf("%s-%s-%d", soNumber, dateStr, warehouseId) + return numberPrefix +} + +func collectDoNumbers(marketing *entity.Marketing) []string { + if marketing == nil || len(marketing.Products) == 0 { + return nil + } + + seen := make(map[string]struct{}) + for _, product := range marketing.Products { + if product.DeliveryProduct == nil || product.DeliveryProduct.DeliveryDate == nil { + continue + } + warehouseID := product.ProductWarehouse.WarehouseId + if warehouseID == 0 && product.ProductWarehouse.Warehouse.Id != 0 { + warehouseID = product.ProductWarehouse.Warehouse.Id + } + if warehouseID == 0 { + continue + } + + doNumber := GenerateDeliveryOrderNumber(marketing.SoNumber, product.DeliveryProduct.DeliveryDate, warehouseID) + if doNumber != "" { + seen[doNumber] = struct{}{} + } + } + + if len(seen) == 0 { + return nil + } + result := make([]string, 0, len(seen)) + for value := range seen { + result = append(result, value) + } + sort.Strings(result) + return result } func getVehicleNumber(e entity.MarketingProduct) string { diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index 493f689f..677ef965 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -116,6 +116,71 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO Preload("Products.ProductWarehouse.Warehouse"). Preload("Products.DeliveryProduct") + if params.Status != "" { + latestApprovalSubQuery := s.MarketingRepo.DB(). + WithContext(c.Context()). + Table("approvals"). + Select("DISTINCT ON (approvable_id) approvable_id, step_name"). + Where("approvable_type = ?", utils.ApprovalWorkflowMarketing.String()). + Order("approvable_id, id DESC") + db = db.Where(`EXISTS ( + SELECT 1 + FROM (?) AS latest_approval + WHERE latest_approval.approvable_id = marketings.id + AND LOWER(latest_approval.step_name) = LOWER(?) + )`, latestApprovalSubQuery, params.Status) + } + + if params.Search != "" { + searchPattern := "%" + params.Search + "%" + db = db.Where(`( + marketings.so_number ILIKE ? OR + EXISTS ( + SELECT 1 + FROM customers c + WHERE c.id = marketings.customer_id + AND c.name ILIKE ? + ) OR + EXISTS ( + SELECT 1 + FROM users su + WHERE su.id = marketings.sales_person_id + AND su.name ILIKE ? + ) OR + EXISTS ( + SELECT 1 + FROM marketing_products mp + JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id + JOIN products p ON p.id = pw.product_id + WHERE mp.marketing_id = marketings.id + AND p.name ILIKE ? + ) OR + EXISTS ( + SELECT 1 + FROM marketing_products mp + JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id + JOIN warehouses w ON w.id = pw.warehouse_id + WHERE mp.marketing_id = marketings.id + AND w.name ILIKE ? + ) + )`, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern) + } + + if len(params.ProductIDs) > 0 { + db = db.Where(`EXISTS ( + SELECT 1 + FROM marketing_products mp + JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id + JOIN products p ON p.id = pw.product_id + WHERE mp.marketing_id = marketings.id + AND p.id IN ? + )`, params.ProductIDs) + } + + if params.CustomerId != 0 { + db = db.Where("marketings.customer_id = ?", params.CustomerId) + } + if scope.Restrict { if len(scope.IDs) == 0 { return db.Where("1 = 0") diff --git a/internal/modules/marketing/validations/deliveryorder.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go index a879db6f..e4687fad 100644 --- a/internal/modules/marketing/validations/deliveryorder.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -19,9 +19,13 @@ type DeliveryOrderUpdate struct { } type DeliveryOrderQuery struct { - Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` - MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"` + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=100"` + ProductIDs []uint `query:"product_ids" validate:"omitempty,dive,gt=0"` + Status string `query:"status" validate:"omitempty,max=50"` + CustomerId uint `query:"customer_id" validate:"omitempty,gt=0"` + MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"` } type DeliveryOrderApprove struct { diff --git a/internal/modules/master/product-categories/services/product-category.service.go b/internal/modules/master/product-categories/services/product-category.service.go index ae1577f1..757d5787 100644 --- a/internal/modules/master/product-categories/services/product-category.service.go +++ b/internal/modules/master/product-categories/services/product-category.service.go @@ -52,7 +52,22 @@ func (s productCategoryService) GetAll(c *fiber.Ctx, params *validation.Query) ( productCategories, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) if params.Search != "" { - return db.Where("name ILIKE ?", "%"+params.Search+"%") + terms := splitSearchTerms(params.Search) + if len(terms) == 0 { + return db + } + if len(terms) == 1 { + return db.Where("name ILIKE ?", "%"+terms[0]+"%") + } + for i, term := range terms { + like := "%" + term + "%" + if i == 0 { + db = db.Where("name ILIKE ?", like) + } else { + db = db.Or("name ILIKE ?", like) + } + } + return db } return db.Order("created_at DESC").Order("updated_at DESC") }) @@ -64,6 +79,20 @@ func (s productCategoryService) GetAll(c *fiber.Ctx, params *validation.Query) ( return productCategories, total, nil } +func splitSearchTerms(raw string) []string { + parts := strings.FieldsFunc(raw, func(r rune) bool { + return r == ',' || r == ';' || r == '|' + }) + terms := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + terms = append(terms, trimmed) + } + } + return terms +} + func (s productCategoryService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductCategory, error) { productCategory, err := s.Repository.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index b9867d2b..3ed66f87 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -40,6 +40,7 @@ type RecordingRepository interface { SumRecordingDepletions(tx *gorm.DB, recordingID uint) (float64, error) GetCumulativeDepletionByProjectFlockKandangUntil(tx *gorm.DB, projectFlockKandangId uint, recordTime time.Time) (float64, error) + GetUniformityMeanBwByWeek(tx *gorm.DB, projectFlockKandangId uint, week int) (float64, bool, error) FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error) GetTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error) GetTotalChickinByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint) (float64, error) @@ -331,6 +332,33 @@ func (r *RecordingRepositoryImpl) GetCumulativeDepletionByProjectFlockKandangUnt return total, err } +func (r *RecordingRepositoryImpl) GetUniformityMeanBwByWeek(tx *gorm.DB, projectFlockKandangId uint, week int) (float64, bool, error) { + if projectFlockKandangId == 0 || week <= 0 { + return 0, false, nil + } + + var row struct { + ID uint + MeanUp float64 + } + if err := tx. + Table("project_flock_kandang_uniformity"). + Select("id, mean_up"). + Where("project_flock_kandang_id = ?", projectFlockKandangId). + Where("week = ?", week). + Order("id DESC"). + Limit(1). + Scan(&row).Error; err != nil { + return 0, false, err + } + if row.ID == 0 { + return 0, false, nil + } + + meanBw := row.MeanUp / 1.10 + return meanBw, true, nil +} + func (r *RecordingRepositoryImpl) FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error) { if currentDay <= 1 { return nil, nil diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 28329041..b9b1126e 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -1178,13 +1178,40 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm } var fcrValue float64 - if usageInGrams > 0 && totalEggWeightGrams > 0 { - fcrValue = usageInGrams / totalEggWeightGrams + isGrowing := false + if s.ProjectFlockKandangRepo != nil { + if pfk, err := s.ProjectFlockKandangRepo.GetByID(ctx, recording.ProjectFlockKandangId); err == nil { + if strings.EqualFold(pfk.ProjectFlock.Category, string(utils.ProjectFlockCategoryGrowing)) { + isGrowing = true + } + } + } + + if isGrowing { + week := 0 + if recording.Day != nil && *recording.Day > 0 { + week = (*recording.Day-1)/7 + 1 + } + if week > 0 && s.Repository != nil { + meanBw, ok, err := s.Repository.GetUniformityMeanBwByWeek(tx, recording.ProjectFlockKandangId, week) + if err != nil { + return fmt.Errorf("getUniformityMeanBwByWeek: %w", err) + } + if ok && meanBw > 0 && feedIntake > 0 { + fcrValue = feedIntake / meanBw + } + } updates["fcr_value"] = fcrValue recording.FcrValue = &fcrValue } else { - updates["fcr_value"] = gorm.Expr("NULL") - recording.FcrValue = nil + if usageInGrams > 0 && totalEggWeightGrams > 0 { + fcrValue = usageInGrams / totalEggWeightGrams + updates["fcr_value"] = fcrValue + recording.FcrValue = &fcrValue + } else { + updates["fcr_value"] = gorm.Expr("NULL") + recording.FcrValue = nil + } } if usageInGrams > 0 && totalChick > 0 { diff --git a/internal/modules/production/uniformities/services/uniformity.service.go b/internal/modules/production/uniformities/services/uniformity.service.go index 1e4ccbd5..5a747fca 100644 --- a/internal/modules/production/uniformities/services/uniformity.service.go +++ b/internal/modules/production/uniformities/services/uniformity.service.go @@ -465,11 +465,16 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file ); err != nil { return err } + if strings.EqualFold(category, string(utils.ProjectFlockCategoryGrowing)) { + if err := s.updateGrowingFcrForWeek(tx, createBody.ProjectFlockKandangId, createBody.Week, calculation.MeanUp); err != nil { + return err + } + } return nil }); err != nil { s.Log.Errorf("Failed to create uniformity: %+v", err) return nil, err - } + } if s.DocumentSvc != nil { actorIDCopy := actorID @@ -633,6 +638,9 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui return nil, err } + if err := s.updateGrowingFcrFromUniformity(c.Context(), id); err != nil { + return nil, err + } return s.GetOne(c, id) } @@ -694,6 +702,10 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui } } + if err := s.updateGrowingFcrFromUniformity(c.Context(), id); err != nil { + return nil, err + } + return s.GetOne(c, id) } @@ -724,7 +736,48 @@ func (s uniformityService) DeleteOne(c *fiber.Ctx, id uint) error { return err } - if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + type uniformityContext struct { + ID uint + Week int + ProjectFlockKandangId uint + Category string + } + var ctxRow uniformityContext + if err := s.Repository.DB().WithContext(c.Context()). + Table("project_flock_kandang_uniformity u"). + Select("u.id, u.week, u.project_flock_kandang_id, pf.category"). + Joins("JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id"). + Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id"). + Where("u.id = ?", id). + Scan(&ctxRow).Error; err != nil { + return err + } + + if ctxRow.ID == 0 { + return fiber.NewError(fiber.StatusNotFound, "Uniformity not found") + } + + if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { + repoTx := s.Repository.WithTx(tx) + if err := repoTx.DeleteOne(c.Context(), id); err != nil { + return err + } + + if strings.EqualFold(ctxRow.Category, string(utils.ProjectFlockCategoryGrowing)) { + startDay := (ctxRow.Week-1)*7 + 1 + endDay := ctxRow.Week * 7 + if ctxRow.Week > 0 { + if err := tx.Model(&entity.Recording{}). + Where("project_flock_kandangs_id = ?", ctxRow.ProjectFlockKandangId). + Where("day BETWEEN ? AND ?", startDay, endDay). + Update("fcr_value", 0).Error; err != nil { + return err + } + } + } + + return nil + }); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Uniformity not found") } @@ -734,6 +787,58 @@ func (s uniformityService) DeleteOne(c *fiber.Ctx, id uint) error { return nil } +func (s *uniformityService) updateGrowingFcrFromUniformity(ctx context.Context, uniformityID uint) error { + if uniformityID == 0 { + return nil + } + + type uniformityRow struct { + ID uint + Week int + MeanUp float64 + ProjectFlockKandangId uint + Category string + } + var row uniformityRow + if err := s.Repository.DB().WithContext(ctx). + Table("project_flock_kandang_uniformity u"). + Select("u.id, u.week, u.mean_up, u.project_flock_kandang_id, pf.category"). + Joins("JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id"). + Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id"). + Where("u.id = ?", uniformityID). + Scan(&row).Error; err != nil { + return err + } + if row.ID == 0 { + return nil + } + if !strings.EqualFold(row.Category, string(utils.ProjectFlockCategoryGrowing)) { + return nil + } + + return s.updateGrowingFcrForWeek(s.Repository.DB().WithContext(ctx), row.ProjectFlockKandangId, row.Week, row.MeanUp) +} + +func (s *uniformityService) updateGrowingFcrForWeek(tx *gorm.DB, projectFlockKandangID uint, week int, meanUp float64) error { + if tx == nil || projectFlockKandangID == 0 || week <= 0 { + return nil + } + startDay := (week-1)*7 + 1 + endDay := week * 7 + meanBw := meanUp / 1.10 + if meanBw <= 0 { + return tx.Model(&entity.Recording{}). + Where("project_flock_kandangs_id = ?", projectFlockKandangID). + Where("day BETWEEN ? AND ?", startDay, endDay). + Update("fcr_value", 0).Error + } + + return tx.Model(&entity.Recording{}). + Where("project_flock_kandangs_id = ?", projectFlockKandangID). + Where("day BETWEEN ? AND ?", startDay, endDay). + Update("fcr_value", gorm.Expr("CASE WHEN feed_intake IS NULL OR feed_intake = 0 THEN 0 ELSE feed_intake / ? END", meanBw)).Error +} + func (s uniformityService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlockKandangUniformity, error) { if err := s.Validate.Struct(req); err != nil { return nil, err diff --git a/internal/modules/purchases/dto/purchase.dto.go b/internal/modules/purchases/dto/purchase.dto.go index 444c41f0..c8df2294 100644 --- a/internal/modules/purchases/dto/purchase.dto.go +++ b/internal/modules/purchases/dto/purchase.dto.go @@ -25,17 +25,17 @@ type PurchaseRelationDTO struct { type PurchaseListDTO struct { PurchaseRelationDTO - Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` - DueDate *time.Time `json:"due_date"` - CreatedUser *userDTO.UserRelationDTO `json:"created_user"` - RequesterName string `json:"requester_name"` - PoExpedition []string `json:"po_expedition"` - Products []productDTO.ProductRelationDTO `json:"products"` - Location *locationDTO.LocationRelationDTO `json:"location"` - Area *areaDTO.AreaRelationDTO `json:"area"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"` + Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` + DueDate *time.Time `json:"due_date"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + RequesterName string `json:"requester_name"` + PoExpedition []PoExpeditionDTO `json:"po_expedition"` + Products []productDTO.ProductRelationDTO `json:"products"` + Location *locationDTO.LocationRelationDTO `json:"location"` + Area *areaDTO.AreaRelationDTO `json:"area"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } type PurchaseDetailDTO struct { @@ -69,6 +69,11 @@ type PurchaseItemDTO struct { ExpeditionVendor *supplierDTO.SupplierRelationDTO `json:"expedition_vendor,omitempty"` } +type PoExpeditionDTO struct { + Id uint64 `json:"id"` + Refrence string `json:"refrence"` +} + func ToPurchaseRelationDTO(p *entity.Purchase) PurchaseRelationDTO { return PurchaseRelationDTO{ Id: p.Id, @@ -164,12 +169,12 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO { } var ( - poExpedition []string + poExpedition = make([]PoExpeditionDTO, 0) location *locationDTO.LocationRelationDTO area *areaDTO.AreaRelationDTO ) productMap := make(map[uint]productDTO.ProductRelationDTO) - expeditionRefSet := make(map[string]struct{}) + expeditionRefSet := make(map[uint64]struct{}) for i := range p.Items { item := p.Items[i] if item.Product != nil && item.Product.Id != 0 { @@ -178,11 +183,15 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO { } } if item.ExpenseNonstock != nil && item.ExpenseNonstock.Expense != nil { - ref := strings.TrimSpace(item.ExpenseNonstock.Expense.ReferenceNumber) - if ref != "" { - if _, exists := expeditionRefSet[ref]; !exists { - expeditionRefSet[ref] = struct{}{} - poExpedition = append(poExpedition, ref) + exp := item.ExpenseNonstock.Expense + ref := strings.TrimSpace(exp.ReferenceNumber) + if exp.Id != 0 && ref != "" { + if _, exists := expeditionRefSet[exp.Id]; !exists { + expeditionRefSet[exp.Id] = struct{}{} + poExpedition = append(poExpedition, PoExpeditionDTO{ + Id: exp.Id, + Refrence: ref, + }) } } } diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index dabfad39..703c04b9 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -912,6 +912,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation pwID uint qty float64 }, 0, len(prepared)) + resolvePendingIDs := make(map[uint]struct{}) logEntries := make([]struct { itemID uint pwID uint @@ -952,6 +953,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation pwID uint qty float64 }{itemID: item.Id, pwID: *newPWID, qty: deltaQty}) + resolvePendingIDs[*newPWID] = struct{}{} } else { deltas[*newPWID] += deltaQty totalQtyDeltas[item.Id] += deltaQty @@ -964,11 +966,14 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation qty float64 }{itemID: item.Id, pwID: *newPWID, qty: deltaQty}) affected[*newPWID] = struct{}{} + resolvePendingIDs[*newPWID] = struct{}{} } else { deltas[*newPWID] += deltaQty // negative affected[*newPWID] = struct{}{} totalQtyDeltas[item.Id] += deltaQty } + case newPWID != nil: + resolvePendingIDs[*newPWID] = struct{}{} } dateCopy := prep.receivedDate @@ -1066,6 +1071,19 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation return err } } + for pwID := range resolvePendingIDs { + if pwID == 0 { + continue + } + resolved, err := s.FifoSvc.ResolvePending(c.Context(), commonSvc.PendingResolveRequest{ + ProductWarehouseID: pwID, + Tx: tx, + }) + if err != nil { + return err + } + s.Log.Infof("ResolvePending purchase=%d pw=%d resolved=%d", purchase.Id, pwID, len(resolved)) + } } if len(logEntries) > 0 {