mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
Merge branch 'fix/be/response-check-closing' into 'development'
[FEAT/BE] fix response check closing See merge request mbugroup/lti-api!332
This commit is contained in:
@@ -65,7 +65,7 @@ type SapronakCategoryRowDTO struct {
|
|||||||
QtyOut float64 `json:"qty_out"`
|
QtyOut float64 `json:"qty_out"`
|
||||||
QtyUsed float64 `json:"qty_used"`
|
QtyUsed float64 `json:"qty_used"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ProductCategory []string `json:"product_category"`
|
ProductCategory string `json:"product_category"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
TotalAmount float64 `json:"total_amount"`
|
TotalAmount float64 `json:"total_amount"`
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
@@ -183,13 +183,13 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
"PULLET": 0,
|
"PULLET": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFlagList := func(productID uint, fallback string) []string {
|
buildFlagList := func(productID uint, fallback string) string {
|
||||||
rawFlags := productFlags[productID]
|
rawFlags := productFlags[productID]
|
||||||
if len(rawFlags) == 0 {
|
if len(rawFlags) == 0 {
|
||||||
if fallback == "" {
|
if fallback == "" {
|
||||||
return []string{}
|
return ""
|
||||||
}
|
}
|
||||||
return []string{fallback}
|
return fallback
|
||||||
}
|
}
|
||||||
seen := make(map[string]struct{}, len(rawFlags))
|
seen := make(map[string]struct{}, len(rawFlags))
|
||||||
ordered := make([]string, 0, len(rawFlags))
|
ordered := make([]string, 0, len(rawFlags))
|
||||||
@@ -220,7 +220,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
}
|
}
|
||||||
return li < lj
|
return li < lj
|
||||||
})
|
})
|
||||||
return ordered
|
return strings.Join(ordered, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, group := range report.Groups {
|
for _, group := range report.Groups {
|
||||||
@@ -317,6 +317,27 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For chicken categories, keep qty_used aligned with qty_in - qty_out.
|
||||||
|
// Sales are excluded; usage represents remaining after transfers.
|
||||||
|
adjustChicken := func(cat *SapronakCategoryDTO) {
|
||||||
|
if cat == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range cat.Rows {
|
||||||
|
row := &cat.Rows[i]
|
||||||
|
remaining := row.QtyIn - row.QtyOut
|
||||||
|
if remaining < 0 {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
row.QtyUsed = remaining
|
||||||
|
if row.UnitPrice > 0 {
|
||||||
|
row.TotalAmount = row.QtyUsed * row.UnitPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjustChicken(result.Doc)
|
||||||
|
adjustChicken(result.Pullet)
|
||||||
|
|
||||||
buildTotals := func(cat *SapronakCategoryDTO, label string) {
|
buildTotals := func(cat *SapronakCategoryDTO, label string) {
|
||||||
if cat == nil {
|
if cat == nil {
|
||||||
return
|
return
|
||||||
@@ -345,5 +366,22 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
buildTotals(result.Doc, "TOTAL DOC")
|
buildTotals(result.Doc, "TOTAL DOC")
|
||||||
buildTotals(result.Ovk, "TOTAL OVK")
|
buildTotals(result.Ovk, "TOTAL OVK")
|
||||||
buildTotals(result.Pakan, "TOTAL PAKAN")
|
buildTotals(result.Pakan, "TOTAL PAKAN")
|
||||||
|
|
||||||
|
// For chicken categories, enforce total qty_used = qty_in - qty_out.
|
||||||
|
adjustChickenTotal := func(cat *SapronakCategoryDTO) {
|
||||||
|
if cat == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining := cat.Total.QtyIn - cat.Total.QtyOut
|
||||||
|
if remaining < 0 {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
cat.Total.QtyUsed = remaining
|
||||||
|
if cat.Total.AvgUnitPrice > 0 {
|
||||||
|
cat.Total.TotalAmount = cat.Total.AvgUnitPrice * remaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjustChickenTotal(result.Doc)
|
||||||
|
adjustChickenTotal(result.Pullet)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1029,17 +1029,18 @@ func (r *ClosingRepositoryImpl) FetchSapronakUsageAllocatedDetails(ctx context.C
|
|||||||
Joins("LEFT JOIN adjustment_stocks ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()).
|
Joins("LEFT JOIN adjustment_stocks ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()).
|
||||||
Joins("LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
Joins("LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
||||||
Joins("LEFT JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
Joins("LEFT JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
||||||
Joins("LEFT JOIN products p_resolve ON p_resolve.id = COALESCE(pi.product_id, pw_ltt.product_id, pw.product_id)").
|
Joins("LEFT JOIN product_warehouses pw_pc ON pw_pc.id = pc.product_warehouse_id").
|
||||||
|
Joins("LEFT JOIN products p_resolve ON p_resolve.id = COALESCE(pi.product_id, pw_ltt.product_id, pw_pc.product_id, pw.product_id)").
|
||||||
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
||||||
Where("sa.stockable_type <> ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
Where("sa.stockable_type <> ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
||||||
Where("f.name IN ?", sapronakFlagsAll).
|
Where("f.name IN ?", sapronakFlagsAll).
|
||||||
Where(`
|
Where(`
|
||||||
(sa.usable_type = ? AND r.project_flock_kandangs_id = ?)
|
(sa.usable_type = ? AND r.project_flock_kandangs_id = ? AND f.name IN ?)
|
||||||
OR
|
OR
|
||||||
(sa.usable_type = ? AND pc_used.project_flock_kandang_id = ?)
|
(sa.usable_type = ? AND pc_used.project_flock_kandang_id = ? AND f.name IN ?)
|
||||||
`,
|
`,
|
||||||
fifo.UsableKeyRecordingStock.String(), projectFlockKandangID,
|
fifo.UsableKeyRecordingStock.String(), projectFlockKandangID, sapronakFlagsUsage,
|
||||||
fifo.UsableKeyProjectChickin.String(), projectFlockKandangID,
|
fifo.UsableKeyProjectChickin.String(), projectFlockKandangID, sapronakFlagsChickin,
|
||||||
)
|
)
|
||||||
query = r.joinSapronakProductFlag(query, "p_resolve").
|
query = r.joinSapronakProductFlag(query, "p_resolve").
|
||||||
Group(`
|
Group(`
|
||||||
@@ -1447,51 +1448,90 @@ func (r *ClosingRepositoryImpl) FetchSapronakSalesAllocatedDetails(ctx context.C
|
|||||||
return map[uint][]SapronakDetailRow{}, nil
|
return map[uint][]SapronakDetailRow{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pfpType := fifo.StockableKeyProjectFlockPopulation.String()
|
||||||
|
|
||||||
query := r.withCtx(ctx).
|
query := r.withCtx(ctx).
|
||||||
Table("stock_allocations AS sa").
|
Table("stock_allocations AS sa").
|
||||||
Select(`
|
Select(fmt.Sprintf(`
|
||||||
pw.product_id AS product_id,
|
p_resolve.id AS product_id,
|
||||||
p.name AS product_name,
|
p_resolve.name AS product_name,
|
||||||
f.name AS flag,
|
f.name AS flag,
|
||||||
COALESCE(
|
CASE
|
||||||
|
WHEN sa.stockable_type = '%s' THEN COALESCE(
|
||||||
|
pi_pc.received_date,
|
||||||
|
st_pc.transfer_date,
|
||||||
|
lt_pc.transfer_date,
|
||||||
|
ast_pc.created_at,
|
||||||
|
pc.chick_in_date
|
||||||
|
)
|
||||||
|
ELSE COALESCE(
|
||||||
pi.received_date,
|
pi.received_date,
|
||||||
st.transfer_date,
|
st.transfer_date,
|
||||||
lt.transfer_date,
|
lt.transfer_date,
|
||||||
ast.created_at
|
ast.created_at
|
||||||
) AS date,
|
)
|
||||||
COALESCE(
|
END AS date,
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = '%s' THEN COALESCE(
|
||||||
|
po_pc.po_number,
|
||||||
|
st_pc.movement_number,
|
||||||
|
lt_pc.transfer_number,
|
||||||
|
CASE WHEN ast_pc.id IS NOT NULL THEN CONCAT('ADJ-', ast_pc.id) END,
|
||||||
|
CONCAT('CHICKIN-', pc.id),
|
||||||
|
''
|
||||||
|
)
|
||||||
|
ELSE COALESCE(
|
||||||
po.po_number,
|
po.po_number,
|
||||||
st.movement_number,
|
st.movement_number,
|
||||||
lt.transfer_number,
|
lt.transfer_number,
|
||||||
CONCAT('ADJ-', ast.id),
|
CASE WHEN ast.id IS NOT NULL THEN CONCAT('ADJ-', ast.id) END,
|
||||||
''
|
''
|
||||||
) AS reference,
|
)
|
||||||
|
END AS reference,
|
||||||
0 AS qty_in,
|
0 AS qty_in,
|
||||||
COALESCE(SUM(sa.qty), 0) AS qty_out,
|
COALESCE(SUM(sa.qty), 0) AS qty_out,
|
||||||
COALESCE(pi.price, p.product_price, 0) AS price
|
CASE
|
||||||
`).
|
WHEN sa.stockable_type = '%s' THEN COALESCE(pi_pc.price, p_resolve.product_price, 0)
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = sa.product_warehouse_id").
|
ELSE COALESCE(pi.price, p_resolve.product_price, 0)
|
||||||
Joins("JOIN products p ON p.id = pw.product_id").
|
END AS price
|
||||||
|
`, pfpType, pfpType, pfpType)).
|
||||||
Joins("JOIN marketing_delivery_products mdp ON mdp.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyMarketingDelivery.String()).
|
Joins("JOIN marketing_delivery_products mdp ON mdp.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyMarketingDelivery.String()).
|
||||||
|
Joins("JOIN product_warehouses pw_sales ON pw_sales.id = mdp.product_warehouse_id").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = sa.product_warehouse_id").
|
||||||
Joins("LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
|
Joins("LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
|
||||||
Joins("LEFT JOIN purchases po ON po.id = pi.purchase_id").
|
Joins("LEFT JOIN purchases po ON po.id = pi.purchase_id").
|
||||||
Joins("LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyStockTransferIn.String()).
|
Joins("LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyStockTransferIn.String()).
|
||||||
Joins("LEFT JOIN stock_transfers st ON st.id = std.stock_transfer_id").
|
Joins("LEFT JOIN stock_transfers st ON st.id = std.stock_transfer_id").
|
||||||
Joins("LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()).
|
Joins("LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()).
|
||||||
Joins("LEFT JOIN laying_transfers lt ON lt.id = ltt.laying_transfer_id").
|
Joins("LEFT JOIN laying_transfers lt ON lt.id = ltt.laying_transfer_id").
|
||||||
|
Joins("LEFT JOIN product_warehouses pw_ltt ON pw_ltt.id = ltt.product_warehouse_id").
|
||||||
Joins("LEFT JOIN adjustment_stocks ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()).
|
Joins("LEFT JOIN adjustment_stocks ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()).
|
||||||
|
Joins("LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
||||||
|
Joins("LEFT JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
||||||
|
Joins("LEFT JOIN stock_allocations sa_pc ON sa_pc.usable_type = ? AND sa_pc.usable_id = pc.id", fifo.UsableKeyProjectChickin.String()).
|
||||||
|
Joins("LEFT JOIN purchase_items pi_pc ON pi_pc.id = sa_pc.stockable_id AND sa_pc.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
|
||||||
|
Joins("LEFT JOIN purchases po_pc ON po_pc.id = pi_pc.purchase_id").
|
||||||
|
Joins("LEFT JOIN stock_transfer_details std_pc ON std_pc.id = sa_pc.stockable_id AND sa_pc.stockable_type = ?", fifo.StockableKeyStockTransferIn.String()).
|
||||||
|
Joins("LEFT JOIN stock_transfers st_pc ON st_pc.id = std_pc.stock_transfer_id").
|
||||||
|
Joins("LEFT JOIN laying_transfer_targets ltt_pc ON ltt_pc.id = sa_pc.stockable_id AND sa_pc.stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()).
|
||||||
|
Joins("LEFT JOIN laying_transfers lt_pc ON lt_pc.id = ltt_pc.laying_transfer_id").
|
||||||
|
Joins("LEFT JOIN adjustment_stocks ast_pc ON ast_pc.id = sa_pc.stockable_id AND sa_pc.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()).
|
||||||
|
Joins("LEFT JOIN product_warehouses pw_pc ON pw_pc.id = pc.product_warehouse_id").
|
||||||
|
Joins(fmt.Sprintf("LEFT JOIN products p_resolve ON p_resolve.id = CASE WHEN sa.stockable_type = '%s' THEN pw_pc.product_id ELSE COALESCE(pi.product_id, pw_ltt.product_id, pw.product_id) END", pfpType)).
|
||||||
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
||||||
Where("sa.stockable_type <> ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
Where("sa.stockable_type <> ?", fifo.StockableKeyRecordingEgg.String()).
|
||||||
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
Where("pw_sales.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
Where("f.name IN ?", sapronakFlagsAll).
|
Where("f.name IN ?", sapronakFlagsAll).
|
||||||
Group(`
|
Group(`
|
||||||
pw.product_id, p.name, f.name,
|
p_resolve.id, p_resolve.name, f.name,
|
||||||
|
pi_pc.received_date, st_pc.transfer_date, lt_pc.transfer_date, ast_pc.created_at, pc.chick_in_date,
|
||||||
pi.received_date, st.transfer_date, lt.transfer_date, ast.created_at,
|
pi.received_date, st.transfer_date, lt.transfer_date, ast.created_at,
|
||||||
|
po_pc.po_number, st_pc.movement_number, lt_pc.transfer_number, ast_pc.id, pc.id,
|
||||||
po.po_number, st.movement_number, lt.transfer_number, ast.id,
|
po.po_number, st.movement_number, lt.transfer_number, ast.id,
|
||||||
pi.price, p.product_price
|
pi_pc.price, pi.price, p_resolve.product_price, sa.stockable_type
|
||||||
`)
|
`)
|
||||||
|
|
||||||
query = r.joinSapronakProductFlag(query, "p")
|
query = r.joinSapronakProductFlag(query, "p_resolve")
|
||||||
return scanAndGroupDetails(query)
|
return scanAndGroupDetails(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -470,6 +470,7 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
// should not be counted yet. Only when category is LAYING we allow
|
// should not be counted yet. Only when category is LAYING we allow
|
||||||
// pullet usage to contribute to qty_used.
|
// pullet usage to contribute to qty_used.
|
||||||
isLaying := strings.EqualFold(string(pfk.ProjectFlock.Category), string(utils.ProjectFlockCategoryLaying))
|
isLaying := strings.EqualFold(string(pfk.ProjectFlock.Category), string(utils.ProjectFlockCategoryLaying))
|
||||||
|
hasChickin := len(pfk.Chickins) > 0
|
||||||
|
|
||||||
if !isLaying {
|
if !isLaying {
|
||||||
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
||||||
@@ -775,6 +776,9 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
if !matchesFlag(flag) {
|
if !matchesFlag(flag) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if hasChickin && (strings.EqualFold(flag, "DOC") || strings.EqualFold(flag, "PULLET") || strings.EqualFold(flag, "LAYER")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
group := ensureGroup(flag)
|
group := ensureGroup(flag)
|
||||||
for _, d := range details {
|
for _, d := range details {
|
||||||
if d.Flag == "" {
|
if d.Flag == "" {
|
||||||
@@ -794,6 +798,10 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
if !matchesFlag(flag) {
|
if !matchesFlag(flag) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// For chicken, we don't count sales as sapronak outflow.
|
||||||
|
if strings.EqualFold(flag, "DOC") || strings.EqualFold(flag, "PULLET") || strings.EqualFold(flag, "LAYER") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
group := ensureGroup(flag)
|
group := ensureGroup(flag)
|
||||||
for _, d := range details {
|
for _, d := range details {
|
||||||
if d.Flag == "" {
|
if d.Flag == "" {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -117,18 +117,30 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
Preload("Products.DeliveryProduct")
|
Preload("Products.DeliveryProduct")
|
||||||
|
|
||||||
if params.Status != "" {
|
if params.Status != "" {
|
||||||
|
status := strings.TrimSpace(params.Status)
|
||||||
latestApprovalSubQuery := s.MarketingRepo.DB().
|
latestApprovalSubQuery := s.MarketingRepo.DB().
|
||||||
WithContext(c.Context()).
|
WithContext(c.Context()).
|
||||||
Table("approvals").
|
Table("approvals").
|
||||||
Select("DISTINCT ON (approvable_id) approvable_id, step_name").
|
Select("DISTINCT ON (approvable_id) approvable_id, step_name, action").
|
||||||
Where("approvable_type = ?", utils.ApprovalWorkflowMarketing.String()).
|
Where("approvable_type = ?", utils.ApprovalWorkflowMarketing.String()).
|
||||||
Order("approvable_id, id DESC")
|
Order("approvable_id, id DESC")
|
||||||
|
|
||||||
|
if strings.EqualFold(status, "DITOLAK") {
|
||||||
|
db = db.Where(`EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM (?) AS latest_approval
|
||||||
|
WHERE latest_approval.approvable_id = marketings.id
|
||||||
|
AND latest_approval.action = ?
|
||||||
|
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
|
||||||
|
} else {
|
||||||
db = db.Where(`EXISTS (
|
db = db.Where(`EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM (?) AS latest_approval
|
FROM (?) AS latest_approval
|
||||||
WHERE latest_approval.approvable_id = marketings.id
|
WHERE latest_approval.approvable_id = marketings.id
|
||||||
AND LOWER(latest_approval.step_name) = LOWER(?)
|
AND LOWER(latest_approval.step_name) = LOWER(?)
|
||||||
)`, latestApprovalSubQuery, params.Status)
|
AND (latest_approval.action IS NULL OR latest_approval.action <> ?)
|
||||||
|
)`, latestApprovalSubQuery, status, string(entity.ApprovalActionRejected))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
@@ -548,11 +560,17 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
if deliveryProduct == nil || deliveryProduct.Id == 0 {
|
if deliveryProduct == nil || deliveryProduct.Id == 0 {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Delivery product not found")
|
return fiber.NewError(fiber.StatusInternalServerError, "Delivery product not found")
|
||||||
}
|
}
|
||||||
|
if deliveryProduct.ProductWarehouseId == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Delivery product warehouse not found")
|
||||||
|
}
|
||||||
|
if deliveryProduct.ProductWarehouseId != marketingProduct.ProductWarehouseId {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Delivery product warehouse mismatch with marketing product")
|
||||||
|
}
|
||||||
|
|
||||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
UsableID: deliveryProduct.Id,
|
UsableID: deliveryProduct.Id,
|
||||||
ProductWarehouseID: marketingProduct.ProductWarehouseId,
|
ProductWarehouseID: deliveryProduct.ProductWarehouseId,
|
||||||
Quantity: requestedQty,
|
Quantity: requestedQty,
|
||||||
AllowPending: false,
|
AllowPending: false,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
@@ -573,12 +591,12 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
Decrease: result.UsageQuantity,
|
Decrease: result.UsageQuantity,
|
||||||
LoggableType: string(utils.StockLogTypeMarketing),
|
LoggableType: string(utils.StockLogTypeMarketing),
|
||||||
LoggableId: deliveryProduct.Id,
|
LoggableId: deliveryProduct.Id,
|
||||||
ProductWarehouseId: marketingProduct.ProductWarehouseId,
|
ProductWarehouseId: deliveryProduct.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Notes: fmt.Sprintf("FIFO consume (%.2f)", result.UsageQuantity),
|
Notes: fmt.Sprintf("FIFO consume (%.2f)", result.UsageQuantity),
|
||||||
}
|
}
|
||||||
|
|
||||||
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1)
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, deliveryProduct.ProductWarehouseId, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,31 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestedByWarehouse := make(map[uint]float64)
|
||||||
|
for _, item := range req.MarketingProducts {
|
||||||
|
if item.ProductWarehouseId == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
requestedByWarehouse[item.ProductWarehouseId] += item.Qty
|
||||||
|
}
|
||||||
|
|
||||||
|
for pwID, requestedQty := range requestedByWarehouse {
|
||||||
|
productWarehouse, err := s.ProductWarehouseRepo.GetDetailByID(c.Context(), pwID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", pwID))
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check stock availability")
|
||||||
|
}
|
||||||
|
availableQty := productWarehouse.Quantity
|
||||||
|
if availableQty+1e-6 < requestedQty {
|
||||||
|
return nil, fiber.NewError(
|
||||||
|
fiber.StatusBadRequest,
|
||||||
|
fmt.Sprintf("Stok tidak mencukupi untuk gudang %d: diminta %.3f, tersedia %.3f", pwID, requestedQty, availableQty),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
soDate, err := utils.ParseDateString(req.Date)
|
soDate, err := utils.ParseDateString(req.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||||||
|
|||||||
+26
-3
@@ -308,7 +308,6 @@ func (s projectFlockKandangService) CheckClosing(c *fiber.Ctx, id uint) (*Closin
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pw := range productWarehouses {
|
for _, pw := range productWarehouses {
|
||||||
if pw.Quantity > 0 {
|
|
||||||
category := ""
|
category := ""
|
||||||
if pw.Product.ProductCategory.Id != 0 {
|
if pw.Product.ProductCategory.Id != 0 {
|
||||||
category = pw.Product.ProductCategory.Name
|
category = pw.Product.ProductCategory.Name
|
||||||
@@ -329,7 +328,6 @@ func (s projectFlockKandangService) CheckClosing(c *fiber.Ctx, id uint) (*Closin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
expenseSummaries := make([]ExpenseSummary, 0)
|
expenseSummaries := make([]ExpenseSummary, 0)
|
||||||
if s.ExpenseRepo != nil {
|
if s.ExpenseRepo != nil {
|
||||||
@@ -585,7 +583,7 @@ func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.ApprovalSvc != nil {
|
if s.ApprovalSvc != nil {
|
||||||
reopenAction := entity.ApprovalActionUpdated
|
reopenAction := entity.ApprovalActionApproved
|
||||||
// Hindari duplikasi jika approval terakhir sudah Disetujui + Updated
|
// Hindari duplikasi jika approval terakhir sudah Disetujui + Updated
|
||||||
latestPFK, lerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, id, nil)
|
latestPFK, lerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, id, nil)
|
||||||
if lerr != nil {
|
if lerr != nil {
|
||||||
@@ -611,6 +609,31 @@ func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validati
|
|||||||
return nil, aerr
|
return nil, aerr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pastikan approval project flock kembali ke Aktif
|
||||||
|
latestPF, lerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlock, pfk.ProjectFlockId, nil)
|
||||||
|
if lerr != nil {
|
||||||
|
return nil, lerr
|
||||||
|
}
|
||||||
|
shouldCreatePF := true
|
||||||
|
if latestPF != nil &&
|
||||||
|
latestPF.StepNumber == uint16(utils.ProjectFlockStepAktif) &&
|
||||||
|
latestPF.Action != nil && *latestPF.Action == reopenAction {
|
||||||
|
shouldCreatePF = false
|
||||||
|
}
|
||||||
|
if shouldCreatePF {
|
||||||
|
if _, aerr := s.ApprovalSvc.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowProjectFlock,
|
||||||
|
pfk.ProjectFlockId,
|
||||||
|
utils.ProjectFlockStepAktif,
|
||||||
|
&reopenAction,
|
||||||
|
actorID,
|
||||||
|
nil,
|
||||||
|
); aerr != nil && !errors.Is(aerr, gorm.ErrDuplicatedKey) {
|
||||||
|
return nil, aerr
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "action harus close atau unclose")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "action harus close atau unclose")
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type KandangWithProjectFlockIdDTO struct {
|
|||||||
kandangDTO.KandangRelationDTO
|
kandangDTO.KandangRelationDTO
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
Period int `json:"period"`
|
Period int `json:"period"`
|
||||||
|
ClosedAt *time.Time `json:"closed_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockDetailDTO struct {
|
type ProjectFlockDetailDTO struct {
|
||||||
@@ -76,18 +77,26 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
|
|||||||
var (
|
var (
|
||||||
pfkId uint
|
pfkId uint
|
||||||
period int
|
period int
|
||||||
|
closedAt *time.Time
|
||||||
)
|
)
|
||||||
for _, kh := range e.KandangHistory {
|
for _, kh := range e.KandangHistory {
|
||||||
if kh.KandangId == kandang.Id {
|
if kh.KandangId == kandang.Id {
|
||||||
pfkId = kh.Id
|
pfkId = kh.Id
|
||||||
period = kh.Period
|
period = kh.Period
|
||||||
|
closedAt = kh.ClosedAt
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mapped := kandangDTO.ToKandangRelationDTO(kandang)
|
||||||
|
if closedAt != nil {
|
||||||
|
// Jangan ubah tabel kandang, hanya override status di response.
|
||||||
|
mapped.Status = string(utils.KandangStatusNonActive)
|
||||||
|
}
|
||||||
kandangSummaries[i] = KandangWithProjectFlockIdDTO{
|
kandangSummaries[i] = KandangWithProjectFlockIdDTO{
|
||||||
KandangRelationDTO: kandangDTO.ToKandangRelationDTO(kandang),
|
KandangRelationDTO: mapped,
|
||||||
ProjectFlockKandangId: pfkId,
|
ProjectFlockKandangId: pfkId,
|
||||||
Period: period,
|
Period: period,
|
||||||
|
ClosedAt: closedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user