mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8623e2f7c | |||
| 907b695526 |
@@ -542,15 +542,9 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
||||||
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
|
||||||
marketingRepoTx := marketingRepo.NewMarketingRepository(dbTransaction)
|
marketingRepoTx := marketingRepo.NewMarketingRepository(dbTransaction)
|
||||||
|
|
||||||
marketing, err := marketingRepoTx.GetByID(c.Context(), id, nil)
|
marketing, err := marketingRepoTx.GetByID(c.Context(), id, nil)
|
||||||
@@ -636,23 +630,6 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if latestApproval != nil && latestApproval.StepNumber == uint16(utils.MarketingDeliveryOrder) {
|
|
||||||
action := entity.ApprovalActionUpdated
|
|
||||||
_, err := approvalSvcTx.CreateApproval(
|
|
||||||
c.Context(),
|
|
||||||
utils.ApprovalWorkflowMarketing,
|
|
||||||
id,
|
|
||||||
utils.MarketingStepSalesOrder,
|
|
||||||
&action,
|
|
||||||
actorID,
|
|
||||||
nil)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset approval to Sales Order")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
c.Context(),
|
c.Context(),
|
||||||
utils.ApprovalWorkflowMarketing,
|
utils.ApprovalWorkflowMarketing,
|
||||||
id,
|
id,
|
||||||
utils.MarketingStepPengajuan,
|
approvalutils.ApprovalStep(latestApproval.StepNumber),
|
||||||
&action,
|
&action,
|
||||||
actorID,
|
actorID,
|
||||||
nil)
|
nil)
|
||||||
|
|||||||
@@ -392,6 +392,13 @@ func (c *RepportController) GetDebtSupplier(ctx *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isDebtSupplierExcelExportRequest(ctx) {
|
||||||
|
return exportDebtSupplierExcel(ctx, result)
|
||||||
|
}
|
||||||
|
if isDebtSupplierExcelAllExportRequest(ctx) {
|
||||||
|
return exportDebtSupplierExcelAll(ctx, result)
|
||||||
|
}
|
||||||
|
|
||||||
supplierIDs = query.SupplierIDs
|
supplierIDs = query.SupplierIDs
|
||||||
if supplierIDs == nil {
|
if supplierIDs == nil {
|
||||||
supplierIDs = []int64{}
|
supplierIDs = []int64{}
|
||||||
@@ -478,6 +485,13 @@ func (c *RepportController) GetCustomerPayment(ctx *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isCustomerPaymentExcelExportRequest(ctx) {
|
||||||
|
return exportCustomerPaymentExcel(ctx, result)
|
||||||
|
}
|
||||||
|
if isCustomerPaymentExcelAllExportRequest(ctx) {
|
||||||
|
return exportCustomerPaymentExcelAll(ctx, result)
|
||||||
|
}
|
||||||
|
|
||||||
// If single customer mode (only 1 customer ID), return without pagination
|
// If single customer mode (only 1 customer ID), return without pagination
|
||||||
if len(customerIDs) == 1 {
|
if len(customerIDs) == 1 {
|
||||||
return ctx.Status(fiber.StatusOK).
|
return ctx.Status(fiber.StatusOK).
|
||||||
@@ -505,6 +519,83 @@ func (c *RepportController) GetCustomerPayment(ctx *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BalanceMonitoringResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Meta response.Meta `json:"meta"`
|
||||||
|
Data []dto.BalanceMonitoringRowDTO `json:"data"`
|
||||||
|
Totals dto.BalanceMonitoringTotalsDTO `json:"totals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RepportController) GetBalanceMonitoring(ctx *fiber.Ctx) error {
|
||||||
|
customerIDs, err := parseUintCSV(ctx.Query("customer_ids"))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "customer_ids must be comma separated positive integers")
|
||||||
|
}
|
||||||
|
salesIDs, err := parseUintCSV(ctx.Query("sales_ids"))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "sales_ids must be comma separated positive integers")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &validation.BalanceMonitoringQuery{
|
||||||
|
Page: ctx.QueryInt("page", 1),
|
||||||
|
Limit: ctx.QueryInt("limit", 10),
|
||||||
|
CustomerIDs: customerIDs,
|
||||||
|
SalesIDs: salesIDs,
|
||||||
|
FilterBy: strings.ToLower(ctx.Query("filter_by", "")),
|
||||||
|
SortBy: ctx.Query("sort_by", ""),
|
||||||
|
SortOrder: ctx.Query("sort_order", ""),
|
||||||
|
StartDate: ctx.Query("start_date", ""),
|
||||||
|
EndDate: ctx.Query("end_date", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totals, totalResults, err := c.RepportService.GetBalanceMonitoring(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := query.Limit
|
||||||
|
if limit < 1 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(BalanceMonitoringResponse{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get balance monitoring report successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: result,
|
||||||
|
Totals: totals,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUintCSV(raw string) ([]uint, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(raw, ",")
|
||||||
|
result := make([]uint, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseUint(part, 10, 32)
|
||||||
|
if err != nil || id == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid id: %s", part)
|
||||||
|
}
|
||||||
|
result = append(result, uint(id))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error {
|
func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error {
|
||||||
idParam := ctx.Params("idProjectFlockKandang")
|
idParam := ctx.Params("idProjectFlockKandang")
|
||||||
if idParam == "" {
|
if idParam == "" {
|
||||||
|
|||||||
@@ -0,0 +1,585 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isCustomerPaymentExcelExportRequest(c *fiber.Ctx) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCustomerPaymentExcelAllExportRequest(c *fiber.Ctx) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel-all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportCustomerPaymentExcel(c *fiber.Ctx, items []dto.CustomerPaymentReportItem) error {
|
||||||
|
content, err := buildCustomerPaymentWorkbook(items)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("laporan-kontrol-pembayaran-customer-%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 exportCustomerPaymentExcelAll(c *fiber.Ctx, items []dto.CustomerPaymentReportItem) error {
|
||||||
|
content, err := buildCustomerPaymentAllWorkbook(items)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("laporan-kontrol-pembayaran-customer-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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCustomerPaymentWorkbook(items []dto.CustomerPaymentReportItem) ([]byte, error) {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
if err := writeCustomerPaymentSheet(file, defaultSheet, dto.CustomerPaymentReportItem{}); 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 := sanitizeCustomerPaymentSheetName(customerPaymentName(item))
|
||||||
|
if sheetName == "" {
|
||||||
|
sheetName = fmt.Sprintf("Customer %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 := writeCustomerPaymentSheet(file, sheetName, item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := file.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCustomerPaymentAllWorkbook(items []dto.CustomerPaymentReportItem) ([]byte, error) {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
const sheet = "Kontrol Pembayaran Customer"
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
if defaultSheet != sheet {
|
||||||
|
if err := file.SetSheetName(defaultSheet, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setCustomerPaymentAllColumns(file, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setCustomerPaymentAllHeaders(file, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := writeCustomerPaymentAllRows(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 cpSheetHeaders = []string{
|
||||||
|
"No",
|
||||||
|
"Tanggal DO/Bayar",
|
||||||
|
"Tanggal Realisasi",
|
||||||
|
"Aging",
|
||||||
|
"Referensi",
|
||||||
|
"Nomor Polisi",
|
||||||
|
"Ekor/Qty",
|
||||||
|
"Berat (Kg)",
|
||||||
|
"AVG",
|
||||||
|
"Harga/Unit (Rp)",
|
||||||
|
"Harga Akhir (Rp)",
|
||||||
|
"Total (Rp)",
|
||||||
|
"Pembayaran (Rp)",
|
||||||
|
"Saldo Piutang (Rp)",
|
||||||
|
"Keterangan",
|
||||||
|
"Pengambilan",
|
||||||
|
"Sales/Marketing",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cpAllSheetHeaders = append([]string{"Customer"}, cpSheetHeaders...)
|
||||||
|
|
||||||
|
var cpSheetColumnWidths = map[string]float64{
|
||||||
|
"A": 5,
|
||||||
|
"B": 15,
|
||||||
|
"C": 12,
|
||||||
|
"D": 8,
|
||||||
|
"E": 12,
|
||||||
|
"F": 15,
|
||||||
|
"G": 10,
|
||||||
|
"H": 12,
|
||||||
|
"I": 10,
|
||||||
|
"J": 15,
|
||||||
|
"K": 15,
|
||||||
|
"L": 15,
|
||||||
|
"M": 15,
|
||||||
|
"N": 15,
|
||||||
|
"O": 20,
|
||||||
|
"P": 15,
|
||||||
|
"Q": 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cpAllSheetColumnWidths = map[string]float64{
|
||||||
|
"A": 22,
|
||||||
|
"B": 6,
|
||||||
|
"C": 15,
|
||||||
|
"D": 15,
|
||||||
|
"E": 8,
|
||||||
|
"F": 12,
|
||||||
|
"G": 15,
|
||||||
|
"H": 10,
|
||||||
|
"I": 12,
|
||||||
|
"J": 10,
|
||||||
|
"K": 15,
|
||||||
|
"L": 15,
|
||||||
|
"M": 15,
|
||||||
|
"N": 15,
|
||||||
|
"O": 15,
|
||||||
|
"P": 20,
|
||||||
|
"Q": 15,
|
||||||
|
"R": 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCustomerPaymentSheet(file *excelize.File, sheet string, item dto.CustomerPaymentReportItem) error {
|
||||||
|
for col, width := range cpSheetColumnWidths {
|
||||||
|
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 1: headers
|
||||||
|
for i, h := range cpSheetHeaders {
|
||||||
|
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||||
|
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{Color: "FF0000"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 2: saldo awal
|
||||||
|
initialFormatted := formatCPRupiah(item.InitialBalance)
|
||||||
|
if err := file.SetCellValue(sheet, "N2", initialFormatted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item.InitialBalance < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "N2", "N2", redStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rows 3+: data rows
|
||||||
|
for i, row := range item.Rows {
|
||||||
|
rowNum := i + 3
|
||||||
|
rowStr := fmt.Sprintf("%d", rowNum)
|
||||||
|
|
||||||
|
cells := customerPaymentRowCells(row, i+1)
|
||||||
|
for colIdx, val := range cells {
|
||||||
|
col, _ := excelize.ColumnNumberToName(colIdx + 1)
|
||||||
|
if err := file.SetCellValue(sheet, col+rowStr, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.AccountsReceivable < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "N"+rowStr, "N"+rowStr, redStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total row
|
||||||
|
totalRowNum := len(item.Rows) + 3
|
||||||
|
totalRowStr := fmt.Sprintf("%d", totalRowNum)
|
||||||
|
|
||||||
|
totalCells := map[string]string{
|
||||||
|
"A": "Total",
|
||||||
|
"G": formatCPIDInteger(item.Summary.TotalQty),
|
||||||
|
"H": formatCPIDInteger(item.Summary.TotalWeight),
|
||||||
|
"K": formatCPRupiah(item.Summary.TotalFinalAmount),
|
||||||
|
"L": formatCPRupiah(item.Summary.TotalGrandAmount),
|
||||||
|
"M": formatCPRupiah(item.Summary.TotalPayment),
|
||||||
|
"N": formatCPRupiah(item.Summary.TotalAccountsReceivable),
|
||||||
|
}
|
||||||
|
for col, val := range totalCells {
|
||||||
|
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item.Summary.TotalAccountsReceivable < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "N"+totalRowStr, "N"+totalRowStr, redStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCustomerPaymentAllColumns(file *excelize.File, sheet string) error {
|
||||||
|
for col, width := range cpAllSheetColumnWidths {
|
||||||
|
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file.SetRowHeight(sheet, 1, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCustomerPaymentAllHeaders(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
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, h := range cpAllSheetHeaders {
|
||||||
|
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||||
|
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCol, _ := excelize.ColumnNumberToName(len(cpAllSheetHeaders))
|
||||||
|
return file.SetCellStyle(sheet, "A1", lastCol+"1", headerStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCustomerPaymentAllRows(file *excelize.File, sheet string, items []dto.CustomerPaymentReportItem) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHeaderCol, _ := excelize.ColumnNumberToName(len(cpAllSheetHeaders))
|
||||||
|
currentRow := 2
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
name := customerPaymentName(item)
|
||||||
|
|
||||||
|
// Saldo awal row
|
||||||
|
saldoStr := fmt.Sprintf("%d", currentRow)
|
||||||
|
if err := file.SetCellValue(sheet, "A"+saldoStr, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initialFormatted := formatCPRupiah(item.InitialBalance)
|
||||||
|
if err := file.SetCellValue(sheet, "O"+saldoStr, initialFormatted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "A"+saldoStr, lastHeaderCol+saldoStr, dataStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item.InitialBalance < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "O"+saldoStr, "O"+saldoStr, redDataStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentRow++
|
||||||
|
|
||||||
|
// Data rows
|
||||||
|
for seq, row := range item.Rows {
|
||||||
|
rowStr := fmt.Sprintf("%d", currentRow)
|
||||||
|
if err := file.SetCellValue(sheet, "A"+rowStr, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cells := customerPaymentRowCells(row, seq+1)
|
||||||
|
for colIdx, val := range cells {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if row.AccountsReceivable < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "O"+rowStr, "O"+rowStr, redDataStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentRow++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total row
|
||||||
|
totalStr := fmt.Sprintf("%d", currentRow)
|
||||||
|
totalCells := map[string]string{
|
||||||
|
"A": name,
|
||||||
|
"B": "Total",
|
||||||
|
"H": formatCPIDInteger(item.Summary.TotalQty),
|
||||||
|
"I": formatCPIDInteger(item.Summary.TotalWeight),
|
||||||
|
"L": formatCPRupiah(item.Summary.TotalFinalAmount),
|
||||||
|
"M": formatCPRupiah(item.Summary.TotalGrandAmount),
|
||||||
|
"N": formatCPRupiah(item.Summary.TotalPayment),
|
||||||
|
"O": formatCPRupiah(item.Summary.TotalAccountsReceivable),
|
||||||
|
}
|
||||||
|
for col, val := range totalCells {
|
||||||
|
if err := file.SetCellValue(sheet, col+totalStr, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "A"+totalStr, lastHeaderCol+totalStr, totalStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item.Summary.TotalAccountsReceivable < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "O"+totalStr, "O"+totalStr, redTotalStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentRow++
|
||||||
|
|
||||||
|
// Empty separator row
|
||||||
|
currentRow++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// customerPaymentRowCells returns 17 cell values for cols A..Q.
|
||||||
|
func customerPaymentRowCells(row dto.CustomerPaymentReportRow, seq int) []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
seq,
|
||||||
|
formatCPDate(row.TransDate),
|
||||||
|
formatCPOptionalDate(row.DeliveryDate),
|
||||||
|
formatCPAging(row.AgingDay),
|
||||||
|
safeCPText(row.Reference),
|
||||||
|
joinCPStrings(row.VehicleNumbers),
|
||||||
|
formatCPIDInteger(row.Qty),
|
||||||
|
formatCPIDInteger(row.Weight),
|
||||||
|
formatCPAvg(row.AverageWeight),
|
||||||
|
formatCPRupiah(row.UnitPrice),
|
||||||
|
formatCPRupiah(row.FinalPrice),
|
||||||
|
formatCPRupiah(row.TotalPrice),
|
||||||
|
formatCPRupiah(row.PaymentAmount),
|
||||||
|
formatCPRupiah(row.AccountsReceivable),
|
||||||
|
safeCPText(row.Status),
|
||||||
|
joinCPStrings(row.PickupInfo),
|
||||||
|
safeCPText(row.SalesPerson),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func customerPaymentName(item dto.CustomerPaymentReportItem) string {
|
||||||
|
name := strings.TrimSpace(item.Customer.Name)
|
||||||
|
if name == "" {
|
||||||
|
return "Customer"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeCustomerPaymentSheetName(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var cpIndonesianMonths = [12]string{
|
||||||
|
"Jan", "Feb", "Mar", "Apr", "Mei", "Jun",
|
||||||
|
"Jul", "Agu", "Sep", "Okt", "Nov", "Des",
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPDate(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
loc, err := time.LoadLocation("Asia/Jakarta")
|
||||||
|
if err == nil {
|
||||||
|
t = t.In(loc)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02d %s %d", t.Day(), cpIndonesianMonths[t.Month()-1], t.Year())
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPOptionalDate(t *time.Time) string {
|
||||||
|
if t == nil || t.IsZero() {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return formatCPDate(*t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPAging(v *int) string {
|
||||||
|
if v == nil {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPIDInteger(v float64) string {
|
||||||
|
n := int64(math.Round(v))
|
||||||
|
if n == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
negative := n < 0
|
||||||
|
abs := n
|
||||||
|
if negative {
|
||||||
|
abs = -n
|
||||||
|
}
|
||||||
|
s := strconv.FormatInt(abs, 10)
|
||||||
|
// insert dots as thousand separators
|
||||||
|
var b strings.Builder
|
||||||
|
start := len(s) % 3
|
||||||
|
if start == 0 {
|
||||||
|
start = 3
|
||||||
|
}
|
||||||
|
b.WriteString(s[:start])
|
||||||
|
for i := start; i < len(s); i += 3 {
|
||||||
|
b.WriteByte('.')
|
||||||
|
b.WriteString(s[i : i+3])
|
||||||
|
}
|
||||||
|
if negative {
|
||||||
|
return "-" + b.String()
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPRupiah(v float64) string {
|
||||||
|
const nbsp = " "
|
||||||
|
if v < 0 {
|
||||||
|
return "-Rp" + nbsp + formatCPIDInteger(-v)
|
||||||
|
}
|
||||||
|
return "Rp" + nbsp + formatCPIDInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCPAvg(v float64) string {
|
||||||
|
if v == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
s := strconv.FormatFloat(v, 'f', 2, 64)
|
||||||
|
return strings.ReplaceAll(s, ".", ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeCPText(s string) string {
|
||||||
|
t := strings.TrimSpace(s)
|
||||||
|
if t == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinCPStrings(ss []string) string {
|
||||||
|
var parts []string
|
||||||
|
for _, s := range ss {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s != "" {
|
||||||
|
parts = append(parts, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "\n")
|
||||||
|
}
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
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 isDebtSupplierExcelExportRequest(c *fiber.Ctx) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDebtSupplierExcelAllExportRequest(c *fiber.Ctx) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel-all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportDebtSupplierExcel(c *fiber.Ctx, items []dto.DebtSupplierDTO) error {
|
||||||
|
content, err := buildDebtSupplierWorkbook(items)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("laporan-hutang-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 exportDebtSupplierExcelAll(c *fiber.Ctx, items []dto.DebtSupplierDTO) error {
|
||||||
|
content, err := buildDebtSupplierAllWorkbook(items)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("laporan-hutang-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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDebtSupplierWorkbook creates a workbook with one sheet per supplier.
|
||||||
|
func buildDebtSupplierWorkbook(items []dto.DebtSupplierDTO) ([]byte, error) {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
if err := writeDebtSupplierSheet(file, defaultSheet, dto.DebtSupplierDTO{}); 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 := sanitizeDebtSupplierSheetName(debtSupplierName(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 := writeDebtSupplierSheet(file, sheetName, item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := file.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDebtSupplierAllWorkbook creates a single-sheet workbook with purchase-supplier styling.
|
||||||
|
func buildDebtSupplierAllWorkbook(items []dto.DebtSupplierDTO) ([]byte, error) {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
const sheet = "Rekap Hutang Supplier"
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
if defaultSheet != sheet {
|
||||||
|
if err := file.SetSheetName(defaultSheet, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setDebtSupplierAllColumns(file, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setDebtSupplierAllHeaders(file, sheet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := writeDebtSupplierAllRows(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 debtSupplierSheetHeaders = []string{
|
||||||
|
"No",
|
||||||
|
"Nomor PR",
|
||||||
|
"Nomor PO",
|
||||||
|
"Tanggal Terima/Bayar",
|
||||||
|
"Tanggal PO",
|
||||||
|
"Aging (Hari)",
|
||||||
|
"Area",
|
||||||
|
"Gudang",
|
||||||
|
"Jatuh Tempo",
|
||||||
|
"Status Jatuh Tempo",
|
||||||
|
"Nominal Pembelian (Rp)",
|
||||||
|
"Pembayaran (Rp)",
|
||||||
|
"Sisa Saldo Hutang (Rp)",
|
||||||
|
"Status",
|
||||||
|
"Nomor Perjalanan",
|
||||||
|
}
|
||||||
|
|
||||||
|
var debtSupplierAllSheetHeaders = append([]string{"Supplier"}, debtSupplierSheetHeaders...)
|
||||||
|
|
||||||
|
var debtSupplierSheetColumnWidths = map[string]float64{
|
||||||
|
"A": 5,
|
||||||
|
"B": 14,
|
||||||
|
"C": 12,
|
||||||
|
"D": 20,
|
||||||
|
"E": 10,
|
||||||
|
"F": 12,
|
||||||
|
"G": 15,
|
||||||
|
"H": 20,
|
||||||
|
"I": 12,
|
||||||
|
"J": 20,
|
||||||
|
"K": 20,
|
||||||
|
"L": 15,
|
||||||
|
"M": 20,
|
||||||
|
"N": 12,
|
||||||
|
"O": 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
var debtSupplierAllSheetColumnWidths = map[string]float64{
|
||||||
|
"A": 24,
|
||||||
|
"B": 6,
|
||||||
|
"C": 14,
|
||||||
|
"D": 14,
|
||||||
|
"E": 20,
|
||||||
|
"F": 12,
|
||||||
|
"G": 10,
|
||||||
|
"H": 16,
|
||||||
|
"I": 22,
|
||||||
|
"J": 12,
|
||||||
|
"K": 22,
|
||||||
|
"L": 20,
|
||||||
|
"M": 18,
|
||||||
|
"N": 22,
|
||||||
|
"O": 14,
|
||||||
|
"P": 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDebtSupplierSheet(file *excelize.File, sheet string, item dto.DebtSupplierDTO) error {
|
||||||
|
for col, width := range debtSupplierSheetColumnWidths {
|
||||||
|
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 1: headers
|
||||||
|
for i, h := range debtSupplierSheetHeaders {
|
||||||
|
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||||
|
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 2: saldo awal
|
||||||
|
if err := file.SetCellValue(sheet, "M2", item.InitialBalance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rows 3+: data
|
||||||
|
redStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{Color: "FF0000"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range item.Rows {
|
||||||
|
rowNum := i + 3
|
||||||
|
rowStr := fmt.Sprintf("%d", rowNum)
|
||||||
|
|
||||||
|
values := debtSupplierRowCells(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.DebtPrice < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "M"+rowStr, "M"+rowStr, redStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total row
|
||||||
|
totalRowNum := len(item.Rows) + 3
|
||||||
|
totalRowStr := fmt.Sprintf("%d", totalRowNum)
|
||||||
|
totalCells := map[string]interface{}{
|
||||||
|
"A": "Total",
|
||||||
|
"F": item.Total.Aging,
|
||||||
|
"K": item.Total.TotalPrice,
|
||||||
|
"L": item.Total.PaymentPrice,
|
||||||
|
"M": item.Total.DebtPrice,
|
||||||
|
}
|
||||||
|
for col, val := range totalCells {
|
||||||
|
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item.Total.DebtPrice < 0 {
|
||||||
|
if err := file.SetCellStyle(sheet, "M"+totalRowStr, "M"+totalRowStr, redStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDebtSupplierAllColumns(file *excelize.File, sheet string) error {
|
||||||
|
for col, width := range debtSupplierAllSheetColumnWidths {
|
||||||
|
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 setDebtSupplierAllHeaders(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 debtSupplierAllSheetHeaders {
|
||||||
|
col, _ := excelize.ColumnNumberToName(i + 1)
|
||||||
|
if err := file.SetCellValue(sheet, col+"1", h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCol, _ := excelize.ColumnNumberToName(len(debtSupplierAllSheetHeaders))
|
||||||
|
return file.SetCellStyle(sheet, "A1", lastCol+"1", headerStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDebtSupplierAllRows(file *excelize.File, sheet string, items []dto.DebtSupplierDTO) 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(debtSupplierAllSheetHeaders))
|
||||||
|
|
||||||
|
currentRow := 2
|
||||||
|
for _, item := range items {
|
||||||
|
supplierName := debtSupplierName(item)
|
||||||
|
|
||||||
|
// Saldo awal row
|
||||||
|
saldoRowStr := fmt.Sprintf("%d", currentRow)
|
||||||
|
if err := file.SetCellValue(sheet, "A"+saldoRowStr, supplierName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "N"+saldoRowStr, item.InitialBalance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "A"+saldoRowStr, lastHeaderCol+saldoRowStr, dataStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
currentRow++
|
||||||
|
|
||||||
|
// 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 := debtSupplierRowCells(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++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total row
|
||||||
|
totalRowStr := fmt.Sprintf("%d", currentRow)
|
||||||
|
totalCells := map[string]interface{}{
|
||||||
|
"A": supplierName,
|
||||||
|
"B": "Total",
|
||||||
|
"L": item.Total.TotalPrice,
|
||||||
|
"M": item.Total.PaymentPrice,
|
||||||
|
"N": item.Total.DebtPrice,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// debtSupplierRowCells returns cell values for one data row (columns: No, PR, PO, ReceivedDate, PoDate, Aging, Area, Warehouse, DueDate, DueStatus, TotalPrice, PaymentPrice, DebtPrice, Status, TravelNumber).
|
||||||
|
func debtSupplierRowCells(row dto.DebtSupplierRowDTO, seq int) []interface{} {
|
||||||
|
areaName := "-"
|
||||||
|
if row.Area != nil && strings.TrimSpace(row.Area.Name) != "" {
|
||||||
|
areaName = row.Area.Name
|
||||||
|
}
|
||||||
|
warehouseName := "-"
|
||||||
|
if row.Warehouse != nil && strings.TrimSpace(row.Warehouse.Name) != "" {
|
||||||
|
warehouseName = row.Warehouse.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return []interface{}{
|
||||||
|
seq,
|
||||||
|
safeDebtSupplierText(row.PrNumber),
|
||||||
|
safeDebtSupplierText(row.PoNumber),
|
||||||
|
safeDebtSupplierText(row.ReceivedDate),
|
||||||
|
safeDebtSupplierText(row.PoDate),
|
||||||
|
row.Aging,
|
||||||
|
areaName,
|
||||||
|
warehouseName,
|
||||||
|
safeDebtSupplierText(row.DueDate),
|
||||||
|
safeDebtSupplierText(row.DueStatus),
|
||||||
|
row.TotalPrice,
|
||||||
|
row.PaymentPrice,
|
||||||
|
row.DebtPrice,
|
||||||
|
safeDebtSupplierText(row.Status),
|
||||||
|
safeDebtSupplierText(row.TravelNumber),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debtSupplierName(item dto.DebtSupplierDTO) string {
|
||||||
|
if item.Supplier != nil && strings.TrimSpace(item.Supplier.Name) != "" {
|
||||||
|
return item.Supplier.Name
|
||||||
|
}
|
||||||
|
return "Supplier"
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeDebtSupplierSheetName(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 safeDebtSupplierText(s string) string {
|
||||||
|
t := strings.TrimSpace(s)
|
||||||
|
if t == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user