diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index 4948ae5e..9d08d083 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -10,6 +10,7 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) @@ -914,9 +915,8 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C var rows []ActualUsageCostRow - // Part 1: Get usage from recording_stocks (PAKAN, OVK, Vitamin, Obat, Kimia, dll) - purchaseStockableKey := "PURCHASE_ITEMS" - transferStockableKey := "STOCK_TRANSFER_DETAILS" + purchaseStockableKey := fifo.StockableKeyPurchaseItems.String() + transferStockableKey := fifo.StockableKeyStockTransferIn.String() recordingQuery := db. Table("recordings AS r"). @@ -982,7 +982,6 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C return nil, err } - // Part 2: Get usage from project_chickins (DOC, Pullet) chickinQuery := db. Table("project_chickins AS pc"). Select(` @@ -1006,7 +1005,6 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C return nil, err } - // Merge results rows = append(rows, chickinRows...) return rows, nil diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index c3e3108d..a76085c4 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -413,18 +413,9 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.Ove } func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) { - if projectFlockID == 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") - } if err := commonSvc.EnsureRelations(c.Context(), - commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: func(ctx context.Context, id uint) (bool, error) { - _, err := s.ProjectFlockRepo.GetByID(ctx, id, nil) - if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { - return false, nil - } - return err == nil, err - }}, + commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: s.ProjectFlockRepo.IdExists}, ); err != nil { return nil, err } @@ -439,13 +430,11 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (* return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets") } - // Get actual usage cost instead of purchase items actualUsageRows, err := s.Repository.GetActualUsageCostByProjectFlockID(c.Context(), projectFlockID) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch actual usage cost") } - // Convert actual usage rows to pseudo purchase items purchaseItems := s.convertActualUsageToPurchaseItems(c.Context(), actualUsageRows) realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID) diff --git a/internal/modules/inventory/transfers/services/transfer_expense_bridge.go b/internal/modules/inventory/transfers/services/transfer_expense_bridge.go index b16cfd4f..90350c18 100644 --- a/internal/modules/inventory/transfers/services/transfer_expense_bridge.go +++ b/internal/modules/inventory/transfers/services/transfer_expense_bridge.go @@ -39,11 +39,12 @@ type TransferExpenseReceivingPayload struct { } type groupedTransferItem struct { - detail *entity.StockTransferDetail - payload TransferExpenseReceivingPayload - projectFK *uint - kandangID *uint - totalPrice float64 + detail *entity.StockTransferDetail + payload TransferExpenseReceivingPayload + projectFK *uint + kandangID *uint + totalPrice float64 + shippingCostTotal float64 } func groupingKey(supplierID uint, date time.Time, warehouseID uint) string { @@ -83,7 +84,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it expenseIDs := make(map[uint64]struct{}) expenseNonstockIDs := make([]uint64, 0) - // Collect expense nonstock IDs from items + for _, item := range items { if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 { expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId) @@ -91,7 +92,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it } if len(expenseNonstockIDs) > 0 { - // Get expense IDs from expense nonstocks + for _, nsID := range expenseNonstockIDs { var expenseID uint64 if err := tx.Model(&entity.ExpenseNonstock{}). @@ -105,13 +106,13 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it } } - // Delete expense nonstocks + if err := tx.Delete(&entity.ExpenseNonstock{}, expenseNonstockIDs).Error; err != nil { return err } } - // Check remaining expense nonstocks for each expense + approvalRepoTx := commonRepo.NewApprovalRepository(tx) for expenseID := range expenseIDs { var count int64 @@ -121,7 +122,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it return err } - // If no more expense nonstocks, delete expense and approval + if count == 0 { if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil { return err @@ -208,7 +209,7 @@ func (b *transferExpenseBridge) createExpenseViaService( id := uint64(*kandangID) expenseKandangID = &id } else { - // For transfer, use destination warehouse location + if transfer.ToWarehouse == nil || transfer.ToWarehouse.LocationId == nil || *transfer.ToWarehouse.LocationId == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "Destination warehouse location is required for expense") } @@ -218,13 +219,16 @@ func (b *transferExpenseBridge) createExpenseViaService( costItems := make([]expenseValidation.CostItem, 0, len(items)) for _, gi := range items { note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id) - price := gi.detail.TotalQty + + + price := gi.shippingCostTotal if gi.payload.TransportPerItem != nil { - price = *gi.payload.TransportPerItem + price = *gi.payload.TransportPerItem * gi.payload.DeliveredQty } + costItems = append(costItems, expenseValidation.CostItem{ NonstockID: expeditionNonstockID, - Quantity: gi.payload.DeliveredQty, + Quantity: 1, Price: price, Notes: note, }) @@ -247,7 +251,7 @@ func (b *transferExpenseBridge) createExpenseViaService( return nil, err } - // Mark approvals up to Finance so latest is Manager Finance + action := entity.ApprovalActionApproved actorID := uint(transfer.CreatedBy) if actorID == 0 { @@ -324,12 +328,14 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64 ctx := c.Context() - // Load transfer with details + transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB { return db. Preload("Details"). Preload("Details.Product"). Preload("Details.DestProductWarehouse"). + Preload("Details.DeliveryItems"). + Preload("Details.DeliveryItems.StockTransferDelivery"). Preload("ToWarehouse") }) if err != nil { @@ -337,8 +343,18 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64 } detailMap := make(map[uint64]*entity.StockTransferDetail, len(transfer.Details)) + shippingCostMap := make(map[uint64]float64) // detailID -> ShippingCostTotal + for i := range transfer.Details { detailMap[transfer.Details[i].Id] = &transfer.Details[i] + + + for _, deliveryItem := range transfer.Details[i].DeliveryItems { + if deliveryItem.StockTransferDelivery != nil { + shippingCostMap[transfer.Details[i].Id] = deliveryItem.StockTransferDelivery.ShippingCostTotal + break + } + } } groups := make(map[string][]groupedTransferItem) @@ -379,13 +395,17 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64 } } - pricePerItem := detail.TotalQty - if payload.TransportPerItem != nil { - pricePerItem = *payload.TransportPerItem - } - totalPrice := pricePerItem * payload.DeliveredQty + + shippingCostTotal := shippingCostMap[detail.Id] + + + totalPrice := shippingCostTotal + if payload.TransportPerItem != nil { + + totalPrice = *payload.TransportPerItem * payload.DeliveredQty + } + - // Group by supplier:date:warehouse warehouseID := uint(payload.WarehouseID) if warehouseID == 0 && transfer.ToWarehouse != nil { warehouseID = uint(transfer.ToWarehouse.Id) @@ -396,11 +416,12 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64 key := groupingKey(uint(supplierID), deliveredDate, warehouseID) groups[key] = append(groups[key], groupedTransferItem{ - detail: detail, - payload: payload, - projectFK: projectFK, - kandangID: kandangID, - totalPrice: totalPrice, + detail: detail, + payload: payload, + projectFK: projectFK, + kandangID: kandangID, + totalPrice: totalPrice, + shippingCostTotal: shippingCostTotal, }) } diff --git a/internal/modules/production/project_flocks/repositories/projectflock.repository.go b/internal/modules/production/project_flocks/repositories/projectflock.repository.go index 6cd98a8f..e65dfb4a 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock.repository.go @@ -20,6 +20,7 @@ type ProjectflockRepository interface { GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error) GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error) GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error) + IdExists(ctx context.Context, id uint) (bool, error) AreaExists(ctx context.Context, id uint) (bool, error) FcrExists(ctx context.Context, id uint) (bool, error) ProductionStandardExists(ctx context.Context, id uint) (bool, error) @@ -161,6 +162,10 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s ) } +func (r *ProjectflockRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) { + return repository.Exists[entity.ProjectFlock](ctx, r.DB(), id) +} + func (r *ProjectflockRepositoryImpl) AreaExists(ctx context.Context, id uint) (bool, error) { return repository.Exists[entity.Area](ctx, r.DB(), id) } diff --git a/internal/modules/repports/repositories/hpp_per_kandang.repository.go b/internal/modules/repports/repositories/hpp_per_kandang.repository.go index 6d4185e8..4bd9aab4 100644 --- a/internal/modules/repports/repositories/hpp_per_kandang.repository.go +++ b/internal/modules/repports/repositories/hpp_per_kandang.repository.go @@ -107,21 +107,21 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, recordingPfk = applyLocationFilters(recordingPfk, areaIDs, locationIDs, kandangIDs) purchaseStockableKey := fifo.StockableKeyPurchaseItems.String() - transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_DETAILS").String() + transferStockableKey := fifo.StockableKeyStockTransferIn.String() query := r.db.WithContext(ctx). Table("recordings AS r"). Select(` k.id AS kandang_id, - COALESCE(SUM(CASE + COALESCE(SUM(CASE WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0) - ELSE 0 + ELSE 0 END), 0) AS feed_cost, - COALESCE(SUM(CASE + COALESCE(SUM(CASE WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0) - ELSE 0 + ELSE 0 END), 0) AS ovk_cost`, utils.FlagPakan, transferStockableKey, utils.FlagPakan, utils.FlagOVK, transferStockableKey, utils.FlagOVK).