mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 07:15:43 +00:00
feat[BE]: Refactor Chickin create and approvals support chickin growing and chickin laying, and create get one project flock kandang API
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS project_chickins (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
project_flock_kandang_id BIGINT NOT NULL,
|
|
||||||
chick_in_date DATE NOT NULL,
|
|
||||||
quantity NUMERIC(15, 3) NOT NULL,
|
|
||||||
note TEXT,
|
|
||||||
created_by BIGINT NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
|
||||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
|
||||||
ALTER TABLE project_chickins
|
|
||||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
|
||||||
FOREIGN KEY (project_flock_kandang_id)
|
|
||||||
REFERENCES project_flock_kandangs(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
|
||||||
ALTER TABLE project_chickins
|
|
||||||
ADD CONSTRAINT fk_created_by
|
|
||||||
FOREIGN KEY (created_by)
|
|
||||||
REFERENCES users(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- INDEXES
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_project_chickins_project_flock_kandang_id ON project_chickins (project_flock_kandang_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_project_chickins_created_by ON project_chickins (created_by);
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS project_flock_populations (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
project_flock_kandang_id BIGINT NOT NULL,
|
|
||||||
initial_quantity NUMERIC(15, 3) NOT NULL,
|
|
||||||
current_quantity NUMERIC(15, 3) NOT NULL,
|
|
||||||
reserved_quantity NUMERIC(15, 3),
|
|
||||||
created_by BIGINT NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
|
||||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
|
||||||
deleted_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
|
||||||
ALTER TABLE project_flock_populations
|
|
||||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
|
||||||
FOREIGN KEY (project_flock_kandang_id)
|
|
||||||
REFERENCES project_flock_kandangs(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
|
||||||
ALTER TABLE project_flock_populations
|
|
||||||
ADD CONSTRAINT fk_created_by
|
|
||||||
FOREIGN KEY (created_by)
|
|
||||||
REFERENCES users(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- INDEXES
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_project_flock_populations_project_flock_kandang_id ON project_flock_populations (project_flock_kandang_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_project_flock_populations_created_by ON project_flock_populations (created_by);
|
|
||||||
@@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS project_chickin_details (
|
|||||||
deleted_at TIMESTAMPTZ
|
deleted_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
-- ============================================
|
-- ============================================
|
||||||
|
|
||||||
-- STEP 1: Hapus tabel jika sudah ada
|
-- STEP 1: Hapus tabel jika sudah ada
|
||||||
|
DROP TABLE IF EXISTS project_chickins;
|
||||||
|
|
||||||
-- STEP 2: Buat tabel project_chickins
|
-- STEP 2: Buat tabel project_chickins
|
||||||
CREATE TABLE IF NOT EXISTS project_chickins (
|
CREATE TABLE IF NOT EXISTS project_chickins (
|
||||||
@@ -20,39 +21,38 @@ CREATE TABLE IF NOT EXISTS project_chickins (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- STEP 3: FOREIGN KEYS
|
-- STEP 3: FOREIGN KEYS
|
||||||
DO $$
|
BEGIN;
|
||||||
BEGIN
|
|
||||||
-- Relasi ke project_flock_kandangs
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
|
||||||
ALTER TABLE project_chickins
|
|
||||||
ADD CONSTRAINT fk_project_chickins_kandang
|
|
||||||
FOREIGN KEY (project_flock_kandang_id)
|
|
||||||
REFERENCES project_flock_kandangs(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Relasi ke product_warehouses
|
-- Relasi ke project_flock_kandangs
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
ALTER TABLE project_chickins
|
||||||
ALTER TABLE project_chickins
|
ADD CONSTRAINT fk_project_chickins_kandang FOREIGN KEY (project_flock_kandang_id) REFERENCES project_flock_kandangs (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
ADD CONSTRAINT fk_project_chickins_warehouse
|
|
||||||
FOREIGN KEY (product_warehouse_id)
|
|
||||||
REFERENCES product_warehouses(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Relasi ke users
|
-- Relasi ke product_warehouses
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
ALTER TABLE project_chickins
|
||||||
ALTER TABLE project_chickins
|
ADD CONSTRAINT fk_project_chickins_warehouse FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
ADD CONSTRAINT fk_project_chickins_created_by
|
|
||||||
FOREIGN KEY (created_by)
|
-- Relasi ke users
|
||||||
REFERENCES users(id)
|
ALTER TABLE project_chickins
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
ADD CONSTRAINT fk_project_chickins_created_by FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
END IF;
|
|
||||||
END $$;
|
COMMIT;
|
||||||
|
|
||||||
-- STEP 4: INDEXES
|
-- STEP 4: INDEXES
|
||||||
CREATE INDEX IF NOT EXISTS idx_chickins_kandang_id ON project_chickins (project_flock_kandang_id);
|
CREATE INDEX IF NOT EXISTS idx_chickins_kandang_id ON project_chickins (project_flock_kandang_id)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_chickins_warehouse_id ON project_chickins (product_warehouse_id);
|
CREATE INDEX IF NOT EXISTS idx_chickins_warehouse_id ON project_chickins (product_warehouse_id)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_chickins_created_by ON project_chickins (created_by);
|
CREATE INDEX IF NOT EXISTS idx_chickins_created_by ON project_chickins (created_by);
|
||||||
|
|
||||||
|
-- Composite index for common queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_chickins_kandang_deleted ON project_chickins (
|
||||||
|
project_flock_kandang_id,
|
||||||
|
deleted_at
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index for soft delete queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_chickins_deleted_at ON project_chickins (deleted_at);
|
||||||
+35
-30
@@ -3,6 +3,7 @@
|
|||||||
-- ============================================
|
-- ============================================
|
||||||
|
|
||||||
-- STEP 1: Hapus tabel jika sudah ada
|
-- STEP 1: Hapus tabel jika sudah ada
|
||||||
|
DROP TABLE IF EXISTS project_flock_populations;
|
||||||
|
|
||||||
-- STEP 2: Buat tabel project_flock_populations
|
-- STEP 2: Buat tabel project_flock_populations
|
||||||
CREATE TABLE IF NOT EXISTS project_flock_populations (
|
CREATE TABLE IF NOT EXISTS project_flock_populations (
|
||||||
@@ -19,39 +20,43 @@ CREATE TABLE IF NOT EXISTS project_flock_populations (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- STEP 3: FOREIGN KEYS
|
-- STEP 3: FOREIGN KEYS
|
||||||
DO $$
|
BEGIN;
|
||||||
BEGIN
|
|
||||||
-- Relasi ke project_chickins
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN
|
|
||||||
ALTER TABLE project_flock_populations
|
|
||||||
ADD CONSTRAINT fk_project_flock_populations_chickin
|
|
||||||
FOREIGN KEY (project_chickin_id)
|
|
||||||
REFERENCES project_chickins(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Relasi ke product_warehouses
|
-- Relasi ke project_chickins
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
ALTER TABLE project_flock_populations
|
||||||
ALTER TABLE project_flock_populations
|
ADD CONSTRAINT fk_project_flock_populations_chickin FOREIGN KEY (project_chickin_id) REFERENCES project_chickins (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
ADD CONSTRAINT fk_project_flock_populations_warehouse
|
|
||||||
FOREIGN KEY (product_warehouse_id)
|
|
||||||
REFERENCES product_warehouses(id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Relasi ke users
|
-- Relasi ke product_warehouses
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
ALTER TABLE project_flock_populations
|
||||||
ALTER TABLE project_flock_populations
|
ADD CONSTRAINT fk_project_flock_populations_warehouse FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
ADD CONSTRAINT fk_project_flock_populations_created_by
|
|
||||||
FOREIGN KEY (created_by)
|
-- Relasi ke users
|
||||||
REFERENCES users(id)
|
ALTER TABLE project_flock_populations
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
ADD CONSTRAINT fk_project_flock_populations_created_by FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
END IF;
|
|
||||||
END $$;
|
COMMIT;
|
||||||
|
|
||||||
-- STEP 4: INDEXES
|
-- STEP 4: INDEXES
|
||||||
CREATE INDEX IF NOT EXISTS idx_populations_chickin_id ON project_flock_populations (project_chickin_id);
|
CREATE INDEX IF NOT EXISTS idx_populations_chickin_id ON project_flock_populations (project_chickin_id)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_populations_warehouse_id ON project_flock_populations (product_warehouse_id);
|
CREATE INDEX IF NOT EXISTS idx_populations_warehouse_id ON project_flock_populations (product_warehouse_id)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_populations_created_by ON project_flock_populations (created_by);
|
CREATE INDEX IF NOT EXISTS idx_populations_created_by ON project_flock_populations (created_by);
|
||||||
|
|
||||||
|
-- Composite index for common queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_populations_chickin_deleted ON project_flock_populations (
|
||||||
|
project_chickin_id,
|
||||||
|
deleted_at
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index for soft delete queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_populations_deleted_at ON project_flock_populations (deleted_at);
|
||||||
|
|
||||||
|
-- Unique constraint: one population per chickin
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_populations_chickin_unique ON project_flock_populations (project_chickin_id)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
@@ -573,6 +573,7 @@ func seedProductCategories(tx *gorm.DB, createdBy uint) (map[string]uint, error)
|
|||||||
}{
|
}{
|
||||||
{"Bahan Baku", "RAW"},
|
{"Bahan Baku", "RAW"},
|
||||||
{"Day Old Chick", "DOC"},
|
{"Day Old Chick", "DOC"},
|
||||||
|
{"Pullet", "PULLET"},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]uint, len(seeds))
|
result := make(map[string]uint, len(seeds))
|
||||||
@@ -787,6 +788,26 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
|||||||
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||||
Flags: []utils.FlagType{utils.FlagPakan, utils.FlagStarter},
|
Flags: []utils.FlagType{utils.FlagPakan, utils.FlagStarter},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "DOC MAlindo",
|
||||||
|
Brand: "MAlindo",
|
||||||
|
Sku: "MAL0001",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Day Old Chick",
|
||||||
|
Price: 8000,
|
||||||
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||||
|
Flags: []utils.FlagType{utils.FlagDOC},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Ayam Pullet",
|
||||||
|
Brand: "MBU Pullet",
|
||||||
|
Sku: "PUL0001",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Pullet",
|
||||||
|
Price: 15000,
|
||||||
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||||
|
Flags: []utils.FlagType{utils.FlagPullet},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, seed := range seeds {
|
for _, seed := range seeds {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ type ProjectFlockKandang struct {
|
|||||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
Chickins []ProjectChickin `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
Chickins []ProjectChickin `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
|
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -18,6 +18,7 @@ type ProductWarehouseRepository interface {
|
|||||||
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
|
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
|
||||||
GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error)
|
GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error)
|
||||||
GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error)
|
GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error)
|
||||||
|
GetFirstProductByCategoryCode(ctx context.Context, categoryCode string) (*entity.Product, error)
|
||||||
WithTxRepo(tx *gorm.DB) ProductWarehouseRepository
|
WithTxRepo(tx *gorm.DB) ProductWarehouseRepository
|
||||||
DB() *gorm.DB
|
DB() *gorm.DB
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,8 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous
|
|||||||
func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) {
|
func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) {
|
||||||
var productWarehouses []entity.ProductWarehouse
|
var productWarehouses []entity.ProductWarehouse
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Product").
|
||||||
|
Preload("Warehouse").
|
||||||
Table("product_warehouses").
|
Table("product_warehouses").
|
||||||
Select("product_warehouses.*").
|
Select("product_warehouses.*").
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
@@ -105,6 +108,8 @@ func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx con
|
|||||||
func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error) {
|
func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error) {
|
||||||
var productWarehouse entity.ProductWarehouse
|
var productWarehouse entity.ProductWarehouse
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Product").
|
||||||
|
Preload("Warehouse").
|
||||||
Table("product_warehouses").
|
Table("product_warehouses").
|
||||||
Select("product_warehouses.*").
|
Select("product_warehouses.*").
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
@@ -118,3 +123,15 @@ func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(c
|
|||||||
}
|
}
|
||||||
return &productWarehouse, nil
|
return &productWarehouse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) GetFirstProductByCategoryCode(ctx context.Context, categoryCode string) (*entity.Product, error) {
|
||||||
|
var product entity.Product
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Joins("JOIN product_categories ON products.product_category_id = product_categories.id").
|
||||||
|
Where("product_categories.code = ?", categoryCode).
|
||||||
|
First(&product).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &product, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
// === DTO Structs (ordered) ===
|
// === DTO Structs (ordered) ===
|
||||||
|
|
||||||
type ChickinBaseDTO struct {
|
type ChickinBaseDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
ChickInDate time.Time `json:"chick_in_date"`
|
ChickInDate time.Time `json:"chick_in_date"`
|
||||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
UsageQty float64 `json:"usage_qty"`
|
UsageQty float64 `json:"usage_qty"`
|
||||||
@@ -55,10 +55,9 @@ type ChickinSimpleDTO struct {
|
|||||||
|
|
||||||
type ChickinListDTO struct {
|
type ChickinListDTO struct {
|
||||||
ChickinBaseDTO
|
ChickinBaseDTO
|
||||||
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
||||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChickinDetailDTO struct {
|
type ChickinDetailDTO struct {
|
||||||
@@ -141,14 +140,17 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
||||||
var pfk *ProjectFlockKandangDTO
|
var projectFlockKandangId uint
|
||||||
|
// Check if ProjectFlockKandang relation is loaded
|
||||||
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
||||||
mapped := ToProjectFlockKandangDTO(*e.ProjectFlockKandang)
|
projectFlockKandangId = e.ProjectFlockKandang.Id
|
||||||
pfk = &mapped
|
} else if e.ProjectFlockKandangId != 0 {
|
||||||
|
// If relation is not loaded but ID is available, use the ID
|
||||||
|
projectFlockKandangId = e.ProjectFlockKandangId
|
||||||
}
|
}
|
||||||
return ChickinBaseDTO{
|
return ChickinBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockKandang: pfk,
|
ProjectFlockKandangId: projectFlockKandangId,
|
||||||
ChickInDate: e.ChickInDate,
|
ChickInDate: e.ChickInDate,
|
||||||
ProductWarehouseId: e.ProductWarehouseId,
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
UsageQty: e.UsageQty,
|
UsageQty: e.UsageQty,
|
||||||
@@ -176,17 +178,11 @@ func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
|||||||
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
var pfk *ProjectFlockKandangDTO
|
|
||||||
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
|
||||||
mapped := ToProjectFlockKandangDTO(*e.ProjectFlockKandang)
|
|
||||||
pfk = &mapped
|
|
||||||
}
|
|
||||||
return ChickinListDTO{
|
return ChickinListDTO{
|
||||||
ChickinBaseDTO: ToChickinBaseDTO(e),
|
ChickinBaseDTO: ToChickinBaseDTO(e),
|
||||||
ProjectFlockKandang: pfk,
|
CreatedUser: createdUser,
|
||||||
CreatedUser: createdUser,
|
CreatedAt: e.CreatedAt,
|
||||||
CreatedAt: e.CreatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,21 +11,24 @@ import (
|
|||||||
type ProjectChickinRepository interface {
|
type ProjectChickinRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectChickin]
|
repository.BaseRepository[entity.ProjectChickin]
|
||||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
||||||
|
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChickinRepositoryImpl struct {
|
type ChickinRepositoryImpl struct {
|
||||||
*repository.BaseRepositoryImpl[entity.ProjectChickin]
|
*repository.BaseRepositoryImpl[entity.ProjectChickin]
|
||||||
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChickinRepository(db *gorm.DB) ProjectChickinRepository {
|
func NewChickinRepository(db *gorm.DB) ProjectChickinRepository {
|
||||||
return &ChickinRepositoryImpl{
|
return &ChickinRepositoryImpl{
|
||||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db),
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db),
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) {
|
func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) {
|
||||||
var chickin entity.ProjectChickin
|
var chickin entity.ProjectChickin
|
||||||
err := r.DB().WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
Where("project_floc_id = ?", projectFlockID).
|
Where("project_floc_id = ?", projectFlockID).
|
||||||
Where("deleted_at IS NULL").
|
Where("deleted_at IS NULL").
|
||||||
First(&chickin).Error
|
First(&chickin).Error
|
||||||
@@ -34,3 +37,15 @@ func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, pr
|
|||||||
}
|
}
|
||||||
return &chickin, nil
|
return &chickin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||||
|
var chickins []entity.ProjectChickin
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&chickins).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return chickins, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -125,13 +125,21 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
var productWarehouses []entity.ProductWarehouse
|
var productWarehouses []entity.ProductWarehouse
|
||||||
|
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
||||||
|
|
||||||
if strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category)) == string(utils.ProjectFlockCategoryGrowing) {
|
var productCategoryCode string
|
||||||
|
switch category {
|
||||||
|
case string(utils.ProjectFlockCategoryGrowing):
|
||||||
|
productCategoryCode = "DOC"
|
||||||
|
case string(utils.ProjectFlockCategoryLaying):
|
||||||
|
productCategoryCode = "PULLET"
|
||||||
|
default:
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Unknown category: %s", category))
|
||||||
|
}
|
||||||
|
|
||||||
productWarehouses, err = s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
|
productWarehouses, err = s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), productCategoryCode, warehouse.Id)
|
||||||
if err != nil || len(productWarehouses) == 0 {
|
if err != nil || len(productWarehouses) == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product for growing category in the Kandang's warehouse not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product for %s category in the Kandang's warehouse not found", strings.ToLower(category)))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
||||||
@@ -142,20 +150,17 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
actorID := uint(1) // todo nanti ambil dari auth context
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
newChikins := make([]*entity.ProjectChickin, 0)
|
newChikins := make([]*entity.ProjectChickin, 0)
|
||||||
for _, productWarehouse := range productWarehouses {
|
for _, productWarehouse := range productWarehouses {
|
||||||
|
newChickin := &entity.ProjectChickin{
|
||||||
if productWarehouse.Quantity > 0 {
|
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||||
newChickin := &entity.ProjectChickin{
|
ChickInDate: chickinDate,
|
||||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
UsageQty: 0,
|
||||||
ChickInDate: chickinDate,
|
PendingUsageQty: productWarehouse.Quantity,
|
||||||
UsageQty: 0,
|
ProductWarehouseId: productWarehouse.Id,
|
||||||
PendingUsageQty: productWarehouse.Quantity,
|
Notes: req.Note,
|
||||||
ProductWarehouseId: productWarehouse.Id,
|
CreatedBy: actorID,
|
||||||
Notes: req.Note,
|
|
||||||
CreatedBy: actorID,
|
|
||||||
}
|
|
||||||
|
|
||||||
newChikins = append(newChikins, newChickin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newChikins = append(newChikins, newChickin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(newChikins) == 0 {
|
if len(newChikins) == 0 {
|
||||||
@@ -219,7 +224,6 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
updateBody["chick_in_date"] = req.ChickInDate
|
updateBody["chick_in_date"] = req.ChickInDate
|
||||||
}
|
}
|
||||||
if req.Note != "" {
|
if req.Note != "" {
|
||||||
// entity uses `Notes` => column `notes`
|
|
||||||
updateBody["notes"] = req.Note
|
updateBody["notes"] = req.Note
|
||||||
}
|
}
|
||||||
if len(updateBody) == 0 {
|
if len(updateBody) == 0 {
|
||||||
@@ -239,7 +243,6 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
|
|
||||||
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
|
||||||
// Simplified delete: directly call repository delete. Complex restore logic removed for now.
|
|
||||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||||
@@ -254,7 +257,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID := uint(1) // todo nanti ambil dari auth context
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
|
|
||||||
var action entity.ApprovalAction
|
var action entity.ApprovalAction
|
||||||
@@ -272,26 +274,40 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate all ProjectFlockKandang IDs exist and have valid approval status
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
||||||
|
for _, id := range approvableIDs {
|
||||||
|
idCopy := id
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &idCopy, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check latest approval status - must be PENGAJUAN to be approvable
|
||||||
|
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||||
|
}
|
||||||
|
if latestApproval == nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for ProjectFlockKandang %d - chickins must be created first", id))
|
||||||
|
}
|
||||||
|
if latestApproval.StepNumber != uint16(utils.ProjectFlockKandangStepPengajuan) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ProjectFlockKandang %d cannot be approved - current status is not in PENGAJUAN stage", id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
step := utils.ProjectFlockKandangStepPengajuan
|
step := utils.ProjectFlockKandangStepPengajuan
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
step = utils.ProjectFlockKandangStepDisetujui
|
step = utils.ProjectFlockKandangStepDisetujui
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||||
chickinRepoTx := s.Repository.WithTx(dbTransaction)
|
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||||
|
|
||||||
for _, approvableID := range approvableIDs {
|
for _, approvableID := range approvableIDs {
|
||||||
|
|
||||||
exists, err := s.ProjectflockKandangRepo.WithTx(dbTransaction).IdExists(c.Context(), approvableID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := approvalSvc.CreateApproval(
|
if _, err := approvalSvc.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
utils.ApprovalWorkflowProjectFlockKandang,
|
utils.ApprovalWorkflowProjectFlockKandang,
|
||||||
@@ -311,31 +327,54 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
|
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
|
|
||||||
var chickins []entity.ProjectChickin
|
chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID)
|
||||||
if err := chickinRepoTx.DB().WithContext(c.Context()).Where("project_flock_kandang_id = ?", approvableID).Find(&chickins).Error; err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chickin := range chickins {
|
kandangForApproval, err := s.ProjectflockKandangRepo.GetByID(c.Context(), approvableID)
|
||||||
population := &entity.ProjectFlockPopulation{
|
if err != nil {
|
||||||
ProjectChickinId: chickin.Id,
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
ProductWarehouseId: chickin.ProductWarehouseId,
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
|
||||||
TotalQty: chickin.PendingUsageQty,
|
|
||||||
TotalUsedQty: 0,
|
|
||||||
Notes: chickin.Notes,
|
|
||||||
CreatedBy: actorID,
|
|
||||||
}
|
|
||||||
if err := ProjectFlockPopulationRepotx.CreateOne(c.Context(), population, nil); err != nil {
|
|
||||||
lower := strings.ToLower(err.Error())
|
|
||||||
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err)
|
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pulletPW, err := s.getOrCreatePulletProductWarehouse(c, warehouse.Id, dbTransaction, actorID)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.convertChickinsToPullet(c, chickins, pulletPW, dbTransaction, actorID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if action == entity.ApprovalActionRejected {
|
||||||
|
|
||||||
|
chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Warnf("failed to get chickins for rejection %d: %v", approvableID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chickin := range chickins {
|
||||||
|
|
||||||
|
updates := map[string]any{"quantity": chickin.PendingUsageQty}
|
||||||
|
|
||||||
|
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||||
|
s.Log.Warnf("failed to restore product warehouse quantity for rejection: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -364,6 +403,93 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *chickinService) getOrCreatePulletProductWarehouse(ctx *fiber.Ctx, warehouseId uint, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) {
|
||||||
|
|
||||||
|
pulletProducts, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), "PULLET", warehouseId)
|
||||||
|
if err == nil && len(pulletProducts) > 0 {
|
||||||
|
return &pulletProducts[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pulletProduct, err := s.ProductWarehouseRepo.GetFirstProductByCategoryCode(ctx.Context(), "PULLET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get PULLET product: %w", err)
|
||||||
|
}
|
||||||
|
if pulletProduct == nil {
|
||||||
|
return nil, fmt.Errorf("no PULLET product found in system")
|
||||||
|
}
|
||||||
|
|
||||||
|
newPulletPW := &entity.ProductWarehouse{
|
||||||
|
ProductId: pulletProduct.Id,
|
||||||
|
WarehouseId: warehouseId,
|
||||||
|
Quantity: 0,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPulletPW, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create PULLET product warehouse: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPulletPW, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []entity.ProjectChickin, pulletPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error {
|
||||||
|
|
||||||
|
if pulletPW == nil || pulletPW.Id == 0 {
|
||||||
|
return fmt.Errorf("invalid PULLET product warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||||
|
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||||
|
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
||||||
|
|
||||||
|
for _, chickin := range chickins {
|
||||||
|
|
||||||
|
quantityToConvert := chickin.PendingUsageQty
|
||||||
|
|
||||||
|
if err := chickinRepoTx.PatchOne(ctx.Context(), chickin.Id, map[string]any{
|
||||||
|
"usage_qty": quantityToConvert,
|
||||||
|
"pending_usage_qty": 0,
|
||||||
|
}, nil); err != nil {
|
||||||
|
return fmt.Errorf("failed to update chickin %d qty: %w", chickin.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update quantity di PULLET product warehouse dengan quantity dari chickin
|
||||||
|
if err := productWarehouseTx.PatchOne(ctx.Context(), pulletPW.Id, map[string]any{
|
||||||
|
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
|
||||||
|
}, nil); err != nil {
|
||||||
|
s.Log.Warnf("failed to update PULLET warehouse quantity: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Warnf("failed to check population existence for chickin %d: %v", chickin.Id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !populationExists {
|
||||||
|
population := &entity.ProjectFlockPopulation{
|
||||||
|
ProjectChickinId: chickin.Id,
|
||||||
|
ProductWarehouseId: pulletPW.Id,
|
||||||
|
TotalQty: quantityToConvert,
|
||||||
|
TotalUsedQty: 0,
|
||||||
|
Notes: chickin.Notes,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
}
|
||||||
|
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
|
||||||
|
lower := strings.ToLower(err.Error())
|
||||||
|
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Log.Infof("population already exists for chickin %d", chickin.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func uniqueUintSlice(values []uint) []uint {
|
func uniqueUintSlice(values []uint) []uint {
|
||||||
seen := make(map[uint]struct{}, len(values))
|
seen := make(map[uint]struct{}, len(values))
|
||||||
result := make([]uint, 0, len(values))
|
result := make([]uint, 0, len(values))
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlockKandangController struct {
|
||||||
|
ProjectFlockKandangService service.ProjectFlockKandangService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectFlockKandangController(projectFlockKandangService service.ProjectFlockKandangService) *ProjectFlockKandangController {
|
||||||
|
return &ProjectFlockKandangController{
|
||||||
|
ProjectFlockKandangService: projectFlockKandangService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectFlockKandangController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
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.ProjectFlockKandangService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.ProjectFlockKandangListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all projectFlockKandangs successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToProjectFlockKandangListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, availableQtys, err := u.ProjectFlockKandangService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get projectFlockKandang successfully",
|
||||||
|
Data: dto.ToProjectFlockKandangListDTOWithAvailableQty(*result, availableQtys),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
|
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||||
|
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||||
|
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
|
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
|
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
chickinDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto"
|
||||||
|
projectFlockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlockKandangBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectFlockCustomDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"`
|
||||||
|
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"`
|
||||||
|
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"`
|
||||||
|
Kandangs []kandangDTO.KandangBaseDTO `json:"kandangs,omitempty"`
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KandangCustomDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WarehouseBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductWarehouseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
Product *ProductBaseDTO `json:"product,omitempty"`
|
||||||
|
Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvailableQtyDTO struct {
|
||||||
|
ProductWarehouse *ProductWarehouseDTO `json:"product_warehouse,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectFlockKandangListDTO struct {
|
||||||
|
ProjectFlockKandangBaseDTO
|
||||||
|
ProjectFlock *ProjectFlockCustomDTO `json:"project_flock,omitempty"`
|
||||||
|
Kandang *KandangCustomDTO `json:"kandang,omitempty"`
|
||||||
|
Chickins []chickinDTO.ChickinBaseDTO `json:"chickins,omitempty"`
|
||||||
|
AvailableQtys []AvailableQtyDTO `json:"available_qtys,omitempty"`
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectFlockKandangDetailDTO struct {
|
||||||
|
ProjectFlockKandangListDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockKandangBaseDTO(e entity.ProjectFlockKandang) ProjectFlockKandangBaseDTO {
|
||||||
|
return ProjectFlockKandangBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toProjectFlockCustomDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockCustomDTO {
|
||||||
|
if pf == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ProjectFlockCustomDTO{
|
||||||
|
Id: pf.Id,
|
||||||
|
Period: pf.Period,
|
||||||
|
Flock: pf.Flock,
|
||||||
|
Area: pf.Area,
|
||||||
|
Category: pf.Category,
|
||||||
|
Fcr: pf.Fcr,
|
||||||
|
Location: pf.Location,
|
||||||
|
Kandangs: pf.Kandangs,
|
||||||
|
CreatedUser: pf.CreatedUser,
|
||||||
|
CreatedAt: pf.CreatedAt,
|
||||||
|
UpdatedAt: pf.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAvailableQtys(chickins []entity.ProjectChickin) []AvailableQtyDTO {
|
||||||
|
if len(chickins) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
availableQtyMap := make(map[uint]AvailableQtyDTO)
|
||||||
|
for _, ch := range chickins {
|
||||||
|
if ch.ProductWarehouse == nil || ch.ProductWarehouse.Quantity <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := availableQtyMap[ch.ProductWarehouseId]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pwDTO := &ProductWarehouseDTO{
|
||||||
|
Id: ch.ProductWarehouse.Id,
|
||||||
|
Quantity: ch.ProductWarehouse.Quantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.ProductWarehouse.Product.Id != 0 {
|
||||||
|
pwDTO.Product = &ProductBaseDTO{
|
||||||
|
Id: ch.ProductWarehouse.Product.Id,
|
||||||
|
Name: ch.ProductWarehouse.Product.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.ProductWarehouse.Warehouse.Id != 0 {
|
||||||
|
pwDTO.Warehouse = &WarehouseBaseDTO{
|
||||||
|
Id: ch.ProductWarehouse.Warehouse.Id,
|
||||||
|
Name: ch.ProductWarehouse.Warehouse.Name,
|
||||||
|
Type: ch.ProductWarehouse.Warehouse.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableQtyMap[ch.ProductWarehouseId] = AvailableQtyDTO{
|
||||||
|
ProductWarehouse: pwDTO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(availableQtyMap) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]AvailableQtyDTO, 0, len(availableQtyMap))
|
||||||
|
for _, v := range availableQtyMap {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildChickins(chickins []entity.ProjectChickin) []chickinDTO.ChickinBaseDTO {
|
||||||
|
if len(chickins) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]chickinDTO.ChickinBaseDTO, len(chickins))
|
||||||
|
for i, ch := range chickins {
|
||||||
|
result[i] = chickinDTO.ToChickinBaseDTO(ch)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultProjectFlockKandangLatestApproval(e entity.ProjectFlockKandang) *approvalDTO.ApprovalBaseDTO {
|
||||||
|
if e.LatestApproval != nil {
|
||||||
|
result := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKandangListDTO {
|
||||||
|
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
|
||||||
|
if e.ProjectFlock.Id != 0 {
|
||||||
|
mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
|
||||||
|
projectFlockSummary = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var kandangSummary *KandangCustomDTO
|
||||||
|
if e.Kandang.Id != 0 {
|
||||||
|
kandangSummary = &KandangCustomDTO{
|
||||||
|
Id: e.Kandang.Id,
|
||||||
|
Name: e.Kandang.Name,
|
||||||
|
Status: e.Kandang.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.ProjectFlock.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
} else if e.ProjectFlock.CreatedBy != 0 {
|
||||||
|
mapped := userDTO.UserBaseDTO{Id: e.ProjectFlock.CreatedBy, IdUser: int64(e.ProjectFlock.CreatedBy)}
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProjectFlockKandangListDTO{
|
||||||
|
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
|
||||||
|
ProjectFlock: toProjectFlockCustomDTO(projectFlockSummary),
|
||||||
|
Kandang: kandangSummary,
|
||||||
|
Chickins: buildChickins(e.Chickins),
|
||||||
|
AvailableQtys: buildAvailableQtys(e.Chickins),
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
Approval: defaultProjectFlockKandangLatestApproval(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockKandangListDTOs(e []entity.ProjectFlockKandang) []ProjectFlockKandangListDTO {
|
||||||
|
result := make([]ProjectFlockKandangListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToProjectFlockKandangListDTO(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockKandangDetailDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDetailDTO {
|
||||||
|
return ProjectFlockKandangDetailDTO{
|
||||||
|
ProjectFlockKandangListDTO: ToProjectFlockKandangListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockKandangListDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtysRaw []map[string]interface{}) ProjectFlockKandangListDTO {
|
||||||
|
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
|
||||||
|
if e.ProjectFlock.Id != 0 {
|
||||||
|
mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
|
||||||
|
projectFlockSummary = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var kandangSummary *KandangCustomDTO
|
||||||
|
if e.Kandang.Id != 0 {
|
||||||
|
kandangSummary = &KandangCustomDTO{
|
||||||
|
Id: e.Kandang.Id,
|
||||||
|
Name: e.Kandang.Name,
|
||||||
|
Status: e.Kandang.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.ProjectFlock.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
} else if e.ProjectFlock.CreatedBy != 0 {
|
||||||
|
mapped := userDTO.UserBaseDTO{Id: e.ProjectFlock.CreatedBy, IdUser: int64(e.ProjectFlock.CreatedBy)}
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert available qtys from raw map data
|
||||||
|
var availableQtys []AvailableQtyDTO
|
||||||
|
if len(availableQtysRaw) > 0 {
|
||||||
|
availableQtys = buildAvailableQtysFromRaw(availableQtysRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProjectFlockKandangListDTO{
|
||||||
|
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
|
||||||
|
ProjectFlock: toProjectFlockCustomDTO(projectFlockSummary),
|
||||||
|
Kandang: kandangSummary,
|
||||||
|
Chickins: buildChickins(e.Chickins),
|
||||||
|
AvailableQtys: availableQtys,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
Approval: defaultProjectFlockKandangLatestApproval(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAvailableQtysFromRaw(availableQtysRaw []map[string]interface{}) []AvailableQtyDTO {
|
||||||
|
if len(availableQtysRaw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]AvailableQtyDTO, len(availableQtysRaw))
|
||||||
|
for i, v := range availableQtysRaw {
|
||||||
|
pwData, ok := v["product_warehouse"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pwDTO := buildProductWarehouseFromMap(pwData)
|
||||||
|
result[i] = AvailableQtyDTO{
|
||||||
|
ProductWarehouse: pwDTO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildProductWarehouseFromMap(pwData map[string]interface{}) *ProductWarehouseDTO {
|
||||||
|
if pwData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &ProductWarehouseDTO{}
|
||||||
|
|
||||||
|
if id, ok := pwData["id"].(float64); ok {
|
||||||
|
dto.Id = uint(id)
|
||||||
|
} else if id, ok := pwData["id"].(uint); ok {
|
||||||
|
dto.Id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
if qty, ok := pwData["quantity"].(float64); ok {
|
||||||
|
dto.Quantity = qty
|
||||||
|
}
|
||||||
|
|
||||||
|
if pData, ok := pwData["product"].(map[string]interface{}); ok {
|
||||||
|
dto.Product = buildProductFromMap(pData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wData, ok := pwData["warehouse"].(map[string]interface{}); ok {
|
||||||
|
dto.Warehouse = buildWarehouseFromMap(wData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildProductFromMap(pData map[string]interface{}) *ProductBaseDTO {
|
||||||
|
if pData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
product := &ProductBaseDTO{}
|
||||||
|
if id, ok := pData["id"].(float64); ok {
|
||||||
|
product.Id = uint(id)
|
||||||
|
} else if id, ok := pData["id"].(uint); ok {
|
||||||
|
product.Id = id
|
||||||
|
}
|
||||||
|
if name, ok := pData["name"].(string); ok {
|
||||||
|
product.Name = name
|
||||||
|
}
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildWarehouseFromMap(wData map[string]interface{}) *WarehouseBaseDTO {
|
||||||
|
if wData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
warehouse := &WarehouseBaseDTO{}
|
||||||
|
if id, ok := wData["id"].(float64); ok {
|
||||||
|
warehouse.Id = uint(id)
|
||||||
|
} else if id, ok := wData["id"].(uint); ok {
|
||||||
|
warehouse.Id = id
|
||||||
|
}
|
||||||
|
if name, ok := wData["name"].(string); ok {
|
||||||
|
warehouse.Name = name
|
||||||
|
}
|
||||||
|
if wType, ok := wData["type"].(string); ok {
|
||||||
|
warehouse.Type = wType
|
||||||
|
}
|
||||||
|
return warehouse
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package project_flock_kandangs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
sProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services"
|
||||||
|
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlockKandangModule struct{}
|
||||||
|
|
||||||
|
func (ProjectFlockKandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
|
|
||||||
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
// register workflow steps for project flock kandang approvals
|
||||||
|
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowProjectFlockKandang, utils.ProjectFlockKandangApprovalSteps); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, warehouseRepo, productWarehouseRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
ProjectFlockKandangRoutes(router, userService, projectFlockKandangService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package project_flock_kandangs
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/controllers"
|
||||||
|
projectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlockKandang.ProjectFlockKandangService) {
|
||||||
|
ctrl := controller.NewProjectFlockKandangController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/project-flock-kandangs")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// 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.Get("/", ctrl.GetAll)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
|
||||||
|
}
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlockKandangService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, []map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectFlockKandangService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
Repository repository.ProjectFlockKandangRepository
|
||||||
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
WarehouseRepo rWarehouse.WarehouseRepository
|
||||||
|
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, validate *validator.Validate) ProjectFlockKandangService {
|
||||||
|
return &projectFlockKandangService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
Repository: repo,
|
||||||
|
ApprovalSvc: approvalSvc,
|
||||||
|
WarehouseRepo: warehouseRepo,
|
||||||
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlockKandangs, err := s.Repository.GetAll(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get projectFlockKandangs: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
total := int64(len(projectFlockKandangs))
|
||||||
|
|
||||||
|
return projectFlockKandangs, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, []map[string]interface{}, error) {
|
||||||
|
projectFlockKandang, err := s.Repository.GetByID(c.Context(), id)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get projectFlockKandang by id: %+v", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(projectFlockKandang.Chickins) > 0 && s.ApprovalSvc != nil {
|
||||||
|
latest, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, nil)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch latest kandang approval for projectFlockKandang %d: %+v", projectFlockKandang.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest != nil {
|
||||||
|
projectFlockKandang.LatestApproval = latest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableQtys, err := s.getAvailableQuantities(c, projectFlockKandang)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch available quantities for kandang %d: %+v", projectFlockKandang.Kandang.Id, err)
|
||||||
|
availableQtys = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectFlockKandang, availableQtys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang) ([]map[string]interface{}, error) {
|
||||||
|
if projectFlockKandang.Kandang.Id == 0 || s.WarehouseRepo == nil || s.ProductWarehouseRepo == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectFlockKandang.Kandang.Id)
|
||||||
|
if err != nil || warehouse == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var productCategoryCode string
|
||||||
|
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
||||||
|
productCategoryCode = "DOC"
|
||||||
|
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
|
productCategoryCode = "PULLET"
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
products, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), productCategoryCode, warehouse.Id)
|
||||||
|
if err != nil || len(products) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []map[string]interface{}
|
||||||
|
for _, pw := range products {
|
||||||
|
if pw.Quantity > 0 {
|
||||||
|
// Build product data
|
||||||
|
productData := map[string]interface{}{
|
||||||
|
"id": pw.Product.Id,
|
||||||
|
"name": pw.Product.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build warehouse data
|
||||||
|
warehouseData := map[string]interface{}{
|
||||||
|
"id": pw.Warehouse.Id,
|
||||||
|
"name": pw.Warehouse.Name,
|
||||||
|
"type": pw.Warehouse.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build product warehouse data with nested product and warehouse
|
||||||
|
productWarehouseData := map[string]interface{}{
|
||||||
|
"id": pw.Id,
|
||||||
|
"quantity": pw.Quantity,
|
||||||
|
"product": productData,
|
||||||
|
"warehouse": warehouseData,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"available_qty": pw.Quantity,
|
||||||
|
"product_warehouse": productWarehouseData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
ProjectFlockId uint `json:"project_flock_id" validate:"required"`
|
||||||
|
KandangId uint `json:"kandang_id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
ProjectFlockId *uint `json:"project_flock_id,omitempty" validate:"omitempty"`
|
||||||
|
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
+13
@@ -11,6 +11,7 @@ import (
|
|||||||
type ProjectFlockPopulationRepository interface {
|
type ProjectFlockPopulationRepository interface {
|
||||||
// domain-specific
|
// 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)
|
||||||
|
|
||||||
// subset of base repository methods used by services
|
// subset of base repository methods used by services
|
||||||
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
||||||
@@ -51,3 +52,15 @@ func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx co
|
|||||||
}
|
}
|
||||||
return &record, nil
|
return &record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockPopulationRepositoryImpl) ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("project_chickin_id = ?", projectChickinID).
|
||||||
|
Model(&entity.ProjectFlockPopulation{}).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
+9
@@ -55,6 +55,9 @@ func (r *projectFlockKandangRepositoryImpl) GetAll(ctx context.Context) ([]entit
|
|||||||
Preload("ProjectFlock.Kandangs").
|
Preload("ProjectFlock.Kandangs").
|
||||||
Preload("ProjectFlock.KandangHistory").
|
Preload("ProjectFlock.KandangHistory").
|
||||||
Preload("Kandang").
|
Preload("Kandang").
|
||||||
|
Preload("Chickins").
|
||||||
|
Preload("Chickins.CreatedUser").
|
||||||
|
Preload("Chickins.ProductWarehouse").
|
||||||
Order("project_flock_id ASC, created_at ASC").
|
Order("project_flock_id ASC, created_at ASC").
|
||||||
Find(&records).Error; err != nil {
|
Find(&records).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -85,6 +88,9 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint
|
|||||||
Preload("ProjectFlock.Kandangs").
|
Preload("ProjectFlock.Kandangs").
|
||||||
Preload("ProjectFlock.KandangHistory").
|
Preload("ProjectFlock.KandangHistory").
|
||||||
Preload("Kandang").
|
Preload("Kandang").
|
||||||
|
Preload("Chickins").
|
||||||
|
Preload("Chickins.CreatedUser").
|
||||||
|
Preload("Chickins.ProductWarehouse").
|
||||||
First(record, id).Error; err != nil {
|
First(record, id).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -104,6 +110,9 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
|
|||||||
Preload("ProjectFlock.Kandangs").
|
Preload("ProjectFlock.Kandangs").
|
||||||
Preload("ProjectFlock.KandangHistory").
|
Preload("ProjectFlock.KandangHistory").
|
||||||
Preload("Kandang").
|
Preload("Kandang").
|
||||||
|
Preload("Chickins").
|
||||||
|
Preload("Chickins.CreatedUser").
|
||||||
|
Preload("Chickins.ProductWarehouse").
|
||||||
First(record).Error; err != nil {
|
First(record).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
||||||
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
||||||
transferLayings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings"
|
transferLayings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings"
|
||||||
|
projectFlockKandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
|||||||
recordings.RecordingModule{},
|
recordings.RecordingModule{},
|
||||||
chickins.ChickinModule{},
|
chickins.ChickinModule{},
|
||||||
transferLayings.TransferLayingModule{},
|
transferLayings.TransferLayingModule{},
|
||||||
|
projectFlockKandangs.ProjectFlockKandangModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
FlagIsActive FlagType = "IS_ACTIVE"
|
FlagIsActive FlagType = "IS_ACTIVE"
|
||||||
|
|
||||||
FlagDOC FlagType = "DOC"
|
FlagDOC FlagType = "DOC"
|
||||||
|
FlagPullet FlagType = "PULLET"
|
||||||
FlagPakan FlagType = "PAKAN"
|
FlagPakan FlagType = "PAKAN"
|
||||||
FlagPreStarter FlagType = "PRE-STARTER"
|
FlagPreStarter FlagType = "PRE-STARTER"
|
||||||
FlagStarter FlagType = "STARTER"
|
FlagStarter FlagType = "STARTER"
|
||||||
@@ -37,6 +38,7 @@ const (
|
|||||||
var flagGroupOptions = map[FlagGroup][]FlagType{
|
var flagGroupOptions = map[FlagGroup][]FlagType{
|
||||||
FlagGroupProduct: {
|
FlagGroupProduct: {
|
||||||
FlagDOC,
|
FlagDOC,
|
||||||
|
FlagPullet,
|
||||||
FlagPakan,
|
FlagPakan,
|
||||||
FlagPreStarter,
|
FlagPreStarter,
|
||||||
FlagStarter,
|
FlagStarter,
|
||||||
|
|||||||
Reference in New Issue
Block a user