From 471fd1dbbf9a0f04e6ebeee183c79ee6fa24b2a6 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 16:30:44 +0700 Subject: [PATCH] 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 {