From 635049163e396ac599014c2e1f6c40fadc9addd9 Mon Sep 17 00:00:00 2001 From: ragilap Date: Mon, 29 Dec 2025 23:15:34 +0700 Subject: [PATCH 1/4] feat(BE-74): add production standart to project_flock and implement rbac finance and standart production --- .DS_Store | Bin 6148 -> 6148 bytes ..._standart_production_projectflock.down.sql | 7 +++++ ...nt_standart_production_projectflock.up.sql | 15 +++++++++++ internal/database/seed/seeder.go | 4 +-- internal/entities/projectflock.go | 2 ++ internal/middleware/auth.go | 11 ++++---- internal/middleware/permissions.go | 24 ++++++++++++++++++ internal/modules/finance/initials/route.go | 10 ++++---- internal/modules/finance/injections/route.go | 10 ++++---- internal/modules/finance/payments/route.go | 10 ++++---- .../modules/finance/transactions/route.go | 10 ++++---- .../production-standard.controller.go | 2 +- .../dto/production-standard.dto.go | 22 +++++++++++----- .../master/production-standards/route.go | 10 ++++---- .../dto/project_flock_kandang.dto.go | 3 +++ .../project_flocks/dto/projectflock.dto.go | 9 +++++++ .../dto/projectflock_kandang.dto.go | 6 +++++ .../repositories/projectflock.repository.go | 9 +++++++ .../services/projectflock.service.go | 2 ++ .../validations/projectflock.validation.go | 1 + 20 files changed, 126 insertions(+), 41 deletions(-) create mode 100644 internal/database/migrations/20251229125951_adjustment_standart_production_projectflock.down.sql create mode 100644 internal/database/migrations/20251229125951_adjustment_standart_production_projectflock.up.sql diff --git a/.DS_Store b/.DS_Store index 4c14efd89e4d913a63e6242a245ab626c5fffe6d..e39247fdff6549a6304ce8065c332c38da11c1a4 100644 GIT binary patch delta 31 ncmZoMXfc@J&nU4mU^g?P#AF_p{LPzLLYOBuSZrqJ_{$Ffpo$73 delta 70 zcmZoMXfc@J&nUSuU^g?P Date: Tue, 30 Dec 2025 10:27:12 +0700 Subject: [PATCH 2/4] feat(BE): add standard_fcr column to production_standard_details and update related services and validations --- ...oduction_standards_add_fcr_column.down.sql | 3 ++ ...production_standards_add_fcr_column.up.sql | 3 ++ internal/database/seed/seeder.go | 32 +++++++++++++------ .../entities/production_standard_detail.go | 1 + .../dto/production-standard.dto.go | 3 ++ .../services/production-standard.service.go | 2 ++ .../production-standard.validation.go | 1 + 7 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.down.sql create mode 100644 internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.up.sql diff --git a/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.down.sql b/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.down.sql new file mode 100644 index 00000000..b686a59a --- /dev/null +++ b/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.down.sql @@ -0,0 +1,3 @@ +-- Remove standard_fcr column from production_standard_details table +ALTER TABLE production_standard_details +DROP COLUMN IF EXISTS standard_fcr; diff --git a/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.up.sql b/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.up.sql new file mode 100644 index 00000000..560a24ac --- /dev/null +++ b/internal/database/migrations/20251230014159_alter_table_production_standards_add_fcr_column.up.sql @@ -0,0 +1,3 @@ +-- Add standard_fcr column to production_standard_details table +ALTER TABLE production_standard_details +ADD COLUMN standard_fcr NUMERIC(15, 3); diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index bb4090bb..26c3f6e8 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -891,14 +891,14 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error { WarehouseName string Quantity float64 }{ - {ProductName: "DOC Broiler", WarehouseName: "Gudang Priangan", Quantity: 100}, - {ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Singaparna", Quantity: 200}, - {ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Banten", Quantity: 300}, - {ProductName: "DOC Broiler", WarehouseName: "Gudang Singaparna 1", Quantity: 5000}, - {ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Singaparna 1", Quantity: 600}, - {ProductName: "Telur Pecah", WarehouseName: "Gudang Singaparna 1", Quantity: 80}, - {ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Cikaum 1", Quantity: 450}, - {ProductName: "Telur Pecah", WarehouseName: "Gudang Cikaum 1", Quantity: 60}, + {ProductName: "DOC Broiler", WarehouseName: "Gudang Priangan", Quantity: 0}, + {ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Singaparna", Quantity: 0}, + {ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Banten", Quantity: 0}, + {ProductName: "DOC Broiler", WarehouseName: "Gudang Singaparna 1", Quantity: 0}, + {ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Singaparna 1", Quantity: 0}, + {ProductName: "Telur Pecah", WarehouseName: "Gudang Singaparna 1", Quantity: 0}, + {ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Cikaum 1", Quantity: 0}, + {ProductName: "Telur Pecah", WarehouseName: "Gudang Cikaum 1", Quantity: 0}, } for _, seed := range seeds { @@ -962,12 +962,24 @@ func seedTransferStock(tx *gorm.DB) error { { StockTransferId: transfer.Id, ProductId: 1, - Quantity: 10, + + SourceProductWarehouseID: func() *uint64 { id := uint64(1); return &id }(), + DestProductWarehouseID: func() *uint64 { id := uint64(2); return &id }(), + UsageQty: 10, + PendingQty: 0, + TotalQty: 10, + TotalUsed: 0, }, { StockTransferId: transfer.Id, ProductId: 2, - Quantity: 5, + + SourceProductWarehouseID: func() *uint64 { id := uint64(1); return &id }(), + DestProductWarehouseID: func() *uint64 { id := uint64(2); return &id }(), + UsageQty: 5, + PendingQty: 0, + TotalQty: 5, + TotalUsed: 0, }, } for i := range details { diff --git a/internal/entities/production_standard_detail.go b/internal/entities/production_standard_detail.go index cd50a572..1a18c8b8 100644 --- a/internal/entities/production_standard_detail.go +++ b/internal/entities/production_standard_detail.go @@ -12,6 +12,7 @@ type ProductionStandardDetail struct { TargetHenHouseProduction *float64 `gorm:"type:numeric(15,3)"` TargetEggWeight *float64 `gorm:"type:numeric(15,3)"` TargetEggMass *float64 `gorm:"type:numeric(15,3)"` + StandardFCR *float64 `gorm:"type:numeric(15,3)"` CreatedAt time.Time `gorm:"type:timestamptz;not null"` UpdatedAt time.Time `gorm:"type:timestamptz;not null"` diff --git a/internal/modules/master/production-standards/dto/production-standard.dto.go b/internal/modules/master/production-standards/dto/production-standard.dto.go index 9544732a..d683257f 100644 --- a/internal/modules/master/production-standards/dto/production-standard.dto.go +++ b/internal/modules/master/production-standards/dto/production-standard.dto.go @@ -33,6 +33,7 @@ type EggProductionStandardDetailDTO struct { TargetHenHouseProduction *float64 `json:"target_hen_house_production"` TargetEggWeight *float64 `json:"target_egg_weight"` TargetEggMass *float64 `json:"target_egg_mass"` + StandardFCR *float64 `json:"standard_fcr"` } type WeeklyProductionStandardDTO struct { @@ -87,6 +88,7 @@ func ToWeeklyProductionStandardDTOWithDetails(growth entity.StandardGrowthDetail TargetHenHouseProduction: detail.TargetHenHouseProduction, TargetEggWeight: detail.TargetEggWeight, TargetEggMass: detail.TargetEggMass, + StandardFCR: detail.StandardFCR, } return WeeklyProductionStandardDTO{ @@ -140,6 +142,7 @@ func ToEggProductionStandardDetailDTO(e entity.ProductionStandardDetail) EggProd TargetHenHouseProduction: e.TargetHenHouseProduction, TargetEggWeight: e.TargetEggWeight, TargetEggMass: e.TargetEggMass, + StandardFCR: e.StandardFCR, } } diff --git a/internal/modules/master/production-standards/services/production-standard.service.go b/internal/modules/master/production-standards/services/production-standard.service.go index b81faf8b..77c56299 100644 --- a/internal/modules/master/production-standards/services/production-standard.service.go +++ b/internal/modules/master/production-standards/services/production-standard.service.go @@ -142,6 +142,7 @@ func (s *productionStandardService) CreateOne(c *fiber.Ctx, req *validation.Crea TargetHenHouseProduction: detailReq.ProductionStandardDetails.TargetHenHouseProduction, TargetEggWeight: detailReq.ProductionStandardDetails.TargetEggWeight, TargetEggMass: detailReq.ProductionStandardDetails.TargetEggMass, + StandardFCR: detailReq.ProductionStandardDetails.StandardFCR, } if err := productionStandardDetailRepoTx.CreateOne(c.Context(), productionStandardDetail, nil); err != nil { @@ -255,6 +256,7 @@ func (s productionStandardService) UpdateOne(c *fiber.Ctx, req *validation.Updat TargetHenHouseProduction: detailReq.ProductionStandardDetails.TargetHenHouseProduction, TargetEggWeight: detailReq.ProductionStandardDetails.TargetEggWeight, TargetEggMass: detailReq.ProductionStandardDetails.TargetEggMass, + StandardFCR: detailReq.ProductionStandardDetails.StandardFCR, } if err := productionStandardDetailRepoTx.CreateOne(c.Context(), productionStandardDetail, nil); err != nil { diff --git a/internal/modules/master/production-standards/validations/production-standard.validation.go b/internal/modules/master/production-standards/validations/production-standard.validation.go index 51aeecc7..cdc321f8 100644 --- a/internal/modules/master/production-standards/validations/production-standard.validation.go +++ b/internal/modules/master/production-standards/validations/production-standard.validation.go @@ -5,6 +5,7 @@ type ProductionStandardDetailItem struct { TargetHenHouseProduction *float64 `json:"target_hen_house_production" validate:"omitempty,gte=0"` TargetEggWeight *float64 `json:"target_egg_weight" validate:"omitempty,gte=0"` TargetEggMass *float64 `json:"target_egg_mass" validate:"omitempty,gte=0"` + StandardFCR *float64 `json:"standard_fcr" validate:"omitempty,gte=0"` } type StandardGrowthDetailItem struct { From 0285852c42dfb3a6f5c4548df41aa32a0c78c81a Mon Sep 17 00:00:00 2001 From: MacBook Air M1 Date: Tue, 30 Dec 2025 14:42:53 +0700 Subject: [PATCH 3/4] fix api get all closing; fix get closing sapronak; fix get all maste data product --- internal/modules/closings/dto/closing.dto.go | 50 ++++---- .../repositories/closing.repository.go | 114 +++++++++++++++--- .../closings/services/closing.service.go | 15 ++- .../products/services/product.service.go | 1 + 4 files changed, 131 insertions(+), 49 deletions(-) diff --git a/internal/modules/closings/dto/closing.dto.go b/internal/modules/closings/dto/closing.dto.go index c05bd741..ac172c83 100644 --- a/internal/modules/closings/dto/closing.dto.go +++ b/internal/modules/closings/dto/closing.dto.go @@ -28,18 +28,19 @@ type ClosingDetailDTO struct { } type ClosingListItemDTO struct { - Id uint `json:"id"` - LocationID uint `json:"location_id"` - LocationName string `json:"location_name"` - ProjectCategory string `json:"project_category"` - Period int `json:"period"` - ClosingDate string `json:"closing_date"` - ShedLabel string `json:"shed_label"` - ShedCount int `json:"shed_count"` - SalesPaidAmount int64 `json:"sales_paid_amount"` - SalesRemainingAmount int64 `json:"sales_remaining_amount"` - SalesPaymentStatus string `json:"sales_payment_status"` - ProjectStatus string `json:"project_status"` + Id uint `json:"id"` + ProjectName string `json:"project_name"` + LocationID uint `json:"location_id"` + LocationName string `json:"location_name"` + ProjectCategory string `json:"project_category"` + Period int `json:"period"` + ClosingDate string `json:"closing_date"` + ShedLabel string `json:"shed_label"` + ShedCount int `json:"shed_count"` + // SalesPaidAmount int64 `json:"sales_paid_amount"` + // SalesRemainingAmount int64 `json:"sales_remaining_amount"` + // SalesPaymentStatus string `json:"sales_payment_status"` + ProjectStatus string `json:"project_status"` } type ClosingSummaryDTO struct { @@ -133,18 +134,19 @@ func ToClosingListItemDTO(project entity.ProjectFlock, projectStatus string) Clo shedCount := len(project.KandangHistory) return ClosingListItemDTO{ - Id: project.Id, - LocationID: project.LocationId, - LocationName: project.Location.Name, - ProjectCategory: project.Category, - Period: maxPeriod(project.KandangHistory), - ClosingDate: "17-Nov-2025", - ShedLabel: fmt.Sprintf("%d Kandang", shedCount), - ShedCount: shedCount, - SalesPaidAmount: 21993726, - SalesRemainingAmount: 11075919, - SalesPaymentStatus: "Lunas", - ProjectStatus: projectStatus, + Id: project.Id, + ProjectName: project.FlockName, + LocationID: project.LocationId, + LocationName: project.Location.Name, + ProjectCategory: project.Category, + Period: maxPeriod(project.KandangHistory), + ClosingDate: "17-Nov-2025", + ShedLabel: fmt.Sprintf("%d Kandang", shedCount), + ShedCount: shedCount, + // SalesPaidAmount: 21993726, + // SalesRemainingAmount: 11075919, + // SalesPaymentStatus: "Lunas", + ProjectStatus: projectStatus, } } diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index e3f09dda..912f2f25 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -330,13 +330,33 @@ SELECT COALESCE(p.po_number, '') AS reference_number, 'Purchase' AS transaction_type, prod.name AS product_name, - pc.name AS product_category, COALESCE(( - SELECT string_agg(f.name, ' ') + 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, - 'External Supplier' AS source_warehouse, + '-' AS source_warehouse, w.name AS destination_warehouse, '' AS destination, pi.total_qty AS quantity, @@ -345,7 +365,6 @@ SELECT FROM purchase_items pi JOIN purchases p ON p.id = pi.purchase_id JOIN products prod ON prod.id = pi.product_id -JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id JOIN warehouses w ON w.id = pi.warehouse_id WHERE pi.warehouse_id IN ? @@ -359,9 +378,29 @@ SELECT st.movement_number AS reference_number, 'Internal Transfer In' AS transaction_type, prod.name AS product_name, - pc.name AS product_category, COALESCE(( - SELECT string_agg(f.name, ' ') + 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, @@ -376,7 +415,6 @@ JOIN stock_transfers st ON st.id = std.stock_transfer_id LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id JOIN products prod ON prod.id = std.product_id -JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id WHERE st.to_warehouse_id IN ? ` @@ -389,9 +427,29 @@ SELECT st.movement_number AS reference_number, 'Internal Transfer Out' AS transaction_type, prod.name AS product_name, - pc.name AS product_category, COALESCE(( - SELECT string_agg(f.name, ' ') + 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, @@ -406,7 +464,6 @@ JOIN stock_transfers st ON st.id = std.stock_transfer_id LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id JOIN products prod ON prod.id = std.product_id -JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id WHERE st.from_warehouse_id IN ? ` @@ -419,9 +476,29 @@ SELECT m.so_number AS reference_number, 'Trading Sales' AS transaction_type, prod.name AS product_name, - pc.name AS product_category, COALESCE(( - SELECT string_agg(f.name, ' ') + 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, @@ -435,7 +512,6 @@ FROM marketing_products mp JOIN marketings m ON m.id = mp.marketing_id JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id JOIN products prod ON prod.id = pw.product_id -JOIN product_categories pc ON pc.id = prod.product_category_id JOIN uoms u ON u.id = prod.uom_id JOIN warehouses w ON w.id = pw.warehouse_id WHERE pw.project_flock_kandang_id IN ? @@ -808,12 +884,12 @@ func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kand } type ActualUsageCostRow struct { - ProductID uint `gorm:"column:product_id"` - ProductName string `gorm:"column:product_name"` - FlagName string `gorm:"column:flag_name"` - TotalQty float64 `gorm:"column:total_qty"` - TotalPrice float64 `gorm:"column:total_price"` - AveragePrice float64 `gorm:"column:average_price"` + ProductID uint `gorm:"column:product_id"` + ProductName string `gorm:"column:product_name"` + FlagName string `gorm:"column:flag_name"` + TotalQty float64 `gorm:"column:total_qty"` + TotalPrice float64 `gorm:"column:total_price"` + AveragePrice float64 `gorm:"column:average_price"` } func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error) { diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 9f643a78..47e30a7f 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -6,6 +6,7 @@ import ( "math" "strconv" "strings" + "time" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -332,18 +333,20 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID } var ( - minStep uint16 - statusProject string - completed int + minStep uint16 + statusProject string + completed int + latestActionAt time.Time ) for _, rec := range records { if minStep == 0 || rec.StepNumber < minStep { minStep = rec.StepNumber - statusProject = rec.StepName } - if rec.StepNumber == uint16(utils.ProjectFlockStepAktif) { - completed++ + + if latestActionAt.IsZero() || rec.ActionAt.After(latestActionAt) { + latestActionAt = rec.ActionAt + statusProject = rec.StepName } } diff --git a/internal/modules/master/products/services/product.service.go b/internal/modules/master/products/services/product.service.go index 35e24927..f40d92be 100644 --- a/internal/modules/master/products/services/product.service.go +++ b/internal/modules/master/products/services/product.service.go @@ -70,6 +70,7 @@ func (s productService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity products, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + db = db.Where("is_visible = ?", true) if params.Search != "" { return db.Where("name LIKE ?", "%"+params.Search+"%") } From 471fd1dbbf9a0f04e6ebeee183c79ee6fa24b2a6 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 16:30:44 +0700 Subject: [PATCH 4/4] feat(BE): enhance product warehouse handling and automatic calculations for delivery and sales orders --- .../services/adjustment.service.go | 40 +++++++++---------- .../product_warehouse.repository.go | 15 +++++++ .../transfers/services/transfer.service.go | 11 ++--- .../services/deliveryorder.service.go | 16 ++++++-- .../marketing/services/salesorder.service.go | 37 ++++++++++------- .../validations/deliveryorder.validation.go | 2 - .../validations/salesorder.validation.go | 2 - .../services/production-standard.service.go | 5 +-- .../validations/projectflock.validation.go | 16 ++++---- 9 files changed, 80 insertions(+), 64 deletions(-) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index d7b1641b..edf5f72b 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -117,39 +117,37 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e var createdLogId uint - isProductWarehouseExist, err := s.ProductWarehouseRepo.ProductWarehouseExistByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) - if err != nil { - s.Log.Errorf("Failed to check product warehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product warehouse") + var projectFlockKandangID *uint + pfk, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(req.WarehouseID)) + if err == nil && pfk != nil { + idCopy := uint(pfk.Id) + projectFlockKandangID = &idCopy } - if !isProductWarehouseExist { - projectFlockKandangID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID)) - if err != nil { - return nil, err + + pw, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk( + ctx, + uint(req.ProductID), + uint(req.WarehouseID), + projectFlockKandangID, + ) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + s.Log.Errorf("Failed to find product warehouse: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse") } + newPW := &entity.ProductWarehouse{ ProductId: uint(req.ProductID), WarehouseId: uint(req.WarehouseID), Quantity: 0, - ProjectFlockKandangId: &projectFlockKandangID, - // CreatedBy: 1, // TODO: should Get from auth middleware + ProjectFlockKandangId: projectFlockKandangID, } if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil { s.Log.Errorf("Failed to create product warehouse: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create product warehouse") } - s.Log.Infof("Product warehouse created: %+v", newPW.Id) - } - - pw, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID( - ctx, - uint(req.ProductID), - uint(req.WarehouseID), - ) - if err != nil { - s.Log.Errorf("Failed to get product warehouse for project flock check: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product warehouse") + pw = newPW } if err := common.EnsureProjectFlockNotClosedForProductWarehouses( diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index e759138e..92330f26 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -18,6 +18,7 @@ type ProductWarehouseRepository interface { ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) ExistsByID(ctx context.Context, id uint) (bool, error) GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error) + FindByProductWarehouseAndPfk(ctx context.Context, productID uint, warehouseID uint, projectFlockKandangID *uint) (*entity.ProductWarehouse, error) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error) GetByFlagAndWarehouseID(ctx context.Context, flagName string, warehouseId uint) ([]entity.ProductWarehouse, error) @@ -107,6 +108,20 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous return &productWarehouse, nil } +func (r *ProductWarehouseRepositoryImpl) FindByProductWarehouseAndPfk(ctx context.Context, productID uint, warehouseID uint, projectFlockKandangID *uint) (*entity.ProductWarehouse, error) { + var productWarehouse entity.ProductWarehouse + + err := r.DB().WithContext(ctx). + Where("product_id = ? AND warehouse_id = ? AND project_flock_kandang_id IS NOT DISTINCT FROM ?", productID, warehouseID, projectFlockKandangID). + First(&productWarehouse).Error + + if err != nil { + return nil, err + } + + return &productWarehouse, nil +} + func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) { var productWarehouses []entity.ProductWarehouse q := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}). diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 8ae019a4..1ca35a71 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -106,23 +106,17 @@ func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit } func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, error) { - s.Log.Infof("Attempting to get StockTransfer with ID: %d", id) transferPtr, err := s.StockTransferRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { return s.withRelations(db) }) if err != nil { - s.Log.Errorf("Error getting StockTransfer ID %d: %+v", id, err) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Transfer not found") } return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer") } - if transferPtr != nil { - s.Log.Infof("StockTransfer %d has %d documents", transferPtr.Id, len(transferPtr.Documents)) - } - return transferPtr, nil } @@ -336,7 +330,9 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Files: documentFiles, }) if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to upload document for delivery %d", idx+1)) + s.Log.WithError(err).Errorf("Failed to upload document for delivery %d (delivery_id: %d, filename: %s)", + idx+1, deliveries[idx].Id, file.Filename) + return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to upload document for delivery %d: %v", idx+1, err)) } } } @@ -396,7 +392,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques }) if err != nil { - s.Log.Errorf("Transaction failed in CreateOne: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to process transfer transaction: %v", err)) } diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index e864a778..a1f4e1dd 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -247,11 +247,15 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery itemDeliveryDate = &parsedDate } + // Hitung total_weight dan total_price otomatis + totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight + totalPrice := requestedProduct.UnitPrice * requestedProduct.Qty + deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.AvgWeight = requestedProduct.AvgWeight - deliveryProduct.TotalWeight = requestedProduct.TotalWeight - deliveryProduct.TotalPrice = requestedProduct.TotalPrice + deliveryProduct.TotalWeight = totalWeight + deliveryProduct.TotalPrice = totalPrice deliveryProduct.DeliveryDate = itemDeliveryDate deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber @@ -357,11 +361,15 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty + // Hitung total_weight dan total_price otomatis + totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight + totalPrice := requestedProduct.UnitPrice * requestedProduct.Qty + deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.AvgWeight = requestedProduct.AvgWeight - deliveryProduct.TotalWeight = requestedProduct.TotalWeight - deliveryProduct.TotalPrice = requestedProduct.TotalPrice + deliveryProduct.TotalWeight = totalWeight + deliveryProduct.TotalPrice = totalPrice deliveryProduct.DeliveryDate = itemDeliveryDate deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index dc6e62de..d57b323e 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -75,7 +75,6 @@ func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, er return nil, fiber.NewError(fiber.StatusNotFound, "SalesOrders not found") } if err != nil { - s.Log.Errorf("Failed get marketing by id: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order") } @@ -293,13 +292,17 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u for _, rp := range req.MarketingProducts { if old, ok := oldByPW[rp.ProductWarehouseId]; ok { + // Hitung total_weight dan total_price otomatis + totalWeight := rp.Qty * rp.AvgWeight + totalPrice := rp.UnitPrice * rp.Qty + updateBody := map[string]any{ "product_warehouse_id": rp.ProductWarehouseId, "qty": rp.Qty, "unit_price": rp.UnitPrice, "avg_weight": rp.AvgWeight, - "total_weight": rp.TotalWeight, - "total_price": rp.TotalPrice, + "total_weight": totalWeight, + "total_price": totalPrice, } if err := marketingProductRepoTx.PatchOne(c.Context(), old.Id, updateBody, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to update marketing product") @@ -589,30 +592,34 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error { + // Hitung total_weight dan total_price otomatis + totalWeight := rp.Qty * rp.AvgWeight + totalPrice := rp.UnitPrice * rp.Qty + marketingProduct := &entity.MarketingProduct{ MarketingId: marketingId, ProductWarehouseId: rp.ProductWarehouseId, Qty: rp.Qty, UnitPrice: rp.UnitPrice, AvgWeight: rp.AvgWeight, - TotalWeight: rp.TotalWeight, - TotalPrice: rp.TotalPrice, + TotalWeight: totalWeight, + TotalPrice: totalPrice, } if err := marketingProductRepo.CreateOne(ctx, marketingProduct, nil); err != nil { return err } marketingDeliveryProduct := &entity.MarketingDeliveryProduct{ - MarketingProductId: marketingProduct.Id, - ProductWarehouseId: marketingProduct.ProductWarehouseId, - UnitPrice: 0, - TotalWeight: 0, - AvgWeight: 0, - TotalPrice: 0, - DeliveryDate: nil, - VehicleNumber: rp.VehicleNumber, - UsageQty: 0, - PendingQty: 0, + MarketingProductId: marketingProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + UnitPrice: 0, + TotalWeight: 0, + AvgWeight: 0, + TotalPrice: 0, + DeliveryDate: nil, + VehicleNumber: rp.VehicleNumber, + UsageQty: 0, + PendingQty: 0, } if err := invDeliveryRepo.CreateOne(ctx, marketingDeliveryProduct, nil); err != nil { return err diff --git a/internal/modules/marketing/validations/deliveryorder.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go index 7db2cdd1..a879db6f 100644 --- a/internal/modules/marketing/validations/deliveryorder.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -5,8 +5,6 @@ type DeliveryProduct struct { Qty float64 `json:"qty" validate:"omitempty,gte=0"` UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"` AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"` - TotalWeight float64 `json:"total_weight" validate:"omitempty,gte=0"` - TotalPrice float64 `json:"total_price" validate:"omitempty,gte=0"` DeliveryDate string `json:"delivery_date" validate:"omitempty,datetime=2006-01-02"` VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"` } diff --git a/internal/modules/marketing/validations/salesorder.validation.go b/internal/modules/marketing/validations/salesorder.validation.go index 47d2e616..b69da394 100644 --- a/internal/modules/marketing/validations/salesorder.validation.go +++ b/internal/modules/marketing/validations/salesorder.validation.go @@ -12,10 +12,8 @@ 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"` - TotalWeight float64 `json:"total_weight" validate:"required,gt=0"` Qty float64 `json:"qty" validate:"required,gt=0"` AvgWeight float64 `json:"avg_weight" validate:"required,gt=0"` - TotalPrice float64 `json:"total_price" validate:"required,gt=0"` } type Update struct { diff --git a/internal/modules/master/production-standards/services/production-standard.service.go b/internal/modules/master/production-standards/services/production-standard.service.go index 77c56299..4005b014 100644 --- a/internal/modules/master/production-standards/services/production-standard.service.go +++ b/internal/modules/master/production-standards/services/production-standard.service.go @@ -84,7 +84,6 @@ func (s productionStandardService) GetOne(c *fiber.Ctx, id uint) (*entity.Produc return nil, fiber.NewError(fiber.StatusNotFound, "ProductionStandard not found") } if err != nil { - s.Log.Errorf("Failed get productionStandard by id: %+v", err) return nil, err } return productionStandard, nil @@ -111,6 +110,7 @@ func (s *productionStandardService) CreateOne(c *fiber.Ctx, req *validation.Crea var createdStandard *entity.ProductionStandard err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { + standardRepoTx := repository.NewProductionStandardRepository(tx) productionStandardDetailRepoTx := repository.NewProductionStandardDetailRepository(tx) standardGrowthDetailRepoTx := repository.NewStandardGrowthDetailRepository(tx) @@ -207,7 +207,6 @@ func (s productionStandardService) UpdateOne(c *fiber.Ctx, req *validation.Updat nameExists, err := s.Repository.NameExists(c.Context(), *req.Name, &id) if err != nil { - s.Log.Errorf("Failed to check existing production standard: %+v", err) return err } if nameExists { @@ -285,7 +284,6 @@ func (s productionStandardService) UpdateOne(c *fiber.Ctx, req *validation.Updat }) if err != nil { - s.Log.Errorf("Failed to update production standard: %+v", err) return nil, err } @@ -297,7 +295,6 @@ func (s productionStandardService) DeleteOne(c *fiber.Ctx, id uint) error { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "ProductionStandard not found") } - s.Log.Errorf("Failed to delete productionStandard: %+v", err) return err } return nil diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 2e938041..5b2a9407 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -1,14 +1,14 @@ package validation 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"` - ProjectBudgets []ProjectBudget `json:"project_budgets" validate:"required,min=1,dive"` + 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"` + ProjectBudgets []ProjectBudget `json:"project_budgets" validate:"required,min=1,dive"` } type Query struct {