mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 15:55:44 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2da476b276 | |||
| 3232fc90bb | |||
| ef985b5da5 | |||
| 55666c1dcd | |||
| c107f0f683 | |||
| ba8f00a560 | |||
| 65a1282312 | |||
| 1ca632d838 | |||
| f0403e2699 | |||
| 3e34da7385 | |||
| 8750e2ffec | |||
| 3429529162 | |||
| 32b8acb9dc | |||
| 0d7a0e30cd | |||
| d0e7b7aad1 | |||
| e781115390 |
+31
@@ -0,0 +1,31 @@
|
||||
BEGIN;
|
||||
|
||||
-- Rollback konsolidasi: kembalikan data ke loc 18 / 25 sesuai snapshot pre-migration.
|
||||
-- Order: un-soft-delete locations dulu agar FK tidak gagal saat UPDATE child.
|
||||
|
||||
-- 1. Un-soft-delete locations
|
||||
UPDATE locations SET deleted_at = NULL WHERE id IN (18, 25);
|
||||
|
||||
-- 2. project_flocks: PF 30 -> 18, PF 25 & 31 -> 25
|
||||
UPDATE project_flocks SET location_id = 18, updated_at = NOW() WHERE id = 30;
|
||||
UPDATE project_flocks SET location_id = 25, updated_at = NOW() WHERE id IN (25, 31);
|
||||
|
||||
-- 3. kandangs: K9, K72, K117 -> 18; K10, K73, K116 -> 25
|
||||
UPDATE kandangs SET location_id = 18, updated_at = NOW() WHERE id IN (9, 72, 117);
|
||||
UPDATE kandangs SET location_id = 25, updated_at = NOW() WHERE id IN (10, 73, 116);
|
||||
|
||||
-- 4. kandang_groups: KG 26, 68 -> 18; KG 27, 67 -> 25
|
||||
UPDATE kandang_groups SET location_id = 18, updated_at = NOW() WHERE id IN (26, 68);
|
||||
UPDATE kandang_groups SET location_id = 25, updated_at = NOW() WHERE id IN (27, 67);
|
||||
|
||||
-- 5. warehouses: W27, W145, W152 -> 18; W3, W146, W153 -> 25
|
||||
UPDATE warehouses SET location_id = 18, updated_at = NOW() WHERE id IN (27, 145, 152);
|
||||
UPDATE warehouses SET location_id = 25, updated_at = NOW() WHERE id IN (3, 146, 153);
|
||||
|
||||
-- 6. expenses: list eksplisit per location
|
||||
UPDATE expenses SET location_id = 18, updated_at = NOW()
|
||||
WHERE id IN (36, 345, 500, 501, 502, 503, 504, 505, 506, 507, 508);
|
||||
UPDATE expenses SET location_id = 25, updated_at = NOW()
|
||||
WHERE id IN (9, 37, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518);
|
||||
|
||||
COMMIT;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
BEGIN;
|
||||
|
||||
-- Konsolidasi 3 lokasi "Pullet Cikaum" jadi 1.
|
||||
-- Pindahkan semua data di loc 18 (Pullet Cikaum 1) & 25 (Pullet Cikaum 2) ke loc 2 (Pullet Cikaum).
|
||||
-- Urutan wajib: semua UPDATE child harus selesai SEBELUM soft-delete locations,
|
||||
-- karena trigger trg_soft_delete_fk_locations akan RAISE EXCEPTION untuk FK
|
||||
-- RESTRICT (project_flocks, kandangs, kandang_groups, expenses) atau SET NULL
|
||||
-- untuk warehouses kalau masih ada child yang reference.
|
||||
|
||||
-- 1. project_flocks (PF 25, 30, 31)
|
||||
UPDATE project_flocks SET location_id = 2, updated_at = NOW()
|
||||
WHERE location_id IN (18, 25);
|
||||
|
||||
-- 2. kandangs (K9, K72, K117, K10, K73, K116)
|
||||
UPDATE kandangs SET location_id = 2, updated_at = NOW()
|
||||
WHERE location_id IN (18, 25);
|
||||
|
||||
-- 3. kandang_groups (KG 26, 68, 27, 67)
|
||||
UPDATE kandang_groups SET location_id = 2, updated_at = NOW()
|
||||
WHERE location_id IN (18, 25);
|
||||
|
||||
-- 4. warehouses (W3, W27, W145, W146, W152, W153)
|
||||
UPDATE warehouses SET location_id = 2, updated_at = NOW()
|
||||
WHERE location_id IN (18, 25);
|
||||
|
||||
-- 5. expenses (23 row BOP)
|
||||
UPDATE expenses SET location_id = 2, updated_at = NOW()
|
||||
WHERE location_id IN (18, 25);
|
||||
|
||||
-- 6. Soft-delete locations 18 & 25 (kosong, aman karena semua child sudah pindah)
|
||||
UPDATE locations SET deleted_at = NOW()
|
||||
WHERE id IN (18, 25) AND deleted_at IS NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -75,9 +75,18 @@ func setMarketingExportColumns(file *excelize.File, sheet string) error {
|
||||
"B": 14,
|
||||
"C": 18,
|
||||
"D": 20,
|
||||
"E": 18,
|
||||
"F": 60,
|
||||
"G": 24,
|
||||
"E": 14,
|
||||
"F": 40,
|
||||
"G": 10,
|
||||
"H": 12,
|
||||
"I": 12,
|
||||
"J": 12,
|
||||
"K": 16,
|
||||
"L": 16,
|
||||
"M": 18,
|
||||
"N": 18,
|
||||
"O": 18,
|
||||
"P": 24,
|
||||
}
|
||||
|
||||
for col, width := range columnWidths {
|
||||
@@ -95,13 +104,22 @@ func setMarketingExportColumns(file *excelize.File, sheet string) error {
|
||||
|
||||
func setMarketingExportHeaders(file *excelize.File, sheet string) error {
|
||||
headers := []string{
|
||||
"No. Order",
|
||||
"Tanggal",
|
||||
"Status",
|
||||
"Customer",
|
||||
"Grand Total",
|
||||
"Products",
|
||||
"Notes",
|
||||
"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
|
||||
}
|
||||
|
||||
for i, header := range headers {
|
||||
@@ -130,7 +148,7 @@ func setMarketingExportHeaders(file *excelize.File, sheet string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "A1", "G1", headerStyle)
|
||||
return file.SetCellStyle(sheet, "A1", "P1", headerStyle)
|
||||
}
|
||||
|
||||
func setMarketingExportRows(file *excelize.File, sheet string, items []dto.MarketingListDTO) error {
|
||||
@@ -138,70 +156,154 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, item := range items {
|
||||
rowNumber := i + 2
|
||||
if err := file.SetCellValue(sheet, "A"+strconv.Itoa(rowNumber), safeMarketingExportText(item.SoNumber)); err != nil {
|
||||
row := 1
|
||||
for _, item := range items {
|
||||
soNumber := safeMarketingExportText(item.SoNumber)
|
||||
soDate := formatMarketingExportDate(item.SoDate)
|
||||
status := formatMarketingExportStatus(item)
|
||||
customer := safeMarketingExportText(item.Customer.Name)
|
||||
grandTotal := sumMarketingGrandTotal(item.SalesOrder)
|
||||
notes := safeMarketingExportText(item.Notes)
|
||||
|
||||
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,
|
||||
}
|
||||
for col, val := range vals {
|
||||
if err := file.SetCellValue(sheet, col+r, val); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "B"+strconv.Itoa(rowNumber), formatMarketingExportDate(item.SoDate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "C"+strconv.Itoa(rowNumber), formatMarketingExportStatus(item)); err != nil {
|
||||
return err
|
||||
continue
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "D"+strconv.Itoa(rowNumber), safeMarketingExportText(item.Customer.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "E"+strconv.Itoa(rowNumber), sumMarketingGrandTotal(item.SalesOrder)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "F"+strconv.Itoa(rowNumber), formatMarketingProducts(item.SalesOrder)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "G"+strconv.Itoa(rowNumber), safeMarketingExportText(item.Notes)); err != nil {
|
||||
return err
|
||||
|
||||
for _, prod := range item.SalesOrder {
|
||||
row++
|
||||
r := strconv.Itoa(row)
|
||||
|
||||
productName := "-"
|
||||
if prod.ProductWarehouse != nil && prod.ProductWarehouse.Product != nil {
|
||||
if n := strings.TrimSpace(prod.ProductWarehouse.Product.Name); n != "" {
|
||||
productName = n
|
||||
}
|
||||
}
|
||||
|
||||
lastRow := len(items) + 1
|
||||
week := "-"
|
||||
if prod.Week != nil {
|
||||
week = strconv.Itoa(*prod.Week)
|
||||
}
|
||||
|
||||
satuan := "-"
|
||||
if prod.ConvertionUnit != nil && strings.TrimSpace(*prod.ConvertionUnit) != "" {
|
||||
satuan = *prod.ConvertionUnit
|
||||
}
|
||||
|
||||
if err := file.SetCellValue(sheet, "A"+r, soNumber); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "B"+r, soDate); 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, safeMarketingExportText(prod.MarketingType)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "F"+r, productName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "G"+r, week); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "H"+r, prod.Qty); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "I"+r, satuan); err != nil {
|
||||
return err
|
||||
}
|
||||
if prod.TotalPeti != nil {
|
||||
if err := file.SetCellValue(sheet, "J"+r, *prod.TotalPeti); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := file.SetCellValue(sheet, "J"+r, "-"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "K"+r, prod.AvgWeight); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "L"+r, prod.TotalWeight); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "M"+r, prod.UnitPrice); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "N"+r, prod.TotalPrice); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "O"+r, grandTotal); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "P"+r, notes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastRow := row
|
||||
lastRowStr := strconv.Itoa(lastRow)
|
||||
border := []excelize.Border{
|
||||
{Type: "left", Color: "D1D5DB", Style: 1},
|
||||
{Type: "top", Color: "D1D5DB", Style: 1},
|
||||
{Type: "bottom", Color: "D1D5DB", Style: 1},
|
||||
{Type: "right", Color: "D1D5DB", Style: 1},
|
||||
}
|
||||
|
||||
dataStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "left",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "D1D5DB", Style: 1},
|
||||
{Type: "top", Color: "D1D5DB", Style: 1},
|
||||
{Type: "bottom", Color: "D1D5DB", Style: 1},
|
||||
{Type: "right", Color: "D1D5DB", Style: 1},
|
||||
},
|
||||
Alignment: &excelize.Alignment{Horizontal: "left", Vertical: "center", WrapText: true},
|
||||
Border: border,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.SetCellStyle(sheet, "A2", "G"+strconv.Itoa(lastRow), dataStyle); err != nil {
|
||||
if err := file.SetCellStyle(sheet, "A2", "P"+lastRowStr, dataStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
moneyStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "right",
|
||||
Vertical: "center",
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "D1D5DB", Style: 1},
|
||||
{Type: "top", Color: "D1D5DB", Style: 1},
|
||||
{Type: "bottom", Color: "D1D5DB", Style: 1},
|
||||
{Type: "right", Color: "D1D5DB", Style: 1},
|
||||
},
|
||||
numberStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{Horizontal: "right", Vertical: "center"},
|
||||
Border: border,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "K2", "O"+lastRowStr, numberStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "E2", "E"+strconv.Itoa(lastRow), moneyStyle)
|
||||
centerStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
|
||||
Border: border,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, col := range []string{"G", "H", "J"} {
|
||||
if err := file.SetCellStyle(sheet, col+"2", col+lastRowStr, centerStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatMarketingExportDate(value time.Time) string {
|
||||
@@ -225,36 +327,6 @@ func formatMarketingExportStatus(item dto.MarketingListDTO) string {
|
||||
return safeMarketingExportText(item.LatestApproval.StepName)
|
||||
}
|
||||
|
||||
func formatMarketingProducts(items []dto.DeliveryMarketingProductDTO) string {
|
||||
if len(items) == 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
names := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if item.ProductWarehouse == nil || item.ProductWarehouse.Product == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(item.ProductWarehouse.Product.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := seen[name]; exists {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
func sumMarketingGrandTotal(items []dto.DeliveryMarketingProductDTO) float64 {
|
||||
total := 0.0
|
||||
|
||||
@@ -29,6 +29,7 @@ type MarketingListDTO struct {
|
||||
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||
SoDocs string `json:"so_docs"`
|
||||
SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"`
|
||||
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
|
||||
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -203,6 +204,7 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M
|
||||
SalesPerson: salesPerson,
|
||||
SoDocs: marketing.SoDocs,
|
||||
SalesOrder: salesOrderProducts,
|
||||
DeliveryOrder: extractDeliveryGroupsFromProducts(marketing),
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: marketing.CreatedAt,
|
||||
UpdatedAt: marketing.UpdatedAt,
|
||||
@@ -376,6 +378,23 @@ func GenerateDeliveryOrderNumber(soNumber string, deliveryDate *time.Time, wareh
|
||||
return numberPrefix
|
||||
}
|
||||
|
||||
func extractDeliveryGroupsFromProducts(marketing *entity.Marketing) []DeliveryGroupDTO {
|
||||
var dps []MarketingDeliveryProductDTO
|
||||
for _, product := range marketing.Products {
|
||||
if product.DeliveryProduct == nil || product.DeliveryProduct.DeliveryDate == nil {
|
||||
continue
|
||||
}
|
||||
dp := ToMarketingDeliveryProductDTO(*product.DeliveryProduct)
|
||||
if product.ProductWarehouse.Id != 0 {
|
||||
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse)
|
||||
dp.ProductWarehouse = &mapped
|
||||
}
|
||||
dp.ConvertionUnit = product.ConvertionUnit
|
||||
dps = append(dps, dp)
|
||||
}
|
||||
return groupDeliveryProducts(dps, marketing.SoNumber)
|
||||
}
|
||||
|
||||
func collectDoNumbers(marketing *entity.Marketing) []string {
|
||||
if marketing == nil || len(marketing.Products) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
)
|
||||
|
||||
func isBalanceMonitoringExcelExportRequest(c *fiber.Ctx) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel")
|
||||
}
|
||||
|
||||
func exportBalanceMonitoringExcel(c *fiber.Ctx, items []dto.BalanceMonitoringRowDTO, totals dto.BalanceMonitoringTotalsDTO) error {
|
||||
content, err := buildBalanceMonitoringWorkbook(items, totals)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("laporan-balance-monitoring-%s.xlsx", time.Now().Format("2006-01-02-1504"))
|
||||
c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
return c.Status(fiber.StatusOK).Send(content)
|
||||
}
|
||||
|
||||
func buildBalanceMonitoringWorkbook(items []dto.BalanceMonitoringRowDTO, totals dto.BalanceMonitoringTotalsDTO) ([]byte, error) {
|
||||
file := excelize.NewFile()
|
||||
defer file.Close()
|
||||
|
||||
const sheet = "Balance Monitoring"
|
||||
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||
if defaultSheet != sheet {
|
||||
if err := file.SetSheetName(defaultSheet, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := setBalanceMonitoringColumns(file, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setBalanceMonitoringHeaders(file, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeBalanceMonitoringRows(file, sheet, items, totals); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := file.SetPanes(sheet, &excelize.Panes{
|
||||
Freeze: true,
|
||||
YSplit: 2,
|
||||
TopLeftCell: "A3",
|
||||
ActivePane: "bottomLeft",
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var bmColumnWidths = map[string]float64{
|
||||
"A": 5,
|
||||
"B": 28,
|
||||
"C": 18,
|
||||
"D": 12,
|
||||
"E": 12,
|
||||
"F": 20,
|
||||
"G": 12,
|
||||
"H": 12,
|
||||
"I": 20,
|
||||
"J": 20,
|
||||
"K": 18,
|
||||
"L": 12,
|
||||
"M": 16,
|
||||
"N": 20,
|
||||
}
|
||||
|
||||
func setBalanceMonitoringColumns(file *excelize.File, sheet string) error {
|
||||
for col, width := range bmColumnWidths {
|
||||
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetRowHeight(sheet, 1, 24); err != nil {
|
||||
return err
|
||||
}
|
||||
return file.SetRowHeight(sheet, 2, 24)
|
||||
}
|
||||
|
||||
func setBalanceMonitoringHeaders(file *excelize.File, sheet string) error {
|
||||
borderStyle := []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
}
|
||||
headerStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Color: "FFFFFF", Family: "Arial", Size: 10},
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Single-column headers: merge rows 1 and 2 vertically
|
||||
singleColHeaders := map[string]string{
|
||||
"A": "No",
|
||||
"B": "Customer",
|
||||
"C": "Saldo Awal",
|
||||
"J": "Penjualan Trading",
|
||||
"K": "Pembayaran",
|
||||
"L": "Aging",
|
||||
"M": "Aging Rata-Rata",
|
||||
"N": "Saldo Akhir",
|
||||
}
|
||||
for col, header := range singleColHeaders {
|
||||
if err := file.SetCellValue(sheet, col+"1", header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.MergeCell(sheet, col+"1", col+"2"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Group headers: merge columns horizontally in row 1
|
||||
if err := file.SetCellValue(sheet, "D1", "Penjualan Ayam"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.MergeCell(sheet, "D1", "F1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "G1", "Penjualan Telur"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.MergeCell(sheet, "G1", "I1"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sub-column headers in row 2
|
||||
subHeaders := map[string]string{
|
||||
"D": "Ekor",
|
||||
"E": "Kg",
|
||||
"F": "Nominal",
|
||||
"G": "Butir",
|
||||
"H": "Kg",
|
||||
"I": "Nominal",
|
||||
}
|
||||
for col, header := range subHeaders {
|
||||
if err := file.SetCellValue(sheet, col+"2", header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "A1", "N2", headerStyle)
|
||||
}
|
||||
|
||||
func writeBalanceMonitoringRows(file *excelize.File, sheet string, items []dto.BalanceMonitoringRowDTO, totals dto.BalanceMonitoringTotalsDTO) error {
|
||||
borderStyle := []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
}
|
||||
|
||||
dataStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Color: "000000", Family: "Arial", Size: 10},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
totalStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Color: "000000", Family: "Arial", Size: 10},
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"E2EFDA"}},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
redDataStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Color: "FF0000", Family: "Arial", Size: 10},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
redTotalStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Color: "FF0000", Family: "Arial", Size: 10},
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"E2EFDA"}},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, row := range items {
|
||||
rowNum := i + 3
|
||||
rowStr := strconv.Itoa(rowNum)
|
||||
|
||||
cells := map[string]interface{}{
|
||||
"A": i + 1,
|
||||
"B": row.Customer.Name,
|
||||
"C": row.SaldoAwal,
|
||||
"D": row.PenjualanAyam.Ekor,
|
||||
"E": row.PenjualanAyam.Kg,
|
||||
"F": row.PenjualanAyam.Nominal,
|
||||
"G": row.PenjualanTelur.Butir,
|
||||
"H": row.PenjualanTelur.Kg,
|
||||
"I": row.PenjualanTelur.Nominal,
|
||||
"J": row.PenjualanTrading.Nominal,
|
||||
"K": row.Pembayaran,
|
||||
"L": fmt.Sprintf("%d hari", row.Aging),
|
||||
"M": formatBMAging(row.AgingRataRata),
|
||||
"N": row.SaldoAkhir,
|
||||
}
|
||||
for col, val := range cells {
|
||||
if err := file.SetCellValue(sheet, col+rowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A"+rowStr, "N"+rowStr, dataStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.SaldoAkhir < 0 {
|
||||
if err := file.SetCellStyle(sheet, "N"+rowStr, "N"+rowStr, redDataStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Totals row
|
||||
totalRowStr := strconv.Itoa(len(items) + 3)
|
||||
totalCells := map[string]interface{}{
|
||||
"A": "Total",
|
||||
"C": totals.SaldoAwal,
|
||||
"D": totals.PenjualanAyam.Ekor,
|
||||
"E": totals.PenjualanAyam.Kg,
|
||||
"F": totals.PenjualanAyam.Nominal,
|
||||
"G": totals.PenjualanTelur.Butir,
|
||||
"H": totals.PenjualanTelur.Kg,
|
||||
"I": totals.PenjualanTelur.Nominal,
|
||||
"J": totals.PenjualanTrading.Nominal,
|
||||
"K": totals.Pembayaran,
|
||||
"N": totals.SaldoAkhir,
|
||||
}
|
||||
for col, val := range totalCells {
|
||||
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A"+totalRowStr, "N"+totalRowStr, totalStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
if totals.SaldoAkhir < 0 {
|
||||
if err := file.SetCellStyle(sheet, "N"+totalRowStr, "N"+totalRowStr, redTotalStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatBMAging(v float64) string {
|
||||
s := strconv.FormatFloat(v, 'f', 2, 64)
|
||||
s = strings.ReplaceAll(s, ".", ",")
|
||||
return s + " hari"
|
||||
}
|
||||
@@ -324,6 +324,13 @@ func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPurchaseSupplierExcelExportRequest(ctx) {
|
||||
return exportPurchaseSupplierExcel(ctx, result)
|
||||
}
|
||||
if isPurchaseSupplierExcelAllExportRequest(ctx) {
|
||||
return exportPurchaseSupplierExcelAll(ctx, result)
|
||||
}
|
||||
|
||||
filters := map[string]interface{}{
|
||||
"area_id": query.AreaIDs,
|
||||
"supplier_id": query.SupplierIDs,
|
||||
@@ -555,6 +562,10 @@ func (c *RepportController) GetBalanceMonitoring(ctx *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if isBalanceMonitoringExcelExportRequest(ctx) {
|
||||
return exportBalanceMonitoringExcel(ctx, result, totals)
|
||||
}
|
||||
|
||||
limit := query.Limit
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
)
|
||||
|
||||
func isPurchaseSupplierExcelExportRequest(c *fiber.Ctx) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel")
|
||||
}
|
||||
|
||||
func isPurchaseSupplierExcelAllExportRequest(c *fiber.Ctx) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel-all")
|
||||
}
|
||||
|
||||
func exportPurchaseSupplierExcel(c *fiber.Ctx, items []dto.PurchaseSupplierDTO) error {
|
||||
content, err := buildPurchaseSupplierWorkbook(items)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("laporan-pembelian-supplier-%s.xlsx", time.Now().Format("2006-01-02-1504"))
|
||||
c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
return c.Status(fiber.StatusOK).Send(content)
|
||||
}
|
||||
|
||||
func exportPurchaseSupplierExcelAll(c *fiber.Ctx, items []dto.PurchaseSupplierDTO) error {
|
||||
content, err := buildPurchaseSupplierAllWorkbook(items)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("laporan-pembelian-supplier-all-%s.xlsx", time.Now().Format("2006-01-02-1504"))
|
||||
c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
return c.Status(fiber.StatusOK).Send(content)
|
||||
}
|
||||
|
||||
// buildPurchaseSupplierWorkbook creates a workbook with one sheet per supplier.
|
||||
func buildPurchaseSupplierWorkbook(items []dto.PurchaseSupplierDTO) ([]byte, error) {
|
||||
file := excelize.NewFile()
|
||||
defer file.Close()
|
||||
|
||||
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||
|
||||
if len(items) == 0 {
|
||||
if err := writePurchaseSupplierSheet(file, defaultSheet, dto.PurchaseSupplierDTO{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
for idx, item := range items {
|
||||
sheetName := sanitizePurchaseSupplierSheetName(purchaseSupplierName(item))
|
||||
if sheetName == "" {
|
||||
sheetName = fmt.Sprintf("Supplier %d", idx+1)
|
||||
}
|
||||
|
||||
if idx == 0 {
|
||||
if defaultSheet != sheetName {
|
||||
if err := file.SetSheetName(defaultSheet, sheetName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := file.NewSheet(sheetName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writePurchaseSupplierSheet(file, sheetName, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
buf, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// buildPurchaseSupplierAllWorkbook creates a single-sheet workbook with all suppliers.
|
||||
func buildPurchaseSupplierAllWorkbook(items []dto.PurchaseSupplierDTO) ([]byte, error) {
|
||||
file := excelize.NewFile()
|
||||
defer file.Close()
|
||||
|
||||
const sheet = "Rekap Pembelian Supplier"
|
||||
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||
if defaultSheet != sheet {
|
||||
if err := file.SetSheetName(defaultSheet, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := setPurchaseSupplierAllColumns(file, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setPurchaseSupplierAllHeaders(file, sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writePurchaseSupplierAllRows(file, sheet, items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := file.SetPanes(sheet, &excelize.Panes{
|
||||
Freeze: true,
|
||||
YSplit: 1,
|
||||
TopLeftCell: "A2",
|
||||
ActivePane: "bottomLeft",
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var purchaseSupplierSheetHeaders = []string{
|
||||
"No",
|
||||
"Tanggal Terima",
|
||||
"Tanggal PO",
|
||||
"No. Referensi",
|
||||
"Nama Produk",
|
||||
"Tujuan",
|
||||
"QTY",
|
||||
"Harga Beli (Rp)",
|
||||
"Value Harga Beli (Rp)",
|
||||
"Transport (Rp)",
|
||||
"Value Transport (Rp)",
|
||||
"Jumlah (Rp)",
|
||||
"Ekspedisi",
|
||||
"Surat Jalan",
|
||||
}
|
||||
|
||||
var purchaseSupplierAllSheetHeaders = append([]string{"Supplier"}, purchaseSupplierSheetHeaders...)
|
||||
|
||||
var purchaseSupplierSheetColumnWidths = map[string]float64{
|
||||
"A": 5,
|
||||
"B": 14,
|
||||
"C": 12,
|
||||
"D": 16,
|
||||
"E": 20,
|
||||
"F": 20,
|
||||
"G": 10,
|
||||
"H": 20,
|
||||
"I": 20,
|
||||
"J": 22,
|
||||
"K": 22,
|
||||
"L": 16,
|
||||
"M": 20,
|
||||
"N": 20,
|
||||
}
|
||||
|
||||
var purchaseSupplierAllSheetColumnWidths = map[string]float64{
|
||||
"A": 24,
|
||||
"B": 6,
|
||||
"C": 14,
|
||||
"D": 12,
|
||||
"E": 16,
|
||||
"F": 20,
|
||||
"G": 20,
|
||||
"H": 10,
|
||||
"I": 20,
|
||||
"J": 20,
|
||||
"K": 22,
|
||||
"L": 22,
|
||||
"M": 16,
|
||||
"N": 20,
|
||||
"O": 20,
|
||||
}
|
||||
|
||||
func writePurchaseSupplierSheet(file *excelize.File, sheet string, item dto.PurchaseSupplierDTO) error {
|
||||
for col, width := range purchaseSupplierSheetColumnWidths {
|
||||
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, h := range purchaseSupplierSheetHeaders {
|
||||
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, row := range item.Rows {
|
||||
rowNum := i + 2
|
||||
rowStr := fmt.Sprintf("%d", rowNum)
|
||||
|
||||
values := purchaseSupplierRowCells(row, i+1)
|
||||
for colIdx, val := range values {
|
||||
col, _ := excelize.ColumnNumberToName(colIdx + 1)
|
||||
if err := file.SetCellValue(sheet, col+rowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary row
|
||||
totalRowNum := len(item.Rows) + 2
|
||||
totalRowStr := fmt.Sprintf("%d", totalRowNum)
|
||||
totalCells := map[string]interface{}{
|
||||
"A": "Total",
|
||||
"G": item.Summary.TotalQty,
|
||||
"H": item.Summary.TotalUnitPrice,
|
||||
"I": item.Summary.TotalPurchaseValue,
|
||||
"J": item.Summary.TotalTransportUnitPrice,
|
||||
"K": item.Summary.TotalTransportValue,
|
||||
"L": item.Summary.TotalAmount,
|
||||
}
|
||||
for col, val := range totalCells {
|
||||
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setPurchaseSupplierAllColumns(file *excelize.File, sheet string) error {
|
||||
for col, width := range purchaseSupplierAllSheetColumnWidths {
|
||||
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetRowHeight(sheet, 1, 24); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setPurchaseSupplierAllHeaders(file *excelize.File, sheet string) error {
|
||||
headerStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Color: "FFFFFF", Family: "Arial", Size: 10},
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, h := range purchaseSupplierAllSheetHeaders {
|
||||
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lastCol, _ := excelize.ColumnNumberToName(len(purchaseSupplierAllSheetHeaders))
|
||||
return file.SetCellStyle(sheet, "A1", lastCol+"1", headerStyle)
|
||||
}
|
||||
|
||||
func writePurchaseSupplierAllRows(file *excelize.File, sheet string, items []dto.PurchaseSupplierDTO) error {
|
||||
borderStyle := []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
}
|
||||
|
||||
dataStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Color: "000000", Family: "Arial", Size: 10},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
totalStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Color: "000000", Family: "Arial", Size: 10},
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"E2EFDA"}},
|
||||
Alignment: &excelize.Alignment{Vertical: "center", WrapText: true},
|
||||
Border: borderStyle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastHeaderCol, _ := excelize.ColumnNumberToName(len(purchaseSupplierAllSheetHeaders))
|
||||
|
||||
currentRow := 2
|
||||
for _, item := range items {
|
||||
supplierName := purchaseSupplierName(item)
|
||||
|
||||
// Data rows
|
||||
for seq, row := range item.Rows {
|
||||
rowStr := fmt.Sprintf("%d", currentRow)
|
||||
if err := file.SetCellValue(sheet, "A"+rowStr, supplierName); err != nil {
|
||||
return err
|
||||
}
|
||||
values := purchaseSupplierRowCells(row, seq+1)
|
||||
for colIdx, val := range values {
|
||||
col, _ := excelize.ColumnNumberToName(colIdx + 2)
|
||||
if err := file.SetCellValue(sheet, col+rowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A"+rowStr, lastHeaderCol+rowStr, dataStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
currentRow++
|
||||
}
|
||||
|
||||
// Summary row
|
||||
totalRowStr := fmt.Sprintf("%d", currentRow)
|
||||
totalCells := map[string]interface{}{
|
||||
"A": supplierName,
|
||||
"B": "Total",
|
||||
"H": item.Summary.TotalQty,
|
||||
"I": item.Summary.TotalUnitPrice,
|
||||
"J": item.Summary.TotalPurchaseValue,
|
||||
"K": item.Summary.TotalTransportUnitPrice,
|
||||
"L": item.Summary.TotalTransportValue,
|
||||
"M": item.Summary.TotalAmount,
|
||||
}
|
||||
for col, val := range totalCells {
|
||||
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A"+totalRowStr, lastHeaderCol+totalRowStr, totalStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
currentRow++
|
||||
|
||||
// Empty separator row
|
||||
currentRow++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// purchaseSupplierRowCells returns cell values for one data row.
|
||||
func purchaseSupplierRowCells(row dto.PurchaseSupplierRowDTO, seq int) []interface{} {
|
||||
productName := "-"
|
||||
if row.Product != nil && strings.TrimSpace(row.Product.Name) != "" {
|
||||
productName = row.Product.Name
|
||||
}
|
||||
warehouseName := "-"
|
||||
if row.Warehouse != nil && strings.TrimSpace(row.Warehouse.Name) != "" {
|
||||
warehouseName = row.Warehouse.Name
|
||||
}
|
||||
|
||||
return []interface{}{
|
||||
seq,
|
||||
safePurchaseSupplierText(row.ReceiveDate),
|
||||
safePurchaseSupplierText(row.PoDate),
|
||||
safePurchaseSupplierText(row.PoNumber),
|
||||
productName,
|
||||
warehouseName,
|
||||
row.Qty,
|
||||
row.UnitPrice,
|
||||
row.PurchaseValue,
|
||||
row.TransportUnitPrice,
|
||||
row.TransportValue,
|
||||
row.TotalAmount,
|
||||
safePurchaseSupplierText(row.Expedition),
|
||||
safePurchaseSupplierText(row.DeliveryNumber),
|
||||
}
|
||||
}
|
||||
|
||||
func purchaseSupplierName(item dto.PurchaseSupplierDTO) string {
|
||||
if item.Supplier != nil && strings.TrimSpace(item.Supplier.Name) != "" {
|
||||
return item.Supplier.Name
|
||||
}
|
||||
return "Supplier"
|
||||
}
|
||||
|
||||
func sanitizePurchaseSupplierSheetName(name string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
":", " ", "\\", " ", "/", " ",
|
||||
"?", " ", "*", " ", "[", " ", "]", " ",
|
||||
)
|
||||
sanitized := strings.TrimSpace(replacer.Replace(name))
|
||||
if sanitized == "" {
|
||||
return "Sheet"
|
||||
}
|
||||
runes := []rune(sanitized)
|
||||
if len(runes) > 31 {
|
||||
return string(runes[:31])
|
||||
}
|
||||
return sanitized
|
||||
}
|
||||
|
||||
func safePurchaseSupplierText(s string) string {
|
||||
t := strings.TrimSpace(s)
|
||||
if t == "" {
|
||||
return "-"
|
||||
}
|
||||
return t
|
||||
}
|
||||
Reference in New Issue
Block a user