diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index 37094c16..c005e24e 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -219,25 +219,53 @@ func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandang return totals.TotalPieces, totals.TotalWeightKg, nil } -func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) { +func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds( + ctx context.Context, + projectFlockKandangIDs []uint, + date *time.Time, +) (float64, float64, error) { + if date == nil { now := time.Now() date = &now } + type subResult struct { + UsableID uint + MdpUsageQty float64 + MdpWeight float64 + } + + subQuery := r.db.WithContext(ctx). + Table("recordings AS r"). + Select(` + DISTINCT sa.usable_id, + mdp.usage_qty AS mdp_usage_qty, + mdp.total_weight AS mdp_weight + `). + Joins("JOIN recording_eggs re ON re.recording_id = r.id"). + Joins( + "JOIN stock_allocations sa ON sa.stockable_type = ? AND sa.stockable_id = re.id AND sa.usable_type = ?", + fifo.StockableKeyRecordingEgg.String(), + fifo.UsableKeyMarketingDelivery.String(), + ). + Joins("JOIN marketing_delivery_products mdp ON mdp.id = sa.usable_id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date) + var totals struct { TotalPieces float64 TotalWeight float64 } + err := r.db.WithContext(ctx). - Table("recordings AS r"). - Select("COALESCE(SUM(mdp.usage_qty), 0) AS total_pieces, COALESCE(SUM(mdp.total_weight), 0) AS total_weight"). - Joins("JOIN recording_eggs AS re ON re.recording_id = r.id"). - Joins("JOIN stock_allocations AS sa ON sa.stockable_type = ? AND sa.stockable_id = re.id AND sa.usable_type = ?", fifo.StockableKeyRecordingEgg.String(), fifo.UsableKeyMarketingDelivery.String()). - Joins("JOIN marketing_delivery_products AS mdp ON mdp.id = sa.usable_id"). - Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). - Where("r.record_datetime <= ?", *date). + Table("(?) AS x", subQuery). + Select(` + COALESCE(SUM(x.mdp_usage_qty), 0) AS total_pieces, + COALESCE(SUM(x.mdp_weight), 0) AS total_weight + `). Scan(&totals).Error + if err != nil { return 0, 0, err } diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index d725b430..72523b69 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -44,7 +44,12 @@ type PenjualanRealisasiResponseDTO struct { func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO { - ageInDay, ageInWeeks := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate) + productFlags := make([]string, len(e.MarketingProduct.ProductWarehouse.Product.Flags)) + for i, f := range e.MarketingProduct.ProductWarehouse.Product.Flags { + productFlags[i] = f.Name + } + + ageInDay, ageInWeeks := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate, productFlags) var product *productDTO.ProductRelationDTO if e.MarketingProduct.ProductWarehouse.Product.Id != 0 { @@ -126,11 +131,17 @@ func ToPenjualanRealisasiResponseDTO(e []entity.MarketingDeliveryProduct) Penjua } } -func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) (int, int) { +func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time, productFlags []string) (int, int) { if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 { return 0, 0 } + for _, flag := range productFlags { + if flag == "OVK" || flag == "PAKAN" { + return 0, 0 // + } + } + earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate for _, chickin := range projectFlockKandang.Chickins { if chickin.ChickInDate.Before(earliestChickinDate) { diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index c92d059b..bec0ef74 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -167,7 +167,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e newLog.Increase = afterQuantity } else { if productWarehouse.Quantity < req.Quantity { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Current: %.2f, Requested: %.2f", productWarehouse.Quantity, req.Quantity)) + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk pengurangan. Stok saat ini: %.2f, Jumlah yang akan dikurangi: %.2f", productWarehouse.Quantity, req.Quantity)) } afterQuantity -= req.Quantity newLog.Decrease = afterQuantity diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 86ace0c2..ea1041ea 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -124,7 +124,8 @@ func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, e if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Transfer dengan ID %d tidak ditemukan", id)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data transfer dengan ID %d", id)) + s.Log.Errorf("Failed to fetch transfer by ID %d: %+v", id, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data transfer") } return transferPtr, nil @@ -142,7 +143,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk dengan ID %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengecek stok produk %d di gudang asal", product.ProductID)) + s.Log.Errorf("Failed to fetch product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.SourceWarehouseID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengecek stok produk") } if sourcePW.Quantity < product.ProductQty { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak mencukupi. Tersedia: %.2f, Diminta: %.2f", product.ProductID, sourcePW.Quantity, product.ProductQty)) @@ -163,12 +165,15 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques return nil, err } - projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID) - if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock untuk gudang tujuan") - } - if projectFlockKandang.ClosedAt != nil { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02"))) + if destPfkID > 0 { + projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID) + if err != nil { + s.Log.Errorf("Failed to fetch project flock kandang by ID %d: %+v", destPfkID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock") + } + if projectFlockKandang.ClosedAt != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02"))) + } } actorID, err := m.ActorIDFromContext(c) @@ -196,7 +201,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier dengan ID %d tidak ditemukan", delivery.SupplierID)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data supplier dengan ID %d", delivery.SupplierID)) + s.Log.Errorf("Failed to fetch supplier by ID %d: %+v", delivery.SupplierID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data supplier") } if supplier.Category != string(utils.SupplierCategoryBOP) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier '%s' (ID: %d) bukan kategori BOP. Kategori saat ini: %s", supplier.Name, delivery.SupplierID, supplier.Category)) @@ -205,7 +211,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques movementNumber, err := s.StockTransferRepo.GenerateMovementNumber(c.Context()) if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor movement transfer") + s.Log.Errorf("Failed to generate movement number: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor transfer") } transferDate, _ := utils.ParseDateString(req.TransferDate) @@ -245,14 +252,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID)) } - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang asal", product.ProductID)) + s.Log.Errorf("Failed to fetch source product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.SourceWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang asal") } destPW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID( c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID), ) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang tujuan", product.ProductID)) + s.Log.Errorf("Failed to fetch dest product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang tujuan") } if errors.Is(err, gorm.ErrRecordNotFound) { ctx := c.Context() @@ -261,7 +270,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques return err } - // Set ProjectFlockKandangId hanya jika ada kandang var pfkID *uint if projectFlockKandangID > 0 { pfkID = &projectFlockKandangID @@ -274,7 +282,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques ProjectFlockKandangId: pfkID, } if err := productWarehouseRepoTX.CreateOne(c.Context(), destPW, nil); err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk produk %d di gudang tujuan", product.ProductID)) + s.Log.Errorf("Failed to create product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat data stok gudang tujuan") } } @@ -364,9 +373,9 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Files: documentFiles, }) if err != nil { - s.Log.WithError(err).Errorf("Failed to upload document for delivery %d (delivery_id: %d, filename: %s)", - deliveryIdx+1, delivery.Id, file.Filename) - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to upload document for delivery %d: %v", deliveryIdx+1, err)) + s.Log.Errorf("Failed to upload document for delivery %d (delivery_id=%d, filename=%s): %+v", + deliveryIdx+1, delivery.Id, file.Filename, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengunggah dokumen") } } } @@ -392,7 +401,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques "usage_qty": consumeResult.UsageQuantity, "pending_qty": consumeResult.PendingQuantity, }).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking usage untuk produk %d", product.ProductID)) + s.Log.Errorf("Failed to update tracking usage for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking") } note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber) @@ -405,7 +415,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Tx: tx, }) if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menambah stok untuk produk %d di gudang tujuan. Error: %v", product.ProductID, err)) + s.Log.Errorf("Failed to replenish stock for product_id=%d, pw_id=%d, qty=%.2f: %+v", product.ProductID, *detail.DestProductWarehouseID, product.ProductQty, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal menambah stok gudang tujuan") } if err := tx.Model(&entity.StockTransferDetail{}). @@ -413,7 +424,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Updates(map[string]interface{}{ "total_qty": replenishResult.AddedQuantity, }).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking total untuk produk %d", product.ProductID)) + s.Log.Errorf("Failed to update tracking total for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking") } } @@ -447,7 +459,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques }) if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal memproses transfer. Error: %v", err)) + if fiberErr, ok := err.(*fiber.Error); ok { + return nil, fiberErr + } + return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal server error") } result, err := s.GetOne(c, uint(entityTransfer.Id)) @@ -457,7 +472,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if len(expensePayloads) > 0 { if err := s.notifyExpenseItemsDelivered(c, entityTransfer.Id, expensePayloads); err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal sinkronisasi data expense untuk transfer %s. Silakan cek manual di module expense", entityTransfer.MovementNumber)) + s.Log.Errorf("Failed to sync expense for transfer_id=%d, movement_number=%s: %+v", entityTransfer.Id, entityTransfer.MovementNumber, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal sinkronisasi data expense. Silakan cek manual di module expense") } } @@ -477,10 +493,10 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa if errors.Is(err, gorm.ErrRecordNotFound) { return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID)) } - return 0, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data gudang dengan ID %d", warehouseID)) + s.Log.Errorf("Failed to fetch warehouse by ID %d: %+v", warehouseID, err) + return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang") } - // Jika warehouse tidak punya kandang_id, return 0 tanpa error if warehouse.KandangId == nil || *warehouse.KandangId == 0 { return 0, nil } @@ -490,7 +506,8 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa if errors.Is(err, gorm.ErrRecordNotFound) { return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak ada project flock aktif untuk kandang %d", *warehouse.KandangId)) } - return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock kandang yang aktif") + s.Log.Errorf("Failed to fetch active project flock kandang for kandang_id=%d: %+v", *warehouse.KandangId, err) + return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock") } return uint(projectFlockKandang.Id), nil diff --git a/internal/modules/marketing/module.go b/internal/modules/marketing/module.go index b93c6129..2f8ea4fb 100644 --- a/internal/modules/marketing/module.go +++ b/internal/modules/marketing/module.go @@ -16,6 +16,7 @@ import ( rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -32,6 +33,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate userRepo := rUser.NewUserRepository(db) customerRepo := rCustomer.NewCustomerRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) + stockLogRepo := rShared.NewStockLogRepository(db) stockAllocationRepo := commonRepo.NewStockAllocationRepository(db) fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log) @@ -63,7 +65,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db) salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, warehouseRepo, projectFlockKandangRepo, validate) - deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, fifoService, validate) + deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, stockLogRepo, approvalSvc, fifoService, validate) userService := sUser.NewUserService(userRepo, validate) RegisterRoutes(router, userService, salesOrdersService, deliveryOrdersService) diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index b4e3eea0..7e60662d 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -14,6 +14,7 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" + rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" @@ -34,6 +35,7 @@ type deliveryOrdersService struct { MarketingRepo marketingRepo.MarketingRepository MarketingProductRepo marketingRepo.MarketingProductRepository MarketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository + StockLogRepo rShared.StockLogRepository ApprovalSvc commonSvc.ApprovalService FifoSvc commonSvc.FifoService } @@ -42,6 +44,7 @@ func NewDeliveryOrdersService( marketingRepo marketingRepo.MarketingRepository, marketingProductRepo marketingRepo.MarketingProductRepository, marketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository, + stockLogRepo rShared.StockLogRepository, approvalSvc commonSvc.ApprovalService, fifoSvc commonSvc.FifoService, validate *validator.Validate, @@ -51,6 +54,7 @@ func NewDeliveryOrdersService( MarketingRepo: marketingRepo, MarketingProductRepo: marketingProductRepo, MarketingDeliveryProductRepo: marketingDeliveryProductRepo, + StockLogRepo: stockLogRepo, ApprovalSvc: approvalSvc, FifoSvc: fifoSvc, } @@ -247,7 +251,6 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery itemDeliveryDate = &parsedDate } - // Cek apakah product punya flag PAKAN atau OVK isPakanOrOVK := false if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 { for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags { @@ -258,14 +261,13 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price + totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice } else { - // Produk lain: total_weight × unit_price + totalPrice = totalWeight * requestedProduct.UnitPrice } @@ -279,7 +281,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery if requestedProduct.Qty > 0 { - if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil { + if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil { return err } } @@ -327,7 +329,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return nil, err } - err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + + err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction) marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction) @@ -390,14 +397,13 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price + totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice } else { - // Produk lain: total_weight × unit_price + totalPrice = totalWeight * requestedProduct.UnitPrice } @@ -412,13 +418,13 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO if requestedProduct.Qty != oldRequestedQty { if oldRequestedQty > 0 { - if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct); err != nil { + if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, actorID); err != nil { return err } } if requestedProduct.Qty > 0 { - if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil { + if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil { return err } } @@ -443,7 +449,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return s.getMarketingWithDeliveries(c, id) } -func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64) error { +func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error { if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 { return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found") } @@ -463,6 +469,20 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx) + if err == nil && result.UsageQuantity > 0 { + if actorID > 0 { + decreaseLog := &entity.StockLog{ + Decrease: result.UsageQuantity, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil) + } + } + if err != nil { pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx) pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil) @@ -483,6 +503,19 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product") } + + if actorID > 0 { + decreaseLog := &entity.StockLog{ + Decrease: requestedQty, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil) + } + return nil } @@ -493,7 +526,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor return nil } -func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct) error { +func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, actorID uint) error { if deliveryProduct == nil || deliveryProduct.Id == 0 { return nil } @@ -520,6 +553,18 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor return err } + if actorID > 0 && currentUsage > 0 { + increaseLog := &entity.StockLog{ + Increase: currentUsage, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil) + } + if err := deliveryProductRepo.ResetFifoFields(ctx, deliveryProduct.Id); err != nil { return err } diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index e2cfcabb..dbf99219 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -311,14 +311,11 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := rp.Qty * rp.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price totalPrice = rp.Qty * rp.UnitPrice } else { - // Produk lain: total_weight × unit_price totalPrice = totalWeight * rp.UnitPrice } diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index 336b6576..b12fdfeb 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -190,16 +190,13 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalWeightKg += item.TotalWeightKg totalSalesAmount += int64(item.SalesAmount) totalHppAmount += int64(item.HppAmount) - avgSalesPrice += item.SalesPricePerKg } totalHppPricePerKg := float64(0) + if totalWeightKg > 0 { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg - } - - if len(items) > 0 { - avgSalesPrice = avgSalesPrice / float64(len(items)) + avgSalesPrice = float64(totalSalesAmount) / totalWeightKg } if totalQty > 0 { diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 9c976138..2b6ab745 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -218,8 +218,23 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing projectFlockIDMap[projectFlockID] = true category := projectFlockKandang.ProjectFlock.Category - hppPerKg := s.calculateHppPricePerKg(c.Context(), projectFlockID, category) - hppMap[projectFlockID] = hppPerKg + if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryLaying { + if s.HppSvc != nil { + hppCost, err := s.HppSvc.CalculateHppCost(projectFlockID, nil) + if err != nil { + hppMap[projectFlockID] = 0.0 + } else if hppCost != nil { + hppMap[projectFlockID] = hppCost.Real.HargaKg + } else { + hppMap[projectFlockID] = 0.0 + } + } else { + hppMap[projectFlockID] = 0.0 + } + } else { + + hppMap[projectFlockID] = 0.0 + } } } } @@ -228,81 +243,6 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing return items, total, nil } -func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFlockID uint, category string) float64 { - totalCost := s.getTotalProjectCost(ctx, projectFlockID) - if totalCost == 0 { - return 0 - } - - chickinQty, err := s.ChickinRepo.GetTotalChickinQtyByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get chickin qty for project flock ID %d: %v", projectFlockID, err) - } - - depletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get depletion for project flock ID %d: %v", projectFlockID, err) - } - - avgWeight, err := s.RecordingRepo.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get avg weight for project flock ID %d: %v", projectFlockID, err) - } - - var totalWeight float64 - if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryGrowing { - totalWeight = (chickinQty - depletion) * avgWeight - } else { - eggWeight, err := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get egg weight for project flock ID %d: %v", projectFlockID, err) - } - totalWeight = (chickinQty-depletion)*avgWeight + eggWeight - } - - if totalWeight == 0 { - return 0 - } - - hppPricePerKg := totalCost / totalWeight - return hppPricePerKg -} - -func (s *repportService) getTotalProjectCost(ctx context.Context, projectFlockID uint) float64 { - if projectFlockID == 0 { - return 0 - } - - purchases, err := s.PurchaseRepo.GetItemsByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Errorf("getTotalProjectCost: GetItemsByProjectFlockID error for project flock ID %d: %v", projectFlockID, err) - return 0 - } - - cost := float64(0) - purchaseCost := float64(0) - for _, p := range purchases { - purchaseCost += p.TotalPrice - } - cost += purchaseCost - - realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("getTotalProjectCost: GetByProjectFlockID error for project flock ID %d: %v", projectFlockID, err) - } - - bopCost := float64(0) - for _, r := range realizations { - if r.ExpenseNonstock != nil && r.ExpenseNonstock.Expense != nil && - r.ExpenseNonstock.Expense.Category == string(utils.ExpenseCategoryBOP) { - bopCost += r.Price * r.Qty - } - } - cost += bopCost - - return cost -} - func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.ProductionResultQuery) ([]dto.ProductionResultDTO, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err