feature/exclusive-summary #15
@ -40,12 +40,12 @@ type UpdatePurchaseOrderRequest struct {
|
||||
}
|
||||
|
||||
type UpdatePurchaseOrderItemRequest struct {
|
||||
ID *uuid.UUID `json:"id,omitempty"` // For existing items
|
||||
IngredientID *uuid.UUID `json:"ingredient_id,omitempty" validate:"omitempty"`
|
||||
PurchaseCategoryID *uuid.UUID `json:"purchase_category_id,omitempty" validate:"omitempty"`
|
||||
ID *uuid.UUID `json:"id,omitempty"` // Ignored. Supplying items replaces all existing PO items.
|
||||
IngredientID *uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||
PurchaseCategoryID *uuid.UUID `json:"purchase_category_id" validate:"required"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty"`
|
||||
Quantity *float64 `json:"quantity,omitempty" validate:"omitempty,gt=0"`
|
||||
UnitID *uuid.UUID `json:"unit_id,omitempty" validate:"omitempty"`
|
||||
Quantity *float64 `json:"quantity" validate:"required,gt=0"`
|
||||
UnitID *uuid.UUID `json:"unit_id" validate:"required"`
|
||||
Amount *float64 `json:"amount,omitempty" validate:"omitempty,gte=0"`
|
||||
}
|
||||
|
||||
|
||||
@ -117,7 +117,7 @@ type UpdatePurchaseOrderRequest struct {
|
||||
}
|
||||
|
||||
type UpdatePurchaseOrderItemRequest struct {
|
||||
ID *uuid.UUID `json:"id,omitempty"` // For existing items
|
||||
ID *uuid.UUID `json:"id,omitempty"` // Ignored. Supplying items replaces all existing PO items.
|
||||
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
|
||||
PurchaseCategoryID *uuid.UUID `json:"purchase_category_id,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
||||
@ -842,10 +842,10 @@ func exclusiveSummarySalaryBreakdown(transactions []entities.ExclusiveSummaryDai
|
||||
|
||||
classification := strings.ToLower(transaction.CategoryCode + " " + transaction.CategoryName + " " + transaction.Description)
|
||||
switch {
|
||||
case strings.Contains(classification, "staff") || strings.Contains(classification, "kary") || strings.Contains(classification, "karyawan"):
|
||||
salaryStaff += transaction.Amount
|
||||
case strings.Contains(classification, "dw"):
|
||||
salaryDW += transaction.Amount
|
||||
case strings.Contains(classification, "staff") || strings.Contains(classification, "kary") || strings.Contains(classification, "karyawan"):
|
||||
salaryStaff += transaction.Amount
|
||||
default:
|
||||
salaryOther += transaction.Amount
|
||||
}
|
||||
|
||||
@ -293,7 +293,7 @@ func TestAnalyticsProcessorGetExclusiveSummaryPeriodCalculatesSummaryAndReimburs
|
||||
},
|
||||
DailyTransactions: []entities.ExclusiveSummaryDailyTransaction{
|
||||
{Date: now, CategoryCode: "biaya_gaji", CategoryName: "Gaji", Description: "gaji kary", Amount: 48203333, Source: "expense"},
|
||||
{Date: now, CategoryCode: "biaya_gaji", CategoryName: "Gaji", Description: "DW", Amount: 3555000, Source: "expense"},
|
||||
{Date: now, CategoryCode: "biaya_gaji_dw", CategoryName: "Gaji DW", Description: "gaji karyawan", Amount: 3555000, Source: "expense"},
|
||||
},
|
||||
},
|
||||
}, expenseRepositoryStub{})
|
||||
|
||||
@ -173,7 +173,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Joins("JOIN units u ON poi.unit_id = u.id").
|
||||
Where("po.organization_id = ?", organizationID).
|
||||
Where("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.status = ?", "received").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo)
|
||||
summaryQuery = r.applyPurchaseOrderItemOutletFilter(summaryQuery, outletID)
|
||||
|
||||
@ -214,7 +214,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Joins("JOIN units u ON poi.unit_id = u.id").
|
||||
Where("po.organization_id = ?", organizationID).
|
||||
Where("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.status = ?", "received").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
|
||||
Group(dateFormat).
|
||||
Order(dateFormat)
|
||||
@ -245,7 +245,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Joins("LEFT JOIN units u ON poi.unit_id = u.id").
|
||||
Where("po.organization_id = ?", organizationID).
|
||||
Where("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.status = ?", "received").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
|
||||
Group("i.id, i.name").
|
||||
Order("total_cost DESC")
|
||||
@ -273,7 +273,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Joins("JOIN units u ON poi.unit_id = u.id").
|
||||
Where("po.organization_id = ?", organizationID).
|
||||
Where("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.status = ?", "received").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
|
||||
Group("v.id, v.name").
|
||||
Order("total_cost DESC")
|
||||
@ -296,7 +296,15 @@ func (r *AnalyticsRepositoryImpl) applyPurchaseOrderItemOutletFilter(query *gorm
|
||||
if outletID == nil {
|
||||
return query
|
||||
}
|
||||
return query.Where("(i.outlet_id = ? OR u.outlet_id = ?)", *outletID, *outletID)
|
||||
return query.Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM inventory_movements im
|
||||
WHERE im.purchase_order_item_id = poi.id
|
||||
AND im.movement_type = ?
|
||||
AND im.outlet_id = ?
|
||||
)
|
||||
`, entities.InventoryMovementTypePurchase, *outletID)
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error) {
|
||||
@ -307,6 +315,7 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ
|
||||
Select(`
|
||||
p.id as product_id,
|
||||
p.name as product_name,
|
||||
p.price as product_price,
|
||||
c.id as category_id,
|
||||
c.name as category_name,
|
||||
c.order as category_order,
|
||||
@ -365,7 +374,7 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ
|
||||
query = r.resolveOutletID(query, outletID, "o.outlet_id")
|
||||
|
||||
err := query.
|
||||
Group("p.id, p.name, p.cost, c.id, c.name, c.order, mahpp.hpp_per_unit").
|
||||
Group("p.id, p.name, p.price, p.cost, c.id, c.name, c.order, mahpp.hpp_per_unit").
|
||||
Order("revenue DESC").
|
||||
Limit(limit).
|
||||
Scan(&results).Error
|
||||
@ -854,8 +863,14 @@ func (r *AnalyticsRepositoryImpl) exclusiveSummaryTransactionUnionQuery(organiza
|
||||
}
|
||||
|
||||
if outletID != nil {
|
||||
poOutletFilter = "AND (i.outlet_id = ? OR u.outlet_id = ?)"
|
||||
args = append(args, *outletID, *outletID)
|
||||
poOutletFilter = `AND EXISTS (
|
||||
SELECT 1
|
||||
FROM inventory_movements im
|
||||
WHERE im.purchase_order_item_id = poi.id
|
||||
AND im.movement_type = 'purchase'
|
||||
AND im.outlet_id = ?
|
||||
)`
|
||||
args = append(args, *outletID)
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
|
||||
@ -73,3 +73,31 @@ func TestPurchaseOrderValidatorCreateRejectsDueDateBeforeTransactionDate(t *test
|
||||
require.Equal(t, constants.MalformedFieldErrorCode, code)
|
||||
require.Contains(t, err.Error(), "due_date must be after transaction_date")
|
||||
}
|
||||
|
||||
func TestPurchaseOrderValidatorUpdateItemsRequireFullReplacementFields(t *testing.T) {
|
||||
validator := NewPurchaseOrderValidator()
|
||||
req := &contract.UpdatePurchaseOrderRequest{
|
||||
Items: []contract.UpdatePurchaseOrderItemRequest{
|
||||
{
|
||||
PurchaseCategoryID: ptrUUID(uuid.New()),
|
||||
Quantity: ptrFloat64(1),
|
||||
UnitID: ptrUUID(uuid.New()),
|
||||
Amount: ptrFloat64(1000),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err, code := validator.ValidateUpdatePurchaseOrderRequest(req)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Equal(t, constants.MissingFieldErrorCode, code)
|
||||
require.Contains(t, err.Error(), "ingredient_id is required")
|
||||
}
|
||||
|
||||
func ptrUUID(id uuid.UUID) *uuid.UUID {
|
||||
return &id
|
||||
}
|
||||
|
||||
func ptrFloat64(value float64) *float64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ DROP TRIGGER IF EXISTS trigger_validate_purchase_order_item_raw_material ON purc
|
||||
DROP FUNCTION IF EXISTS validate_purchase_order_item_raw_material();
|
||||
|
||||
ALTER TABLE purchase_order_items
|
||||
ALTER COLUMN purchase_category_id DROP NOT NULL,
|
||||
ALTER COLUMN ingredient_id DROP NOT NULL,
|
||||
ALTER COLUMN quantity DROP NOT NULL,
|
||||
ALTER COLUMN unit_id DROP NOT NULL;
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
UPDATE purchase_order_items poi
|
||||
SET purchase_category_id = pc.id
|
||||
FROM purchase_orders po
|
||||
JOIN purchase_categories pc ON pc.organization_id = po.organization_id
|
||||
AND pc.code = 'bahan_baku'
|
||||
AND pc.type = 'raw_material'
|
||||
WHERE poi.purchase_order_id = po.id
|
||||
AND poi.purchase_category_id IS NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_order_items poi
|
||||
JOIN purchase_categories pc ON pc.id = poi.purchase_category_id
|
||||
WHERE pc.type <> 'raw_material'
|
||||
LEFT JOIN purchase_categories pc ON pc.id = poi.purchase_category_id
|
||||
WHERE poi.purchase_category_id IS NULL
|
||||
OR pc.id IS NULL
|
||||
OR pc.type <> 'raw_material'
|
||||
OR poi.ingredient_id IS NULL
|
||||
OR poi.quantity IS NULL
|
||||
OR poi.unit_id IS NULL
|
||||
@ -14,6 +25,7 @@ BEGIN
|
||||
END $$;
|
||||
|
||||
ALTER TABLE purchase_order_items
|
||||
ALTER COLUMN purchase_category_id SET NOT NULL,
|
||||
ALTER COLUMN ingredient_id SET NOT NULL,
|
||||
ALTER COLUMN quantity SET NOT NULL,
|
||||
ALTER COLUMN unit_id SET NOT NULL;
|
||||
|
||||
@ -1,3 +1,20 @@
|
||||
UPDATE expense_items ei
|
||||
SET purchase_category_id = pc.id
|
||||
FROM expenses e
|
||||
JOIN purchase_categories pc ON pc.organization_id = e.organization_id
|
||||
AND pc.code = 'biaya_lain_lain'
|
||||
AND pc.type = 'expense'
|
||||
WHERE ei.expense_id = e.id
|
||||
AND (
|
||||
ei.purchase_category_id IS NULL
|
||||
OR NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_categories current_pc
|
||||
WHERE current_pc.id = ei.purchase_category_id
|
||||
AND current_pc.type = 'expense'
|
||||
)
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user