mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 14:55:42 +00:00
feat/BE/US-279/Closing unfinished
This commit is contained in:
@@ -3,7 +3,7 @@ package capabilities
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
permission "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromPermissions returns a filtered map of capabilities that the frontend can use
|
// FromPermissions returns a filtered map of capabilities that the frontend can use
|
||||||
@@ -37,8 +37,8 @@ func normalizeAndAllow(perm string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allowed = map[string]struct{}{
|
var allowed = map[string]struct{}{
|
||||||
recordings.PermissionRecordingRead: {},
|
permission.PermissionRecordingRead: {},
|
||||||
recordings.PermissionRecordingCreate: {},
|
permission.PermissionRecordingCreate: {},
|
||||||
recordings.PermissionRecordingUpdate: {},
|
permission.PermissionRecordingUpdate: {},
|
||||||
recordings.PermissionRecordingDelete: {},
|
permission.PermissionRecordingDelete: {},
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_project_flock_kandangs_closed_at;
|
||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
DROP COLUMN IF EXISTS closed_at;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_project_flock_kandangs_closed_at
|
||||||
|
ON project_flock_kandangs (closed_at);
|
||||||
@@ -3,11 +3,12 @@ package entities
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type ProjectFlockKandang struct {
|
type ProjectFlockKandang struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
Period int `gorm:"not null"`
|
Period int `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
ClosedAt *time.Time `gorm:"index"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
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/sso"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,6 +110,7 @@ func ActorIDFromContext(c *fiber.Ctx) (uint, error) {
|
|||||||
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
}
|
}
|
||||||
return user.Id, nil
|
return user.Id, nil
|
||||||
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDetails returns the full authentication context (token, claims, user).
|
// AuthDetails returns the full authentication context (token, claims, user).
|
||||||
@@ -199,3 +199,72 @@ func hasAllScopes(have, required []string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RequirePermissions ensures the authenticated user possesses all specified permissions.
|
||||||
|
func RequirePermissions(perms ...string) fiber.Handler {
|
||||||
|
required := canonicalPermissions(perms)
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
if len(required) == 0 {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, ok := AuthDetails(c)
|
||||||
|
if !ok || ctx == nil {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
|
userPerms := ctx.permissionSet()
|
||||||
|
if len(userPerms) == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, perm := range required {
|
||||||
|
if _, has := userPerms[perm]; !has {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermission reports whether the current request context includes the given permission.
|
||||||
|
func HasPermission(c *fiber.Ctx, perm string) bool {
|
||||||
|
ctx, ok := AuthDetails(c)
|
||||||
|
if !ok || ctx == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
perm = canonicalPermission(perm)
|
||||||
|
if perm == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, has := ctx.permissionSet()[perm]
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthContext) permissionSet() map[string]struct{} {
|
||||||
|
if a == nil || a.Permissions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.Permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalPermissions(perms []string) []string {
|
||||||
|
out := make([]string, 0, len(perms))
|
||||||
|
seen := make(map[string]struct{}, len(perms))
|
||||||
|
for _, perm := range perms {
|
||||||
|
if canonical := canonicalPermission(perm); canonical != "" {
|
||||||
|
if _, ok := seen[canonical]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[canonical] = struct{}{}
|
||||||
|
out = append(out, canonical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalPermission(perm string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(perm))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,75 +1,14 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
//project-flock
|
||||||
"strings"
|
const (
|
||||||
|
PermissionProjectFlockClosing = "lti:project-flock:closing"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequirePermissions ensures the authenticated user possesses all specified permissions.
|
//recording
|
||||||
func RequirePermissions(perms ...string) fiber.Handler {
|
const (
|
||||||
required := canonicalPermissions(perms)
|
PermissionRecordingRead = "recording.read"
|
||||||
return func(c *fiber.Ctx) error {
|
PermissionRecordingCreate = "recording.write"
|
||||||
if len(required) == 0 {
|
PermissionRecordingUpdate = "recording.update"
|
||||||
return c.Next()
|
PermissionRecordingDelete = "recording.delete"
|
||||||
}
|
)
|
||||||
|
|
||||||
ctx, ok := AuthDetails(c)
|
|
||||||
if !ok || ctx == nil {
|
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
|
||||||
}
|
|
||||||
|
|
||||||
userPerms := ctx.permissionSet()
|
|
||||||
if len(userPerms) == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, perm := range required {
|
|
||||||
if _, has := userPerms[perm]; !has {
|
|
||||||
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPermission reports whether the current request context includes the given permission.
|
|
||||||
func HasPermission(c *fiber.Ctx, perm string) bool {
|
|
||||||
ctx, ok := AuthDetails(c)
|
|
||||||
if !ok || ctx == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
perm = canonicalPermission(perm)
|
|
||||||
if perm == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, has := ctx.permissionSet()[perm]
|
|
||||||
return has
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AuthContext) permissionSet() map[string]struct{} {
|
|
||||||
if a == nil || a.Permissions == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return a.Permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
func canonicalPermissions(perms []string) []string {
|
|
||||||
out := make([]string, 0, len(perms))
|
|
||||||
seen := make(map[string]struct{}, len(perms))
|
|
||||||
for _, perm := range perms {
|
|
||||||
if canonical := canonicalPermission(perm); canonical != "" {
|
|
||||||
if _, ok := seen[canonical]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[canonical] = struct{}{}
|
|
||||||
out = append(out, canonical)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func canonicalPermission(perm string) string {
|
|
||||||
return strings.ToLower(strings.TrimSpace(perm))
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,11 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,6 +15,8 @@ type ExpenseRepository interface {
|
|||||||
IdExists(ctx context.Context, id uint64) (bool, error)
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
GetNextSequence(ctx context.Context) (int, error)
|
GetNextSequence(ctx context.Context) (int, error)
|
||||||
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseRepositoryImpl struct {
|
type ExpenseRepositoryImpl struct {
|
||||||
@@ -49,3 +53,51 @@ func (r *ExpenseRepositoryImpl) GetWithSupplier(ctx context.Context, id uint64)
|
|||||||
}
|
}
|
||||||
return &expense, nil
|
return &expense, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRepositoryImpl) WithProjectFlockKandangFilter(pfkID uint) func(*gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
if pfkID == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
return db.Joins("JOIN expense_nonstocks ON expense_nonstocks.expense_id = expenses.id").
|
||||||
|
Where("expense_nonstocks.project_flock_kandang_id = ?", pfkID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID uint, isFinished func(*entity.Approval) bool) (int64, error) {
|
||||||
|
if pfkID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids []uint64
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Table("expenses").
|
||||||
|
Scopes(r.WithProjectFlockKandangFilter(pfkID)).
|
||||||
|
Group("expenses.id").
|
||||||
|
Pluck("expenses.id", &ids).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var unfinished int64
|
||||||
|
for _, id := range ids {
|
||||||
|
var latest entity.Approval
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("approvals").
|
||||||
|
Where("approvable_type = ? AND approvable_id = ?", utils.ApprovalWorkflowExpense.String(), id).
|
||||||
|
Order("action_at DESC").
|
||||||
|
Limit(1).
|
||||||
|
First(&latest).Error
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if isFinished != nil {
|
||||||
|
if !isFinished(&latest) {
|
||||||
|
unfinished++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unfinished, nil
|
||||||
|
}
|
||||||
|
|||||||
+26
@@ -84,3 +84,29 @@ func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error {
|
|||||||
Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQty(*result, availableQtys, productWarehouses),
|
Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQty(*result, availableQtys, productWarehouses),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ProjectFlockKandangController) Closing(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(validation.Closing)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ProjectFlockKandangService.Closing(c, uint(id), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Status closing kandang diperbarui",
|
||||||
|
// Data: dto.ToProjectFlockKandangDetailDTO(*result),
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
rExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -36,7 +37,8 @@ func (ProjectFlockKandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB
|
|||||||
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
|
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, validate)
|
expenseRepo := rExpense.NewExpenseRepository(db)
|
||||||
|
projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, expenseRepo, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
ProjectFlockKandangRoutes(router, userService, projectFlockKandangService)
|
ProjectFlockKandangRoutes(router, userService, projectFlockKandangService)
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo
|
|||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
route.Post("/:id/closing", ctrl.Closing)
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-5
@@ -2,24 +2,28 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectFlockKandangService interface {
|
type ProjectFlockKandangService interface {
|
||||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
|
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)
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, 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
|
// Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity
|
||||||
@@ -29,17 +33,19 @@ type projectFlockKandangService struct {
|
|||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.ProjectFlockKandangRepository
|
Repository repository.ProjectFlockKandangRepository
|
||||||
ApprovalSvc commonSvc.ApprovalService
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
ExpenseRepo expenseRepo.ExpenseRepository
|
||||||
WarehouseRepo rWarehouse.WarehouseRepository
|
WarehouseRepo rWarehouse.WarehouseRepository
|
||||||
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
||||||
PopulationRepo repository.ProjectFlockPopulationRepository
|
PopulationRepo repository.ProjectFlockPopulationRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService {
|
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{
|
return &projectFlockKandangService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
ApprovalSvc: approvalSvc,
|
ApprovalSvc: approvalSvc,
|
||||||
|
ExpenseRepo: expenseRepo,
|
||||||
WarehouseRepo: warehouseRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
ProductWarehouseRepo: productWarehouseRepo,
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
PopulationRepo: populationRepo,
|
PopulationRepo: populationRepo,
|
||||||
@@ -166,6 +172,99 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project
|
|||||||
return result, nil
|
return result, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ApprovalSvc != nil {
|
||||||
|
latest, aerr := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlock, pfk.ProjectFlockId, nil)
|
||||||
|
if aerr != nil {
|
||||||
|
return nil, aerr
|
||||||
|
}
|
||||||
|
if latest == nil || latest.StepNumber != uint16(utils.ProjectFlockStepAktif) || latest.Action == nil || *latest.Action != entity.ApprovalActionApproved {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Project flock belum berstatus aktif")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action := strings.ToLower(strings.TrimSpace(req.Action))
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "close":
|
||||||
|
if pfk.ClosedAt != nil {
|
||||||
|
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 {
|
||||||
|
return appr != nil && appr.StepNumber == uint16(utils.ExpenseStepSelesai) && appr.Action != nil && *appr.Action == entity.ApprovalActionApproved
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if unfinished > 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Masih ada expense belum selesai untuk kandang ini")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeTime := now
|
||||||
|
if req.ClosedDate != nil {
|
||||||
|
parsed, perr := utils.ParseDateString(strings.TrimSpace(*req.ClosedDate))
|
||||||
|
if perr != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "closed_date tidak valid, gunakan format YYYY-MM-DD")
|
||||||
|
}
|
||||||
|
closeTime = parsed
|
||||||
|
}
|
||||||
|
if err := s.Repository.UpdateClosedAt(c.Context(), id, &closeTime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.ApprovalSvc != nil {
|
||||||
|
closeAction := entity.ApprovalActionApproved
|
||||||
|
if _, aerr := s.ApprovalSvc.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowProjectFlockKandang,
|
||||||
|
id,
|
||||||
|
utils.ProjectFlockKandangStepDisetujui,
|
||||||
|
&closeAction,
|
||||||
|
actorID,
|
||||||
|
nil,
|
||||||
|
); aerr != nil {
|
||||||
|
return nil, aerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "unclose":
|
||||||
|
if pfk.ClosedAt == nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Kandang belum closed")
|
||||||
|
}
|
||||||
|
openNewer, err := s.Repository.HasOpenNewerPeriod(c.Context(), pfk.KandangId, pfk.Period, &pfk.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if openNewer {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Tidak dapat un-close: ada periode yang sedang berjalan")
|
||||||
|
}
|
||||||
|
if err := s.Repository.UpdateClosedAt(c.Context(), id, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "action harus close atau unclose")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Repository.GetByID(c.Context(), id)
|
||||||
|
}
|
||||||
|
|
||||||
func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehouse(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang, productWarehouse *entity.ProductWarehouse) (float64, error) {
|
func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehouse(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang, productWarehouse *entity.ProductWarehouse) (float64, error) {
|
||||||
availableQty := productWarehouse.Quantity
|
availableQty := productWarehouse.Quantity
|
||||||
|
|
||||||
|
|||||||
+5
@@ -22,3 +22,8 @@ type Query struct {
|
|||||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=ASC DESC"`
|
SortOrder string `query:"sort_order" validate:"omitempty,oneof=ASC DESC"`
|
||||||
StepName string `query:"step_name" validate:"omitempty,max=50"`
|
StepName string `query:"step_name" validate:"omitempty,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Closing struct {
|
||||||
|
Action string `json:"action" validate:"required,oneof=close unclose"`
|
||||||
|
ClosedDate *string `json:"closed_date,omitempty"`
|
||||||
|
}
|
||||||
+29
-1
@@ -3,6 +3,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -14,6 +15,7 @@ type ProjectFlockKandangRepository interface {
|
|||||||
GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error)
|
GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error)
|
||||||
GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error)
|
GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error)
|
||||||
GetActiveByKandangID(ctx context.Context, kandangID uint) (*entity.ProjectFlockKandang, error)
|
GetActiveByKandangID(ctx context.Context, kandangID uint) (*entity.ProjectFlockKandang, error)
|
||||||
|
UpdateClosedAt(ctx context.Context, id uint, t *time.Time) error
|
||||||
CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error
|
CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error
|
||||||
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
||||||
GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error)
|
GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error)
|
||||||
@@ -23,9 +25,9 @@ type ProjectFlockKandangRepository interface {
|
|||||||
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
||||||
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||||
ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error)
|
ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error)
|
||||||
|
HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error)
|
||||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
DB() *gorm.DB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type projectFlockKandangRepositoryImpl struct {
|
type projectFlockKandangRepositoryImpl struct {
|
||||||
@@ -251,6 +253,32 @@ func (r *projectFlockKandangRepositoryImpl) GetActiveByKandangID(ctx context.Con
|
|||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) UpdateClosedAt(ctx context.Context, id uint, t *time.Time) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Model(&entity.ProjectFlockKandang{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("closed_at", t).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error) {
|
||||||
|
if kandangID == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
q := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("kandang_id = ?", kandangID).
|
||||||
|
Where("period > ?", currentPeriod).
|
||||||
|
Where("closed_at IS NULL")
|
||||||
|
if excludeID != nil {
|
||||||
|
q = q.Where("id <> ?", *excludeID)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := q.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) {
|
func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) {
|
||||||
if len(kandangIDs) == 0 {
|
if len(kandangIDs) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package recordings
|
|
||||||
|
|
||||||
const (
|
|
||||||
PermissionRecordingRead = "recording.read"
|
|
||||||
PermissionRecordingCreate = "recording.write"
|
|
||||||
PermissionRecordingUpdate = "recording.update"
|
|
||||||
PermissionRecordingDelete = "recording.delete"
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user