mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat[BE-127]: Createing transfer laying create one, approvals, get one, get all, update, delete, but Still unfinished
This commit is contained in:
@@ -4,7 +4,8 @@ CREATE TABLE IF NOT EXISTS laying_transfers (
|
||||
from_project_flock_id BIGINT NOT NULL,
|
||||
to_project_flock_id BIGINT NOT NULL,
|
||||
transfer_date DATE NOT NULL,
|
||||
total_qty NUMERIC(15, 3) NOT NULL,
|
||||
pending_usage_qty NUMERIC(15, 3),
|
||||
usage_qty NUMERIC(15, 3),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
|
||||
@@ -12,7 +12,8 @@ type LayingTransfer struct {
|
||||
FromProjectFlockId uint `gorm:"not null"`
|
||||
ToProjectFlockId uint `gorm:"not null"`
|
||||
TransferDate time.Time `gorm:"type:date;not null"`
|
||||
TotalQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
PendingUsageQty *float64 `gorm:"type:numeric(15,3)"`
|
||||
UsageQty *float64 `gorm:"type:numeric(15,3)"`
|
||||
Notes string `gorm:"type:text"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
@@ -24,5 +25,5 @@ type LayingTransfer struct {
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ type WarehouseRepository interface {
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||
GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||
}
|
||||
|
||||
type WarehouseRepositoryImpl struct {
|
||||
@@ -60,3 +61,16 @@ func (r *WarehouseRepositoryImpl) GetByKandangID(ctx context.Context, kandangId
|
||||
}
|
||||
return &warehouse, nil
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) {
|
||||
var warehouse entity.Warehouse
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("kandang_id = ?", kandangId).
|
||||
Where("deleted_at IS NULL").
|
||||
Order("id DESC").
|
||||
First(&warehouse).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &warehouse, nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ type ProjectChickinRepository interface {
|
||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
}
|
||||
|
||||
type ChickinRepositoryImpl struct {
|
||||
@@ -64,3 +65,17 @@ func (r *ChickinRepositoryImpl) GetPendingByProjectFlockKandangID(ctx context.Co
|
||||
}
|
||||
return chickins, nil
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
|
||||
var total float64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entity.ProjectChickin{}).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Where("pending_usage_qty > 0").
|
||||
Select("COALESCE(SUM(pending_usage_qty), 0)").
|
||||
Row().Scan(&total)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
+8
-6
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
type ProjectFlockPopulationRepository interface {
|
||||
// domain-specific
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error)
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error)
|
||||
GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
@@ -44,15 +44,17 @@ func (r *projectFlockPopulationRepositoryImpl) DB() *gorm.DB {
|
||||
return r.BaseRepositoryImpl.DB()
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) {
|
||||
var record entity.ProjectFlockPopulation
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error) {
|
||||
var records []entity.ProjectFlockPopulation
|
||||
err := r.DB().WithContext(ctx).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
First(&record).Error
|
||||
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
||||
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Preload("ProjectChickin").
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error) {
|
||||
|
||||
+37
-2
@@ -30,6 +30,10 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
||||
TargetProjectFlockId: uint(c.QueryInt("target_project_flock_id", 0)),
|
||||
TransferDateFrom: c.Query("transfer_date_from", ""),
|
||||
TransferDateTo: c.Query("transfer_date_to", ""),
|
||||
ApprovalStatus: c.Query("approval_status", ""),
|
||||
TransferNumber: c.Query("transfer_number", ""),
|
||||
Sort: c.Query("sort", "created_at"),
|
||||
Order: c.Query("order", "desc"),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
@@ -64,7 +68,7 @@ func (u *TransferLayingController) GetOne(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
result, err := u.TransferLayingService.GetOne(c, uint(id))
|
||||
result, approval, err := u.TransferLayingService.GetOneWithApproval(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,7 +78,7 @@ func (u *TransferLayingController) GetOne(c *fiber.Ctx) error {
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get transferLaying successfully",
|
||||
Data: dto.ToTransferLayingListDTO(*result),
|
||||
Data: dto.ToTransferLayingDetailDTOWithSingleApproval(*result, approval),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -145,3 +149,34 @@ func (u *TransferLayingController) DeleteOne(c *fiber.Ctx) error {
|
||||
Message: "Delete transferLaying successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) Approval(c *fiber.Ctx) error {
|
||||
req := new(validation.Approve)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
results, err := u.TransferLayingService.Approval(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
data interface{}
|
||||
message = "Submit transfer laying approval successfully"
|
||||
)
|
||||
if len(results) == 1 {
|
||||
data = dto.ToTransferLayingListDTO(results[0])
|
||||
} else {
|
||||
message = "Submit transfer laying approvals successfully"
|
||||
data = dto.ToTransferLayingListDTOs(results)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,61 +4,252 @@ import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type TransferLayingBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Id uint `json:"id"`
|
||||
TransferNumber string `json:"transfer_number"`
|
||||
TransferDate time.Time `json:"transfer_date"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type ProjectFlockSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Period int `json:"period"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
type ProductSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type WarehouseSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ProductWarehouseSummaryDTO struct {
|
||||
Product *ProductSummaryDTO `json:"product,omitempty"`
|
||||
Warehouse *WarehouseSummaryDTO `json:"warehouse,omitempty"`
|
||||
}
|
||||
|
||||
type ProjectFlockKandangSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
KandangId uint `json:"kandang_id"`
|
||||
}
|
||||
|
||||
type LayingTransferSourceDTO struct {
|
||||
SourceProjectFlockKandang *ProjectFlockKandangSummaryDTO `json:"source_project_flock_kandang,omitempty"`
|
||||
Qty float64 `json:"qty"`
|
||||
ProductWarehouse *ProductWarehouseSummaryDTO `json:"product_warehouse,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
type LayingTransferTargetDTO struct {
|
||||
TargetProjectFlockKandang *ProjectFlockKandangSummaryDTO `json:"target_project_flock_kandang,omitempty"`
|
||||
Qty float64 `json:"qty"`
|
||||
ProductWarehouse *ProductWarehouseSummaryDTO `json:"product_warehouse,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
type TransferLayingListDTO struct {
|
||||
TransferLayingBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"`
|
||||
ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"`
|
||||
PendingUsageQty *float64 `json:"pending_usage_qty"`
|
||||
UsageQty *float64 `json:"usage_qty"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||
}
|
||||
|
||||
type TransferLayingDetailDTO struct {
|
||||
TransferLayingListDTO
|
||||
Sources []LayingTransferSourceDTO `json:"sources,omitempty"`
|
||||
Targets []LayingTransferTargetDTO `json:"targets,omitempty"`
|
||||
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToProjectFlockSummaryDTO(pf *entity.ProjectFlock) *ProjectFlockSummaryDTO {
|
||||
if pf == nil || pf.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectFlockSummaryDTO{
|
||||
Id: pf.Id,
|
||||
Period: pf.Period,
|
||||
Category: pf.Category,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProjectFlockKandangSummaryDTO(pfk *entity.ProjectFlockKandang) *ProjectFlockKandangSummaryDTO {
|
||||
if pfk == nil || pfk.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectFlockKandangSummaryDTO{
|
||||
Id: pfk.Id,
|
||||
KandangId: pfk.KandangId,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductSummaryDTO(product *entity.Product) *ProductSummaryDTO {
|
||||
if product == nil || product.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProductSummaryDTO{
|
||||
Id: product.Id,
|
||||
Name: product.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func ToWarehouseSummaryDTO(warehouse *entity.Warehouse) *WarehouseSummaryDTO {
|
||||
if warehouse == nil || warehouse.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &WarehouseSummaryDTO{
|
||||
Id: warehouse.Id,
|
||||
Name: warehouse.Name,
|
||||
Type: warehouse.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductWarehouseSummaryDTO(pw *entity.ProductWarehouse) *ProductWarehouseSummaryDTO {
|
||||
if pw == nil || pw.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProductWarehouseSummaryDTO{
|
||||
Product: ToProductSummaryDTO(&pw.Product),
|
||||
Warehouse: ToWarehouseSummaryDTO(&pw.Warehouse),
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
||||
return LayingTransferSourceDTO{
|
||||
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
||||
Qty: source.Qty,
|
||||
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
||||
Note: source.Note,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferSourceDTOs(sources []entity.LayingTransferSource) []LayingTransferSourceDTO {
|
||||
if len(sources) == 0 {
|
||||
return []LayingTransferSourceDTO{}
|
||||
}
|
||||
result := make([]LayingTransferSourceDTO, len(sources))
|
||||
for i, s := range sources {
|
||||
result[i] = ToLayingTransferSourceDTO(s)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToLayingTransferTargetDTO(target entity.LayingTransferTarget) LayingTransferTargetDTO {
|
||||
return LayingTransferTargetDTO{
|
||||
TargetProjectFlockKandang: ToProjectFlockKandangSummaryDTO(target.TargetProjectFlockKandang),
|
||||
Qty: target.Qty,
|
||||
ProductWarehouse: ToProductWarehouseSummaryDTO(target.ProductWarehouse),
|
||||
Note: target.Note,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferTargetDTOs(targets []entity.LayingTransferTarget) []LayingTransferTargetDTO {
|
||||
if len(targets) == 0 {
|
||||
return []LayingTransferTargetDTO{}
|
||||
}
|
||||
result := make([]LayingTransferTargetDTO, len(targets))
|
||||
for i, t := range targets {
|
||||
result[i] = ToLayingTransferTargetDTO(t)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToTransferLayingBaseDTO(e entity.LayingTransfer) TransferLayingBaseDTO {
|
||||
return TransferLayingBaseDTO{
|
||||
Id: e.Id,
|
||||
|
||||
Id: e.Id,
|
||||
TransferNumber: e.TransferNumber,
|
||||
TransferDate: e.TransferDate,
|
||||
Notes: e.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return TransferLayingListDTO{
|
||||
TransferLayingBaseDTO: ToTransferLayingBaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock),
|
||||
ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock),
|
||||
PendingUsageQty: e.PendingUsageQty,
|
||||
UsageQty: e.UsageQty,
|
||||
CreatedBy: e.CreatedBy,
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingListDTOs(e []entity.LayingTransfer) []TransferLayingListDTO {
|
||||
result := make([]TransferLayingListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToTransferLayingListDTO(r)
|
||||
func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Approval) TransferLayingDetailDTO {
|
||||
var latestApproval *approvalDTO.ApprovalBaseDTO
|
||||
|
||||
// Use LatestApproval from entity if available
|
||||
if e.LatestApproval != nil {
|
||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||
latestApproval = &mapped
|
||||
} else if len(approvals) > 0 {
|
||||
// Fallback to approvals slice
|
||||
latest := approvalDTO.ToApprovalDTO(approvals[len(approvals)-1])
|
||||
latestApproval = &latest
|
||||
}
|
||||
|
||||
return TransferLayingDetailDTO{
|
||||
TransferLayingListDTO: ToTransferLayingListDTO(e),
|
||||
Sources: ToLayingTransferSourceDTOs(e.Sources),
|
||||
Targets: ToLayingTransferTargetDTOs(e.Targets),
|
||||
Approval: latestApproval,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingDetailDTOWithSingleApproval(e entity.LayingTransfer, approval *entity.Approval) TransferLayingDetailDTO {
|
||||
var mappedApproval *approvalDTO.ApprovalBaseDTO
|
||||
|
||||
// Prefer LatestApproval from entity
|
||||
if e.LatestApproval != nil && e.LatestApproval.Id != 0 {
|
||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||
mappedApproval = &mapped
|
||||
} else if approval != nil && approval.Id != 0 {
|
||||
// Fallback to passed approval parameter
|
||||
mapped := approvalDTO.ToApprovalDTO(*approval)
|
||||
mappedApproval = &mapped
|
||||
}
|
||||
|
||||
return TransferLayingDetailDTO{
|
||||
TransferLayingListDTO: ToTransferLayingListDTO(e),
|
||||
Sources: ToLayingTransferSourceDTOs(e.Sources),
|
||||
Targets: ToLayingTransferTargetDTOs(e.Targets),
|
||||
Approval: mappedApproval,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingListDTOs(items []entity.LayingTransfer) []TransferLayingListDTO {
|
||||
result := make([]TransferLayingListDTO, len(items))
|
||||
for i, item := range items {
|
||||
result[i] = ToTransferLayingListDTO(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToTransferLayingDetailDTO(e entity.LayingTransfer) TransferLayingDetailDTO {
|
||||
return TransferLayingDetailDTO{
|
||||
TransferLayingListDTO: ToTransferLayingListDTO(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
package transfer_layings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
@@ -20,8 +27,26 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
|
||||
transferLayingService := sTransferLaying.NewTransferLayingService(transferLayingRepo, projectFlockRepo, projectFlockKandangRepo, validate)
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register transfer to laying approval workflow: %v", err))
|
||||
}
|
||||
|
||||
transferLayingService := sTransferLaying.NewTransferLayingService(
|
||||
transferLayingRepo,
|
||||
projectFlockRepo,
|
||||
projectFlockKandangRepo,
|
||||
projectFlockPopulationRepo,
|
||||
productWarehouseRepo,
|
||||
warehouseRepo,
|
||||
approvalService,
|
||||
validate,
|
||||
)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
TransferLayingRoutes(router, userService, transferLayingService)
|
||||
|
||||
+4
@@ -11,6 +11,7 @@ import (
|
||||
type TransferLayingRepository interface {
|
||||
repository.BaseRepository[entity.LayingTransfer]
|
||||
GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
}
|
||||
|
||||
type TransferLayingRepositoryImpl struct {
|
||||
@@ -24,6 +25,9 @@ func NewTransferLayingRepository(db *gorm.DB) TransferLayingRepository {
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
func (r *TransferLayingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.LayingTransfer](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *TransferLayingRepositoryImpl) GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error) {
|
||||
var transfer entity.LayingTransfer
|
||||
|
||||
@@ -19,10 +19,12 @@ func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying.
|
||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||
// route.Post("/approval", m.Auth(u), ctrl.Approval)
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
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"
|
||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
||||
@@ -19,31 +26,61 @@ import (
|
||||
type TransferLayingService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, error)
|
||||
GetOneWithApproval(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.LayingTransfer, error)
|
||||
}
|
||||
|
||||
type transferLayingService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.TransferLayingRepository
|
||||
ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository
|
||||
ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.TransferLayingRepository
|
||||
ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository
|
||||
ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository
|
||||
ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository
|
||||
ProductWarehouseRepo rInventory.ProductWarehouseRepository
|
||||
WarehouseRepo rWarehouse.WarehouseRepository
|
||||
ApprovalService commonSvc.ApprovalService
|
||||
}
|
||||
|
||||
func NewTransferLayingService(repo repository.TransferLayingRepository, projectFlockRepo ProjectFlockRepository.ProjectflockRepository, projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository, validate *validator.Validate) TransferLayingService {
|
||||
func NewTransferLayingService(
|
||||
repo repository.TransferLayingRepository,
|
||||
projectFlockRepo ProjectFlockRepository.ProjectflockRepository,
|
||||
projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository,
|
||||
projectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository,
|
||||
productWarehouseRepo rInventory.ProductWarehouseRepository,
|
||||
warehouseRepo rWarehouse.WarehouseRepository,
|
||||
approvalService commonSvc.ApprovalService,
|
||||
validate *validator.Validate,
|
||||
) TransferLayingService {
|
||||
return &transferLayingService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ProjectFlockRepo: projectFlockRepo,
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ProjectFlockRepo: projectFlockRepo,
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
WarehouseRepo: warehouseRepo,
|
||||
ApprovalService: approvalService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s transferLayingService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser")
|
||||
return db.
|
||||
Preload("CreatedUser").
|
||||
Preload("Sources").
|
||||
Preload("Sources.SourceProjectFlockKandang").
|
||||
Preload("Sources.ProductWarehouse").
|
||||
Preload("Sources.ProductWarehouse.Product").
|
||||
Preload("Sources.ProductWarehouse.Warehouse").
|
||||
Preload("Targets").
|
||||
Preload("Targets.TargetProjectFlockKandang").
|
||||
Preload("Targets.ProductWarehouse").
|
||||
Preload("Targets.ProductWarehouse.Product").
|
||||
Preload("Targets.ProductWarehouse.Warehouse")
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error) {
|
||||
@@ -67,13 +104,45 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
||||
if params.TransferDateTo != "" {
|
||||
db = db.Where("transfer_date <= ?", params.TransferDateTo)
|
||||
}
|
||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||
if params.TransferNumber != "" {
|
||||
db = db.Where("transfer_number ILIKE ?", "%"+params.TransferNumber+"%")
|
||||
}
|
||||
|
||||
// Handle sort
|
||||
sortField := "created_at"
|
||||
if params.Sort != "" {
|
||||
sortField = params.Sort
|
||||
}
|
||||
sortOrder := "DESC"
|
||||
if params.Order == "asc" {
|
||||
sortOrder = "ASC"
|
||||
}
|
||||
db = db.Order(fmt.Sprintf("%s %s", sortField, sortOrder))
|
||||
|
||||
return db
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get transferLayings: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Filter by approval status if requested
|
||||
if params.ApprovalStatus != "" {
|
||||
var filtered []entity.LayingTransfer
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
|
||||
for _, transfer := range transferLayings {
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transfer.Id, nil)
|
||||
if err == nil && latestApproval != nil && latestApproval.Action != nil {
|
||||
if string(*latestApproval.Action) == params.ApprovalStatus {
|
||||
filtered = append(filtered, transfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
transferLayings = filtered
|
||||
}
|
||||
|
||||
return transferLayings, total, nil
|
||||
}
|
||||
|
||||
@@ -86,37 +155,69 @@ func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTran
|
||||
s.Log.Errorf("Failed get transferLaying by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch and populate latest approval
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, nil)
|
||||
if err == nil && latestApproval != nil {
|
||||
transferLaying.LatestApproval = latestApproval
|
||||
}
|
||||
|
||||
return transferLaying, nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetOneWithApproval(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) {
|
||||
transferLaying, err := s.GetOne(c, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Return the LatestApproval that was populated in GetOne
|
||||
return transferLaying, transferLaying.LatestApproval, nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Source Project Flock", ID: &req.SourceProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||
common.RelationCheck{Name: "Target Project Flock", ID: &req.TargetProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Source Project Flock", ID: &req.SourceProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||
commonSvc.RelationCheck{Name: "Target Project Flock", ID: &req.TargetProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate source kandangs
|
||||
for _, detail := range req.SourceKandangs {
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Source Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Source Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get source project flock kandang")
|
||||
}
|
||||
if pfk.ProjectFlockId != req.SourceProjectFlockId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d does not belong to source project flock %d", detail.ProjectFlockKandangId, req.SourceProjectFlockId))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate target kandangs
|
||||
for _, detail := range req.TargetKandangs {
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Target Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Target Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
if pfk.ProjectFlockId != req.TargetProjectFlockId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Target kandang %d does not belong to target project flock %d", detail.ProjectFlockKandangId, req.TargetProjectFlockId))
|
||||
}
|
||||
}
|
||||
|
||||
transferDate, err := utils.ParseDateString(req.TransferDate)
|
||||
@@ -124,34 +225,133 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format")
|
||||
}
|
||||
|
||||
var totalQty float64
|
||||
for _, item := range req.SourceKandangs {
|
||||
totalQty += item.Quantity
|
||||
var totalSourceQty, totalTargetQty float64
|
||||
sourceWarehouseMap := make(map[uint]uint)
|
||||
|
||||
for _, sourceDetail := range req.SourceKandangs {
|
||||
if sourceDetail.Quantity <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Source kandang quantity must be greater than 0")
|
||||
}
|
||||
totalSourceQty += sourceDetail.Quantity
|
||||
|
||||
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var totalPopulation float64
|
||||
var productWarehouseId uint
|
||||
for _, pop := range populations {
|
||||
totalPopulation += pop.TotalQty
|
||||
if productWarehouseId == 0 {
|
||||
productWarehouseId = pop.ProductWarehouseId
|
||||
}
|
||||
}
|
||||
|
||||
if totalPopulation == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available for transfer", sourceDetail.ProjectFlockKandangId))
|
||||
}
|
||||
|
||||
if totalPopulation < sourceDetail.Quantity {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has insufficient quantity. Available: %.0f, Requested: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
||||
}
|
||||
|
||||
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
||||
}
|
||||
|
||||
for _, targetDetail := range req.TargetKandangs {
|
||||
if targetDetail.Quantity <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Target kandang quantity must be greater than 0")
|
||||
}
|
||||
totalTargetQty += targetDetail.Quantity
|
||||
}
|
||||
|
||||
if totalSourceQty != totalTargetQty {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Total source quantity (%f) must equal total target quantity (%f)", totalSourceQty, totalTargetQty))
|
||||
}
|
||||
|
||||
transferNumber := fmt.Sprintf("TL-%d", time.Now().UnixNano())
|
||||
|
||||
createBody := &entity.LayingTransfer{
|
||||
TransferNumber: transferNumber,
|
||||
Notes: req.Reason,
|
||||
FromProjectFlockId: req.SourceProjectFlockId,
|
||||
ToProjectFlockId: req.TargetProjectFlockId,
|
||||
TransferDate: transferDate,
|
||||
PendingUsageQty: &totalSourceQty,
|
||||
CreatedBy: 1, //todo : harus diambil dari auth
|
||||
}
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
createBody := &entity.LayingTransfer{
|
||||
Notes: req.Reason,
|
||||
FromProjectFlockId: req.SourceProjectFlockId,
|
||||
ToProjectFlockId: req.TargetProjectFlockId,
|
||||
TransferDate: transferDate,
|
||||
TotalQty: totalQty,
|
||||
CreatedBy: 1, //todo : harus diambil dari auth
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying record")
|
||||
}
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
return err
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTxRepo(dbTransaction)
|
||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, sourceDetail := range req.SourceKandangs {
|
||||
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
|
||||
|
||||
source := entity.LayingTransferSource{
|
||||
LayingTransferId: createBody.Id,
|
||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||
Qty: sourceDetail.Quantity,
|
||||
ProductWarehouseId: &productWarehouseId,
|
||||
}
|
||||
if err := dbTransaction.Create(&source).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
|
||||
}
|
||||
|
||||
if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
|
||||
}
|
||||
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"quantity": gorm.Expr("quantity - ?", sourceDetail.Quantity)}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
||||
}
|
||||
}
|
||||
|
||||
for _, targetDetail := range req.TargetKandangs {
|
||||
|
||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
|
||||
// Get warehouse for this kandang
|
||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||
}
|
||||
|
||||
target := entity.LayingTransferTarget{
|
||||
LayingTransferId: createBody.Id,
|
||||
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
||||
Qty: targetDetail.Quantity,
|
||||
ProductWarehouseId: &targetWarehouse.Id,
|
||||
}
|
||||
if err := dbTransaction.Create(&target).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer target")
|
||||
}
|
||||
}
|
||||
|
||||
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer approval")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return s.GetOne(c, createBody.Id)
|
||||
}
|
||||
|
||||
func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
|
||||
@@ -159,6 +359,30 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if transfer laying exists
|
||||
_, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer laying")
|
||||
}
|
||||
|
||||
// Check if latest approval is PENDING (not approved)
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
|
||||
// If latest approval exists and is APPROVED or REJECTED, cannot update
|
||||
if latestApproval != nil && latestApproval.Action != nil {
|
||||
action := string(*latestApproval.Action)
|
||||
if action == string(entity.ApprovalActionApproved) || action == string(entity.ApprovalActionRejected) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot update transfer laying with status %s", action))
|
||||
}
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.TransferDate != nil {
|
||||
@@ -178,19 +402,333 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update transferLaying: %+v", err)
|
||||
return nil, err
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying")
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||
// Verify transfer laying exists
|
||||
_, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete transferLaying: %+v", err)
|
||||
return err
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer laying")
|
||||
}
|
||||
|
||||
// Check if latest approval is PENDING (not approved/rejected)
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
|
||||
// If latest approval exists and is APPROVED or REJECTED, cannot delete
|
||||
if latestApproval != nil && latestApproval.Action != nil {
|
||||
action := string(*latestApproval.Action)
|
||||
if action == string(entity.ApprovalActionApproved) || action == string(entity.ApprovalActionRejected) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete transfer laying with status %s", action))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete in transaction to handle cascades and qty restoration
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
// Restore source warehouse quantities
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTxRepo(dbTransaction)
|
||||
|
||||
// Get source repository for detail info
|
||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), id)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
||||
}
|
||||
|
||||
// Restore quantity for each source that was reduced
|
||||
for _, source := range sources {
|
||||
if source.ProductWarehouseId != nil && source.Qty > 0 {
|
||||
// Add back the quantity that was transferred
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *source.ProductWarehouseId, map[string]any{
|
||||
"quantity": gorm.Expr("quantity + ?", source.Qty),
|
||||
}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore source warehouse quantity")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore project flock population that was reduced
|
||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
||||
for _, source := range sources {
|
||||
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), source.SourceProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations for restoration")
|
||||
}
|
||||
|
||||
// Restore to latest populations first
|
||||
remainingToRestore := source.Qty
|
||||
for i := len(populations) - 1; i >= 0 && remainingToRestore > 0; i-- {
|
||||
pop := populations[i]
|
||||
restoreAmount := remainingToRestore
|
||||
if remainingToRestore < pop.TotalQty {
|
||||
// Cap restore to what can fit in this population
|
||||
restoreAmount = remainingToRestore
|
||||
}
|
||||
|
||||
newQty := pop.TotalQty + restoreAmount
|
||||
if err := projectFlockPopulationRepoTx.PatchOne(c.Context(), pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore population quantity")
|
||||
}
|
||||
|
||||
remainingToRestore -= restoreAmount
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the transfer laying (cascade will delete sources and targets)
|
||||
if err := s.Repository.WithTx(dbTransaction).DeleteOne(c.Context(), id); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return fiberErr
|
||||
}
|
||||
s.Log.Errorf("Failed to delete transferLaying: %+v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.LayingTransfer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID := uint(1) // TODO: change from auth context
|
||||
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")
|
||||
}
|
||||
|
||||
step := utils.TransferToLayingStepPengajuan
|
||||
if action == entity.ApprovalActionApproved {
|
||||
step = utils.TransferToLayingStepDisetujui
|
||||
}
|
||||
|
||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTxRepo(dbTransaction)
|
||||
|
||||
for _, approvableID := range approvableIDs {
|
||||
transfer, err := s.Repository.GetByID(c.Context(), approvableID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("TransferLaying %d not found", approvableID))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowTransferToLaying,
|
||||
approvableID,
|
||||
step,
|
||||
&action,
|
||||
actorID,
|
||||
req.Notes,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||
}
|
||||
|
||||
if action == entity.ApprovalActionApproved && transfer.PendingUsageQty != nil && *transfer.PendingUsageQty > 0 {
|
||||
|
||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
||||
}
|
||||
|
||||
targets, err := targetRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer targets")
|
||||
}
|
||||
|
||||
if len(sources) > 0 && len(targets) > 0 {
|
||||
firstSource := sources[0]
|
||||
if firstSource.ProductWarehouseId == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
|
||||
}
|
||||
|
||||
sourceWarehouse, err := productWarehouseRepoTx.GetByID(c.Context(), *firstSource.ProductWarehouseId, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source warehouse")
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
|
||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), target.TargetProjectFlockKandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
|
||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||
}
|
||||
|
||||
if _, err := s.getOrCreateProductWarehouse(
|
||||
c.Context(),
|
||||
dbTransaction,
|
||||
sourceWarehouse.ProductId,
|
||||
targetWarehouse.Id,
|
||||
target.Qty,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create or update product warehouse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usageQty := *transfer.PendingUsageQty
|
||||
updateData := map[string]any{
|
||||
"usage_qty": usageQty,
|
||||
"pending_usage_qty": nil,
|
||||
}
|
||||
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.LayingTransfer, 0, len(approvableIDs))
|
||||
for _, approvableID := range approvableIDs {
|
||||
transfer, err := s.GetOne(c, approvableID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updated = append(updated, *transfer)
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func createApprovalTransferLaying(ctx context.Context, tx *gorm.DB, transferLayingID uint, actorID uint) error {
|
||||
if transferLayingID == 0 || actorID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||
action := entity.ApprovalActionCreated
|
||||
|
||||
_, err := approvalSvc.CreateApproval(
|
||||
ctx,
|
||||
utils.ApprovalWorkflowTransferToLaying,
|
||||
transferLayingID,
|
||||
utils.TransferToLayingStepPengajuan,
|
||||
&action,
|
||||
actorID,
|
||||
nil,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context, tx *gorm.DB, productID uint, warehouseID uint, quantity float64) (*entity.ProductWarehouse, error) {
|
||||
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTxRepo(tx)
|
||||
|
||||
existing, err := productWarehouseRepoTx.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
||||
if err == nil && existing != nil {
|
||||
|
||||
if err := productWarehouseRepoTx.PatchOne(ctx, existing.Id, map[string]any{"quantity": gorm.Expr("quantity + ?", quantity)}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return existing, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newWarehouse := &entity.ProductWarehouse{
|
||||
ProductId: productID,
|
||||
WarehouseId: warehouseID,
|
||||
Quantity: quantity,
|
||||
}
|
||||
|
||||
if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newWarehouse, nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) reduceProjectFlockPopulation(ctx context.Context, populationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockKandangID uint, quantityToReduce float64) error {
|
||||
|
||||
populations, err := populationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(populations) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "No populations found for reduction")
|
||||
}
|
||||
|
||||
remainingToReduce := quantityToReduce
|
||||
|
||||
for i := len(populations) - 1; i >= 0; i-- {
|
||||
if remainingToReduce <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
pop := populations[i]
|
||||
reductionAmount := remainingToReduce
|
||||
if pop.TotalQty < remainingToReduce {
|
||||
reductionAmount = pop.TotalQty
|
||||
}
|
||||
|
||||
newQty := pop.TotalQty - reductionAmount
|
||||
if err := populationRepo.PatchOne(ctx, pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remainingToReduce -= reductionAmount
|
||||
}
|
||||
|
||||
if remainingToReduce > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient population to reduce. Still need to reduce: %.0f", remainingToReduce))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+10
@@ -31,4 +31,14 @@ type Query struct {
|
||||
TargetProjectFlockId uint `query:"target_project_flock_id" validate:"omitempty"`
|
||||
TransferDateFrom string `query:"transfer_date_from" validate:"omitempty,datetime=2006-01-02"`
|
||||
TransferDateTo string `query:"transfer_date_to" validate:"omitempty,datetime=2006-01-02"`
|
||||
ApprovalStatus string `query:"approval_status" validate:"omitempty,oneof=PENDING APPROVED REJECTED"` // Filter by latest approval status
|
||||
TransferNumber string `query:"transfer_number" validate:"omitempty"` // Search by transfer number
|
||||
Sort string `query:"sort" validate:"omitempty,oneof=created_at transfer_date"` // Sort by field
|
||||
Order string `query:"order" validate:"omitempty,oneof=asc desc"` // Sort order
|
||||
}
|
||||
|
||||
type Approve struct {
|
||||
Action string `json:"action" validate:"required_strict"`
|
||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
}
|
||||
|
||||
@@ -176,6 +176,22 @@ var ProjectFlockKandangApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
ProjectFlockKandangStepDisetujui: "Disetujui",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Transfer To laying Approval
|
||||
// -------------------------------------------------------------------
|
||||
const (
|
||||
ApprovalWorkflowTransferToLaying approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("TRANSFER_TO_LAYINGS")
|
||||
TransferToLayingStepPengajuan approvalutils.ApprovalStep = 1
|
||||
TransferToLayingStepDisetujui approvalutils.ApprovalStep = 2
|
||||
)
|
||||
|
||||
var TransferToLayingApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
TransferToLayingStepPengajuan: "Pengajuan",
|
||||
TransferToLayingStepDisetujui: "Disetujui",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Validators
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user