mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
678 lines
24 KiB
Go
678 lines
24 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
|
||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||
customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||
warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||
userRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/gofiber/fiber/v2"
|
||
"github.com/sirupsen/logrus"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type SalesOrdersService interface {
|
||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Marketing, error)
|
||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Marketing, error)
|
||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Marketing, error)
|
||
}
|
||
|
||
type salesOrdersService struct {
|
||
Log *logrus.Logger
|
||
Validate *validator.Validate
|
||
MarketingRepo repository.MarketingRepository
|
||
CustomerRepo customerRepo.CustomerRepository
|
||
ProductWarehouseRepo productWarehouseRepo.ProductWarehouseRepository
|
||
UserRepo userRepo.UserRepository
|
||
ApprovalSvc commonSvc.ApprovalService
|
||
WarehouseRepo warehouseRepo.WarehouseRepository
|
||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||
}
|
||
|
||
func NewSalesOrdersService(marketingRepo repository.MarketingRepository, customerRepo customerRepo.CustomerRepository, productWarehouseRepo productWarehouseRepo.ProductWarehouseRepository, userRepo userRepo.UserRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo warehouseRepo.WarehouseRepository,
|
||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, validate *validator.Validate) SalesOrdersService {
|
||
return &salesOrdersService{
|
||
Log: utils.Log,
|
||
Validate: validate,
|
||
MarketingRepo: marketingRepo,
|
||
CustomerRepo: customerRepo,
|
||
ProductWarehouseRepo: productWarehouseRepo,
|
||
UserRepo: userRepo,
|
||
ApprovalSvc: approvalSvc,
|
||
WarehouseRepo: warehouseRepo,
|
||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||
}
|
||
}
|
||
|
||
func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||
return db.
|
||
Preload("CreatedUser").
|
||
Preload("Customer").
|
||
Preload("SalesPerson").
|
||
Preload("Products.ProductWarehouse.Product.Flags").
|
||
Preload("Products.ProductWarehouse.Warehouse")
|
||
}
|
||
|
||
func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, error) {
|
||
marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations)
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, fiber.NewError(fiber.StatusNotFound, "SalesOrders not found")
|
||
}
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order")
|
||
}
|
||
|
||
if s.ApprovalSvc != nil {
|
||
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, func(db *gorm.DB) *gorm.DB {
|
||
return db.Preload("ActionUser")
|
||
})
|
||
if err == nil && len(approvals) > 0 {
|
||
latest := approvals[len(approvals)-1]
|
||
marketing.LatestApproval = &latest
|
||
}
|
||
}
|
||
|
||
return marketing, nil
|
||
}
|
||
|
||
func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Marketing, error) {
|
||
if err := s.Validate.Struct(req); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
actorID, err := m.ActorIDFromContext(c)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := commonSvc.EnsureRelations(c.Context(),
|
||
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, item := range req.MarketingProducts {
|
||
if err := commonSvc.EnsureRelations(c.Context(),
|
||
commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists},
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
soDate, err := utils.ParseDateString(req.Date)
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||
}
|
||
|
||
soNumber, err := s.MarketingRepo.NextSoNumber(context.Background(), nil)
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate SO number")
|
||
}
|
||
|
||
var marketing *entity.Marketing
|
||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||
|
||
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||
invDeliveryRepoTx := repository.NewMarketingDeliveryProductRepository(dbTransaction)
|
||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||
|
||
marketing = &entity.Marketing{
|
||
CustomerId: req.CustomerId,
|
||
SoNumber: soNumber,
|
||
SoDate: soDate,
|
||
SalesPersonId: req.SalesPersonId,
|
||
Notes: req.Notes,
|
||
CreatedBy: actorID,
|
||
}
|
||
if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
||
}
|
||
|
||
if len(req.MarketingProducts) > 0 {
|
||
pwIDs := make([]uint, 0, len(req.MarketingProducts))
|
||
for _, product := range req.MarketingProducts {
|
||
if product.ProductWarehouseId != 0 {
|
||
pwIDs = append(pwIDs, product.ProductWarehouseId)
|
||
}
|
||
if err := s.createMarketingProductWithDelivery(c.Context(), marketing.Id, product, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||
}
|
||
|
||
}
|
||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), s.MarketingRepo.DB(), pwIDs); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
approvalAction := entity.ApprovalActionCreated
|
||
if _, err := approvalSvcTx.CreateApproval(
|
||
c.Context(),
|
||
utils.ApprovalWorkflowMarketing,
|
||
marketing.Id,
|
||
utils.MarketingStepPengajuan,
|
||
&approvalAction,
|
||
actorID,
|
||
nil); err != nil {
|
||
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||
fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||
}
|
||
}
|
||
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||
return nil, fiberErr
|
||
}
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
||
}
|
||
|
||
marketing, err = s.MarketingRepo.GetByID(c.Context(), marketing.Id, s.withRelations)
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch created sales order")
|
||
}
|
||
|
||
return marketing, nil
|
||
}
|
||
|
||
func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Marketing, error) {
|
||
if err := s.Validate.Struct(req); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
actorID, err := m.ActorIDFromContext(c)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := commonSvc.EnsureRelations(c.Context(),
|
||
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||
commonSvc.RelationCheck{Name: "SalesPerson", ID: &req.SalesPersonId, Exists: s.UserRepo.IdExists},
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||
}
|
||
if latestApproval != nil && latestApproval.StepNumber >= 3 {
|
||
return nil, fiber.NewError(fiber.StatusBadRequest, "Cannot update sales order after delivery order approval")
|
||
}
|
||
|
||
if len(req.MarketingProducts) > 0 {
|
||
for _, item := range req.MarketingProducts {
|
||
if err := commonSvc.EnsureRelations(c.Context(),
|
||
commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists},
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
}
|
||
if len(req.MarketingProducts) > 0 {
|
||
pwIDs := make([]uint, 0, len(req.MarketingProducts))
|
||
for _, item := range req.MarketingProducts {
|
||
if item.ProductWarehouseId != 0 {
|
||
pwIDs = append(pwIDs, item.ProductWarehouseId)
|
||
}
|
||
}
|
||
|
||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), s.MarketingRepo.DB(), pwIDs); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||
|
||
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||
invDeliveryRepoTx := repository.NewMarketingDeliveryProductRepository(dbTransaction)
|
||
|
||
updateBody := make(map[string]any)
|
||
if req.CustomerId != 0 {
|
||
updateBody["customer_id"] = req.CustomerId
|
||
}
|
||
if req.SalesPersonId != 0 {
|
||
updateBody["sales_person_id"] = req.SalesPersonId
|
||
}
|
||
if req.Date != "" {
|
||
soDate, err := utils.ParseDateString(req.Date)
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||
}
|
||
updateBody["so_date"] = soDate
|
||
}
|
||
if req.Notes != "" {
|
||
updateBody["notes"] = req.Notes
|
||
}
|
||
|
||
if len(updateBody) > 0 {
|
||
if err := marketingRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update sales order")
|
||
}
|
||
}
|
||
|
||
if len(req.MarketingProducts) > 0 {
|
||
|
||
oldProducts, err := marketingProductRepoTx.GetByMarketingID(c.Context(), id)
|
||
if err != nil && err != gorm.ErrRecordNotFound {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch existing products")
|
||
}
|
||
|
||
oldByPW := make(map[uint]*entity.MarketingProduct)
|
||
for i := range oldProducts {
|
||
p := oldProducts[i]
|
||
oldByPW[p.ProductWarehouseId] = &p
|
||
}
|
||
|
||
reqByPW := make(map[uint]validation.CreateMarketingProduct)
|
||
for _, rp := range req.MarketingProducts {
|
||
reqByPW[rp.ProductWarehouseId] = rp
|
||
}
|
||
|
||
for _, rp := range req.MarketingProducts {
|
||
if old, ok := oldByPW[rp.ProductWarehouseId]; ok {
|
||
|
||
// 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
|
||
var totalPrice float64
|
||
if isPakanOrOVK {
|
||
totalPrice = rp.Qty * rp.UnitPrice
|
||
} else {
|
||
totalPrice = totalWeight * rp.UnitPrice
|
||
}
|
||
|
||
updateBody := map[string]any{
|
||
"product_warehouse_id": rp.ProductWarehouseId,
|
||
"qty": rp.Qty,
|
||
"unit_price": rp.UnitPrice,
|
||
"avg_weight": rp.AvgWeight,
|
||
"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")
|
||
}
|
||
|
||
if _, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id); err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
|
||
mdp := &entity.MarketingDeliveryProduct{
|
||
MarketingProductId: old.Id,
|
||
UnitPrice: 0,
|
||
TotalWeight: 0,
|
||
AvgWeight: 0,
|
||
TotalPrice: 0,
|
||
DeliveryDate: nil,
|
||
VehicleNumber: rp.VehicleNumber,
|
||
UsageQty: 0,
|
||
PendingQty: 0,
|
||
}
|
||
if err := invDeliveryRepoTx.CreateOne(c.Context(), mdp, nil); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing delivery product")
|
||
}
|
||
} else {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check delivery product")
|
||
}
|
||
}
|
||
} else {
|
||
if err := s.createMarketingProductWithDelivery(c.Context(), id, rp, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, old := range oldProducts {
|
||
if _, ok := reqByPW[old.ProductWarehouseId]; !ok {
|
||
|
||
deliveryProduct, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id)
|
||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing delivery product")
|
||
}
|
||
if err == nil {
|
||
|
||
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))
|
||
}
|
||
|
||
if err := invDeliveryRepoTx.DeleteOne(c.Context(), deliveryProduct.Id); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete marketing delivery product")
|
||
}
|
||
}
|
||
|
||
if err := marketingProductRepoTx.DeleteOne(c.Context(), old.Id); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete marketing product")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if latestApproval != nil {
|
||
action := entity.ApprovalActionUpdated
|
||
_, err := approvalSvcTx.CreateApproval(
|
||
c.Context(),
|
||
utils.ApprovalWorkflowMarketing,
|
||
id,
|
||
approvalutils.ApprovalStep(latestApproval.StepNumber),
|
||
&action,
|
||
actorID,
|
||
nil)
|
||
if err != nil {
|
||
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create update approval")
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||
return nil, fiberErr
|
||
}
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update sales order")
|
||
}
|
||
|
||
return s.getOne(c, id)
|
||
}
|
||
|
||
func (s salesOrdersService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||
marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations)
|
||
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return fiber.NewError(fiber.StatusNotFound, "SalesOrders not found")
|
||
}
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order")
|
||
}
|
||
if len(marketing.Products) > 0 {
|
||
pwIDs := make([]uint, 0, len(marketing.Products))
|
||
for _, p := range marketing.Products {
|
||
if p.ProductWarehouseId != 0 {
|
||
pwIDs = append(pwIDs, p.ProductWarehouseId)
|
||
}
|
||
}
|
||
|
||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), s.MarketingRepo.DB(), pwIDs); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||
|
||
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||
marketingDeliveryProductRepoTx := repository.NewMarketingDeliveryProductRepository(dbTransaction)
|
||
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||
|
||
if len(marketing.Products) > 0 {
|
||
for _, product := range marketing.Products {
|
||
if err := marketingDeliveryProductRepoTx.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB {
|
||
return db.Where("marketing_product_id = ?", product.Id).Unscoped()
|
||
}); err != nil && err != gorm.ErrRecordNotFound {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order products")
|
||
}
|
||
}
|
||
}
|
||
|
||
if err := marketingProductRepoTx.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB {
|
||
return db.Where("marketing_id = ?", id).Unscoped()
|
||
}); err != nil && err != gorm.ErrRecordNotFound {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order products")
|
||
}
|
||
|
||
if err := marketingRepoTx.DeleteOne(c.Context(), id); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order")
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||
return fiberErr
|
||
}
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.Marketing, error) {
|
||
if err := s.Validate.Struct(req); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
actorID, err := m.ActorIDFromContext(c)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
||
|
||
var action entity.ApprovalAction
|
||
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||
case string(entity.ApprovalActionRejected):
|
||
action = entity.ApprovalActionRejected
|
||
case string(entity.ApprovalActionApproved):
|
||
action = entity.ApprovalActionApproved
|
||
default:
|
||
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||
}
|
||
|
||
approvableIDs := utils.UniqueUintSlice(req.ApprovableIds)
|
||
if len(approvableIDs) == 0 {
|
||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||
}
|
||
|
||
for _, id := range approvableIDs {
|
||
if err := commonSvc.EnsureRelations(c.Context(),
|
||
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
||
if err != nil {
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||
}
|
||
if latestApproval == nil {
|
||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for Marketing %d - sales orders must be created first", id))
|
||
}
|
||
|
||
if action == entity.ApprovalActionApproved {
|
||
switch latestApproval.StepNumber {
|
||
case uint16(utils.MarketingStepPengajuan):
|
||
case uint16(utils.MarketingStepSalesOrder):
|
||
default:
|
||
return nil, fiber.NewError(fiber.StatusBadRequest,
|
||
fmt.Sprintf("Marketing %d cannot be approved - current step is %d", id, latestApproval.StepNumber))
|
||
}
|
||
}
|
||
marketing, mErr := s.MarketingRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||
return db.Preload("Products")
|
||
})
|
||
if mErr != nil {
|
||
if errors.Is(mErr, gorm.ErrRecordNotFound) {
|
||
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("SalesOrders %d not found", id))
|
||
}
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order for project validation")
|
||
}
|
||
|
||
if len(marketing.Products) > 0 {
|
||
pwIDs := make([]uint, 0, len(marketing.Products))
|
||
for _, p := range marketing.Products {
|
||
if p.ProductWarehouseId != 0 {
|
||
pwIDs = append(pwIDs, p.ProductWarehouseId)
|
||
}
|
||
}
|
||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), s.MarketingRepo.DB(), pwIDs); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
}
|
||
|
||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||
|
||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||
|
||
for _, approvableID := range approvableIDs {
|
||
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, approvableID, nil)
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check current approval step")
|
||
}
|
||
|
||
if latestApproval == nil {
|
||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for Marketing %d", approvableID))
|
||
}
|
||
|
||
var nextStep approvalutils.ApprovalStep
|
||
currentStep := latestApproval.StepNumber
|
||
|
||
if action == entity.ApprovalActionApproved {
|
||
|
||
if currentStep == uint16(utils.MarketingStepPengajuan) {
|
||
nextStep = utils.MarketingStepSalesOrder
|
||
} else if currentStep == uint16(utils.MarketingStepSalesOrder) {
|
||
nextStep = utils.MarketingDeliveryOrder
|
||
} else {
|
||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing %d already completed all approval steps", approvableID))
|
||
}
|
||
} else {
|
||
|
||
nextStep = approvalutils.ApprovalStep(currentStep)
|
||
}
|
||
|
||
if _, err := approvalSvc.CreateApproval(
|
||
c.Context(),
|
||
utils.ApprovalWorkflowMarketing,
|
||
approvableID,
|
||
nextStep,
|
||
&action,
|
||
actorID,
|
||
req.Notes,
|
||
); err != nil {
|
||
s.Log.Errorf("Failed to create approval for %d: %+v", approvableID, err)
|
||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||
}
|
||
}
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||
return nil, fiberErr
|
||
}
|
||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||
}
|
||
|
||
updated := make([]entity.Marketing, 0, len(approvableIDs))
|
||
for _, id := range approvableIDs {
|
||
marketing, err := s.getOne(c, id)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
updated = append(updated, *marketing)
|
||
}
|
||
|
||
return updated, nil
|
||
}
|
||
|
||
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error {
|
||
|
||
// 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
|
||
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,
|
||
ProductWarehouseId: rp.ProductWarehouseId,
|
||
Qty: rp.Qty,
|
||
UnitPrice: rp.UnitPrice,
|
||
AvgWeight: rp.AvgWeight,
|
||
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,
|
||
}
|
||
if err := invDeliveryRepo.CreateOne(ctx, marketingDeliveryProduct, nil); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|