feat: filter improvement

This commit is contained in:
Adnan Zahir
2026-04-23 00:17:24 +07:00
parent a15fd1b174
commit e24e2ff123
13 changed files with 702 additions and 378 deletions
@@ -51,9 +51,18 @@ func (u *ExpenseController) GetAll(c *fiber.Ctx) error {
}
query := &validation.Query{
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""),
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: strings.TrimSpace(c.Query("search", "")),
TransactionDate: strings.TrimSpace(c.Query("transaction_date", "")),
RealizationDate: strings.TrimSpace(c.Query("realization_date", "")),
LocationID: uint64(c.QueryInt("location_id", 0)),
VendorID: uint64(c.QueryInt("vendor_id", 0)),
Category: strings.TrimSpace(c.Query("category", "")),
ApprovalStatus: strings.TrimSpace(c.Query("approval_status", "")),
RealizationStatus: strings.TrimSpace(c.Query("realization_status", "")),
ProjectFlockID: uint64(c.QueryInt("project_flock_id", 0)),
ProjectFlockKandangID: uint64(c.QueryInt("project_flock_kandang_id", 0)),
}
if query.Page < 1 || query.Limit < 1 {
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
@@ -86,6 +87,25 @@ func (s expenseService) withRelations(db *gorm.DB) *gorm.DB {
})
}
func normalizeExpenseApprovalStatusFilter(raw string) string {
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(raw), " ", "_")) {
case "HEAD_AREA", "APPROVAL_HEAD_AREA":
return "Approval Head Area"
case "UNIT_VICE_PRESIDENT", "APPROVAL_UNIT_VICE_PRESIDENT", "BUSINESS_UNIT_VICE_PRESIDENT", "APPROVAL_BUSINESS_UNIT_VICE_PRESIDENT":
return "Approval Unit Vice President"
case "FINANCE", "APPROVAL_FINANCE":
return "Approval Finance"
case "REALISASI":
return "Realisasi"
case "SELESAI":
return "Selesai"
case "DITOLAK", "REJECTED":
return "REJECTED"
default:
return ""
}
}
func (s expenseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]expenseDto.ExpenseListDTO, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
@@ -98,10 +118,177 @@ func (s expenseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]expens
expenses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db)
db, scopeErr = middleware.ApplyLocationScope(c, db, "expenses.location_id")
if params.Search != "" {
return db.Where("category ILIKE ?", "%"+params.Search+"%")
db = db.Where("expenses.deleted_at IS NULL")
if params.TransactionDate != "" {
db = db.Where("DATE(expenses.transaction_date) = DATE(?)", params.TransactionDate)
}
return db.Order("created_at DESC").Order("updated_at DESC")
if params.RealizationDate != "" {
db = db.Where("DATE(expenses.realization_date) = DATE(?)", params.RealizationDate)
}
if params.LocationID > 0 {
db = db.Where("expenses.location_id = ?", params.LocationID)
}
if params.VendorID > 0 {
db = db.Where("expenses.supplier_id = ?", params.VendorID)
}
if params.Category != "" {
db = db.Where("expenses.category = ?", params.Category)
}
if params.ProjectFlockID > 0 {
projectFlockJSON := fmt.Sprintf("[%d]", params.ProjectFlockID)
db = db.Where(`(
EXISTS (
SELECT 1
FROM expense_nonstocks en
LEFT JOIN project_flock_kandangs pfk ON pfk.id = en.project_flock_kandang_id
WHERE en.expense_id = expenses.id
AND (
pfk.project_flock_id = ? OR
en.kandang_id IN (
SELECT kandang_id
FROM project_flock_kandangs
WHERE project_flock_id = ?
)
)
) OR
(expenses.project_flock_id IS NOT NULL AND expenses.project_flock_id::jsonb @> ?::jsonb)
)`, params.ProjectFlockID, params.ProjectFlockID, projectFlockJSON)
}
if params.ProjectFlockKandangID > 0 {
db = db.Where(`EXISTS (
SELECT 1
FROM expense_nonstocks en
LEFT JOIN project_flock_kandangs selected_pfk ON selected_pfk.id = ?
WHERE en.expense_id = expenses.id
AND (
en.project_flock_kandang_id = ? OR
(selected_pfk.kandang_id IS NOT NULL AND en.kandang_id = selected_pfk.kandang_id)
)
)`, params.ProjectFlockKandangID, params.ProjectFlockKandangID)
}
latestApprovalSubQuery := s.Repository.DB().
WithContext(c.Context()).
Table("approvals").
Select("DISTINCT ON (approvable_id) approvable_id, step_name, action, step_number").
Where("approvable_type = ?", utils.ApprovalWorkflowExpense.String()).
Order("approvable_id, action_at DESC, id DESC")
if approvalStatus := normalizeExpenseApprovalStatusFilter(params.ApprovalStatus); approvalStatus != "" {
if approvalStatus == "REJECTED" {
db = db.Where(`EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND latest_approval.action = ?
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
} else {
db = db.Where(`EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND LOWER(latest_approval.step_name) = LOWER(?)
AND (latest_approval.action IS NULL OR latest_approval.action <> ?)
)`, latestApprovalSubQuery, approvalStatus, string(entity.ApprovalActionRejected))
}
}
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(params.RealizationStatus), " ", "_")) {
case "REALIZED", "SUDAH_REALISASI":
db = db.Where(`EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND (latest_approval.action IS NULL OR latest_approval.action <> ?)
AND latest_approval.step_number >= 5
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
case "NOT_REALIZED", "BELUM_REALISASI":
db = db.Where(`NOT EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND (latest_approval.action IS NULL OR latest_approval.action <> ?)
AND latest_approval.step_number >= 5
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
db = db.Where(`NOT EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND latest_approval.action = ?
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
case "REJECTED", "DITOLAK":
db = db.Where(`EXISTS (
SELECT 1
FROM (?) AS latest_approval
WHERE latest_approval.approvable_id = expenses.id
AND latest_approval.action = ?
)`, latestApprovalSubQuery, string(entity.ApprovalActionRejected))
}
if search := strings.ToLower(strings.TrimSpace(params.Search)); search != "" {
like := "%" + search + "%"
db = db.Where(`(
LOWER(COALESCE(expenses.reference_number, '')) LIKE ?
OR LOWER(COALESCE(expenses.po_number, '')) LIKE ?
OR LOWER(COALESCE(expenses.category, '')) LIKE ?
OR EXISTS (
SELECT 1
FROM suppliers s
WHERE s.id = expenses.supplier_id
AND LOWER(COALESCE(s.name, '')) LIKE ?
)
OR EXISTS (
SELECT 1
FROM locations l
WHERE l.id = expenses.location_id
AND LOWER(COALESCE(l.name, '')) LIKE ?
)
OR EXISTS (
SELECT 1
FROM users u
WHERE u.id = expenses.created_by
AND LOWER(COALESCE(u.name, '')) LIKE ?
)
OR EXISTS (
SELECT 1
FROM expense_nonstocks en
LEFT JOIN project_flock_kandangs pfk ON pfk.id = en.project_flock_kandang_id
LEFT JOIN project_flocks pf ON pf.id = pfk.project_flock_id
LEFT JOIN kandangs k ON k.id = COALESCE(en.kandang_id, pfk.kandang_id)
WHERE en.expense_id = expenses.id
AND (
LOWER(COALESCE(pf.flock_name, '')) LIKE ? OR
LOWER(COALESCE(k.name, '')) LIKE ?
)
)
OR EXISTS (
SELECT 1
FROM approvals a
WHERE a.approvable_type = ?
AND a.approvable_id = expenses.id
AND (
LOWER(COALESCE(a.step_name, '')) LIKE ? OR
LOWER(COALESCE(CAST(a.action AS TEXT), '')) LIKE ? OR
LOWER(COALESCE(a.notes, '')) LIKE ?
)
)
)`,
like,
like,
like,
like,
like,
like,
like,
like,
utils.ApprovalWorkflowExpense.String(),
like,
like,
like,
)
}
return db.Order("expenses.created_at DESC").Order("expenses.updated_at DESC")
})
if scopeErr != nil {
@@ -42,9 +42,18 @@ type Update struct {
}
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty,max=50"`
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty,max=100"`
TransactionDate string `query:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
RealizationDate string `query:"realization_date" validate:"omitempty,datetime=2006-01-02"`
LocationID uint64 `query:"location_id" validate:"omitempty,gt=0"`
VendorID uint64 `query:"vendor_id" validate:"omitempty,gt=0"`
Category string `query:"category" validate:"omitempty,oneof=BOP NON-BOP"`
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=100"`
RealizationStatus string `query:"realization_status" validate:"omitempty,max=100"`
ProjectFlockID uint64 `query:"project_flock_id" validate:"omitempty,gt=0"`
ProjectFlockKandangID uint64 `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
}
type CreateRealization struct {