diff --git a/internal/modules/marketing/controllers/deliveryorder.controller.go b/internal/modules/marketing/controllers/deliveryorder.controller.go index e9ccb285..208d8b48 100644 --- a/internal/modules/marketing/controllers/deliveryorder.controller.go +++ b/internal/modules/marketing/controllers/deliveryorder.controller.go @@ -72,6 +72,7 @@ func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error { MarketingId: uint(c.QueryInt("marketing_id", 0)), ProjectFlockID: uint(c.QueryInt("project_flock_id", 0)), ProjectFlockKandangID: uint(c.QueryInt("project_flock_kandang_id", 0)), + WarehouseID: uint(c.QueryInt("warehouse_id", 0)), SortBy: sortBy, SortOrder: sortOrder, } diff --git a/internal/modules/marketing/controllers/deliveryorder.export.go b/internal/modules/marketing/controllers/deliveryorder.export.go index 06b3ab28..7dc20a64 100644 --- a/internal/modules/marketing/controllers/deliveryorder.export.go +++ b/internal/modules/marketing/controllers/deliveryorder.export.go @@ -70,23 +70,26 @@ func buildMarketingExportWorkbook(items []dto.MarketingListDTO) ([]byte, error) } func setMarketingExportColumns(file *excelize.File, sheet string) error { + // A–Q = 17 columns + // E = Sales (new), H = Gudang (new), Satuan (old I) removed columnWidths := map[string]float64{ - "A": 16, - "B": 14, - "C": 18, - "D": 20, - "E": 14, - "F": 40, - "G": 10, - "H": 12, - "I": 12, - "J": 12, - "K": 16, - "L": 16, - "M": 18, - "N": 18, - "O": 18, - "P": 24, + "A": 16, // No. Order + "B": 14, // Tanggal + "C": 18, // Status + "D": 20, // Customer + "E": 20, // Sales (new) + "F": 14, // Tipe + "G": 40, // Nama Produk + "H": 20, // Gudang (new) + "I": 10, // Week + "J": 12, // Jumlah + "K": 12, // Qty Peti + "L": 16, // Berat Rata-rata (kg) + "M": 16, // Total Berat (kg) + "N": 18, // Harga Satuan + "O": 18, // Total Harga + "P": 18, // Grand Total + "Q": 24, // Catatan } for col, width := range columnWidths { @@ -104,22 +107,23 @@ func setMarketingExportColumns(file *excelize.File, sheet string) error { func setMarketingExportHeaders(file *excelize.File, sheet string) error { headers := []string{ - "No. Order", // A - "Tanggal", // B - "Status", // C - "Customer", // D - "Tipe", // E - "Nama Produk", // F - "Week", // G - "Jumlah", // H - "Satuan", // I - "Qty Peti", // J - "Berat Rata-rata (kg)", // K - "Total Berat (kg)", // L - "Harga Satuan", // M - "Total Harga", // N - "Grand Total", // O - "Catatan", // P + "No. Order", // A + "Tanggal", // B + "Status", // C + "Customer", // D + "Sales", // E (new) + "Tipe", // F + "Nama Produk", // G + "Gudang", // H (new) + "Week", // I + "Jumlah Butir", // J + "Qty Peti", // K + "Berat Rata-rata (kg)", // L + "Total Berat (kg)", // M + "Harga Satuan", // N + "Total Harga", // O + "Grand Total", // P + "Catatan", // Q } for i, header := range headers { @@ -148,7 +152,7 @@ func setMarketingExportHeaders(file *excelize.File, sheet string) error { return err } - return file.SetCellStyle(sheet, "A1", "P1", headerStyle) + return file.SetCellStyle(sheet, "A1", "Q1", headerStyle) } func setMarketingExportRows(file *excelize.File, sheet string, items []dto.MarketingListDTO) error { @@ -162,17 +166,161 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke soDate := formatMarketingExportDate(item.SoDate) status := formatMarketingExportStatus(item) customer := safeMarketingExportText(item.Customer.Name) - grandTotal := sumMarketingGrandTotal(item.SalesOrder) notes := safeMarketingExportText(item.Notes) + salesPerson := safeMarketingExportText(item.SalesPerson.Name) + + isDeliveryOrder := strings.EqualFold(strings.TrimSpace(status), "delivery order") + + // ── Delivery Order branch ────────────────────────────────────────────── + if isDeliveryOrder { + grandTotal := sumDeliveryGrandTotal(item.DeliveryOrder) + + if len(item.DeliveryOrder) == 0 { + row++ + r := strconv.Itoa(row) + vals := map[string]interface{}{ + "A": soNumber, "B": soDate, "C": status, "D": customer, "E": salesPerson, + "F": "-", "G": "-", "H": "-", "I": "-", "J": "-", "K": "-", + "L": "-", "M": "-", "N": "-", "O": "-", + "P": grandTotal, "Q": notes, + } + for col, val := range vals { + if err := file.SetCellValue(sheet, col+r, val); err != nil { + return err + } + } + continue + } + + // Build lookup map: MarketingProductId → SO product (for Week & MarketingType) + soProductMap := make(map[uint]*dto.DeliveryMarketingProductDTO, len(item.SalesOrder)) + for i := range item.SalesOrder { + soProductMap[item.SalesOrder[i].Id] = &item.SalesOrder[i] + } + + 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) + } + + if len(group.Deliveries) == 0 { + row++ + r := strconv.Itoa(row) + vals := map[string]interface{}{ + "A": doNumber, "B": doDate, "C": status, "D": customer, "E": salesPerson, + "F": "-", "G": "-", "H": gudang, "I": "-", "J": "-", "K": "-", + "L": "-", "M": "-", "N": "-", "O": "-", + "P": grandTotal, "Q": notes, + } + for col, val := range vals { + if err := file.SetCellValue(sheet, col+r, val); err != nil { + return err + } + } + continue + } + + for _, delivery := range group.Deliveries { + row++ + r := strconv.Itoa(row) + + productName := "-" + if delivery.ProductWarehouse != nil && delivery.ProductWarehouse.Product != nil { + if n := strings.TrimSpace(delivery.ProductWarehouse.Product.Name); n != "" { + productName = n + } + } + + week := "-" + marketingType := "-" + if soProduct, ok := soProductMap[delivery.MarketingProductId]; ok { + if soProduct.Week != nil { + week = strconv.Itoa(*soProduct.Week) + } + marketingType = safeMarketingExportText(soProduct.MarketingType) + } + + if err := file.SetCellValue(sheet, "A"+r, doNumber); err != nil { + return err + } + if err := file.SetCellValue(sheet, "B"+r, doDate); err != nil { + return err + } + if err := file.SetCellValue(sheet, "C"+r, status); err != nil { + return err + } + if err := file.SetCellValue(sheet, "D"+r, customer); err != nil { + return err + } + if err := file.SetCellValue(sheet, "E"+r, salesPerson); err != nil { + return err + } + if err := file.SetCellValue(sheet, "F"+r, marketingType); err != nil { + return err + } + if err := file.SetCellValue(sheet, "G"+r, productName); err != nil { + return err + } + if err := file.SetCellValue(sheet, "H"+r, gudang); err != nil { + return err + } + if err := file.SetCellValue(sheet, "I"+r, week); err != nil { + return err + } + if err := file.SetCellValue(sheet, "J"+r, delivery.Qty); err != nil { + return err + } + if delivery.TotalPeti != nil { + if err := file.SetCellValue(sheet, "K"+r, *delivery.TotalPeti); err != nil { + return err + } + } else { + if err := file.SetCellValue(sheet, "K"+r, "-"); err != nil { + return err + } + } + if err := file.SetCellValue(sheet, "L"+r, delivery.AvgWeight); err != nil { + return err + } + if err := file.SetCellValue(sheet, "M"+r, delivery.TotalWeight); err != nil { + return err + } + if err := file.SetCellValue(sheet, "N"+r, delivery.UnitPrice); err != nil { + return err + } + if err := file.SetCellValue(sheet, "O"+r, delivery.TotalPrice); err != nil { + return err + } + if err := file.SetCellValue(sheet, "P"+r, grandTotal); err != nil { + return err + } + if err := file.SetCellValue(sheet, "Q"+r, notes); err != nil { + return err + } + } + } + continue + } + + // ── Sales Order branch (all other statuses) ─────────────────────────── + grandTotal := sumMarketingGrandTotal(item.SalesOrder) if len(item.SalesOrder) == 0 { row++ r := strconv.Itoa(row) vals := map[string]interface{}{ - "A": soNumber, "B": soDate, "C": status, "D": customer, - "E": "-", "F": "-", "G": "-", "H": "-", "I": "-", "J": "-", - "K": "-", "L": "-", "M": "-", "N": "-", - "O": grandTotal, "P": notes, + "A": soNumber, "B": soDate, "C": status, "D": customer, "E": salesPerson, + "F": "-", "G": "-", "H": "-", "I": "-", "J": "-", "K": "-", + "L": "-", "M": "-", "N": "-", "O": "-", + "P": grandTotal, "Q": notes, } for col, val := range vals { if err := file.SetCellValue(sheet, col+r, val); err != nil { @@ -198,9 +346,9 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke week = strconv.Itoa(*prod.Week) } - satuan := "-" - if prod.ConvertionUnit != nil && strings.TrimSpace(*prod.ConvertionUnit) != "" { - satuan = *prod.ConvertionUnit + gudang := "-" + if prod.ProductWarehouse != nil { + gudang = safeMarketingExportText(prod.ProductWarehouse.Warehouse.Name) } if err := file.SetCellValue(sheet, "A"+r, soNumber); err != nil { @@ -215,46 +363,49 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke if err := file.SetCellValue(sheet, "D"+r, customer); err != nil { return err } - if err := file.SetCellValue(sheet, "E"+r, safeMarketingExportText(prod.MarketingType)); err != nil { + if err := file.SetCellValue(sheet, "E"+r, salesPerson); err != nil { return err } - if err := file.SetCellValue(sheet, "F"+r, productName); err != nil { + if err := file.SetCellValue(sheet, "F"+r, safeMarketingExportText(prod.MarketingType)); err != nil { return err } - if err := file.SetCellValue(sheet, "G"+r, week); err != nil { + if err := file.SetCellValue(sheet, "G"+r, productName); err != nil { return err } - if err := file.SetCellValue(sheet, "H"+r, prod.Qty); err != nil { + if err := file.SetCellValue(sheet, "H"+r, gudang); err != nil { return err } - if err := file.SetCellValue(sheet, "I"+r, satuan); err != nil { + if err := file.SetCellValue(sheet, "I"+r, week); err != nil { + return err + } + if err := file.SetCellValue(sheet, "J"+r, prod.Qty); err != nil { return err } if prod.TotalPeti != nil { - if err := file.SetCellValue(sheet, "J"+r, *prod.TotalPeti); err != nil { + if err := file.SetCellValue(sheet, "K"+r, *prod.TotalPeti); err != nil { return err } } else { - if err := file.SetCellValue(sheet, "J"+r, "-"); err != nil { + if err := file.SetCellValue(sheet, "K"+r, "-"); err != nil { return err } } - if err := file.SetCellValue(sheet, "K"+r, prod.AvgWeight); err != nil { + if err := file.SetCellValue(sheet, "L"+r, prod.AvgWeight); err != nil { return err } - if err := file.SetCellValue(sheet, "L"+r, prod.TotalWeight); err != nil { + if err := file.SetCellValue(sheet, "M"+r, prod.TotalWeight); err != nil { return err } - if err := file.SetCellValue(sheet, "M"+r, prod.UnitPrice); err != nil { + if err := file.SetCellValue(sheet, "N"+r, prod.UnitPrice); err != nil { return err } - if err := file.SetCellValue(sheet, "N"+r, prod.TotalPrice); err != nil { + if err := file.SetCellValue(sheet, "O"+r, prod.TotalPrice); err != nil { return err } - if err := file.SetCellValue(sheet, "O"+r, grandTotal); err != nil { + if err := file.SetCellValue(sheet, "P"+r, grandTotal); err != nil { return err } - if err := file.SetCellValue(sheet, "P"+r, notes); err != nil { + if err := file.SetCellValue(sheet, "Q"+r, notes); err != nil { return err } } @@ -276,7 +427,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke if err != nil { return err } - if err := file.SetCellStyle(sheet, "A2", "P"+lastRowStr, dataStyle); err != nil { + if err := file.SetCellStyle(sheet, "A2", "Q"+lastRowStr, dataStyle); err != nil { return err } @@ -287,7 +438,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke if err != nil { return err } - if err := file.SetCellStyle(sheet, "K2", "O"+lastRowStr, numberStyle); err != nil { + if err := file.SetCellStyle(sheet, "L2", "P"+lastRowStr, numberStyle); err != nil { return err } @@ -298,7 +449,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke if err != nil { return err } - for _, col := range []string{"G", "H", "J"} { + for _, col := range []string{"I", "J", "K"} { if err := file.SetCellStyle(sheet, col+"2", col+lastRowStr, centerStyle); err != nil { return err } @@ -327,16 +478,23 @@ func formatMarketingExportStatus(item dto.MarketingListDTO) string { return safeMarketingExportText(item.LatestApproval.StepName) } - func sumMarketingGrandTotal(items []dto.DeliveryMarketingProductDTO) float64 { total := 0.0 for _, item := range items { total += item.TotalPrice } - return total } +func sumDeliveryGrandTotal(groups []dto.DeliveryGroupDTO) float64 { + total := 0.0 + for _, g := range groups { + for _, d := range g.Deliveries { + total += d.TotalPrice + } + } + return total +} func safeMarketingExportText(value string) string { trimmed := strings.TrimSpace(value) diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index 396a06e3..d442f457 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -287,6 +287,16 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO db = db.Where("marketings.customer_id = ?", params.CustomerId) } + if params.WarehouseID != 0 { + db = db.Where(`EXISTS ( + SELECT 1 + FROM marketing_products mp + JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id + WHERE mp.marketing_id = marketings.id + AND pw.warehouse_id = ? + )`, params.WarehouseID) + } + db = s.applyMarketingProjectFlockFilter(c.Context(), db, params.ProjectFlockID, params.ProjectFlockKandangID) db = s.applyMarketingSearchFilter(c.Context(), db, params.Search) diff --git a/internal/modules/marketing/validations/deliveryorder.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go index 629a5df6..c602de54 100644 --- a/internal/modules/marketing/validations/deliveryorder.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -31,6 +31,7 @@ type DeliveryOrderQuery struct { MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"` ProjectFlockID uint `query:"project_flock_id" validate:"omitempty,gt=0"` ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"` + 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"` } diff --git a/internal/modules/production/recordings/controllers/recording.controller.go b/internal/modules/production/recordings/controllers/recording.controller.go index 3bf55546..f7e90f80 100644 --- a/internal/modules/production/recordings/controllers/recording.controller.go +++ b/internal/modules/production/recordings/controllers/recording.controller.go @@ -8,10 +8,12 @@ import ( "time" "gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations" "gitlab.com/mbugroup/lti-api.git/internal/response" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/gofiber/fiber/v2" ) @@ -75,6 +77,43 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error { } listDTO := dto.ToRecordingListDTOs(result) + + recordingIDs := make([]uint, 0, len(result)) + for i := range result { + if result[i].Id != 0 { + recordingIDs = append(recordingIDs, result[i].Id) + } + } + if len(recordingIDs) > 0 { + eggs, err := u.RecordingService.GetEggsWithFlagsByRecordingIDs(c.Context(), recordingIDs) + if err != nil { + return err + } + eggByRecording := make(map[uint][]entity.RecordingEgg, len(recordingIDs)) + for _, egg := range eggs { + eggByRecording[egg.RecordingId] = append(eggByRecording[egg.RecordingId], egg) + } + for i := range listDTO { + id := listDTO[i].Id + if eggList, ok := eggByRecording[id]; ok { + breakdown := make(map[string]dto.EggExportBreakdownDTO) + for _, egg := range eggList { + flagName := eggTypeFromProductName(egg.ProductWarehouse.Product.Name) + if flagName == "" { + continue + } + entry := breakdown[flagName] + entry.Qty += egg.Qty + if egg.Weight != nil { + entry.Kg += *egg.Weight + } + breakdown[flagName] = entry + } + listDTO[i].EggExportBreakdown = breakdown + } + } + } + if strings.EqualFold(exportType, "excel") { return exportRecordingListExcel(c, listDTO) } @@ -94,6 +133,33 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error { }) } +// eggTypeFromProductName maps product name to egg type flag name by keyword matching. +// Falls back to empty string if no keyword matches. +func eggTypeFromProductName(name string) string { + normalized := strings.ToLower(strings.TrimSpace(name)) + if normalized == "" { + return "" + } + // Ordered longest-first to prefer "papacal" over partial match of "pacal", etc. + keywords := []struct { + keyword string + flag string + }{ + {"papacal", string(utils.FlagTelurPapacal)}, + {"jumbo", string(utils.FlagTelurJumbo)}, + {"retak", string(utils.FlagTelurRetak)}, + {"putih", string(utils.FlagTelurPutih)}, + {"pecah", string(utils.FlagTelurPecah)}, + {"utuh", string(utils.FlagTelurUtuh)}, + } + for _, k := range keywords { + if strings.Contains(normalized, k.keyword) { + return k.flag + } + } + return "" +} + func (u *RecordingController) GetOne(c *fiber.Ctx) error { param := c.Params("id") diff --git a/internal/modules/production/recordings/controllers/recording.export.go b/internal/modules/production/recordings/controllers/recording.export.go index fc514fbd..6260f1de 100644 --- a/internal/modules/production/recordings/controllers/recording.export.go +++ b/internal/modules/production/recordings/controllers/recording.export.go @@ -8,6 +8,7 @@ import ( "time" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/gofiber/fiber/v2" "github.com/xuri/excelize/v2" @@ -79,6 +80,18 @@ func setRecordingExportColumns(file *excelize.File, sheet string) error { "AB": 18, "AC": 24, "AD": 18, + "AE": 12, + "AF": 10, + "AG": 12, + "AH": 10, + "AI": 12, + "AJ": 10, + "AK": 12, + "AL": 10, + "AM": 12, + "AN": 10, + "AO": 12, + "AP": 10, } for col, width := range columnWidths { @@ -208,6 +221,31 @@ func setRecordingExportHeaders(file *excelize.File, sheet string) error { return err } + eggTypes := []struct { + col1, col2, label string + }{ + {"AE", "AF", "Telur Utuh"}, + {"AG", "AH", "Telur Pecah"}, + {"AI", "AJ", "Telur Putih"}, + {"AK", "AL", "Telur Retak"}, + {"AM", "AN", "Telur Papacal"}, + {"AO", "AP", "Telur Jumbo"}, + } + for _, et := range eggTypes { + if err := file.MergeCell(sheet, et.col1+"1", et.col2+"1"); err != nil { + return err + } + if err := file.SetCellValue(sheet, et.col1+"1", et.label); err != nil { + return err + } + if err := file.SetCellValue(sheet, et.col1+"2", "Butir"); err != nil { + return err + } + if err := file.SetCellValue(sheet, et.col2+"2", "Kg"); err != nil { + return err + } + } + headerStyle, err := file.NewStyle(&excelize.Style{ Font: &excelize.Font{ Bold: true, @@ -234,7 +272,7 @@ func setRecordingExportHeaders(file *excelize.File, sheet string) error { return err } - return file.SetCellStyle(sheet, "A1", "AD2", headerStyle) + return file.SetCellStyle(sheet, "A1", "AP2", headerStyle) } func setRecordingExportRows(file *excelize.File, sheet string, items []dto.RecordingListDTO) error { @@ -245,7 +283,8 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor columns := []string{ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", - "AC", "AD", + "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", + "AO", "AP", } currentRow := 3 @@ -293,14 +332,14 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor // Expand recordings into one row per sapronak type sapronakRow struct { name string - input string + input interface{} // float64 for numeric, string "-" for placeholder } sapronaks := make([]sapronakRow, 0) if len(item.FeedUsage) > 0 { for _, fu := range item.FeedUsage { sapronaks = append(sapronaks, sapronakRow{ name: safeExportText(fu.ProductName), - input: formatNumberID(fu.UsageAmount+fu.PendingQty, 2, true), + input: fu.UsageAmount + fu.PendingQty, }) } } else { @@ -311,37 +350,66 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor for sIdx, s := range sapronaks { if sIdx == 0 { + eggQty := func(flagName string) int { + if item.EggExportBreakdown != nil { + if bd, ok := item.EggExportBreakdown[flagName]; ok { + return bd.Qty + } + } + return 0 + } + eggKg := func(flagName string) float64 { + if item.EggExportBreakdown != nil { + if bd, ok := item.EggExportBreakdown[flagName]; ok { + return bd.Kg + } + } + return 0 + } + rowValues := []interface{}{ - i + 1, // A - locationName, // B - safeExportText(item.ProjectFlock.FlockName), // C - kandangName, // D - item.ProjectFlock.Period, // E + i + 1, // A + locationName, // B + safeExportText(item.ProjectFlock.FlockName), // C + kandangName, // D + item.ProjectFlock.Period, // E formatCategoryLabel(item.ProjectFlock.ProjectFlockCategory), // F - formatAgeLabel(item), // G - formatDateIndonesian(item.RecordDatetime), // H - formatNumberID(item.ProjectFlock.TotalChickQty, 0, false), // I - formatNumberID(item.FcrValue, 2, true), // J - formatNumberID(fcrStd, 2, true), // K - formatNumberID(item.FeedIntake, 2, true), // L - formatNumberID(feedIntakeStd, 2, true), // M - formatPercentID(item.CumDepletionRate, 2), // N - formatPercentID(maxDepletionStd, 2), // O - formatNumberID(item.TotalDepletionQty, 2, true), // P - formatNumberID(item.EggMass, 2, true), // Q - formatNumberID(eggMassStd, 2, true), // R - formatNumberID(item.EggWeight, 2, true), // S - formatNumberID(eggWeightStd, 2, true), // T - formatPercentID(item.HenDay, 2), // U - formatPercentID(henDayStd, 2), // V - formatPercentID(item.HenHouse, 2), // W - formatPercentID(henHouseStd, 2), // X - formatApprovalStatus(item), // Y + formatAgeLabel(item), // G + formatDateIndonesian(item.RecordDatetime), // H + item.ProjectFlock.TotalChickQty, // I + item.FcrValue, // J + fcrStd, // K + item.FeedIntake, // L + feedIntakeStd, // M + item.CumDepletionRate, // N + maxDepletionStd, // O + item.TotalDepletionQty, // P + item.EggMass, // Q + eggMassStd, // R + item.EggWeight, // S + eggWeightStd, // T + item.HenDay, // U + henDayStd, // V + item.HenHouse, // W + henHouseStd, // X + formatApprovalStatus(item), // Y safeExportText(pointerString(item.Approval.Notes)), // Z - createdBy, // AA - formatDateIndonesian(item.CreatedAt), // AB - s.name, // AC - s.input, // AD + createdBy, // AA + formatDateIndonesian(item.CreatedAt), // AB + s.name, // AC + s.input, // AD + eggQty(string(utils.FlagTelurUtuh)), // AE + eggKg(string(utils.FlagTelurUtuh)), // AF + eggQty(string(utils.FlagTelurPecah)), // AG + eggKg(string(utils.FlagTelurPecah)), // AH + eggQty(string(utils.FlagTelurPutih)), // AI + eggKg(string(utils.FlagTelurPutih)), // AJ + eggQty(string(utils.FlagTelurRetak)), // AK + eggKg(string(utils.FlagTelurRetak)), // AL + eggQty(string(utils.FlagTelurPapacal)), // AM + eggKg(string(utils.FlagTelurPapacal)), // AN + eggQty(string(utils.FlagTelurJumbo)), // AO + eggKg(string(utils.FlagTelurJumbo)), // AP } for idx, col := range columns { @@ -379,7 +447,7 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor if err != nil { return err } - if err := file.SetCellStyle(sheet, "A3", fmt.Sprintf("AD%d", lastRow), dataCenterStyle); err != nil { + if err := file.SetCellStyle(sheet, "A3", fmt.Sprintf("AP%d", lastRow), dataCenterStyle); err != nil { return err } @@ -445,6 +513,7 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor mergeCols := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", + "AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", } for _, rng := range itemRanges { if rng.end > rng.start { @@ -454,6 +523,53 @@ func setRecordingExportRows(file *excelize.File, sheet string, items []dto.Recor } file.SetCellStyle(sheet, fmt.Sprintf("AC%d", rng.end), fmt.Sprintf("AC%d", rng.end), borderBottomLeftStyle) file.SetCellStyle(sheet, fmt.Sprintf("AD%d", rng.end), fmt.Sprintf("AD%d", rng.end), borderBottomCenterStyle) + // Egg columns use center + thick bottom border + for _, col := range []string{"AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP"} { + file.SetCellStyle(sheet, fmt.Sprintf("%s%d", col, rng.end), fmt.Sprintf("%s%d", col, rng.end), borderBottomCenterStyle) + } + } + + numFmtInt := "0" + numberIntStyle, err := file.NewStyle(&excelize.Style{ + CustomNumFmt: &numFmtInt, + Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true}, + Border: []excelize.Border{ + {Type: "left", Color: "E6E6E6", Style: 1}, + {Type: "top", Color: "E6E6E6", Style: 1}, + {Type: "bottom", Color: "E6E6E6", Style: 1}, + {Type: "right", Color: "E6E6E6", Style: 1}, + }, + }) + if err != nil { + return err + } + numFmtFloat := "0.00" + numberFloatStyle, err := file.NewStyle(&excelize.Style{ + CustomNumFmt: &numFmtFloat, + Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true}, + Border: []excelize.Border{ + {Type: "left", Color: "E6E6E6", Style: 1}, + {Type: "top", Color: "E6E6E6", Style: 1}, + {Type: "bottom", Color: "E6E6E6", Style: 1}, + {Type: "right", Color: "E6E6E6", Style: 1}, + }, + }) + if err != nil { + return err + } + + intCols := []string{"E", "I", "AE", "AG", "AI", "AK", "AM", "AO"} + for _, col := range intCols { + if err := file.SetCellStyle(sheet, col+"3", fmt.Sprintf("%s%d", col, lastRow), numberIntStyle); err != nil { + return err + } + } + + floatCols := []string{"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "AD", "AF", "AH", "AJ", "AL", "AN", "AP"} + for _, col := range floatCols { + if err := file.SetCellStyle(sheet, col+"3", fmt.Sprintf("%s%d", col, lastRow), numberFloatStyle); err != nil { + return err + } } return nil diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index b24bfd68..f8e6cf22 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -100,14 +100,20 @@ type RecordingFeedUsageDTO struct { PendingQty float64 `json:"pending_qty"` } +type EggExportBreakdownDTO struct { + Qty int `json:"qty"` + Kg float64 `json:"kg"` +} + type RecordingListDTO struct { RecordingRelationDTO - CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Kandang *RecordingKandangDTO `json:"kandang,omitempty"` - Location *RecordingLocationDTO `json:"location,omitempty"` - FeedUsage []RecordingFeedUsageDTO `json:"feed_usage,omitempty"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Kandang *RecordingKandangDTO `json:"kandang,omitempty"` + Location *RecordingLocationDTO `json:"location,omitempty"` + FeedUsage []RecordingFeedUsageDTO `json:"feed_usage,omitempty"` + EggExportBreakdown map[string]EggExportBreakdownDTO `json:"egg_breakdown,omitempty"` } type RecordingDetailDTO struct { diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index ca989359..7a829ead 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -51,6 +51,7 @@ type RecordingRepository interface { UpdateEggTotalQty(tx *gorm.DB, eggID uint, totalQty float64) error UpdateEggWeight(tx *gorm.DB, eggID uint, weight *float64) error GetRecordingEggByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.RecordingEgg, error) + GetEggsWithFlagsByRecordingIDs(ctx context.Context, recordingIDs []uint) ([]entity.RecordingEgg, error) ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error) @@ -581,6 +582,22 @@ func (r *RecordingRepositoryImpl) GetRecordingEggByID( return &egg, nil } +func (r *RecordingRepositoryImpl) GetEggsWithFlagsByRecordingIDs(ctx context.Context, recordingIDs []uint) ([]entity.RecordingEgg, error) { + if len(recordingIDs) == 0 { + return nil, nil + } + + var eggs []entity.RecordingEgg + err := r.DB().WithContext(ctx). + Preload("ProductWarehouse.Product"). + Where("recording_eggs.recording_id IN ?", recordingIDs). + Find(&eggs).Error + if err != nil { + return nil, err + } + return eggs, nil +} + func (r *RecordingRepositoryImpl) ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error) { if projectFlockKandangId == 0 { return false, nil diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index f06216eb..a4647980 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -46,6 +46,7 @@ type RecordingService interface { DeleteOne(ctx *fiber.Ctx, id uint) error Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error) GetProgressRows(ctx *fiber.Ctx, query *exportprogress.Query) ([]exportprogress.Row, error) + GetEggsWithFlagsByRecordingIDs(ctx context.Context, recordingIDs []uint) ([]entity.RecordingEgg, error) } type recordingService struct { @@ -259,6 +260,10 @@ func (s recordingService) GetProgressRows(c *fiber.Ctx, query *exportprogress.Qu return s.Repository.GetProgressRows(c.Context(), query.StartDate, query.EndDate, scope.IDs, scope.Restrict) } +func (s recordingService) GetEggsWithFlagsByRecordingIDs(ctx context.Context, recordingIDs []uint) ([]entity.RecordingEgg, error) { + return s.Repository.GetEggsWithFlagsByRecordingIDs(ctx, recordingIDs) +} + func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, error) { if err := m.EnsureRecordingAccess(c, s.Repository.DB(), id); err != nil { return nil, err