Merge branch 'fix/delivery-order' into 'development'

[FIX][BE]: adjust edit delivery order; add migration for delivery order; adjust response get marketing

See merge request mbugroup/lti-api!417
This commit is contained in:
Adnan Zahir
2026-04-14 15:08:32 +07:00
7 changed files with 111 additions and 53 deletions
@@ -0,0 +1,3 @@
-- Remove convertion fields from marketing_delivery_products table
ALTER TABLE marketing_delivery_products
DROP COLUMN IF EXISTS weight_per_convertion;
@@ -0,0 +1,4 @@
-- Add convertion fields to marketing_delivery_products table
ALTER TABLE marketing_delivery_products
ADD COLUMN IF NOT EXISTS weight_per_convertion NUMERIC(15, 3);
@@ -12,6 +12,7 @@ type MarketingDeliveryProduct struct {
UnitPrice float64 `gorm:"type:numeric(15,3)"` UnitPrice float64 `gorm:"type:numeric(15,3)"`
TotalWeight float64 `gorm:"type:numeric(15,3)"` TotalWeight float64 `gorm:"type:numeric(15,3)"`
AvgWeight float64 `gorm:"type:numeric(15,3)"` AvgWeight float64 `gorm:"type:numeric(15,3)"`
WeightPerConvertion *float64 `gorm:"type:numeric(15,3)"`
TotalPrice float64 `gorm:"type:numeric(15,3)"` TotalPrice float64 `gorm:"type:numeric(15,3)"`
DeliveryDate *time.Time `gorm:"type:timestamptz"` DeliveryDate *time.Time `gorm:"type:timestamptz"`
VehicleNumber string `gorm:"type:varchar(50)"` VehicleNumber string `gorm:"type:varchar(50)"`
@@ -49,26 +49,30 @@ type MarketingDetailDTO struct {
} }
type MarketingDeliveryProductDTO struct { type MarketingDeliveryProductDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
MarketingProductId uint `json:"marketing_product_id"` MarketingProductId uint `json:"marketing_product_id"`
Qty float64 `json:"qty"` Qty float64 `json:"qty"`
UnitPrice float64 `json:"unit_price"` UnitPrice float64 `json:"unit_price"`
TotalWeight float64 `json:"total_weight"` TotalWeight float64 `json:"total_weight"`
AvgWeight float64 `json:"avg_weight"` AvgWeight float64 `json:"avg_weight"`
TotalPrice float64 `json:"total_price"` TotalPrice float64 `json:"total_price"`
DeliveryDate *time.Time `json:"delivery_date"` DeliveryDate *time.Time `json:"delivery_date"`
VehicleNumber string `json:"vehicle_number"` VehicleNumber string `json:"vehicle_number"`
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"` ConvertionUnit *string `json:"-"`
WeightPerConvertion *float64 `json:"-"`
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
} }
type DeliveryItemDTO struct { type DeliveryItemDTO struct {
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"` ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"`
Qty float64 `json:"qty"` Qty float64 `json:"qty"`
UnitPrice float64 `json:"unit_price"` UnitPrice float64 `json:"unit_price"`
TotalWeight float64 `json:"total_weight"` TotalWeight float64 `json:"total_weight"`
AvgWeight float64 `json:"avg_weight"` AvgWeight float64 `json:"avg_weight"`
TotalPrice float64 `json:"total_price"` WeightPerConvertion *float64 `json:"weight_per_convertion"`
VehicleNumber string `json:"vehicle_number"` TotalPeti *float64 `json:"total_peti"`
TotalPrice float64 `json:"total_price"`
VehicleNumber string `json:"vehicle_number"`
} }
type DeliveryGroupDTO struct { type DeliveryGroupDTO struct {
@@ -147,15 +151,16 @@ func ToDeliveryMarketingProductDTO(e entity.MarketingProduct, marketingType stri
func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO { func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO {
return MarketingDeliveryProductDTO{ return MarketingDeliveryProductDTO{
Id: e.Id, Id: e.Id,
MarketingProductId: e.MarketingProductId, MarketingProductId: e.MarketingProductId,
Qty: e.UsageQty, Qty: e.UsageQty,
UnitPrice: e.UnitPrice, UnitPrice: e.UnitPrice,
TotalWeight: e.TotalWeight, TotalWeight: e.TotalWeight,
AvgWeight: e.AvgWeight, AvgWeight: e.AvgWeight,
TotalPrice: e.TotalPrice, TotalPrice: e.TotalPrice,
DeliveryDate: e.DeliveryDate, DeliveryDate: e.DeliveryDate,
VehicleNumber: e.VehicleNumber, VehicleNumber: e.VehicleNumber,
WeightPerConvertion: e.WeightPerConvertion,
} }
} }
@@ -285,6 +290,7 @@ func enrichDeliveryProductDTOsWithWarehouse(deliveryProductDTOs []MarketingDeliv
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse) mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse)
deliveryProductDTOs[i].ProductWarehouse = &mapped deliveryProductDTOs[i].ProductWarehouse = &mapped
} }
deliveryProductDTOs[i].ConvertionUnit = product.ConvertionUnit
} }
} }
@@ -322,13 +328,21 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri
} }
deliveryItem := DeliveryItemDTO{ deliveryItem := DeliveryItemDTO{
ProductWarehouse: product.ProductWarehouse, ProductWarehouse: product.ProductWarehouse,
Qty: product.Qty, Qty: product.Qty,
UnitPrice: product.UnitPrice, UnitPrice: product.UnitPrice,
TotalWeight: product.TotalWeight, TotalWeight: product.TotalWeight,
AvgWeight: product.AvgWeight, AvgWeight: product.AvgWeight,
TotalPrice: product.TotalPrice, WeightPerConvertion: product.WeightPerConvertion,
VehicleNumber: product.VehicleNumber, TotalPrice: product.TotalPrice,
VehicleNumber: product.VehicleNumber,
}
if product.ConvertionUnit != nil &&
strings.EqualFold(*product.ConvertionUnit, "PETI") &&
product.WeightPerConvertion != nil &&
*product.WeightPerConvertion > 0 {
totalPeti := product.TotalWeight / *product.WeightPerConvertion
deliveryItem.TotalPeti = &totalPeti
} }
group.Deliveries = append(group.Deliveries, deliveryItem) group.Deliveries = append(group.Deliveries, deliveryItem)
} }
@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math"
"strings" "strings"
"time" "time"
@@ -375,11 +376,12 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
itemDeliveryDate = &parsedDate itemDeliveryDate = &parsedDate
} }
totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) totalWeight, totalPrice := s.resolveDeliveryTotals(marketing.MarketingType, requestedProduct, foundMarketingProduct)
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.UnitPrice = requestedProduct.UnitPrice
deliveryProduct.AvgWeight = requestedProduct.AvgWeight deliveryProduct.AvgWeight = requestedProduct.AvgWeight
deliveryProduct.WeightPerConvertion = requestedProduct.WeightPerConvertion
deliveryProduct.TotalWeight = totalWeight deliveryProduct.TotalWeight = totalWeight
deliveryProduct.TotalPrice = totalPrice deliveryProduct.TotalPrice = totalPrice
deliveryProduct.DeliveryDate = itemDeliveryDate deliveryProduct.DeliveryDate = itemDeliveryDate
@@ -498,11 +500,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
itemDeliveryDate = deliveryProduct.DeliveryDate itemDeliveryDate = deliveryProduct.DeliveryDate
} }
totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) totalWeight, totalPrice := s.resolveDeliveryTotals(marketing.MarketingType, requestedProduct, foundMarketingProduct)
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.UnitPrice = requestedProduct.UnitPrice
deliveryProduct.AvgWeight = requestedProduct.AvgWeight deliveryProduct.AvgWeight = requestedProduct.AvgWeight
deliveryProduct.WeightPerConvertion = requestedProduct.WeightPerConvertion
deliveryProduct.TotalWeight = totalWeight deliveryProduct.TotalWeight = totalWeight
deliveryProduct.TotalPrice = totalPrice deliveryProduct.TotalPrice = totalPrice
deliveryProduct.DeliveryDate = itemDeliveryDate deliveryProduct.DeliveryDate = itemDeliveryDate
@@ -541,20 +544,53 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
return s.getMarketingWithDeliveries(c, id) return s.getMarketingWithDeliveries(c, id)
} }
func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) { func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, _ *float64) (totalWeight, totalPrice float64) {
if marketingType == string(utils.MarketingTypeTrading) { if marketingType == string(utils.MarketingTypeTrading) {
totalWeight = 0 totalWeight = 0
totalPrice = qty * unitPrice totalPrice = math.Round(qty*unitPrice*100) / 100
} else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 { } else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 {
totalWeight = qty * avgWeight totalWeight = math.Round(qty*avgWeight*100) / 100
totalPrice = unitPrice * float64(*week) * qty totalPrice = math.Round(unitPrice*float64(*week)*qty*100) / 100
} else { } else {
totalWeight = qty * avgWeight totalWeight = math.Round(qty*avgWeight*100) / 100
totalPrice = totalWeight * unitPrice
if marketingType == string(utils.MarketingTypeTelur) && convertionUnit != nil {
switch *convertionUnit {
case string(utils.ConvertionUnitQty):
totalPrice = math.Round(qty*unitPrice*100) / 100
return totalWeight, totalPrice
case string(utils.ConvertionUnitPeti):
totalPrice = math.Round(totalWeight*unitPrice*100) / 100
return totalWeight, totalPrice
}
}
totalPrice = math.Round(totalWeight*unitPrice*100) / 100
} }
return totalWeight, totalPrice return totalWeight, totalPrice
} }
func (s *deliveryOrdersService) resolveDeliveryTotals(marketingType string, requestedProduct validation.DeliveryProduct, marketingProduct *entity.MarketingProduct) (totalWeight, totalPrice float64) {
totalWeight, totalPrice = s.calculatePriceByMarketingType(
marketingType,
requestedProduct.Qty,
requestedProduct.AvgWeight,
requestedProduct.UnitPrice,
marketingProduct.Week,
marketingProduct.ConvertionUnit,
marketingProduct.WeightPerConvertion,
)
if requestedProduct.TotalWeight != nil {
totalWeight = *requestedProduct.TotalWeight
}
if requestedProduct.TotalPrice != nil {
totalPrice = *requestedProduct.TotalPrice
}
return totalWeight, totalPrice
}
func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error { 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 { if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 {
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found") return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
@@ -815,7 +815,7 @@ func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Cont
return nil return nil
} }
func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, weightPerConvertion *float64) (totalWeight, totalPrice float64) { func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, _ *float64) (totalWeight, totalPrice float64) {
if marketingType == string(utils.MarketingTypeTrading) { if marketingType == string(utils.MarketingTypeTrading) {
totalWeight = 0 totalWeight = 0
totalPrice = math.Round(qty*unitPrice*100) / 100 totalPrice = math.Round(qty*unitPrice*100) / 100
@@ -831,11 +831,8 @@ func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string,
totalPrice = math.Round(qty*unitPrice*100) / 100 totalPrice = math.Round(qty*unitPrice*100) / 100
return totalWeight, totalPrice return totalWeight, totalPrice
case string(utils.ConvertionUnitPeti): case string(utils.ConvertionUnitPeti):
if weightPerConvertion != nil && *weightPerConvertion > 0 { totalPrice = math.Round(totalWeight*unitPrice*100) / 100
totalPeti := totalWeight / *weightPerConvertion return totalWeight, totalPrice
totalPrice = math.Round(totalPeti*unitPrice*100) / 100
return totalWeight, totalPrice
}
} }
} }
@@ -1,12 +1,15 @@
package validation package validation
type DeliveryProduct struct { type DeliveryProduct struct {
MarketingProductId uint `json:"marketing_product_id" validate:"required,gt=0"` MarketingProductId uint `json:"marketing_product_id" validate:"required,gt=0"`
Qty float64 `json:"qty" validate:"omitempty,gte=0"` Qty float64 `json:"qty" validate:"omitempty,gte=0"`
UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"` UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"`
AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"` AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"`
DeliveryDate string `json:"delivery_date" validate:"omitempty,datetime=2006-01-02"` WeightPerConvertion *float64 `json:"weight_per_convertion" validate:"omitempty,gt=0"`
VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"` 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"`
} }
type DeliveryOrderCreate struct { type DeliveryOrderCreate struct {