mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat: bulk approve endpoint for marketings and expenses
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -32,6 +33,7 @@ type DeliveryOrdersService interface {
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error)
|
||||
BulkApproveToStatus(ctx *fiber.Ctx, req *validation.BulkApprovalRequest, target approvalutils.ApprovalStep) ([]dto.MarketingDetailDTO, error)
|
||||
}
|
||||
|
||||
type deliveryOrdersService struct {
|
||||
@@ -544,6 +546,192 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
||||
return s.getMarketingWithDeliveries(c, id)
|
||||
}
|
||||
|
||||
func (s deliveryOrdersService) BulkApproveToStatus(c *fiber.Ctx, req *validation.BulkApprovalRequest, target approvalutils.ApprovalStep) ([]dto.MarketingDetailDTO, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deliveryDate time.Time
|
||||
if req.RequiresDate(target) {
|
||||
deliveryDate, err = utils.ParseDateString(req.Date)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||||
}
|
||||
}
|
||||
|
||||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||
marketingRepoTx := marketingRepo.NewMarketingRepository(tx)
|
||||
|
||||
for _, id := range approvableIDs {
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: marketingRepoTx.IdExists},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
marketing, err := marketingRepoTx.GetByID(c.Context(), id, s.withRelations)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Marketing %d not found", id))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing")
|
||||
}
|
||||
|
||||
latestApproval, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
if latestApproval == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for Marketing %d", id))
|
||||
}
|
||||
if latestApproval.Action != nil && *latestApproval.Action == entity.ApprovalActionRejected {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing %d is rejected and cannot be bulk approved", id))
|
||||
}
|
||||
|
||||
currentStep := approvalutils.ApprovalStep(latestApproval.StepNumber)
|
||||
if currentStep >= target {
|
||||
currentStepName := utils.MarketingApprovalSteps[currentStep]
|
||||
targetStepName := utils.MarketingApprovalSteps[target]
|
||||
if currentStep == target {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing %d is already at %s step", id, targetStepName))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing %d is already beyond %s step (current step: %s)", id, targetStepName, currentStepName))
|
||||
}
|
||||
|
||||
if len(marketing.Products) > 0 {
|
||||
pwIDs := make([]uint, 0, len(marketing.Products))
|
||||
for _, product := range marketing.Products {
|
||||
if product.ProductWarehouseId != 0 {
|
||||
pwIDs = append(pwIDs, product.ProductWarehouseId)
|
||||
}
|
||||
}
|
||||
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(c.Context(), tx, pwIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for step := currentStep + 1; step <= target; step++ {
|
||||
if step == utils.MarketingDeliveryOrder {
|
||||
if err := s.createDeliveryFromMarketingProducts(c.Context(), tx, marketing, deliveryDate, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
approvalAction := entity.ApprovalActionApproved
|
||||
if _, err := approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowMarketing,
|
||||
id,
|
||||
step,
|
||||
&approvalAction,
|
||||
actorID,
|
||||
req.Notes,
|
||||
); err != nil {
|
||||
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 bulk approve marketings")
|
||||
}
|
||||
|
||||
results := make([]dto.MarketingDetailDTO, 0, len(approvableIDs))
|
||||
for _, id := range approvableIDs {
|
||||
result, err := s.getMarketingWithDeliveries(c, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, *result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s deliveryOrdersService) createDeliveryFromMarketingProducts(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
marketing *entity.Marketing,
|
||||
deliveryDate time.Time,
|
||||
actorID uint,
|
||||
) error {
|
||||
if marketing == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Marketing not found")
|
||||
}
|
||||
if tx == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Transaction is required")
|
||||
}
|
||||
|
||||
marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||
|
||||
for _, marketingProduct := range marketing.Products {
|
||||
deliveryProduct := marketingProduct.DeliveryProduct
|
||||
if deliveryProduct == nil {
|
||||
record, err := marketingDeliveryProductRepositoryTx.GetByMarketingProductID(ctx, marketingProduct.Id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Delivery product for marketing product %d not found", marketingProduct.Id))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product")
|
||||
}
|
||||
deliveryProduct = record
|
||||
}
|
||||
|
||||
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
||||
deliveryDateCopy := deliveryDate
|
||||
|
||||
deliveryProduct.ProductWarehouseId = marketingProduct.ProductWarehouseId
|
||||
deliveryProduct.UnitPrice = marketingProduct.UnitPrice
|
||||
deliveryProduct.AvgWeight = marketingProduct.AvgWeight
|
||||
deliveryProduct.WeightPerConvertion = marketingProduct.WeightPerConvertion
|
||||
deliveryProduct.TotalWeight = marketingProduct.TotalWeight
|
||||
deliveryProduct.TotalPrice = marketingProduct.TotalPrice
|
||||
deliveryProduct.DeliveryDate = &deliveryDateCopy
|
||||
|
||||
requestedQty := marketingProduct.Qty
|
||||
if requestedQty != oldRequestedQty {
|
||||
if oldRequestedQty > 0 {
|
||||
if err := s.releaseDeliveryStock(ctx, tx, deliveryProduct, &marketingProduct, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if requestedQty > 0 {
|
||||
if err := s.consumeDeliveryStock(ctx, tx, deliveryProduct, &marketingProduct, requestedQty, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := marketingDeliveryProductRepositoryTx.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, _ *float64) (totalWeight, totalPrice float64) {
|
||||
if marketingType == string(utils.MarketingTypeTrading) {
|
||||
totalWeight = 0
|
||||
|
||||
Reference in New Issue
Block a user