mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-22 14:25:45 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d7a0e30cd | |||
| b12f563bc4 | |||
| d0e7b7aad1 | |||
| c676aed371 | |||
| 7bbb6a836c | |||
| 6bbab2f1d5 | |||
| 70546c2302 | |||
| 6c7d8ac83e | |||
| 1e48bc8762 | |||
| 77a30837e2 | |||
| a63460e853 | |||
| 1be0fa1a5f | |||
| c9e3905a65 | |||
| 495f5f5cc1 | |||
| 71e80634b1 | |||
| 621d0d2bfd | |||
| 1fd3f96038 | |||
| cf0fc9e7e6 | |||
| d9041a89bb | |||
| c75281ebd9 | |||
| ca3ad810c6 | |||
| 655b1ad5fe | |||
| 84db5fe37a | |||
| 63a78da18d | |||
| ac50c06cd7 | |||
| b60649f59d | |||
| 6acc9416c1 | |||
| bb4e5d6e3e | |||
| 170c221957 | |||
| 812327f148 | |||
| cd192128f1 | |||
| a5d4d6c11d | |||
| 1452f8d083 | |||
| 33c6706181 | |||
| c9618e1095 | |||
| cae7f3ef63 | |||
| 42793d94bd | |||
| 1369bf0e36 | |||
| 361d14bd3e | |||
| 7923352535 | |||
| 010240066a |
@@ -0,0 +1,4 @@
|
|||||||
|
UPDATE adjustment_stocks
|
||||||
|
SET price = 9535,
|
||||||
|
grand_total = ROUND(8700 * 9535, 3)
|
||||||
|
WHERE id = 532 AND adj_number = 'ADJ-00507';
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
UPDATE adjustment_stocks
|
||||||
|
SET price = 12635,
|
||||||
|
grand_total = ROUND(8700 * 12635, 3)
|
||||||
|
WHERE id = 532 AND adj_number = 'ADJ-00507';
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/validations"
|
||||||
@@ -13,6 +14,8 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const transactionExcelExportFetchLimit = 99999999
|
||||||
|
|
||||||
type TransactionController struct {
|
type TransactionController struct {
|
||||||
TransactionService service.TransactionService
|
TransactionService service.TransactionService
|
||||||
}
|
}
|
||||||
@@ -107,6 +110,14 @@ func (u *TransactionController) GetAll(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isTransactionExcelExportRequest(c) {
|
||||||
|
results, err := u.getAllTransactionsForExcel(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return exportTransactionListExcel(c, results)
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.TransactionService.GetAll(c, query)
|
result, totalResults, err := u.TransactionService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -149,6 +160,32 @@ func (u *TransactionController) GetOne(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTransactionExcelExportRequest(c *fiber.Ctx) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TransactionController) getAllTransactionsForExcel(c *fiber.Ctx, baseQuery *validation.Query) ([]entity.Payment, error) {
|
||||||
|
query := *baseQuery
|
||||||
|
query.Page = 1
|
||||||
|
query.Limit = transactionExcelExportFetchLimit
|
||||||
|
results := make([]entity.Payment, 0)
|
||||||
|
for {
|
||||||
|
pageResults, total, err := u.TransactionService.GetAll(c, &query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pageResults) == 0 || total == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
results = append(results, pageResults...)
|
||||||
|
if int64(len(results)) >= total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
query.Page++
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *TransactionController) DeleteOne(c *fiber.Ctx) error {
|
func (u *TransactionController) DeleteOne(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
const transactionExportSheetName = "Transaksi"
|
||||||
|
|
||||||
|
func exportTransactionListExcel(c *fiber.Ctx, payments []entity.Payment) error {
|
||||||
|
content, err := buildTransactionExportWorkbook(payments)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("transaksi_%s.xlsx", time.Now().Format("20060102_150405"))
|
||||||
|
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 buildTransactionExportWorkbook(payments []entity.Payment) ([]byte, error) {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
if defaultSheet != transactionExportSheetName {
|
||||||
|
if err := file.SetSheetName(defaultSheet, transactionExportSheetName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setTransactionExportColumns(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setTransactionExportHeaders(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := setTransactionExportRows(file, payments); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := file.SetPanes(transactionExportSheetName, &excelize.Panes{
|
||||||
|
Freeze: true,
|
||||||
|
YSplit: 1,
|
||||||
|
TopLeftCell: "A2",
|
||||||
|
ActivePane: "bottomLeft",
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer, err := file.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransactionExportColumns(file *excelize.File) error {
|
||||||
|
columnWidths := map[string]float64{
|
||||||
|
"A": 20,
|
||||||
|
"B": 22,
|
||||||
|
"C": 18,
|
||||||
|
"D": 25,
|
||||||
|
"E": 14,
|
||||||
|
"F": 16,
|
||||||
|
"G": 16,
|
||||||
|
"H": 22,
|
||||||
|
"I": 22,
|
||||||
|
"J": 18,
|
||||||
|
"K": 18,
|
||||||
|
"L": 18,
|
||||||
|
"M": 30,
|
||||||
|
"N": 22,
|
||||||
|
"O": 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet := transactionExportSheetName
|
||||||
|
for col, width := range columnWidths {
|
||||||
|
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.SetRowHeight(sheet, 1, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransactionExportHeaders(file *excelize.File) error {
|
||||||
|
sheet := transactionExportSheetName
|
||||||
|
headers := []string{
|
||||||
|
"Kode Pembayaran",
|
||||||
|
"No. Referensi",
|
||||||
|
"Tipe Transaksi",
|
||||||
|
"Pihak",
|
||||||
|
"Tipe Pihak",
|
||||||
|
"Tanggal Bayar",
|
||||||
|
"Metode Bayar",
|
||||||
|
"Bank",
|
||||||
|
"No. Rekening Bank",
|
||||||
|
"Pemasukan",
|
||||||
|
"Pengeluaran",
|
||||||
|
"Nominal",
|
||||||
|
"Catatan",
|
||||||
|
"Dibuat Oleh",
|
||||||
|
"Status",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, header := range headers {
|
||||||
|
colName, err := excelize.ColumnNumberToName(i + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, colName+"1", header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{Bold: true, Color: "1F2937"},
|
||||||
|
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"DCEBFA"}},
|
||||||
|
Alignment: &excelize.Alignment{Horizontal: "center", 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},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.SetCellStyle(sheet, "A1", "O1", headerStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransactionExportRows(file *excelize.File, payments []entity.Payment) error {
|
||||||
|
if len(payments) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet := transactionExportSheetName
|
||||||
|
for i, p := range payments {
|
||||||
|
row := strconv.Itoa(i + 2)
|
||||||
|
if err := writeTransactionExportRow(file, sheet, row, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRow := strconv.Itoa(len(payments) + 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},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "A2", "O"+lastRow, dataStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
numericStyle, 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},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.SetCellStyle(sheet, "J2", "L"+lastRow, numericStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTransactionExportRow(file *excelize.File, sheet, row string, p entity.Payment) error {
|
||||||
|
incomeAmount, expenseAmount := txAmounts(p.Direction, p.Nominal)
|
||||||
|
|
||||||
|
values := []interface{}{
|
||||||
|
safeTxText(p.PaymentCode),
|
||||||
|
safeTxRefNumber(p.ReferenceNumber),
|
||||||
|
safeTxText(txTransactionType(p)),
|
||||||
|
safeTxText(txPartyName(p)),
|
||||||
|
safeTxText(p.PartyType),
|
||||||
|
formatTxDate(p.PaymentDate),
|
||||||
|
safeTxText(p.PaymentMethod),
|
||||||
|
safeTxBank(p),
|
||||||
|
safeTxBankAccount(p),
|
||||||
|
incomeAmount,
|
||||||
|
expenseAmount,
|
||||||
|
p.Nominal,
|
||||||
|
safeTxText(p.Notes),
|
||||||
|
safeTxText(txCreatedBy(p)),
|
||||||
|
formatTxStatus(p),
|
||||||
|
}
|
||||||
|
|
||||||
|
for colIdx, val := range values {
|
||||||
|
colName, err := excelize.ColumnNumberToName(colIdx + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, colName+row, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeTxText(s string) string {
|
||||||
|
trimmed := strings.TrimSpace(s)
|
||||||
|
if trimmed == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeTxRefNumber(s *string) string {
|
||||||
|
if s == nil {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safeTxText(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeTxBank(p entity.Payment) string {
|
||||||
|
if p.BankWarehouse.Id == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safeTxText(p.BankWarehouse.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeTxBankAccount(p entity.Payment) string {
|
||||||
|
if p.BankWarehouse.Id == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safeTxText(p.BankWarehouse.AccountNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTxDate(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
loc, err := time.LoadLocation("Asia/Jakarta")
|
||||||
|
if err == nil {
|
||||||
|
t = t.In(loc)
|
||||||
|
}
|
||||||
|
return t.Format("02-01-2006")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTxStatus(p entity.Payment) string {
|
||||||
|
if p.LatestApproval == nil {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safeTxText(p.LatestApproval.StepName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func txTransactionType(p entity.Payment) string {
|
||||||
|
if p.TransactionType != "" {
|
||||||
|
return p.TransactionType
|
||||||
|
}
|
||||||
|
return p.Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
func txPartyName(p entity.Payment) string {
|
||||||
|
switch p.PartyType {
|
||||||
|
case "CUSTOMER":
|
||||||
|
if p.Customer != nil && p.Customer.Id != 0 {
|
||||||
|
return p.Customer.Name
|
||||||
|
}
|
||||||
|
case "SUPPLIER":
|
||||||
|
if p.Supplier != nil && p.Supplier.Id != 0 {
|
||||||
|
return p.Supplier.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func txCreatedBy(p entity.Payment) string {
|
||||||
|
if p.CreatedUser.Id == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.CreatedUser.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func txAmounts(direction string, nominal float64) (income, expense float64) {
|
||||||
|
switch strings.ToUpper(direction) {
|
||||||
|
case "IN":
|
||||||
|
return nominal, 0
|
||||||
|
case "OUT":
|
||||||
|
return 0, nominal
|
||||||
|
default:
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ type Update struct {
|
|||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
TransactionTypes []string `query:"transaction_types" validate:"omitempty,dive,max=50"`
|
TransactionTypes []string `query:"transaction_types" validate:"omitempty,dive,max=50"`
|
||||||
BankIDs []uint `query:"bank_ids" validate:"omitempty,dive,gt=0"`
|
BankIDs []uint `query:"bank_ids" validate:"omitempty,dive,gt=0"`
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -153,7 +152,7 @@ func setMarketingExportRows(file *excelize.File, sheet string, items []dto.Marke
|
|||||||
if err := file.SetCellValue(sheet, "D"+strconv.Itoa(rowNumber), safeMarketingExportText(item.Customer.Name)); err != nil {
|
if err := file.SetCellValue(sheet, "D"+strconv.Itoa(rowNumber), safeMarketingExportText(item.Customer.Name)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "E"+strconv.Itoa(rowNumber), formatMarketingRupiah(sumMarketingGrandTotal(item.SalesOrder))); err != nil {
|
if err := file.SetCellValue(sheet, "E"+strconv.Itoa(rowNumber), sumMarketingGrandTotal(item.SalesOrder)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "F"+strconv.Itoa(rowNumber), formatMarketingProducts(item.SalesOrder)); err != nil {
|
if err := file.SetCellValue(sheet, "F"+strconv.Itoa(rowNumber), formatMarketingProducts(item.SalesOrder)); err != nil {
|
||||||
@@ -266,40 +265,6 @@ func sumMarketingGrandTotal(items []dto.DeliveryMarketingProductDTO) float64 {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatMarketingRupiah(value float64) string {
|
|
||||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
|
||||||
return "Rp 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
rounded := int64(math.Round(value))
|
|
||||||
sign := ""
|
|
||||||
if rounded < 0 {
|
|
||||||
sign = "-"
|
|
||||||
rounded = -rounded
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := strconv.FormatInt(rounded, 10)
|
|
||||||
if raw == "" {
|
|
||||||
raw = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
var grouped strings.Builder
|
|
||||||
rem := len(raw) % 3
|
|
||||||
if rem > 0 {
|
|
||||||
grouped.WriteString(raw[:rem])
|
|
||||||
if len(raw) > rem {
|
|
||||||
grouped.WriteString(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := rem; i < len(raw); i += 3 {
|
|
||||||
grouped.WriteString(raw[i : i+3])
|
|
||||||
if i+3 < len(raw) {
|
|
||||||
grouped.WriteString(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Rp " + sign + grouped.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeMarketingExportText(value string) string {
|
func safeMarketingExportText(value string) string {
|
||||||
trimmed := strings.TrimSpace(value)
|
trimmed := strings.TrimSpace(value)
|
||||||
|
|||||||
@@ -91,11 +91,9 @@ func buildPurchaseQuery(c *fiber.Ctx) *validation.Query {
|
|||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: strings.TrimSpace(c.Query("search")),
|
Search: strings.TrimSpace(c.Query("search")),
|
||||||
ApprovalStatus: strings.TrimSpace(c.Query("approval_status")),
|
ApprovalStatus: strings.TrimSpace(c.Query("approval_status")),
|
||||||
PoDate: strings.TrimSpace(c.Query("po_date")),
|
StartDate: strings.TrimSpace(c.Query("start_date")),
|
||||||
PoDateFrom: strings.TrimSpace(c.Query("po_date_from")),
|
EndDate: strings.TrimSpace(c.Query("end_date")),
|
||||||
PoDateTo: strings.TrimSpace(c.Query("po_date_to")),
|
FilterBy: strings.TrimSpace(c.Query("filter_by")),
|
||||||
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
|
|
||||||
CreatedTo: strings.TrimSpace(c.Query("created_to")),
|
|
||||||
SupplierID: uint(c.QueryInt("supplier_id", 0)),
|
SupplierID: uint(c.QueryInt("supplier_id", 0)),
|
||||||
AreaID: uint(c.QueryInt("area_id", 0)),
|
AreaID: uint(c.QueryInt("area_id", 0)),
|
||||||
LocationID: uint(c.QueryInt("location_id", 0)),
|
LocationID: uint(c.QueryInt("location_id", 0)),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -43,15 +42,13 @@ func buildPurchaseExportWorkbook(purchases []entity.Purchase) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
grandTotals := buildPurchaseGrandTotalMap(purchases)
|
|
||||||
|
|
||||||
if err := setPurchaseExportColumns(file, purchaseExportSheetName); err != nil {
|
if err := setPurchaseExportColumns(file, purchaseExportSheetName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := setPurchaseExportHeaders(file, purchaseExportSheetName); err != nil {
|
if err := setPurchaseExportHeaders(file, purchaseExportSheetName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := setPurchaseExportRows(file, purchaseExportSheetName, purchases, grandTotals); err != nil {
|
if err := setPurchaseExportRows(file, purchaseExportSheetName, purchases); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := file.SetPanes(purchaseExportSheetName, &excelize.Panes{
|
if err := file.SetPanes(purchaseExportSheetName, &excelize.Panes{
|
||||||
@@ -80,9 +77,17 @@ func setPurchaseExportColumns(file *excelize.File, sheet string) error {
|
|||||||
"F": 22,
|
"F": 22,
|
||||||
"G": 22,
|
"G": 22,
|
||||||
"H": 32,
|
"H": 32,
|
||||||
"I": 18,
|
"I": 10,
|
||||||
"J": 18,
|
"J": 12,
|
||||||
"K": 24,
|
"K": 16,
|
||||||
|
"L": 16,
|
||||||
|
"M": 22,
|
||||||
|
"N": 12,
|
||||||
|
"O": 16,
|
||||||
|
"P": 16,
|
||||||
|
"Q": 18,
|
||||||
|
"R": 18,
|
||||||
|
"S": 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
for col, width := range columnWidths {
|
for col, width := range columnWidths {
|
||||||
@@ -99,17 +104,25 @@ func setPurchaseExportColumns(file *excelize.File, sheet string) error {
|
|||||||
|
|
||||||
func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
||||||
headers := []string{
|
headers := []string{
|
||||||
"PR Number",
|
"PR Number", // A
|
||||||
"PO Number",
|
"PO Number", // B
|
||||||
"Tanggal PO",
|
"Tanggal PO", // C
|
||||||
"Tanggal Terima",
|
"Tanggal Terima", // D
|
||||||
"Supplier",
|
"Supplier", // E
|
||||||
"Lokasi",
|
"Lokasi", // F
|
||||||
"Gudang",
|
"Gudang", // G
|
||||||
"Product",
|
"Product", // H
|
||||||
"Status",
|
"Qty", // I
|
||||||
"Grand Total",
|
"Satuan", // J
|
||||||
"Notes",
|
"Price", // K
|
||||||
|
"Total Produk", // L
|
||||||
|
"Vendor Ekspedisi",// M
|
||||||
|
"Qty Ekspedisi", // N
|
||||||
|
"Price Ekspedisi", // O
|
||||||
|
"Total Ekspedisi", // P
|
||||||
|
"Grand Total All", // Q
|
||||||
|
"Status", // R
|
||||||
|
"Notes", // S
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, header := range headers {
|
for i, header := range headers {
|
||||||
@@ -137,34 +150,36 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.SetCellStyle(sheet, "A1", "K1", headerStyle)
|
return file.SetCellStyle(sheet, "A1", "S1", headerStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPurchaseExportRows(file *excelize.File, sheet string, purchases []entity.Purchase, grandTotals map[uint]float64) error {
|
func setPurchaseExportRows(file *excelize.File, sheet string, purchases []entity.Purchase) error {
|
||||||
if len(purchases) == 0 {
|
if len(purchases) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sumL, sumP, sumQ float64
|
||||||
|
|
||||||
rowIdx := 2
|
rowIdx := 2
|
||||||
for p := range purchases {
|
for p := range purchases {
|
||||||
purchase := &purchases[p]
|
purchase := &purchases[p]
|
||||||
total := grandTotals[purchase.Id]
|
|
||||||
if len(purchase.Items) == 0 {
|
if len(purchase.Items) == 0 {
|
||||||
if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, nil, total); err != nil {
|
if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, nil, &sumL, &sumP, &sumQ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rowIdx++
|
rowIdx++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for it := range purchase.Items {
|
for it := range purchase.Items {
|
||||||
if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, &purchase.Items[it], total); err != nil {
|
if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, &purchase.Items[it], &sumL, &sumP, &sumQ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rowIdx++
|
rowIdx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRow := rowIdx - 1
|
lastDataRow := rowIdx - 1
|
||||||
|
|
||||||
dataStyle, err := file.NewStyle(&excelize.Style{
|
dataStyle, err := file.NewStyle(&excelize.Style{
|
||||||
Alignment: &excelize.Alignment{
|
Alignment: &excelize.Alignment{
|
||||||
Horizontal: "left",
|
Horizontal: "left",
|
||||||
@@ -181,7 +196,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, purchases []entity
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellStyle(sheet, "A2", "K"+strconv.Itoa(lastRow), dataStyle); err != nil {
|
if err := file.SetCellStyle(sheet, "A2", "S"+strconv.Itoa(lastDataRow), dataStyle); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +215,17 @@ func setPurchaseExportRows(file *excelize.File, sheet string, purchases []entity
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "K2", "Q"+strconv.Itoa(lastDataRow), moneyStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return file.SetCellStyle(sheet, "J2", "J"+strconv.Itoa(lastRow), moneyStyle)
|
return addPurchaseExportSumRow(file, sheet, rowIdx, sumL, sumP, sumQ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePurchaseExportRow(file *excelize.File, sheet string, rowIdx int, purchase *entity.Purchase, item *entity.PurchaseItem, grandTotal float64) error {
|
func writePurchaseExportRow(file *excelize.File, sheet string, rowIdx int, purchase *entity.Purchase, item *entity.PurchaseItem, sumL, sumP, sumQ *float64) error {
|
||||||
row := strconv.Itoa(rowIdx)
|
row := strconv.Itoa(rowIdx)
|
||||||
|
|
||||||
// Purchase-level columns (repeat across rows of the same purchase)
|
// Purchase-level columns (repeat for every item row of the same purchase)
|
||||||
if err := file.SetCellValue(sheet, "A"+row, safePurchaseExportText(purchase.PrNumber)); err != nil {
|
if err := file.SetCellValue(sheet, "A"+row, safePurchaseExportText(purchase.PrNumber)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -220,26 +238,40 @@ func writePurchaseExportRow(file *excelize.File, sheet string, rowIdx int, purch
|
|||||||
if err := file.SetCellValue(sheet, "E"+row, safePurchaseExportEntitySupplierName(purchase)); err != nil {
|
if err := file.SetCellValue(sheet, "E"+row, safePurchaseExportEntitySupplierName(purchase)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "I"+row, formatPurchaseExportEntityStatus(purchase)); err != nil {
|
if err := file.SetCellValue(sheet, "R"+row, formatPurchaseExportEntityStatus(purchase)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "J"+row, formatPurchaseRupiah(grandTotal)); err != nil {
|
if err := file.SetCellValue(sheet, "S"+row, safePurchaseExportPointerText(purchase.Notes)); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := file.SetCellValue(sheet, "K"+row, safePurchaseExportPointerText(purchase.Notes)); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item-level columns
|
|
||||||
if item == nil {
|
if item == nil {
|
||||||
for _, col := range []string{"D", "F", "G", "H"} {
|
for _, col := range []string{"D", "F", "G", "H", "J", "M"} {
|
||||||
if err := file.SetCellValue(sheet, col+row, "-"); err != nil {
|
if err := file.SetCellValue(sheet, col+row, "-"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, col := range []string{"I", "K", "L", "N", "O", "P", "Q"} {
|
||||||
|
if err := file.SetCellValue(sheet, col+row, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Item-level columns
|
||||||
|
var expeditionQty, expeditionPrice, expeditionTotal float64
|
||||||
|
if item.ExpenseNonstock != nil {
|
||||||
|
expeditionQty = item.ExpenseNonstock.Qty
|
||||||
|
expeditionPrice = item.ExpenseNonstock.Price
|
||||||
|
expeditionTotal = expeditionQty * expeditionPrice
|
||||||
|
}
|
||||||
|
itemGrandTotal := item.TotalPrice + expeditionTotal
|
||||||
|
|
||||||
|
*sumL += item.TotalPrice
|
||||||
|
*sumP += expeditionTotal
|
||||||
|
*sumQ += itemGrandTotal
|
||||||
|
|
||||||
if err := file.SetCellValue(sheet, "D"+row, formatPurchaseExportDate(item.ReceivedDate)); err != nil {
|
if err := file.SetCellValue(sheet, "D"+row, formatPurchaseExportDate(item.ReceivedDate)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -252,20 +284,96 @@ func writePurchaseExportRow(file *excelize.File, sheet string, rowIdx int, purch
|
|||||||
if err := file.SetCellValue(sheet, "H"+row, safePurchaseItemProductName(item)); err != nil {
|
if err := file.SetCellValue(sheet, "H"+row, safePurchaseItemProductName(item)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "I"+row, item.TotalQty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "J"+row, safePurchaseItemUomName(item)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "K"+row, item.Price); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "L"+row, item.TotalPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "M"+row, safePurchaseItemExpeditionVendorName(item)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "N"+row, expeditionQty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "O"+row, expeditionPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "P"+row, expeditionTotal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "Q"+row, itemGrandTotal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 {
|
func addPurchaseExportSumRow(file *excelize.File, sheet string, rowIdx int, sumL, sumP, sumQ float64) error {
|
||||||
result := make(map[uint]float64, len(items))
|
row := strconv.Itoa(rowIdx)
|
||||||
for i := range items {
|
|
||||||
total := 0.0
|
sumStyle, err := file.NewStyle(&excelize.Style{
|
||||||
for j := range items[i].Items {
|
Font: &excelize.Font{Bold: true, Color: "1F2937"},
|
||||||
total += items[i].Items[j].TotalPrice
|
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"FEF3C7"}},
|
||||||
}
|
Alignment: &excelize.Alignment{
|
||||||
result[items[i].Id] = total
|
Horizontal: "left",
|
||||||
|
Vertical: "center",
|
||||||
|
},
|
||||||
|
Border: []excelize.Border{
|
||||||
|
{Type: "left", Color: "D1D5DB", Style: 1},
|
||||||
|
{Type: "top", Color: "D1D5DB", Style: 2},
|
||||||
|
{Type: "bottom", Color: "D1D5DB", Style: 1},
|
||||||
|
{Type: "right", Color: "D1D5DB", Style: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
|
sumMoneyStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{Bold: true, Color: "1F2937"},
|
||||||
|
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"FEF3C7"}},
|
||||||
|
Alignment: &excelize.Alignment{
|
||||||
|
Horizontal: "right",
|
||||||
|
Vertical: "center",
|
||||||
|
},
|
||||||
|
Border: []excelize.Border{
|
||||||
|
{Type: "left", Color: "D1D5DB", Style: 1},
|
||||||
|
{Type: "top", Color: "D1D5DB", Style: 2},
|
||||||
|
{Type: "bottom", Color: "D1D5DB", Style: 1},
|
||||||
|
{Type: "right", Color: "D1D5DB", Style: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.SetCellStyle(sheet, "A"+row, "S"+row, sumStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "L"+row, "L"+row, sumMoneyStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "P"+row, "Q"+row, sumMoneyStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.SetCellValue(sheet, "A"+row, "TOTAL"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "L"+row, sumL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "P"+row, sumP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.SetCellValue(sheet, "Q"+row, sumQ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func safePurchaseExportEntitySupplierName(purchase *entity.Purchase) string {
|
func safePurchaseExportEntitySupplierName(purchase *entity.Purchase) string {
|
||||||
@@ -296,6 +404,24 @@ func safePurchaseItemProductName(item *entity.PurchaseItem) string {
|
|||||||
return safePurchaseExportText(item.Product.Name)
|
return safePurchaseExportText(item.Product.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func safePurchaseItemUomName(item *entity.PurchaseItem) string {
|
||||||
|
if item.Product == nil || item.Product.Uom.Id == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safePurchaseExportText(item.Product.Uom.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safePurchaseItemExpeditionVendorName(item *entity.PurchaseItem) string {
|
||||||
|
if item.ExpenseNonstock == nil || item.ExpenseNonstock.Expense == nil {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
exp := item.ExpenseNonstock.Expense
|
||||||
|
if exp.Supplier == nil || exp.Supplier.Id == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return safePurchaseExportText(exp.Supplier.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func formatPurchaseExportEntityStatus(purchase *entity.Purchase) string {
|
func formatPurchaseExportEntityStatus(purchase *entity.Purchase) string {
|
||||||
if purchase.LatestApproval == nil {
|
if purchase.LatestApproval == nil {
|
||||||
return "-"
|
return "-"
|
||||||
@@ -309,6 +435,21 @@ func formatPurchaseExportEntityStatus(purchase *entity.Purchase) string {
|
|||||||
return safePurchaseExportText(purchase.LatestApproval.StepName)
|
return safePurchaseExportText(purchase.LatestApproval.StepName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var purchaseIndonesianMonths = map[time.Month]string{
|
||||||
|
time.January: "Jan",
|
||||||
|
time.February: "Feb",
|
||||||
|
time.March: "Mar",
|
||||||
|
time.April: "Apr",
|
||||||
|
time.May: "Mei",
|
||||||
|
time.June: "Jun",
|
||||||
|
time.July: "Jul",
|
||||||
|
time.August: "Ags",
|
||||||
|
time.September: "Sep",
|
||||||
|
time.October: "Okt",
|
||||||
|
time.November: "Nov",
|
||||||
|
time.December: "Des",
|
||||||
|
}
|
||||||
|
|
||||||
func formatPurchaseExportDate(value *time.Time) string {
|
func formatPurchaseExportDate(value *time.Time) string {
|
||||||
if value == nil || value.IsZero() {
|
if value == nil || value.IsZero() {
|
||||||
return "-"
|
return "-"
|
||||||
@@ -320,7 +461,8 @@ func formatPurchaseExportDate(value *time.Time) string {
|
|||||||
t = t.In(location)
|
t = t.In(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.Format("02-01-2006")
|
month := purchaseIndonesianMonths[t.Month()]
|
||||||
|
return fmt.Sprintf("%d-%s-%02d", t.Day(), month, t.Year()%100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func safePurchaseExportPointerText(value *string) string {
|
func safePurchaseExportPointerText(value *string) string {
|
||||||
@@ -338,37 +480,3 @@ func safePurchaseExportText(value string) string {
|
|||||||
return trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatPurchaseRupiah(value float64) string {
|
|
||||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
|
||||||
return "Rp 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
rounded := int64(math.Round(value))
|
|
||||||
sign := ""
|
|
||||||
if rounded < 0 {
|
|
||||||
sign = "-"
|
|
||||||
rounded = -rounded
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := strconv.FormatInt(rounded, 10)
|
|
||||||
if raw == "" {
|
|
||||||
raw = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
var grouped strings.Builder
|
|
||||||
rem := len(raw) % 3
|
|
||||||
if rem > 0 {
|
|
||||||
grouped.WriteString(raw[:rem])
|
|
||||||
if len(raw) > rem {
|
|
||||||
grouped.WriteString(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := rem; i < len(raw); i += 3 {
|
|
||||||
grouped.WriteString(raw[i : i+3])
|
|
||||||
if i+3 < len(raw) {
|
|
||||||
grouped.WriteString(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Rp " + sign + grouped.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ func TestBuildPurchaseExportWorkbookHeadersAndRows(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
"catatan",
|
"catatan",
|
||||||
[]entity.PurchaseItem{
|
[]entity.PurchaseItem{
|
||||||
buildPurchaseItemForExportTest(11, "Pakan Starter", 1000000, "Location A"),
|
buildPurchaseItemForExportTest(11, "Pakan Starter", 500, 2, 1000000, "Location A", "kg"),
|
||||||
buildPurchaseItemForExportTest(12, "Vitamin A", 350000, "Location B"),
|
buildPurchaseItemForExportTest(12, "Vitamin A", 350, 1, 350000, "Location B", "botol"),
|
||||||
buildPurchaseItemForExportTest(11, "Pakan Starter", 0, ""),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
buildPurchaseForExportTest(
|
buildPurchaseForExportTest(
|
||||||
@@ -37,7 +36,7 @@ func TestBuildPurchaseExportWorkbookHeadersAndRows(t *testing.T) {
|
|||||||
ptrApprovalAction(entity.ApprovalActionRejected),
|
ptrApprovalAction(entity.ApprovalActionRejected),
|
||||||
"",
|
"",
|
||||||
[]entity.PurchaseItem{
|
[]entity.PurchaseItem{
|
||||||
buildPurchaseItemForExportTest(21, "Obat X", 75000, ""),
|
buildPurchaseItemForExportTest(21, "Obat X", 75000, 1, 75000, "", ""),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
@@ -51,16 +50,27 @@ func TestBuildPurchaseExportWorkbookHeadersAndRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
// Verify all 19 headers
|
||||||
expectedHeaders := map[string]string{
|
expectedHeaders := map[string]string{
|
||||||
"A1": "PR Number",
|
"A1": "PR Number",
|
||||||
"B1": "PO Number",
|
"B1": "PO Number",
|
||||||
"C1": "Tanggal PO",
|
"C1": "Tanggal PO",
|
||||||
"D1": "Supplier",
|
"D1": "Tanggal Terima",
|
||||||
"E1": "Lokasi",
|
"E1": "Supplier",
|
||||||
"F1": "Status",
|
"F1": "Lokasi",
|
||||||
"G1": "Grand Total",
|
"G1": "Gudang",
|
||||||
"H1": "Products",
|
"H1": "Product",
|
||||||
"I1": "Notes",
|
"I1": "Qty",
|
||||||
|
"J1": "Satuan",
|
||||||
|
"K1": "Price",
|
||||||
|
"L1": "Total Produk",
|
||||||
|
"M1": "Vendor Ekspedisi",
|
||||||
|
"N1": "Qty Ekspedisi",
|
||||||
|
"O1": "Price Ekspedisi",
|
||||||
|
"P1": "Total Ekspedisi",
|
||||||
|
"Q1": "Grand Total All",
|
||||||
|
"R1": "Status",
|
||||||
|
"S1": "Notes",
|
||||||
}
|
}
|
||||||
for cell, expected := range expectedHeaders {
|
for cell, expected := range expectedHeaders {
|
||||||
got, err := file.GetCellValue(purchaseExportSheetName, cell)
|
got, err := file.GetCellValue(purchaseExportSheetName, cell)
|
||||||
@@ -72,24 +82,46 @@ func TestBuildPurchaseExportWorkbookHeadersAndRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row 2: Purchase 1, Item 1 (Pakan Starter)
|
||||||
assertPurchaseCellEquals(t, file, "A2", "PR-00011")
|
assertPurchaseCellEquals(t, file, "A2", "PR-00011")
|
||||||
assertPurchaseCellEquals(t, file, "B2", "PO-00011")
|
assertPurchaseCellEquals(t, file, "B2", "PO-00011")
|
||||||
assertPurchaseCellEquals(t, file, "C2", "22-04-2026")
|
assertPurchaseCellEquals(t, file, "C2", "22-04-2026")
|
||||||
assertPurchaseCellEquals(t, file, "D2", "Supplier A")
|
assertPurchaseCellEquals(t, file, "E2", "Supplier A")
|
||||||
assertPurchaseCellEquals(t, file, "E2", "Location A")
|
assertPurchaseCellEquals(t, file, "F2", "Location A")
|
||||||
assertPurchaseCellEquals(t, file, "F2", "Manager Purchase")
|
assertPurchaseCellEquals(t, file, "H2", "Pakan Starter")
|
||||||
assertPurchaseCellEquals(t, file, "G2", "Rp 1.350.000")
|
assertPurchaseCellEquals(t, file, "J2", "kg")
|
||||||
assertPurchaseCellEquals(t, file, "H2", "Pakan Starter, Vitamin A")
|
assertPurchaseCellEquals(t, file, "K2", "500")
|
||||||
assertPurchaseCellEquals(t, file, "I2", "catatan")
|
assertPurchaseCellEquals(t, file, "L2", "1000000")
|
||||||
|
assertPurchaseCellEquals(t, file, "M2", "-")
|
||||||
|
assertPurchaseCellEquals(t, file, "P2", "0")
|
||||||
|
assertPurchaseCellEquals(t, file, "Q2", "1000000")
|
||||||
|
assertPurchaseCellEquals(t, file, "R2", "Manager Purchase")
|
||||||
|
assertPurchaseCellEquals(t, file, "S2", "catatan")
|
||||||
|
|
||||||
assertPurchaseCellEquals(t, file, "A3", "PR-00012")
|
// Row 3: Purchase 1, Item 2 (Vitamin A)
|
||||||
assertPurchaseCellEquals(t, file, "B3", "-")
|
assertPurchaseCellEquals(t, file, "A3", "PR-00011")
|
||||||
assertPurchaseCellEquals(t, file, "C3", "-")
|
assertPurchaseCellEquals(t, file, "H3", "Vitamin A")
|
||||||
assertPurchaseCellEquals(t, file, "E3", "-")
|
assertPurchaseCellEquals(t, file, "J3", "botol")
|
||||||
assertPurchaseCellEquals(t, file, "F3", "Ditolak")
|
assertPurchaseCellEquals(t, file, "L3", "350000")
|
||||||
assertPurchaseCellEquals(t, file, "G3", "Rp 75.000")
|
assertPurchaseCellEquals(t, file, "Q3", "350000")
|
||||||
assertPurchaseCellEquals(t, file, "H3", "Obat X")
|
|
||||||
assertPurchaseCellEquals(t, file, "I3", "-")
|
// Row 4: Purchase 2, Item 1 (Obat X) — no location, rejected
|
||||||
|
assertPurchaseCellEquals(t, file, "A4", "PR-00012")
|
||||||
|
assertPurchaseCellEquals(t, file, "B4", "-")
|
||||||
|
assertPurchaseCellEquals(t, file, "C4", "-")
|
||||||
|
assertPurchaseCellEquals(t, file, "F4", "-")
|
||||||
|
assertPurchaseCellEquals(t, file, "H4", "Obat X")
|
||||||
|
assertPurchaseCellEquals(t, file, "J4", "-")
|
||||||
|
assertPurchaseCellEquals(t, file, "L4", "75000")
|
||||||
|
assertPurchaseCellEquals(t, file, "Q4", "75000")
|
||||||
|
assertPurchaseCellEquals(t, file, "R4", "Ditolak")
|
||||||
|
assertPurchaseCellEquals(t, file, "S4", "-")
|
||||||
|
|
||||||
|
// Row 5: SUM row — total produk=1425000, ekspedisi=0, grand total all=1425000
|
||||||
|
assertPurchaseCellEquals(t, file, "A5", "TOTAL")
|
||||||
|
assertPurchaseCellEquals(t, file, "L5", "1425000")
|
||||||
|
assertPurchaseCellEquals(t, file, "P5", "0")
|
||||||
|
assertPurchaseCellEquals(t, file, "Q5", "1425000")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertPurchaseCellEquals(t *testing.T, file *excelize.File, cell, expected string) {
|
func assertPurchaseCellEquals(t *testing.T, file *excelize.File, cell, expected string) {
|
||||||
@@ -144,13 +176,20 @@ func buildPurchaseForExportTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPurchaseItemForExportTest(productID uint, productName string, totalPrice float64, locationName string) entity.PurchaseItem {
|
func buildPurchaseItemForExportTest(productID uint, productName string, price, totalQty, totalPrice float64, locationName, uomName string) entity.PurchaseItem {
|
||||||
|
uomID := uint(0)
|
||||||
|
if uomName != "" {
|
||||||
|
uomID = productID + 2000
|
||||||
|
}
|
||||||
item := entity.PurchaseItem{
|
item := entity.PurchaseItem{
|
||||||
ProductId: productID,
|
ProductId: productID,
|
||||||
|
Price: price,
|
||||||
|
TotalQty: totalQty,
|
||||||
TotalPrice: totalPrice,
|
TotalPrice: totalPrice,
|
||||||
Product: &entity.Product{
|
Product: &entity.Product{
|
||||||
Id: productID,
|
Id: productID,
|
||||||
Name: productName,
|
Name: productName,
|
||||||
|
Uom: entity.Uom{Id: uomID, Name: uomName},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,15 @@ type PurchaseListDTO struct {
|
|||||||
RequesterName string `json:"requester_name"`
|
RequesterName string `json:"requester_name"`
|
||||||
PoExpedition []PoExpeditionDTO `json:"po_expedition"`
|
PoExpedition []PoExpeditionDTO `json:"po_expedition"`
|
||||||
Items []PurchaseItemDTO `json:"items"`
|
Items []PurchaseItemDTO `json:"items"`
|
||||||
Products []productDTO.ProductRelationDTO `json:"products"`
|
Products []productDTO.ProductRelationDTO `json:"products"`
|
||||||
Location *locationDTO.LocationRelationDTO `json:"location"`
|
Location *locationDTO.LocationRelationDTO `json:"location"`
|
||||||
Area *areaDTO.AreaRelationDTO `json:"area"`
|
Area *areaDTO.AreaRelationDTO `json:"area"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||||
|
ProductsTotal float64 `json:"products_total"`
|
||||||
|
ExpeditionTotal float64 `json:"expedition_total"`
|
||||||
|
GrandTotalAll float64 `json:"grand_total_all"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PurchaseDetailDTO struct {
|
type PurchaseDetailDTO struct {
|
||||||
@@ -69,6 +72,8 @@ type PurchaseItemDTO struct {
|
|||||||
VehicleNumber *string `json:"vehicle_number"`
|
VehicleNumber *string `json:"vehicle_number"`
|
||||||
TransportPerItem *float64 `json:"transport_per_item,omitempty"`
|
TransportPerItem *float64 `json:"transport_per_item,omitempty"`
|
||||||
ExpeditionVendor *supplierDTO.SupplierRelationDTO `json:"expedition_vendor,omitempty"`
|
ExpeditionVendor *supplierDTO.SupplierRelationDTO `json:"expedition_vendor,omitempty"`
|
||||||
|
ExpeditionQty float64 `json:"expedition_qty"`
|
||||||
|
ExpeditionTotal float64 `json:"expedition_total"`
|
||||||
HasChickin bool `json:"has_chickin"`
|
HasChickin bool `json:"has_chickin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +132,8 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
|||||||
if item.ExpenseNonstock != nil {
|
if item.ExpenseNonstock != nil {
|
||||||
priceCopy := item.ExpenseNonstock.Price
|
priceCopy := item.ExpenseNonstock.Price
|
||||||
dto.TransportPerItem = &priceCopy
|
dto.TransportPerItem = &priceCopy
|
||||||
|
dto.ExpeditionQty = item.ExpenseNonstock.Qty
|
||||||
|
dto.ExpeditionTotal = item.ExpenseNonstock.Qty * item.ExpenseNonstock.Price
|
||||||
|
|
||||||
if item.ExpenseNonstock.Expense != nil {
|
if item.ExpenseNonstock.Expense != nil {
|
||||||
exp := item.ExpenseNonstock.Expense
|
exp := item.ExpenseNonstock.Expense
|
||||||
@@ -173,15 +180,21 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
poExpedition = make([]PoExpeditionDTO, 0)
|
poExpedition = make([]PoExpeditionDTO, 0)
|
||||||
location *locationDTO.LocationRelationDTO
|
location *locationDTO.LocationRelationDTO
|
||||||
area *areaDTO.AreaRelationDTO
|
area *areaDTO.AreaRelationDTO
|
||||||
receivedDate *time.Time
|
receivedDate *time.Time
|
||||||
|
productsTotal float64
|
||||||
|
expeditionTotal float64
|
||||||
)
|
)
|
||||||
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
||||||
expeditionRefSet := make(map[uint64]struct{})
|
expeditionRefSet := make(map[uint64]struct{})
|
||||||
for i := range p.Items {
|
for i := range p.Items {
|
||||||
item := p.Items[i]
|
item := p.Items[i]
|
||||||
|
productsTotal += item.TotalPrice
|
||||||
|
if item.ExpenseNonstock != nil {
|
||||||
|
expeditionTotal += item.ExpenseNonstock.Qty * item.ExpenseNonstock.Price
|
||||||
|
}
|
||||||
if item.Product != nil && item.Product.Id != 0 {
|
if item.Product != nil && item.Product.Id != 0 {
|
||||||
if _, exists := productMap[item.Product.Id]; !exists {
|
if _, exists := productMap[item.Product.Id]; !exists {
|
||||||
productMap[item.Product.Id] = productDTO.ToProductRelationDTO(*item.Product)
|
productMap[item.Product.Id] = productDTO.ToProductRelationDTO(*item.Product)
|
||||||
@@ -235,6 +248,9 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
|||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
UpdatedAt: p.UpdatedAt,
|
UpdatedAt: p.UpdatedAt,
|
||||||
LatestApproval: latestApproval,
|
LatestApproval: latestApproval,
|
||||||
|
ProductsTotal: productsTotal,
|
||||||
|
ExpeditionTotal: expeditionTotal,
|
||||||
|
GrandTotalAll: productsTotal + expeditionTotal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,33 +145,16 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
createdFrom, createdTo, err := utils.ParseDateRangeForQuery(params.CreatedFrom, params.CreatedTo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, utils.BadRequest(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
productCategoryIDs, err := parseUintCSVFilter(params.ProductCategoryID, "product_category_id")
|
productCategoryIDs, err := parseUintCSVFilter(params.ProductCategoryID, "product_category_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, utils.BadRequest(err.Error())
|
return nil, 0, utils.BadRequest(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var poDateStart *time.Time
|
dateStart, dateEnd, err := parsePurchaseDateRangeForQuery(params.StartDate, params.EndDate, "date")
|
||||||
var poDateEnd *time.Time
|
if err != nil {
|
||||||
|
return nil, 0, utils.BadRequest(err.Error())
|
||||||
if strings.TrimSpace(params.PoDate) != "" {
|
|
||||||
poDate, parseErr := utils.ParseDateString(strings.TrimSpace(params.PoDate))
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, 0, utils.BadRequest("po_date must use format YYYY-MM-DD")
|
|
||||||
}
|
|
||||||
poDateStart = &poDate
|
|
||||||
poDateEndValue := poDate.AddDate(0, 0, 1)
|
|
||||||
poDateEnd = &poDateEndValue
|
|
||||||
} else {
|
|
||||||
poDateStart, poDateEnd, err = parsePoDateRangeForQuery(params.PoDateFrom, params.PoDateTo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, utils.BadRequest(err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
filterBy := strings.TrimSpace(params.FilterBy)
|
||||||
|
|
||||||
search := strings.ToLower(strings.TrimSpace(params.Search))
|
search := strings.ToLower(strings.TrimSpace(params.Search))
|
||||||
approvalStatuses := parseStringCSVFilter(params.ApprovalStatus)
|
approvalStatuses := parseStringCSVFilter(params.ApprovalStatus)
|
||||||
@@ -187,23 +170,41 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
db = db.Where("supplier_id = ?", params.SupplierID)
|
db = db.Where("supplier_id = ?", params.SupplierID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if createdFrom != nil {
|
switch filterBy {
|
||||||
db = db.Where("created_at >= ?", *createdFrom)
|
case "po_date":
|
||||||
}
|
if dateStart != nil {
|
||||||
|
db = db.Where("purchases.po_date >= ?", *dateStart)
|
||||||
if createdTo != nil {
|
}
|
||||||
db = db.Where("created_at < ?", *createdTo)
|
if dateEnd != nil {
|
||||||
}
|
db = db.Where("purchases.po_date < ?", *dateEnd)
|
||||||
if poDateStart != nil {
|
}
|
||||||
db = db.Where("purchases.po_date >= ?", *poDateStart)
|
case "due_date":
|
||||||
}
|
if dateStart != nil {
|
||||||
|
db = db.Where("purchases.due_date >= ?", *dateStart)
|
||||||
if poDateStart != nil {
|
}
|
||||||
db = db.Where("purchases.po_date >= ?", *poDateStart)
|
if dateEnd != nil {
|
||||||
}
|
db = db.Where("purchases.due_date < ?", *dateEnd)
|
||||||
|
}
|
||||||
if poDateEnd != nil {
|
case "received_date":
|
||||||
db = db.Where("purchases.po_date < ?", *poDateEnd)
|
if dateStart != nil {
|
||||||
|
db = db.Where(
|
||||||
|
`EXISTS (SELECT 1 FROM purchase_items pi WHERE pi.purchase_id = purchases.id AND pi.received_date >= ?)`,
|
||||||
|
*dateStart,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if dateEnd != nil {
|
||||||
|
db = db.Where(
|
||||||
|
`EXISTS (SELECT 1 FROM purchase_items pi WHERE pi.purchase_id = purchases.id AND pi.received_date < ?)`,
|
||||||
|
*dateEnd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if dateStart != nil {
|
||||||
|
db = db.Where("purchases.created_at >= ?", *dateStart)
|
||||||
|
}
|
||||||
|
if dateEnd != nil {
|
||||||
|
db = db.Where("purchases.created_at < ?", *dateEnd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if scope.Restrict {
|
if scope.Restrict {
|
||||||
@@ -263,6 +264,14 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
|
|
||||||
sortBy := strings.TrimSpace(params.SortBy)
|
sortBy := strings.TrimSpace(params.SortBy)
|
||||||
sortOrder := strings.ToUpper(strings.TrimSpace(params.SortOrder))
|
sortOrder := strings.ToUpper(strings.TrimSpace(params.SortOrder))
|
||||||
|
|
||||||
|
if sortBy == "" && (filterBy == "po_date" || filterBy == "due_date" || filterBy == "received_date" || filterBy == "created_at") {
|
||||||
|
sortBy = filterBy
|
||||||
|
if sortOrder == "" {
|
||||||
|
sortOrder = "ASC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sortOrder == "" {
|
if sortOrder == "" {
|
||||||
sortOrder = "DESC"
|
sortOrder = "DESC"
|
||||||
}
|
}
|
||||||
@@ -2238,30 +2247,36 @@ func (s *purchaseService) attachLatestApprovals(ctx context.Context, items []ent
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePoDateRangeForQuery(fromStr, toStr string) (*time.Time, *time.Time, error) {
|
func parsePurchaseDateRangeForQuery(fromStr, toStr, fieldName string) (*time.Time, *time.Time, error) {
|
||||||
|
jakartaLoc, err := time.LoadLocation("Asia/Jakarta")
|
||||||
|
if err != nil {
|
||||||
|
jakartaLoc = time.FixedZone("WIB", 7*60*60)
|
||||||
|
}
|
||||||
|
|
||||||
var fromPtr *time.Time
|
var fromPtr *time.Time
|
||||||
var toPtr *time.Time
|
var toPtr *time.Time
|
||||||
|
|
||||||
if strings.TrimSpace(fromStr) != "" {
|
if strings.TrimSpace(fromStr) != "" {
|
||||||
parsed, err := utils.ParseDateString(strings.TrimSpace(fromStr))
|
parsed, err := utils.ParseDateString(strings.TrimSpace(fromStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.New("po_date_from must use format YYYY-MM-DD")
|
return nil, nil, errors.New(fieldName + "_from must use format YYYY-MM-DD")
|
||||||
}
|
}
|
||||||
fromValue := parsed
|
t := time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, jakartaLoc)
|
||||||
fromPtr = &fromValue
|
fromPtr = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(toStr) != "" {
|
if strings.TrimSpace(toStr) != "" {
|
||||||
parsed, err := utils.ParseDateString(strings.TrimSpace(toStr))
|
parsed, err := utils.ParseDateString(strings.TrimSpace(toStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.New("po_date_to must use format YYYY-MM-DD")
|
return nil, nil, errors.New(fieldName + "_to must use format YYYY-MM-DD")
|
||||||
}
|
}
|
||||||
nextDay := parsed.AddDate(0, 0, 1)
|
t := time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, jakartaLoc)
|
||||||
|
nextDay := t.AddDate(0, 0, 1)
|
||||||
toPtr = &nextDay
|
toPtr = &nextDay
|
||||||
}
|
}
|
||||||
|
|
||||||
if fromPtr != nil && toPtr != nil && fromPtr.After(*toPtr) {
|
if fromPtr != nil && toPtr != nil && fromPtr.After(*toPtr) {
|
||||||
return nil, nil, errors.New("po_date_from must be earlier than po_date_to")
|
return nil, nil, errors.New(fieldName + "_from must be earlier than " + fieldName + "_to")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromPtr, toPtr, nil
|
return fromPtr, toPtr, nil
|
||||||
|
|||||||
@@ -75,12 +75,10 @@ type Query struct {
|
|||||||
ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
|
ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
|
||||||
ProductCategoryID string `query:"product_category_id" validate:"omitempty,max=500"`
|
ProductCategoryID string `query:"product_category_id" validate:"omitempty,max=500"`
|
||||||
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=500"`
|
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=500"`
|
||||||
PoDate string `query:"po_date" validate:"omitempty,datetime=2006-01-02"`
|
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
PoDateFrom string `query:"po_date_from" validate:"omitempty,datetime=2006-01-02"`
|
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
PoDateTo string `query:"po_date_to" validate:"omitempty,datetime=2006-01-02"`
|
FilterBy string `query:"filter_by" validate:"omitempty,oneof=po_date due_date received_date created_at"`
|
||||||
Search string `query:"search" validate:"omitempty,max=100"`
|
Search string `query:"search" validate:"omitempty,max=100"`
|
||||||
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
|
|
||||||
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
|
|
||||||
SortBy string `query:"sort_by" validate:"omitempty,oneof=po_expedition supplier requester_name products location po_date received_date due_date status created_at po_number"`
|
SortBy string `query:"sort_by" validate:"omitempty,oneof=po_expedition supplier requester_name products location po_date received_date due_date status created_at po_number"`
|
||||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc ASC DESC"`
|
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc ASC DESC"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,8 +214,7 @@ func writeCustomerPaymentSheet(file *excelize.File, sheet string, item dto.Custo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Row 2: saldo awal
|
// Row 2: saldo awal
|
||||||
initialFormatted := formatCPRupiah(item.InitialBalance)
|
if err := file.SetCellValue(sheet, "N2", item.InitialBalance); err != nil {
|
||||||
if err := file.SetCellValue(sheet, "N2", initialFormatted); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if item.InitialBalance < 0 {
|
if item.InitialBalance < 0 {
|
||||||
@@ -248,14 +247,14 @@ func writeCustomerPaymentSheet(file *excelize.File, sheet string, item dto.Custo
|
|||||||
totalRowNum := len(item.Rows) + 3
|
totalRowNum := len(item.Rows) + 3
|
||||||
totalRowStr := fmt.Sprintf("%d", totalRowNum)
|
totalRowStr := fmt.Sprintf("%d", totalRowNum)
|
||||||
|
|
||||||
totalCells := map[string]string{
|
totalCells := map[string]interface{}{
|
||||||
"A": "Total",
|
"A": "Total",
|
||||||
"G": formatCPIDInteger(item.Summary.TotalQty),
|
"G": formatCPIDInteger(item.Summary.TotalQty),
|
||||||
"H": formatCPIDInteger(item.Summary.TotalWeight),
|
"H": formatCPIDInteger(item.Summary.TotalWeight),
|
||||||
"K": formatCPRupiah(item.Summary.TotalFinalAmount),
|
"K": item.Summary.TotalFinalAmount,
|
||||||
"L": formatCPRupiah(item.Summary.TotalGrandAmount),
|
"L": item.Summary.TotalGrandAmount,
|
||||||
"M": formatCPRupiah(item.Summary.TotalPayment),
|
"M": item.Summary.TotalPayment,
|
||||||
"N": formatCPRupiah(item.Summary.TotalAccountsReceivable),
|
"N": item.Summary.TotalAccountsReceivable,
|
||||||
}
|
}
|
||||||
for col, val := range totalCells {
|
for col, val := range totalCells {
|
||||||
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
if err := file.SetCellValue(sheet, col+totalRowStr, val); err != nil {
|
||||||
@@ -369,8 +368,7 @@ func writeCustomerPaymentAllRows(file *excelize.File, sheet string, items []dto.
|
|||||||
if err := file.SetCellValue(sheet, "A"+saldoStr, name); err != nil {
|
if err := file.SetCellValue(sheet, "A"+saldoStr, name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
initialFormatted := formatCPRupiah(item.InitialBalance)
|
if err := file.SetCellValue(sheet, "O"+saldoStr, item.InitialBalance); err != nil {
|
||||||
if err := file.SetCellValue(sheet, "O"+saldoStr, initialFormatted); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellStyle(sheet, "A"+saldoStr, lastHeaderCol+saldoStr, dataStyle); err != nil {
|
if err := file.SetCellStyle(sheet, "A"+saldoStr, lastHeaderCol+saldoStr, dataStyle); err != nil {
|
||||||
@@ -409,15 +407,15 @@ func writeCustomerPaymentAllRows(file *excelize.File, sheet string, items []dto.
|
|||||||
|
|
||||||
// Total row
|
// Total row
|
||||||
totalStr := fmt.Sprintf("%d", currentRow)
|
totalStr := fmt.Sprintf("%d", currentRow)
|
||||||
totalCells := map[string]string{
|
totalCells := map[string]interface{}{
|
||||||
"A": name,
|
"A": name,
|
||||||
"B": "Total",
|
"B": "Total",
|
||||||
"H": formatCPIDInteger(item.Summary.TotalQty),
|
"H": formatCPIDInteger(item.Summary.TotalQty),
|
||||||
"I": formatCPIDInteger(item.Summary.TotalWeight),
|
"I": formatCPIDInteger(item.Summary.TotalWeight),
|
||||||
"L": formatCPRupiah(item.Summary.TotalFinalAmount),
|
"L": item.Summary.TotalFinalAmount,
|
||||||
"M": formatCPRupiah(item.Summary.TotalGrandAmount),
|
"M": item.Summary.TotalGrandAmount,
|
||||||
"N": formatCPRupiah(item.Summary.TotalPayment),
|
"N": item.Summary.TotalPayment,
|
||||||
"O": formatCPRupiah(item.Summary.TotalAccountsReceivable),
|
"O": item.Summary.TotalAccountsReceivable,
|
||||||
}
|
}
|
||||||
for col, val := range totalCells {
|
for col, val := range totalCells {
|
||||||
if err := file.SetCellValue(sheet, col+totalStr, val); err != nil {
|
if err := file.SetCellValue(sheet, col+totalStr, val); err != nil {
|
||||||
@@ -453,11 +451,11 @@ func customerPaymentRowCells(row dto.CustomerPaymentReportRow, seq int) []interf
|
|||||||
formatCPIDInteger(row.Qty),
|
formatCPIDInteger(row.Qty),
|
||||||
formatCPIDInteger(row.Weight),
|
formatCPIDInteger(row.Weight),
|
||||||
formatCPAvg(row.AverageWeight),
|
formatCPAvg(row.AverageWeight),
|
||||||
formatCPRupiah(row.UnitPrice),
|
row.UnitPrice,
|
||||||
formatCPRupiah(row.FinalPrice),
|
row.FinalPrice,
|
||||||
formatCPRupiah(row.TotalPrice),
|
row.TotalPrice,
|
||||||
formatCPRupiah(row.PaymentAmount),
|
row.PaymentAmount,
|
||||||
formatCPRupiah(row.AccountsReceivable),
|
row.AccountsReceivable,
|
||||||
safeCPText(row.Status),
|
safeCPText(row.Status),
|
||||||
joinCPStrings(row.PickupInfo),
|
joinCPStrings(row.PickupInfo),
|
||||||
safeCPText(row.SalesPerson),
|
safeCPText(row.SalesPerson),
|
||||||
@@ -546,13 +544,6 @@ func formatCPIDInteger(v float64) string {
|
|||||||
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 {
|
func formatCPAvg(v float64) string {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -197,9 +196,9 @@ func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingIte
|
|||||||
item.Qty,
|
item.Qty,
|
||||||
item.AverageWeightKg,
|
item.AverageWeightKg,
|
||||||
item.TotalWeightKg,
|
item.TotalWeightKg,
|
||||||
formatMarketingRupiah(item.SalesPricePerKg),
|
item.SalesPricePerKg,
|
||||||
formatMarketingRupiah(item.HppPricePerKg),
|
item.HppPricePerKg,
|
||||||
formatMarketingRupiah(item.SalesAmount),
|
item.SalesAmount,
|
||||||
}
|
}
|
||||||
|
|
||||||
for colIdx, val := range values {
|
for colIdx, val := range values {
|
||||||
@@ -229,13 +228,13 @@ func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingIte
|
|||||||
if err := file.SetCellValue(sheet, "N"+totalRow, summary.TotalWeightKg); err != nil {
|
if err := file.SetCellValue(sheet, "N"+totalRow, summary.TotalWeightKg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "O"+totalRow, formatMarketingRupiah(summary.AverageSalesPrice)); err != nil {
|
if err := file.SetCellValue(sheet, "O"+totalRow, summary.AverageSalesPrice); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "P"+totalRow, formatMarketingRupiah(summary.TotalHppPricePerKg)); err != nil {
|
if err := file.SetCellValue(sheet, "P"+totalRow, summary.TotalHppPricePerKg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := file.SetCellValue(sheet, "Q"+totalRow, formatMarketingRupiah(float64(summary.TotalSalesAmount))); err != nil {
|
if err := file.SetCellValue(sheet, "Q"+totalRow, float64(summary.TotalSalesAmount)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,30 +332,3 @@ func safeMarketingExportText(value string) string {
|
|||||||
return trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatMarketingRupiah formats a float64 as Indonesian Rupiah string.
|
|
||||||
// e.g. 1000000 → "Rp 1.000.000"
|
|
||||||
func formatMarketingRupiah(value float64) string {
|
|
||||||
rounded := int64(math.Round(value))
|
|
||||||
|
|
||||||
negative := rounded < 0
|
|
||||||
abs := rounded
|
|
||||||
if negative {
|
|
||||||
abs = -rounded
|
|
||||||
}
|
|
||||||
|
|
||||||
numStr := strconv.FormatInt(abs, 10)
|
|
||||||
n := len(numStr)
|
|
||||||
|
|
||||||
var b strings.Builder
|
|
||||||
for i, c := range numStr {
|
|
||||||
if i > 0 && (n-i)%3 == 0 {
|
|
||||||
b.WriteByte('.')
|
|
||||||
}
|
|
||||||
b.WriteRune(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if negative {
|
|
||||||
return "Rp -" + b.String()
|
|
||||||
}
|
|
||||||
return "Rp " + b.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -240,22 +240,33 @@ func (r *balanceMonitoringRepositoryImpl) GetSalesTotalsBeforeDate(ctx context.C
|
|||||||
return map[uint]float64{}, nil
|
return map[uint]float64{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dateColumn := resolveBalanceMonitoringDateColumn(filters.FilterBy)
|
|
||||||
|
|
||||||
type row struct {
|
type row struct {
|
||||||
CustomerID uint `gorm:"column:customer_id"`
|
CustomerID uint `gorm:"column:customer_id"`
|
||||||
Total float64 `gorm:"column:total"`
|
Total float64 `gorm:"column:total"`
|
||||||
}
|
}
|
||||||
rows := make([]row, 0)
|
rows := make([]row, 0)
|
||||||
db := r.db.WithContext(ctx).
|
|
||||||
Table("marketing_delivery_products mdp").
|
var db *gorm.DB
|
||||||
Select("m.customer_id AS customer_id, COALESCE(SUM(mdp.total_price), 0) AS total").
|
if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" {
|
||||||
Joins("INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
|
// Count products that have at least one delivery before startDate (no double-counting)
|
||||||
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
db = r.db.WithContext(ctx).
|
||||||
Where("m.customer_id IN ?", customerIDs).
|
Table("marketing_products mp").
|
||||||
Where("m.deleted_at IS NULL").
|
Select("m.customer_id AS customer_id, COALESCE(SUM(mp.total_price), 0) AS total").
|
||||||
Where("mdp.delivery_date IS NOT NULL").
|
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
||||||
Where(fmt.Sprintf("DATE(%s) < ?", dateColumn), startDate)
|
Where("m.customer_id IN ?", customerIDs).
|
||||||
|
Where("m.deleted_at IS NULL").
|
||||||
|
Where("EXISTS (SELECT 1 FROM marketing_delivery_products mdp WHERE mdp.marketing_product_id = mp.id AND mdp.delivery_date IS NOT NULL AND DATE(mdp.delivery_date) < ?)", startDate)
|
||||||
|
} else {
|
||||||
|
// sold_at: SO-date sebelum startDate DAN sudah di-approve sebagai Delivery Order (step >= 3)
|
||||||
|
db = r.db.WithContext(ctx).
|
||||||
|
Table("marketing_products mp").
|
||||||
|
Select("m.customer_id AS customer_id, COALESCE(SUM(mp.total_price), 0) AS total").
|
||||||
|
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
||||||
|
Where("m.customer_id IN ?", customerIDs).
|
||||||
|
Where("m.deleted_at IS NULL").
|
||||||
|
Where("DATE(m.so_date) < ?", startDate).
|
||||||
|
Where("EXISTS (SELECT 1 FROM approvals a WHERE a.approvable_type = 'MARKETINGS' AND a.approvable_id = mp.marketing_id AND a.step_number >= 3)")
|
||||||
|
}
|
||||||
|
|
||||||
if len(filters.SalesIDs) > 0 {
|
if len(filters.SalesIDs) > 0 {
|
||||||
db = db.Where("m.sales_person_id IN ?", filters.SalesIDs)
|
db = db.Where("m.sales_person_id IN ?", filters.SalesIDs)
|
||||||
@@ -318,28 +329,41 @@ func (r *balanceMonitoringRepositoryImpl) GetSalesByCategoryInPeriod(ctx context
|
|||||||
return map[uint]BalanceMonitoringCategoryRow{}, nil
|
return map[uint]BalanceMonitoringCategoryRow{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dateColumn := resolveBalanceMonitoringDateColumn(filters.FilterBy)
|
const selectCols = `m.customer_id AS customer_id,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.qty ELSE 0 END), 0) AS ayam_qty,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.total_weight ELSE 0 END), 0) AS ayam_kg,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mp.total_price ELSE 0 END), 0) AS ayam_nominal,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.qty ELSE 0 END), 0) AS telur_qty,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.total_weight ELSE 0 END), 0) AS telur_kg,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mp.total_price ELSE 0 END), 0) AS telur_nominal,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.qty ELSE 0 END), 0) AS trading_qty,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.total_weight ELSE 0 END), 0) AS trading_kg,
|
||||||
|
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mp.total_price ELSE 0 END), 0) AS trading_nominal`
|
||||||
|
|
||||||
rows := make([]BalanceMonitoringCategoryRow, 0)
|
rows := make([]BalanceMonitoringCategoryRow, 0)
|
||||||
db := r.db.WithContext(ctx).
|
|
||||||
Table("marketing_delivery_products mdp").
|
var db *gorm.DB
|
||||||
Select(`m.customer_id AS customer_id,
|
if strings.ToLower(strings.TrimSpace(filters.FilterBy)) == "realized_at" {
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mdp.usage_qty ELSE 0 END), 0) AS ayam_qty,
|
// Count products that have at least one delivery in the period (no double-counting)
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mdp.total_weight ELSE 0 END), 0) AS ayam_kg,
|
db = r.db.WithContext(ctx).
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type IN ('AYAM','AYAM_PULLET') THEN mdp.total_price ELSE 0 END), 0) AS ayam_nominal,
|
Table("marketing_products mp").
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mdp.usage_qty ELSE 0 END), 0) AS telur_qty,
|
Select(selectCols).
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mdp.total_weight ELSE 0 END), 0) AS telur_kg,
|
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TELUR' THEN mdp.total_price ELSE 0 END), 0) AS telur_nominal,
|
Where("m.customer_id IN ?", customerIDs).
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mdp.usage_qty ELSE 0 END), 0) AS trading_qty,
|
Where("m.deleted_at IS NULL").
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mdp.total_weight ELSE 0 END), 0) AS trading_kg,
|
Where("EXISTS (SELECT 1 FROM marketing_delivery_products mdp WHERE mdp.marketing_product_id = mp.id AND mdp.delivery_date IS NOT NULL AND DATE(mdp.delivery_date) >= ? AND DATE(mdp.delivery_date) <= ?)", startDate, endDate)
|
||||||
COALESCE(SUM(CASE WHEN m.marketing_type = 'TRADING' THEN mdp.total_price ELSE 0 END), 0) AS trading_nominal`).
|
} else {
|
||||||
Joins("INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
|
// sold_at: SO-date dalam period DAN sudah di-approve sebagai Delivery Order (step >= 3)
|
||||||
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
db = r.db.WithContext(ctx).
|
||||||
Where("m.customer_id IN ?", customerIDs).
|
Table("marketing_products mp").
|
||||||
Where("m.deleted_at IS NULL").
|
Select(selectCols).
|
||||||
Where("mdp.delivery_date IS NOT NULL").
|
Joins("INNER JOIN marketings m ON m.id = mp.marketing_id").
|
||||||
Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), startDate).
|
Where("m.customer_id IN ?", customerIDs).
|
||||||
Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), endDate)
|
Where("m.deleted_at IS NULL").
|
||||||
|
Where("DATE(m.so_date) >= ?", startDate).
|
||||||
|
Where("DATE(m.so_date) <= ?", endDate).
|
||||||
|
Where("EXISTS (SELECT 1 FROM approvals a WHERE a.approvable_type = 'MARKETINGS' AND a.approvable_id = mp.marketing_id AND a.step_number >= 3)")
|
||||||
|
}
|
||||||
|
|
||||||
if len(filters.SalesIDs) > 0 {
|
if len(filters.SalesIDs) > 0 {
|
||||||
db = db.Where("m.sales_person_id IN ?", filters.SalesIDs)
|
db = db.Where("m.sales_person_id IN ?", filters.SalesIDs)
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ func (r *debtSupplierRepositoryImpl) baseExpenseSupplierIDs(ctx context.Context,
|
|||||||
Table("expenses").
|
Table("expenses").
|
||||||
Select("DISTINCT expenses.supplier_id").
|
Select("DISTINCT expenses.supplier_id").
|
||||||
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
||||||
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)).
|
Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
Where("expenses.deleted_at IS NULL")
|
Where("expenses.deleted_at IS NULL")
|
||||||
|
|
||||||
@@ -623,7 +623,7 @@ func (r *debtSupplierRepositoryImpl) GetExpensesBySuppliers(ctx context.Context,
|
|||||||
Model(&entity.Expense{}).
|
Model(&entity.Expense{}).
|
||||||
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
||||||
Where("expenses.supplier_id IN ?", supplierIDs).
|
Where("expenses.supplier_id IN ?", supplierIDs).
|
||||||
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)).
|
Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
Where("expenses.deleted_at IS NULL")
|
Where("expenses.deleted_at IS NULL")
|
||||||
|
|
||||||
@@ -692,7 +692,7 @@ func (r *debtSupplierRepositoryImpl) GetExpenseTotalsBeforeDate(ctx context.Cont
|
|||||||
Joins("JOIN expense_nonstocks en ON en.expense_id = expenses.id").
|
Joins("JOIN expense_nonstocks en ON en.expense_id = expenses.id").
|
||||||
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
Joins("JOIN (?) AS la ON la.approvable_id = expenses.id", r.latestExpenseApproval(ctx)).
|
||||||
Where("expenses.supplier_id IN ?", supplierIDs).
|
Where("expenses.supplier_id IN ?", supplierIDs).
|
||||||
Where("la.step_number >= ?", uint16(utils.ExpenseStepFinance)).
|
Where("la.step_number >= ?", uint16(utils.ExpenseStepRealisasi)).
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
Where("expenses.deleted_at IS NULL").
|
Where("expenses.deleted_at IS NULL").
|
||||||
Where("DATE(expenses.transaction_date) < ?", dateFrom).
|
Where("DATE(expenses.transaction_date) < ?", dateFrom).
|
||||||
|
|||||||
Reference in New Issue
Block a user