feat(BE-ExpenseApproval): add unit vice president approval step and permissions

This commit is contained in:
aguhh18
2026-01-11 20:10:19 +07:00
parent 4ee5bf3628
commit 3422fceec7
7 changed files with 60 additions and 48 deletions
+13 -12
View File
@@ -19,18 +19,19 @@ const (
) )
const ( const (
P_ExpenseGetAll = "lti.expense.list" P_ExpenseGetAll = "lti.expense.list"
P_ExpenseCreateOne = "lti.expense.create" P_ExpenseCreateOne = "lti.expense.create"
P_ExpenseUpdateOne = "lti.expense.update" P_ExpenseUpdateOne = "lti.expense.update"
P_ExpenseGetOne = "lti.expense.detail" P_ExpenseGetOne = "lti.expense.detail"
P_ExpenseDeleteOne = "lti.expense.delete" P_ExpenseDeleteOne = "lti.expense.delete"
P_ExpenseApprovalManager = "lti.expense.approve.manager" P_ExpenseApprovalManager = "lti.expense.approve.manager"
P_ExpenseApprovalFinance = "lti.expense.approve.finance" P_ExpenseApprovalFinance = "lti.expense.approve.finance"
P_ExpenseCreateRealizations = "lti.expense.create.realization" P_ExpenseApprovalUnitVicePresident = "lti.expense.approve.unit_vice_president"
P_ExpenseUpdateRealizations = "lti.expense.update.realization" P_ExpenseCreateRealizations = "lti.expense.create.realization"
P_ExpenseCompleteExpense = "lti.expense.complete.expense" P_ExpenseUpdateRealizations = "lti.expense.update.realization"
P_ExpenseDocument = "lti.expense.document" P_ExpenseCompleteExpense = "lti.expense.complete.expense"
P_ExpenseDocumentRealizations = "lti.expense.document.realization" P_ExpenseDocument = "lti.expense.document"
P_ExpenseDocumentRealizations = "lti.expense.document.realization"
) )
const ( const (
P_AdjustmentGetAll = "lti.inventory.list" P_AdjustmentGetAll = "lti.inventory.list"
@@ -233,6 +233,8 @@ func (u *ExpenseController) Approval(c *fiber.Ctx) error {
approvalType = "manager" approvalType = "manager"
} else if strings.Contains(path, "/approvals/finance") { } else if strings.Contains(path, "/approvals/finance") {
approvalType = "finance" approvalType = "finance"
} else if strings.Contains(path, "/approvals/unit-vice-president") {
approvalType = "unit-vice-president"
} else { } else {
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path") return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path")
} }
+3
View File
@@ -27,8 +27,11 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
route.Get("/:id", m.RequirePermissions(m.P_ExpenseGetOne), ctrl.GetOne) route.Get("/:id", m.RequirePermissions(m.P_ExpenseGetOne), ctrl.GetOne)
route.Patch("/:id", m.RequirePermissions(m.P_ExpenseUpdateOne), ctrl.UpdateOne) route.Patch("/:id", m.RequirePermissions(m.P_ExpenseUpdateOne), ctrl.UpdateOne)
route.Delete("/:id", m.RequirePermissions(m.P_ExpenseDeleteOne), ctrl.DeleteOne) route.Delete("/:id", m.RequirePermissions(m.P_ExpenseDeleteOne), ctrl.DeleteOne)
route.Post("/approvals/manager", m.RequirePermissions(m.P_ExpenseApprovalManager), ctrl.Approval) route.Post("/approvals/manager", m.RequirePermissions(m.P_ExpenseApprovalManager), ctrl.Approval)
route.Post("/approvals/finance", m.RequirePermissions(m.P_ExpenseApprovalFinance), ctrl.Approval) route.Post("/approvals/finance", m.RequirePermissions(m.P_ExpenseApprovalFinance), ctrl.Approval)
route.Post("/approvals/unit-vice-president", m.RequirePermissions(m.P_ExpenseApprovalUnitVicePresident), ctrl.Approval)
route.Post("/:id/realizations", m.RequirePermissions(m.P_ExpenseCreateRealizations), ctrl.CreateRealization) route.Post("/:id/realizations", m.RequirePermissions(m.P_ExpenseCreateRealizations), ctrl.CreateRealization)
route.Patch("/:id/realizations", m.RequirePermissions(m.P_ExpenseUpdateRealizations), ctrl.UpdateRealization) route.Patch("/:id/realizations", m.RequirePermissions(m.P_ExpenseUpdateRealizations), ctrl.UpdateRealization)
route.Post("/:id/complete", m.RequirePermissions(m.P_ExpenseCompleteExpense), ctrl.CompleteExpense) route.Post("/:id/complete", m.RequirePermissions(m.P_ExpenseCompleteExpense), ctrl.CompleteExpense)
@@ -1055,15 +1055,24 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
if latestApproval.StepNumber != uint16(utils.ExpenseStepPengajuan) { if latestApproval.StepNumber != uint16(utils.ExpenseStepPengajuan) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)] currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest, return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Manager step. Latest approval is at %s step. Expected previous step: Pengajuan", currentStepName)) fmt.Sprintf("Cannot process at Head Area step. Latest approval is at %s step. Expected previous step: Pengajuan", currentStepName))
} }
} else if approvalType == "finance" { } else if approvalType == "unit-vice-president" {
stepNumber = utils.ExpenseStepFinance stepNumber = utils.ExpenseStepUnitVicePresident
if latestApproval.StepNumber != uint16(utils.ExpenseStepManager) { if latestApproval.StepNumber != uint16(utils.ExpenseStepManager) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)] currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest, return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Finance step. Latest approval is at %s step. Expected previous step: Manager", currentStepName)) fmt.Sprintf("Cannot process at Unit Vice President step. Latest approval is at %s step. Expected previous step: Head Area", currentStepName))
}
} else if approvalType == "finance" {
stepNumber = utils.ExpenseStepFinance
if latestApproval.StepNumber != uint16(utils.ExpenseStepUnitVicePresident) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Finance step. Latest approval is at %s step. Expected previous step: Unit Vice President", currentStepName))
} }
} else { } else {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid approval type: %v", approvalType)) return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid approval type: %v", approvalType))
@@ -39,12 +39,12 @@ type TransferExpenseReceivingPayload struct {
} }
type groupedTransferItem struct { type groupedTransferItem struct {
detail *entity.StockTransferDetail detail *entity.StockTransferDetail
payload TransferExpenseReceivingPayload payload TransferExpenseReceivingPayload
projectFK *uint projectFK *uint
kandangID *uint kandangID *uint
totalPrice float64 totalPrice float64
shippingCostTotal float64 shippingCostTotal float64
} }
func groupingKey(supplierID uint, date time.Time, warehouseID uint) string { func groupingKey(supplierID uint, date time.Time, warehouseID uint) string {
@@ -84,7 +84,6 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
expenseIDs := make(map[uint64]struct{}) expenseIDs := make(map[uint64]struct{})
expenseNonstockIDs := make([]uint64, 0) expenseNonstockIDs := make([]uint64, 0)
for _, item := range items { for _, item := range items {
if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 { if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 {
expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId) expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId)
@@ -92,7 +91,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
} }
if len(expenseNonstockIDs) > 0 { if len(expenseNonstockIDs) > 0 {
for _, nsID := range expenseNonstockIDs { for _, nsID := range expenseNonstockIDs {
var expenseID uint64 var expenseID uint64
if err := tx.Model(&entity.ExpenseNonstock{}). if err := tx.Model(&entity.ExpenseNonstock{}).
@@ -106,13 +105,11 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
} }
} }
if err := tx.Delete(&entity.ExpenseNonstock{}, expenseNonstockIDs).Error; err != nil { if err := tx.Delete(&entity.ExpenseNonstock{}, expenseNonstockIDs).Error; err != nil {
return err return err
} }
} }
approvalRepoTx := commonRepo.NewApprovalRepository(tx) approvalRepoTx := commonRepo.NewApprovalRepository(tx)
for expenseID := range expenseIDs { for expenseID := range expenseIDs {
var count int64 var count int64
@@ -122,7 +119,6 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
return err return err
} }
if count == 0 { if count == 0 {
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil { if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil {
return err return err
@@ -220,7 +216,6 @@ func (b *transferExpenseBridge) createExpenseViaService(
for _, gi := range items { for _, gi := range items {
note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id) note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id)
price := gi.shippingCostTotal price := gi.shippingCostTotal
if gi.payload.TransportPerItem != nil { if gi.payload.TransportPerItem != nil {
price = *gi.payload.TransportPerItem * gi.payload.DeliveredQty price = *gi.payload.TransportPerItem * gi.payload.DeliveredQty
@@ -228,7 +223,7 @@ func (b *transferExpenseBridge) createExpenseViaService(
costItems = append(costItems, expenseValidation.CostItem{ costItems = append(costItems, expenseValidation.CostItem{
NonstockID: expeditionNonstockID, NonstockID: expeditionNonstockID,
Quantity: 1, Quantity: 1,
Price: price, Price: price,
Notes: note, Notes: note,
}) })
@@ -251,7 +246,6 @@ func (b *transferExpenseBridge) createExpenseViaService(
return nil, err return nil, err
} }
action := entity.ApprovalActionApproved action := entity.ApprovalActionApproved
actorID := uint(transfer.CreatedBy) actorID := uint(transfer.CreatedBy)
if actorID == 0 { if actorID == 0 {
@@ -261,6 +255,9 @@ func (b *transferExpenseBridge) createExpenseViaService(
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil { if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil {
return nil, err return nil, err
} }
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepUnitVicePresident, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil { if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
return nil, err return nil, err
} }
@@ -328,7 +325,6 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
ctx := c.Context() ctx := c.Context()
transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB { transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB {
return db. return db.
Preload("Details"). Preload("Details").
@@ -348,11 +344,10 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
for i := range transfer.Details { for i := range transfer.Details {
detailMap[transfer.Details[i].Id] = &transfer.Details[i] detailMap[transfer.Details[i].Id] = &transfer.Details[i]
for _, deliveryItem := range transfer.Details[i].DeliveryItems { for _, deliveryItem := range transfer.Details[i].DeliveryItems {
if deliveryItem.StockTransferDelivery != nil { if deliveryItem.StockTransferDelivery != nil {
shippingCostMap[transfer.Details[i].Id] = deliveryItem.StockTransferDelivery.ShippingCostTotal shippingCostMap[transfer.Details[i].Id] = deliveryItem.StockTransferDelivery.ShippingCostTotal
break break
} }
} }
} }
@@ -395,17 +390,14 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
} }
} }
shippingCostTotal := shippingCostMap[detail.Id] shippingCostTotal := shippingCostMap[detail.Id]
totalPrice := shippingCostTotal totalPrice := shippingCostTotal
if payload.TransportPerItem != nil { if payload.TransportPerItem != nil {
totalPrice = *payload.TransportPerItem * payload.DeliveredQty totalPrice = *payload.TransportPerItem * payload.DeliveredQty
} }
warehouseID := uint(payload.WarehouseID) warehouseID := uint(payload.WarehouseID)
if warehouseID == 0 && transfer.ToWarehouse != nil { if warehouseID == 0 && transfer.ToWarehouse != nil {
warehouseID = uint(transfer.ToWarehouse.Id) warehouseID = uint(transfer.ToWarehouse.Id)
@@ -621,6 +621,9 @@ func (b *expenseBridge) createExpenseViaService(
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil { if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil {
return nil, err return nil, err
} }
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepUnitVicePresident, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil { if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
return nil, err return nil, err
} }
+13 -11
View File
@@ -354,20 +354,22 @@ var MarketingApprovalSteps = map[approvalutils.ApprovalStep]string{
// ------------------------------------------------------------------- // -------------------------------------------------------------------
const ( const (
ApprovalWorkflowExpense approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("EXPENSES") ApprovalWorkflowExpense approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("EXPENSES")
ExpenseStepPengajuan approvalutils.ApprovalStep = 1 ExpenseStepPengajuan approvalutils.ApprovalStep = 1
ExpenseStepManager approvalutils.ApprovalStep = 2 ExpenseStepManager approvalutils.ApprovalStep = 2
ExpenseStepFinance approvalutils.ApprovalStep = 3 ExpenseStepUnitVicePresident approvalutils.ApprovalStep = 3
ExpenseStepRealisasi approvalutils.ApprovalStep = 4 ExpenseStepFinance approvalutils.ApprovalStep = 4
ExpenseStepSelesai approvalutils.ApprovalStep = 5 ExpenseStepRealisasi approvalutils.ApprovalStep = 5
ExpenseStepSelesai approvalutils.ApprovalStep = 6
) )
var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{ var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{
ExpenseStepPengajuan: "Pengajuan", ExpenseStepPengajuan: "Pengajuan",
ExpenseStepManager: "Approval Manager", ExpenseStepManager: "Approval Head Area",
ExpenseStepFinance: "Approval Finance", ExpenseStepUnitVicePresident: "Approval Business Unit Vice President",
ExpenseStepRealisasi: "Realisasi", ExpenseStepFinance: "Approval Finance",
ExpenseStepSelesai: "Selesai", ExpenseStepRealisasi: "Realisasi",
ExpenseStepSelesai: "Selesai",
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------