mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat: filter improvement
This commit is contained in:
@@ -51,9 +51,18 @@ func (u *ExpenseController) GetAll(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: c.Query("search", ""),
|
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 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
|
"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) {
|
func (s expenseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]expenseDto.ExpenseListDTO, int64, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
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 {
|
expenses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
db, scopeErr = middleware.ApplyLocationScope(c, db, "expenses.location_id")
|
db, scopeErr = middleware.ApplyLocationScope(c, db, "expenses.location_id")
|
||||||
if params.Search != "" {
|
db = db.Where("expenses.deleted_at IS NULL")
|
||||||
return db.Where("category ILIKE ?", "%"+params.Search+"%")
|
|
||||||
|
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 {
|
if scopeErr != nil {
|
||||||
|
|||||||
@@ -42,9 +42,18 @@ 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,max=100,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
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 {
|
type CreateRealization struct {
|
||||||
|
|||||||
@@ -57,13 +57,15 @@ func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &validation.DeliveryOrderQuery{
|
query := &validation.DeliveryOrderQuery{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: strings.TrimSpace(c.Query("search", "")),
|
Search: strings.TrimSpace(c.Query("search", "")),
|
||||||
ProductIDs: productIDs,
|
ProductIDs: productIDs,
|
||||||
Status: strings.ReplaceAll(strings.TrimSpace(c.Query("status", "")), "_", " "),
|
Status: strings.ReplaceAll(strings.TrimSpace(c.Query("status", "")), "_", " "),
|
||||||
CustomerId: uint(c.QueryInt("customer_id", 0)),
|
CustomerId: uint(c.QueryInt("customer_id", 0)),
|
||||||
MarketingId: uint(c.QueryInt("marketing_id", 0)),
|
MarketingId: uint(c.QueryInt("marketing_id", 0)),
|
||||||
|
ProjectFlockID: uint(c.QueryInt("project_flock_id", 0)),
|
||||||
|
ProjectFlockKandangID: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAllExcelExportRequest(c) {
|
if isAllExcelExportRequest(c) {
|
||||||
|
|||||||
@@ -85,6 +85,102 @@ func (s deliveryOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("Products.DeliveryProduct")
|
Preload("Products.DeliveryProduct")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) marketingOwnerRelationQuery(ctx context.Context) *gorm.DB {
|
||||||
|
return s.MarketingRepo.DB().
|
||||||
|
WithContext(ctx).
|
||||||
|
Table("marketing_products mp").
|
||||||
|
Select("1").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||||
|
Joins("LEFT JOIN project_flock_kandangs pfk ON pfk.id = pw.project_flock_kandang_id").
|
||||||
|
Joins("LEFT JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
|
Joins("LEFT JOIN warehouses w ON w.id = pw.warehouse_id").
|
||||||
|
Joins("LEFT JOIN kandangs k ON k.id = COALESCE(pfk.kandang_id, w.kandang_id)").
|
||||||
|
Where("mp.marketing_id = marketings.id")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) marketingAttributionRelationQuery(ctx context.Context) *gorm.DB {
|
||||||
|
baseDB := s.MarketingRepo.DB().WithContext(ctx)
|
||||||
|
return baseDB.
|
||||||
|
Table("marketing_delivery_products mdp").
|
||||||
|
Select("1").
|
||||||
|
Joins("JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
|
||||||
|
Joins("JOIN (?) AS mda ON mda.marketing_delivery_product_id = mdp.id", commonRepo.MarketingDeliveryAttributionRowsQuery(baseDB)).
|
||||||
|
Joins("JOIN project_flock_kandangs pfk_attr ON pfk_attr.id = mda.project_flock_kandang_id").
|
||||||
|
Joins("JOIN project_flocks pf_attr ON pf_attr.id = pfk_attr.project_flock_id").
|
||||||
|
Joins("JOIN kandangs k_attr ON k_attr.id = pfk_attr.kandang_id").
|
||||||
|
Where("mp.marketing_id = marketings.id")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) applyMarketingProjectFlockFilter(ctx context.Context, db *gorm.DB, projectFlockID, projectFlockKandangID uint) *gorm.DB {
|
||||||
|
if projectFlockID > 0 {
|
||||||
|
db = db.Where(
|
||||||
|
"(EXISTS (?) OR EXISTS (?))",
|
||||||
|
s.marketingOwnerRelationQuery(ctx).Where("pfk.project_flock_id = ?", projectFlockID),
|
||||||
|
s.marketingAttributionRelationQuery(ctx).Where("mda.project_flock_id = ?", projectFlockID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectFlockKandangID > 0 {
|
||||||
|
db = db.Where(
|
||||||
|
"(EXISTS (?) OR EXISTS (?))",
|
||||||
|
s.marketingOwnerRelationQuery(ctx).Where("pw.project_flock_kandang_id = ?", projectFlockKandangID),
|
||||||
|
s.marketingAttributionRelationQuery(ctx).Where("mda.project_flock_kandang_id = ?", projectFlockKandangID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) applyMarketingSearchFilter(ctx context.Context, db *gorm.DB, rawSearch string) *gorm.DB {
|
||||||
|
searchPattern := "%" + strings.TrimSpace(rawSearch) + "%"
|
||||||
|
if searchPattern == "%%" {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Where(
|
||||||
|
`(
|
||||||
|
marketings.so_number ILIKE ? OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM customers c
|
||||||
|
WHERE c.id = marketings.customer_id
|
||||||
|
AND c.name ILIKE ?
|
||||||
|
) OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users su
|
||||||
|
WHERE su.id = marketings.sales_person_id
|
||||||
|
AND su.name ILIKE ?
|
||||||
|
) OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM marketing_products mp
|
||||||
|
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
WHERE mp.marketing_id = marketings.id
|
||||||
|
AND p.name ILIKE ?
|
||||||
|
) OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM marketing_products mp
|
||||||
|
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||||
|
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||||
|
WHERE mp.marketing_id = marketings.id
|
||||||
|
AND w.name ILIKE ?
|
||||||
|
) OR
|
||||||
|
EXISTS (?) OR
|
||||||
|
EXISTS (?)
|
||||||
|
)`,
|
||||||
|
searchPattern,
|
||||||
|
searchPattern,
|
||||||
|
searchPattern,
|
||||||
|
searchPattern,
|
||||||
|
searchPattern,
|
||||||
|
s.marketingOwnerRelationQuery(ctx).Where("pf.flock_name ILIKE ? OR k.name ILIKE ?", searchPattern, searchPattern),
|
||||||
|
s.marketingAttributionRelationQuery(ctx).Where("pf_attr.flock_name ILIKE ? OR k_attr.name ILIKE ?", searchPattern, searchPattern),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketingId uint) (*dto.MarketingDetailDTO, error) {
|
func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketingId uint) (*dto.MarketingDetailDTO, error) {
|
||||||
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), marketingId); err != nil {
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), marketingId); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -158,41 +254,6 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Search != "" {
|
|
||||||
searchPattern := "%" + params.Search + "%"
|
|
||||||
db = db.Where(`(
|
|
||||||
marketings.so_number ILIKE ? OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM customers c
|
|
||||||
WHERE c.id = marketings.customer_id
|
|
||||||
AND c.name ILIKE ?
|
|
||||||
) OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM users su
|
|
||||||
WHERE su.id = marketings.sales_person_id
|
|
||||||
AND su.name ILIKE ?
|
|
||||||
) OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM marketing_products mp
|
|
||||||
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
|
||||||
JOIN products p ON p.id = pw.product_id
|
|
||||||
WHERE mp.marketing_id = marketings.id
|
|
||||||
AND p.name ILIKE ?
|
|
||||||
) OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM marketing_products mp
|
|
||||||
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
|
||||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
|
||||||
WHERE mp.marketing_id = marketings.id
|
|
||||||
AND w.name ILIKE ?
|
|
||||||
)
|
|
||||||
)`, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(params.ProductIDs) > 0 {
|
if len(params.ProductIDs) > 0 {
|
||||||
db = db.Where(`EXISTS (
|
db = db.Where(`EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -208,6 +269,9 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
db = db.Where("marketings.customer_id = ?", params.CustomerId)
|
db = db.Where("marketings.customer_id = ?", params.CustomerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db = s.applyMarketingProjectFlockFilter(c.Context(), db, params.ProjectFlockID, params.ProjectFlockKandangID)
|
||||||
|
db = s.applyMarketingSearchFilter(c.Context(), db, params.Search)
|
||||||
|
|
||||||
if scope.Restrict {
|
if scope.Restrict {
|
||||||
if len(scope.IDs) == 0 {
|
if len(scope.IDs) == 0 {
|
||||||
return db.Where("1 = 0")
|
return db.Where("1 = 0")
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ type DeliveryOrderUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeliveryOrderQuery struct {
|
type DeliveryOrderQuery 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,max=100,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=100"`
|
Search string `query:"search" validate:"omitempty,max=100"`
|
||||||
ProductIDs []uint `query:"product_ids" validate:"omitempty,dive,gt=0"`
|
ProductIDs []uint `query:"product_ids" validate:"omitempty,dive,gt=0"`
|
||||||
Status string `query:"status" validate:"omitempty,max=50"`
|
Status string `query:"status" validate:"omitempty,max=50"`
|
||||||
CustomerId uint `query:"customer_id" validate:"omitempty,gt=0"`
|
CustomerId uint `query:"customer_id" validate:"omitempty,gt=0"`
|
||||||
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
||||||
|
ProjectFlockID uint `query:"project_flock_id" validate:"omitempty,gt=0"`
|
||||||
|
ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeliveryOrderApprove struct {
|
type DeliveryOrderApprove struct {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func NewRecordingController(recordingService service.RecordingService) *Recordin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||||
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
projectFlockKandangID := c.QueryInt("project_flock_kandang_id", 0)
|
||||||
exportType := strings.TrimSpace(c.Query("export"))
|
exportType := strings.TrimSpace(c.Query("export"))
|
||||||
|
|
||||||
if exportprogress.IsProgressExportRequest(c) {
|
if exportprogress.IsProgressExportRequest(c) {
|
||||||
@@ -54,13 +54,19 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
|||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: page,
|
Page: page,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
Search: c.Query("search"),
|
Search: strings.TrimSpace(c.Query("search")),
|
||||||
|
ProjectFlockId: uint(c.QueryInt("project_flock_id", 0)),
|
||||||
|
AreaId: uint(c.QueryInt("area_id", 0)),
|
||||||
|
LocationId: uint(c.QueryInt("location_id", 0)),
|
||||||
|
KandangId: uint(c.QueryInt("kandang_id", 0)),
|
||||||
|
ProjectFlockCategory: strings.TrimSpace(c.Query("project_flock_category")),
|
||||||
|
ApprovalStatus: strings.TrimSpace(c.Query("approval_status")),
|
||||||
}
|
}
|
||||||
if projectFlockID > 0 {
|
if projectFlockKandangID > 0 {
|
||||||
query.ProjectFlockKandangId = uint(projectFlockID)
|
query.ProjectFlockKandangId = uint(projectFlockKandangID)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,10 +21,10 @@ type RecordingRepository interface {
|
|||||||
|
|
||||||
WithRelations(db *gorm.DB) *gorm.DB
|
WithRelations(db *gorm.DB) *gorm.DB
|
||||||
WithRelationsList(db *gorm.DB) *gorm.DB
|
WithRelationsList(db *gorm.DB) *gorm.DB
|
||||||
ApplyListFilters(db *gorm.DB, search string, projectFlockKandangId uint) *gorm.DB
|
ApplyListFilters(db *gorm.DB, params *validation.Query) *gorm.DB
|
||||||
ApplyListCountFilters(db *gorm.DB, search string, projectFlockKandangId uint) *gorm.DB
|
ApplyListCountFilters(db *gorm.DB, params *validation.Query) *gorm.DB
|
||||||
ApplySearchFilters(db *gorm.DB, rawSearch string) *gorm.DB
|
ApplySearchFilters(db *gorm.DB, rawSearch string) *gorm.DB
|
||||||
GetAllWithFilters(ctx context.Context, offset, limit int, search string, projectFlockKandangId uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Recording, int64, error)
|
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query, modifier func(*gorm.DB) *gorm.DB) ([]entity.Recording, int64, error)
|
||||||
GetLatestByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (*entity.Recording, error)
|
GetLatestByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (*entity.Recording, error)
|
||||||
ListByProjectFlockKandangID(ctx context.Context, tx *gorm.DB, projectFlockKandangId uint, from *time.Time) ([]entity.Recording, error)
|
ListByProjectFlockKandangID(ctx context.Context, tx *gorm.DB, projectFlockKandangId uint, from *time.Time) ([]entity.Recording, error)
|
||||||
GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error)
|
GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error)
|
||||||
@@ -147,36 +149,89 @@ func (r *RecordingRepositoryImpl) WithRelationsList(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard")
|
Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) ApplyListFilters(db *gorm.DB, search string, projectFlockKandangId uint) *gorm.DB {
|
func (r *RecordingRepositoryImpl) latestApprovalSubQuery(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Session(&gorm.Session{NewDB: true}).
|
||||||
|
Table("approvals").
|
||||||
|
Select("DISTINCT ON (approvable_id) approvable_id, action, step_name, notes").
|
||||||
|
Where("approvable_type = ?", utils.ApprovalWorkflowRecording.String()).
|
||||||
|
Order("approvable_id, action_at DESC, id DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) applyStructuredListFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
||||||
|
if params == nil {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.ProjectFlockKandangId != 0 {
|
||||||
|
db = db.Where("recordings.project_flock_kandangs_id = ?", params.ProjectFlockKandangId)
|
||||||
|
}
|
||||||
|
if params.ProjectFlockId != 0 {
|
||||||
|
db = db.Where("pfk.project_flock_id = ?", params.ProjectFlockId)
|
||||||
|
}
|
||||||
|
if params.KandangId != 0 {
|
||||||
|
db = db.Where("pfk.kandang_id = ?", params.KandangId)
|
||||||
|
}
|
||||||
|
if params.LocationId != 0 {
|
||||||
|
db = db.Where("pf.location_id = ?", params.LocationId)
|
||||||
|
}
|
||||||
|
if params.AreaId != 0 {
|
||||||
|
db = db.Where("pf.area_id = ?", params.AreaId)
|
||||||
|
}
|
||||||
|
if params.ProjectFlockCategory != "" {
|
||||||
|
db = db.Where("UPPER(COALESCE(pf.category, '')) = ?", strings.ToUpper(strings.TrimSpace(params.ProjectFlockCategory)))
|
||||||
|
}
|
||||||
|
if params.ApprovalStatus != "" {
|
||||||
|
db = db.Where(
|
||||||
|
`EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM (?) AS latest_approval
|
||||||
|
WHERE latest_approval.approvable_id = recordings.id
|
||||||
|
AND UPPER(COALESCE(CAST(latest_approval.action AS TEXT), '')) = ?
|
||||||
|
)`,
|
||||||
|
r.latestApprovalSubQuery(db),
|
||||||
|
strings.ToUpper(strings.TrimSpace(params.ApprovalStatus)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) ApplyListFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
||||||
|
search := ""
|
||||||
|
if params != nil {
|
||||||
|
search = params.Search
|
||||||
|
}
|
||||||
db = r.WithRelationsList(db)
|
db = r.WithRelationsList(db)
|
||||||
db = db.
|
db = db.
|
||||||
Joins("JOIN project_flock_kandangs pfk ON pfk.id = recordings.project_flock_kandangs_id").
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = recordings.project_flock_kandangs_id").
|
||||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id")
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
if projectFlockKandangId != 0 {
|
Joins("LEFT JOIN kandangs k ON k.id = pfk.kandang_id")
|
||||||
db = db.Where("recordings.project_flock_kandangs_id = ?", projectFlockKandangId)
|
db = r.applyStructuredListFilters(db, params)
|
||||||
}
|
|
||||||
db = r.ApplySearchFilters(db, search)
|
db = r.ApplySearchFilters(db, search)
|
||||||
return db.Order("recordings.record_datetime DESC").Order("recordings.created_at DESC")
|
return db.Order("recordings.record_datetime DESC").Order("recordings.created_at DESC")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) ApplyListCountFilters(db *gorm.DB, search string, projectFlockKandangId uint) *gorm.DB {
|
func (r *RecordingRepositoryImpl) ApplyListCountFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
||||||
|
search := ""
|
||||||
|
if params != nil {
|
||||||
|
search = params.Search
|
||||||
|
}
|
||||||
db = db.
|
db = db.
|
||||||
Joins("JOIN project_flock_kandangs pfk ON pfk.id = recordings.project_flock_kandangs_id").
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = recordings.project_flock_kandangs_id").
|
||||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id")
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
if projectFlockKandangId != 0 {
|
Joins("LEFT JOIN kandangs k ON k.id = pfk.kandang_id")
|
||||||
db = db.Where("recordings.project_flock_kandangs_id = ?", projectFlockKandangId)
|
db = r.applyStructuredListFilters(db, params)
|
||||||
}
|
|
||||||
db = r.ApplySearchFilters(db, search)
|
db = r.ApplySearchFilters(db, search)
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, search string, projectFlockKandangId uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Recording, int64, error) {
|
func (r *RecordingRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query, modifier func(*gorm.DB) *gorm.DB) ([]entity.Recording, int64, error) {
|
||||||
var (
|
var (
|
||||||
records []entity.Recording
|
records []entity.Recording
|
||||||
total int64
|
total int64
|
||||||
)
|
)
|
||||||
|
|
||||||
countQ := r.ApplyListCountFilters(r.DB().WithContext(ctx).Model(&entity.Recording{}), search, projectFlockKandangId)
|
countQ := r.ApplyListCountFilters(r.DB().WithContext(ctx).Model(&entity.Recording{}), params)
|
||||||
if modifier != nil {
|
if modifier != nil {
|
||||||
countQ = modifier(countQ)
|
countQ = modifier(countQ)
|
||||||
}
|
}
|
||||||
@@ -184,7 +239,7 @@ func (r *RecordingRepositoryImpl) GetAllWithFilters(ctx context.Context, offset,
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
listQ := r.ApplyListFilters(r.DB().WithContext(ctx).Model(&entity.Recording{}), search, projectFlockKandangId)
|
listQ := r.ApplyListFilters(r.DB().WithContext(ctx).Model(&entity.Recording{}), params)
|
||||||
if modifier != nil {
|
if modifier != nil {
|
||||||
listQ = modifier(listQ)
|
listQ = modifier(listQ)
|
||||||
}
|
}
|
||||||
@@ -218,6 +273,8 @@ func (r *RecordingRepositoryImpl) ApplySearchFilters(db *gorm.DB, rawSearch stri
|
|||||||
Joins("LEFT JOIN warehouses ws ON ws.id = pws.warehouse_id").
|
Joins("LEFT JOIN warehouses ws ON ws.id = pws.warehouse_id").
|
||||||
Joins("LEFT JOIN warehouses wd ON wd.id = pwd.warehouse_id").
|
Joins("LEFT JOIN warehouses wd ON wd.id = pwd.warehouse_id").
|
||||||
Joins("LEFT JOIN warehouses we ON we.id = pwe.warehouse_id").
|
Joins("LEFT JOIN warehouses we ON we.id = pwe.warehouse_id").
|
||||||
|
Joins("LEFT JOIN users cu ON cu.id = recordings.created_by").
|
||||||
|
Joins("LEFT JOIN (?) AS latest_approval ON latest_approval.approvable_id = recordings.id", r.latestApprovalSubQuery(db)).
|
||||||
Where(`
|
Where(`
|
||||||
LOWER(pf.flock_name) LIKE ?
|
LOWER(pf.flock_name) LIKE ?
|
||||||
OR LOWER(k.name) LIKE ?
|
OR LOWER(k.name) LIKE ?
|
||||||
@@ -225,8 +282,12 @@ func (r *RecordingRepositoryImpl) ApplySearchFilters(db *gorm.DB, rawSearch stri
|
|||||||
OR LOWER(l.address) LIKE ?
|
OR LOWER(l.address) LIKE ?
|
||||||
OR LOWER(ws.name) LIKE ?
|
OR LOWER(ws.name) LIKE ?
|
||||||
OR LOWER(wd.name) LIKE ?
|
OR LOWER(wd.name) LIKE ?
|
||||||
OR LOWER(we.name) LIKE ?`,
|
OR LOWER(we.name) LIKE ?
|
||||||
likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery,
|
OR LOWER(COALESCE(cu.name, '')) LIKE ?
|
||||||
|
OR LOWER(COALESCE(latest_approval.step_name, '')) LIKE ?
|
||||||
|
OR LOWER(COALESCE(CAST(latest_approval.action AS TEXT), '')) LIKE ?
|
||||||
|
OR LOWER(COALESCE(latest_approval.notes, '')) LIKE ?`,
|
||||||
|
likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery, likeQuery,
|
||||||
)
|
)
|
||||||
return db.Where("recordings.id IN (?)", subQuery)
|
return db.Where("recordings.id IN (?)", subQuery)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,8 +116,7 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
c.Context(),
|
c.Context(),
|
||||||
params.Offset,
|
params.Offset,
|
||||||
params.Limit,
|
params.Limit,
|
||||||
params.Search,
|
params,
|
||||||
params.ProjectFlockKandangId,
|
|
||||||
func(db *gorm.DB) *gorm.DB {
|
func(db *gorm.DB) *gorm.DB {
|
||||||
db, scopeErr = m.ApplyLocationScope(c, db, "pf.location_id")
|
db, scopeErr = m.ApplyLocationScope(c, db, "pf.location_id")
|
||||||
return db
|
return db
|
||||||
@@ -450,12 +449,12 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedStocks := recordingutil.MapStocks(createdRecording.Id, stockOwnerProjectFlockKandangID, req.Stocks)
|
mappedStocks := recordingutil.MapStocks(createdRecording.Id, stockOwnerProjectFlockKandangID, req.Stocks)
|
||||||
stockDesired := resetStockQuantitiesForFIFO(mappedStocks)
|
stockDesired := resetStockQuantitiesForFIFO(mappedStocks)
|
||||||
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
|
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
|
||||||
s.Log.Errorf("Failed to persist stocks: %+v", err)
|
s.Log.Errorf("Failed to persist stocks: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range mappedStocks {
|
for i := range mappedStocks {
|
||||||
if i >= len(stockDesired) {
|
if i >= len(stockDesired) {
|
||||||
@@ -519,14 +518,14 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
s.Log.Errorf("Failed to compute recording metrics: %+v", err)
|
s.Log.Errorf("Failed to compute recording metrics: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.recalculateFrom(ctx, tx, createdRecording.ProjectFlockKandangId, createdRecording.RecordDatetime); err != nil {
|
if err := s.recalculateFrom(ctx, tx, createdRecording.ProjectFlockKandangId, createdRecording.RecordDatetime); err != nil {
|
||||||
s.Log.Errorf("Failed to recalculate recordings after create: %+v", err)
|
s.Log.Errorf("Failed to recalculate recordings after create: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, createdRecording.ProjectFlockKandangId, createdRecording.RecordDatetime); err != nil {
|
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, createdRecording.ProjectFlockKandangId, createdRecording.RecordDatetime); err != nil {
|
||||||
s.Log.Errorf("Failed to sync farm depreciation manual input after create: %+v", err)
|
s.Log.Errorf("Failed to sync farm depreciation manual input after create: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
action := entity.ApprovalActionCreated
|
action := entity.ApprovalActionCreated
|
||||||
if err := s.createRecordingApproval(ctx, tx, createdRecording.Id, utils.RecordingStepPengajuan, action, createdRecording.CreatedBy, nil); err != nil {
|
if err := s.createRecordingApproval(ctx, tx, createdRecording.Id, utils.RecordingStepPengajuan, action, createdRecording.CreatedBy, nil); err != nil {
|
||||||
@@ -587,8 +586,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
recordingEntity = recording
|
recordingEntity = recording
|
||||||
pfkForRoute := recordingEntity.ProjectFlockKandang
|
pfkForRoute := recordingEntity.ProjectFlockKandang
|
||||||
if pfkForRoute == nil || pfkForRoute.Id == 0 {
|
if pfkForRoute == nil || pfkForRoute.Id == 0 {
|
||||||
fetchedPfk, fetchErr := s.ProjectFlockKandangRepo.GetByIDLight(ctx, recordingEntity.ProjectFlockKandangId)
|
fetchedPfk, fetchErr := s.ProjectFlockKandangRepo.GetByIDLight(ctx, recordingEntity.ProjectFlockKandangId)
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
@@ -599,43 +598,43 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return fetchErr
|
return fetchErr
|
||||||
}
|
}
|
||||||
pfkForRoute = fetchedPfk
|
pfkForRoute = fetchedPfk
|
||||||
}
|
}
|
||||||
if err := s.tryAutoExecuteTransferForRecordingCreate(c, pfkForRoute, recordingEntity.RecordDatetime); err != nil {
|
if err := s.tryAutoExecuteTransferForRecordingCreate(c, pfkForRoute, recordingEntity.RecordDatetime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
routePayload := buildRecordingRoutePayloadFromUpdate(req)
|
routePayload := buildRecordingRoutePayloadFromUpdate(req)
|
||||||
if err := s.enforceTransferRecordingRoute(ctx, pfkForRoute, recordingEntity.RecordDatetime, routePayload); err != nil {
|
if err := s.enforceTransferRecordingRoute(ctx, pfkForRoute, recordingEntity.RecordDatetime, routePayload); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasStockChanges := req.Stocks != nil
|
hasStockChanges := req.Stocks != nil
|
||||||
hasDepletionChanges := req.Depletions != nil
|
hasDepletionChanges := req.Depletions != nil
|
||||||
hasEggChanges := req.Eggs != nil
|
hasEggChanges := req.Eggs != nil
|
||||||
|
|
||||||
var existingStocks []entity.RecordingStock
|
var existingStocks []entity.RecordingStock
|
||||||
var existingDepletions []entity.RecordingDepletion
|
var existingDepletions []entity.RecordingDepletion
|
||||||
var existingEggs []entity.RecordingEgg
|
var existingEggs []entity.RecordingEgg
|
||||||
var mappedDepletions []entity.RecordingDepletion
|
var mappedDepletions []entity.RecordingDepletion
|
||||||
var stockOwnerProjectFlockKandangID *uint
|
var stockOwnerProjectFlockKandangID *uint
|
||||||
|
|
||||||
note := recordingutil.RecordingNote("Edit", recordingEntity.Id)
|
note := recordingutil.RecordingNote("Edit", recordingEntity.Id)
|
||||||
|
|
||||||
if hasStockChanges {
|
if hasStockChanges {
|
||||||
stockOwnerProjectFlockKandangID, err = s.resolveRecordingStockOwnerProjectFlockKandangID(ctx, pfkForRoute, recordingEntity.RecordDatetime)
|
stockOwnerProjectFlockKandangID, err = s.resolveRecordingStockOwnerProjectFlockKandangID(ctx, pfkForRoute, recordingEntity.RecordDatetime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
existingStocks, err = s.Repository.ListStocks(tx, recordingEntity.Id)
|
existingStocks, err = s.Repository.ListStocks(tx, recordingEntity.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to list existing stocks: %+v", err)
|
s.Log.Errorf("Failed to list existing stocks: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
existingUsage := recordingutil.StockUsageByWarehouse(existingStocks)
|
existingUsage := recordingutil.StockUsageByWarehouse(existingStocks)
|
||||||
incomingUsage := recordingutil.StockUsageByWarehouseReq(req.Stocks)
|
incomingUsage := recordingutil.StockUsageByWarehouseReq(req.Stocks)
|
||||||
match := recordingutil.FloatMapsEqual(existingUsage, incomingUsage) && recordingStocksAllOwnedBy(existingStocks, stockOwnerProjectFlockKandangID)
|
match := recordingutil.FloatMapsEqual(existingUsage, incomingUsage) && recordingStocksAllOwnedBy(existingStocks, stockOwnerProjectFlockKandangID)
|
||||||
if match {
|
if match {
|
||||||
hasStockChanges = false
|
hasStockChanges = false
|
||||||
} else {
|
} else {
|
||||||
if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
|
if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -643,11 +642,11 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
|
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.reflowSyncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, stockOwnerProjectFlockKandangID, note, actorID); err != nil {
|
if err := s.reflowSyncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, stockOwnerProjectFlockKandangID, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if hasDepletionChanges {
|
if hasDepletionChanges {
|
||||||
existingDepletions, err = s.Repository.ListDepletions(tx, recordingEntity.Id)
|
existingDepletions, err = s.Repository.ListDepletions(tx, recordingEntity.Id)
|
||||||
@@ -809,22 +808,22 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasStockChanges || hasDepletionChanges || hasEggChanges {
|
if hasStockChanges || hasDepletionChanges || hasEggChanges {
|
||||||
if err := s.computeAndUpdateMetrics(ctx, tx, recordingEntity); err != nil {
|
if err := s.computeAndUpdateMetrics(ctx, tx, recordingEntity); err != nil {
|
||||||
s.Log.Errorf("Failed to recompute recording metrics: %+v", err)
|
s.Log.Errorf("Failed to recompute recording metrics: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.recalculateFrom(ctx, tx, recordingEntity.ProjectFlockKandangId, recordingEntity.RecordDatetime); err != nil {
|
if err := s.recalculateFrom(ctx, tx, recordingEntity.ProjectFlockKandangId, recordingEntity.RecordDatetime); err != nil {
|
||||||
s.Log.Errorf("Failed to recalculate recordings after update: %+v", err)
|
s.Log.Errorf("Failed to recalculate recordings after update: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if hasStockChanges {
|
}
|
||||||
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, recordingEntity.ProjectFlockKandangId, recordingEntity.RecordDatetime); err != nil {
|
if hasStockChanges {
|
||||||
s.Log.Errorf("Failed to sync farm depreciation manual input after update: %+v", err)
|
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, recordingEntity.ProjectFlockKandangId, recordingEntity.RecordDatetime); err != nil {
|
||||||
return err
|
s.Log.Errorf("Failed to sync farm depreciation manual input after update: %+v", err)
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
action := entity.ApprovalActionUpdated
|
action := entity.ApprovalActionUpdated
|
||||||
actorID := recordingEntity.CreatedBy
|
actorID := recordingEntity.CreatedBy
|
||||||
@@ -1082,15 +1081,15 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.recalculateFrom(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime); err != nil {
|
if err := s.recalculateFrom(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime); err != nil {
|
||||||
s.Log.Errorf("Failed to recalculate recordings after delete: %+v", err)
|
s.Log.Errorf("Failed to recalculate recordings after delete: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime); err != nil {
|
if err := s.syncFarmDepreciationManualInputFromRecordingStocks(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime); err != nil {
|
||||||
s.Log.Errorf("Failed to sync farm depreciation manual input after delete: %+v", err)
|
s.Log.Errorf("Failed to sync farm depreciation manual input after delete: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.invalidateDepreciationSnapshots(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime)
|
s.invalidateDepreciationSnapshots(ctx, tx, recording.ProjectFlockKandangId, recording.RecordDatetime)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -2905,32 +2904,32 @@ func (s *recordingService) reflowSyncRecordingStocks(
|
|||||||
if len(list) > 0 {
|
if len(list) > 0 {
|
||||||
stock = list[0]
|
stock = list[0]
|
||||||
existingByWarehouse[item.ProductWarehouseId] = list[1:]
|
existingByWarehouse[item.ProductWarehouseId] = list[1:]
|
||||||
} else {
|
} else {
|
||||||
zero := 0.0
|
zero := 0.0
|
||||||
stock = entity.RecordingStock{
|
stock = entity.RecordingStock{
|
||||||
RecordingId: recordingID,
|
RecordingId: recordingID,
|
||||||
ProductWarehouseId: item.ProductWarehouseId,
|
ProductWarehouseId: item.ProductWarehouseId,
|
||||||
ProjectFlockKandangId: ownerProjectFlockKandangID,
|
ProjectFlockKandangId: ownerProjectFlockKandangID,
|
||||||
UsageQty: &zero,
|
UsageQty: &zero,
|
||||||
PendingQty: &zero,
|
PendingQty: &zero,
|
||||||
}
|
|
||||||
if err := s.Repository.CreateStock(tx, &stock); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
stock.ProjectFlockKandangId = ownerProjectFlockKandangID
|
if err := s.Repository.CreateStock(tx, &stock); err != nil {
|
||||||
if stock.Id != 0 {
|
return err
|
||||||
if err := tx.Model(&entity.RecordingStock{}).
|
|
||||||
Where("id = ?", stock.Id).
|
|
||||||
Updates(map[string]any{
|
|
||||||
"project_flock_kandang_id": ownerProjectFlockKandangID,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
stock.ProjectFlockKandangId = ownerProjectFlockKandangID
|
||||||
|
if stock.Id != 0 {
|
||||||
|
if err := tx.Model(&entity.RecordingStock{}).
|
||||||
|
Where("id = ?", stock.Id).
|
||||||
|
Updates(map[string]any{
|
||||||
|
"project_flock_kandang_id": ownerProjectFlockKandangID,
|
||||||
|
}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
desired := item.Qty
|
desired := item.Qty
|
||||||
stock.UsageQty = &desired
|
stock.UsageQty = &desired
|
||||||
zero := 0.0
|
zero := 0.0
|
||||||
stock.PendingQty = &zero
|
stock.PendingQty = &zero
|
||||||
stocksToApply = append(stocksToApply, stock)
|
stocksToApply = append(stocksToApply, stock)
|
||||||
|
|||||||
@@ -39,8 +39,14 @@ type Query struct {
|
|||||||
Page int `query:"page" validate:"omitempty,number,min=1"`
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1"`
|
||||||
Offset int `query:"-" validate:"omitempty,number,min=0"`
|
Offset int `query:"-" validate:"omitempty,number,min=0"`
|
||||||
|
ProjectFlockId uint `query:"project_flock_id" validate:"omitempty,number,min=1"`
|
||||||
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
AreaId uint `query:"area_id" validate:"omitempty,number,min=1"`
|
||||||
|
LocationId uint `query:"location_id" validate:"omitempty,number,min=1"`
|
||||||
|
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
|
||||||
|
ProjectFlockCategory string `query:"project_flock_category" validate:"omitempty,oneof=GROWING LAYING"`
|
||||||
|
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=50"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=100"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Approve struct {
|
type Approve struct {
|
||||||
|
|||||||
@@ -87,19 +87,21 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func buildPurchaseQuery(c *fiber.Ctx) *validation.Query {
|
func buildPurchaseQuery(c *fiber.Ctx) *validation.Query {
|
||||||
return &validation.Query{
|
return &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
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")),
|
PoDate: strings.TrimSpace(c.Query("po_date")),
|
||||||
PoDateFrom: strings.TrimSpace(c.Query("po_date_from")),
|
PoDateFrom: strings.TrimSpace(c.Query("po_date_from")),
|
||||||
PoDateTo: strings.TrimSpace(c.Query("po_date_to")),
|
PoDateTo: strings.TrimSpace(c.Query("po_date_to")),
|
||||||
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
|
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
|
||||||
CreatedTo: strings.TrimSpace(c.Query("created_to")),
|
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)),
|
||||||
ProductCategoryID: strings.TrimSpace(c.Query("product_category_id")),
|
ProjectFlockID: uint(c.QueryInt("project_flock_id", 0)),
|
||||||
|
ProjectFlockKandangID: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||||
|
ProductCategoryID: strings.TrimSpace(c.Query("product_category_id")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
if len(productCategoryIDs) > 0 {
|
if len(productCategoryIDs) > 0 {
|
||||||
db = db.Where(
|
db = db.Where(
|
||||||
`EXISTS (
|
`EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM purchase_items pi
|
FROM purchase_items pi
|
||||||
JOIN products p ON p.id = pi.product_id
|
JOIN products p ON p.id = pi.product_id
|
||||||
WHERE pi.purchase_id = purchases.id AND p.product_category_id IN ?
|
WHERE pi.purchase_id = purchases.id AND p.product_category_id IN ?
|
||||||
@@ -256,183 +256,9 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(approvalStatuses) > 0 {
|
db = applyPurchaseProjectFlockFilter(db, params.ProjectFlockID, params.ProjectFlockKandangID)
|
||||||
approvalConditions := make([]string, 0, len(approvalStatuses))
|
db = applyPurchaseApprovalStatusFilter(db, approvalStatuses)
|
||||||
approvalArgs := make([]any, 0, 2+(len(approvalStatuses)*3))
|
db = applyPurchaseSearchFilter(db, search)
|
||||||
approvalArgs = append(approvalArgs, utils.ApprovalWorkflowPurchase.String(), utils.ApprovalWorkflowPurchase.String())
|
|
||||||
for _, status := range approvalStatuses {
|
|
||||||
if status == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
like := "%" + status + "%"
|
|
||||||
approvalConditions = append(approvalConditions, `(LOWER(COALESCE(a.step_name, '')) LIKE ? OR LOWER(COALESCE(CAST(a.action AS TEXT), '')) LIKE ? OR CAST(a.step_number AS TEXT) = ?)`)
|
|
||||||
approvalArgs = append(approvalArgs, like, like, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(approvalConditions) > 0 {
|
|
||||||
approvalClause := strings.Join(approvalConditions, " OR ")
|
|
||||||
approvalQuery := fmt.Sprintf(
|
|
||||||
`EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM approvals a
|
|
||||||
WHERE a.approvable_type = ?
|
|
||||||
AND a.approvable_id = purchases.id
|
|
||||||
AND a.id = (
|
|
||||||
SELECT a2.id
|
|
||||||
FROM approvals a2
|
|
||||||
WHERE a2.approvable_type = ?
|
|
||||||
AND a2.approvable_id = purchases.id
|
|
||||||
ORDER BY a2.action_at DESC, a2.id DESC
|
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
AND (%s)
|
|
||||||
)`,
|
|
||||||
approvalClause,
|
|
||||||
)
|
|
||||||
db = db.Where(approvalQuery, approvalArgs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if search != "" {
|
|
||||||
like := "%" + search + "%"
|
|
||||||
db = db.Where(
|
|
||||||
`(
|
|
||||||
LOWER(COALESCE(purchases.pr_number, '')) LIKE ?
|
|
||||||
OR LOWER(COALESCE(purchases.po_number, '')) LIKE ?
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM suppliers s
|
|
||||||
WHERE s.id = purchases.supplier_id
|
|
||||||
AND LOWER(COALESCE(s.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM users u
|
|
||||||
WHERE u.id = purchases.created_by
|
|
||||||
AND LOWER(COALESCE(u.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN products p ON p.id = pi.product_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(p.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN warehouses w ON w.id = pi.warehouse_id
|
|
||||||
JOIN locations l ON l.id = w.location_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(l.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id
|
|
||||||
JOIN expenses e ON e.id = en.expense_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(e.reference_number, '')) LIKE ?
|
|
||||||
)
|
|
||||||
)`,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(approvalStatuses) > 0 {
|
|
||||||
approvalConditions := make([]string, 0, len(approvalStatuses))
|
|
||||||
approvalArgs := make([]any, 0, 2+(len(approvalStatuses)*3))
|
|
||||||
approvalArgs = append(approvalArgs, utils.ApprovalWorkflowPurchase.String(), utils.ApprovalWorkflowPurchase.String())
|
|
||||||
for _, status := range approvalStatuses {
|
|
||||||
if status == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
like := "%" + status + "%"
|
|
||||||
approvalConditions = append(approvalConditions, `(LOWER(COALESCE(a.step_name, '')) LIKE ? OR LOWER(COALESCE(CAST(a.action AS TEXT), '')) LIKE ? OR CAST(a.step_number AS TEXT) = ?)`)
|
|
||||||
approvalArgs = append(approvalArgs, like, like, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(approvalConditions) > 0 {
|
|
||||||
approvalClause := strings.Join(approvalConditions, " OR ")
|
|
||||||
approvalQuery := fmt.Sprintf(
|
|
||||||
`EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM approvals a
|
|
||||||
WHERE a.approvable_type = ?
|
|
||||||
AND a.approvable_id = purchases.id
|
|
||||||
AND a.id = (
|
|
||||||
SELECT a2.id
|
|
||||||
FROM approvals a2
|
|
||||||
WHERE a2.approvable_type = ?
|
|
||||||
AND a2.approvable_id = purchases.id
|
|
||||||
ORDER BY a2.action_at DESC, a2.id DESC
|
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
AND (%s)
|
|
||||||
)`,
|
|
||||||
approvalClause,
|
|
||||||
)
|
|
||||||
db = db.Where(approvalQuery, approvalArgs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if search != "" {
|
|
||||||
like := "%" + search + "%"
|
|
||||||
db = db.Where(
|
|
||||||
`(
|
|
||||||
LOWER(COALESCE(purchases.pr_number, '')) LIKE ?
|
|
||||||
OR LOWER(COALESCE(purchases.po_number, '')) LIKE ?
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM suppliers s
|
|
||||||
WHERE s.id = purchases.supplier_id
|
|
||||||
AND LOWER(COALESCE(s.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM users u
|
|
||||||
WHERE u.id = purchases.created_by
|
|
||||||
AND LOWER(COALESCE(u.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN products p ON p.id = pi.product_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(p.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN warehouses w ON w.id = pi.warehouse_id
|
|
||||||
JOIN locations l ON l.id = w.location_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(l.name, '')) LIKE ?
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM purchase_items pi
|
|
||||||
JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id
|
|
||||||
JOIN expenses e ON e.id = en.expense_id
|
|
||||||
WHERE pi.purchase_id = purchases.id
|
|
||||||
AND LOWER(COALESCE(e.reference_number, '')) LIKE ?
|
|
||||||
)
|
|
||||||
)`,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
like,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Order("created_at DESC").Order("purchases.id DESC")
|
return db.Order("created_at DESC").Order("purchases.id DESC")
|
||||||
})
|
})
|
||||||
@@ -2361,6 +2187,155 @@ func parsePoDateRangeForQuery(fromStr, toStr string) (*time.Time, *time.Time, er
|
|||||||
return fromPtr, toPtr, nil
|
return fromPtr, toPtr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyPurchaseProjectFlockFilter(db *gorm.DB, projectFlockID, projectFlockKandangID uint) *gorm.DB {
|
||||||
|
if projectFlockID > 0 {
|
||||||
|
db = db.Where(
|
||||||
|
`EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
LEFT JOIN project_flock_kandangs pfk_explicit ON pfk_explicit.id = pi.project_flock_kandang_id
|
||||||
|
LEFT JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
LEFT JOIN project_flock_kandangs pfk_active ON pfk_active.kandang_id = w.kandang_id AND pfk_active.closed_at IS NULL
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND COALESCE(pfk_explicit.project_flock_id, pfk_active.project_flock_id) = ?
|
||||||
|
)`,
|
||||||
|
projectFlockID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectFlockKandangID > 0 {
|
||||||
|
db = db.Where(
|
||||||
|
`EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
LEFT JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
LEFT JOIN project_flock_kandangs pfk_active ON pfk_active.kandang_id = w.kandang_id AND pfk_active.closed_at IS NULL
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND COALESCE(pi.project_flock_kandang_id, pfk_active.id) = ?
|
||||||
|
)`,
|
||||||
|
projectFlockKandangID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPurchaseApprovalStatusFilter(db *gorm.DB, approvalStatuses []string) *gorm.DB {
|
||||||
|
if len(approvalStatuses) == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalConditions := make([]string, 0, len(approvalStatuses))
|
||||||
|
approvalArgs := make([]any, 0, 2+(len(approvalStatuses)*3))
|
||||||
|
approvalArgs = append(approvalArgs, utils.ApprovalWorkflowPurchase.String(), utils.ApprovalWorkflowPurchase.String())
|
||||||
|
for _, status := range approvalStatuses {
|
||||||
|
if status == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
like := "%" + status + "%"
|
||||||
|
approvalConditions = append(approvalConditions, `(LOWER(COALESCE(a.step_name, '')) LIKE ? OR LOWER(COALESCE(CAST(a.action AS TEXT), '')) LIKE ? OR CAST(a.step_number AS TEXT) = ?)`)
|
||||||
|
approvalArgs = append(approvalArgs, like, like, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(approvalConditions) == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalClause := strings.Join(approvalConditions, " OR ")
|
||||||
|
approvalQuery := fmt.Sprintf(
|
||||||
|
`EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM approvals a
|
||||||
|
WHERE a.approvable_type = ?
|
||||||
|
AND a.approvable_id = purchases.id
|
||||||
|
AND a.id = (
|
||||||
|
SELECT a2.id
|
||||||
|
FROM approvals a2
|
||||||
|
WHERE a2.approvable_type = ?
|
||||||
|
AND a2.approvable_id = purchases.id
|
||||||
|
ORDER BY a2.action_at DESC, a2.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
AND (%s)
|
||||||
|
)`,
|
||||||
|
approvalClause,
|
||||||
|
)
|
||||||
|
|
||||||
|
return db.Where(approvalQuery, approvalArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPurchaseSearchFilter(db *gorm.DB, search string) *gorm.DB {
|
||||||
|
if search == "" {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
like := "%" + search + "%"
|
||||||
|
return db.Where(
|
||||||
|
`(
|
||||||
|
LOWER(COALESCE(purchases.pr_number, '')) LIKE ?
|
||||||
|
OR LOWER(COALESCE(purchases.po_number, '')) LIKE ?
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM suppliers s
|
||||||
|
WHERE s.id = purchases.supplier_id
|
||||||
|
AND LOWER(COALESCE(s.name, '')) LIKE ?
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users u
|
||||||
|
WHERE u.id = purchases.created_by
|
||||||
|
AND LOWER(COALESCE(u.name, '')) LIKE ?
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
JOIN products p ON p.id = pi.product_id
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND LOWER(COALESCE(p.name, '')) LIKE ?
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
JOIN locations l ON l.id = w.location_id
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND LOWER(COALESCE(l.name, '')) LIKE ?
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
LEFT JOIN project_flock_kandangs pfk_explicit ON pfk_explicit.id = pi.project_flock_kandang_id
|
||||||
|
LEFT JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
LEFT JOIN project_flock_kandangs pfk_active ON pfk_active.kandang_id = w.kandang_id AND pfk_active.closed_at IS NULL
|
||||||
|
LEFT JOIN project_flocks pf ON pf.id = COALESCE(pfk_explicit.project_flock_id, pfk_active.project_flock_id)
|
||||||
|
LEFT JOIN kandangs k ON k.id = COALESCE(pfk_explicit.kandang_id, pfk_active.kandang_id, w.kandang_id)
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND (
|
||||||
|
LOWER(COALESCE(pf.flock_name, '')) LIKE ? OR
|
||||||
|
LOWER(COALESCE(k.name, '')) LIKE ?
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM purchase_items pi
|
||||||
|
JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id
|
||||||
|
JOIN expenses e ON e.id = en.expense_id
|
||||||
|
WHERE pi.purchase_id = purchases.id
|
||||||
|
AND LOWER(COALESCE(e.reference_number, '')) LIKE ?
|
||||||
|
)
|
||||||
|
)`,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
like,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeApprovalStatusFilter(raw string) string {
|
func normalizeApprovalStatusFilter(raw string) string {
|
||||||
value := strings.ToLower(strings.TrimSpace(raw))
|
value := strings.ToLower(strings.TrimSpace(raw))
|
||||||
switch value {
|
switch value {
|
||||||
|
|||||||
@@ -61,17 +61,19 @@ type DeletePurchaseItemsRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1"`
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
|
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
|
||||||
AreaID uint `query:"area_id" validate:"omitempty,gt=0"`
|
AreaID uint `query:"area_id" validate:"omitempty,gt=0"`
|
||||||
LocationID uint `query:"location_id" validate:"omitempty,gt=0"`
|
LocationID uint `query:"location_id" validate:"omitempty,gt=0"`
|
||||||
ProductCategoryID string `query:"product_category_id" validate:"omitempty,max=500"`
|
ProjectFlockID uint `query:"project_flock_id" validate:"omitempty,gt=0"`
|
||||||
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=500"`
|
ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
|
||||||
PoDate string `query:"po_date" validate:"omitempty,datetime=2006-01-02"`
|
ProductCategoryID string `query:"product_category_id" validate:"omitempty,max=500"`
|
||||||
PoDateFrom string `query:"po_date_from" validate:"omitempty,datetime=2006-01-02"`
|
ApprovalStatus string `query:"approval_status" validate:"omitempty,max=500"`
|
||||||
PoDateTo string `query:"po_date_to" validate:"omitempty,datetime=2006-01-02"`
|
PoDate string `query:"po_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
Search string `query:"search" validate:"omitempty,max=100"`
|
PoDateFrom string `query:"po_date_from" validate:"omitempty,datetime=2006-01-02"`
|
||||||
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
|
PoDateTo string `query:"po_date_to" validate:"omitempty,datetime=2006-01-02"`
|
||||||
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user