mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Feat[BE]: implement FIFO stock management for marketing delivery products, including migration scripts and updates to related services and DTOs
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- Rollback: Remove FIFO fields and restore qty column
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- STEP 1: Drop indexes
|
||||||
|
DROP INDEX IF EXISTS idx_marketing_delivery_products_fifo_lookup;
|
||||||
|
DROP INDEX IF EXISTS idx_marketing_delivery_products_pending_qty;
|
||||||
|
DROP INDEX IF EXISTS idx_marketing_delivery_products_usage_qty;
|
||||||
|
DROP INDEX IF EXISTS idx_marketing_delivery_products_created_at;
|
||||||
|
|
||||||
|
-- STEP 2: Drop constraints
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_marketing_delivery_products_fifo_nonneg;
|
||||||
|
|
||||||
|
-- STEP 3: Restore qty column from usage_qty data
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD COLUMN IF NOT EXISTS qty NUMERIC(15, 3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
-- Migrate data back from usage_qty to qty
|
||||||
|
UPDATE marketing_delivery_products
|
||||||
|
SET qty = usage_qty
|
||||||
|
WHERE qty = 0;
|
||||||
|
|
||||||
|
-- STEP 4: Drop FIFO columns
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
DROP COLUMN IF EXISTS usage_qty,
|
||||||
|
DROP COLUMN IF EXISTS pending_qty,
|
||||||
|
DROP COLUMN IF EXISTS created_at;
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- Add FIFO fields to marketing_delivery_products
|
||||||
|
-- This migration adds fields needed for FIFO stock management
|
||||||
|
-- and removes the old qty field in favor of FIFO-based allocation
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- STEP 0: Drop orphan indexes from previous migration
|
||||||
|
DROP INDEX IF EXISTS idx_marketing_delivery_products_deleted_at;
|
||||||
|
|
||||||
|
-- STEP 1: Add created_at column (required for FIFO ordering)
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW();
|
||||||
|
|
||||||
|
-- STEP 2: Add FIFO tracking fields
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD COLUMN IF NOT EXISTS usage_qty NUMERIC(15, 3) DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS pending_qty NUMERIC(15, 3) DEFAULT 0;
|
||||||
|
|
||||||
|
-- STEP 3: Migrate data from old qty to usage_qty for existing records
|
||||||
|
-- This preserves existing quantity data as allocated quantity
|
||||||
|
UPDATE marketing_delivery_products
|
||||||
|
SET
|
||||||
|
usage_qty = COALESCE(qty, 0),
|
||||||
|
pending_qty = 0
|
||||||
|
WHERE usage_qty = 0;
|
||||||
|
|
||||||
|
-- STEP 4: Drop the old qty column (replaced by usage_qty + pending_qty)
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
DROP COLUMN IF EXISTS qty;
|
||||||
|
|
||||||
|
-- STEP 5: Make FIFO fields NOT NULL
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ALTER COLUMN usage_qty SET NOT NULL,
|
||||||
|
ALTER COLUMN pending_qty SET NOT NULL,
|
||||||
|
ALTER COLUMN created_at SET NOT NULL;
|
||||||
|
|
||||||
|
-- STEP 6: Add constraints to ensure non-negative values
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD CONSTRAINT chk_marketing_delivery_products_fifo_nonneg CHECK (
|
||||||
|
usage_qty >= 0 AND
|
||||||
|
pending_qty >= 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- STEP 7: Create indexes for FIFO operations
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_marketing_delivery_products_created_at
|
||||||
|
ON marketing_delivery_products(created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_marketing_delivery_products_usage_qty
|
||||||
|
ON marketing_delivery_products(usage_qty)
|
||||||
|
WHERE usage_qty > 0;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_marketing_delivery_products_pending_qty
|
||||||
|
ON marketing_delivery_products(pending_qty)
|
||||||
|
WHERE pending_qty > 0;
|
||||||
|
|
||||||
|
-- Composite index for FIFO lookups
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_marketing_delivery_products_fifo_lookup
|
||||||
|
ON marketing_delivery_products(marketing_product_id, created_at DESC);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- Remove foreign key constraint
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
DROP CONSTRAINT IF EXISTS fk_marketing_delivery_products_product_warehouse;
|
||||||
|
|
||||||
|
-- Drop product_warehouse_id column
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
DROP COLUMN IF EXISTS product_warehouse_id;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- Add product_warehouse_id column to marketing_delivery_products
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD COLUMN IF NOT EXISTS product_warehouse_id INT NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Fill product_warehouse_id from marketing_products
|
||||||
|
UPDATE marketing_delivery_products mdp
|
||||||
|
SET product_warehouse_id = mp.product_warehouse_id
|
||||||
|
FROM marketing_products mp
|
||||||
|
WHERE mdp.marketing_product_id = mp.id
|
||||||
|
AND mdp.product_warehouse_id = 0;
|
||||||
|
|
||||||
|
-- Set NOT NULL constraint
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ALTER COLUMN product_warehouse_id SET NOT NULL;
|
||||||
|
|
||||||
|
-- Add foreign key constraint
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD CONSTRAINT fk_marketing_delivery_products_product_warehouse
|
||||||
|
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id);
|
||||||
@@ -5,15 +5,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MarketingDeliveryProduct struct {
|
type MarketingDeliveryProduct struct {
|
||||||
Id uint `gorm:"primaryKey;autoIncrement"`
|
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||||
MarketingProductId uint `gorm:"uniqueIndex;not null"`
|
MarketingProductId uint `gorm:"uniqueIndex;not null"`
|
||||||
Qty float64 `gorm:"type:numeric(15,3)"`
|
ProductWarehouseId uint `gorm:"not null"`
|
||||||
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)"`
|
||||||
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)"`
|
||||||
|
|
||||||
|
// FIFO Fields
|
||||||
|
UsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"`
|
||||||
|
PendingQty float64 `gorm:"type:numeric(15,3);default:0;not null"`
|
||||||
|
CreatedAt *time.Time `gorm:"type:timestamptz;not null"`
|
||||||
|
|
||||||
MarketingProduct MarketingProduct `gorm:"foreignKey:MarketingProductId;references:Id"`
|
MarketingProduct MarketingProduct `gorm:"foreignKey:MarketingProductId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const (
|
|||||||
P_DeliveryGetAll = "lti.marketing.delivery_order.list"
|
P_DeliveryGetAll = "lti.marketing.delivery_order.list"
|
||||||
P_DeliveryGetOne = "lti.marketing.delivery_order.detail"
|
P_DeliveryGetOne = "lti.marketing.delivery_order.detail"
|
||||||
P_DeliveryUpdateOne = "lti.marketing.delivery_order.update"
|
P_DeliveryUpdateOne = "lti.marketing.delivery_order.update"
|
||||||
|
P_DeliveryCreateOne = "lti.marketing.delivery_order.Create"
|
||||||
P_SalesOrderDelete = "lti.marketing.sales_order.delete"
|
P_SalesOrderDelete = "lti.marketing.sales_order.delete"
|
||||||
P_SalesOrderApproval = "lti.marketing.sales_order.approve"
|
P_SalesOrderApproval = "lti.marketing.sales_order.approve"
|
||||||
P_SalesOrderCreateOne = "lti.marketing.sales_order.create"
|
P_SalesOrderCreateOne = "lti.marketing.sales_order.create"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
|||||||
DoNumber: doNumber,
|
DoNumber: doNumber,
|
||||||
Product: product,
|
Product: product,
|
||||||
Customer: customer,
|
Customer: customer,
|
||||||
Qty: e.Qty,
|
Qty: e.UsageQty, // Show allocated quantity from FIFO
|
||||||
Weight: e.TotalWeight,
|
Weight: e.TotalWeight,
|
||||||
AvgWeight: e.AvgWeight,
|
AvgWeight: e.AvgWeight,
|
||||||
Price: e.UnitPrice,
|
Price: e.UnitPrice,
|
||||||
|
|||||||
@@ -712,13 +712,13 @@ func (s closingService) calculateAverageSalesAge(ctx context.Context, projectFlo
|
|||||||
)
|
)
|
||||||
|
|
||||||
for _, product := range deliveryProducts {
|
for _, product := range deliveryProducts {
|
||||||
if product.Qty == 0 {
|
if product.UsageQty == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
projectFlockKandang := product.MarketingProduct.ProductWarehouse.ProjectFlockKandang
|
projectFlockKandang := product.MarketingProduct.ProductWarehouse.ProjectFlockKandang
|
||||||
ageWeeks := dto.CalculateAgeFromChickinDataProduksi(projectFlockKandang, product.DeliveryDate)
|
ageWeeks := dto.CalculateAgeFromChickinDataProduksi(projectFlockKandang, product.DeliveryDate)
|
||||||
totalAgeWeeks += float64(ageWeeks) * product.Qty
|
totalAgeWeeks += float64(ageWeeks) * product.UsageQty
|
||||||
totalQty += product.Qty
|
totalQty += product.UsageQty
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalQty == 0 {
|
if totalQty == 0 {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingD
|
|||||||
return MarketingDeliveryProductDTO{
|
return MarketingDeliveryProductDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
MarketingProductId: e.MarketingProductId,
|
MarketingProductId: e.MarketingProductId,
|
||||||
Qty: e.Qty,
|
Qty: e.UsageQty,
|
||||||
UnitPrice: e.UnitPrice,
|
UnitPrice: e.UnitPrice,
|
||||||
TotalWeight: e.TotalWeight,
|
TotalWeight: e.TotalWeight,
|
||||||
AvgWeight: e.AvgWeight,
|
AvgWeight: e.AvgWeight,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package marketing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -13,11 +14,12 @@ import (
|
|||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||||
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MarketingModule struct{}
|
type MarketingModule struct{}
|
||||||
@@ -31,6 +33,28 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
customerRepo := rCustomer.NewCustomerRepository(db)
|
customerRepo := rCustomer.NewCustomerRepository(db)
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
|
|
||||||
|
// Initialize FIFO service
|
||||||
|
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
||||||
|
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
||||||
|
|
||||||
|
// Register marketing_delivery_products as FIFO Usable
|
||||||
|
// Note: ProductWarehouseID comes from marketing_products table via preload
|
||||||
|
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
||||||
|
Key: fifo.UsableKeyMarketingDelivery,
|
||||||
|
Table: "marketing_delivery_products",
|
||||||
|
Columns: fifo.UsableColumns{
|
||||||
|
ID: "id",
|
||||||
|
ProductWarehouseID: "product_warehouse_id", // Resolved from marketing_products via preload
|
||||||
|
UsageQuantity: "usage_qty",
|
||||||
|
PendingQuantity: "pending_qty",
|
||||||
|
CreatedAt: "created_at",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
||||||
|
panic(fmt.Sprintf("failed to register marketing delivery usable workflow: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize approval service
|
// Initialize approval service
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||||
@@ -42,9 +66,10 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc,warehouseRepo,projectFlockKandangRepo, validate)
|
salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, warehouseRepo, projectFlockKandangRepo, validate)
|
||||||
deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate)
|
deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, fifoService, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
// Register routes
|
// Register routes
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ type MarketingDeliveryProductRepository interface {
|
|||||||
GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error)
|
GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error)
|
||||||
GetByMarketingProductID(ctx context.Context, marketingProductID uint) (*entity.MarketingDeliveryProduct, error)
|
GetByMarketingProductID(ctx context.Context, marketingProductID uint) (*entity.MarketingDeliveryProduct, error)
|
||||||
GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.MarketingQuery) ([]entity.MarketingDeliveryProduct, int64, error)
|
GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.MarketingQuery) ([]entity.MarketingDeliveryProduct, int64, error)
|
||||||
|
UpdateFifoFields(ctx context.Context, id uint, usageQty, pendingQty float64) error
|
||||||
|
GetUsageQty(ctx context.Context, id uint) (float64, error)
|
||||||
|
ResetFifoFields(ctx context.Context, id uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarketingDeliveryProductRepositoryImpl struct {
|
type MarketingDeliveryProductRepositoryImpl struct {
|
||||||
@@ -241,3 +244,33 @@ func containsJoin(db *gorm.DB, tableName string) bool {
|
|||||||
joinSQL := statement.SQL.String()
|
joinSQL := statement.SQL.String()
|
||||||
return strings.Contains(joinSQL, "JOIN "+tableName)
|
return strings.Contains(joinSQL, "JOIN "+tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) UpdateFifoFields(ctx context.Context, id uint, usageQty, pendingQty float64) error {
|
||||||
|
return r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.MarketingDeliveryProduct{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"usage_qty": usageQty,
|
||||||
|
"pending_qty": pendingQty,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) GetUsageQty(ctx context.Context, id uint) (float64, error) {
|
||||||
|
var usageQty float64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.MarketingDeliveryProduct{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Select("usage_qty").
|
||||||
|
Scan(&usageQty).Error
|
||||||
|
return usageQty, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) ResetFifoFields(ctx context.Context, id uint) error {
|
||||||
|
return r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.MarketingDeliveryProduct{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"usage_qty": 0,
|
||||||
|
"pending_qty": 0,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,12 +16,15 @@ func RegisterRoutes(router fiber.Router, userService user.UserService, salesOrde
|
|||||||
route := router.Group("/marketing")
|
route := router.Group("/marketing")
|
||||||
route.Use(m.Auth(userService))
|
route.Use(m.Auth(userService))
|
||||||
|
|
||||||
route.Get("/",m.RequirePermissions(m.P_DeliveryGetAll), deliveryOrdersCtrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DeliveryGetAll), deliveryOrdersCtrl.GetAll)
|
||||||
route.Get("/:id",m.RequirePermissions(m.P_DeliveryGetOne), deliveryOrdersCtrl.GetOne)
|
route.Get("/:id", m.RequirePermissions(m.P_DeliveryGetOne), deliveryOrdersCtrl.GetOne)
|
||||||
route.Delete("/:id",m.RequirePermissions(m.P_SalesOrderDelete), salesOrdersCtrl.DeleteOne)
|
route.Delete("/:id", m.RequirePermissions(m.P_SalesOrderDelete), salesOrdersCtrl.DeleteOne)
|
||||||
|
|
||||||
route.Post("/sales-orders",m.RequirePermissions(m.P_SalesOrderCreateOne), salesOrdersCtrl.CreateOne)
|
route.Post("/sales-orders", m.RequirePermissions(m.P_SalesOrderCreateOne), salesOrdersCtrl.CreateOne)
|
||||||
route.Patch("/sales-orders/:id",m.RequirePermissions(m.P_SalesOrderUpdateOne), salesOrdersCtrl.UpdateOne)
|
route.Patch("/sales-orders/:id", m.RequirePermissions(m.P_SalesOrderUpdateOne), salesOrdersCtrl.UpdateOne)
|
||||||
route.Post("/sales-orders/approvals",m.RequirePermissions(m.P_SalesOrderApproval), salesOrdersCtrl.Approval)
|
route.Post("/sales-orders/approvals", m.RequirePermissions(m.P_SalesOrderApproval), salesOrdersCtrl.Approval)
|
||||||
|
|
||||||
|
route.Post("/delivery-orders", m.RequirePermissions(m.P_DeliveryCreateOne), deliveryOrdersCtrl.CreateOne)
|
||||||
|
route.Patch("/delivery-orders/:id", m.RequirePermissions(m.P_DeliveryUpdateOne), deliveryOrdersCtrl.UpdateOne)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import (
|
|||||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,12 +30,12 @@ type DeliveryOrdersService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type deliveryOrdersService struct {
|
type deliveryOrdersService struct {
|
||||||
Log *logrus.Logger
|
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
MarketingRepo marketingRepo.MarketingRepository
|
MarketingRepo marketingRepo.MarketingRepository
|
||||||
MarketingProductRepo marketingRepo.MarketingProductRepository
|
MarketingProductRepo marketingRepo.MarketingProductRepository
|
||||||
MarketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository
|
MarketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository
|
||||||
ApprovalSvc commonSvc.ApprovalService
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
FifoSvc commonSvc.FifoService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeliveryOrdersService(
|
func NewDeliveryOrdersService(
|
||||||
@@ -43,15 +43,16 @@ func NewDeliveryOrdersService(
|
|||||||
marketingProductRepo marketingRepo.MarketingProductRepository,
|
marketingProductRepo marketingRepo.MarketingProductRepository,
|
||||||
marketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository,
|
marketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository,
|
||||||
approvalSvc commonSvc.ApprovalService,
|
approvalSvc commonSvc.ApprovalService,
|
||||||
|
fifoSvc commonSvc.FifoService,
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
) DeliveryOrdersService {
|
) DeliveryOrdersService {
|
||||||
return &deliveryOrdersService{
|
return &deliveryOrdersService{
|
||||||
Log: utils.Log,
|
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
MarketingRepo: marketingRepo,
|
MarketingRepo: marketingRepo,
|
||||||
MarketingProductRepo: marketingProductRepo,
|
MarketingProductRepo: marketingProductRepo,
|
||||||
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
||||||
ApprovalSvc: approvalSvc,
|
ApprovalSvc: approvalSvc,
|
||||||
|
FifoSvc: fifoSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +109,6 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get marketings: %+v", err)
|
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
for i := range marketings {
|
for i := range marketings {
|
||||||
@@ -116,7 +116,7 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
return db.Preload("ActionUser")
|
return db.Preload("ActionUser")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Failed to load approval for marketing %d: %+v", marketings[i].Id, err)
|
continue
|
||||||
}
|
}
|
||||||
marketings[i].LatestApproval = latestApproval
|
marketings[i].LatestApproval = latestApproval
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
|
|||||||
itemDeliveryDate = &parsedDate
|
itemDeliveryDate = &parsedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
deliveryProduct.Qty = requestedProduct.Qty
|
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
||||||
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
||||||
@@ -256,7 +256,8 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
|
|||||||
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
||||||
|
|
||||||
if requestedProduct.Qty > 0 {
|
if requestedProduct.Qty > 0 {
|
||||||
if err := s.validateAndReduceProductWarehouse(c.Context(), dbTransaction, foundMarketingProduct, requestedProduct.Qty); err != nil {
|
|
||||||
|
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,8 +355,9 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
itemDeliveryDate = deliveryProduct.DeliveryDate
|
itemDeliveryDate = deliveryProduct.DeliveryDate
|
||||||
}
|
}
|
||||||
|
|
||||||
oldQty := deliveryProduct.Qty
|
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
||||||
deliveryProduct.Qty = requestedProduct.Qty
|
|
||||||
|
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
||||||
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
||||||
@@ -363,14 +365,18 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
deliveryProduct.DeliveryDate = itemDeliveryDate
|
deliveryProduct.DeliveryDate = itemDeliveryDate
|
||||||
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
||||||
|
|
||||||
qtyChange := requestedProduct.Qty - oldQty
|
if requestedProduct.Qty != oldRequestedQty {
|
||||||
if qtyChange > 0 {
|
|
||||||
if err := s.validateAndReduceProductWarehouse(c.Context(), dbTransaction, foundMarketingProduct, qtyChange); err != nil {
|
if oldRequestedQty > 0 {
|
||||||
return err
|
if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if qtyChange < 0 {
|
|
||||||
if err := s.restoreProductWarehouseStock(c.Context(), dbTransaction, foundMarketingProduct, -qtyChange); err != nil {
|
if requestedProduct.Qty > 0 {
|
||||||
return err
|
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,50 +399,79 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
return s.getMarketingWithDeliveries(c, id)
|
return s.getMarketingWithDeliveries(c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s deliveryOrdersService) validateAndReduceProductWarehouse(ctx context.Context, tx *gorm.DB, marketingProduct *entity.MarketingProduct, qtyDeliver float64) error {
|
func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64) 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
if deliveryProduct == nil || deliveryProduct.Id == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Delivery product not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
|
UsableID: deliveryProduct.Id,
|
||||||
|
ProductWarehouseID: marketingProduct.ProductWarehouseId,
|
||||||
|
Quantity: requestedQty,
|
||||||
|
AllowPending: false,
|
||||||
|
Tx: tx,
|
||||||
|
})
|
||||||
|
|
||||||
|
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||||
|
|
||||||
pw, err := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
||||||
|
if err2 != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check product warehouse stock")
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check stock")
|
|
||||||
|
if pw == nil || pw.Quantity < requestedQty {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Available: %.2f, Requested: %.2f", func() float64 { if pw != nil { return pw.Quantity } else { return 0 } }(), requestedQty))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if pw.Quantity < qtyDeliver {
|
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock for warehouse - available: %.2f, requested: %.2f", pw.Quantity, qtyDeliver))
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.Quantity = pw.Quantity - qtyDeliver
|
|
||||||
if err := pwRepo.UpdateOne(ctx, pw.Id, pw, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update stock")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s deliveryOrdersService) restoreProductWarehouseStock(ctx context.Context, tx *gorm.DB, marketingProduct *entity.MarketingProduct, qtyRestore float64) error {
|
func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct) error {
|
||||||
|
if deliveryProduct == nil || deliveryProduct.Id == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||||
pw, err := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
currentUsage, err := deliveryProductRepo.GetUsageQty(ctx, deliveryProduct.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
currentUsage = 0
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check stock")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.Quantity = pw.Quantity + qtyRestore
|
if currentUsage == 0 {
|
||||||
if err := pwRepo.UpdateOne(ctx, pw.Id, pw, nil); err != nil {
|
return nil
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update stock")
|
}
|
||||||
|
|
||||||
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
|
UsableID: deliveryProduct.Id,
|
||||||
|
Tx: tx,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deliveryProductRepo.ResetFifoFields(ctx, deliveryProduct.Id); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -307,15 +307,17 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
|
|
||||||
if _, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id); err != nil {
|
if _, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
|
||||||
mdp := &entity.MarketingDeliveryProduct{
|
mdp := &entity.MarketingDeliveryProduct{
|
||||||
MarketingProductId: old.Id,
|
MarketingProductId: old.Id,
|
||||||
Qty: 0,
|
|
||||||
UnitPrice: 0,
|
UnitPrice: 0,
|
||||||
TotalWeight: 0,
|
TotalWeight: 0,
|
||||||
AvgWeight: 0,
|
AvgWeight: 0,
|
||||||
TotalPrice: 0,
|
TotalPrice: 0,
|
||||||
DeliveryDate: nil,
|
DeliveryDate: nil,
|
||||||
VehicleNumber: rp.VehicleNumber,
|
VehicleNumber: rp.VehicleNumber,
|
||||||
|
UsageQty: 0,
|
||||||
|
PendingQty: 0,
|
||||||
}
|
}
|
||||||
if err := invDeliveryRepoTx.CreateOne(c.Context(), mdp, nil); err != nil {
|
if err := invDeliveryRepoTx.CreateOne(c.Context(), mdp, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing delivery product")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing delivery product")
|
||||||
@@ -340,7 +342,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
||||||
if deliveryProduct.DeliveryDate != nil || deliveryProduct.Qty > 0 {
|
if deliveryProduct.DeliveryDate != nil || deliveryProduct.UsageQty > 0 || deliveryProduct.PendingQty > 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete marketing product %d because it has delivery records", old.Id))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete marketing product %d because it has delivery records", old.Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,13 +604,14 @@ func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Cont
|
|||||||
|
|
||||||
marketingDeliveryProduct := &entity.MarketingDeliveryProduct{
|
marketingDeliveryProduct := &entity.MarketingDeliveryProduct{
|
||||||
MarketingProductId: marketingProduct.Id,
|
MarketingProductId: marketingProduct.Id,
|
||||||
Qty: 0,
|
|
||||||
UnitPrice: 0,
|
UnitPrice: 0,
|
||||||
TotalWeight: 0,
|
TotalWeight: 0,
|
||||||
AvgWeight: 0,
|
AvgWeight: 0,
|
||||||
TotalPrice: 0,
|
TotalPrice: 0,
|
||||||
DeliveryDate: nil,
|
DeliveryDate: nil,
|
||||||
VehicleNumber: rp.VehicleNumber,
|
VehicleNumber: rp.VehicleNumber,
|
||||||
|
UsageQty: 0,
|
||||||
|
PendingQty: 0,
|
||||||
}
|
}
|
||||||
if err := invDeliveryRepo.CreateOne(ctx, marketingDeliveryProduct, nil); err != nil {
|
if err := invDeliveryRepo.CreateOne(ctx, marketingDeliveryProduct, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK
|
|||||||
|
|
||||||
doNumber := marketingDTO.GenerateDeliveryOrderNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId)
|
doNumber := marketingDTO.GenerateDeliveryOrderNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId)
|
||||||
|
|
||||||
totalWeightKg := mdp.Qty * mdp.AvgWeight
|
totalWeightKg := mdp.UsageQty * mdp.AvgWeight
|
||||||
salesAmount := totalWeightKg * mdp.UnitPrice
|
salesAmount := totalWeightKg * mdp.UnitPrice
|
||||||
|
|
||||||
var hpp float64
|
var hpp float64
|
||||||
@@ -78,7 +78,7 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK
|
|||||||
AgingDays: agingDays,
|
AgingDays: agingDays,
|
||||||
DoNumber: doNumber,
|
DoNumber: doNumber,
|
||||||
MarketingType: getMarketingType(mdp),
|
MarketingType: getMarketingType(mdp),
|
||||||
Qty: mdp.Qty,
|
Qty: mdp.UsageQty,
|
||||||
AverageWeightKg: mdp.AvgWeight,
|
AverageWeightKg: mdp.AvgWeight,
|
||||||
TotalWeightKg: totalWeightKg,
|
TotalWeightKg: totalWeightKg,
|
||||||
SalesPricePerKg: mdp.UnitPrice,
|
SalesPricePerKg: mdp.UnitPrice,
|
||||||
@@ -194,8 +194,8 @@ func ToSummary(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64, ca
|
|||||||
totalHppAmount := int64(0)
|
totalHppAmount := int64(0)
|
||||||
|
|
||||||
for _, mdp := range mdps {
|
for _, mdp := range mdps {
|
||||||
calculatedTotalWeight := mdp.Qty * mdp.AvgWeight
|
calculatedTotalWeight := mdp.UsageQty * mdp.AvgWeight
|
||||||
totalQty += int(mdp.Qty)
|
totalQty += int(mdp.UsageQty)
|
||||||
totalWeightKg += calculatedTotalWeight
|
totalWeightKg += calculatedTotalWeight
|
||||||
totalSalesAmount += int64(calculatedTotalWeight * mdp.UnitPrice)
|
totalSalesAmount += int64(calculatedTotalWeight * mdp.UnitPrice)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fifo
|
package fifo
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
|
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
|
||||||
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
|
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
|
||||||
|
UsableKeyMarketingDelivery UsableKey = "MARKETING_DELIVERY"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user