Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into feat/BE/sso-adjustment

This commit is contained in:
ragilap
2026-01-24 19:03:52 +07:00
109 changed files with 6479 additions and 2422 deletions
@@ -9,6 +9,7 @@ import (
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
productwarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
)
@@ -68,10 +69,10 @@ type DeliveryItemDTO struct {
}
type DeliveryGroupDTO struct {
DoNumber string `json:"do_number"`
DeliveryDate *time.Time `json:"delivery_date"`
Warehouse *productwarehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
Deliveries []DeliveryItemDTO `json:"deliveries"`
DoNumber string `json:"do_number"`
DeliveryDate *time.Time `json:"delivery_date"`
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
Deliveries []DeliveryItemDTO `json:"deliveries"`
}
type DeliveryMarketingProductDTO struct {
@@ -286,7 +287,7 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri
if !exists {
group = &DeliveryGroupDTO{
DeliveryDate: product.DeliveryDate,
Warehouse: &productwarehouseDTO.WarehouseRelationDTO{
Warehouse: &warehouseDTO.WarehouseRelationDTO{
Id: warehouseId,
Name: warehouseName,
},
+3 -1
View File
@@ -16,6 +16,7 @@ import (
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/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"
rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/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"
@@ -32,6 +33,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
userRepo := rUser.NewUserRepository(db)
customerRepo := rCustomer.NewCustomerRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
stockLogRepo := rShared.NewStockLogRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
@@ -63,7 +65,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, warehouseRepo, projectFlockKandangRepo, validate)
deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, fifoService, validate)
deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, stockLogRepo, approvalSvc, fifoService, validate)
userService := sUser.NewUserService(userRepo, validate)
RegisterRoutes(router, userService, salesOrdersService, deliveryOrdersService)
@@ -140,16 +140,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id").
Where("marketing_delivery_products.delivery_date IS NOT NULL")
if len(filters.AllowedLocationIDs) > 0 {
if !containsJoin(db, "product_warehouses") {
db = db.Joins("JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
}
db = db.Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id").
Where("project_flocks.location_id IN ?", filters.AllowedLocationIDs)
}
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.Search != "" || filters.MarketingType != "" {
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.AreaId > 0 || filters.LocationId > 0 || filters.Search != "" || filters.MarketingType != "" {
db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
}
@@ -157,18 +148,30 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
db = db.Joins("LEFT JOIN products ON products.id = product_warehouses.product_id")
}
if filters.WarehouseId > 0 {
if filters.WarehouseId > 0 || filters.Search != "" {
db = db.Joins("LEFT JOIN warehouses ON warehouses.id = product_warehouses.warehouse_id")
}
if filters.Search != "" {
db = db.Joins("LEFT JOIN customers ON customers.id = marketings.customer_id")
}
db = db.Joins("LEFT JOIN users AS sales_users ON sales_users.id = marketings.sales_person_id")
if filters.Search != "" {
searchPattern := "%" + filters.Search + "%"
db = db.Where("marketing_delivery_products.vehicle_number ILIKE ? OR marketings.so_number ILIKE ? OR customers.name ILIKE ? OR products.name ILIKE ?",
searchPattern, searchPattern, searchPattern, searchPattern)
db = db.Where(`(
marketing_delivery_products.vehicle_number ILIKE ? OR
customers.name ILIKE ? OR
warehouses.name ILIKE ? OR
products.name ILIKE ? OR
sales_users.name ILIKE ? OR
CONCAT(
marketings.so_number,
'-',
COALESCE(TO_CHAR(marketing_delivery_products.delivery_date, 'YYYYMMDD'), ''),
'-',
COALESCE(product_warehouses.warehouse_id::text, '')
) ILIKE ?
)`,
searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern)
}
if filters.CustomerId > 0 {
@@ -187,6 +190,19 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
db = db.Where("product_warehouses.warehouse_id = ?", filters.WarehouseId)
}
if filters.AreaId > 0 || filters.LocationId > 0 {
db = db.Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
Joins("LEFT JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id")
if filters.AreaId > 0 {
db = db.Where("project_flocks.area_id = ?", filters.AreaId)
}
if filters.LocationId > 0 {
db = db.Where("project_flocks.location_id = ?", filters.LocationId)
}
}
if filters.MarketingType != "" {
db = db.Joins("LEFT JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
Group("marketing_delivery_products.id")
@@ -195,7 +211,6 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
case "ayam":
db = db.Where("flags.name IN (?)", []string{
string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer),
string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagAyamMati),
})
case "telur":
db = db.Where("flags.name IN (?)", []string{
@@ -210,8 +225,12 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
}
}
if filters.FilterBy != "" && (filters.StartDate != "" || filters.EndDate != "") {
if filters.FilterBy == "so_date" {
if filters.StartDate != "" || filters.EndDate != "" {
filterBy := filters.FilterBy
if filterBy == "" {
filterBy = "so_date"
}
if filterBy == "so_date" {
if filters.StartDate != "" {
if startDate, err := utils.ParseDateString(filters.StartDate); err == nil {
db = db.Where("marketings.so_date >= ?", startDate)
@@ -223,7 +242,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
db = db.Where("marketings.so_date < ?", nextDate)
}
}
} else if filters.FilterBy == "realization_date" {
} else if filterBy == "realization_date" {
if filters.StartDate != "" {
if startDate, err := utils.ParseDateString(filters.StartDate); err == nil {
db = db.Where("marketing_delivery_products.delivery_date >= ?", startDate)
@@ -26,7 +26,10 @@ func NewMarketingProductRepository(db *gorm.DB) MarketingProductRepository {
func (r *MarketingProductRepositoryImpl) GetByMarketingID(ctx context.Context, marketingID uint) ([]entity.MarketingProduct, error) {
var products []entity.MarketingProduct
if err := r.DB().WithContext(ctx).Where("marketing_id = ?", marketingID).Find(&products).Error; err != nil {
if err := r.DB().WithContext(ctx).
Preload("ProductWarehouse.Product.Flags").
Where("marketing_id = ?", marketingID).
Find(&products).Error; err != nil {
return nil, err
}
if len(products) == 0 {
@@ -14,6 +14,7 @@ import (
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
@@ -34,6 +35,7 @@ type deliveryOrdersService struct {
MarketingRepo marketingRepo.MarketingRepository
MarketingProductRepo marketingRepo.MarketingProductRepository
MarketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository
StockLogRepo rShared.StockLogRepository
ApprovalSvc commonSvc.ApprovalService
FifoSvc commonSvc.FifoService
}
@@ -42,6 +44,7 @@ func NewDeliveryOrdersService(
marketingRepo marketingRepo.MarketingRepository,
marketingProductRepo marketingRepo.MarketingProductRepository,
marketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository,
stockLogRepo rShared.StockLogRepository,
approvalSvc commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService,
validate *validator.Validate,
@@ -51,6 +54,7 @@ func NewDeliveryOrdersService(
MarketingRepo: marketingRepo,
MarketingProductRepo: marketingProductRepo,
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
StockLogRepo: stockLogRepo,
ApprovalSvc: approvalSvc,
FifoSvc: fifoSvc,
}
@@ -275,9 +279,25 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
itemDeliveryDate = &parsedDate
}
// Hitung total_weight dan total_price otomatis
isPakanOrOVK := false
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
isPakanOrOVK = true
break
}
}
}
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
totalPrice := requestedProduct.UnitPrice * requestedProduct.Qty
var totalPrice float64
if isPakanOrOVK {
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
} else {
totalPrice = totalWeight * requestedProduct.UnitPrice
}
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
@@ -289,7 +309,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
if requestedProduct.Qty > 0 {
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil {
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil {
return err
}
}
@@ -341,7 +361,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
return nil, err
}
err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
actorID, err := m.ActorIDFromContext(c)
if err != nil {
return nil, err
}
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction)
@@ -393,9 +418,26 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
// Hitung total_weight dan total_price otomatis
// Cek apakah product punya flag PAKAN atau OVK
isPakanOrOVK := false
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
isPakanOrOVK = true
break
}
}
}
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
totalPrice := requestedProduct.UnitPrice * requestedProduct.Qty
var totalPrice float64
if isPakanOrOVK {
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
} else {
totalPrice = totalWeight * requestedProduct.UnitPrice
}
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
@@ -408,13 +450,13 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
if requestedProduct.Qty != oldRequestedQty {
if oldRequestedQty > 0 {
if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct); err != nil {
if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, actorID); err != nil {
return err
}
}
if requestedProduct.Qty > 0 {
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil {
if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil {
return err
}
}
@@ -439,7 +481,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
return s.getMarketingWithDeliveries(c, id)
}
func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64) 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 {
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
}
@@ -459,6 +501,20 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
if err == nil && result.UsageQuantity > 0 {
if actorID > 0 {
decreaseLog := &entity.StockLog{
Decrease: result.UsageQuantity,
LoggableType: string(utils.StockLogTypeMarketing),
LoggableId: deliveryProduct.Id,
ProductWarehouseId: marketingProduct.ProductWarehouseId,
CreatedBy: actorID,
Notes: "",
}
s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil)
}
}
if err != nil {
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
@@ -467,12 +523,31 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
}
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))
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")
}
if actorID > 0 {
decreaseLog := &entity.StockLog{
Decrease: requestedQty,
LoggableType: string(utils.StockLogTypeMarketing),
LoggableId: deliveryProduct.Id,
ProductWarehouseId: marketingProduct.ProductWarehouseId,
CreatedBy: actorID,
Notes: "",
}
s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil)
}
return nil
}
@@ -483,7 +558,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
return nil
}
func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct) error {
func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, actorID uint) error {
if deliveryProduct == nil || deliveryProduct.Id == 0 {
return nil
}
@@ -510,6 +585,18 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
return err
}
if actorID > 0 && currentUsage > 0 {
increaseLog := &entity.StockLog{
Increase: currentUsage,
LoggableType: string(utils.StockLogTypeMarketing),
LoggableId: deliveryProduct.Id,
ProductWarehouseId: marketingProduct.ProductWarehouseId,
CreatedBy: actorID,
Notes: "",
}
s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil)
}
if err := deliveryProductRepo.ResetFifoFields(ctx, deliveryProduct.Id); err != nil {
return err
}
@@ -306,9 +306,32 @@ 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
// Get product untuk cek flag PAKAN atau OVK
productWarehouse, err := s.ProductWarehouseRepo.GetByID(c.Context(), rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
return db.Preload("Product.Flags")
})
if err != nil {
return err
}
// Cek apakah product punya flag PAKAN atau OVK
isPakanOrOVK := false
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
for _, flag := range productWarehouse.Product.Flags {
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
isPakanOrOVK = true
break
}
}
}
totalWeight := rp.Qty * rp.AvgWeight
totalPrice := rp.UnitPrice * rp.Qty
var totalPrice float64
if isPakanOrOVK {
totalPrice = rp.Qty * rp.UnitPrice
} else {
totalPrice = totalWeight * rp.UnitPrice
}
updateBody := map[string]any{
"product_warehouse_id": rp.ProductWarehouseId,
@@ -616,9 +639,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
// Get product untuk cek flag PAKAN atau OVK
productWarehouse, err := s.ProductWarehouseRepo.GetByID(ctx, rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
return db.Preload("Product.Flags")
})
if err != nil {
return err
}
// Cek apakah product punya flag PAKAN atau OVK
isPakanOrOVK := false
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
for _, flag := range productWarehouse.Product.Flags {
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
isPakanOrOVK = true
break
}
}
}
totalWeight := rp.Qty * rp.AvgWeight
totalPrice := rp.UnitPrice * rp.Qty
var totalPrice float64
if isPakanOrOVK {
// PAKAN atau OVK: qty × unit_price
totalPrice = rp.Qty * rp.UnitPrice
} else {
// Produk lain: total_weight × unit_price
totalPrice = totalWeight * rp.UnitPrice
}
marketingProduct := &entity.MarketingProduct{
MarketingId: marketingId,