Compare commits

...

36 Commits

Author SHA1 Message Date
giovanni 1992005b01 fix monitoring saldo 2026-05-23 09:32:21 +07:00
giovanni b12f563bc4 fix debt supplier ekspedisi only realisasi 2026-05-22 19:54:53 +07:00
giovanni c676aed371 fix monitorin saldo without sales order;format date excel po 2026-05-22 19:40:05 +07:00
Giovanni Gabriel Septriadi 7bbb6a836c Merge branch 'hot-fix/price-adj' into 'development'
Hot fix/price adj

See merge request mbugroup/lti-api!552
2026-05-22 11:41:53 +00:00
giovanni 6bbab2f1d5 hot fit update price adjustment stock 2026-05-22 18:40:52 +07:00
Giovanni Gabriel Septriadi 70546c2302 Merge branch 'fix/monitoring' into 'development'
fix balance monitoring

See merge request mbugroup/lti-api!550
2026-05-22 08:56:31 +00:00
Giovanni Gabriel Septriadi 1e48bc8762 Merge branch 'fix/filter-po' into 'development'
fix

See merge request mbugroup/lti-api!549
2026-05-22 06:28:39 +00:00
giovanni 77a30837e2 fix 2026-05-22 13:27:47 +07:00
Giovanni Gabriel Septriadi a63460e853 Merge branch 'fix/filter-po' into 'development'
fix filter

See merge request mbugroup/lti-api!548
2026-05-22 05:31:57 +00:00
giovanni 1be0fa1a5f fix filter 2026-05-22 12:30:23 +07:00
Giovanni Gabriel Septriadi 621d0d2bfd Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!541
2026-05-19 06:48:41 +00:00
Giovanni Gabriel Septriadi 1fd3f96038 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!532
2026-05-12 09:31:19 +00:00
Giovanni Gabriel Septriadi cf0fc9e7e6 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!530
2026-05-11 08:32:04 +00:00
Adnan Zahir d9041a89bb Merge branch 'fix/chickin' into 'production'
[FIX][BE]: add migration for edit chickin_date pullet cikaum 1 dan pullet cikaum 2

See merge request mbugroup/lti-api!518
2026-05-08 15:05:02 +07:00
giovanni c75281ebd9 add migration for update day recording pullet cikaum 1 dan 2 2026-05-07 17:35:15 +07:00
Adnan Zahir ca3ad810c6 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!505
2026-05-05 14:12:22 +07:00
Adnan Zahir 655b1ad5fe Merge branch 'development' into 'production'
fix: resolve dashboard OpenAPI integration issues

