mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 06:45:43 +00:00
feat[BE-127]: create available qty API and inisiate sales order and delivery order
This commit is contained in:
@@ -71,6 +71,10 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
||||
if period := c.QueryInt("period", 0); period > 0 {
|
||||
query.Period = period
|
||||
}
|
||||
if category := c.Query("category", ""); category != "" {
|
||||
query.Category = category
|
||||
|
||||
}
|
||||
|
||||
if kandangRaw := c.Query("kandang_id", c.Query("kandang_ids", "")); kandangRaw != "" {
|
||||
ids, err := parseUintList(kandangRaw)
|
||||
|
||||
+15
@@ -14,6 +14,7 @@ type ProjectFlockPopulationRepository interface {
|
||||
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)
|
||||
GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
|
||||
// subset of base repository methods used by services
|
||||
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
||||
@@ -91,3 +92,17 @@ func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangIDAndProd
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
|
||||
var total float64
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("project_flock_populations").
|
||||
Select("COALESCE(SUM(total_qty), 0) AS total_qty").
|
||||
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
||||
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Scan(&total).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
@@ -127,6 +127,9 @@ func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *vali
|
||||
return db
|
||||
}
|
||||
|
||||
if params.Category != "" {
|
||||
db = db.Where("project_flocks.category = ?", params.Category)
|
||||
}
|
||||
if params.AreaId > 0 {
|
||||
db = db.Where("project_flocks.area_id = ?", params.AreaId)
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
|
||||
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
route.Get("/kandangs/:project-flock_kandang-id/periods", ctrl.GetFlockPeriodSummary)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type Query struct {
|
||||
AreaId uint `query:"area_id" validate:"omitempty,number,gt=0"`
|
||||
LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"`
|
||||
Period int `query:"period" validate:"omitempty,number,gt=0"`
|
||||
Category string `query:"category" validate:"omitempty"`
|
||||
KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"`
|
||||
}
|
||||
|
||||
|
||||
+49
-14
@@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
@@ -24,16 +25,8 @@ func NewTransferLayingController(transferLayingService service.TransferLayingSer
|
||||
|
||||
func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
SourceProjectFlockId: uint(c.QueryInt("source_project_flock_id", 0)),
|
||||
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"),
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
@@ -45,8 +38,13 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data := make([]dto.TransferLayingDetailDTO, len(result))
|
||||
for i, transfer := range result {
|
||||
data[i] = dto.ToTransferLayingDetailDTOWithSingleApproval(transfer, transfer.LatestApproval)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.TransferLayingListDTO]{
|
||||
JSON(response.SuccessWithPaginate[dto.TransferLayingDetailDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all transferLayings successfully",
|
||||
@@ -56,7 +54,7 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToTransferLayingListDTOs(result),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -113,7 +111,7 @@ func (u *TransferLayingController) UpdateOne(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %s", err.Error()))
|
||||
}
|
||||
|
||||
result, err := u.TransferLayingService.UpdateOne(c, req, uint(id))
|
||||
@@ -126,7 +124,7 @@ func (u *TransferLayingController) UpdateOne(c *fiber.Ctx) error {
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Update transferLaying successfully",
|
||||
Data: dto.ToTransferLayingListDTO(*result),
|
||||
Data: dto.ToTransferLayingDetailDTOWithSingleApproval(*result, result.LatestApproval),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -180,3 +178,40 @@ func (u *TransferLayingController) Approval(c *fiber.Ctx) error {
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func (u *TransferLayingController) GetAvailableQtyPerKandang(c *fiber.Ctx) error {
|
||||
projectFlockID, err := strconv.ParseUint(c.Params("project_flock_id"), 10, 32)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
|
||||
}
|
||||
|
||||
pf, kandangQtyMap, err := u.TransferLayingService.GetAvailableQtyPerKandang(c, uint(projectFlockID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build kandang list response
|
||||
kandangs := make([]dto.KandangAvailableQtyDTO, 0, len(kandangQtyMap))
|
||||
for kandangPFID, qty := range kandangQtyMap {
|
||||
kandangs = append(kandangs, dto.KandangAvailableQtyDTO{
|
||||
ProjectFlockKandangId: kandangPFID,
|
||||
AvailableQty: qty,
|
||||
})
|
||||
}
|
||||
|
||||
resp := dto.AvailableQtyForTransferDTO{
|
||||
ProjectFlockId: pf.Id,
|
||||
ProjectFlockCode: pf.FlockName,
|
||||
Category: pf.Category,
|
||||
Kandangs: kandangs,
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get available quantity successfully",
|
||||
Data: resp,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -77,6 +77,20 @@ type TransferLayingDetailDTO struct {
|
||||
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||
}
|
||||
|
||||
// === Available Quantity DTOs ===
|
||||
|
||||
type KandangAvailableQtyDTO struct {
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||
AvailableQty float64 `json:"available_qty"`
|
||||
}
|
||||
|
||||
type AvailableQtyForTransferDTO struct {
|
||||
ProjectFlockId uint `json:"project_flock_id"`
|
||||
ProjectFlockCode string `json:"project_flock_code"`
|
||||
Category string `json:"category"`
|
||||
Kandangs []KandangAvailableQtyDTO `json:"kandangs"`
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToProjectFlockSummaryDTO(pf *entity.ProjectFlock) *ProjectFlockSummaryDTO {
|
||||
@@ -207,7 +221,6 @@ func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO {
|
||||
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
|
||||
|
||||
@@ -27,4 +27,5 @@ func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying.
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
route.Get("/project-flocks/:project_flock_id/available-qty", ctrl.GetAvailableQtyPerKandang)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ type TransferLayingService interface {
|
||||
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)
|
||||
GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error)
|
||||
}
|
||||
|
||||
type transferLayingService struct {
|
||||
@@ -92,32 +93,7 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
||||
|
||||
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
if params.SourceProjectFlockId != 0 {
|
||||
db = db.Where("from_project_flock_id = ?", params.SourceProjectFlockId)
|
||||
}
|
||||
if params.TargetProjectFlockId != 0 {
|
||||
db = db.Where("to_project_flock_id = ?", params.TargetProjectFlockId)
|
||||
}
|
||||
if params.TransferDateFrom != "" {
|
||||
db = db.Where("transfer_date >= ?", params.TransferDateFrom)
|
||||
}
|
||||
if params.TransferDateTo != "" {
|
||||
db = db.Where("transfer_date <= ?", params.TransferDateTo)
|
||||
}
|
||||
if params.TransferNumber != "" {
|
||||
db = db.Where("transfer_number ILIKE ?", "%"+params.TransferNumber+"%")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
db = db.Order("created_at DESC")
|
||||
return db
|
||||
})
|
||||
|
||||
@@ -126,19 +102,14 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
for i, transfer := range transferLayings {
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transfer.Id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ActionUser")
|
||||
})
|
||||
if err == nil && latestApproval != nil {
|
||||
transferLayings[i].LatestApproval = latestApproval
|
||||
}
|
||||
transferLayings = filtered
|
||||
}
|
||||
|
||||
return transferLayings, total, nil
|
||||
@@ -155,7 +126,9 @@ func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTran
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, nil)
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ActionUser")
|
||||
})
|
||||
if err == nil && latestApproval != nil {
|
||||
transferLaying.LatestApproval = latestApproval
|
||||
}
|
||||
@@ -547,7 +520,6 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete transfer laying with status %s", action))
|
||||
}
|
||||
}
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
@@ -851,8 +823,6 @@ func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Contex
|
||||
return fiber.NewError(fiber.StatusBadRequest, "No populations found for restoration")
|
||||
}
|
||||
|
||||
// Restore in LIFO order (from newest to oldest)
|
||||
// Add all quantity back to the last (newest) population
|
||||
if len(populations) > 0 {
|
||||
lastPop := populations[len(populations)-1]
|
||||
newQty := lastPop.TotalQty + quantityToRestore
|
||||
@@ -863,3 +833,37 @@ func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Contex
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) {
|
||||
|
||||
pf, err := s.ProjectFlockRepo.GetByID(ctx.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
|
||||
return db
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get project flock %d: %+v", projectFlockID, err)
|
||||
return nil, nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
|
||||
}
|
||||
|
||||
kandangs, _, err := s.ProjectFlockKandangRepo.GetAll(ctx.Context(), 0, 1000, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("project_flock_id = ?", projectFlockID).Order("kandang_id ASC")
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get kandangs for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs")
|
||||
}
|
||||
|
||||
kandangAvailableQty := make(map[uint]float64)
|
||||
for _, kandang := range kandangs {
|
||||
|
||||
totalQty, err := s.ProjectFlockPopulationRepo.GetTotalQtyByProjectFlockKandangID(ctx.Context(), kandang.Id)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to get total qty for kandang %d: %+v", kandang.Id, err)
|
||||
kandangAvailableQty[kandang.Id] = 0
|
||||
continue
|
||||
}
|
||||
|
||||
kandangAvailableQty[kandang.Id] = totalQty
|
||||
}
|
||||
|
||||
return pf, kandangAvailableQty, nil
|
||||
}
|
||||
|
||||
+2
-10
@@ -29,16 +29,8 @@ type Update struct {
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
SourceProjectFlockId uint `query:"source_project_flock_id" validate:"omitempty"`
|
||||
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
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
}
|
||||
|
||||
type Approve struct {
|
||||
|
||||
Reference in New Issue
Block a user