mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE): fixing wrong perhitungan biaya
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
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"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -914,9 +915,8 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C
|
|||||||
|
|
||||||
var rows []ActualUsageCostRow
|
var rows []ActualUsageCostRow
|
||||||
|
|
||||||
// Part 1: Get usage from recording_stocks (PAKAN, OVK, Vitamin, Obat, Kimia, dll)
|
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
|
||||||
purchaseStockableKey := "PURCHASE_ITEMS"
|
transferStockableKey := fifo.StockableKeyStockTransferIn.String()
|
||||||
transferStockableKey := "STOCK_TRANSFER_DETAILS"
|
|
||||||
|
|
||||||
recordingQuery := db.
|
recordingQuery := db.
|
||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
@@ -982,7 +982,6 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part 2: Get usage from project_chickins (DOC, Pullet)
|
|
||||||
chickinQuery := db.
|
chickinQuery := db.
|
||||||
Table("project_chickins AS pc").
|
Table("project_chickins AS pc").
|
||||||
Select(`
|
Select(`
|
||||||
@@ -1006,7 +1005,6 @@ func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.C
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge results
|
|
||||||
rows = append(rows, chickinRows...)
|
rows = append(rows, chickinRows...)
|
||||||
|
|
||||||
return rows, nil
|
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) {
|
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(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: func(ctx context.Context, id uint) (bool, error) {
|
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: s.ProjectFlockRepo.IdExists},
|
||||||
_, err := s.ProjectFlockRepo.GetByID(ctx, id, nil)
|
|
||||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return err == nil, err
|
|
||||||
}},
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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")
|
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)
|
actualUsageRows, err := s.Repository.GetActualUsageCostByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch actual usage cost")
|
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)
|
purchaseItems := s.convertActualUsageToPurchaseItems(c.Context(), actualUsageRows)
|
||||||
|
|
||||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ type TransferExpenseReceivingPayload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type groupedTransferItem struct {
|
type groupedTransferItem struct {
|
||||||
detail *entity.StockTransferDetail
|
detail *entity.StockTransferDetail
|
||||||
payload TransferExpenseReceivingPayload
|
payload TransferExpenseReceivingPayload
|
||||||
projectFK *uint
|
projectFK *uint
|
||||||
kandangID *uint
|
kandangID *uint
|
||||||
totalPrice float64
|
totalPrice float64
|
||||||
|
shippingCostTotal float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func groupingKey(supplierID uint, date time.Time, warehouseID uint) string {
|
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{})
|
expenseIDs := make(map[uint64]struct{})
|
||||||
expenseNonstockIDs := make([]uint64, 0)
|
expenseNonstockIDs := make([]uint64, 0)
|
||||||
|
|
||||||
// Collect expense nonstock IDs from items
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 {
|
if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 {
|
||||||
expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId)
|
expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId)
|
||||||
@@ -91,7 +92,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(expenseNonstockIDs) > 0 {
|
if len(expenseNonstockIDs) > 0 {
|
||||||
// Get expense IDs from expense nonstocks
|
|
||||||
for _, nsID := range expenseNonstockIDs {
|
for _, nsID := range expenseNonstockIDs {
|
||||||
var expenseID uint64
|
var expenseID uint64
|
||||||
if err := tx.Model(&entity.ExpenseNonstock{}).
|
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 {
|
if err := tx.Delete(&entity.ExpenseNonstock{}, expenseNonstockIDs).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check remaining expense nonstocks for each expense
|
|
||||||
approvalRepoTx := commonRepo.NewApprovalRepository(tx)
|
approvalRepoTx := commonRepo.NewApprovalRepository(tx)
|
||||||
for expenseID := range expenseIDs {
|
for expenseID := range expenseIDs {
|
||||||
var count int64
|
var count int64
|
||||||
@@ -121,7 +122,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no more expense nonstocks, delete expense and approval
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil {
|
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -208,7 +209,7 @@ func (b *transferExpenseBridge) createExpenseViaService(
|
|||||||
id := uint64(*kandangID)
|
id := uint64(*kandangID)
|
||||||
expenseKandangID = &id
|
expenseKandangID = &id
|
||||||
} else {
|
} else {
|
||||||
// For transfer, use destination warehouse location
|
|
||||||
if transfer.ToWarehouse == nil || transfer.ToWarehouse.LocationId == nil || *transfer.ToWarehouse.LocationId == 0 {
|
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")
|
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))
|
costItems := make([]expenseValidation.CostItem, 0, len(items))
|
||||||
for _, gi := range items {
|
for _, gi := range items {
|
||||||
note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id)
|
note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id)
|
||||||
price := gi.detail.TotalQty
|
|
||||||
|
|
||||||
|
price := gi.shippingCostTotal
|
||||||
if gi.payload.TransportPerItem != nil {
|
if gi.payload.TransportPerItem != nil {
|
||||||
price = *gi.payload.TransportPerItem
|
price = *gi.payload.TransportPerItem * gi.payload.DeliveredQty
|
||||||
}
|
}
|
||||||
|
|
||||||
costItems = append(costItems, expenseValidation.CostItem{
|
costItems = append(costItems, expenseValidation.CostItem{
|
||||||
NonstockID: expeditionNonstockID,
|
NonstockID: expeditionNonstockID,
|
||||||
Quantity: gi.payload.DeliveredQty,
|
Quantity: 1,
|
||||||
Price: price,
|
Price: price,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
})
|
})
|
||||||
@@ -247,7 +251,7 @@ func (b *transferExpenseBridge) createExpenseViaService(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark approvals up to Finance so latest is Manager Finance
|
|
||||||
action := entity.ApprovalActionApproved
|
action := entity.ApprovalActionApproved
|
||||||
actorID := uint(transfer.CreatedBy)
|
actorID := uint(transfer.CreatedBy)
|
||||||
if actorID == 0 {
|
if actorID == 0 {
|
||||||
@@ -324,12 +328,14 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
|
|||||||
|
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
|
|
||||||
// Load transfer with details
|
|
||||||
transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB {
|
transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB {
|
||||||
return db.
|
return db.
|
||||||
Preload("Details").
|
Preload("Details").
|
||||||
Preload("Details.Product").
|
Preload("Details.Product").
|
||||||
Preload("Details.DestProductWarehouse").
|
Preload("Details.DestProductWarehouse").
|
||||||
|
Preload("Details.DeliveryItems").
|
||||||
|
Preload("Details.DeliveryItems.StockTransferDelivery").
|
||||||
Preload("ToWarehouse")
|
Preload("ToWarehouse")
|
||||||
})
|
})
|
||||||
if err != nil {
|
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))
|
detailMap := make(map[uint64]*entity.StockTransferDetail, len(transfer.Details))
|
||||||
|
shippingCostMap := make(map[uint64]float64) // detailID -> ShippingCostTotal
|
||||||
|
|
||||||
for i := range transfer.Details {
|
for i := range transfer.Details {
|
||||||
detailMap[transfer.Details[i].Id] = &transfer.Details[i]
|
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)
|
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 {
|
shippingCostTotal := shippingCostMap[detail.Id]
|
||||||
pricePerItem = *payload.TransportPerItem
|
|
||||||
}
|
|
||||||
totalPrice := pricePerItem * payload.DeliveredQty
|
totalPrice := shippingCostTotal
|
||||||
|
if payload.TransportPerItem != nil {
|
||||||
|
|
||||||
|
totalPrice = *payload.TransportPerItem * payload.DeliveredQty
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Group by supplier:date:warehouse
|
|
||||||
warehouseID := uint(payload.WarehouseID)
|
warehouseID := uint(payload.WarehouseID)
|
||||||
if warehouseID == 0 && transfer.ToWarehouse != nil {
|
if warehouseID == 0 && transfer.ToWarehouse != nil {
|
||||||
warehouseID = uint(transfer.ToWarehouse.Id)
|
warehouseID = uint(transfer.ToWarehouse.Id)
|
||||||
@@ -396,11 +416,12 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
|
|||||||
|
|
||||||
key := groupingKey(uint(supplierID), deliveredDate, warehouseID)
|
key := groupingKey(uint(supplierID), deliveredDate, warehouseID)
|
||||||
groups[key] = append(groups[key], groupedTransferItem{
|
groups[key] = append(groups[key], groupedTransferItem{
|
||||||
detail: detail,
|
detail: detail,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
projectFK: projectFK,
|
projectFK: projectFK,
|
||||||
kandangID: kandangID,
|
kandangID: kandangID,
|
||||||
totalPrice: totalPrice,
|
totalPrice: totalPrice,
|
||||||
|
shippingCostTotal: shippingCostTotal,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type ProjectflockRepository interface {
|
|||||||
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
|
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
|
||||||
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
|
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
|
||||||
GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, 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)
|
AreaExists(ctx context.Context, id uint) (bool, error)
|
||||||
FcrExists(ctx context.Context, id uint) (bool, error)
|
FcrExists(ctx context.Context, id uint) (bool, error)
|
||||||
ProductionStandardExists(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) {
|
func (r *ProjectflockRepositoryImpl) AreaExists(ctx context.Context, id uint) (bool, error) {
|
||||||
return repository.Exists[entity.Area](ctx, r.DB(), id)
|
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)
|
recordingPfk = applyLocationFilters(recordingPfk, areaIDs, locationIDs, kandangIDs)
|
||||||
|
|
||||||
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
|
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
|
||||||
transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_DETAILS").String()
|
transferStockableKey := fifo.StockableKeyStockTransferIn.String()
|
||||||
|
|
||||||
query := r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
Select(`
|
Select(`
|
||||||
k.id AS kandang_id,
|
k.id AS kandang_id,
|
||||||
COALESCE(SUM(CASE
|
COALESCE(SUM(CASE
|
||||||
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
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)
|
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,
|
END), 0) AS feed_cost,
|
||||||
COALESCE(SUM(CASE
|
COALESCE(SUM(CASE
|
||||||
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
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)
|
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`,
|
END), 0) AS ovk_cost`,
|
||||||
utils.FlagPakan, transferStockableKey, utils.FlagPakan,
|
utils.FlagPakan, transferStockableKey, utils.FlagPakan,
|
||||||
utils.FlagOVK, transferStockableKey, utils.FlagOVK).
|
utils.FlagOVK, transferStockableKey, utils.FlagOVK).
|
||||||
|
|||||||
Reference in New Issue
Block a user