See merge request mbugroup/lti-api!498
2026-05-03 13:08:58 +07:00
Adnan Zahir 84db5fe37a Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!494
2026-04-29 12:53:02 +07:00
Adnan Zahir 63a78da18d Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!480
2026-04-26 00:13:58 +07:00
Adnan Zahir ac50c06cd7 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!478
2026-04-25 15:26:22 +07:00
Adnan Zahir b60649f59d Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!476
2026-04-25 14:16:20 +07:00
Adnan Zahir 6acc9416c1 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!473
2026-04-24 21:24:56 +07:00
Adnan Zahir bb4e5d6e3e Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!469
2026-04-24 14:20:43 +07:00
Adnan Zahir 170c221957 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!467
2026-04-24 13:31:35 +07:00
Adnan Zahir 812327f148 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!461
2026-04-24 12:30:41 +07:00
Adnan Zahir cd192128f1 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!443
2026-04-23 12:38:24 +07:00
Adnan Zahir a5d4d6c11d Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!436
2026-04-22 13:13:06 +07:00
Adnan Zahir 1452f8d083 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!427
2026-04-20 10:16:45 +07:00
Adnan Zahir 33c6706181 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!425
2026-04-20 08:24:44 +07:00
Adnan Zahir c9618e1095 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!422
2026-04-18 09:47:04 +07:00
Adnan Zahir cae7f3ef63 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!414
2026-04-14 12:01:04 +07:00
Adnan Zahir 42793d94bd Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!412
2026-04-13 14:12:48 +07:00
Adnan Zahir 1369bf0e36 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!411
2026-04-11 14:08:35 +07:00
Adnan Zahir 361d14bd3e Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!406
2026-04-10 10:50:07 +07:00
Adnan Zahir 7923352535 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!401
2026-04-07 22:58:38 +07:00
Adnan Zahir 010240066a Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!399
2026-04-07 16:52:45 +07:00
6 changed files with 77 additions and 27 deletions
@@ -0,0 +1,4 @@
UPDATE adjustment_stocks
SET price = 9535,
grand_total = ROUND(8700 * 9535, 3)
WHERE id = 532 AND adj_number = 'ADJ-00507';
@@ -0,0 +1,5 @@
UPDATE adjustment_stocks
SET price = 12635,
grand_total = ROUND(8700 * 12635, 3)
WHERE id = 532 AND adj_number = 'ADJ-00507';
@@ -435,6 +435,21 @@ func formatPurchaseExportEntityStatus(purchase *entity.Purchase) string {
return safePurchaseExportText(purchase.LatestApproval.StepName) return safePurchaseExportText(purchase.LatestApproval.StepName)
} }
var purchaseIndonesianMonths = map[time.Month]string{
time.January: "Jan",
time.February: "Feb",
time.March: "Mar",
time.April: "Apr",
time.May: "Mei",
time.June: "Jun",
time.July: "Jul",
time.August: "Ags",
time.September: "Sep",
time.October: "Okt",
time.November: "Nov",
time.December: "Des",
}
func formatPurchaseExportDate(value *time.Time) string { func formatPurchaseExportDate(value *time.Time) string {
if value == nil || value.IsZero() { if value == nil || value.IsZero() {
return "-" return "-"
@@ -446,7 +461,8 @@ func formatPurchaseExportDate(value *time.Time) string {
t = t.In(location) t = t.In(location)
} }
return t.Format("02-01-2006") month := purchaseIndonesianMonths[t.Month()]
return fmt.Sprintf("%d-%s-%02d", t.Day(), month, t.Year()%100)
} }
func safePurchaseExportPointerText(value *string) string { func safePurchaseExportPointerText(value *string) string {
@@ -264,6 +264,14 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
sortBy := strings.TrimSpace(params.SortBy) sortBy := strings.TrimSpace(params.SortBy)
sortOrder := strings.ToUpper(strings.TrimSpace(params.SortOrder)) sortOrder := strings.ToUpper(strings.TrimSpace(params.SortOrder))
if sortBy == "" && (filterBy == "po_date" || filterBy == "due_date" || filterBy == "received_date" || filterBy == "created_at") {
sortBy = filterBy
if sortOrder == "" {
sortOrder = "ASC"
}
}
if sortOrder == "" { if sortOrder == "" {
sortOrder = "DESC" sortOrder = "DESC"
} }
@@ -2240,6 +2248,11 @@ func (s *purchaseService) attachLatestApprovals(ctx context.Context, items []ent
} }
func parsePurchaseDateRangeForQuery(fromStr, toStr, fieldName string) (*time.Time, *time.Time, error) { func parsePurchaseDateRangeForQuery(fromStr, toStr, fieldName string) (*time.Time, *time.Time, error) {
jakartaLoc, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
jakartaLoc = time.FixedZone("WIB", 7*60*60)
}
var fromPtr *time.Time var fromPtr *time.Time
var toPtr *time.Time var toPtr *time.Time
@@ -2248,7 +2261,8 @@ func parsePurchaseDateRangeForQuery(fromStr, toStr, fieldName string) (*time.Tim
if err != nil { if err != nil {
return nil, nil, errors.New(fieldName + "_from must use format YYYY-MM-DD") return nil, nil, errors.New(fieldName + "_from must use format YYYY-MM-DD")
} }
fromPtr = &parsed t := time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, jakartaLoc)
fromPtr = &t
} }
if strings.TrimSpace(toStr) != "" { if strings.TrimSpace(toStr) != "" {
@@ -2256,7 +2270,8 @@ func parsePurchaseDateRangeForQuery(fromStr, toStr, fieldName string) (*time.Tim
if err != nil { if err != nil {
return nil, nil, errors.New(fieldName + "_to must use format YYYY-MM-DD") return nil, nil, errors.New(fieldName + "_to must use format YYYY-MM-DD")
} }
nextDay := parsed.AddDate(0, 0, 1) t := time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, jakartaLoc)
nextDay := t.AddDate(0, 0, 1)
toPtr = &nextDay toPtr = &nextDay
} }
@@ -248,23 +248,27 @@ func (r *balanceMonitoringRepositoryImpl) GetSalesTotalsBeforeDate(ctx context.C
var db *gorm.DB var db *gorm.DB
if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" { if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" {
// Count products that have at least one delivery before startDate (no double-counting) // realized_at: gunakan data DO (mdp.total_price), filter by delivery_date < startDate
db = r.db.WithContext(ctx). db = r.db.WithContext(ctx).
Table("marketing_products mp"). Table("marketing_delivery_products mdp").
Select("m.customer_id AS customer_id, COALESCE(SUM(mp.total_price), 0) AS total"). Select("m.customer_id AS customer_id, COALESCE(SUM(mdp.total_price), 0) AS total").
Joins("INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id"). Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
Where("m.customer_id IN ?", customerIDs). Where("m.customer_id IN ?", customerIDs).
Where("m.deleted_at IS NULL"). Where("m.deleted_at IS NULL").
Where("EXISTS (SELECT 1 FROM marketing_delivery_products mdp WHERE mdp.marketing_product_id = mp.id AND mdp.deleted_at IS NULL AND mdp.delivery_date IS NOT NULL AND DATE(mdp.delivery_date) < ?)", startDate) Where("mdp.delivery_date IS NOT NULL").
Where("DATE(mdp.delivery_date) < ?", startDate)
} else { } else {
// sold_at: count all SO products ordered before startDate // sold_at: SO-date sebelum startDate DAN approval terbaru sudah DO — gunakan data DO (mdp.total_price)
db = r.db.WithContext(ctx). db = r.db.WithContext(ctx).
Table("marketing_products mp"). Table("marketing_products mp").
Select("m.customer_id AS customer_id, COALESCE(SUM(mp.total_price), 0) AS total"). Select("m.customer_id AS customer_id, COALESCE(SUM(mdp.total_price), 0) AS total").
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id"). Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
Joins("INNER JOIN marketing_delivery_products mdp ON mdp.marketing_product_id = mp.id").
Where("m.customer_id IN ?", customerIDs). Where("m.customer_id IN ?", customerIDs).
Where("m.deleted_at IS NULL"). Where("m.deleted_at IS NULL").
Where("DATE(m.so_date) < ?", startDate) Where("DATE(m.so_date) < ?", startDate).
Where("(SELECT step_number FROM approvals WHERE approvable_type = 'MARKETINGS' AND approvable_id = mp.marketing_id ORDER BY id DESC LIMIT 1) >= ?", uint16(utils.MarketingDeliveryOrder))
} }
if len(filters.SalesIDs) > 0 { if len(filters.SalesIDs) > 0 {
@@ -328,39 +332,45 @@ func (r *balanceMonitoringRepositoryImpl) GetSalesByCategoryInPeriod(ctx context
return map[uint]BalanceMonitoringCategoryRow{}, nil return map[uint]BalanceMonitoringCategoryRow{}, nil
} }
// Gunakan data DO (mdp) bukan SO (mp) agar nominal/qty/kg mencerminkan nilai aktual DO
const selectCols = `m.customer_id AS customer_id, const selectCols = `m.customer_id AS customer_id,
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.qty ELSE 0 END), 0) AS ayam_qty, COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN (mdp.usage_qty + mdp.pending_qty) ELSE 0 END), 0) AS ayam_qty,
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.total_weight ELSE 0 END), 0) AS ayam_kg, COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mdp.total_weight ELSE 0 END), 0) AS ayam_kg,
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.total_price ELSE 0 END), 0) AS ayam_nominal, COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mdp.total_price ELSE 0 END), 0) AS ayam_nominal,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.qty ELSE 0 END), 0) AS telur_qty, COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN (mdp.usage_qty + mdp.pending_qty) ELSE 0 END), 0) AS telur_qty,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.total_weight ELSE 0 END), 0) AS telur_kg, COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mdp.total_weight ELSE 0 END), 0) AS telur_kg,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.total_price ELSE 0 END), 0) AS telur_nominal, COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mdp.total_price ELSE 0 END), 0) AS telur_nominal,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.qty ELSE 0 END), 0) AS trading_qty, COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN (mdp.usage_qty + mdp.pending_qty) ELSE 0 END), 0) AS trading_qty,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.total_weight ELSE 0 END), 0) AS trading_kg, COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mdp.total_weight ELSE 0 END), 0) AS trading_kg,
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.total_price ELSE 0 END), 0) AS trading_nominal` COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mdp.total_price ELSE 0 END), 0) AS trading_nominal`
rows := make([]BalanceMonitoringCategoryRow, 0) rows := make([]BalanceMonitoringCategoryRow, 0)
var db *gorm.DB var db *gorm.DB
if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" { if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" {
// Count products that have at least one delivery in the period (no double-counting) // realized_at: FROM mdp langsung, filter by delivery_date in period — data DO
db = r.db.WithContext(ctx). db = r.db.WithContext(ctx).
Table("marketing_products mp"). Table("marketing_delivery_products mdp").
Select(selectCols). Select(selectCols).
Joins("INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id"). Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
Where("m.customer_id IN ?", customerIDs). Where("m.customer_id IN ?", customerIDs).
Where("m.deleted_at IS NULL"). Where("m.deleted_at IS NULL").
Where("EXISTS (SELECT 1 FROM marketing_delivery_products mdp WHERE mdp.marketing_product_id = mp.id AND mdp.deleted_at IS NULL AND mdp.delivery_date IS NOT NULL AND DATE(mdp.delivery_date) >= ? AND DATE(mdp.delivery_date) <= ?)", startDate, endDate) Where("mdp.delivery_date IS NOT NULL").
Where("DATE(mdp.delivery_date) >= ?", startDate).
Where("DATE(mdp.delivery_date) <= ?", endDate)
} else { } else {
// sold_at: count all SO products placed in the period regardless of delivery status // sold_at: SO-date dalam period DAN approval terbaru DO — JOIN mdp untuk data DO
db = r.db.WithContext(ctx). db = r.db.WithContext(ctx).
Table("marketing_products mp"). Table("marketing_products mp").
Select(selectCols). Select(selectCols).
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id"). Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
Joins("INNER JOIN marketing_delivery_products mdp ON mdp.marketing_product_id = mp.id").
Where("m.customer_id IN ?", customerIDs). Where("m.customer_id IN ?", customerIDs).
Where("m.deleted_at IS NULL"). Where("m.deleted_at IS NULL").
Where("DATE(m.so_date) >= ?", startDate). Where("DATE(m.so_date) >= ?", startDate).
Where("DATE(m.so_date) <= ?", endDate) Where("DATE(m.so_date) <= ?", endDate).
Where("(SELECT step_number FROM approvals WHERE approvable_type = 'MARKETINGS' AND approvable_id = mp.marketing_id ORDER BY id DESC LIMIT 1) >= ?", uint16(utils.MarketingDeliveryOrder))
} }
if len(filters.SalesIDs) > 0 { if len(filters.SalesIDs) > 0 {
@@ -514,7 +514,7 @@ func (r *debtSupplierRepositoryImpl) baseExpenseSupplierIDs(ctx context.Context,
Table("expenses"). Table("expenses").
Select("DISTINCT expenses.supplier_id"). Select("DISTINCT expenses.supplier_id").
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)). Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)). Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)). Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
Where("expenses.deleted_at IS NULL") Where("expenses.deleted_at IS NULL")
@@ -623,7 +623,7 @@ func (r *debtSupplierRepositoryImpl) GetExpensesBySuppliers(ctx context.Context,
Model(&entity.Expense{}). Model(&entity.Expense{}).
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)). Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
Where("expenses.supplier_id IN ?", supplierIDs). Where("expenses.supplier_id IN ?", supplierIDs).
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)). Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)). Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
Where("expenses.deleted_at IS NULL") Where("expenses.deleted_at IS NULL")
@@ -692,7 +692,7 @@ func (r *debtSupplierRepositoryImpl) GetExpenseTotalsBeforeDate(ctx context.Cont
Joins("JOIN expense_nonstocks en ON en.expense_id = expenses.id"). Joins("JOIN expense_nonstocks en ON en.expense_id = expenses.id").
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)). Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
Where("expenses.supplier_id IN ?", supplierIDs). Where("expenses.supplier_id IN ?", supplierIDs).
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)). Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)). Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
Where("expenses.deleted_at IS NULL"). Where("expenses.deleted_at IS NULL").
Where("DATE(expenses.transaction_date) < ?", dateFrom). Where("DATE(expenses.transaction_date) < ?", dateFrom).