mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
feat/BE/US-33/TASK-292,293,Adjust Project Flock status (add status Selesai), Validate with restriction when expense not finish and stock is not used
This commit is contained in:
@@ -3,13 +3,13 @@ package middleware
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/sso"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -199,7 +199,6 @@ func hasAllScopes(have, required []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// RequirePermissions ensures the authenticated user possesses all specified permissions.
|
||||
func RequirePermissions(perms ...string) fiber.Handler {
|
||||
required := canonicalPermissions(perms)
|
||||
|
||||
@@ -7,8 +7,8 @@ const (
|
||||
|
||||
//recording
|
||||
const (
|
||||
PermissionRecordingRead = "recording.read"
|
||||
PermissionRecordingCreate = "recording.write"
|
||||
PermissionRecordingRead = "recording.index"
|
||||
PermissionRecordingCreate = "recording.create"
|
||||
PermissionRecordingUpdate = "recording.update"
|
||||
PermissionRecordingDelete = "recording.delete"
|
||||
)
|
||||
@@ -15,8 +15,8 @@ type ExpenseRepository interface {
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetNextSequence(ctx context.Context) (int, error)
|
||||
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error)
|
||||
WithProjectFlockKandangFilter(pfkID uint) func(*gorm.DB) *gorm.DB
|
||||
CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID uint, isFinished func(*entity.Approval) bool) (int64, error)
|
||||
WithProjectFlockKandangFilter(pfkID, kandangID uint) func(*gorm.DB) *gorm.DB
|
||||
CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID, kandangID uint, isFinished func(*entity.Approval) bool) (int64, error)
|
||||
}
|
||||
|
||||
type ExpenseRepositoryImpl struct {
|
||||
@@ -54,25 +54,31 @@ func (r *ExpenseRepositoryImpl) GetWithSupplier(ctx context.Context, id uint64)
|
||||
return &expense, nil
|
||||
}
|
||||
|
||||
func (r *ExpenseRepositoryImpl) WithProjectFlockKandangFilter(pfkID uint) func(*gorm.DB) *gorm.DB {
|
||||
func (r *ExpenseRepositoryImpl) WithProjectFlockKandangFilter(pfkID, kandangID uint) func(*gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if pfkID == 0 {
|
||||
if pfkID == 0 && kandangID == 0 {
|
||||
return db
|
||||
}
|
||||
return db.Joins("JOIN expense_nonstocks ON expense_nonstocks.expense_id = expenses.id").
|
||||
Where("expense_nonstocks.project_flock_kandang_id = ?", pfkID)
|
||||
q := db.Joins("JOIN expense_nonstocks ON expense_nonstocks.expense_id = expenses.id")
|
||||
if pfkID > 0 && kandangID > 0 {
|
||||
return q.Where("expense_nonstocks.project_flock_kandang_id = ? OR expense_nonstocks.kandang_id = ?", pfkID, kandangID)
|
||||
}
|
||||
if pfkID > 0 {
|
||||
return q.Where("expense_nonstocks.project_flock_kandang_id = ?", pfkID)
|
||||
}
|
||||
return q.Where("expense_nonstocks.kandang_id = ?", kandangID)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID uint, isFinished func(*entity.Approval) bool) (int64, error) {
|
||||
if pfkID == 0 {
|
||||
func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID, kandangID uint, isFinished func(*entity.Approval) bool) (int64, error) {
|
||||
if pfkID == 0 && kandangID == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var ids []uint64
|
||||
if err := r.DB().WithContext(ctx).
|
||||
Table("expenses").
|
||||
Scopes(r.WithProjectFlockKandangFilter(pfkID)).
|
||||
Scopes(r.WithProjectFlockKandangFilter(pfkID, kandangID)).
|
||||
Group("expenses.id").
|
||||
Pluck("expenses.id", &ids).Error; err != nil {
|
||||
return 0, err
|
||||
|
||||
+4
-1
@@ -258,7 +258,10 @@ func (r *ProductWarehouseRepositoryImpl) GetByFlagAndWarehouseID(ctx context.Con
|
||||
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
||||
Where("flags.name = ? AND product_warehouses.warehouse_id = ?", flagName, warehouseId).
|
||||
Order("product_warehouses.id DESC").
|
||||
Preload("Product").Preload("Warehouse").
|
||||
Preload("Product").
|
||||
Preload("Product.ProductCategory").
|
||||
Preload("Product.Uom").
|
||||
Preload("Warehouse").
|
||||
Find(&productWarehouses).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+19
@@ -110,3 +110,22 @@ func (u *ProjectFlockKandangController) Closing(c *fiber.Ctx) error {
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ProjectFlockKandangController) CheckClosing(c *fiber.Ctx) error {
|
||||
id, err := strconv.Atoi(c.Params("id"))
|
||||
if err != nil || id <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
result, err := u.ProjectFlockKandangService.CheckClosing(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Cek persyaratan closing kandang",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,5 +22,8 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
// route.Post("/:id/closing", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.Closing)
|
||||
// route.Get("/:id/closing/check", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.CheckClosing)
|
||||
route.Post("/:id/closing", ctrl.Closing)
|
||||
route.Get("/:id/closing/check", ctrl.CheckClosing)
|
||||
}
|
||||
|
||||
+182
-2
@@ -24,10 +24,10 @@ import (
|
||||
type ProjectFlockKandangService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error)
|
||||
CheckClosing(ctx *fiber.Ctx, id uint) (*ClosingCheckResult, error)
|
||||
Closing(ctx *fiber.Ctx, id uint, req *validation.Closing) (*entity.ProjectFlockKandang, error)
|
||||
}
|
||||
|
||||
// Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity
|
||||
|
||||
type projectFlockKandangService struct {
|
||||
Log *logrus.Logger
|
||||
@@ -40,6 +40,33 @@ type projectFlockKandangService struct {
|
||||
PopulationRepo repository.ProjectFlockPopulationRepository
|
||||
}
|
||||
|
||||
type ClosingCheckResult struct {
|
||||
UnfinishedExpenses int64 `json:"unfinished_expenses"`
|
||||
StockRemaining []StockRemainingDetail `json:"stock_remaining"`
|
||||
Expenses []ExpenseSummary `json:"expenses"`
|
||||
}
|
||||
|
||||
type StockRemainingDetail struct {
|
||||
FlagName string `json:"flag_name"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
ProductId uint `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductCategory string `json:"product_category"`
|
||||
Uom string `json:"uom"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
}
|
||||
|
||||
type ExpenseSummary struct {
|
||||
Id uint64 `json:"id"`
|
||||
PoNumber string `json:"po_number"`
|
||||
Category string `json:"category"`
|
||||
Total float64 `json:"total"`
|
||||
Status string `json:"status"`
|
||||
StepName string `json:"step_name"`
|
||||
Step uint16 `json:"step"`
|
||||
Reference string `json:"reference_number"`
|
||||
}
|
||||
|
||||
func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, expenseRepo expenseRepo.ExpenseRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService {
|
||||
return &projectFlockKandangService{
|
||||
Log: utils.Log,
|
||||
@@ -173,6 +200,127 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s projectFlockKandangService) CheckClosing(c *fiber.Ctx, id uint) (*ClosingCheckResult, error) {
|
||||
pfk, err := s.Repository.GetByID(c.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var unfinished int64
|
||||
if s.ExpenseRepo != nil && s.ApprovalSvc != nil {
|
||||
count, err := s.ExpenseRepo.CountUnfinishedByProjectFlockKandang(c.Context(), pfk.Id, pfk.KandangId, func(appr *entity.Approval) bool {
|
||||
return appr != nil && appr.StepNumber == uint16(utils.ExpenseStepSelesai) && appr.Action != nil && *appr.Action == entity.ApprovalActionApproved
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unfinished = count
|
||||
}
|
||||
|
||||
stockRemain := make([]StockRemainingDetail, 0)
|
||||
if s.WarehouseRepo != nil && s.ProductWarehouseRepo != nil {
|
||||
warehouse, werr := s.WarehouseRepo.GetByKandangID(c.Context(), pfk.KandangId)
|
||||
if werr != nil {
|
||||
return nil, werr
|
||||
}
|
||||
|
||||
for _, flagName := range []utils.FlagType{utils.FlagPakan, utils.FlagOVK} {
|
||||
productWarehouses, pwErr := s.ProductWarehouseRepo.GetByFlagAndWarehouseID(c.Context(), string(flagName), warehouse.Id)
|
||||
if pwErr != nil {
|
||||
return nil, pwErr
|
||||
}
|
||||
|
||||
for _, pw := range productWarehouses {
|
||||
if pw.Quantity > 0 {
|
||||
category := ""
|
||||
if pw.Product.ProductCategory.Id != 0 {
|
||||
category = pw.Product.ProductCategory.Name
|
||||
}
|
||||
uomName := ""
|
||||
if pw.Product.Uom.Id != 0 {
|
||||
uomName = pw.Product.Uom.Name
|
||||
}
|
||||
stockRemain = append(stockRemain, StockRemainingDetail{
|
||||
FlagName: string(flagName),
|
||||
ProductWarehouseId: pw.Id,
|
||||
ProductId: pw.ProductId,
|
||||
ProductName: pw.Product.Name,
|
||||
ProductCategory: category,
|
||||
Uom: uomName,
|
||||
Quantity: pw.Quantity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expenseSummaries := make([]ExpenseSummary, 0)
|
||||
if s.ExpenseRepo != nil {
|
||||
var expenses []entity.Expense
|
||||
if err := s.ExpenseRepo.DB().WithContext(c.Context()).
|
||||
Scopes(s.ExpenseRepo.WithProjectFlockKandangFilter(pfk.Id, pfk.KandangId)).
|
||||
Preload("Nonstocks").
|
||||
Find(&expenses).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(expenses) > 0 && s.ApprovalSvc != nil {
|
||||
ids := make([]uint, 0, len(expenses))
|
||||
for _, e := range expenses {
|
||||
ids = append(ids, uint(e.Id))
|
||||
}
|
||||
latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), utils.ApprovalWorkflowExpense, ids, nil)
|
||||
if err == nil {
|
||||
for i := range expenses {
|
||||
if latest, ok := latestMap[uint(expenses[i].Id)]; ok {
|
||||
expenses[i].LatestApproval = latest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range expenses {
|
||||
total := 0.0
|
||||
for _, ns := range exp.Nonstocks {
|
||||
total += ns.Qty * ns.Price
|
||||
}
|
||||
|
||||
status := "Pending"
|
||||
stepName := ""
|
||||
stepNum := uint16(0)
|
||||
if exp.LatestApproval != nil {
|
||||
stepName = exp.LatestApproval.StepName
|
||||
stepNum = exp.LatestApproval.StepNumber
|
||||
if exp.LatestApproval.Action != nil {
|
||||
status = string(*exp.LatestApproval.Action)
|
||||
} else if stepName != "" {
|
||||
status = stepName
|
||||
}
|
||||
}
|
||||
|
||||
expenseSummaries = append(expenseSummaries, ExpenseSummary{
|
||||
Id: exp.Id,
|
||||
PoNumber: exp.PoNumber,
|
||||
Category: exp.Category,
|
||||
Total: total,
|
||||
Status: status,
|
||||
StepName: stepName,
|
||||
Step: stepNum,
|
||||
Reference: exp.ReferenceNumber,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &ClosingCheckResult{
|
||||
UnfinishedExpenses: unfinished,
|
||||
StockRemaining: stockRemain,
|
||||
Expenses: expenseSummaries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validation.Closing) (*entity.ProjectFlockKandang, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
@@ -210,7 +358,7 @@ func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validati
|
||||
return nil, fiber.NewError(fiber.StatusConflict, "Kandang sudah closed")
|
||||
}
|
||||
if s.ExpenseRepo != nil && s.ApprovalSvc != nil {
|
||||
unfinished, err := s.ExpenseRepo.CountUnfinishedByProjectFlockKandang(c.Context(), pfk.Id, func(appr *entity.Approval) bool {
|
||||
unfinished, err := s.ExpenseRepo.CountUnfinishedByProjectFlockKandang(c.Context(), pfk.Id, pfk.KandangId, func(appr *entity.Approval) bool {
|
||||
return appr != nil && appr.StepNumber == uint16(utils.ExpenseStepSelesai) && appr.Action != nil && *appr.Action == entity.ApprovalActionApproved
|
||||
})
|
||||
if err != nil {
|
||||
@@ -265,6 +413,38 @@ func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validati
|
||||
); aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
// Jika semua kandang dalam project sudah ditutup, set approval project flock ke SELESAI.
|
||||
pfks, ferr := s.Repository.GetByProjectFlockID(c.Context(), pfk.ProjectFlockId)
|
||||
if ferr != nil {
|
||||
return nil, ferr
|
||||
}
|
||||
allClosed := true
|
||||
for _, item := range pfks {
|
||||
if item.ClosedAt == nil {
|
||||
allClosed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allClosed {
|
||||
latestPF, lerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlock, pfk.ProjectFlockId, nil)
|
||||
if lerr != nil {
|
||||
return nil, lerr
|
||||
}
|
||||
if latestPF == nil || latestPF.StepNumber != uint16(utils.ProjectFlockStepSelesai) {
|
||||
if _, aerr := s.ApprovalSvc.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlock,
|
||||
pfk.ProjectFlockId,
|
||||
utils.ProjectFlockStepSelesai,
|
||||
&closeAction,
|
||||
actorID,
|
||||
nil,
|
||||
); aerr != nil && !errors.Is(aerr, gorm.ErrDuplicatedKey) {
|
||||
return nil, aerr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "unclose":
|
||||
if pfk.ClosedAt == nil {
|
||||
|
||||
@@ -154,12 +154,14 @@ const (
|
||||
ApprovalWorkflowProjectFlock approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("PROJECT_FLOCKS")
|
||||
ProjectFlockStepPengajuan approvalutils.ApprovalStep = 1
|
||||
ProjectFlockStepAktif approvalutils.ApprovalStep = 2
|
||||
ProjectFlockStepSelesai approvalutils.ApprovalStep = 3
|
||||
)
|
||||
|
||||
// projectFlockApprovalSteps keeps the workflow step definitions for project flock approvals.
|
||||
var ProjectFlockApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
ProjectFlockStepPengajuan: "Pengajuan",
|
||||
ProjectFlockStepAktif: "Aktif",
|
||||
ProjectFlockStepSelesai: "Selesai",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user