productstock

This commit is contained in:
ragilap
2025-12-31 07:08:03 +07:00
111 changed files with 6946 additions and 1060 deletions
@@ -29,7 +29,7 @@ ADD CONSTRAINT fk_project_chickins_kandang FOREIGN KEY (project_flock_kandang_id
-- 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;
ADD CONSTRAINT fk_project_chickins_warehouse FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses (id) ON DELETE CASCADE ON UPDATE CASCADE;
-- Relasi ke users
ALTER TABLE project_chickins
@@ -0,0 +1,3 @@
DROP INDEX IF EXISTS idx_payments_bank_id;
DROP INDEX IF EXISTS payments_party_polymorphic;
DROP TABLE IF EXISTS payments;
@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS payments (
id BIGSERIAL PRIMARY KEY,
payment_code VARCHAR(50) NOT NULL,
reference_number VARCHAR(100) NULL,
transaction_type VARCHAR(50),
party_type VARCHAR(50) NOT NULL,
party_id BIGINT NOT NULL,
payment_date TIMESTAMPTZ NOT NULL,
payment_method VARCHAR(20) NOT NULL,
bank_id BIGINT NULL REFERENCES banks(id) ON DELETE RESTRICT ON UPDATE CASCADE,
direction VARCHAR(5) NOT NULL,
nominal NUMERIC(15, 3) NOT NULL,
notes TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL,
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
);
-- Indexes
CREATE INDEX payments_party_polymorphic ON payments (party_type, party_id);
CREATE INDEX idx_payments_bank_id ON payments (bank_id);
@@ -0,0 +1,18 @@
DO $$
DECLARE
r record;
trigger_name text;
BEGIN
FOR r IN
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'deleted_at'
AND table_schema = 'public'
GROUP BY table_schema, table_name
LOOP
trigger_name := format('trg_soft_delete_fk_%s', r.table_name);
EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', trigger_name, r.table_schema, r.table_name);
END LOOP;
END $$;
DROP FUNCTION IF EXISTS soft_delete_handle_fk();
@@ -0,0 +1,126 @@
CREATE OR REPLACE FUNCTION soft_delete_handle_fk() RETURNS TRIGGER AS $$
DECLARE
fk record;
child_column text;
parent_column text;
parent_value text;
child_has_deleted_at boolean;
ref_exists boolean;
sql text;
BEGIN
IF OLD.deleted_at IS NULL AND NEW.deleted_at IS NOT NULL THEN
FOR fk IN
SELECT conrelid::regclass AS child_table,
conkey AS child_cols,
confkey AS parent_cols,
confdeltype
FROM pg_constraint
WHERE contype = 'f'
AND confrelid = TG_RELID
LOOP
IF array_length(fk.child_cols, 1) IS DISTINCT FROM 1
OR array_length(fk.parent_cols, 1) IS DISTINCT FROM 1 THEN
RAISE NOTICE 'soft_delete_handle_fk skipped composite fk on %', fk.child_table;
CONTINUE;
END IF;
SELECT attname INTO child_column
FROM pg_attribute
WHERE attrelid = fk.child_table
AND attnum = fk.child_cols[1]
AND NOT attisdropped;
SELECT attname INTO parent_column
FROM pg_attribute
WHERE attrelid = TG_RELID
AND attnum = fk.parent_cols[1]
AND NOT attisdropped;
EXECUTE format('SELECT ($1).%I', parent_column)
INTO parent_value
USING OLD;
SELECT EXISTS (
SELECT 1
FROM pg_attribute
WHERE attrelid = fk.child_table
AND attname = 'deleted_at'
AND NOT attisdropped
) INTO child_has_deleted_at;
IF fk.confdeltype IN ('r', 'a') THEN
sql := format(
'SELECT EXISTS (SELECT 1 FROM %s WHERE %I = $1 %s)',
fk.child_table,
child_column,
CASE WHEN child_has_deleted_at THEN 'AND deleted_at IS NULL' ELSE '' END
);
EXECUTE sql INTO ref_exists USING parent_value;
IF ref_exists THEN
RAISE EXCEPTION 'Cannot soft delete %, still referenced by %',
TG_TABLE_NAME, fk.child_table;
END IF;
ELSIF fk.confdeltype = 'n' THEN
sql := format(
'UPDATE %s SET %I = NULL WHERE %I = $1 %s',
fk.child_table,
child_column,
child_column,
CASE WHEN child_has_deleted_at THEN 'AND deleted_at IS NULL' ELSE '' END
);
EXECUTE sql USING parent_value;
ELSIF fk.confdeltype = 'c' THEN
IF child_has_deleted_at THEN
sql := format(
'UPDATE %s SET deleted_at = NOW() WHERE %I = $1 AND deleted_at IS NULL',
fk.child_table,
child_column
);
EXECUTE sql USING parent_value;
ELSE
sql := format(
'DELETE FROM %s WHERE %I = $1',
fk.child_table,
child_column
);
EXECUTE sql USING parent_value;
END IF;
ELSIF fk.confdeltype = 'd' THEN
sql := format(
'UPDATE %s SET %I = DEFAULT WHERE %I = $1 %s',
fk.child_table,
child_column,
child_column,
CASE WHEN child_has_deleted_at THEN 'AND deleted_at IS NULL' ELSE '' END
);
EXECUTE sql USING parent_value;
END IF;
END LOOP;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
r record;
trigger_name text;
BEGIN
FOR r IN
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'deleted_at'
AND table_schema = 'public'
GROUP BY table_schema, table_name
LOOP
trigger_name := format('trg_soft_delete_fk_%s', r.table_name);
EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I.%I', trigger_name, r.table_schema, r.table_name);
EXECUTE format(
'CREATE TRIGGER %I BEFORE UPDATE OF deleted_at ON %I.%I FOR EACH ROW EXECUTE FUNCTION soft_delete_handle_fk()',
trigger_name,
r.table_schema,
r.table_name
);
END LOOP;
END $$;
@@ -0,0 +1 @@
DROP SEQUENCE IF EXISTS payments_code_seq;
@@ -0,0 +1 @@
CREATE SEQUENCE IF NOT EXISTS payments_code_seq START WITH 1 INCREMENT BY 1;
@@ -0,0 +1,24 @@
-- Rollback: Update expense and expense_nonstocks tables
-- Drop indexes
DROP INDEX IF EXISTS idx_expenses_project_flock_id;
DROP INDEX IF EXISTS idx_expenses_location_id;
-- Drop Foreign Key constraint
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_expenses_location_id'
) THEN
ALTER TABLE expenses
DROP CONSTRAINT fk_expenses_location_id;
END IF;
END $$;
-- Drop columns from expenses table
ALTER TABLE expenses
DROP COLUMN IF EXISTS project_flock_id;
ALTER TABLE expenses
DROP COLUMN IF EXISTS location_id;
@@ -0,0 +1,29 @@
-- Migration: Update expense and expense_nonstocks tables
-- Add location_id column to expenses table
ALTER TABLE expenses
ADD COLUMN IF NOT EXISTS location_id BIGINT NOT NULL DEFAULT 1;
-- Add project_flock_id column to expenses table (JSON type)
ALTER TABLE expenses
ADD COLUMN IF NOT EXISTS project_flock_id JSON NULL;
-- Add Foreign Key constraint to locations table
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'locations') THEN
ALTER TABLE expenses
ADD CONSTRAINT fk_expenses_location_id
FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE RESTRICT ON UPDATE CASCADE;
END IF;
END $$;
-- Create index for location_id
CREATE INDEX IF NOT EXISTS idx_expenses_location_id ON expenses (location_id);
-- Create index for project_flock_id
CREATE INDEX IF NOT EXISTS idx_expenses_project_flock_id ON expenses ((project_flock_id::text));
-- Ensure kandang_id is nullable in expense_nonstocks table
ALTER TABLE expense_nonstocks
ALTER COLUMN kandang_id DROP NOT NULL;
@@ -0,0 +1,42 @@
-- ===============================================================
-- ROLLBACK: Remove FIFO fields from STOCK_TRANSFER_DETAILS
-- ===============================================================
-- Drop indexes
DROP INDEX IF EXISTS idx_stock_transfer_details_dest_pw;
DROP INDEX IF EXISTS idx_stock_transfer_details_source_pw;
-- Drop foreign keys
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_stock_transfer_details_source_pw'
) THEN
EXECUTE 'ALTER TABLE stock_transfer_details
DROP CONSTRAINT fk_stock_transfer_details_source_pw';
END IF;
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_stock_transfer_details_dest_pw'
) THEN
EXECUTE 'ALTER TABLE stock_transfer_details
DROP CONSTRAINT fk_stock_transfer_details_dest_pw';
END IF;
END $$;
-- Drop FIFO columns
ALTER TABLE stock_transfer_details
DROP COLUMN IF EXISTS total_used,
DROP COLUMN IF EXISTS total_qty,
DROP COLUMN IF EXISTS pending_qty,
DROP COLUMN IF EXISTS usage_qty,
DROP COLUMN IF EXISTS dest_product_warehouse_id,
DROP COLUMN IF EXISTS source_product_warehouse_id;
-- Restore original columns (in case rollback)
ALTER TABLE stock_transfer_details
ADD COLUMN IF NOT EXISTS quantity NUMERIC(15, 3) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS before_quantity NUMERIC(15, 3),
ADD COLUMN IF NOT EXISTS after_quantity NUMERIC(15, 3);
@@ -0,0 +1,83 @@
-- ===============================================================
-- ADD FIFO FIELDS TO STOCK_TRANSFER_DETAILS
-- Enable transfer module to work with FIFO stock system
--
-- Notes:
-- - Field 'quantity' will be removed (replaced by usage_qty + pending_qty)
-- - Fields 'before_quantity' & 'after_quantity' will be removed (unused legacy)
-- - New FIFO fields track actual allocation instead of requested quantity
-- ===============================================================
-- Add FIFO tracking fields
ALTER TABLE stock_transfer_details
ADD COLUMN IF NOT EXISTS source_product_warehouse_id BIGINT,
ADD COLUMN IF NOT EXISTS dest_product_warehouse_id BIGINT,
ADD COLUMN IF NOT EXISTS usage_qty NUMERIC(15, 3) DEFAULT 0,
ADD COLUMN IF NOT EXISTS pending_qty NUMERIC(15, 3) DEFAULT 0,
ADD COLUMN IF NOT EXISTS total_qty NUMERIC(15, 3) DEFAULT 0,
ADD COLUMN IF NOT EXISTS total_used NUMERIC(15, 3) DEFAULT 0;
-- Remove obsolete columns (quantity replaced by FIFO fields, legacy fields never used)
ALTER TABLE stock_transfer_details
DROP COLUMN IF EXISTS quantity,
DROP COLUMN IF EXISTS before_quantity,
DROP COLUMN IF EXISTS after_quantity;
-- Add foreign keys for product warehouse references
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
-- Source warehouse foreign key
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_stock_transfer_details_source_pw'
) THEN
EXECUTE
'ALTER TABLE stock_transfer_details
ADD CONSTRAINT fk_stock_transfer_details_source_pw
FOREIGN KEY (source_product_warehouse_id)
REFERENCES product_warehouses(id)
ON DELETE SET NULL ON UPDATE CASCADE';
END IF;
-- Destination warehouse foreign key
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_stock_transfer_details_dest_pw'
) THEN
EXECUTE
'ALTER TABLE stock_transfer_details
ADD CONSTRAINT fk_stock_transfer_details_dest_pw
FOREIGN KEY (dest_product_warehouse_id)
REFERENCES product_warehouses(id)
ON DELETE SET NULL ON UPDATE CASCADE';
END IF;
END IF;
END $$;
-- Add indexes for FIFO operations
CREATE INDEX IF NOT EXISTS idx_stock_transfer_details_source_pw
ON stock_transfer_details (source_product_warehouse_id);
CREATE INDEX IF NOT EXISTS idx_stock_transfer_details_dest_pw
ON stock_transfer_details (dest_product_warehouse_id);
-- Add comments for documentation
COMMENT ON COLUMN stock_transfer_details.source_product_warehouse_id IS
'Source product warehouse ID - referensi warehouse asal (FIFO usable)';
COMMENT ON COLUMN stock_transfer_details.dest_product_warehouse_id IS
'Destination product warehouse ID - referensi warehouse tujuan (FIFO stockable)';
COMMENT ON COLUMN stock_transfer_details.usage_qty IS
'Actual quantity successfully taken from source warehouse (FIFO usable tracking) - replaces quantity field';
COMMENT ON COLUMN stock_transfer_details.pending_qty IS
'Quantity waiting for stock availability (FIFO usable tracking)';
COMMENT ON COLUMN stock_transfer_details.total_qty IS
'Total lot quantity available at destination warehouse (FIFO stockable tracking)';
COMMENT ON COLUMN stock_transfer_details.total_used IS
'Quantity already consumed from this lot at destination warehouse (FIFO stockable tracking)';
@@ -0,0 +1,16 @@
-- Rollback: Drop adjustment_stocks table
BEGIN;
DROP INDEX IF EXISTS idx_adjustment_stocks_product_warehouse;
DROP INDEX IF EXISTS idx_adjustment_stocks_stock_log;
ALTER TABLE adjustment_stocks
DROP CONSTRAINT IF EXISTS fk_adjustment_stocks_product_warehouse;
ALTER TABLE adjustment_stocks
DROP CONSTRAINT IF EXISTS fk_adjustment_stocks_stock_log;
DROP TABLE IF EXISTS adjustment_stocks;
COMMIT;
@@ -0,0 +1,40 @@
-- Migration: Create adjustment_stocks table for FIFO tracking
-- This table tracks FIFO allocation for stock adjustments (both increase and decrease)
BEGIN;
CREATE TABLE IF NOT EXISTS adjustment_stocks (
id BIGSERIAL PRIMARY KEY,
stock_log_id BIGINT NOT NULL,
product_warehouse_id BIGINT NOT NULL,
-- FIFO fields for Adjustment INCREASE (Stockable)
-- Tracks stock added to warehouse via adjustment
total_qty NUMERIC(15, 3) DEFAULT 0,
total_used NUMERIC(15, 3) DEFAULT 0,
-- FIFO fields for Adjustment DECREASE (Usable)
-- Tracks stock consumed from warehouse via adjustment
usage_qty NUMERIC(15, 3) DEFAULT 0,
pending_qty NUMERIC(15, 3) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Foreign keys
ALTER TABLE adjustment_stocks
ADD CONSTRAINT fk_adjustment_stocks_stock_log
FOREIGN KEY (stock_log_id) REFERENCES stock_logs(id)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE adjustment_stocks
ADD CONSTRAINT fk_adjustment_stocks_product_warehouse
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id)
ON DELETE CASCADE ON UPDATE CASCADE;
-- Indexes
CREATE INDEX idx_adjustment_stocks_stock_log ON adjustment_stocks(stock_log_id);
CREATE INDEX idx_adjustment_stocks_product_warehouse ON adjustment_stocks(product_warehouse_id);
COMMIT;
@@ -0,0 +1,7 @@
DROP INDEX IF EXISTS idx_project_flocks_production_standard_id;
ALTER TABLE project_flocks
DROP CONSTRAINT IF EXISTS fk_project_flocks_production_standard_id;
ALTER TABLE project_flocks
DROP COLUMN IF EXISTS production_standard_id;
@@ -0,0 +1,15 @@
-- Add production_standard_id to project_flocks
ALTER TABLE project_flocks
ADD COLUMN IF NOT EXISTS production_standard_id BIGINT;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'production_standards') THEN
ALTER TABLE project_flocks
ADD CONSTRAINT fk_project_flocks_production_standard_id
FOREIGN KEY (production_standard_id) REFERENCES production_standards (id) ON DELETE RESTRICT ON UPDATE CASCADE;
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_project_flocks_production_standard_id
ON project_flocks (production_standard_id);
@@ -0,0 +1,3 @@
-- Remove standard_fcr column from production_standard_details table
ALTER TABLE production_standard_details
DROP COLUMN IF EXISTS standard_fcr;
@@ -0,0 +1,3 @@
-- Add standard_fcr column to production_standard_details table
ALTER TABLE production_standard_details
ADD COLUMN standard_fcr NUMERIC(15, 3);