mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/US-75/chick-in-doc' into 'development-before-sso'
feat[BE]: Refactor Chickin create and approvals support chickin growing and... See merge request mbugroup/lti-api!52
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
|
||||
);
|
||||
|
||||
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN
|
||||
|
||||
+26
-18
@@ -1,22 +1,30 @@
|
||||
|
||||
ALTER TABLE kandangs
|
||||
DROP CONSTRAINT IF EXISTS kandangs_project_flock_id_fkey;
|
||||
DROP CONSTRAINT IF EXISTS kandangs_project_flock_id_fkey;
|
||||
|
||||
ALTER TABLE kandangs
|
||||
DROP COLUMN IF EXISTS project_flock_id;
|
||||
ALTER TABLE kandangs DROP COLUMN IF EXISTS project_flock_id;
|
||||
|
||||
ALTER TABLE project_chickins
|
||||
DROP CONSTRAINT fk_project_flock_kandang_id,
|
||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||
FOREIGN KEY (project_flock_kandang_id)
|
||||
REFERENCES project_flock_kandangs(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE;
|
||||
-- Only alter if tables exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN
|
||||
ALTER TABLE project_chickins
|
||||
DROP CONSTRAINT IF EXISTS fk_project_flock_kandang_id;
|
||||
ALTER TABLE project_chickins
|
||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||
FOREIGN KEY (project_flock_kandang_id)
|
||||
REFERENCES project_flock_kandangs(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE;
|
||||
END IF;
|
||||
|
||||
ALTER TABLE project_flock_populations
|
||||
DROP CONSTRAINT fk_project_flock_kandang_id,
|
||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||
FOREIGN KEY (project_flock_kandang_id)
|
||||
REFERENCES project_flock_kandangs(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE;
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_populations') THEN
|
||||
ALTER TABLE project_flock_populations
|
||||
DROP CONSTRAINT IF EXISTS fk_project_flock_kandang_id;
|
||||
ALTER TABLE project_flock_populations
|
||||
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||
FOREIGN KEY (project_flock_kandang_id)
|
||||
REFERENCES project_flock_kandangs(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS laying_transfers CASCADE;
|
||||
@@ -0,0 +1,52 @@
|
||||
CREATE TABLE IF NOT EXISTS laying_transfers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
transfer_number VARCHAR(50) UNIQUE NOT NULL,
|
||||
from_project_flock_id BIGINT NOT NULL,
|
||||
to_project_flock_id BIGINT NOT NULL,
|
||||
transfer_date DATE NOT NULL,
|
||||
pending_usage_qty NUMERIC(15, 3),
|
||||
usage_qty NUMERIC(15, 3),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL
|
||||
);
|
||||
|
||||
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flocks') THEN
|
||||
ALTER TABLE laying_transfers
|
||||
ADD CONSTRAINT fk_laying_from_project_flock
|
||||
FOREIGN KEY (from_project_flock_id)
|
||||
REFERENCES project_flocks(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE laying_transfers
|
||||
ADD CONSTRAINT fk_laying_to_project_flock
|
||||
FOREIGN KEY (to_project_flock_id)
|
||||
REFERENCES project_flocks(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||
ALTER TABLE laying_transfers
|
||||
ADD CONSTRAINT fk_laying_created_by
|
||||
FOREIGN KEY (created_by)
|
||||
REFERENCES users(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- INDEXES
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_laying_transfers_transfer_number ON laying_transfers (transfer_number)
|
||||
WHERE
|
||||
deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_laying_transfers_from_project_flock_id ON laying_transfers (from_project_flock_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_laying_transfers_to_project_flock_id ON laying_transfers (to_project_flock_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_laying_transfers_created_by ON laying_transfers (created_by);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_laying_transfers_deleted_at ON laying_transfers (deleted_at);
|
||||
@@ -0,0 +1,58 @@
|
||||
-- ============================================
|
||||
-- MIGRATION: project_chickins
|
||||
-- ============================================
|
||||
|
||||
-- STEP 1: Hapus tabel jika sudah ada
|
||||
DROP TABLE IF EXISTS project_chickins;
|
||||
|
||||
-- STEP 2: Buat tabel project_chickins
|
||||
CREATE TABLE IF NOT EXISTS project_chickins (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_flock_kandang_id BIGINT NOT NULL,
|
||||
product_warehouse_id BIGINT NOT NULL,
|
||||
chick_in_date DATE NOT NULL,
|
||||
usage_qty NUMERIC(15, 3) NOT NULL,
|
||||
pending_usage_qty NUMERIC(15, 3) DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_by BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- STEP 3: FOREIGN KEYS
|
||||
BEGIN;
|
||||
|
||||
-- Relasi ke project_flock_kandangs
|
||||
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;
|
||||
|
||||
-- Relasi ke product_warehouses
|
||||
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;
|
||||
|
||||
-- Relasi ke users
|
||||
ALTER TABLE project_chickins
|
||||
ADD CONSTRAINT fk_project_chickins_created_by FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- STEP 4: INDEXES
|
||||
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)
|
||||
WHERE
|
||||
deleted_at IS NULL;
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,62 @@
|
||||
-- ============================================
|
||||
-- MIGRATION: project_flock_populations
|
||||
-- ============================================
|
||||
|
||||
-- STEP 1: Hapus tabel jika sudah ada
|
||||
DROP TABLE IF EXISTS project_flock_populations;
|
||||
|
||||
-- STEP 2: Buat tabel project_flock_populations
|
||||
CREATE TABLE IF NOT EXISTS project_flock_populations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_chickin_id BIGINT NOT NULL,
|
||||
product_warehouse_id BIGINT NOT NULL,
|
||||
total_qty NUMERIC(15, 3) NOT NULL,
|
||||
total_used_qty NUMERIC(15, 3) DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_by BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- STEP 3: FOREIGN KEYS
|
||||
BEGIN;
|
||||
|
||||
-- Relasi ke project_chickins
|
||||
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;
|
||||
|
||||
-- Relasi ke product_warehouses
|
||||
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;
|
||||
|
||||
-- Relasi ke users
|
||||
ALTER TABLE project_flock_populations
|
||||
ADD CONSTRAINT fk_project_flock_populations_created_by FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- STEP 4: INDEXES
|
||||
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)
|
||||
WHERE
|
||||
deleted_at IS NULL;
|
||||
|
||||
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;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- Rollback laying_transfer_sources dan laying_transfer_targets tables
|
||||
|
||||
DROP TABLE IF EXISTS laying_transfer_targets CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS laying_transfer_sources CASCADE;
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
-- Create laying_transfer_sources dan laying_transfer_targets tables
|
||||
|
||||
-- 1. Create laying_transfer_sources table (detail sumber - kandang asal growing)
|
||||
CREATE TABLE laying_transfer_sources (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
laying_transfer_id BIGINT NOT NULL,
|
||||
source_project_flock_kandang_id BIGINT NOT NULL,
|
||||
product_warehouse_id BIGINT,
|
||||
qty NUMERIC(15, 3) NOT NULL,
|
||||
note TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Add foreign keys untuk laying_transfer_sources
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'laying_transfers') THEN
|
||||
ALTER TABLE laying_transfer_sources
|
||||
ADD CONSTRAINT fk_laying_transfer_sources_laying_transfer_id
|
||||
FOREIGN KEY (laying_transfer_id) REFERENCES laying_transfers(id) ON DELETE CASCADE;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
||||
ALTER TABLE laying_transfer_sources
|
||||
ADD CONSTRAINT fk_laying_transfer_sources_project_flock_kandang_id
|
||||
FOREIGN KEY (source_project_flock_kandang_id) REFERENCES project_flock_kandangs(id) ON DELETE RESTRICT;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
||||
ALTER TABLE laying_transfer_sources
|
||||
ADD CONSTRAINT fk_laying_transfer_sources_product_warehouse_id
|
||||
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 2. Create laying_transfer_targets table (detail tujuan - kandang laying)
|
||||
CREATE TABLE laying_transfer_targets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
laying_transfer_id BIGINT NOT NULL,
|
||||
target_project_flock_kandang_id BIGINT NOT NULL,
|
||||
qty NUMERIC(15, 3) NOT NULL,
|
||||
product_warehouse_id BIGINT,
|
||||
note TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Add foreign keys untuk laying_transfer_targets
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'laying_transfers') THEN
|
||||
ALTER TABLE laying_transfer_targets
|
||||
ADD CONSTRAINT fk_laying_transfer_targets_laying_transfer_id
|
||||
FOREIGN KEY (laying_transfer_id) REFERENCES laying_transfers(id) ON DELETE CASCADE;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
||||
ALTER TABLE laying_transfer_targets
|
||||
ADD CONSTRAINT fk_laying_transfer_targets_project_flock_kandang_id
|
||||
FOREIGN KEY (target_project_flock_kandang_id) REFERENCES project_flock_kandangs(id) ON DELETE RESTRICT;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
||||
ALTER TABLE laying_transfer_targets
|
||||
ADD CONSTRAINT fk_laying_transfer_targets_product_warehouse_id
|
||||
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 3. Create indexes untuk laying_transfer_sources
|
||||
CREATE INDEX idx_laying_transfer_sources_laying_transfer_id ON laying_transfer_sources (laying_transfer_id);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_sources_source_kandang_id ON laying_transfer_sources (
|
||||
source_project_flock_kandang_id
|
||||
);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_sources_product_warehouse_id ON laying_transfer_sources (product_warehouse_id);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_sources_deleted_at ON laying_transfer_sources (deleted_at);
|
||||
|
||||
-- 4. Create indexes untuk laying_transfer_targets
|
||||
CREATE INDEX idx_laying_transfer_targets_laying_transfer_id ON laying_transfer_targets (laying_transfer_id);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_targets_target_kandang_id ON laying_transfer_targets (
|
||||
target_project_flock_kandang_id
|
||||
);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_targets_product_warehouse_id ON laying_transfer_targets (product_warehouse_id);
|
||||
|
||||
CREATE INDEX idx_laying_transfer_targets_deleted_at ON laying_transfer_targets (deleted_at);
|
||||
@@ -571,52 +571,54 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
||||
Flags: []utils.FlagType{utils.FlagDOC},
|
||||
},
|
||||
{
|
||||
Name: "Ayam Afkir",
|
||||
Brand: "-",
|
||||
Sku: "1",
|
||||
Name: "Ayam Pullet",
|
||||
Brand: "MBU Pullet",
|
||||
Sku: "PLT0001",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
|
||||
|
||||
Category: "Pullet",
|
||||
Price: 15000,
|
||||
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||
Flags: []utils.FlagType{utils.FlagPullet},
|
||||
},
|
||||
{
|
||||
Name: "Ayam Mati",
|
||||
Brand: "-",
|
||||
Sku: "2",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
|
||||
|
||||
Name: "Ayam Afkir",
|
||||
Brand: "-",
|
||||
Sku: "1",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
},
|
||||
{
|
||||
Name: "Ayam Culling",
|
||||
Brand: "-",
|
||||
Sku: "3",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
|
||||
|
||||
Name: "Ayam Mati",
|
||||
Brand: "-",
|
||||
Sku: "2",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
},
|
||||
{
|
||||
Name: "Telur Konsumsi Baik",
|
||||
Brand: "-",
|
||||
Sku: "4",
|
||||
Uom: "Unit",
|
||||
Category: "Telur",
|
||||
Price: 1,
|
||||
|
||||
Name: "Ayam Culling",
|
||||
Brand: "-",
|
||||
Sku: "3",
|
||||
Uom: "Ekor",
|
||||
Category: "Day Old Chick",
|
||||
Price: 1,
|
||||
},
|
||||
{
|
||||
Name: "Telur Pecah",
|
||||
Brand: "-",
|
||||
Sku: "5",
|
||||
Uom: "Unit",
|
||||
Category: "Telur",
|
||||
Price: 1,
|
||||
|
||||
Name: "Telur Konsumsi Baik",
|
||||
Brand: "-",
|
||||
Sku: "4",
|
||||
Uom: "Unit",
|
||||
Category: "Telur",
|
||||
Price: 1,
|
||||
},
|
||||
{
|
||||
Name: "Telur Pecah",
|
||||
Brand: "-",
|
||||
Sku: "5",
|
||||
Uom: "Unit",
|
||||
Category: "Telur",
|
||||
Price: 1,
|
||||
},
|
||||
{
|
||||
Name: "281 SPECIAL STARTER",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingKandangTransfer struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
KandangId uint
|
||||
ProductWarehouseId uint
|
||||
Qty float64 `gorm:"type:numeric(15,3)"`
|
||||
LayingTransferId uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
LayingTransfer *LayingTransfer `gorm:"foreignKey:LayingTransferId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingTransfer struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
TransferNumber string `gorm:"uniqueIndex;not null"`
|
||||
FromProjectFlockId uint `gorm:"not null"`
|
||||
ToProjectFlockId uint `gorm:"not null"`
|
||||
TransferDate time.Time `gorm:"type:date;not null"`
|
||||
PendingUsageQty *float64 `gorm:"type:numeric(15,3)"`
|
||||
UsageQty *float64 `gorm:"type:numeric(15,3)"`
|
||||
Notes string `gorm:"type:text"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
FromProjectFlock *ProjectFlock `gorm:"foreignKey:FromProjectFlockId;references:Id"`
|
||||
ToProjectFlock *ProjectFlock `gorm:"foreignKey:ToProjectFlockId;references:Id"`
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingTransferSource struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
LayingTransferId uint `gorm:"index;not null"`
|
||||
SourceProjectFlockKandangId uint `gorm:"not null"`
|
||||
ProductWarehouseId *uint `gorm:""`
|
||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
Note string `gorm:"type:text"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
LayingTransfer *LayingTransfer `gorm:"foreignKey:LayingTransferId;references:Id"`
|
||||
SourceProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:SourceProjectFlockKandangId;references:Id"`
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingTransferTarget struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
LayingTransferId uint `gorm:"index;not null"`
|
||||
TargetProjectFlockKandangId uint `gorm:"not null"`
|
||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
ProductWarehouseId *uint `gorm:""`
|
||||
Note string `gorm:"type:text"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
LayingTransfer *LayingTransfer `gorm:"foreignKey:LayingTransferId;references:Id"`
|
||||
TargetProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:TargetProjectFlockKandangId;references:Id"`
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ type ProjectChickin struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
ProjectFlockKandangId uint `gorm:"not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
ChickInDate time.Time `gorm:"not null"`
|
||||
Quantity float64 `gorm:"not null"`
|
||||
Note string `gorm:"type:text"`
|
||||
ProductWarehouseId uint `gorm:"not null"`
|
||||
UsageQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
PendingUsageQty float64 `gorm:"type:numeric(15,3);default:0"`
|
||||
Notes string `gorm:"type:text"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
ProjectFlockKandang ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
}
|
||||
|
||||
@@ -7,17 +7,18 @@ import (
|
||||
)
|
||||
|
||||
type ProjectFlockPopulation struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
ProjectFlockKandangId uint `gorm:"not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
InitialQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
||||
CurrentQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
||||
ReservedQuantity float64 `gorm:"type:numeric(15,3)"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Id uint `gorm:"primaryKey"`
|
||||
ProjectChickinId uint `gorm:"not null"`
|
||||
ProductWarehouseId uint `gorm:"not null"`
|
||||
TotalQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
TotalUsedQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||
Notes string `gorm:"type:text"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
ProjectChickin *ProjectChickin `gorm:"foreignKey:ProjectChickinId;references:Id"`
|
||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
}
|
||||
|
||||
@@ -28,3 +28,4 @@ type ProjectFlock struct {
|
||||
|
||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package entities
|
||||
import "time"
|
||||
|
||||
type ProjectFlockKandang struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
|
||||
|
||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||
Id uint `gorm:"primaryKey"`
|
||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
|
||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||
Chickins []ProjectChickin `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ func (r *ConstantRepositoryImpl) GetConstants() map[string]interface{} {
|
||||
"LOKASI",
|
||||
"KANDANG",
|
||||
},
|
||||
"stock_log": map[string][]string{
|
||||
"log_types": []string{"TRANSFER", "ADJUSTMENT"},
|
||||
"transaction_types": []string{"INCREASE", "DECREASE"},
|
||||
},
|
||||
"supplier_categories": []string{
|
||||
"BOP",
|
||||
"SAPRONAK",
|
||||
|
||||
+48
-7
@@ -19,6 +19,8 @@ type ProductWarehouseRepository interface {
|
||||
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
|
||||
GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error)
|
||||
GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error)
|
||||
GetByFlagAndWarehouseID(ctx context.Context, flagName string, warehouseId uint) ([]entity.ProductWarehouse, error)
|
||||
GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error)
|
||||
ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB
|
||||
AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error
|
||||
}
|
||||
@@ -78,14 +80,14 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous
|
||||
|
||||
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.*").
|
||||
q := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
||||
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
|
||||
Order("product_warehouses.created_at DESC")
|
||||
|
||||
// preload relations so nested Product and Warehouse are populated
|
||||
err := q.Preload("Product").Preload("Warehouse").Find(&productWarehouses).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -100,12 +102,12 @@ func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(c
|
||||
}
|
||||
fmt.Println(warehouseId)
|
||||
err := query.WithContext(ctx).
|
||||
Table("product_warehouses").
|
||||
Select("product_warehouses.*").
|
||||
Model(&entity.ProductWarehouse{}).
|
||||
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").
|
||||
Preload("Product").Preload("Warehouse").
|
||||
First(&productWarehouse).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -146,3 +148,42 @@ func (r *ProductWarehouseRepositoryImpl) AdjustQuantities(ctx context.Context, d
|
||||
}
|
||||
return 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 product_categories.id = products.product_category_id").
|
||||
Where("product_categories.code = ?", categoryCode).
|
||||
First(&product).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
func (r *ProductWarehouseRepositoryImpl) GetByFlagAndWarehouseID(ctx context.Context, flagName string, warehouseId uint) ([]entity.ProductWarehouse, error) {
|
||||
var productWarehouses []entity.ProductWarehouse
|
||||
err := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
||||
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 = ? AND product_warehouses.warehouse_id = ?", flagName, warehouseId).
|
||||
Order("product_warehouses.created_at DESC").
|
||||
Preload("Product").Preload("Warehouse").
|
||||
Find(&productWarehouses).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return productWarehouses, nil
|
||||
}
|
||||
|
||||
func (r *ProductWarehouseRepositoryImpl) GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error) {
|
||||
var product entity.Product
|
||||
err := r.DB().WithContext(ctx).
|
||||
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
|
||||
Where("flags.name = ?", flagName).
|
||||
First(&product).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type WarehouseRepository interface {
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||
GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||
}
|
||||
|
||||
type WarehouseRepositoryImpl struct {
|
||||
@@ -60,3 +61,16 @@ func (r *WarehouseRepositoryImpl) GetByKandangID(ctx context.Context, kandangId
|
||||
}
|
||||
return &warehouse, nil
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) {
|
||||
var warehouse entity.Warehouse
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("kandang_id = ?", kandangId).
|
||||
Where("deleted_at IS NULL").
|
||||
Order("id DESC").
|
||||
First(&warehouse).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &warehouse, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto"
|
||||
@@ -22,30 +21,84 @@ func NewChickinController(chickinService service.ChickinService) *ChickinControl
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ChickinController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||
// func (u *ChickinController) GetAll(c *fiber.Ctx) error {
|
||||
// query := &validation.Query{
|
||||
// Page: c.QueryInt("page", 1),
|
||||
// Limit: c.QueryInt("limit", 10),
|
||||
// ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||
// }
|
||||
|
||||
// result, totalResults, err := u.ChickinService.GetAll(c, query)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return c.Status(fiber.StatusOK).
|
||||
// JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
||||
// Code: fiber.StatusOK,
|
||||
// Status: "success",
|
||||
// Message: "Get all chickins successfully",
|
||||
// Meta: response.Meta{
|
||||
// Page: query.Page,
|
||||
// Limit: query.Limit,
|
||||
// TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
// TotalResults: totalResults,
|
||||
// },
|
||||
// Data: dto.ToChickinListDTOs(result),
|
||||
// })
|
||||
// }
|
||||
|
||||
// func (u *ChickinController) 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, err := u.ChickinService.GetOne(c, uint(id))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return c.Status(fiber.StatusOK).
|
||||
// JSON(response.Success{
|
||||
// Code: fiber.StatusOK,
|
||||
// Status: "success",
|
||||
// Message: "Get chickin successfully",
|
||||
// Data: dto.ToChickinListDTO(*result),
|
||||
// })
|
||||
// }
|
||||
|
||||
func (u *ChickinController) CreateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Create)
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
result, totalResults, err := u.ChickinService.GetAll(c, query)
|
||||
results, err := u.ChickinService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
var (
|
||||
data interface{}
|
||||
message = "Create chickin successfully"
|
||||
)
|
||||
if len(results) == 1 {
|
||||
data = dto.ToChickinListDTO(results[0])
|
||||
} else {
|
||||
message = "Create chickins successfully"
|
||||
data = dto.ToChickinListDTOs(results)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Get all chickins successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToChickinListDTOs(result),
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,95 +120,85 @@ func (u *ChickinController) GetOne(c *fiber.Ctx) error {
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get chickin successfully",
|
||||
Data: dto.ToChickinListDTO(*result),
|
||||
Data: dto.ToChickinDetailDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ChickinController) CreateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Create)
|
||||
// func (u *ChickinController) UpdateOne(c *fiber.Ctx) error {
|
||||
// req := new(validation.Update)
|
||||
// param := c.Params("id")
|
||||
|
||||
// id, err := strconv.Atoi(param)
|
||||
// if err != nil {
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
// }
|
||||
|
||||
// if err := c.BodyParser(req); err != nil {
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
// }
|
||||
|
||||
// result, err := u.ChickinService.UpdateOne(c, req, uint(id))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return c.Status(fiber.StatusOK).
|
||||
// JSON(response.Success{
|
||||
// Code: fiber.StatusOK,
|
||||
// Status: "success",
|
||||
// Message: "Update chickin successfully",
|
||||
// Data: dto.ToChickinListDTO(*result),
|
||||
// })
|
||||
// }
|
||||
|
||||
// func (u *ChickinController) DeleteOne(c *fiber.Ctx) error {
|
||||
// param := c.Params("id")
|
||||
|
||||
// id, err := strconv.Atoi(param)
|
||||
// if err != nil {
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
// }
|
||||
|
||||
// if err := u.ChickinService.DeleteOne(c, uint(id)); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return c.Status(fiber.StatusOK).
|
||||
// JSON(response.Common{
|
||||
// Code: fiber.StatusOK,
|
||||
// Status: "success",
|
||||
// Message: "Delete chickin successfully",
|
||||
// })
|
||||
// }
|
||||
|
||||
func (u *ChickinController) Approval(c *fiber.Ctx) error {
|
||||
req := new(validation.Approve)
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
result, err := u.ChickinService.CreateOne(c, req)
|
||||
results, err := u.ChickinService.Approval(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create chickin successfully",
|
||||
Data: dto.ToChickinListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ChickinController) UpdateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Update)
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
result, err := u.ChickinService.UpdateOne(c, req, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
var (
|
||||
data interface{}
|
||||
message = "Submit chickin approval successfully"
|
||||
)
|
||||
if len(results) == 1 {
|
||||
data = dto.ToChickinListDTO(results[0])
|
||||
} else {
|
||||
message = "Submit chickin approvals successfully"
|
||||
data = dto.ToChickinListDTOs(results)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Update chickin successfully",
|
||||
Data: dto.ToChickinListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ChickinController) DeleteOne(c *fiber.Ctx) error {
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
if err := u.ChickinService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete chickin successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ChickinController) Approve(c *fiber.Ctx) error {
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
if err := u.ChickinService.Approve(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Approve chickin successfully",
|
||||
Data: nil,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@ import (
|
||||
// === DTO Structs (ordered) ===
|
||||
|
||||
type ChickinBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
||||
ChickInDate time.Time `json:"chick_in_date"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Note string `json:"note"`
|
||||
Id uint `json:"id"`
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||
ChickInDate time.Time `json:"chick_in_date"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
UsageQty float64 `json:"usage_qty"`
|
||||
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type ProjectFlockDTO struct {
|
||||
@@ -45,21 +47,32 @@ type ChickinSimpleDTO struct {
|
||||
Id uint `json:"id"`
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||
ChickInDate time.Time `json:"chick_in_date"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Note string `json:"note"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
UsageQty float64 `json:"usage_qty"`
|
||||
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||
Notes string `json:"notes"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
}
|
||||
|
||||
type ChickinListDTO struct {
|
||||
ChickinBaseDTO
|
||||
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ChickinDetailDTO struct {
|
||||
ChickinListDTO
|
||||
Id uint `json:"id"`
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||
ChickInDate time.Time `json:"chick_in_date"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
UsageQty float64 `json:"usage_qty"`
|
||||
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||
Notes string `json:"notes"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// === Mapper Functions (ordered) ===
|
||||
@@ -138,17 +151,22 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
||||
}
|
||||
|
||||
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
||||
var pfk *ProjectFlockKandangDTO
|
||||
if e.ProjectFlockKandang.Id != 0 {
|
||||
mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang)
|
||||
pfk = &mapped
|
||||
var projectFlockKandangId uint
|
||||
// Check if ProjectFlockKandang relation is loaded
|
||||
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
||||
projectFlockKandangId = e.ProjectFlockKandang.Id
|
||||
} else if e.ProjectFlockKandangId != 0 {
|
||||
// If relation is not loaded but ID is available, use the ID
|
||||
projectFlockKandangId = e.ProjectFlockKandangId
|
||||
}
|
||||
return ChickinBaseDTO{
|
||||
Id: e.Id,
|
||||
ProjectFlockKandang: pfk,
|
||||
ChickInDate: e.ChickInDate,
|
||||
Quantity: e.Quantity,
|
||||
Note: e.Note,
|
||||
Id: e.Id,
|
||||
ProjectFlockKandangId: projectFlockKandangId,
|
||||
ChickInDate: e.ChickInDate,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
UsageQty: e.UsageQty,
|
||||
PendingUsageQty: e.PendingUsageQty,
|
||||
Notes: e.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,29 +175,25 @@ func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO {
|
||||
Id: e.Id,
|
||||
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
||||
ChickInDate: e.ChickInDate,
|
||||
Quantity: e.Quantity,
|
||||
Note: e.Note,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
UsageQty: e.UsageQty,
|
||||
PendingUsageQty: e.PendingUsageQty,
|
||||
Notes: e.Notes,
|
||||
CreatedBy: e.CreatedBy,
|
||||
}
|
||||
}
|
||||
|
||||
func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
||||
var createdUser *userBaseDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userBaseDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
var pfk *ProjectFlockKandangDTO
|
||||
if e.ProjectFlockKandang.Id != 0 {
|
||||
mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang)
|
||||
pfk = &mapped
|
||||
}
|
||||
return ChickinListDTO{
|
||||
ChickinBaseDTO: ToChickinBaseDTO(e),
|
||||
ProjectFlockKandang: pfk,
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
ChickinBaseDTO: ToChickinBaseDTO(e),
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +214,31 @@ func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO {
|
||||
}
|
||||
|
||||
func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO {
|
||||
var createdUser *userBaseDTO.UserBaseDTO
|
||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return ChickinDetailDTO{
|
||||
ChickinListDTO: ToChickinListDTO(e),
|
||||
Id: e.Id,
|
||||
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
||||
ChickInDate: e.ChickInDate,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
UsageQty: e.UsageQty,
|
||||
PendingUsageQty: e.PendingUsageQty,
|
||||
Notes: e.Notes,
|
||||
CreatedBy: e.CreatedBy,
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ToChickinDetailDTOs(e []entity.ProjectChickin) []ChickinDetailDTO {
|
||||
result := make([]ChickinDetailDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToChickinDetailDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package chickins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/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"
|
||||
@@ -15,6 +20,8 @@ import (
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type ChickinModule struct{}
|
||||
@@ -32,6 +39,12 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowProjectFlockKandang, utils.ProjectFlockKandangApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
|
||||
}
|
||||
|
||||
chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, chickinDetailRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
|
||||
@@ -11,21 +11,26 @@ import (
|
||||
type ProjectChickinRepository interface {
|
||||
repository.BaseRepository[entity.ProjectChickin]
|
||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
}
|
||||
|
||||
type ChickinRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.ProjectChickin]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewChickinRepository(db *gorm.DB) ProjectChickinRepository {
|
||||
return &ChickinRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) {
|
||||
var chickin entity.ProjectChickin
|
||||
err := r.DB().WithContext(ctx).
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("project_floc_id = ?", projectFlockID).
|
||||
Where("deleted_at IS NULL").
|
||||
First(&chickin).Error
|
||||
@@ -34,3 +39,43 @@ func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, pr
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||
var chickins []entity.ProjectChickin
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Where("usage_qty = 0").
|
||||
Where("pending_usage_qty > 0").
|
||||
Order("created_at DESC").
|
||||
Find(&chickins).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chickins, nil
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
|
||||
var total float64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entity.ProjectChickin{}).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Where("pending_usage_qty > 0").
|
||||
Select("COALESCE(SUM(pending_usage_qty), 0)").
|
||||
Row().Scan(&total)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
+25
@@ -1,6 +1,8 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
@@ -8,6 +10,10 @@ import (
|
||||
|
||||
type ProjectChickinDetailRepository interface {
|
||||
repository.BaseRepository[entity.ProjectChickinDetail]
|
||||
CreateOne(ctx context.Context, entity *entity.ProjectChickinDetail, modifier func(*gorm.DB) *gorm.DB) error
|
||||
DeleteMany(ctx context.Context, modifier func(*gorm.DB) *gorm.DB) error
|
||||
GetByProjectChickinID(ctx context.Context, projectChickinID uint) ([]entity.ProjectChickinDetail, error)
|
||||
WithTxRepo(tx *gorm.DB) ProjectChickinDetailRepository
|
||||
}
|
||||
|
||||
type ChickinDetailRepositoryImpl struct {
|
||||
@@ -19,3 +25,22 @@ func NewChickinDetailRepository(db *gorm.DB) ProjectChickinDetailRepository {
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickinDetail](db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ChickinDetailRepositoryImpl) WithTxRepo(tx *gorm.DB) ProjectChickinDetailRepository {
|
||||
return &ChickinDetailRepositoryImpl{BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickinDetail](tx)}
|
||||
}
|
||||
|
||||
func (r *ChickinDetailRepositoryImpl) DB() *gorm.DB {
|
||||
return r.BaseRepositoryImpl.DB()
|
||||
}
|
||||
|
||||
func (r *ChickinDetailRepositoryImpl) GetByProjectChickinID(ctx context.Context, projectChickinID uint) ([]entity.ProjectChickinDetail, error) {
|
||||
var records []entity.ProjectChickinDetail
|
||||
if err := r.DB().WithContext(ctx).Where("project_chickin_id = ?", projectChickinID).Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService
|
||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
// route.Get("/", ctrl.GetAll)
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
route.Post("/:id/approve", ctrl.Approve)
|
||||
// route.Patch("/:id", ctrl.UpdateOne)
|
||||
// route.Delete("/:id", ctrl.DeleteOne)
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
@@ -21,10 +25,10 @@ import (
|
||||
type ChickinService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) ([]entity.ProjectChickin, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
Approve(ctx *fiber.Ctx, id uint) error
|
||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error)
|
||||
}
|
||||
|
||||
type chickinService struct {
|
||||
@@ -76,6 +80,7 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
chickins, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
if params.ProjectFlockKandangId != 0 {
|
||||
@@ -103,112 +108,164 @@ func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, e
|
||||
return chickin, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) {
|
||||
func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]entity.ProjectChickin, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId)
|
||||
projectFlockKandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get projectflock kandang: %+v", err)
|
||||
return nil, err
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found")
|
||||
}
|
||||
|
||||
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId)
|
||||
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectFlockKandang.KandangId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get warehouse: %+v", err)
|
||||
return nil, err
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse for Kandang not found")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if len(productWarehouses) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
|
||||
}
|
||||
totalQuantity := 0.0
|
||||
for _, pw := range productWarehouses {
|
||||
totalQuantity += pw.Quantity
|
||||
var productWarehouses []entity.ProductWarehouse
|
||||
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
if totalQuantity < 1 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses")
|
||||
productWarehouses, err = s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), productCategoryCode, warehouse.Id)
|
||||
if err != nil || len(productWarehouses) == 0 {
|
||||
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)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to parse chickin date: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format")
|
||||
}
|
||||
newChickin := &entity.ProjectChickin{
|
||||
ProjectFlockKandangId: projectflockkandang.Id,
|
||||
ChickInDate: chickinDate,
|
||||
Quantity: totalQuantity,
|
||||
Note: req.Note,
|
||||
CreatedBy: 1, //todo: ganti dengan user login
|
||||
}
|
||||
err = s.Repository.CreateOne(c.Context(), newChickin, nil)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to create chickin: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update semua product warehouse: set quantity jadi 0
|
||||
for _, pw := range productWarehouses {
|
||||
err = s.ProductWarehouseRepo.PatchOne(c.Context(), pw.Id, map[string]any{
|
||||
"quantity": 0,
|
||||
}, nil)
|
||||
actorID := uint(1) // todo nanti ambil dari auth context
|
||||
newChikins := make([]*entity.ProjectChickin, 0)
|
||||
for _, productWarehouse := range productWarehouses {
|
||||
availableQty, err := s.calculateAvailableQuantity(c, req.ProjectFlockKandangId, &productWarehouse, category)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to update product warehouse quantity: %+v", err)
|
||||
return nil, err
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to calculate available quantity for product warehouse %d", productWarehouse.Id))
|
||||
}
|
||||
|
||||
newChickinDetail := &entity.ProjectChickinDetail{
|
||||
ProjectChickinId: newChickin.Id,
|
||||
ProductWarehouseId: pw.Id,
|
||||
Quantity: pw.Quantity,
|
||||
CreatedBy: 1, // todo: ganti dengan user login
|
||||
if availableQty <= 0 {
|
||||
continue
|
||||
}
|
||||
err = s.ProjectChickinDetailRepo.CreateOne(c.Context(), newChickinDetail, nil)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to create chickin detail: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to get project flock population: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
if existingPopulation != nil {
|
||||
|
||||
err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), existingPopulation.Id, map[string]any{
|
||||
"reserved_quantity": newChickin.Quantity + existingPopulation.ReservedQuantity,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to update project flock population: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
newPopulation := &entity.ProjectFlockPopulation{
|
||||
newChickin := &entity.ProjectChickin{
|
||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||
InitialQuantity: 0,
|
||||
CurrentQuantity: 0,
|
||||
ReservedQuantity: newChickin.Quantity,
|
||||
CreatedBy: 1, // todo: ganti dengan user login
|
||||
}
|
||||
err = s.ProjectflockPopulationRepo.CreateOne(c.Context(), newPopulation, nil)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to create project flock population: %+v", err)
|
||||
return nil, err
|
||||
ChickInDate: chickinDate,
|
||||
UsageQty: 0,
|
||||
PendingUsageQty: availableQty,
|
||||
ProductWarehouseId: productWarehouse.Id,
|
||||
Notes: req.Note,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
newChikins = append(newChikins, newChickin)
|
||||
}
|
||||
|
||||
return s.GetOne(c, newChickin.Id)
|
||||
if len(newChikins) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "No chickins to create")
|
||||
}
|
||||
|
||||
existingChikins, err := s.Repository.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing chickins")
|
||||
}
|
||||
|
||||
isFirstTime := len(existingChikins) == 0
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateMany(c.Context(), newChikins, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create chickins")
|
||||
}
|
||||
|
||||
latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get latest approval")
|
||||
}
|
||||
|
||||
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
for _, chickin := range newChikins {
|
||||
updates := map[string]any{"quantity": gorm.Expr("quantity - ?", chickin.PendingUsageQty)}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickin.ProductWarehouseId))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var approvalAction entity.ApprovalAction
|
||||
if isFirstTime {
|
||||
approvalAction = entity.ApprovalActionCreated
|
||||
} else {
|
||||
approvalAction = entity.ApprovalActionUpdated
|
||||
}
|
||||
|
||||
if latest == nil {
|
||||
if _, err := approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlockKandang,
|
||||
projectFlockKandang.Id,
|
||||
utils.ProjectFlockKandangStepPengajuan,
|
||||
&approvalAction,
|
||||
actorID,
|
||||
nil); err != nil {
|
||||
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||||
}
|
||||
}
|
||||
} else if latest.StepNumber != uint16(utils.ProjectFlockKandangStepPengajuan) {
|
||||
if _, err := approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlockKandang,
|
||||
projectFlockKandang.Id,
|
||||
utils.ProjectFlockKandangStepPengajuan,
|
||||
&approvalAction,
|
||||
actorID,
|
||||
nil); err != nil {
|
||||
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return nil, fiberErr
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create chickins")
|
||||
}
|
||||
|
||||
result := make([]entity.ProjectChickin, 0, len(newChikins))
|
||||
for _, chickin := range newChikins {
|
||||
loaded, err := s.GetOne(c, chickin.Id)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to reload chickin %d with relations: %v", chickin.Id, err))
|
||||
}
|
||||
result = append(result, *loaded)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load created chickins")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) {
|
||||
@@ -222,7 +279,7 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
updateBody["chick_in_date"] = req.ChickInDate
|
||||
}
|
||||
if req.Note != "" {
|
||||
updateBody["note"] = req.Note
|
||||
updateBody["notes"] = req.Note
|
||||
}
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
@@ -240,174 +297,335 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
}
|
||||
|
||||
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
db := s.Repository.DB()
|
||||
|
||||
tx := db.WithContext(c.Context()).Begin()
|
||||
if tx.Error != nil {
|
||||
s.Log.Errorf("Failed to begin transaction: %+v", tx.Error)
|
||||
return tx.Error
|
||||
}
|
||||
rollback := func(err error) error {
|
||||
if rerr := tx.Rollback().Error; rerr != nil {
|
||||
s.Log.Errorf("Rollback failed: %+v", rerr)
|
||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
chickinRepoTx := s.Repository.WithTx(tx)
|
||||
pfkRepoTx := s.ProjectflockKandangRepo.WithTx(tx)
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(tx)
|
||||
return nil
|
||||
}
|
||||
|
||||
chickin, err := chickinRepoTx.GetByID(c.Context(), id, nil)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found"))
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get chickin by id: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
func (s chickinService) calculateAvailableQuantity(ctx *fiber.Ctx, projectFlockKandangID uint, productWarehouse *entity.ProductWarehouse, category string) (float64, error) {
|
||||
availableQty := productWarehouse.Quantity
|
||||
|
||||
var population entity.ProjectFlockPopulation
|
||||
if err := tx.WithContext(c.Context()).Where("project_flock_kandang_id = ?", chickin.ProjectFlockKandangId).First(&population).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Project flock population not found"))
|
||||
}
|
||||
s.Log.Errorf("Failed to get project flock population: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
if category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
var totalPendingQty float64
|
||||
|
||||
newReserved := population.ReservedQuantity - chickin.Quantity
|
||||
if newReserved < 0 {
|
||||
newReserved = 0
|
||||
}
|
||||
if err := tx.WithContext(c.Context()).Model(&entity.ProjectFlockPopulation{}).Where("id = ?", population.Id).Updates(map[string]any{"reserved_quantity": newReserved}).Error; err != nil {
|
||||
s.Log.Errorf("Failed to update project flock population: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
chickins, err := s.Repository.GetByProjectFlockKandangID(ctx.Context(), projectFlockKandangID)
|
||||
if err == nil {
|
||||
for _, chickin := range chickins {
|
||||
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
if len(details) == 0 {
|
||||
return false, nil
|
||||
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
|
||||
totalPendingQty += chickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range details {
|
||||
var pw entity.ProductWarehouse
|
||||
if err := tx.WithContext(c.Context()).Where("id = ?", d.ProductWarehouseId).First(&pw).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
availableQty = productWarehouse.Quantity - totalPendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
} else if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
var totalPopulation float64
|
||||
var totalPendingQty float64
|
||||
|
||||
populations, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(ctx.Context(), projectFlockKandangID, productWarehouse.Id)
|
||||
if err == nil {
|
||||
for _, pop := range populations {
|
||||
totalPopulation += pop.TotalQty
|
||||
}
|
||||
}
|
||||
chickins, err := s.Repository.GetByProjectFlockKandangID(ctx.Context(), projectFlockKandangID)
|
||||
if err == nil {
|
||||
for _, chickin := range chickins {
|
||||
|
||||
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
|
||||
totalPendingQty += chickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
}
|
||||
availableQty = productWarehouse.Quantity - totalPopulation - totalPendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
}
|
||||
|
||||
return availableQty, nil
|
||||
}
|
||||
|
||||
func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
||||
|
||||
var action entity.ApprovalAction
|
||||
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||
case string(entity.ApprovalActionRejected):
|
||||
action = entity.ApprovalActionRejected
|
||||
case string(entity.ApprovalActionApproved):
|
||||
action = entity.ApprovalActionApproved
|
||||
default:
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||||
}
|
||||
|
||||
approvableIDs := utils.UniqueUintSlice(req.ApprovableIds)
|
||||
if len(approvableIDs) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
if action == entity.ApprovalActionApproved {
|
||||
step = utils.ProjectFlockKandangStepDisetujui
|
||||
}
|
||||
|
||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, approvableID := range approvableIDs {
|
||||
actorID := uint(1) // todo nanti ambil dari auth context
|
||||
if _, err := approvalSvc.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlockKandang,
|
||||
approvableID,
|
||||
step,
|
||||
&action,
|
||||
actorID,
|
||||
req.Notes,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||||
}
|
||||
|
||||
if action == entity.ApprovalActionApproved {
|
||||
chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get chickins for approval %d", approvableID))
|
||||
}
|
||||
|
||||
kandangForApproval, err := s.ProjectflockKandangRepo.GetByID(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get ProjectFlockKandang")
|
||||
}
|
||||
|
||||
category := strings.ToUpper(strings.TrimSpace(kandangForApproval.ProjectFlock.Category))
|
||||
|
||||
if category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
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 fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
|
||||
}
|
||||
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
|
||||
}
|
||||
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert chickins to target")
|
||||
}
|
||||
} else if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
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 fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
|
||||
}
|
||||
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
|
||||
}
|
||||
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert chickins to target")
|
||||
}
|
||||
}
|
||||
}
|
||||
if action == entity.ApprovalActionRejected {
|
||||
chickins, err := chickinRepoTx.GetPendingByProjectFlockKandangID(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get pending chickins for rejection %d", approvableID))
|
||||
}
|
||||
|
||||
if len(chickins) == 0 {
|
||||
continue
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
updatedQuantity := pw.Quantity + d.Quantity
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), pw.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil {
|
||||
return false, err
|
||||
kandangForRejection, err := s.ProjectflockKandangRepo.GetByID(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get ProjectFlockKandang")
|
||||
}
|
||||
|
||||
categoryForRejection := strings.ToUpper(strings.TrimSpace(kandangForRejection.ProjectFlock.Category))
|
||||
|
||||
for _, chickin := range chickins {
|
||||
|
||||
if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) {
|
||||
updates := map[string]any{"quantity": gorm.Expr("quantity + ?", chickin.PendingUsageQty)}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found during rejection", chickin.ProductWarehouseId))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to restore product warehouse quantity for chickin %d", chickin.Id))
|
||||
}
|
||||
}
|
||||
|
||||
if err := chickinRepoTx.DeleteOne(c.Context(), chickin.Id); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to delete rejected chickin %d", chickin.Id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Delete(&entity.ProjectChickinDetail{}).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
restored, err := restoreFromDetails()
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to restore from chickin details: %+v", err)
|
||||
return rollback(err)
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return nil, fiberErr
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||
}
|
||||
|
||||
if !restored {
|
||||
updated := make([]entity.ProjectChickin, 0)
|
||||
for _, kandangID := range approvableIDs {
|
||||
var chickins []entity.ProjectChickin
|
||||
if err := s.Repository.DB().WithContext(c.Context()).Where("project_flock_kandang_id = ?", kandangID).Find(&chickins).Error; err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load approved chickins")
|
||||
}
|
||||
updated = append(updated, chickins...)
|
||||
}
|
||||
|
||||
projectflockkandang, err := pfkRepoTx.GetByID(c.Context(), population.ProjectFlockKandangId)
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) {
|
||||
|
||||
products, err := s.ProductWarehouseRepo.GetByFlagAndWarehouseID(ctx.Context(), categoryCode, warehouseId)
|
||||
if err == nil && len(products) > 0 {
|
||||
return &products[0], nil
|
||||
}
|
||||
|
||||
product, err := s.ProductWarehouseRepo.GetFirstProductByFlag(ctx.Context(), categoryCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get %s product: %w", categoryCode, err)
|
||||
}
|
||||
if product == nil {
|
||||
return nil, fmt.Errorf("no %s product found in system", categoryCode)
|
||||
}
|
||||
|
||||
newPW := &entity.ProductWarehouse{
|
||||
ProductId: product.Id,
|
||||
WarehouseId: warehouseId,
|
||||
Quantity: 0,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to create %s product warehouse: %w", categoryCode, err)
|
||||
}
|
||||
|
||||
return newPW, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []entity.ProjectChickin, targetPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error {
|
||||
|
||||
if targetPW == nil || targetPW.Id == 0 {
|
||||
return fmt.Errorf("invalid target product warehouse")
|
||||
}
|
||||
|
||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, chickin := range chickins {
|
||||
|
||||
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get projectflock kandang: %+v", err)
|
||||
return rollback(err)
|
||||
return fmt.Errorf("failed to check population existence for chickin %d: %w", chickin.Id, err)
|
||||
}
|
||||
|
||||
var warehouse entity.Warehouse
|
||||
if err := tx.WithContext(c.Context()).Where("kandang_id = ?", projectflockkandang.KandangId).First(&warehouse).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Warehouse not found for kandang"))
|
||||
if populationExists {
|
||||
s.Log.Infof("population already exists for chickin %d, skipping", chickin.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if chickin.ProductWarehouseId != targetPW.Id {
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{
|
||||
"quantity": gorm.Expr("quantity - ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Source product warehouse %d not found", chickin.ProductWarehouseId))
|
||||
}
|
||||
return fmt.Errorf("failed to deduct source warehouse quantity for chickin %d: %w", chickin.Id, err)
|
||||
}
|
||||
s.Log.Errorf("Failed to get warehouse: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
|
||||
productWarehouse, err := s.ProductWarehouseRepo.GetLatestByCategoryCodeAndWarehouseID(
|
||||
c.Context(),
|
||||
"DOC",
|
||||
warehouse.Id,
|
||||
tx,
|
||||
)
|
||||
if err != nil {
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
|
||||
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse"))
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id))
|
||||
}
|
||||
s.Log.Errorf("Failed to get product warehouse: %+v", err)
|
||||
return rollback(err)
|
||||
return fmt.Errorf("failed to update target warehouse quantity: %w", err)
|
||||
}
|
||||
|
||||
updatedQuantity := productWarehouse.Quantity + chickin.Quantity
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil {
|
||||
s.Log.Errorf("Failed to update product warehouse quantity: %+v", err)
|
||||
return rollback(err)
|
||||
population := &entity.ProjectFlockPopulation{
|
||||
ProjectChickinId: chickin.Id,
|
||||
ProductWarehouseId: targetPW.Id,
|
||||
TotalQty: quantityToConvert,
|
||||
TotalUsedQty: 0,
|
||||
Notes: chickin.Notes,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
}
|
||||
|
||||
// delete chickin (single place)
|
||||
if err := chickinRepoTx.DeleteOne(c.Context(), id); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found"))
|
||||
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Log.Errorf("Failed to delete chickin: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
s.Log.Errorf("Failed to commit transaction: %+v", err)
|
||||
return rollback(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) Approve(c *fiber.Ctx, id uint) error {
|
||||
|
||||
// todo: ini contoh akhir jika sudah approved
|
||||
|
||||
chickin, err := s.Repository.GetByID(
|
||||
c.Context(),
|
||||
id,
|
||||
nil,
|
||||
)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get chickin by id: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get project flock population: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{
|
||||
"reserved_quantity": population.ReservedQuantity - chickin.Quantity,
|
||||
"initial_quantity": population.InitialQuantity + chickin.Quantity,
|
||||
"current_quantity": population.CurrentQuantity + chickin.Quantity,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to update project flock population: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -3,7 +3,7 @@ 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`
|
||||
Note string `json:"note" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
@@ -16,3 +16,9 @@ type Query struct {
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
||||
}
|
||||
|
||||
type Approve struct {
|
||||
Action string `json:"action" validate:"required_strict"`
|
||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
}
|
||||
|
||||
+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,330 @@
|
||||
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"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/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"
|
||||
)
|
||||
|
||||
// === DTO Structs (ordered) ===
|
||||
|
||||
type ProjectFlockKandangBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
}
|
||||
|
||||
type ProjectFlockDTO 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"`
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type KandangDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ProductWarehouseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Product *productDTO.ProductBaseDTO `json:"product,omitempty"`
|
||||
Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"`
|
||||
}
|
||||
|
||||
type AvailableQtyDTO struct {
|
||||
AvailableQty float64 `json:"available_qty"`
|
||||
ProductWarehouse *ProductWarehouseDTO `json:"product_warehouse,omitempty"`
|
||||
}
|
||||
|
||||
type ProjectFlockKandangListDTO struct {
|
||||
ProjectFlockKandangBaseDTO
|
||||
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
|
||||
Kandang *KandangDTO `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
|
||||
}
|
||||
|
||||
// === Mapper Functions (ordered) ===
|
||||
|
||||
func ToProjectFlockKandangBaseDTO(e entity.ProjectFlockKandang) ProjectFlockKandangBaseDTO {
|
||||
return ProjectFlockKandangBaseDTO{
|
||||
Id: e.Id,
|
||||
}
|
||||
}
|
||||
|
||||
func toProjectFlockDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockDTO {
|
||||
if pf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectFlockDTO{
|
||||
Id: pf.Id,
|
||||
Period: pf.Period,
|
||||
Area: pf.Area,
|
||||
Category: pf.Category,
|
||||
Fcr: pf.Fcr,
|
||||
Location: pf.Location,
|
||||
CreatedUser: pf.CreatedUser,
|
||||
CreatedAt: pf.CreatedAt,
|
||||
UpdatedAt: pf.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return ProjectFlockKandangListDTO{
|
||||
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
|
||||
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
||||
Kandang: toKandangDTO(e.Kandang),
|
||||
Chickins: toChickinDTOs(e.Chickins),
|
||||
AvailableQtys: toAvailableQtyDTOsFromRaw(availableQtysRaw),
|
||||
CreatedAt: e.CreatedAt,
|
||||
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
||||
Approval: toApprovalDTO(e),
|
||||
}
|
||||
}
|
||||
|
||||
func toKandangDTO(kandang entity.Kandang) *KandangDTO {
|
||||
if kandang.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &KandangDTO{
|
||||
Id: kandang.Id,
|
||||
Name: kandang.Name,
|
||||
Status: kandang.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func toApprovalDTO(e entity.ProjectFlockKandang) *approvalDTO.ApprovalBaseDTO {
|
||||
if e.LatestApproval != nil {
|
||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||
return &mapped
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKandangListDTO {
|
||||
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
|
||||
if e.ProjectFlock.Id != 0 {
|
||||
mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
|
||||
projectFlockSummary = &mapped
|
||||
}
|
||||
|
||||
return ProjectFlockKandangListDTO{
|
||||
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
|
||||
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
||||
Kandang: toKandangDTO(e.Kandang),
|
||||
Chickins: toChickinDTOs(e.Chickins),
|
||||
AvailableQtys: toAvailableQtyDTOs(e.Chickins),
|
||||
CreatedAt: e.CreatedAt,
|
||||
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
||||
Approval: toApprovalDTO(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),
|
||||
}
|
||||
}
|
||||
|
||||
// === Helper Functions (ordered) ===
|
||||
|
||||
func toProductWarehouseDTO(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 pData, ok := pwData["product"].(map[string]interface{}); ok {
|
||||
dto.Product = toProductDTO(pData)
|
||||
}
|
||||
|
||||
if wData, ok := pwData["warehouse"].(map[string]interface{}); ok {
|
||||
dto.Warehouse = toWarehouseDTO(wData)
|
||||
}
|
||||
|
||||
return dto
|
||||
}
|
||||
|
||||
func toProductDTO(pData map[string]interface{}) *productDTO.ProductBaseDTO {
|
||||
if pData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
product := &productDTO.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 toWarehouseDTO(wData map[string]interface{}) *warehouseDTO.WarehouseBaseDTO {
|
||||
if wData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
warehouse := &warehouseDTO.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
|
||||
}
|
||||
|
||||
func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserBaseDTO {
|
||||
if pf.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(pf.CreatedUser)
|
||||
return &mapped
|
||||
} else if pf.CreatedBy != 0 {
|
||||
return &userDTO.UserBaseDTO{
|
||||
Id: pf.CreatedBy,
|
||||
IdUser: int64(pf.CreatedBy),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toChickinDTOs(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 toAvailableQtyDTOs(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,
|
||||
}
|
||||
|
||||
if ch.ProductWarehouse.Product.Id != 0 {
|
||||
pwDTO.Product = &productDTO.ProductBaseDTO{
|
||||
Id: ch.ProductWarehouse.Product.Id,
|
||||
Name: ch.ProductWarehouse.Product.Name,
|
||||
}
|
||||
}
|
||||
|
||||
if ch.ProductWarehouse.Warehouse.Id != 0 {
|
||||
pwDTO.Warehouse = &warehouseDTO.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 toAvailableQtyDTOsFromRaw(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 := toProductWarehouseDTO(pwData)
|
||||
availableQty := 0.0
|
||||
if qty, ok := v["available_qty"].(float64); ok {
|
||||
availableQty = qty
|
||||
}
|
||||
|
||||
result[i] = AvailableQtyDTO{
|
||||
AvailableQty: availableQty,
|
||||
ProductWarehouse: pwDTO,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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)
|
||||
projectFlockPopulationRepo := rProjectFlockKandang.NewProjectFlockPopulationRepository(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, projectFlockPopulationRepo, 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)
|
||||
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
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
|
||||
PopulationRepo repository.ProjectFlockPopulationRepository
|
||||
}
|
||||
|
||||
func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService {
|
||||
return &projectFlockKandangService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ApprovalSvc: approvalSvc,
|
||||
WarehouseRepo: warehouseRepo,
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
PopulationRepo: populationRepo,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
availableQty, err := s.calculateAvailableQuantityForProductWarehouse(c, projectFlockKandang, &pw)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", pw.Id, err)
|
||||
}
|
||||
|
||||
// Only include product warehouse if available_qty > 0
|
||||
if availableQty <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
productData := map[string]interface{}{
|
||||
"id": pw.Product.Id,
|
||||
"name": pw.Product.Name,
|
||||
}
|
||||
|
||||
warehouseData := map[string]interface{}{
|
||||
"id": pw.Warehouse.Id,
|
||||
"name": pw.Warehouse.Name,
|
||||
"type": pw.Warehouse.Type,
|
||||
}
|
||||
|
||||
productWarehouseData := map[string]interface{}{
|
||||
"id": pw.Id,
|
||||
"product": productData,
|
||||
"warehouse": warehouseData,
|
||||
}
|
||||
|
||||
result = append(result, map[string]interface{}{
|
||||
"available_qty": availableQty,
|
||||
"product_warehouse": productWarehouseData,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehouse(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang, productWarehouse *entity.ProductWarehouse) (float64, error) {
|
||||
availableQty := productWarehouse.Quantity
|
||||
|
||||
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
var totalPendingQty float64
|
||||
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
|
||||
totalPendingQty += chickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
|
||||
availableQty = productWarehouse.Quantity - totalPendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||
var totalPopulation float64
|
||||
var totalPendingQty float64
|
||||
|
||||
populations, err := s.PopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(c.Context(), projectFlockKandang.Id, productWarehouse.Id)
|
||||
if err == nil {
|
||||
for _, pop := range populations {
|
||||
totalPopulation += pop.TotalQty
|
||||
}
|
||||
}
|
||||
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
|
||||
totalPendingQty += chickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
|
||||
availableQty = productWarehouse.Quantity - totalPopulation - totalPendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
}
|
||||
|
||||
return availableQty, 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"`
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
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"
|
||||
|
||||
// pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
@@ -22,6 +23,13 @@ type ProjectFlockBaseDTO struct {
|
||||
FlockName string `json:"flock_name"`
|
||||
}
|
||||
|
||||
type KandangWithProjectFlockIdDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||
}
|
||||
|
||||
type ProjectFlockListDTO struct {
|
||||
ProjectFlockBaseDTO
|
||||
// Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"`
|
||||
@@ -93,15 +101,15 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
|
||||
return ProjectFlockListDTO{
|
||||
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
|
||||
// Flock: flockSummary,
|
||||
Area: areaSummary,
|
||||
Kandangs: kandangSummaries,
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
Approval: latestApproval,
|
||||
Area: areaSummary,
|
||||
Kandangs: kandangSummaries,
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
Approval: latestApproval,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+65
-7
@@ -9,8 +9,19 @@ import (
|
||||
)
|
||||
|
||||
type ProjectFlockPopulationRepository interface {
|
||||
repository.BaseRepository[entity.ProjectFlockPopulation]
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error)
|
||||
// domain-specific
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error)
|
||||
GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
|
||||
|
||||
// subset of base repository methods used by services
|
||||
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
||||
PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error
|
||||
|
||||
// transaction helpers
|
||||
WithTx(tx *gorm.DB) ProjectFlockPopulationRepository
|
||||
DB() *gorm.DB
|
||||
}
|
||||
|
||||
type projectFlockPopulationRepositoryImpl struct {
|
||||
@@ -23,13 +34,60 @@ func NewProjectFlockPopulationRepository(db *gorm.DB) ProjectFlockPopulationRepo
|
||||
}
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) {
|
||||
var record entity.ProjectFlockPopulation
|
||||
func (r *projectFlockPopulationRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockPopulationRepository {
|
||||
return &projectFlockPopulationRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlockPopulation](tx),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) DB() *gorm.DB {
|
||||
return r.BaseRepositoryImpl.DB()
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error) {
|
||||
var records []entity.ProjectFlockPopulation
|
||||
err := r.DB().WithContext(ctx).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
First(&record).Error
|
||||
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
||||
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Preload("ProjectChickin").
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error) {
|
||||
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
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) {
|
||||
var records []entity.ProjectFlockPopulation
|
||||
err := r.DB().WithContext(ctx).
|
||||
Where("project_chickin_id = ? AND product_warehouse_id = ?", projectChickinID, productWarehouseID).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) {
|
||||
var records []entity.ProjectFlockPopulation
|
||||
err := r.DB().WithContext(ctx).
|
||||
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
||||
Where("project_chickins.project_flock_kandang_id = ? AND project_flock_populations.product_warehouse_id = ?", projectFlockKandangID, productWarehouseID).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
+14
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -19,6 +20,7 @@ type ProjectFlockKandangRepository interface {
|
||||
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
||||
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
DB() *gorm.DB
|
||||
}
|
||||
|
||||
@@ -59,6 +61,9 @@ func (r *projectFlockKandangRepositoryImpl) GetAll(ctx context.Context) ([]entit
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
Preload("Chickins").
|
||||
Preload("Chickins.CreatedUser").
|
||||
Preload("Chickins.ProductWarehouse").
|
||||
Order("project_flock_id ASC, created_at ASC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -73,6 +78,9 @@ func (r *projectFlockKandangRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockKand
|
||||
func (r *projectFlockKandangRepositoryImpl) DB() *gorm.DB {
|
||||
return r.db
|
||||
}
|
||||
func (r *projectFlockKandangRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.ProjectFlockKandang](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) {
|
||||
record := new(entity.ProjectFlockKandang)
|
||||
@@ -85,6 +93,9 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
Preload("Chickins").
|
||||
Preload("Chickins.CreatedUser").
|
||||
Preload("Chickins.ProductWarehouse").
|
||||
First(record, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,6 +114,9 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
|
||||
Preload("ProjectFlock.Kandangs").
|
||||
Preload("ProjectFlock.KandangHistory").
|
||||
Preload("Kandang").
|
||||
Preload("Chickins").
|
||||
Preload("Chickins.CreatedUser").
|
||||
Preload("Chickins.ProductWarehouse").
|
||||
First(record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -80,6 +80,18 @@ func NewProjectflockService(
|
||||
}
|
||||
}
|
||||
|
||||
func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.
|
||||
Preload("CreatedUser").
|
||||
Preload("Flock").
|
||||
Preload("Area").
|
||||
Preload("Fcr").
|
||||
Preload("Location").
|
||||
Preload("Kandangs").
|
||||
Preload("KandangHistory").
|
||||
Preload("KandangHistory.Kandang")
|
||||
}
|
||||
|
||||
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
@@ -265,7 +265,7 @@ func (r *RecordingRepositoryImpl) GetTotalChick(tx *gorm.DB, projectFlockKandang
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(math.Round(population.InitialQuantity)), nil
|
||||
return int64(math.Round(population.TotalQty)), nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
chickins "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins"
|
||||
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
||||
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
||||
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
|
||||
)
|
||||
|
||||
@@ -20,8 +22,10 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
||||
projectflocks.ProjectflockModule{},
|
||||
recordings.RecordingModule{},
|
||||
chickins.ChickinModule{},
|
||||
transferLayings.TransferLayingModule{},
|
||||
projectFlockKandangs.ProjectFlockKandangModule{},
|
||||
// MODULE REGISTRY
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range allModules {
|
||||
m.RegisterRoutes(group, db, validate)
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type TransferLayingController struct {
|
||||
TransferLayingService service.TransferLayingService
|
||||
}
|
||||
|
||||
func NewTransferLayingController(transferLayingService service.TransferLayingService) *TransferLayingController {
|
||||
return &TransferLayingController{
|
||||
TransferLayingService: transferLayingService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
SourceProjectFlockId: uint(c.QueryInt("source_project_flock_id", 0)),
|
||||
TargetProjectFlockId: uint(c.QueryInt("target_project_flock_id", 0)),
|
||||
TransferDateFrom: c.Query("transfer_date_from", ""),
|
||||
TransferDateTo: c.Query("transfer_date_to", ""),
|
||||
ApprovalStatus: c.Query("approval_status", ""),
|
||||
TransferNumber: c.Query("transfer_number", ""),
|
||||
Sort: c.Query("sort", "created_at"),
|
||||
Order: c.Query("order", "desc"),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
}
|
||||
|
||||
result, totalResults, err := u.TransferLayingService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.TransferLayingListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all transferLayings successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToTransferLayingListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) 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, approval, err := u.TransferLayingService.GetOneWithApproval(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get transferLaying successfully",
|
||||
Data: dto.ToTransferLayingDetailDTOWithSingleApproval(*result, approval),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) CreateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Create)
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
result, err := u.TransferLayingService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create transferLaying successfully",
|
||||
Data: dto.ToTransferLayingListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) UpdateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Update)
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
result, err := u.TransferLayingService.UpdateOne(c, req, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Update transferLaying successfully",
|
||||
Data: dto.ToTransferLayingListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) DeleteOne(c *fiber.Ctx) error {
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
if err := u.TransferLayingService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete transferLaying successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (u *TransferLayingController) Approval(c *fiber.Ctx) error {
|
||||
req := new(validation.Approve)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
results, err := u.TransferLayingService.Approval(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
data interface{}
|
||||
message = "Submit transfer laying approval successfully"
|
||||
)
|
||||
if len(results) == 1 {
|
||||
data = dto.ToTransferLayingListDTO(results[0])
|
||||
} else {
|
||||
message = "Submit transfer laying approvals successfully"
|
||||
data = dto.ToTransferLayingListDTOs(results)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type TransferLayingBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
TransferNumber string `json:"transfer_number"`
|
||||
TransferDate time.Time `json:"transfer_date"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type ProjectFlockSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Period int `json:"period"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
type ProductSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type WarehouseSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ProductWarehouseSummaryDTO struct {
|
||||
Product *ProductSummaryDTO `json:"product,omitempty"`
|
||||
Warehouse *WarehouseSummaryDTO `json:"warehouse,omitempty"`
|
||||
}
|
||||
|
||||
type ProjectFlockKandangSummaryDTO struct {
|
||||
Id uint `json:"id"`
|
||||
KandangId uint `json:"kandang_id"`
|
||||
}
|
||||
|
||||
type LayingTransferSourceDTO struct {
|
||||
SourceProjectFlockKandang *ProjectFlockKandangSummaryDTO `json:"source_project_flock_kandang,omitempty"`
|
||||
Qty float64 `json:"qty"`
|
||||
ProductWarehouse *ProductWarehouseSummaryDTO `json:"product_warehouse,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
type LayingTransferTargetDTO struct {
|
||||
TargetProjectFlockKandang *ProjectFlockKandangSummaryDTO `json:"target_project_flock_kandang,omitempty"`
|
||||
Qty float64 `json:"qty"`
|
||||
ProductWarehouse *ProductWarehouseSummaryDTO `json:"product_warehouse,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
type TransferLayingListDTO struct {
|
||||
TransferLayingBaseDTO
|
||||
FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"`
|
||||
ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"`
|
||||
PendingUsageQty *float64 `json:"pending_usage_qty"`
|
||||
UsageQty *float64 `json:"usage_qty"`
|
||||
CreatedBy uint `json:"created_by"`
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||
}
|
||||
|
||||
type TransferLayingDetailDTO struct {
|
||||
TransferLayingListDTO
|
||||
Sources []LayingTransferSourceDTO `json:"sources,omitempty"`
|
||||
Targets []LayingTransferTargetDTO `json:"targets,omitempty"`
|
||||
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToProjectFlockSummaryDTO(pf *entity.ProjectFlock) *ProjectFlockSummaryDTO {
|
||||
if pf == nil || pf.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectFlockSummaryDTO{
|
||||
Id: pf.Id,
|
||||
Period: pf.Period,
|
||||
Category: pf.Category,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProjectFlockKandangSummaryDTO(pfk *entity.ProjectFlockKandang) *ProjectFlockKandangSummaryDTO {
|
||||
if pfk == nil || pfk.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectFlockKandangSummaryDTO{
|
||||
Id: pfk.Id,
|
||||
KandangId: pfk.KandangId,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductSummaryDTO(product *entity.Product) *ProductSummaryDTO {
|
||||
if product == nil || product.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProductSummaryDTO{
|
||||
Id: product.Id,
|
||||
Name: product.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func ToWarehouseSummaryDTO(warehouse *entity.Warehouse) *WarehouseSummaryDTO {
|
||||
if warehouse == nil || warehouse.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &WarehouseSummaryDTO{
|
||||
Id: warehouse.Id,
|
||||
Name: warehouse.Name,
|
||||
Type: warehouse.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductWarehouseSummaryDTO(pw *entity.ProductWarehouse) *ProductWarehouseSummaryDTO {
|
||||
if pw == nil || pw.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProductWarehouseSummaryDTO{
|
||||
Product: ToProductSummaryDTO(&pw.Product),
|
||||
Warehouse: ToWarehouseSummaryDTO(&pw.Warehouse),
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
||||
return LayingTransferSourceDTO{
|
||||
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
||||
Qty: source.Qty,
|
||||
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
||||
Note: source.Note,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferSourceDTOs(sources []entity.LayingTransferSource) []LayingTransferSourceDTO {
|
||||
if len(sources) == 0 {
|
||||
return []LayingTransferSourceDTO{}
|
||||
}
|
||||
result := make([]LayingTransferSourceDTO, len(sources))
|
||||
for i, s := range sources {
|
||||
result[i] = ToLayingTransferSourceDTO(s)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToLayingTransferTargetDTO(target entity.LayingTransferTarget) LayingTransferTargetDTO {
|
||||
return LayingTransferTargetDTO{
|
||||
TargetProjectFlockKandang: ToProjectFlockKandangSummaryDTO(target.TargetProjectFlockKandang),
|
||||
Qty: target.Qty,
|
||||
ProductWarehouse: ToProductWarehouseSummaryDTO(target.ProductWarehouse),
|
||||
Note: target.Note,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLayingTransferTargetDTOs(targets []entity.LayingTransferTarget) []LayingTransferTargetDTO {
|
||||
if len(targets) == 0 {
|
||||
return []LayingTransferTargetDTO{}
|
||||
}
|
||||
result := make([]LayingTransferTargetDTO, len(targets))
|
||||
for i, t := range targets {
|
||||
result[i] = ToLayingTransferTargetDTO(t)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToTransferLayingBaseDTO(e entity.LayingTransfer) TransferLayingBaseDTO {
|
||||
return TransferLayingBaseDTO{
|
||||
Id: e.Id,
|
||||
TransferNumber: e.TransferNumber,
|
||||
TransferDate: e.TransferDate,
|
||||
Notes: e.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return TransferLayingListDTO{
|
||||
TransferLayingBaseDTO: ToTransferLayingBaseDTO(e),
|
||||
FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock),
|
||||
ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock),
|
||||
PendingUsageQty: e.PendingUsageQty,
|
||||
UsageQty: e.UsageQty,
|
||||
CreatedBy: e.CreatedBy,
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Approval) TransferLayingDetailDTO {
|
||||
var latestApproval *approvalDTO.ApprovalBaseDTO
|
||||
|
||||
// Use LatestApproval from entity if available
|
||||
if e.LatestApproval != nil {
|
||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||
latestApproval = &mapped
|
||||
} else if len(approvals) > 0 {
|
||||
// Fallback to approvals slice
|
||||
latest := approvalDTO.ToApprovalDTO(approvals[len(approvals)-1])
|
||||
latestApproval = &latest
|
||||
}
|
||||
|
||||
return TransferLayingDetailDTO{
|
||||
TransferLayingListDTO: ToTransferLayingListDTO(e),
|
||||
Sources: ToLayingTransferSourceDTOs(e.Sources),
|
||||
Targets: ToLayingTransferTargetDTOs(e.Targets),
|
||||
Approval: latestApproval,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingDetailDTOWithSingleApproval(e entity.LayingTransfer, approval *entity.Approval) TransferLayingDetailDTO {
|
||||
var mappedApproval *approvalDTO.ApprovalBaseDTO
|
||||
|
||||
// Prefer LatestApproval from entity
|
||||
if e.LatestApproval != nil && e.LatestApproval.Id != 0 {
|
||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||
mappedApproval = &mapped
|
||||
} else if approval != nil && approval.Id != 0 {
|
||||
// Fallback to passed approval parameter
|
||||
mapped := approvalDTO.ToApprovalDTO(*approval)
|
||||
mappedApproval = &mapped
|
||||
}
|
||||
|
||||
return TransferLayingDetailDTO{
|
||||
TransferLayingListDTO: ToTransferLayingListDTO(e),
|
||||
Sources: ToLayingTransferSourceDTOs(e.Sources),
|
||||
Targets: ToLayingTransferTargetDTOs(e.Targets),
|
||||
Approval: mappedApproval,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTransferLayingListDTOs(items []entity.LayingTransfer) []TransferLayingListDTO {
|
||||
result := make([]TransferLayingListDTO, len(items))
|
||||
for i, item := range items {
|
||||
result[i] = ToTransferLayingListDTO(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package transfer_layings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type TransferLayingModule struct{}
|
||||
|
||||
func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register transfer to laying approval workflow: %v", err))
|
||||
}
|
||||
|
||||
transferLayingService := sTransferLaying.NewTransferLayingService(
|
||||
transferLayingRepo,
|
||||
projectFlockRepo,
|
||||
projectFlockKandangRepo,
|
||||
projectFlockPopulationRepo,
|
||||
productWarehouseRepo,
|
||||
warehouseRepo,
|
||||
approvalService,
|
||||
validate,
|
||||
)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
TransferLayingRoutes(router, userService, transferLayingService)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TransferLayingRepository interface {
|
||||
repository.BaseRepository[entity.LayingTransfer]
|
||||
GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
}
|
||||
|
||||
type TransferLayingRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.LayingTransfer]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTransferLayingRepository(db *gorm.DB) TransferLayingRepository {
|
||||
return &TransferLayingRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.LayingTransfer](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
func (r *TransferLayingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.LayingTransfer](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *TransferLayingRepositoryImpl) GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error) {
|
||||
var transfer entity.LayingTransfer
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("transfer_number = ?", transferNumber).
|
||||
Where("deleted_at IS NULL").
|
||||
First(&transfer).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &transfer, nil
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingTransferSourceRepository interface {
|
||||
repository.BaseRepository[entity.LayingTransferSource]
|
||||
GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferSource, error)
|
||||
}
|
||||
|
||||
type LayingTransferSourceRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.LayingTransferSource]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewLayingTransferSourceRepository(db *gorm.DB) LayingTransferSourceRepository {
|
||||
return &LayingTransferSourceRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.LayingTransferSource](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LayingTransferSourceRepositoryImpl) GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferSource, error) {
|
||||
var sources []entity.LayingTransferSource
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("laying_transfer_id = ?", layingTransferId).
|
||||
Order("created_at DESC").
|
||||
Find(&sources).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LayingTransferTargetRepository interface {
|
||||
repository.BaseRepository[entity.LayingTransferTarget]
|
||||
GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferTarget, error)
|
||||
}
|
||||
|
||||
type LayingTransferTargetRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.LayingTransferTarget]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewLayingTransferTargetRepository(db *gorm.DB) LayingTransferTargetRepository {
|
||||
return &LayingTransferTargetRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.LayingTransferTarget](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LayingTransferTargetRepositoryImpl) GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferTarget, error) {
|
||||
var targets []entity.LayingTransferTarget
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("laying_transfer_id = ?", layingTransferId).
|
||||
Order("created_at DESC").
|
||||
Find(&targets).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package transfer_layings
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/controllers"
|
||||
transferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying.TransferLayingService) {
|
||||
ctrl := controller.NewTransferLayingController(s)
|
||||
|
||||
route := v1.Group("/transfer_layings")
|
||||
|
||||
// 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.Post("/approval", m.Auth(u), ctrl.Approval)
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
||||
"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 TransferLayingService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, error)
|
||||
GetOneWithApproval(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.LayingTransfer, error)
|
||||
}
|
||||
|
||||
type transferLayingService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.TransferLayingRepository
|
||||
ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository
|
||||
ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository
|
||||
ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository
|
||||
ProductWarehouseRepo rInventory.ProductWarehouseRepository
|
||||
WarehouseRepo rWarehouse.WarehouseRepository
|
||||
ApprovalService commonSvc.ApprovalService
|
||||
}
|
||||
|
||||
func NewTransferLayingService(
|
||||
repo repository.TransferLayingRepository,
|
||||
projectFlockRepo ProjectFlockRepository.ProjectflockRepository,
|
||||
projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository,
|
||||
projectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository,
|
||||
productWarehouseRepo rInventory.ProductWarehouseRepository,
|
||||
warehouseRepo rWarehouse.WarehouseRepository,
|
||||
approvalService commonSvc.ApprovalService,
|
||||
validate *validator.Validate,
|
||||
) TransferLayingService {
|
||||
return &transferLayingService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ProjectFlockRepo: projectFlockRepo,
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
WarehouseRepo: warehouseRepo,
|
||||
ApprovalService: approvalService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s transferLayingService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.
|
||||
Preload("CreatedUser").
|
||||
Preload("Sources").
|
||||
Preload("Sources.SourceProjectFlockKandang").
|
||||
Preload("Sources.ProductWarehouse").
|
||||
Preload("Sources.ProductWarehouse.Product").
|
||||
Preload("Sources.ProductWarehouse.Warehouse").
|
||||
Preload("Targets").
|
||||
Preload("Targets.TargetProjectFlockKandang").
|
||||
Preload("Targets.ProductWarehouse").
|
||||
Preload("Targets.ProductWarehouse.Product").
|
||||
Preload("Targets.ProductWarehouse.Warehouse")
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
if params.SourceProjectFlockId != 0 {
|
||||
db = db.Where("from_project_flock_id = ?", params.SourceProjectFlockId)
|
||||
}
|
||||
if params.TargetProjectFlockId != 0 {
|
||||
db = db.Where("to_project_flock_id = ?", params.TargetProjectFlockId)
|
||||
}
|
||||
if params.TransferDateFrom != "" {
|
||||
db = db.Where("transfer_date >= ?", params.TransferDateFrom)
|
||||
}
|
||||
if params.TransferDateTo != "" {
|
||||
db = db.Where("transfer_date <= ?", params.TransferDateTo)
|
||||
}
|
||||
if params.TransferNumber != "" {
|
||||
db = db.Where("transfer_number ILIKE ?", "%"+params.TransferNumber+"%")
|
||||
}
|
||||
|
||||
sortField := "created_at"
|
||||
if params.Sort != "" {
|
||||
sortField = params.Sort
|
||||
}
|
||||
sortOrder := "DESC"
|
||||
if params.Order == "asc" {
|
||||
sortOrder = "ASC"
|
||||
}
|
||||
db = db.Order(fmt.Sprintf("%s %s", sortField, sortOrder))
|
||||
|
||||
return db
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get transferLayings: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if params.ApprovalStatus != "" {
|
||||
var filtered []entity.LayingTransfer
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
|
||||
for _, transfer := range transferLayings {
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transfer.Id, nil)
|
||||
if err == nil && latestApproval != nil && latestApproval.Action != nil {
|
||||
if string(*latestApproval.Action) == params.ApprovalStatus {
|
||||
filtered = append(filtered, transfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
transferLayings = filtered
|
||||
}
|
||||
|
||||
return transferLayings, total, nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, error) {
|
||||
transferLaying, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get transferLaying by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, nil)
|
||||
if err == nil && latestApproval != nil {
|
||||
transferLaying.LatestApproval = latestApproval
|
||||
}
|
||||
|
||||
return transferLaying, nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetOneWithApproval(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) {
|
||||
transferLaying, err := s.GetOne(c, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return transferLaying, transferLaying.LatestApproval, nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Source Project Flock not found")
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate source project flock")
|
||||
}
|
||||
|
||||
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.TargetProjectFlockId, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Target Project Flock not found")
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate target project flock")
|
||||
}
|
||||
|
||||
for _, detail := range req.SourceKandangs {
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Source Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get source project flock kandang")
|
||||
}
|
||||
if pfk.ProjectFlockId != req.SourceProjectFlockId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d does not belong to source project flock %d", detail.ProjectFlockKandangId, req.SourceProjectFlockId))
|
||||
}
|
||||
}
|
||||
|
||||
for _, detail := range req.TargetKandangs {
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Target Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
if pfk.ProjectFlockId != req.TargetProjectFlockId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Target kandang %d does not belong to target project flock %d", detail.ProjectFlockKandangId, req.TargetProjectFlockId))
|
||||
}
|
||||
}
|
||||
|
||||
transferDate, err := utils.ParseDateString(req.TransferDate)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format")
|
||||
}
|
||||
|
||||
var totalSourceQty, totalTargetQty float64
|
||||
sourceWarehouseMap := make(map[uint]uint)
|
||||
|
||||
for _, sourceDetail := range req.SourceKandangs {
|
||||
if sourceDetail.Quantity <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Source kandang quantity must be greater than 0")
|
||||
}
|
||||
totalSourceQty += sourceDetail.Quantity
|
||||
|
||||
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var totalPopulation float64
|
||||
var productWarehouseId uint
|
||||
for _, pop := range populations {
|
||||
totalPopulation += pop.TotalQty
|
||||
if productWarehouseId == 0 {
|
||||
productWarehouseId = pop.ProductWarehouseId
|
||||
}
|
||||
}
|
||||
|
||||
if totalPopulation == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available for transfer", sourceDetail.ProjectFlockKandangId))
|
||||
}
|
||||
|
||||
if totalPopulation < sourceDetail.Quantity {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has insufficient quantity. Available: %.0f, Requested: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
||||
}
|
||||
|
||||
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
||||
}
|
||||
|
||||
for _, targetDetail := range req.TargetKandangs {
|
||||
if targetDetail.Quantity <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Target kandang quantity must be greater than 0")
|
||||
}
|
||||
totalTargetQty += targetDetail.Quantity
|
||||
}
|
||||
|
||||
if totalSourceQty != totalTargetQty {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Total source quantity (%f) must equal total target quantity (%f)", totalSourceQty, totalTargetQty))
|
||||
}
|
||||
|
||||
transferNumber := fmt.Sprintf("TL-%d", time.Now().UnixNano())
|
||||
|
||||
createBody := &entity.LayingTransfer{
|
||||
TransferNumber: transferNumber,
|
||||
Notes: req.Reason,
|
||||
FromProjectFlockId: req.SourceProjectFlockId,
|
||||
ToProjectFlockId: req.TargetProjectFlockId,
|
||||
TransferDate: transferDate,
|
||||
PendingUsageQty: &totalSourceQty,
|
||||
CreatedBy: 1, //todo : harus diambil dari auth
|
||||
}
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying record")
|
||||
}
|
||||
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, sourceDetail := range req.SourceKandangs {
|
||||
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
|
||||
|
||||
source := entity.LayingTransferSource{
|
||||
LayingTransferId: createBody.Id,
|
||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||
Qty: sourceDetail.Quantity,
|
||||
ProductWarehouseId: &productWarehouseId,
|
||||
}
|
||||
if err := dbTransaction.Create(&source).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
|
||||
}
|
||||
|
||||
if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
|
||||
}
|
||||
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"quantity": gorm.Expr("quantity - ?", sourceDetail.Quantity)}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
||||
}
|
||||
}
|
||||
|
||||
for _, targetDetail := range req.TargetKandangs {
|
||||
|
||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
|
||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||
}
|
||||
|
||||
target := entity.LayingTransferTarget{
|
||||
LayingTransferId: createBody.Id,
|
||||
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
||||
Qty: targetDetail.Quantity,
|
||||
ProductWarehouseId: &targetWarehouse.Id,
|
||||
}
|
||||
if err := dbTransaction.Create(&target).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer target")
|
||||
}
|
||||
}
|
||||
|
||||
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer approval")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying")
|
||||
}
|
||||
|
||||
return s.GetOne(c, createBody.Id)
|
||||
}
|
||||
|
||||
func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer laying")
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
|
||||
if latestApproval != nil && latestApproval.Action != nil {
|
||||
action := string(*latestApproval.Action)
|
||||
if action == string(entity.ApprovalActionApproved) || action == string(entity.ApprovalActionRejected) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot update transfer laying with status %s", action))
|
||||
}
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.TransferDate != nil {
|
||||
updateBody["transfer_date"] = *req.TransferDate
|
||||
}
|
||||
|
||||
if req.Reason != nil {
|
||||
updateBody["notes"] = *req.Reason
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update transferLaying: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying")
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
|
||||
_, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer laying")
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
|
||||
if latestApproval != nil && latestApproval.Action != nil {
|
||||
action := string(*latestApproval.Action)
|
||||
if action == string(entity.ApprovalActionApproved) || action == string(entity.ApprovalActionRejected) {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete transfer laying with status %s", action))
|
||||
}
|
||||
}
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), id)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.ProductWarehouseId != nil && source.Qty > 0 {
|
||||
|
||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *source.ProductWarehouseId, map[string]any{
|
||||
"quantity": gorm.Expr("quantity + ?", source.Qty),
|
||||
}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore source warehouse quantity")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
||||
for _, source := range sources {
|
||||
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), source.SourceProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations for restoration")
|
||||
}
|
||||
|
||||
remainingToRestore := source.Qty
|
||||
for i := len(populations) - 1; i >= 0 && remainingToRestore > 0; i-- {
|
||||
pop := populations[i]
|
||||
restoreAmount := remainingToRestore
|
||||
if remainingToRestore < pop.TotalQty {
|
||||
|
||||
restoreAmount = remainingToRestore
|
||||
}
|
||||
|
||||
newQty := pop.TotalQty + restoreAmount
|
||||
if err := projectFlockPopulationRepoTx.PatchOne(c.Context(), pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore population quantity")
|
||||
}
|
||||
|
||||
remainingToRestore -= restoreAmount
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).DeleteOne(c.Context(), id); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return fiberErr
|
||||
}
|
||||
s.Log.Errorf("Failed to delete transferLaying: %+v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.LayingTransfer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID := uint(1) // TODO: change from auth context
|
||||
var action entity.ApprovalAction
|
||||
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||
case string(entity.ApprovalActionRejected):
|
||||
action = entity.ApprovalActionRejected
|
||||
case string(entity.ApprovalActionApproved):
|
||||
action = entity.ApprovalActionApproved
|
||||
default:
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||||
}
|
||||
|
||||
approvableIDs := utils.UniqueUintSlice(req.ApprovableIds)
|
||||
if len(approvableIDs) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||
}
|
||||
|
||||
step := utils.TransferToLayingStepPengajuan
|
||||
if action == entity.ApprovalActionApproved {
|
||||
step = utils.TransferToLayingStepDisetujui
|
||||
}
|
||||
|
||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, approvableID := range approvableIDs {
|
||||
transfer, err := s.Repository.GetByID(c.Context(), approvableID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("TransferLaying %d not found", approvableID))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := approvalSvcTx.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowTransferToLaying,
|
||||
approvableID,
|
||||
step,
|
||||
&action,
|
||||
actorID,
|
||||
req.Notes,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||
}
|
||||
|
||||
if action == entity.ApprovalActionApproved && transfer.PendingUsageQty != nil && *transfer.PendingUsageQty > 0 {
|
||||
|
||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
||||
}
|
||||
|
||||
targets, err := targetRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer targets")
|
||||
}
|
||||
|
||||
if len(sources) > 0 && len(targets) > 0 {
|
||||
firstSource := sources[0]
|
||||
if firstSource.ProductWarehouseId == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
|
||||
}
|
||||
|
||||
sourceWarehouse, err := productWarehouseRepoTx.GetByID(c.Context(), *firstSource.ProductWarehouseId, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source warehouse")
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
|
||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), target.TargetProjectFlockKandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||
}
|
||||
|
||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||
}
|
||||
|
||||
if _, err := s.getOrCreateProductWarehouse(
|
||||
c.Context(),
|
||||
dbTransaction,
|
||||
sourceWarehouse.ProductId,
|
||||
targetWarehouse.Id,
|
||||
target.Qty,
|
||||
actorID,
|
||||
); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create or update product warehouse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usageQty := *transfer.PendingUsageQty
|
||||
updateData := map[string]any{
|
||||
"usage_qty": usageQty,
|
||||
"pending_usage_qty": nil,
|
||||
}
|
||||
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return nil, fiberErr
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||
}
|
||||
|
||||
updated := make([]entity.LayingTransfer, 0, len(approvableIDs))
|
||||
for _, approvableID := range approvableIDs {
|
||||
transfer, err := s.GetOne(c, approvableID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updated = append(updated, *transfer)
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func createApprovalTransferLaying(ctx context.Context, tx *gorm.DB, transferLayingID uint, actorID uint) error {
|
||||
if transferLayingID == 0 || actorID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||
action := entity.ApprovalActionCreated
|
||||
|
||||
_, err := approvalSvc.CreateApproval(
|
||||
ctx,
|
||||
utils.ApprovalWorkflowTransferToLaying,
|
||||
transferLayingID,
|
||||
utils.TransferToLayingStepPengajuan,
|
||||
&action,
|
||||
actorID,
|
||||
nil,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context, tx *gorm.DB, productID uint, warehouseID uint, quantity float64, actorID uint) (*entity.ProductWarehouse, error) {
|
||||
|
||||
productWarehouseRepoTx := rInventory.NewProductWarehouseRepository(tx)
|
||||
|
||||
existing, err := productWarehouseRepoTx.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
||||
if err == nil && existing != nil {
|
||||
|
||||
if err := productWarehouseRepoTx.PatchOne(ctx, existing.Id, map[string]any{"quantity": gorm.Expr("quantity + ?", quantity)}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return existing, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newWarehouse := &entity.ProductWarehouse{
|
||||
ProductId: productID,
|
||||
WarehouseId: warehouseID,
|
||||
Quantity: quantity,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newWarehouse, nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) reduceProjectFlockPopulation(ctx context.Context, populationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockKandangID uint, quantityToReduce float64) error {
|
||||
|
||||
populations, err := populationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(populations) == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "No populations found for reduction")
|
||||
}
|
||||
|
||||
remainingToReduce := quantityToReduce
|
||||
|
||||
for i := len(populations) - 1; i >= 0; i-- {
|
||||
if remainingToReduce <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
pop := populations[i]
|
||||
reductionAmount := remainingToReduce
|
||||
if pop.TotalQty < remainingToReduce {
|
||||
reductionAmount = pop.TotalQty
|
||||
}
|
||||
|
||||
newQty := pop.TotalQty - reductionAmount
|
||||
if err := populationRepo.PatchOne(ctx, pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remainingToReduce -= reductionAmount
|
||||
}
|
||||
|
||||
if remainingToReduce > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient population to reduce. Still need to reduce: %.0f", remainingToReduce))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package validation
|
||||
|
||||
type SourceKandangDetail struct {
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||
}
|
||||
|
||||
type TargetKandangDetail struct {
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||
}
|
||||
|
||||
type Create struct {
|
||||
TransferDate string `json:"transfer_date" validate:"required,datetime=2006-01-02"`
|
||||
SourceProjectFlockId uint `json:"source_project_flock_id" validate:"required"`
|
||||
TargetProjectFlockId uint `json:"target_project_flock_id" validate:"required"`
|
||||
SourceKandangs []SourceKandangDetail `json:"source_kandangs" validate:"required,min=1,dive,required"`
|
||||
TargetKandangs []TargetKandangDetail `json:"target_kandangs" validate:"required,min=1,dive,required"`
|
||||
Reason string `json:"reason" validate:"omitempty,max=1000"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
TransferDate *string `json:"transfer_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
|
||||
Reason *string `json:"reason,omitempty" validate:"omitempty,max=1000"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
SourceProjectFlockId uint `query:"source_project_flock_id" validate:"omitempty"`
|
||||
TargetProjectFlockId uint `query:"target_project_flock_id" validate:"omitempty"`
|
||||
TransferDateFrom string `query:"transfer_date_from" validate:"omitempty,datetime=2006-01-02"`
|
||||
TransferDateTo string `query:"transfer_date_to" validate:"omitempty,datetime=2006-01-02"`
|
||||
ApprovalStatus string `query:"approval_status" validate:"omitempty,oneof=PENDING APPROVED REJECTED"` // Filter by latest approval status
|
||||
TransferNumber string `query:"transfer_number" validate:"omitempty"` // Search by transfer number
|
||||
Sort string `query:"sort" validate:"omitempty,oneof=created_at transfer_date"` // Sort by field
|
||||
Order string `query:"order" validate:"omitempty,oneof=asc desc"` // Sort order
|
||||
}
|
||||
|
||||
type Approve struct {
|
||||
Action string `json:"action" validate:"required_strict"`
|
||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
}
|
||||
@@ -18,6 +18,8 @@ const (
|
||||
FlagIsActive FlagType = "IS_ACTIVE"
|
||||
|
||||
FlagDOC FlagType = "DOC"
|
||||
FlagPullet FlagType = "PULLET"
|
||||
FlagLayer FlagType = "LAYER"
|
||||
FlagPakan FlagType = "PAKAN"
|
||||
FlagPreStarter FlagType = "PRE-STARTER"
|
||||
FlagStarter FlagType = "STARTER"
|
||||
@@ -37,6 +39,8 @@ const (
|
||||
var flagGroupOptions = map[FlagGroup][]FlagType{
|
||||
FlagGroupProduct: {
|
||||
FlagDOC,
|
||||
FlagPullet,
|
||||
FlagLayer,
|
||||
FlagPakan,
|
||||
FlagPreStarter,
|
||||
FlagStarter,
|
||||
@@ -79,6 +83,24 @@ const (
|
||||
WarehouseTypeKandang WarehouseType = "KANDANG"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Stock log
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type StockLogTransactionType string
|
||||
|
||||
const (
|
||||
StockLogTransactionTypeIncrease StockLogTransactionType = "INCREASE"
|
||||
StockLogTransactionTypeDecrease StockLogTransactionType = "DECREASE"
|
||||
)
|
||||
|
||||
type StockLogType string
|
||||
|
||||
const (
|
||||
StockLogTypeAdjustment StockLogType = "ADJUSTMENT"
|
||||
StockLogTypeTransfer StockLogType = "TRANSFER"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// WarehouseType
|
||||
// -------------------------------------------------------------------
|
||||
@@ -140,6 +162,36 @@ var ProjectFlockApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
ProjectFlockStepAktif: "Aktif",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Project Flock Kandang Approval
|
||||
// -------------------------------------------------------------------
|
||||
const (
|
||||
ApprovalWorkflowProjectFlockKandang approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("PROJECT_FLOCK_KANDANGS")
|
||||
ProjectFlockKandangStepPengajuan approvalutils.ApprovalStep = 1
|
||||
ProjectFlockKandangStepDisetujui approvalutils.ApprovalStep = 2
|
||||
)
|
||||
|
||||
var ProjectFlockKandangApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
ProjectFlockKandangStepPengajuan: "Pengajuan",
|
||||
ProjectFlockKandangStepDisetujui: "Disetujui",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Transfer To laying Approval
|
||||
// -------------------------------------------------------------------
|
||||
const (
|
||||
ApprovalWorkflowTransferToLaying approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("TRANSFER_TO_LAYINGS")
|
||||
TransferToLayingStepPengajuan approvalutils.ApprovalStep = 1
|
||||
TransferToLayingStepDisetujui approvalutils.ApprovalStep = 2
|
||||
)
|
||||
|
||||
var TransferToLayingApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
TransferToLayingStepPengajuan: "Pengajuan",
|
||||
TransferToLayingStepDisetujui: "Disetujui",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Recording Approval
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user