From 4b9e86427d253ef7f61d83d1f6798926ac0cb184 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 2 Jun 2026 13:19:52 +0700 Subject: [PATCH 1/2] feat: add date range filter to marketing list API Added start_date, end_date, and filter_by query parameters to the GET /api/marketing/ endpoint. Users can now filter marketing records by a date range using either so_date (Sales Order date, default) or created_at as the target column. Changes: - validation: added StartDate, EndDate (YYYY-MM-DD format), and FilterBy (oneof: so_date, created_at) to DeliveryOrderQuery struct - controller: parse the three new query params in GetAll handler - service: apply >=start / --- .../controllers/deliveryorder.controller.go | 3 +++ .../marketing/services/deliveryorder.service.go | 15 +++++++++++++++ .../validations/deliveryorder.validation.go | 3 +++ 3 files changed, 21 insertions(+) diff --git a/internal/modules/marketing/controllers/deliveryorder.controller.go b/internal/modules/marketing/controllers/deliveryorder.controller.go index 208d8b48..1b1b1d41 100644 --- a/internal/modules/marketing/controllers/deliveryorder.controller.go +++ b/internal/modules/marketing/controllers/deliveryorder.controller.go @@ -75,6 +75,9 @@ func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error { WarehouseID: uint(c.QueryInt("warehouse_id", 0)), SortBy: sortBy, SortOrder: sortOrder, + StartDate: strings.TrimSpace(c.Query("start_date", "")), + EndDate: strings.TrimSpace(c.Query("end_date", "")), + FilterBy: strings.TrimSpace(c.Query("filter_by", "")), } if isAllExcelExportRequest(c) { diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index d442f457..7665a1d8 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -321,6 +321,21 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO return db.Where("id = ?", params.MarketingId) } + dateStart, dateEnd, dateErr := utils.ParseDateRangeForQuery(params.StartDate, params.EndDate) + if dateErr != nil { + return db.Where("1 = 0") + } + dateCol := "marketings.so_date" + if strings.TrimSpace(params.FilterBy) == "created_at" { + dateCol = "marketings.created_at" + } + if dateStart != nil { + db = db.Where(dateCol+" >= ?", *dateStart) + } + if dateEnd != nil { + db = db.Where(dateCol+" < ?", *dateEnd) + } + orderDir := "DESC" if params.SortOrder != "" { orderDir = strings.ToUpper(params.SortOrder) diff --git a/internal/modules/marketing/validations/deliveryorder.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go index c602de54..3504eb7b 100644 --- a/internal/modules/marketing/validations/deliveryorder.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -34,6 +34,9 @@ type DeliveryOrderQuery struct { WarehouseID uint `query:"warehouse_id" validate:"omitempty,gt=0"` SortBy string `query:"sort_by" validate:"omitempty,oneof=so_number so_date status customer grand_total created_at"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` + StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"` + EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"` + FilterBy string `query:"filter_by" validate:"omitempty,oneof=so_date created_at"` } type DeliveryOrderApprove struct { From 981fb982481f963703fea6a4bbe335406f7ba79f Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 2 Jun 2026 16:48:06 +0700 Subject: [PATCH 2/2] fix: use soDate instead of deliveryDate for Delivery Order rows in marketing export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the Excel export, Delivery Order rows were writing `group.DeliveryDate` (the actual delivery date) to column B ("Tanggal"), while the web UI always shows `so_date` for every row. This caused a visible mismatch — e.g. DO-01954 displayed "31 Mei 2026" on the web but "01-06-2026" in the exported file. Changes: - Remove the `doDate` variable from the DO branch; both the empty-deliveries fallback row and each per-delivery row now write `soDate` to column B, consistent with what the web shows - Fix a pre-existing nil pointer dereference: `prod.ProductWarehouse.Warehouse` was accessed without a nil guard in the SO branch - Update the export test to match the current 17-column layout (headers and row assertions were stale), and add a regression case that explicitly asserts a DO row with soDate=2026-05-31 / deliveryDate=2026-06-01 produces "31-05-2026" in column B Co-Authored-By: Claude Sonnet 4.6 --- .../controllers/deliveryorder.export.go | 11 ++--- .../controllers/deliveryorder.export_test.go | 49 ++++++++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/internal/modules/marketing/controllers/deliveryorder.export.go b/internal/modules/marketing/controllers/deliveryorder.export.go index 7dc20a64..b2fd72eb 100644 --- a/internal/modules/marketing/controllers/deliveryorder.export.go +++ b/internal/modules/marketing/controllers/deliveryorder.export.go @@ -201,11 +201,6 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke for _, group := range item.DeliveryOrder { doNumber := safeMarketingExportText(group.DoNumber) - doDate := "-" - if group.DeliveryDate != nil { - doDate = formatMarketingExportDate(*group.DeliveryDate) - } - gudang := "-" if group.Warehouse != nil { gudang = safeMarketingExportText(group.Warehouse.Name) @@ -215,7 +210,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke row++ r := strconv.Itoa(row) vals := map[string]interface{}{ - "A": doNumber, "B": doDate, "C": status, "D": customer, "E": salesPerson, + "A": doNumber, "B": soDate, "C": status, "D": customer, "E": salesPerson, "F": "-", "G": "-", "H": gudang, "I": "-", "J": "-", "K": "-", "L": "-", "M": "-", "N": "-", "O": "-", "P": grandTotal, "Q": notes, @@ -251,7 +246,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke if err := file.SetCellValue(sheet, "A"+r, doNumber); err != nil { return err } - if err := file.SetCellValue(sheet, "B"+r, doDate); err != nil { + if err := file.SetCellValue(sheet, "B"+r, soDate); err != nil { return err } if err := file.SetCellValue(sheet, "C"+r, status); err != nil { @@ -347,7 +342,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke } gudang := "-" - if prod.ProductWarehouse != nil { + if prod.ProductWarehouse != nil && prod.ProductWarehouse.Warehouse != nil { gudang = safeMarketingExportText(prod.ProductWarehouse.Warehouse.Name) } diff --git a/internal/modules/marketing/controllers/deliveryorder.export_test.go b/internal/modules/marketing/controllers/deliveryorder.export_test.go index d41a3f6e..b73b891e 100644 --- a/internal/modules/marketing/controllers/deliveryorder.export_test.go +++ b/internal/modules/marketing/controllers/deliveryorder.export_test.go @@ -15,6 +15,10 @@ import ( ) func TestBuildMarketingExportWorkbookHeadersAndRows(t *testing.T) { + // DO item has soDate=2026-05-31 and deliveryDate=2026-06-01 to verify + // the export uses soDate (not deliveryDate) in column B. + deliveryDate := time.Date(2026, time.June, 1, 0, 0, 0, 0, time.UTC) + items := []dto.MarketingListDTO{ { MarketingRelationDTO: dto.MarketingRelationDTO{ @@ -51,6 +55,22 @@ func TestBuildMarketingExportWorkbookHeadersAndRows(t *testing.T) { Action: strPtr("REJECTED"), }, }, + { + MarketingRelationDTO: dto.MarketingRelationDTO{ + SoNumber: "SO-00760", + SoDate: time.Date(2026, time.May, 31, 0, 0, 0, 0, time.UTC), + }, + Customer: customerDTO.CustomerRelationDTO{Name: "CORDELA"}, + DeliveryOrder: []dto.DeliveryGroupDTO{ + { + DoNumber: "DO-01954", + DeliveryDate: &deliveryDate, + }, + }, + LatestApproval: approvalDTO.ApprovalRelationDTO{ + StepName: "Delivery Order", + }, + }, } content, err := buildMarketingExportWorkbook(items) @@ -69,9 +89,10 @@ func TestBuildMarketingExportWorkbookHeadersAndRows(t *testing.T) { "B1": "Tanggal", "C1": "Status", "D1": "Customer", - "E1": "Grand Total", - "F1": "Products", - "G1": "Notes", + "E1": "Sales", + "G1": "Nama Produk", + "P1": "Grand Total", + "Q1": "Catatan", } for cell, expected := range expectedHeaders { got, err := file.GetCellValue(marketingExportSheetName, cell) @@ -83,19 +104,25 @@ func TestBuildMarketingExportWorkbookHeadersAndRows(t *testing.T) { } } + // SO-00762: 3 products → rows 2, 3, 4 assertCellEquals(t, file, "A2", "SO-00762") assertCellEquals(t, file, "B2", "22-04-2026") assertCellEquals(t, file, "C2", "Pengajuan") assertCellEquals(t, file, "D2", "AJAT") - assertCellEquals(t, file, "E2", "Rp 5.206.200.000") - assertCellEquals(t, file, "F2", "PAKAN GROWING CRUMBLE 8603 MALINDO, 295 GOLD PELLET") - assertCellEquals(t, file, "G2", "tes") + assertCellEquals(t, file, "G2", "PAKAN GROWING CRUMBLE 8603 MALINDO") + assertCellEquals(t, file, "Q2", "tes") - assertCellEquals(t, file, "A3", "SO-00761") - assertCellEquals(t, file, "C3", "Ditolak") - assertCellEquals(t, file, "E3", "Rp 75.000") - assertCellEquals(t, file, "F3", "HS30 FOAM @20 LITER") - assertCellEquals(t, file, "G3", "-") + // SO-00761 (rejected): 1 product → row 5 + assertCellEquals(t, file, "A5", "SO-00761") + assertCellEquals(t, file, "C5", "Ditolak") + assertCellEquals(t, file, "G5", "HS30 FOAM @20 LITER") + assertCellEquals(t, file, "Q5", "-") + + // DO-01954: column B must use soDate (31-05-2026), not deliveryDate (01-06-2026) + assertCellEquals(t, file, "A6", "DO-01954") + assertCellEquals(t, file, "B6", "31-05-2026") + assertCellEquals(t, file, "C6", "Delivery Order") + assertCellEquals(t, file, "D6", "CORDELA") } func assertCellEquals(t *testing.T, file *excelize.File, cell, expected string) {