mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development' into 'staging'
Development See merge request mbugroup/lti-api!310
This commit is contained in:
@@ -743,8 +743,6 @@ func (s *fifoService) releaseUsagePortion(
|
||||
if expectedWarehouseID == 0 || alloc.ProductWarehouseId == expectedWarehouseID {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("WARN[FIFO] ALLOC WAREHOUSE MISMATCH usable_key=%s usable_id=%d alloc_id=%d expected_pw=%d actual_pw=%d\n",
|
||||
usableKey.String(), usableID, alloc.Id, expectedWarehouseID, alloc.ProductWarehouseId)
|
||||
if err := tx.Model(&entities.StockAllocation{}).
|
||||
Where("id = ?", alloc.Id).
|
||||
Update("product_warehouse_id", expectedWarehouseID).Error; err != nil {
|
||||
@@ -848,41 +846,80 @@ func (s *fifoService) fetchPendingCandidates(ctx context.Context, tx *gorm.DB, p
|
||||
cfg.Columns.CreatedAt,
|
||||
)
|
||||
|
||||
var rows []struct {
|
||||
ID uint
|
||||
Pending float64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
query := tx.Table(cfg.Table).
|
||||
Select(selectStmt).
|
||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ProductWarehouseID), productWarehouseID).
|
||||
Where(fmt.Sprintf("%s > 0", cfg.Columns.PendingQuantity)).
|
||||
Limit(s.pendingBatchPerUsable)
|
||||
|
||||
if cfg.Scope != nil {
|
||||
query = cfg.Scope(query)
|
||||
}
|
||||
|
||||
for _, order := range s.orderClauses(cfg.OrderBy) {
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
if row.Pending <= 0 {
|
||||
continue
|
||||
if cfg.Columns.CreatedAt == cfg.Columns.ID {
|
||||
var rows []struct {
|
||||
ID uint
|
||||
Pending float64
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
query := tx.Table(cfg.Table).
|
||||
Select(selectStmt).
|
||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ProductWarehouseID), productWarehouseID).
|
||||
Where(fmt.Sprintf("%s > 0", cfg.Columns.PendingQuantity)).
|
||||
Limit(s.pendingBatchPerUsable)
|
||||
|
||||
if cfg.Scope != nil {
|
||||
query = cfg.Scope(query)
|
||||
}
|
||||
|
||||
for _, order := range s.orderClauses(cfg.OrderBy) {
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
if row.Pending <= 0 {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, pendingCandidate{
|
||||
UsableKey: key,
|
||||
Config: cfg,
|
||||
UsableID: row.ID,
|
||||
Pending: row.Pending,
|
||||
CreatedAt: time.Unix(0, row.CreatedAt),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var rows []struct {
|
||||
ID uint
|
||||
Pending float64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
query := tx.Table(cfg.Table).
|
||||
Select(selectStmt).
|
||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ProductWarehouseID), productWarehouseID).
|
||||
Where(fmt.Sprintf("%s > 0", cfg.Columns.PendingQuantity)).
|
||||
Limit(s.pendingBatchPerUsable)
|
||||
|
||||
if cfg.Scope != nil {
|
||||
query = cfg.Scope(query)
|
||||
}
|
||||
|
||||
for _, order := range s.orderClauses(cfg.OrderBy) {
|
||||
query = query.Order(order)
|
||||
}
|
||||
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
if row.Pending <= 0 {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, pendingCandidate{
|
||||
UsableKey: key,
|
||||
Config: cfg,
|
||||
UsableID: row.ID,
|
||||
Pending: row.Pending,
|
||||
CreatedAt: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
candidates = append(candidates, pendingCandidate{
|
||||
UsableKey: key,
|
||||
Config: cfg,
|
||||
UsableID: row.ID,
|
||||
Pending: row.Pending,
|
||||
CreatedAt: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-- Create sequence for transfer laying movement number
|
||||
CREATE SEQUENCE transfer_laying_seq START
|
||||
CREATE SEQUENCE IF NOT EXISTS transfer_laying_seq START
|
||||
WITH
|
||||
1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 99999 NO CYCLE;
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE adjustment_stocks
|
||||
DROP COLUMN adj_number;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,10 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE adjustment_stocks
|
||||
ADD COLUMN adj_number VARCHAR(255);
|
||||
|
||||
UPDATE adjustment_stocks
|
||||
SET adj_number = CONCAT('ADJ-', LPAD(id::text, 5, '0'))
|
||||
WHERE adj_number IS NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Remove columns from marketing_products
|
||||
ALTER TABLE marketing_products
|
||||
DROP COLUMN IF EXISTS week,
|
||||
DROP COLUMN IF EXISTS weight_per_convertion,
|
||||
DROP COLUMN IF EXISTS convertion_unit;
|
||||
|
||||
-- Remove column from marketings
|
||||
ALTER TABLE marketings DROP COLUMN IF EXISTS marketing_type;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Add marketing_type to marketings table
|
||||
ALTER TABLE marketings
|
||||
ADD COLUMN IF NOT EXISTS marketing_type VARCHAR(50);
|
||||
|
||||
-- Add convertion fields to marketing_products table
|
||||
ALTER TABLE marketing_products
|
||||
ADD COLUMN IF NOT EXISTS convertion_unit VARCHAR(20),
|
||||
ADD COLUMN IF NOT EXISTS weight_per_convertion NUMERIC(15, 3),
|
||||
ADD COLUMN IF NOT EXISTS week INTEGER;
|
||||
@@ -0,0 +1,47 @@
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
fallback_fcr_id BIGINT;
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'project_flocks'
|
||||
AND column_name = 'fcr_id'
|
||||
) THEN
|
||||
ALTER TABLE project_flocks
|
||||
ADD COLUMN fcr_id BIGINT;
|
||||
END IF;
|
||||
|
||||
SELECT id INTO fallback_fcr_id
|
||||
FROM fcrs
|
||||
ORDER BY id ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF fallback_fcr_id IS NOT NULL THEN
|
||||
UPDATE project_flocks
|
||||
SET fcr_id = fallback_fcr_id
|
||||
WHERE fcr_id IS NULL;
|
||||
|
||||
ALTER TABLE project_flocks
|
||||
ALTER COLUMN fcr_id SET NOT NULL;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'project_flocks_fcr_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE project_flocks
|
||||
DROP CONSTRAINT project_flocks_fcr_id_fkey;
|
||||
END IF;
|
||||
|
||||
ALTER TABLE project_flocks
|
||||
ADD CONSTRAINT project_flocks_fcr_id_fkey
|
||||
FOREIGN KEY (fcr_id) REFERENCES fcrs(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,26 @@
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'project_flocks'
|
||||
AND column_name = 'fcr_id'
|
||||
) THEN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'project_flocks_fcr_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE project_flocks
|
||||
DROP CONSTRAINT project_flocks_fcr_id_fkey;
|
||||
END IF;
|
||||
|
||||
ALTER TABLE project_flocks
|
||||
DROP COLUMN fcr_id;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -11,6 +11,7 @@ type AdjustmentStock struct {
|
||||
PendingQty float64 `gorm:"column:pending_qty;default:0"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"`
|
||||
AdjNumber string `gorm:"column:adj_number;uniqueIndex;not null"`
|
||||
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
StockLog *StockLog `gorm:"polymorphic:Loggable;polymorphicType:LoggableType;polymorphicId:LoggableId;polymorphicValue:ADJUSTMENT"`
|
||||
|
||||
@@ -14,6 +14,7 @@ type Marketing struct {
|
||||
SoDate time.Time `gorm:"type:date;not null"`
|
||||
SalesPersonId uint `gorm:"not null"`
|
||||
Notes string `gorm:"type:text"`
|
||||
MarketingType string `gorm:"type:varchar(50)"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package entities
|
||||
|
||||
type MarketingProduct struct {
|
||||
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||
MarketingId uint `gorm:"not null"`
|
||||
ProductWarehouseId uint `gorm:"not null"`
|
||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||
AvgWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||
TotalWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||
MarketingId uint `gorm:"not null"`
|
||||
ProductWarehouseId uint `gorm:"not null"`
|
||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
ConvertionUnit *string `gorm:"type:varchar(20)"`
|
||||
WeightPerConvertion *float64 `gorm:"type:numeric(15,3)"`
|
||||
Week *int `gorm:"type:integer"`
|
||||
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||
AvgWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||
TotalWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||
|
||||
Marketing Marketing `gorm:"foreignKey:MarketingId;references:Id"`
|
||||
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
|
||||
@@ -11,7 +11,6 @@ type ProjectFlock struct {
|
||||
FlockName string `gorm:"type:varchar(255);not null;uniqueIndex"`
|
||||
AreaId uint `gorm:"not null"`
|
||||
Category string `gorm:"type:varchar(20);not null"`
|
||||
FcrId uint `gorm:"not null"`
|
||||
ProductionStandardId uint `gorm:"column:production_standard_id"`
|
||||
LocationId uint `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
@@ -20,7 +19,6 @@ type ProjectFlock struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||
Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"`
|
||||
ProductionStandard ProductionStandard `gorm:"foreignKey:ProductionStandardId;references:Id"`
|
||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
|
||||
@@ -24,7 +24,6 @@ type ClosingRepository interface {
|
||||
SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error)
|
||||
SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error)
|
||||
SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error)
|
||||
GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error)
|
||||
GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error)
|
||||
FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error)
|
||||
FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error)
|
||||
@@ -102,12 +101,12 @@ func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params Sapronak
|
||||
if len(params.WarehouseIDs) == 0 {
|
||||
return []SapronakRow{}, 0, nil
|
||||
}
|
||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs, params.WarehouseIDs)
|
||||
case validation.SapronakTypeOutgoing:
|
||||
if len(params.WarehouseIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs)
|
||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||
}
|
||||
if len(params.ProjectFlockKandangIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingMarketingsSQL)
|
||||
@@ -174,12 +173,12 @@ func (r *ClosingRepositoryImpl) GetSapronakSummary(ctx context.Context, params S
|
||||
if len(params.WarehouseIDs) == 0 {
|
||||
return []SapronakSummaryRow{}, nil
|
||||
}
|
||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs, params.WarehouseIDs)
|
||||
case validation.SapronakTypeOutgoing:
|
||||
if len(params.WarehouseIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs)
|
||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||
}
|
||||
if len(params.ProjectFlockKandangIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingMarketingsSQL)
|
||||
@@ -393,22 +392,6 @@ func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFla
|
||||
return agg.TotalQty, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) {
|
||||
if fcrID == 0 {
|
||||
return []entity.FcrStandard{}, nil
|
||||
}
|
||||
|
||||
var standards []entity.FcrStandard
|
||||
if err := r.DB().WithContext(ctx).
|
||||
Where("fcr_id = ?", fcrID).
|
||||
Order("weight ASC").
|
||||
Find(&standards).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return standards, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) {
|
||||
db := r.DB().WithContext(ctx)
|
||||
|
||||
@@ -456,7 +439,7 @@ SELECT
|
||||
COALESCE(pi.received_date, '1970-01-01') AS sort_date,
|
||||
COALESCE(TO_CHAR(pi.received_date, 'DD-Mon-YYYY'), '') AS date_text,
|
||||
COALESCE(p.po_number, '') AS reference_number,
|
||||
'Purchase' AS transaction_type,
|
||||
'Pembelian' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
@@ -505,7 +488,7 @@ SELECT
|
||||
st.transfer_date AS sort_date,
|
||||
TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text,
|
||||
st.movement_number AS reference_number,
|
||||
'Internal Transfer In' AS transaction_type,
|
||||
'Mutasi' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
@@ -549,13 +532,63 @@ JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE st.to_warehouse_id IN ?
|
||||
`
|
||||
|
||||
sapronakIncomingAdjustmentsSQL = `
|
||||
SELECT
|
||||
CAST(ast.id AS BIGINT) AS id,
|
||||
ast.created_at AS sort_date,
|
||||
COALESCE(TO_CHAR(ast.created_at, 'DD-Mon-YYYY'), '') AS date_text,
|
||||
COALESCE(ast.adj_number, '') AS reference_number,
|
||||
'Adjustment stock' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
)
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
)
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
'-' AS source_warehouse,
|
||||
COALESCE(w.name, '') AS destination_warehouse,
|
||||
'' AS destination,
|
||||
COALESCE(ast.total_qty, 0) AS quantity,
|
||||
u.id AS unit_id,
|
||||
u.name AS unit,
|
||||
'-' AS notes
|
||||
FROM adjustment_stocks ast
|
||||
JOIN product_warehouses pw ON pw.id = ast.product_warehouse_id
|
||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||
JOIN products prod ON prod.id = pw.product_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE pw.warehouse_id IN ?
|
||||
AND COALESCE(ast.total_qty, 0) <> 0
|
||||
`
|
||||
|
||||
sapronakOutgoingTransfersSQL = `
|
||||
SELECT
|
||||
CAST(st.id AS BIGINT) AS id,
|
||||
st.transfer_date AS sort_date,
|
||||
TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text,
|
||||
st.movement_number AS reference_number,
|
||||
'Internal Transfer Out' AS transaction_type,
|
||||
'Mutasi' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
@@ -599,13 +632,70 @@ JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE st.from_warehouse_id IN ?
|
||||
`
|
||||
|
||||
sapronakOutgoingAdjustmentsSQL = `
|
||||
SELECT
|
||||
CAST(ast.id AS BIGINT) AS id,
|
||||
ast.created_at AS sort_date,
|
||||
COALESCE(TO_CHAR(ast.created_at, 'DD-Mon-YYYY'), '') AS date_text,
|
||||
COALESCE(ast.adj_number, '') AS reference_number,
|
||||
'Adjustment stock' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
)
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
)
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
COALESCE(w.name, '') AS source_warehouse,
|
||||
'-' AS destination_warehouse,
|
||||
'' AS destination,
|
||||
COALESCE(ast.usage_qty, 0) AS quantity,
|
||||
u.id AS unit_id,
|
||||
u.name AS unit,
|
||||
'-' AS notes
|
||||
FROM adjustment_stocks ast
|
||||
JOIN product_warehouses pw ON pw.id = ast.product_warehouse_id
|
||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||
JOIN products prod ON prod.id = pw.product_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE pw.warehouse_id IN ?
|
||||
AND COALESCE(ast.usage_qty, 0) <> 0
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM flags f
|
||||
WHERE f.flagable_id = pw.product_id
|
||||
AND f.flagable_type = 'products'
|
||||
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
)
|
||||
`
|
||||
|
||||
sapronakOutgoingMarketingsSQL = `
|
||||
SELECT
|
||||
CAST(mp.id AS BIGINT) AS id,
|
||||
m.so_date AS sort_date,
|
||||
TO_CHAR(m.so_date, 'DD-Mon-YYYY') AS date_text,
|
||||
m.so_number AS reference_number,
|
||||
'Trading Sales' AS transaction_type,
|
||||
'Penjualan' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
COALESCE((
|
||||
SELECT string_agg(
|
||||
@@ -653,7 +743,7 @@ WHERE pw.project_flock_kandang_id IN ?
|
||||
FROM flags f
|
||||
WHERE f.flagable_id = pw.product_id
|
||||
AND f.flagable_type = 'products'
|
||||
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET')
|
||||
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
)
|
||||
`
|
||||
)
|
||||
|
||||
@@ -836,14 +836,6 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
|
||||
finalPopulation := population - claimCulling
|
||||
|
||||
var standards []entity.FcrStandard
|
||||
if project.FcrId > 0 {
|
||||
standards, err = s.Repository.GetFcrStandardsByFcrID(c.Context(), project.FcrId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch FCR standards for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch FCR standard data")
|
||||
}
|
||||
}
|
||||
age, err := s.calculateAverageSalesAge(c.Context(), projectFlockID, kandangID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to calculate sales age for project flock %d: %+v", projectFlockID, err)
|
||||
@@ -893,7 +885,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
chickenDepletion = 0
|
||||
}
|
||||
|
||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age, standards)
|
||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
||||
if fcrActFromRecording != nil {
|
||||
chickenPerformance.FcrAct = *fcrActFromRecording
|
||||
}
|
||||
@@ -943,7 +935,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
eggDepletion = 0
|
||||
}
|
||||
|
||||
eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age, standards)
|
||||
eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age)
|
||||
if fcrActFromRecording != nil {
|
||||
eggPerf.FcrAct = *fcrActFromRecording
|
||||
}
|
||||
@@ -1001,10 +993,10 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
performance.EggMass = eggMass
|
||||
}
|
||||
}
|
||||
performance.DeffFcr = performance.FcrStd - performance.FcrAct
|
||||
if productionStandardDetail != nil {
|
||||
if productionStandardDetail.StandardFCR != nil {
|
||||
performance.FcrStd = *productionStandardDetail.StandardFCR
|
||||
performance.DeffFcr = performance.FcrStd - performance.FcrAct
|
||||
}
|
||||
if !isGrowing {
|
||||
if productionStandardDetail.TargetHenDayProduction != nil {
|
||||
@@ -1091,8 +1083,8 @@ func (s closingService) determineProductionWeek(ctx context.Context, projectFloc
|
||||
return week, nil
|
||||
}
|
||||
|
||||
func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64, standards []entity.FcrStandard) dto.ClosingPerformanceDTO {
|
||||
mortalityStd, fcrStd := closestFcrValues(standards, averageWeight)
|
||||
func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64) dto.ClosingPerformanceDTO {
|
||||
mortalityStd, fcrStd := 0.0, 0.0
|
||||
|
||||
fcrAct := 0.0
|
||||
if totalWeight > 0 {
|
||||
@@ -1124,21 +1116,3 @@ func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopul
|
||||
AwgAct: awg,
|
||||
}
|
||||
}
|
||||
|
||||
func closestFcrValues(standards []entity.FcrStandard, averageWeight float64) (float64, float64) {
|
||||
if len(standards) == 0 || averageWeight <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
closest := standards[0]
|
||||
minDiff := math.Abs(closest.Weight - averageWeight)
|
||||
for _, std := range standards[1:] {
|
||||
diff := math.Abs(std.Weight - averageWeight)
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
closest = std
|
||||
}
|
||||
}
|
||||
|
||||
return closest.Mortality, closest.FcrNumber
|
||||
}
|
||||
|
||||
@@ -444,30 +444,6 @@ func (r *DashboardRepositoryImpl) standardIDSubquery(filters *validation.Dashboa
|
||||
return db
|
||||
}
|
||||
|
||||
func (r *DashboardRepositoryImpl) standardSourceSubquery(filters *validation.DashboardFilter) *gorm.DB {
|
||||
db := r.DB().
|
||||
Table("project_flocks AS pf").
|
||||
Select("DISTINCT pf.production_standard_id, pf.fcr_id").
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Where("pf.production_standard_id > 0").
|
||||
Where("pf.fcr_id > 0")
|
||||
|
||||
if filters != nil {
|
||||
if len(filters.FlockIds) > 0 {
|
||||
db = db.Where("pf.id IN ?", filters.FlockIds)
|
||||
}
|
||||
if len(filters.KandangIds) > 0 {
|
||||
db = db.Where("k.id IN ?", filters.KandangIds)
|
||||
}
|
||||
if len(filters.LokasiIds) > 0 {
|
||||
db = db.Where("k.location_id IN ?", filters.LokasiIds)
|
||||
}
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func (r *DashboardRepositoryImpl) GetComparisonSeries(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType string) ([]ComparisonSeries, error) {
|
||||
seriesExpr, labelExpr, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType)
|
||||
if err != nil {
|
||||
|
||||
@@ -24,46 +24,81 @@ func NewTransactionController(transactionService service.TransactionService) *Tr
|
||||
}
|
||||
|
||||
func (u *TransactionController) GetAll(c *fiber.Ctx) error {
|
||||
parseOptionalUint := func(key string) (*uint, error) {
|
||||
parseUintListParam := func(key string) ([]uint, error) {
|
||||
raw := strings.TrimSpace(c.Query(key, ""))
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := strconv.ParseUint(raw, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid "+key)
|
||||
parts := strings.Split(raw, ",")
|
||||
ids := make([]uint, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
return nil, strconv.ErrSyntax
|
||||
}
|
||||
parsed, err := strconv.ParseUint(trimmed, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsed == 0 {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, uint(parsed))
|
||||
}
|
||||
if parsed == 0 {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
value := uint(parsed)
|
||||
return &value, nil
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
bankId, err := parseOptionalUint("bank_id")
|
||||
if err != nil {
|
||||
return err
|
||||
parseStringListParam := func(key string) ([]string, error) {
|
||||
raw := strings.TrimSpace(c.Query(key, ""))
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
values := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
return nil, strconv.ErrSyntax
|
||||
}
|
||||
values = append(values, trimmed)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
customerId, err := parseOptionalUint("customer_id")
|
||||
|
||||
bankIDs, err := parseUintListParam("bank_ids")
|
||||
if err != nil {
|
||||
return err
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid bank_ids")
|
||||
}
|
||||
supplierId, err := parseOptionalUint("supplier_id")
|
||||
customerIDs, err := parseUintListParam("customer_ids")
|
||||
if err != nil {
|
||||
return err
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid customer_ids")
|
||||
}
|
||||
supplierIDs, err := parseUintListParam("supplier_ids")
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid supplier_ids")
|
||||
}
|
||||
transactionTypes, err := parseStringListParam("transaction_types")
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction_types")
|
||||
}
|
||||
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
TransactionType: c.Query("transaction_type", ""),
|
||||
BankId: bankId,
|
||||
CustomerId: customerId,
|
||||
SupplierId: supplierId,
|
||||
SortDate: c.Query("sort_date", ""),
|
||||
StartDate: c.Query("start_date", ""),
|
||||
EndDate: c.Query("end_date", ""),
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
TransactionTypes: transactionTypes,
|
||||
BankIDs: bankIDs,
|
||||
CustomerIDs: customerIDs,
|
||||
SupplierIDs: supplierIDs,
|
||||
SortDate: c.Query("sort_date", ""),
|
||||
StartDate: c.Query("start_date", ""),
|
||||
EndDate: c.Query("end_date", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
|
||||
@@ -74,33 +74,58 @@ func (s transactionService) GetAll(c *fiber.Ctx, params *validation.Query) ([]en
|
||||
|
||||
if params.Search != "" {
|
||||
like := "%" + strings.ToLower(strings.TrimSpace(params.Search)) + "%"
|
||||
db = db.Joins(
|
||||
"LEFT JOIN customers ON customers.id = payments.party_id AND payments.party_type = ? AND customers.deleted_at IS NULL",
|
||||
string(utils.PaymentPartyCustomer),
|
||||
).Joins(
|
||||
"LEFT JOIN suppliers ON suppliers.id = payments.party_id AND payments.party_type = ? AND suppliers.deleted_at IS NULL",
|
||||
string(utils.PaymentPartySupplier),
|
||||
).Joins(
|
||||
"LEFT JOIN banks ON banks.id = payments.bank_id AND banks.deleted_at IS NULL",
|
||||
)
|
||||
db = db.Where(
|
||||
`LOWER(payment_code) LIKE ? OR
|
||||
LOWER(COALESCE(reference_number, '')) LIKE ? OR
|
||||
LOWER(COALESCE(transaction_type, '')) LIKE ? OR
|
||||
LOWER(COALESCE(notes, '')) LIKE ?`,
|
||||
like, like, like, like,
|
||||
LOWER(COALESCE(notes, '')) LIKE ? OR
|
||||
LOWER(COALESCE(customers.name, '')) LIKE ? OR
|
||||
LOWER(COALESCE(suppliers.name, '')) LIKE ? OR
|
||||
LOWER(COALESCE(banks.name, '')) LIKE ?`,
|
||||
like, like, like, like, like, like, like,
|
||||
)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(params.TransactionType) != "" {
|
||||
db = db.Where("transaction_type = ?", strings.ToUpper(strings.TrimSpace(params.TransactionType)))
|
||||
if len(params.TransactionTypes) > 0 {
|
||||
types := make([]string, 0, len(params.TransactionTypes))
|
||||
for _, transactionType := range params.TransactionTypes {
|
||||
normalized := strings.ToUpper(strings.TrimSpace(transactionType))
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
types = append(types, normalized)
|
||||
}
|
||||
if len(types) > 0 {
|
||||
db = db.Where("transaction_type IN ?", types)
|
||||
}
|
||||
}
|
||||
|
||||
if params.BankId != nil {
|
||||
db = db.Where("bank_id = ?", *params.BankId)
|
||||
if len(params.BankIDs) > 0 {
|
||||
db = db.Where("bank_id IN ?", params.BankIDs)
|
||||
}
|
||||
|
||||
if params.CustomerId != nil && params.SupplierId != nil {
|
||||
customerIDs := params.CustomerIDs
|
||||
supplierIDs := params.SupplierIDs
|
||||
|
||||
if len(customerIDs) > 0 && len(supplierIDs) > 0 {
|
||||
db = db.Where(
|
||||
"(party_type = ? AND party_id = ?) OR (party_type = ? AND party_id = ?)",
|
||||
string(utils.PaymentPartyCustomer), *params.CustomerId,
|
||||
string(utils.PaymentPartySupplier), *params.SupplierId,
|
||||
"(party_type = ? AND party_id IN ?) OR (party_type = ? AND party_id IN ?)",
|
||||
string(utils.PaymentPartyCustomer), customerIDs,
|
||||
string(utils.PaymentPartySupplier), supplierIDs,
|
||||
)
|
||||
} else if params.CustomerId != nil {
|
||||
db = db.Where("party_type = ? AND party_id = ?", string(utils.PaymentPartyCustomer), *params.CustomerId)
|
||||
} else if params.SupplierId != nil {
|
||||
db = db.Where("party_type = ? AND party_id = ?", string(utils.PaymentPartySupplier), *params.SupplierId)
|
||||
} else if len(customerIDs) > 0 {
|
||||
db = db.Where("party_type = ? AND party_id IN ?", string(utils.PaymentPartyCustomer), customerIDs)
|
||||
} else if len(supplierIDs) > 0 {
|
||||
db = db.Where("party_type = ? AND party_id IN ?", string(utils.PaymentPartySupplier), supplierIDs)
|
||||
}
|
||||
|
||||
if startDate != nil {
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package validation
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
TransactionType string `query:"transaction_type" validate:"omitempty,max=50"`
|
||||
BankId *uint `query:"bank_id" validate:"omitempty,number,gt=0"`
|
||||
CustomerId *uint `query:"customer_id" validate:"omitempty,number,gt=0"`
|
||||
SupplierId *uint `query:"supplier_id" validate:"omitempty,number,gt=0"`
|
||||
SortDate string `query:"sort_date" validate:"omitempty,oneof=created_at payment_date"`
|
||||
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
|
||||
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
||||
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"`
|
||||
TransactionTypes []string `query:"transaction_types" validate:"omitempty,dive,max=50"`
|
||||
BankIDs []uint `query:"bank_ids" validate:"omitempty,dive,gt=0"`
|
||||
CustomerIDs []uint `query:"customer_ids" validate:"omitempty,dive,gt=0"`
|
||||
SupplierIDs []uint `query:"supplier_ids" validate:"omitempty,dive,gt=0"`
|
||||
SortDate string `query:"sort_date" validate:"omitempty,oneof=created_at payment_date"`
|
||||
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
|
||||
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type AdjustmentStockRepository interface {
|
||||
@@ -12,6 +16,7 @@ type AdjustmentStockRepository interface {
|
||||
GetByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.AdjustmentStock, error)
|
||||
WithTx(tx *gorm.DB) AdjustmentStockRepository
|
||||
DB() *gorm.DB
|
||||
GenerateSequentialNumber(ctx context.Context, prefix string) (string, error)
|
||||
}
|
||||
|
||||
type adjustmentStockRepositoryImpl struct {
|
||||
@@ -50,3 +55,71 @@ func (r *adjustmentStockRepositoryImpl) WithTx(tx *gorm.DB) AdjustmentStockRepos
|
||||
func (r *adjustmentStockRepositoryImpl) DB() *gorm.DB {
|
||||
return r.db
|
||||
}
|
||||
|
||||
func (r *adjustmentStockRepositoryImpl) GenerateSequentialNumber(ctx context.Context, prefix string) (string, error) {
|
||||
var values []string
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entity.AdjustmentStock{}).
|
||||
Where(fmt.Sprintf("%s ILIKE ?", "adj_number"), prefix+"%").
|
||||
Select("adj_number").
|
||||
Order(fmt.Sprintf("%s DESC", "adj_number")).
|
||||
Limit(20).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Pluck("adj_number", &values).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
next := 1
|
||||
for _, value := range values {
|
||||
if number, ok := parseNumericSuffix(value, prefix); ok {
|
||||
next = number + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const maxAttempts = 20
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
candidate := fmt.Sprintf("%s%0*d", prefix, 5, next)
|
||||
exists, err := r.numberExists(ctx, r.db, candidate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exists {
|
||||
return candidate, nil
|
||||
}
|
||||
next++
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to generate unique %s", "adj_number")
|
||||
}
|
||||
|
||||
func (r *adjustmentStockRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB, value string) (bool, error) {
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&entity.AdjustmentStock{}).
|
||||
Where(fmt.Sprintf("%s = ?", "adj_number"), value).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func parseNumericSuffix(value, prefix string) (int, bool) {
|
||||
if !strings.HasPrefix(value, prefix) {
|
||||
return 0, false
|
||||
}
|
||||
suffix := strings.TrimPrefix(value, prefix)
|
||||
if suffix == "" {
|
||||
return 0, false
|
||||
}
|
||||
trimmed := strings.TrimLeft(suffix, "0")
|
||||
if trimmed == "" {
|
||||
trimmed = "0"
|
||||
}
|
||||
number, err := strconv.Atoi(trimmed)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return number, true
|
||||
}
|
||||
|
||||
@@ -200,6 +200,11 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
adjustmentStock := &entity.AdjustmentStock{
|
||||
ProductWarehouseId: productWarehouse.Id,
|
||||
}
|
||||
code, err := s.AdjustmentStockRepository.GenerateSequentialNumber(ctx, utils.AdjustmentStockNumberPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adjustmentStock.AdjNumber = code
|
||||
if err := s.AdjustmentStockRepository.WithTx(tx).CreateOne(ctx, adjustmentStock, nil); err != nil {
|
||||
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create adjustment stock record")
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
|
||||
Flags: c.Query("flags", ""),
|
||||
KandangId: uint(c.QueryInt("kandang_id", 0)),
|
||||
TransferContext: c.Query(utils.TransferContextKey, ""),
|
||||
Type: c.Query("type", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
@@ -22,6 +23,7 @@ type ProductWarehouseListDTO struct {
|
||||
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
||||
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||
ProjectFlockKandang *ProjectFlockKandangRelationDTO `json:"project_flock_kandang,omitempty"`
|
||||
Week int `json:"week"`
|
||||
CreatedUser *UserRelationDTO `json:"created_user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -109,6 +111,22 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT
|
||||
}
|
||||
|
||||
dto.ProjectFlockKandang = pfkDTO
|
||||
|
||||
// Calculate week for AYAM_PULLET/AYAM products
|
||||
productFlags := make([]string, len(e.Product.Flags))
|
||||
for i, f := range e.Product.Flags {
|
||||
productFlags[i] = f.Name
|
||||
}
|
||||
|
||||
var category string
|
||||
if e.ProjectFlockKandang.ProjectFlock.Id != 0 {
|
||||
category = e.ProjectFlockKandang.ProjectFlock.Category
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
_, ageInWeeks := calculateAgeFromChickin(e.ProjectFlockKandang, &now, productFlags, category)
|
||||
|
||||
dto.Week = ageInWeeks
|
||||
}
|
||||
|
||||
return dto
|
||||
@@ -138,3 +156,58 @@ func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNeste
|
||||
Warehouse: &warehouse,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate age from chickin (same logic as closingMarketing.dto.go)
|
||||
func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, currentDate *time.Time, productFlags []string, category string) (int, int) {
|
||||
if projectFlockKandang == nil || currentDate == nil || len(projectFlockKandang.Chickins) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Return 0 for TRADING, TELUR, and AYAM flags (only AYAM_PULLET should have week)
|
||||
for _, flag := range productFlags {
|
||||
if flag == string(utils.FlagOVK) ||
|
||||
flag == string(utils.FlagPakan) ||
|
||||
flag == string(utils.FlagPreStarter) ||
|
||||
flag == string(utils.FlagStarter) ||
|
||||
flag == string(utils.FlagFinisher) ||
|
||||
flag == string(utils.FlagObat) ||
|
||||
flag == string(utils.FlagVitamin) ||
|
||||
flag == string(utils.FlagKimia) ||
|
||||
flag == string(utils.FlagEkspedisi) ||
|
||||
flag == string(utils.FlagTelur) ||
|
||||
flag == string(utils.FlagTelurUtuh) ||
|
||||
flag == string(utils.FlagTelurPecah) ||
|
||||
flag == string(utils.FlagTelurPutih) ||
|
||||
flag == string(utils.FlagTelurRetak) ||
|
||||
flag == string(utils.FlagAyamAfkir) ||
|
||||
flag == string(utils.FlagAyamCulling) ||
|
||||
flag == string(utils.FlagAyamMati) {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
// Find earliest chickin date
|
||||
earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
if chickin.ChickInDate.Before(earliestChickinDate) {
|
||||
earliestChickinDate = chickin.ChickInDate
|
||||
}
|
||||
}
|
||||
|
||||
diff := currentDate.Sub(earliestChickinDate)
|
||||
ageInDays := int(diff.Hours() / 24)
|
||||
|
||||
var ageInWeeks int
|
||||
if ageInDays <= 0 {
|
||||
ageInWeeks = 0
|
||||
} else {
|
||||
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
ageInDays = ageInDays + 119
|
||||
ageInWeeks = ((ageInDays - 1) / 7) + 1
|
||||
} else {
|
||||
ageInWeeks = ((ageInDays - 1) / 7) + 1
|
||||
}
|
||||
}
|
||||
|
||||
return ageInDays, ageInWeeks
|
||||
}
|
||||
|
||||
+4
-3
@@ -168,9 +168,10 @@ func (r *ProductWarehouseRepositoryImpl) ApplyFlagsFilter(db *gorm.DB, flags []s
|
||||
}
|
||||
|
||||
return db.
|
||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
|
||||
Where("flags.name IN ?", flags)
|
||||
Joins("JOIN products p_flag ON p_flag.id = product_warehouses.product_id").
|
||||
Joins("JOIN flags f_flag ON f_flag.flagable_id = p_flag.id AND f_flag.flagable_type = ?", "products").
|
||||
Where("f_flag.name IN ?", flags).
|
||||
Distinct()
|
||||
}
|
||||
|
||||
func (r *ProductWarehouseRepositoryImpl) AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error {
|
||||
|
||||
+24
-2
@@ -46,7 +46,8 @@ func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Warehouse.Area").
|
||||
Preload("Warehouse.Kandang").
|
||||
Preload("ProjectFlockKandang").
|
||||
Preload("ProjectFlockKandang.ProjectFlock")
|
||||
Preload("ProjectFlockKandang.ProjectFlock").
|
||||
Preload("ProjectFlockKandang.Chickins")
|
||||
}
|
||||
|
||||
func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) {
|
||||
@@ -99,6 +100,12 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
if params.Type != "" {
|
||||
if !utils.IsValidMarketingType(params.Type) {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing type")
|
||||
}
|
||||
}
|
||||
|
||||
cleanFlags := utils.ParseFlags(params.Flags)
|
||||
|
||||
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
@@ -128,7 +135,22 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
||||
db = db.Where("warehouse_id = ?", params.WarehouseId)
|
||||
}
|
||||
|
||||
db = s.Repository.ApplyFlagsFilter(db, cleanFlags)
|
||||
if params.Type != "" {
|
||||
switch params.Type {
|
||||
case string(utils.MarketingTypeAyamPullet):
|
||||
db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer)})
|
||||
case string(utils.MarketingTypeAyam):
|
||||
db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagAyamMati)})
|
||||
case string(utils.MarketingTypeTelur):
|
||||
db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagTelur), string(utils.FlagTelurUtuh), string(utils.FlagTelurPecah), string(utils.FlagTelurPutih), string(utils.FlagTelurRetak)})
|
||||
case string(utils.MarketingTypeTrading):
|
||||
db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagPakan), string(utils.FlagPreStarter), string(utils.FlagStarter), string(utils.FlagFinisher), string(utils.FlagOVK), string(utils.FlagObat), string(utils.FlagVitamin), string(utils.FlagKimia), string(utils.FlagEkspedisi)})
|
||||
}
|
||||
}
|
||||
|
||||
if len(cleanFlags) > 0 {
|
||||
db = s.Repository.ApplyFlagsFilter(db, cleanFlags)
|
||||
}
|
||||
|
||||
return db.Order("product_warehouses.id DESC")
|
||||
})
|
||||
|
||||
+1
@@ -20,4 +20,5 @@ type Query struct {
|
||||
Flags string `query:"flags" validate:"omitempty"`
|
||||
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
|
||||
TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"`
|
||||
Type string `query:"type" validate:"omitempty,oneof=AYAM TELUR TRADING AYAM_PULLET"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -76,16 +77,21 @@ type DeliveryGroupDTO struct {
|
||||
}
|
||||
|
||||
type DeliveryMarketingProductDTO struct {
|
||||
Id uint `json:"id"`
|
||||
MarketingId uint `json:"marketing_id"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
Qty float64 `json:"qty"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
AvgWeight float64 `json:"avg_weight"`
|
||||
TotalWeight float64 `json:"total_weight"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||
VehicleNumber string `json:"vehicle_number,omitempty"`
|
||||
Id uint `json:"id"`
|
||||
MarketingId uint `json:"marketing_id"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
MarketingType string `json:"marketing_type"`
|
||||
Qty float64 `json:"qty"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
AvgWeight float64 `json:"avg_weight"`
|
||||
TotalWeight float64 `json:"total_weight"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
ConvertionUnit *string `json:"convertion_unit,omitempty"`
|
||||
WeightPerConvertion *float64 `json:"weight_per_convertion,omitempty"`
|
||||
TotalPeti *float64 `json:"total_peti,omitempty"`
|
||||
Week *int `json:"week,omitempty"`
|
||||
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||
VehicleNumber string `json:"vehicle_number,omitempty"`
|
||||
}
|
||||
|
||||
func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
|
||||
@@ -97,24 +103,36 @@ func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func ToDeliveryMarketingProductDTO(e entity.MarketingProduct) DeliveryMarketingProductDTO {
|
||||
func ToDeliveryMarketingProductDTO(e entity.MarketingProduct, marketingType string) DeliveryMarketingProductDTO {
|
||||
var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO
|
||||
if e.ProductWarehouse.Id != 0 {
|
||||
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
||||
productWarehouse = &mapped
|
||||
}
|
||||
|
||||
// Calculate total_peti only for TELUR marketing type
|
||||
var totalPeti *float64
|
||||
if marketingType == "TELUR" && e.ConvertionUnit != nil && *e.ConvertionUnit == "PETI" && e.WeightPerConvertion != nil && *e.WeightPerConvertion > 0 {
|
||||
calculated := math.Floor(e.TotalWeight / *e.WeightPerConvertion)
|
||||
totalPeti = &calculated
|
||||
}
|
||||
|
||||
return DeliveryMarketingProductDTO{
|
||||
Id: e.Id,
|
||||
MarketingId: e.MarketingId,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
Qty: e.Qty,
|
||||
UnitPrice: e.UnitPrice,
|
||||
AvgWeight: e.AvgWeight,
|
||||
TotalWeight: e.TotalWeight,
|
||||
TotalPrice: e.TotalPrice,
|
||||
ProductWarehouse: productWarehouse,
|
||||
VehicleNumber: getVehicleNumber(e),
|
||||
Id: e.Id,
|
||||
MarketingId: e.MarketingId,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
MarketingType: marketingType,
|
||||
Qty: e.Qty,
|
||||
UnitPrice: e.UnitPrice,
|
||||
AvgWeight: e.AvgWeight,
|
||||
TotalWeight: e.TotalWeight,
|
||||
TotalPrice: e.TotalPrice,
|
||||
ConvertionUnit: e.ConvertionUnit,
|
||||
WeightPerConvertion: e.WeightPerConvertion,
|
||||
TotalPeti: totalPeti,
|
||||
Week: e.Week,
|
||||
ProductWarehouse: productWarehouse,
|
||||
VehicleNumber: getVehicleNumber(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +179,7 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M
|
||||
if len(marketing.Products) > 0 {
|
||||
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||
for i, product := range marketing.Products {
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product)
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +219,7 @@ func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity
|
||||
if len(marketing.Products) > 0 {
|
||||
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||
for i, product := range marketing.Products {
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product)
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
@@ -10,13 +11,18 @@ import (
|
||||
// === DTO Structs ===
|
||||
|
||||
type MarketingProductDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Qty float64 `json:"qty"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
AvgWeight float64 `json:"avg_weight"`
|
||||
TotalWeight float64 `json:"total_weight"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
ProductWarehouse *productWarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||
Id uint `json:"id"`
|
||||
MarketingType string `json:"marketing_type"`
|
||||
Qty float64 `json:"qty"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
AvgWeight float64 `json:"avg_weight"`
|
||||
TotalWeight float64 `json:"total_weight"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
ConvertionUnit *string `json:"convertion_unit,omitempty"`
|
||||
WeightPerConvertion *float64 `json:"weight_per_convertion,omitempty"`
|
||||
TotalPeti *float64 `json:"total_peti,omitempty"`
|
||||
Week *int `json:"week,omitempty"`
|
||||
ProductWarehouse *productWarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||
}
|
||||
|
||||
type SalesOrdersListDTO struct {
|
||||
@@ -29,7 +35,7 @@ type SalesOrdersListDTO struct {
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO {
|
||||
func ToMarketingProductDTO(e entity.MarketingProduct, marketingType string) MarketingProductDTO {
|
||||
var productWarehouse *productWarehouseDTO.ProductWarehousNestedDTO
|
||||
|
||||
if e.ProductWarehouse.Id != 0 {
|
||||
@@ -37,21 +43,33 @@ func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO {
|
||||
productWarehouse = &mapped
|
||||
}
|
||||
|
||||
// Calculate total_peti only for TELUR marketing type
|
||||
var totalPeti *float64
|
||||
if marketingType == "TELUR" && e.ConvertionUnit != nil && *e.ConvertionUnit == "PETI" && e.WeightPerConvertion != nil && *e.WeightPerConvertion > 0 {
|
||||
calculated := math.Floor(e.TotalWeight / *e.WeightPerConvertion)
|
||||
totalPeti = &calculated
|
||||
}
|
||||
|
||||
return MarketingProductDTO{
|
||||
Id: e.Id,
|
||||
Qty: e.Qty,
|
||||
UnitPrice: e.UnitPrice,
|
||||
AvgWeight: e.AvgWeight,
|
||||
TotalWeight: e.TotalWeight,
|
||||
TotalPrice: e.TotalPrice,
|
||||
ProductWarehouse: productWarehouse,
|
||||
Id: e.Id,
|
||||
MarketingType: marketingType,
|
||||
Qty: e.Qty,
|
||||
UnitPrice: e.UnitPrice,
|
||||
AvgWeight: e.AvgWeight,
|
||||
TotalWeight: e.TotalWeight,
|
||||
TotalPrice: e.TotalPrice,
|
||||
ConvertionUnit: e.ConvertionUnit,
|
||||
WeightPerConvertion: e.WeightPerConvertion,
|
||||
TotalPeti: totalPeti,
|
||||
Week: e.Week,
|
||||
ProductWarehouse: productWarehouse,
|
||||
}
|
||||
}
|
||||
|
||||
func ToSalesOrdersListDTO(e entity.Marketing) SalesOrdersListDTO {
|
||||
products := make([]MarketingProductDTO, len(e.Products))
|
||||
for i, p := range e.Products {
|
||||
products[i] = ToMarketingProductDTO(p)
|
||||
products[i] = ToMarketingProductDTO(p, e.MarketingType)
|
||||
}
|
||||
|
||||
return SalesOrdersListDTO{
|
||||
@@ -68,7 +86,7 @@ func ToSalesOrdersListDTOFromMarketing(e entity.Marketing) SalesOrdersListDTO {
|
||||
if len(e.Products) > 0 {
|
||||
salesOrder = make([]MarketingProductDTO, len(e.Products))
|
||||
for i, product := range e.Products {
|
||||
salesOrder[i] = ToMarketingProductDTO(product)
|
||||
salesOrder[i] = ToMarketingProductDTO(product, e.MarketingType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ func (s deliveryOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Customer").
|
||||
Preload("SalesPerson").
|
||||
Preload("Products.ProductWarehouse.Product").
|
||||
Preload("Products.ProductWarehouse.Product.Uom").
|
||||
Preload("Products.ProductWarehouse.Warehouse").
|
||||
Preload("Products.DeliveryProduct")
|
||||
}
|
||||
@@ -111,6 +112,7 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
||||
Preload("Customer").
|
||||
Preload("SalesPerson").
|
||||
Preload("Products.ProductWarehouse.Product").
|
||||
Preload("Products.ProductWarehouse.Product.Uom").
|
||||
Preload("Products.ProductWarehouse.Warehouse").
|
||||
Preload("Products.DeliveryProduct")
|
||||
|
||||
@@ -237,6 +239,12 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
|
||||
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
||||
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
marketingRepoTx := marketingRepo.NewMarketingRepository(dbTransaction)
|
||||
|
||||
marketing, err := marketingRepoTx.GetByID(c.Context(), req.MarketingId, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing")
|
||||
}
|
||||
|
||||
allMarketingProducts, err := marketingProductRepositoryTx.GetByMarketingID(c.Context(), req.MarketingId)
|
||||
if err != nil {
|
||||
@@ -283,25 +291,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
|
||||
itemDeliveryDate = &parsedDate
|
||||
}
|
||||
|
||||
isPakanOrOVK := false
|
||||
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
||||
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
||||
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||
isPakanOrOVK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
||||
var totalPrice float64
|
||||
if isPakanOrOVK {
|
||||
|
||||
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
|
||||
} else {
|
||||
|
||||
totalPrice = totalWeight * requestedProduct.UnitPrice
|
||||
}
|
||||
totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week)
|
||||
|
||||
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||
@@ -374,6 +364,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
||||
|
||||
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
||||
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||
marketingRepoTx := marketingRepo.NewMarketingRepository(dbTransaction)
|
||||
|
||||
marketing, err := marketingRepoTx.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing")
|
||||
}
|
||||
|
||||
allMarketingProducts, err := marketingProductRepositoryTx.GetByMarketingID(c.Context(), id)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -421,25 +417,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
||||
itemDeliveryDate = deliveryProduct.DeliveryDate
|
||||
}
|
||||
|
||||
isPakanOrOVK := false
|
||||
if foundMarketingProduct.ProductWarehouse.Id != 0 && foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
||||
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
||||
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||
isPakanOrOVK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
||||
var totalPrice float64
|
||||
if isPakanOrOVK {
|
||||
|
||||
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
|
||||
} else {
|
||||
|
||||
totalPrice = totalWeight * requestedProduct.UnitPrice
|
||||
}
|
||||
totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week)
|
||||
|
||||
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||
@@ -483,6 +461,20 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
||||
return s.getMarketingWithDeliveries(c, id)
|
||||
}
|
||||
|
||||
func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) {
|
||||
if marketingType == string(utils.MarketingTypeTrading) {
|
||||
totalWeight = 0
|
||||
totalPrice = qty * unitPrice
|
||||
} else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 {
|
||||
totalWeight = qty * avgWeight
|
||||
totalPrice = unitPrice * float64(*week) * qty
|
||||
} else {
|
||||
totalWeight = qty * avgWeight
|
||||
totalPrice = totalWeight * unitPrice
|
||||
}
|
||||
return totalWeight, totalPrice
|
||||
}
|
||||
|
||||
func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error {
|
||||
if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
@@ -69,6 +70,7 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Customer").
|
||||
Preload("SalesPerson").
|
||||
Preload("Products.ProductWarehouse.Product.Flags").
|
||||
Preload("Products.ProductWarehouse.Product.Uom").
|
||||
Preload("Products.ProductWarehouse.Warehouse")
|
||||
}
|
||||
|
||||
@@ -103,6 +105,25 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validasi semua product harus punya marketing_type yang sama
|
||||
if len(req.MarketingProducts) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "marketing_products is required")
|
||||
}
|
||||
|
||||
firstMarketingType := req.MarketingProducts[0].MarketingType
|
||||
if !utils.IsValidMarketingType(firstMarketingType) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Tipe penjualan tidak valid")
|
||||
}
|
||||
|
||||
for i, item := range req.MarketingProducts {
|
||||
if !utils.IsValidMarketingType(item.MarketingType) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tipe penjualan tidak valid pada produk ke-%d", i+1))
|
||||
}
|
||||
if item.MarketingType != firstMarketingType {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Semua produk harus memiliki tipe penjualan yang sama")
|
||||
}
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -115,6 +136,12 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
||||
}
|
||||
|
||||
for _, item := range req.MarketingProducts {
|
||||
if item.MarketingType != string(utils.MarketingTypeTrading) && item.AvgWeight == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Berat rata-rata harus diisi")
|
||||
}
|
||||
if item.ConvertionUnit != nil && !utils.IsValidConvertionUnit(*item.ConvertionUnit) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Unit konversi tidak valid")
|
||||
}
|
||||
if err := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,6 +176,7 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
||||
SoDate: soDate,
|
||||
SalesPersonId: req.SalesPersonId,
|
||||
Notes: req.Notes,
|
||||
MarketingType: firstMarketingType,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil {
|
||||
@@ -161,10 +189,9 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
||||
if product.ProductWarehouseId != 0 {
|
||||
pwIDs = append(pwIDs, product.ProductWarehouseId)
|
||||
}
|
||||
if err := s.createMarketingProductWithDelivery(c.Context(), marketing.Id, product, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||
if err := s.createMarketingProductWithDelivery(c.Context(), marketing.Id, product.MarketingType, product, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||||
}
|
||||
|
||||
}
|
||||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), s.MarketingRepo.DB(), pwIDs); err != nil {
|
||||
return err
|
||||
@@ -207,6 +234,23 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validasi semua product harus punya marketing_type yang sama
|
||||
if len(req.MarketingProducts) > 0 {
|
||||
firstMarketingType := req.MarketingProducts[0].MarketingType
|
||||
if !utils.IsValidMarketingType(firstMarketingType) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Tipe penjualan tidak valid")
|
||||
}
|
||||
|
||||
for i, item := range req.MarketingProducts {
|
||||
if !utils.IsValidMarketingType(item.MarketingType) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tipe penjualan tidak valid pada produk ke-%d", i+1))
|
||||
}
|
||||
if item.MarketingType != firstMarketingType {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Semua produk harus memiliki tipe penjualan yang sama")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -234,6 +278,12 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
|
||||
if len(req.MarketingProducts) > 0 {
|
||||
for _, item := range req.MarketingProducts {
|
||||
if item.MarketingType != string(utils.MarketingTypeTrading) && item.AvgWeight == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Berat rata-rata harus diisi")
|
||||
}
|
||||
if item.ConvertionUnit != nil && !utils.IsValidConvertionUnit(*item.ConvertionUnit) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Unit konversi tidak valid")
|
||||
}
|
||||
if err := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -281,6 +331,9 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
if req.Notes != "" {
|
||||
updateBody["notes"] = req.Notes
|
||||
}
|
||||
if len(req.MarketingProducts) > 0 {
|
||||
updateBody["marketing_type"] = req.MarketingProducts[0].MarketingType
|
||||
}
|
||||
|
||||
if len(updateBody) > 0 {
|
||||
if err := marketingRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
@@ -309,38 +362,12 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
for _, rp := range req.MarketingProducts {
|
||||
if old, ok := oldByPW[rp.ProductWarehouseId]; ok {
|
||||
|
||||
// Get product untuk cek flag PAKAN atau OVK
|
||||
productWarehouse, err := s.ProductWarehouseRepo.GetByID(c.Context(), rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Product.Flags")
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cek apakah product punya flag PAKAN atau OVK
|
||||
isPakanOrOVK := false
|
||||
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
|
||||
for _, flag := range productWarehouse.Product.Flags {
|
||||
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||
isPakanOrOVK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalWeight := rp.Qty * rp.AvgWeight
|
||||
var totalPrice float64
|
||||
if isPakanOrOVK {
|
||||
totalPrice = rp.Qty * rp.UnitPrice
|
||||
} else {
|
||||
totalPrice = totalWeight * rp.UnitPrice
|
||||
}
|
||||
totalWeight, totalPrice := s.calculatePriceByMarketingType(rp.MarketingType, rp.Qty, rp.AvgWeight, rp.UnitPrice, rp.Week)
|
||||
|
||||
deliveryProduct, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check delivery product")
|
||||
}
|
||||
|
||||
if err == nil && deliveryProduct.Id != 0 {
|
||||
oldQty := old.Qty
|
||||
newQty := rp.Qty
|
||||
@@ -363,12 +390,15 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
}
|
||||
|
||||
updateBody := map[string]any{
|
||||
"product_warehouse_id": rp.ProductWarehouseId,
|
||||
"qty": rp.Qty,
|
||||
"unit_price": rp.UnitPrice,
|
||||
"avg_weight": rp.AvgWeight,
|
||||
"total_weight": totalWeight,
|
||||
"total_price": totalPrice,
|
||||
"product_warehouse_id": rp.ProductWarehouseId,
|
||||
"qty": rp.Qty,
|
||||
"unit_price": rp.UnitPrice,
|
||||
"avg_weight": rp.AvgWeight,
|
||||
"total_weight": totalWeight,
|
||||
"total_price": totalPrice,
|
||||
"convertion_unit": rp.ConvertionUnit,
|
||||
"weight_per_convertion": rp.WeightPerConvertion,
|
||||
"week": rp.Week,
|
||||
}
|
||||
if err := marketingProductRepoTx.PatchOne(c.Context(), old.Id, updateBody, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update marketing product")
|
||||
@@ -391,7 +421,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := s.createMarketingProductWithDelivery(c.Context(), id, rp, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||
if err := s.createMarketingProductWithDelivery(c.Context(), id, rp.MarketingType, rp, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||||
}
|
||||
}
|
||||
@@ -399,7 +429,6 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
|
||||
for _, old := range oldProducts {
|
||||
if _, ok := reqByPW[old.ProductWarehouseId]; !ok {
|
||||
|
||||
deliveryProduct, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing delivery product")
|
||||
@@ -682,45 +711,21 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error {
|
||||
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, marketingType string, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error {
|
||||
|
||||
// Get product untuk cek flag PAKAN atau OVK
|
||||
productWarehouse, err := s.ProductWarehouseRepo.GetByID(ctx, rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Product.Flags")
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cek apakah product punya flag PAKAN atau OVK
|
||||
isPakanOrOVK := false
|
||||
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
|
||||
for _, flag := range productWarehouse.Product.Flags {
|
||||
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||
isPakanOrOVK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalWeight := rp.Qty * rp.AvgWeight
|
||||
var totalPrice float64
|
||||
if isPakanOrOVK {
|
||||
// PAKAN atau OVK: qty × unit_price
|
||||
totalPrice = rp.Qty * rp.UnitPrice
|
||||
} else {
|
||||
// Produk lain: total_weight × unit_price
|
||||
totalPrice = totalWeight * rp.UnitPrice
|
||||
}
|
||||
totalWeight, totalPrice := s.calculatePriceByMarketingType(marketingType, rp.Qty, rp.AvgWeight, rp.UnitPrice, rp.Week)
|
||||
|
||||
marketingProduct := &entity.MarketingProduct{
|
||||
MarketingId: marketingId,
|
||||
ProductWarehouseId: rp.ProductWarehouseId,
|
||||
Qty: rp.Qty,
|
||||
UnitPrice: rp.UnitPrice,
|
||||
AvgWeight: rp.AvgWeight,
|
||||
TotalWeight: totalWeight,
|
||||
TotalPrice: totalPrice,
|
||||
MarketingId: marketingId,
|
||||
ProductWarehouseId: rp.ProductWarehouseId,
|
||||
Qty: rp.Qty,
|
||||
UnitPrice: rp.UnitPrice,
|
||||
AvgWeight: rp.AvgWeight,
|
||||
TotalWeight: totalWeight,
|
||||
TotalPrice: totalPrice,
|
||||
ConvertionUnit: rp.ConvertionUnit,
|
||||
WeightPerConvertion: rp.WeightPerConvertion,
|
||||
Week: rp.Week,
|
||||
}
|
||||
if err := marketingProductRepo.CreateOne(ctx, marketingProduct, nil); err != nil {
|
||||
return err
|
||||
@@ -744,3 +749,17 @@ func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Cont
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) {
|
||||
if marketingType == string(utils.MarketingTypeTrading) {
|
||||
totalWeight = 0
|
||||
totalPrice = math.Round(qty*unitPrice*100) / 100
|
||||
} else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 {
|
||||
totalWeight = math.Round(qty*avgWeight*100) / 100
|
||||
totalPrice = math.Round(unitPrice*float64(*week)*qty*100) / 100
|
||||
} else {
|
||||
totalWeight = math.Round(qty*avgWeight*100) / 100
|
||||
totalPrice = math.Round(totalWeight*unitPrice*100) / 100
|
||||
}
|
||||
return totalWeight, totalPrice
|
||||
}
|
||||
|
||||
@@ -9,11 +9,15 @@ type Create struct {
|
||||
}
|
||||
|
||||
type CreateMarketingProduct struct {
|
||||
VehicleNumber string `json:"vehicle_number" validate:"required,min=1,max=50"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,gt=0"`
|
||||
UnitPrice float64 `json:"unit_price" validate:"required,gt=0"`
|
||||
Qty float64 `json:"qty" validate:"required,gt=0"`
|
||||
AvgWeight float64 `json:"avg_weight" validate:"required,gt=0"`
|
||||
MarketingType string `json:"marketing_type" validate:"required,min=1,max=50"`
|
||||
VehicleNumber string `json:"vehicle_number" validate:"required,min=1,max=50"`
|
||||
ConvertionUnit *string `json:"convertion_unit" validate:"omitempty,min=1,max=20"`
|
||||
WeightPerConvertion *float64 `json:"weight_per_convertion" validate:"omitempty,gt=0"`
|
||||
Week *int `json:"week" validate:"omitempty,gt=0"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,gt=0"`
|
||||
UnitPrice float64 `json:"unit_price" validate:"required,gt=0"`
|
||||
Qty float64 `json:"qty" validate:"required,gt=0"`
|
||||
AvgWeight float64 `json:"avg_weight" validate:"omitempty,gt=0"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
areaRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
fcrRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||
flockRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||
kandangRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||
userRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
// === DTO Structs (ordered) ===
|
||||
@@ -40,7 +40,7 @@ type ProjectFlockDTO struct {
|
||||
Category string `json:"category"`
|
||||
Flock *flockRelationDTO.FlockRelationDTO `json:"flock"`
|
||||
Area *areaRelationDTO.AreaRelationDTO `json:"area"`
|
||||
Fcr *fcrRelationDTO.FcrRelationDTO `json:"fcr"`
|
||||
StandardFcr *float64 `json:"standard_fcr"`
|
||||
Location *locationRelationDTO.LocationRelationDTO `json:"location"`
|
||||
}
|
||||
|
||||
@@ -97,10 +97,6 @@ func ToAreaDTO(e entity.Area) areaRelationDTO.AreaRelationDTO {
|
||||
return areaRelationDTO.ToAreaRelationDTO(e)
|
||||
}
|
||||
|
||||
func ToFcrDTO(e entity.Fcr) fcrRelationDTO.FcrRelationDTO {
|
||||
return fcrRelationDTO.ToFcrRelationDTO(e)
|
||||
}
|
||||
|
||||
func ToLocationDTO(e entity.Location) locationRelationDTO.LocationRelationDTO {
|
||||
return locationRelationDTO.ToLocationRelationDTO(e)
|
||||
}
|
||||
@@ -121,11 +117,6 @@ func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
||||
mapped := areaRelationDTO.ToAreaRelationDTO(e.Area)
|
||||
area = &mapped
|
||||
}
|
||||
var fcr *fcrRelationDTO.FcrRelationDTO
|
||||
if e.Fcr.Id != 0 {
|
||||
mapped := fcrRelationDTO.ToFcrRelationDTO(e.Fcr)
|
||||
fcr = &mapped
|
||||
}
|
||||
var location *locationRelationDTO.LocationRelationDTO
|
||||
if e.Location.Id != 0 {
|
||||
mapped := locationRelationDTO.ToLocationRelationDTO(e.Location)
|
||||
@@ -137,7 +128,7 @@ func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
||||
Category: e.Category,
|
||||
Flock: flock,
|
||||
Area: area,
|
||||
Fcr: fcr,
|
||||
StandardFcr: resolveProjectFlockStandardFcr(e),
|
||||
Location: location,
|
||||
}
|
||||
}
|
||||
@@ -222,6 +213,22 @@ func ToChickinListDTOs(e []entity.ProjectChickin) []ChickinListDTO {
|
||||
return result
|
||||
}
|
||||
|
||||
func resolveProjectFlockStandardFcr(e entity.ProjectFlock) *float64 {
|
||||
if e.ProductionStandard.Id == 0 || len(e.ProductionStandard.ProductionStandardDetails) == 0 {
|
||||
return nil
|
||||
}
|
||||
week := 1
|
||||
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||
week = 18
|
||||
}
|
||||
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
||||
if detail.Week == week && detail.StandardFCR != nil {
|
||||
return detail.StandardFCR
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO {
|
||||
result := make([]ChickinSimpleDTO, len(e))
|
||||
for i, r := range e {
|
||||
|
||||
@@ -81,7 +81,7 @@ func (s chickinService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("ProjectFlockKandang.Kandang.Pic").
|
||||
Preload("ProjectFlockKandang.ProjectFlock").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.Area").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.Fcr").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard.ProductionStandardDetails").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.Location").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.Location.Area")
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||
productWarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
|
||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
@@ -31,7 +30,7 @@ type ProjectFlockDTO struct {
|
||||
projectFlockDTO.ProjectFlockRelationDTO
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
StandardFcr *float64 `json:"standard_fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||
@@ -86,7 +85,7 @@ func toProjectFlockDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockDTO
|
||||
ProjectFlockRelationDTO: pf.ProjectFlockRelationDTO,
|
||||
Area: pf.Area,
|
||||
Category: pf.Category,
|
||||
Fcr: pf.Fcr,
|
||||
StandardFcr: pf.StandardFcr,
|
||||
ProductionStandard: pf.ProductionStandard,
|
||||
Location: pf.Location,
|
||||
CreatedUser: pf.CreatedUser,
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto"
|
||||
@@ -28,7 +27,7 @@ type ProjectFlockListDTO struct {
|
||||
ProjectFlockRelationDTO
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
StandardFcr *float64 `json:"standard_fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"`
|
||||
@@ -99,12 +98,6 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
|
||||
areaSummary = &mapped
|
||||
}
|
||||
|
||||
var fcrSummary *fcrDTO.FcrRelationDTO
|
||||
if e.Fcr.Id != 0 {
|
||||
mapped := fcrDTO.ToFcrRelationDTO(e.Fcr)
|
||||
fcrSummary = &mapped
|
||||
}
|
||||
|
||||
var productionStandardSummary *productionStandardDTO.ProductionStandardRelationDTO
|
||||
if e.ProductionStandard.Id != 0 {
|
||||
mapped := productionStandardDTO.ToProductionStandardRelationDTO(e.ProductionStandard)
|
||||
@@ -129,7 +122,7 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
|
||||
Kandangs: kandangSummaries,
|
||||
ProjectBudgets: ToProjectBudgetDTOs(e.Budgets),
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
StandardFcr: resolveProjectFlockStandardFcr(e),
|
||||
ProductionStandard: productionStandardSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
@@ -204,6 +197,22 @@ func createProjectFlockRelationDTO(e entity.ProjectFlock, period int) ProjectFlo
|
||||
}
|
||||
}
|
||||
|
||||
func resolveProjectFlockStandardFcr(e entity.ProjectFlock) *float64 {
|
||||
if e.ProductionStandard.Id == 0 || len(e.ProductionStandard.ProductionStandardDetails) == 0 {
|
||||
return nil
|
||||
}
|
||||
week := 1
|
||||
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||
week = 18
|
||||
}
|
||||
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
||||
if detail.Week == week && detail.StandardFCR != nil {
|
||||
return detail.StandardFCR
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToProjectBudgetDTO(e entity.ProjectBudget) ProjectBudgetDTO {
|
||||
var nonstockRef *nonstockDTO.NonstockRelationDTO
|
||||
if e.Nonstock != nil && e.Nonstock.Id != 0 {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
@@ -22,7 +21,7 @@ type ProjectFlockWithPivotDTO struct {
|
||||
ProjectFlockRelationDTO
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
StandardFcr *float64 `json:"standard_fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
ProductionStandardId uint `json:"production_standard_id"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
@@ -67,10 +66,6 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
||||
mapped := areaDTO.ToAreaRelationDTO(e.ProjectFlock.Area)
|
||||
pfLocal.Area = &mapped
|
||||
}
|
||||
if e.ProjectFlock.Fcr.Id != 0 {
|
||||
mapped := fcrDTO.ToFcrRelationDTO(e.ProjectFlock.Fcr)
|
||||
pfLocal.Fcr = &mapped
|
||||
}
|
||||
if e.ProjectFlock.ProductionStandard.Id != 0 {
|
||||
mapped := productionStandardDTO.ToProductionStandardRelationDTO(e.ProjectFlock.ProductionStandard)
|
||||
pfLocal.ProductionStandard = &mapped
|
||||
@@ -83,6 +78,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
||||
mapped := userDTO.ToUserRelationDTO(e.ProjectFlock.CreatedUser)
|
||||
pfLocal.CreatedUser = &mapped
|
||||
}
|
||||
pfLocal.StandardFcr = resolveProjectFlockStandardFcr(e.ProjectFlock)
|
||||
|
||||
for _, k := range e.ProjectFlock.Kandangs {
|
||||
kb := kandangDTO.ToKandangRelationDTO(k)
|
||||
|
||||
@@ -23,7 +23,6 @@ type ProjectflockRepository interface {
|
||||
GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
AreaExists(ctx context.Context, id uint) (bool, error)
|
||||
FcrExists(ctx context.Context, id uint) (bool, error)
|
||||
ProductionStandardExists(ctx context.Context, id uint) (bool, error)
|
||||
LocationExists(ctx context.Context, id uint) (bool, error)
|
||||
}
|
||||
@@ -67,8 +66,8 @@ func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm
|
||||
return db.
|
||||
Preload("CreatedUser").
|
||||
Preload("Area").
|
||||
Preload("Fcr").
|
||||
Preload("ProductionStandard").
|
||||
Preload("ProductionStandard.ProductionStandardDetails").
|
||||
Preload("Location").
|
||||
Preload("Kandangs").
|
||||
Preload("KandangHistory").
|
||||
@@ -134,14 +133,12 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
||||
likeQuery := "%" + normalized + "%"
|
||||
return db.
|
||||
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
|
||||
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
|
||||
Joins("LEFT JOIN production_standards ON production_standards.id = project_flocks.production_standard_id").
|
||||
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
|
||||
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
|
||||
Where(`
|
||||
LOWER(areas.name) LIKE ?
|
||||
OR LOWER(project_flocks.category) LIKE ?
|
||||
OR LOWER(fcrs.name) LIKE ?
|
||||
OR LOWER(production_standards.name) LIKE ?
|
||||
OR LOWER(locations.name) LIKE ?
|
||||
OR LOWER(locations.address) LIKE ?
|
||||
@@ -172,7 +169,6 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -184,10 +180,6 @@ func (r *ProjectflockRepositoryImpl) AreaExists(ctx context.Context, id uint) (b
|
||||
return repository.Exists[entity.Area](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *ProjectflockRepositoryImpl) FcrExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.Fcr](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *ProjectflockRepositoryImpl) ProductionStandardExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.ProductionStandard](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
+4
-4
@@ -117,10 +117,10 @@ func (r *projectFlockKandangRepositoryImpl) GetAllWithFilters(ctx context.Contex
|
||||
Joins("JOIN \"kandangs\" ON \"project_flock_kandangs\".\"kandang_id\" = \"kandangs\".\"id\"").
|
||||
Joins("JOIN \"project_flocks\" ON \"project_flock_kandangs\".\"project_flock_id\" = \"project_flocks\".\"id\"").
|
||||
Preload("ProjectFlock").
|
||||
Preload("ProjectFlock.Fcr").
|
||||
Preload("ProjectFlock.Area").
|
||||
Preload("ProjectFlock.Location").
|
||||
Preload("ProjectFlock.CreatedUser").
|
||||
Preload("ProjectFlock.ProductionStandard.ProductionStandardDetails").
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
@@ -208,10 +208,10 @@ func (r *projectFlockKandangRepositoryImpl) GetAllWithFiltersScoped(ctx context.
|
||||
Joins("JOIN \"kandangs\" ON \"project_flock_kandangs\".\"kandang_id\" = \"kandangs\".\"id\"").
|
||||
Joins("JOIN \"project_flocks\" ON \"project_flock_kandangs\".\"project_flock_id\" = \"project_flocks\".\"id\"").
|
||||
Preload("ProjectFlock").
|
||||
Preload("ProjectFlock.Fcr").
|
||||
Preload("ProjectFlock.Area").
|
||||
Preload("ProjectFlock.Location").
|
||||
Preload("ProjectFlock.CreatedUser").
|
||||
Preload("ProjectFlock.ProductionStandard.ProductionStandardDetails").
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
@@ -324,10 +324,10 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint
|
||||
record := new(entity.ProjectFlockKandang)
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("ProjectFlock").
|
||||
Preload("ProjectFlock.Fcr").
|
||||
Preload("ProjectFlock.Area").
|
||||
Preload("ProjectFlock.Location").
|
||||
Preload("ProjectFlock.CreatedUser").
|
||||
Preload("ProjectFlock.ProductionStandard.ProductionStandardDetails").
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
@@ -347,10 +347,10 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
||||
Preload("ProjectFlock").
|
||||
Preload("ProjectFlock.Fcr").
|
||||
Preload("ProjectFlock.Area").
|
||||
Preload("ProjectFlock.Location").
|
||||
Preload("ProjectFlock.CreatedUser").
|
||||
Preload("ProjectFlock.ProductionStandard.ProductionStandardDetails").
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
|
||||
@@ -282,7 +282,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: s.Repository.FcrExists},
|
||||
commonSvc.RelationCheck{Name: "Production Standard", ID: &req.ProductionStandardId, Exists: s.Repository.ProductionStandardExists},
|
||||
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists},
|
||||
); err != nil {
|
||||
@@ -334,7 +333,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
createBody := &entity.ProjectFlock{
|
||||
AreaId: req.AreaId,
|
||||
Category: cat,
|
||||
FcrId: req.FcrId,
|
||||
ProductionStandardId: req.ProductionStandardId,
|
||||
LocationId: req.LocationId,
|
||||
CreatedBy: actorID,
|
||||
|
||||
@@ -4,7 +4,6 @@ type Create struct {
|
||||
FlockName string `json:"flock_name" validate:"required_strict"`
|
||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||
Category string `json:"category" validate:"required_strict"`
|
||||
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
||||
ProductionStandardId uint `json:"production_standard_id" validate:"required_strict,number,gt=0"`
|
||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
|
||||
|
||||
@@ -280,10 +280,10 @@ func toRecordingProjectFlockDTO(e entity.Recording) RecordingProjectFlockDTO {
|
||||
}
|
||||
}
|
||||
|
||||
if pfk.ProjectFlock.Fcr.Id != 0 || e.StandardFcr != nil {
|
||||
if pfk.ProjectFlock.ProductionStandard.Id != 0 || e.StandardFcr != nil {
|
||||
result.Fcr = &RecordingFcrDTO{
|
||||
Id: pfk.ProjectFlock.Fcr.Id,
|
||||
Name: pfk.ProjectFlock.Fcr.Name,
|
||||
Id: pfk.ProjectFlock.ProductionStandard.Id,
|
||||
Name: pfk.ProjectFlock.ProductionStandard.Name,
|
||||
FcrStd: floatValue(e.StandardFcr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ type RecordingRepository interface {
|
||||
GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error)
|
||||
GetEggSummaryByRecording(tx *gorm.DB, recordingID uint) (totalQty float64, totalWeightGrams float64, err error)
|
||||
GetCumulativeEggQtyByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint, recordTime time.Time) (float64, error)
|
||||
GetFcrStandardNumber(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error)
|
||||
GetTotalWeightProducedFromUniformityByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error)
|
||||
GetTotalWeightProducedFromUniformityByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error)
|
||||
@@ -92,7 +91,7 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("ProjectFlockKandang.Kandang.Location").
|
||||
Preload("ProjectFlockKandang.ProjectFlock").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard").
|
||||
Preload("ProjectFlockKandang.ProjectFlock.Fcr").
|
||||
// Preload("ProjectFlockKandang.ProjectFlock.Fcr").
|
||||
Preload("Depletions").
|
||||
Preload("Depletions.ProductWarehouse").
|
||||
Preload("Depletions.ProductWarehouse.Product").
|
||||
@@ -448,34 +447,6 @@ func (r *RecordingRepositoryImpl) GetCumulativeEggQtyByProjectFlockKandang(
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetFcrStandardNumber(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error) {
|
||||
if fcrId == 0 || currentWeightKg <= 0 {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
var standard entity.FcrStandard
|
||||
err := tx.
|
||||
Where("fcr_id = ? AND weight >= ?", fcrId, currentWeightKg).
|
||||
Order("weight ASC").
|
||||
First(&standard).Error
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = tx.
|
||||
Where("fcr_id = ?", fcrId).
|
||||
Order("weight DESC").
|
||||
First(&standard).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, false, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
return standard.FcrNumber, true, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error) {
|
||||
// Body-weight tracking is removed; keep stub for report compatibility.
|
||||
return 0, 0, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
@@ -24,12 +25,17 @@ type PurchaseRelationDTO struct {
|
||||
|
||||
type PurchaseListDTO struct {
|
||||
PurchaseRelationDTO
|
||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||
RequesterName string `json:"requester_name"`
|
||||
PoExpedition []string `json:"po_expedition"`
|
||||
Products []productDTO.ProductRelationDTO `json:"products"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location"`
|
||||
Area *areaDTO.AreaRelationDTO `json:"area"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||
}
|
||||
|
||||
type PurchaseDetailDTO struct {
|
||||
@@ -146,6 +152,10 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
||||
mapped := userDTO.ToUserRelationDTO(p.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
requesterName := ""
|
||||
if createdUser != nil {
|
||||
requesterName = createdUser.Name
|
||||
}
|
||||
|
||||
var latestApproval *approvalDTO.ApprovalRelationDTO
|
||||
if p.LatestApproval != nil && p.LatestApproval.Id != 0 {
|
||||
@@ -153,11 +163,53 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
||||
latestApproval = &mapped
|
||||
}
|
||||
|
||||
var (
|
||||
poExpedition []string
|
||||
location *locationDTO.LocationRelationDTO
|
||||
area *areaDTO.AreaRelationDTO
|
||||
)
|
||||
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
||||
expeditionRefSet := make(map[string]struct{})
|
||||
for i := range p.Items {
|
||||
item := p.Items[i]
|
||||
if item.Product != nil && item.Product.Id != 0 {
|
||||
if _, exists := productMap[item.Product.Id]; !exists {
|
||||
productMap[item.Product.Id] = productDTO.ToProductRelationDTO(*item.Product)
|
||||
}
|
||||
}
|
||||
if item.ExpenseNonstock != nil && item.ExpenseNonstock.Expense != nil {
|
||||
ref := strings.TrimSpace(item.ExpenseNonstock.Expense.ReferenceNumber)
|
||||
if ref != "" {
|
||||
if _, exists := expeditionRefSet[ref]; !exists {
|
||||
expeditionRefSet[ref] = struct{}{}
|
||||
poExpedition = append(poExpedition, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
if location == nil && item.Warehouse != nil && item.Warehouse.Location != nil && item.Warehouse.Location.Id != 0 {
|
||||
loc := locationDTO.ToLocationRelationDTO(*item.Warehouse.Location)
|
||||
location = &loc
|
||||
}
|
||||
if area == nil && item.Warehouse != nil && item.Warehouse.Area.Id != 0 {
|
||||
ar := areaDTO.ToAreaRelationDTO(item.Warehouse.Area)
|
||||
area = &ar
|
||||
}
|
||||
}
|
||||
products := make([]productDTO.ProductRelationDTO, 0, len(productMap))
|
||||
for _, prod := range productMap {
|
||||
products = append(products, prod)
|
||||
}
|
||||
|
||||
return PurchaseListDTO{
|
||||
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
||||
Supplier: supplier,
|
||||
DueDate: p.DueDate,
|
||||
CreatedUser: createdUser,
|
||||
RequesterName: requesterName,
|
||||
PoExpedition: poExpedition,
|
||||
Products: products,
|
||||
Location: location,
|
||||
Area: area,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
LatestApproval: latestApproval,
|
||||
|
||||
@@ -31,9 +31,25 @@ func (r *productionResultRepositoryImpl) GetRecordingsByProjectFlockKandang(
|
||||
return []entity.Recording{}, 0, nil
|
||||
}
|
||||
|
||||
latestApproval := r.db.WithContext(ctx).
|
||||
Table("approvals AS a").
|
||||
Select("a.approvable_id, a.action, a.step_number").
|
||||
Joins(`
|
||||
JOIN (
|
||||
SELECT approvable_id, MAX(action_at) AS latest_action_at
|
||||
FROM approvals
|
||||
WHERE approvable_type = ?
|
||||
GROUP BY approvable_id
|
||||
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
|
||||
string(utils.ApprovalWorkflowRecording),
|
||||
)
|
||||
|
||||
countQuery := r.db.WithContext(ctx).
|
||||
Model(&entity.Recording{}).
|
||||
Where("project_flock_kandangs_id = ?", projectFlockKandangID)
|
||||
Joins("JOIN (?) AS la ON la.approvable_id = recordings.id", latestApproval).
|
||||
Where("project_flock_kandangs_id = ?", projectFlockKandangID).
|
||||
Where("la.step_number = ?", utils.RecordingStepDisetujui).
|
||||
Where("la.action = ?", string(entity.ApprovalActionApproved))
|
||||
|
||||
var total int64
|
||||
if err := countQuery.Count(&total).Error; err != nil {
|
||||
@@ -59,7 +75,10 @@ func (r *productionResultRepositoryImpl) GetRecordingsByProjectFlockKandang(
|
||||
|
||||
dataQuery := r.db.WithContext(ctx).
|
||||
Model(&entity.Recording{}).
|
||||
Joins("JOIN (?) AS la ON la.approvable_id = recordings.id", latestApproval).
|
||||
Where("project_flock_kandangs_id = ?", projectFlockKandangID).
|
||||
Where("la.step_number = ?", utils.RecordingStepDisetujui).
|
||||
Where("la.action = ?", string(entity.ApprovalActionApproved)).
|
||||
Preload("Eggs", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Select("recording_eggs.*, f.name AS product_flag_name").
|
||||
Joins("LEFT JOIN product_warehouses pw ON pw.id = recording_eggs.product_warehouse_id").
|
||||
|
||||
@@ -398,6 +398,9 @@ func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.
|
||||
if detail != nil && detail.TargetMeanBw != nil {
|
||||
weeklyResults[i].StdBw = *detail.TargetMeanBw
|
||||
}
|
||||
if detail != nil {
|
||||
weeklyResults[i].DepStd = valueOrZero(detail.MaxDepletion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,10 +1355,12 @@ func buildDebtSupplierRow(purchase entity.Purchase, now time.Time, loc *time.Loc
|
||||
poNumber = *purchase.PoNumber
|
||||
}
|
||||
|
||||
prDate := purchase.CreatedAt.In(loc)
|
||||
startDate := time.Date(prDate.Year(), prDate.Month(), prDate.Day(), 0, 0, 0, 0, loc)
|
||||
startDate := resolveDebtSupplierReceivedDate(purchase, loc)
|
||||
endDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
aging := int(endDate.Sub(startDate).Hours() / 24)
|
||||
aging := 0
|
||||
if !startDate.IsZero() {
|
||||
aging = int(endDate.Sub(startDate).Hours() / 24)
|
||||
}
|
||||
|
||||
totalPrice := 0.0
|
||||
travelNumber := "-"
|
||||
@@ -1525,8 +1530,10 @@ func isDebtSupplierPaid(totalPrice, paymentTotal float64) bool {
|
||||
}
|
||||
|
||||
func calculateDebtSupplierAging(purchase entity.Purchase, endDate time.Time, loc *time.Location) int {
|
||||
prDate := purchase.CreatedAt.In(loc)
|
||||
startDate := time.Date(prDate.Year(), prDate.Month(), prDate.Day(), 0, 0, 0, 0, loc)
|
||||
startDate := resolveDebtSupplierReceivedDate(purchase, loc)
|
||||
if startDate.IsZero() {
|
||||
return 0
|
||||
}
|
||||
stopDate := time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 0, 0, 0, 0, loc)
|
||||
if stopDate.Before(startDate) {
|
||||
return 0
|
||||
@@ -1534,6 +1541,23 @@ func calculateDebtSupplierAging(purchase entity.Purchase, endDate time.Time, loc
|
||||
return int(stopDate.Sub(startDate).Hours() / 24)
|
||||
}
|
||||
|
||||
func resolveDebtSupplierReceivedDate(purchase entity.Purchase, loc *time.Location) time.Time {
|
||||
earliest := time.Time{}
|
||||
for _, item := range purchase.Items {
|
||||
if item.ReceivedDate == nil || item.ReceivedDate.IsZero() {
|
||||
continue
|
||||
}
|
||||
received := item.ReceivedDate.In(loc)
|
||||
if earliest.IsZero() || received.Before(earliest) {
|
||||
earliest = received
|
||||
}
|
||||
}
|
||||
if earliest.IsZero() {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Date(earliest.Year(), earliest.Month(), earliest.Day(), 0, 0, 0, 0, loc)
|
||||
}
|
||||
|
||||
func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error) {
|
||||
params, filters, err := s.parseHppPerKandangQuery(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -212,6 +212,31 @@ const (
|
||||
KandangStatusActive KandangStatus = "ACTIVE"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Marketing Type
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type MarketingType string
|
||||
|
||||
const (
|
||||
MarketingTypeAyam MarketingType = "AYAM"
|
||||
MarketingTypeTelur MarketingType = "TELUR"
|
||||
MarketingTypeTrading MarketingType = "TRADING"
|
||||
MarketingTypeAyamPullet MarketingType = "AYAM_PULLET"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Convertion Unit
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type ConvertionUnit string
|
||||
|
||||
const (
|
||||
ConvertionUnitPeti ConvertionUnit = "PETI"
|
||||
ConvertionUnitKG ConvertionUnit = "KG"
|
||||
ConvertionUnitQty ConvertionUnit = "QTY"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ProjectFlockCategory
|
||||
// -------------------------------------------------------------------
|
||||
@@ -329,9 +354,10 @@ const (
|
||||
PurchaseStepReceiving approvalutils.ApprovalStep = 4
|
||||
PurchaseStepCompleted approvalutils.ApprovalStep = 5
|
||||
|
||||
PurchasePRNumberPrefix = "PR-LTI-"
|
||||
PurchasePONumberPrefix = "PO-LTI-"
|
||||
PurchaseNumberPadding = 4
|
||||
PurchasePRNumberPrefix = "PR-LTI-"
|
||||
PurchasePONumberPrefix = "PO-LTI-"
|
||||
AdjustmentStockNumberPrefix = "ADJ-"
|
||||
PurchaseNumberPadding = 4
|
||||
)
|
||||
|
||||
var PurchaseApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
@@ -609,6 +635,22 @@ func IsValidPaymentParty(v string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidMarketingType(v string) bool {
|
||||
switch MarketingType(v) {
|
||||
case MarketingTypeAyam, MarketingTypeTelur, MarketingTypeTrading, MarketingTypeAyamPullet:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidConvertionUnit(v string) bool {
|
||||
switch ConvertionUnit(v) {
|
||||
case ConvertionUnitPeti, ConvertionUnitKG, ConvertionUnitQty:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// example use
|
||||
|
||||
// Recording helper
|
||||
|
||||
Reference in New Issue
Block a user