mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
654 lines
23 KiB
Go
654 lines
23 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) {
|
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|
|
|
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); 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 := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil {
|
|
return nil, err
|
|
}
|
|
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 {
|
|
|
|
// Hitung total_weight dan total_price otomatis
|
|
totalWeight := rp.Qty * rp.AvgWeight
|
|
totalPrice := rp.UnitPrice * rp.Qty
|
|
|
|
updateBody := map[string]any{
|
|
"product_warehouse_id": rp.ProductWarehouseId,
|
|
"qty": rp.Qty,
|
|
"unit_price": rp.UnitPrice,
|
|
"avg_weight": rp.AvgWeight,
|
|
"total_weight": 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 {
|
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
for _, id := range req.ApprovableIds {
|
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); 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 {
|
|
|
|
// Hitung total_weight dan total_price otomatis
|
|
totalWeight := rp.Qty * rp.AvgWeight
|
|
totalPrice := rp.UnitPrice * rp.Qty
|
|
|
|
marketingProduct := &entity.MarketingProduct{
|
|
MarketingId: marketingId,
|
|
ProductWarehouseId: rp.ProductWarehouseId,
|
|
Qty: rp.Qty,
|
|
UnitPrice: rp.UnitPrice,
|
|
AvgWeight: rp.AvgWeight,
|
|
TotalWeight: 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
|
|
}
|