merge: ragil-before-sso from development-before-sso

This commit is contained in:
ragilap
2025-10-28 12:22:08 +07:00
parent 8ae614540f
commit 614da067f7
30 changed files with 309 additions and 110 deletions
@@ -2,6 +2,7 @@ package repository
import (
"context"
"fmt"
"gorm.io/gorm"
)
@@ -32,3 +33,21 @@ func ExistsByName[T any](ctx context.Context, db *gorm.DB, name string, excludeI
}
return count > 0, nil
}
func ExistsByField[T any](ctx context.Context, db *gorm.DB, field string, value any, excludeID *uint) (bool, error) {
if field == "" {
return false, fmt.Errorf("field is required")
}
var count int64
q := db.WithContext(ctx).
Model(new(T)).
Where(fmt.Sprintf("%s = ?", field), value).
Where("deleted_at IS NULL")
if excludeID != nil {
q = q.Where("id <> ?", *excludeID)
}
if err := q.Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
@@ -28,6 +28,11 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
Limit: c.QueryInt("limit", 10),
ProductId: uint(c.QueryInt("product_id", 0)),
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
Flags: c.Query("flags", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.ProductWarehouseService.GetAll(c, query)
@@ -71,5 +76,3 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error {
Data: dto.ToProductWarehouseListDTO(*result),
})
}
@@ -16,6 +16,7 @@ type ProductWarehouseRepository interface {
ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error)
ExistsByID(ctx context.Context, id uint) (bool, error)
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error)
}
type ProductWarehouseRepositoryImpl struct {
@@ -30,6 +31,17 @@ func NewProductWarehouseRepository(db *gorm.DB) ProductWarehouseRepository {
}
}
func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) {
return repository.Exists[entity.Product](ctx, r.db, productId)
}
func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) {
return repository.Exists[entity.Warehouse](ctx, r.db, warehouseId)
}
func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) {
return repository.Exists[entity.ProductWarehouse](ctx, r.db, id)
}
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) {
var count int64
query := r.db.WithContext(ctx).Model(&entity.ProductWarehouse{}).
@@ -43,17 +55,6 @@ func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Cont
return count > 0, nil
}
func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) {
return repository.Exists[entity.Product](ctx, r.db, productId)
}
func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) {
return repository.Exists[entity.Warehouse](ctx, r.db, warehouseId)
}
func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) {
return repository.Exists[entity.ProductWarehouse](ctx, r.db, id)
}
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) {
var count int64
if err := r.db.WithContext(ctx).
@@ -72,3 +73,19 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous
}
return &productWarehouse, nil
}
func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) {
var productWarehouses []entity.ProductWarehouse
err := r.db.WithContext(ctx).
Table("product_warehouses").
Select("product_warehouses.*").
Joins("JOIN products ON products.id = product_warehouses.product_id").
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId).
Order("product_warehouses.created_at DESC").
Find(&productWarehouses).Error
if err != nil {
return nil, err
}
return productWarehouses, nil
}
@@ -49,8 +49,30 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
return nil, 0, err
}
if params.ProductId > 0 {
isProductExist, err := s.Repository.IsProductExist(c.Context(), params.ProductId)
if err != nil {
return nil, 0, err
}
if !isProductExist {
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found")
}
}
if params.WarehouseId > 0 {
isWarehouseExist, err := s.Repository.IsWarehouseExist(c.Context(), params.WarehouseId)
if err != nil {
return nil, 0, err
}
if !isWarehouseExist {
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
}
}
offset := (params.Page - 1) * params.Limit
cleanFlags := utils.ParseFlags(params.Flags)
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db)
@@ -62,6 +84,12 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
db = db.Where("warehouse_id = ?", params.WarehouseId)
}
if len(cleanFlags) > 0 {
db = db.Joins("JOIN products ON products.id = product_warehouses.product_id").
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
Where("flags.name IN ?", cleanFlags)
}
return db.Order("created_at DESC").Order("updated_at DESC")
})
@@ -17,4 +17,5 @@ type Query struct {
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
Flags string `query:"flags" validate:"omitempty"`
}
@@ -29,6 +29,10 @@ func (u *AreaController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.AreaService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *BankController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.BankService.GetAll(c, query)
if err != nil {
return err
@@ -11,6 +11,7 @@ import (
type BankRepository interface {
repository.BaseRepository[entity.Bank]
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error)
}
type BankRepositoryImpl struct {
@@ -28,3 +29,7 @@ func NewBankRepository(db *gorm.DB) BankRepository {
func (r *BankRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
return repository.ExistsByName[entity.Bank](ctx, r.db, name, excludeID)
}
func (r *BankRepositoryImpl) AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error) {
return repository.ExistsByField[entity.Bank](ctx, r.db, "account_number", accountNumber, excludeID)
}
@@ -87,6 +87,13 @@ func (s *bankService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.B
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with name %s already exists", req.Name))
}
if exists, err := s.Repository.AccountNumberExists(c.Context(), req.AccountNumber, nil); err != nil {
s.Log.Errorf("Failed to check bank account number: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check bank account number")
} else if exists {
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with account number %s already exists", req.AccountNumber))
}
createBody := &entity.Bank{
Name: req.Name,
Alias: req.Alias,
@@ -29,6 +29,10 @@ func (u *CustomerController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.CustomerService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *FcrController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.FcrService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *FlockController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.FlockService.GetAll(c, query)
if err != nil {
return err
@@ -31,6 +31,10 @@ func (u *KandangController) GetAll(c *fiber.Ctx) error {
PicId: c.QueryInt("pic_id", 0),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.KandangService.GetAll(c, query)
if err != nil {
return err
@@ -30,6 +30,10 @@ func (u *LocationController) GetAll(c *fiber.Ctx) error {
AreaId: c.QueryInt("area_id", 0),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.LocationService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *NonstockController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.NonstockService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *ProductCategoryController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.ProductCategoryService.GetAll(c, query)
if err != nil {
return err
@@ -30,6 +30,10 @@ func (u *ProductController) GetAll(c *fiber.Ctx) error {
ProductCategoryID: c.QueryInt("product_category_id", 0),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.ProductService.GetAll(c, query)
if err != nil {
return err
@@ -29,6 +29,10 @@ func (u *SupplierController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.SupplierService.GetAll(c, query)
if err != nil {
return err
@@ -11,7 +11,7 @@ import (
type SupplierRepository interface {
repository.BaseRepository[entity.Supplier]
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error)
}
type SupplierRepositoryImpl struct {
@@ -29,3 +29,7 @@ func NewSupplierRepository(db *gorm.DB) SupplierRepository {
func (r *SupplierRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
return repository.ExistsByName[entity.Supplier](ctx, r.db, name, excludeID)
}
func (r *SupplierRepositoryImpl) AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) {
return repository.ExistsByField[entity.Supplier](ctx, r.db, "alias", alias, excludeID)
}
@@ -88,6 +88,13 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with name %s already exists", req.Name))
}
if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(req.Alias)), nil); err != nil {
s.Log.Errorf("Failed to check supplier alias: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias")
} else if exists {
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(req.Alias))))
}
typ := strings.ToUpper(req.Type)
if !utils.IsValidCustomerSupplierType(typ) {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid supplier type")
@@ -143,6 +150,12 @@ func (s supplierService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint
}
if req.Alias != nil {
if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(*req.Alias)), &id); err != nil {
s.Log.Errorf("Failed to check supplier alias: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias")
} else if exists {
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(*req.Alias))))
}
updateBody["alias"] = strings.TrimSpace(strings.ToUpper(*req.Alias))
}
@@ -29,6 +29,10 @@ func (u *UomController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.UomService.GetAll(c, query)
if err != nil {
return err
@@ -30,6 +30,10 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
AreaId: c.QueryInt("area_id", 0),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.WarehouseService.GetAll(c, query)
if err != nil {
return err
@@ -121,14 +121,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
return nil, err
}
var productWarehouses []entity.ProductWarehouse
err = s.ProductWarehouseRepo.DB().
WithContext(c.Context()).
Joins("JOIN products ON products.id = product_warehouses.product_id").
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
Order("created_at DESC").
Find(&productWarehouses).Error
// move complex DB query into repository for cleaner service
productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
if err != nil {
s.Log.Errorf("Failed to get product warehouses: %+v", err)
return nil, err
@@ -136,8 +130,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
if len(productWarehouses) == 0 {
return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
}
// Jumlahkan semua quantity DOC
totalQuantity := 0.0
for _, pw := range productWarehouses {
totalQuantity += pw.Quantity
@@ -147,7 +139,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses")
}
// Buat satu chickin dengan total quantity
chickinDate, err := utils.ParseDateString(req.ChickInDate)
if err != nil {
s.Log.Errorf("Failed to parse chickin date: %+v", err)
@@ -157,7 +148,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
ProjectFlockKandangId: projectflockkandang.Id,
ChickInDate: chickinDate,
Quantity: totalQuantity,
Note: "",
Note: req.Note,
CreatedBy: 1, //todo: ganti dengan user login
}
err = s.Repository.CreateOne(c.Context(), newChickin, nil)
@@ -176,7 +167,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
return nil, err
}
// add ke detail chickin
newChickinDetail := &entity.ProjectChickinDetail{
ProjectChickinId: newChickin.Id,
ProductWarehouseId: pw.Id,
@@ -232,6 +222,9 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
if req.ChickInDate != "" {
updateBody["chick_in_date"] = req.ChickInDate
}
if req.Note != "" {
updateBody["note"] = req.Note
}
if len(updateBody) == 0 {
return s.GetOne(c, id)
}
@@ -293,7 +286,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
return rollback(err)
}
// helper: restore quantities from details; returns (restored bool, error)
restoreFromDetails := func() (bool, error) {
var details []entity.ProjectChickinDetail
if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil {
@@ -3,10 +3,12 @@ package validation
type Create struct {
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
Note string `json:"note" validate:"omitempty`
}
type Update struct {
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
Note string `json:"note" validate:"omitempty"`
}
type Query struct {
@@ -246,17 +246,39 @@ func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
}
func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
projectFlockIdStr := c.Query("project_flock_id", "")
kandangIdStr := c.Query("kandang_id", "")
projectFlockId := c.QueryInt("project_flock_id", 0)
kandangId := c.QueryInt("kandang_id", 0)
result, err := u.ProjectflockService.GetProjectFlockKandangByParams(c, "", projectFlockIdStr, kandangIdStr)
if projectFlockId == 0 || kandangId == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id or kandang_id")
}
result, availableStock, err := u.ProjectflockService.GetProjectFlockKandangByProjectAndKandang(c, uint(projectFlockId), uint(kandangId))
if err != nil {
return err
}
dtoResult := dto.ToProjectFlockKandangDTO(*result)
dtoResult.AvailableQuantity = float64(availableStock)
// populate available quantity for each kandang inside project_flock
if dtoResult.ProjectFlock != nil {
for i := range dtoResult.ProjectFlock.Kandangs {
kand := &dtoResult.ProjectFlock.Kandangs[i]
if kand.Id == 0 {
continue
}
if q, qerr := u.ProjectflockService.GetAvailableDocQuantity(c, kand.Id); qerr == nil {
kand.AvailableQuantity = q
}
}
// remove inner kandangs from project_flock to avoid duplication
dtoResult.ProjectFlock.Kandangs = nil
}
return c.Status(fiber.StatusOK).
JSON(response.Success{Code: fiber.StatusOK,
Status: "success",
Message: "Get projectflock kandang successfully",
Data: dto.ToProjectFlockKandangDTO(*result)})
Data: dtoResult})
}
@@ -10,10 +10,9 @@ import (
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
)
// internal DTO used only for lookup response: project flock with kandangs carrying pivot ids
type KandangWithPivotDTO struct {
kandangDTO.KandangBaseDTO
ProjectFlockKandangId *uint `json:"project_flock_kandang_id,omitempty"`
AvailableQuantity float64 `json:"available_quantity"`
}
type ProjectFlockWithPivotDTO struct {
@@ -29,10 +28,12 @@ type ProjectFlockWithPivotDTO struct {
type ProjectFlockKandangDTO struct {
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
ProjectFlockId uint `json:"project_flock_id"`
KandangId uint `json:"kandang_id"`
Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"`
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
AvailableQuantity float64 `json:"available_quantity"`
}
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
@@ -44,7 +45,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
var pf *ProjectFlockWithPivotDTO
if e.ProjectFlock.Id != 0 {
// build project flock with kandangs that include pivot ids
pfLocal := ProjectFlockWithPivotDTO{
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
Id: e.ProjectFlock.Id,
@@ -53,7 +54,6 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
Category: e.ProjectFlock.Category,
}
// fill related small summaries
if e.ProjectFlock.Flock.Id != 0 {
mapped := ToFlockSummaryDTO(e.ProjectFlock.Flock)
pfLocal.Flock = &mapped
@@ -75,23 +75,16 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
pfLocal.CreatedUser = &mapped
}
// build pivot map
pivotMap := make(map[uint]uint)
for _, ph := range e.ProjectFlock.KandangHistory {
pivotMap[ph.KandangId] = ph.Id
}
// populate kandangs with pivot ids
for _, k := range e.ProjectFlock.Kandangs {
kb := kandangDTO.ToKandangBaseDTO(k)
var pid *uint
if v, ok := pivotMap[k.Id]; ok {
vv := v
pid = &vv
}
pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{
KandangBaseDTO: kb,
ProjectFlockKandangId: pid,
AvailableQuantity: 0,
})
}
@@ -100,9 +93,11 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
return ProjectFlockKandangDTO{
Id: e.Id,
ProjectFlockKandangId: e.Id,
ProjectFlockId: e.ProjectFlockId,
KandangId: e.KandangId,
Kandang: kandang,
ProjectFlock: pf,
AvailableQuantity: 0,
}
}
@@ -9,8 +9,10 @@ import (
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
"gorm.io/gorm"
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
@@ -27,6 +29,8 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid
kandangRepo := rKandang.NewKandangRepository(db)
projectflockRepo := rProjectflock.NewProjectflockRepository(db)
projectflockKandangRepo := rProjectflock.NewProjectFlockKandangRepository(db)
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
userRepo := rUser.NewUserRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db)
@@ -35,7 +39,7 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid
panic(fmt.Sprintf("failed to register project flock approval workflow: %v", err))
}
projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, approvalService, validate)
projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, warehouseRepo, productWarehouseRepo, approvalService, validate)
userService := sUser.NewUserService(userRepo, validate)
ProjectflockRoutes(router, userService, projectflockService)
@@ -4,14 +4,15 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"
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"
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
warehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -29,7 +30,8 @@ type ProjectflockService interface {
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error)
DeleteOne(ctx *fiber.Ctx, id uint) error
GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error)
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error)
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error)
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
}
@@ -40,6 +42,8 @@ type projectflockService struct {
Repository repository.ProjectflockRepository
FlockRepo flockRepository.FlockRepository
KandangRepo kandangRepository.KandangRepository
WarehouseRepo warehouseRepository.WarehouseRepository
ProductWarehouseRepo productWarehouseRepository.ProductWarehouseRepository
PivotRepo repository.ProjectFlockKandangRepository
ApprovalSvc commonSvc.ApprovalService
approvalWorkflow approvalutils.ApprovalWorkflowKey
@@ -55,6 +59,8 @@ func NewProjectflockService(
flockRepo flockRepository.FlockRepository,
kandangRepo kandangRepository.KandangRepository,
pivotRepo repository.ProjectFlockKandangRepository,
warehouseRepo warehouseRepository.WarehouseRepository,
productWarehouseRepo productWarehouseRepository.ProductWarehouseRepository,
approvalSvc commonSvc.ApprovalService,
validate *validator.Validate,
) ProjectflockService {
@@ -64,6 +70,8 @@ func NewProjectflockService(
Repository: repo,
FlockRepo: flockRepo,
KandangRepo: kandangRepo,
WarehouseRepo: warehouseRepo,
ProductWarehouseRepo: productWarehouseRepo,
PivotRepo: pivotRepo,
ApprovalSvc: approvalSvc,
approvalWorkflow: utils.ApprovalWorkflowProjectFlock,
@@ -648,11 +656,6 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
return nil
}
func (s projectflockService) GetProjectFlockKandang(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, error) {
// keep for backward compatibility; delegate to new consolidated method
return s.GetProjectFlockKandangByParams(ctx, fmt.Sprintf("%d", id), "", "")
}
func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) {
pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID)
@@ -665,38 +668,30 @@ func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fibe
return pfk, nil
}
func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) {
idStr = strings.TrimSpace(idStr)
projectFlockIdStr = strings.TrimSpace(projectFlockIdStr)
kandangIdStr = strings.TrimSpace(kandangIdStr)
func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) {
if idStr != "" {
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
}
pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id))
wh, err := s.WarehouseRepo.GetByKandangID(ctx.Context(), kandangID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
}
return nil, err
}
return pfk, nil
return 0, err
}
if projectFlockIdStr == "" || kandangIdStr == "" {
return nil, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters")
var productWarehouses []entity.ProductWarehouse
err = s.ProductWarehouseRepo.DB().
WithContext(ctx.Context()).
Joins("JOIN products ON products.id = product_warehouses.product_id").
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", wh.Id).
Order("created_at DESC").
Find(&productWarehouses).Error
if err != nil {
return 0, err
}
pfid, err := strconv.Atoi(projectFlockIdStr)
if err != nil || pfid <= 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
total := 0.0
for _, pw := range productWarehouses {
total += pw.Quantity
}
kid, err := strconv.Atoi(kandangIdStr)
if err != nil || kid <= 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
}
return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid))
return total, nil
}
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) {
@@ -853,7 +848,6 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *
return nil
}
func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository {
if s.PivotRepo == nil {
return repository.NewProjectFlockKandangRepository(dbTransaction)
@@ -874,3 +868,4 @@ func (s projectflockService) anyKandangLinkedToOtherProject(ctx context.Context,
}
return count > 0, nil
}
+37 -1
View File
@@ -1,6 +1,9 @@
package utils
import "strings"
import (
"sort"
"strings"
)
// NormalizeTrim returns the input string without leading/trailing whitespace.
func NormalizeTrim(value string) string {
@@ -11,3 +14,36 @@ func NormalizeTrim(value string) string {
func NormalizeUpper(value string) string {
return strings.ToUpper(NormalizeTrim(value))
}
// NormalizeFlag trims whitespace, removes surrounding brackets/quotes and returns upper-case flag
func NormalizeFlag(value string) string {
v := NormalizeTrim(value)
v = strings.Trim(v, "[]\"'")
return strings.ToUpper(v)
}
// ParseFlags parses a raw flags string like "[DOC, PAKAN]" or "DOC,PAKAN"
// and returns a deduplicated, sorted slice of normalized flags (upper-case, trimmed).
func ParseFlags(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
set := make(map[string]struct{}, len(parts))
for _, p := range parts {
f := NormalizeFlag(p)
if f == "" {
continue
}
set[f] = struct{}{}
}
if len(set) == 0 {
return nil
}
res := make([]string, 0, len(set))
for k := range set {
res = append(res, k)
}
sort.Strings(res)
return res
}
+4
View File
@@ -29,6 +29,10 @@ func (u *{{Pascal .Entity}}Controller) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
result, totalResults, err := u.{{Pascal .Entity}}Service.GetAll(c, query)
if err != nil {
return err