feat(BE): fixing wrong perhitungan biaya

This commit is contained in:
aguhh18
2026-01-09 09:15:53 +07:00
parent 76e65704d7
commit b11f03dfda
5 changed files with 62 additions and 49 deletions
@@ -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
@@ -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)
@@ -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,
})
}
@@ -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)
}
@@ -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).