GWM : plan and warehouse merged as locations
This commit is contained in:
parent
58d281c6b9
commit
f04b917b76
@ -171,8 +171,9 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
|
||||
| [x] | Asset Categories | `/masters/asset-categories` |
|
||||
| [x] | Departments | `/masters/departments` |
|
||||
| [x] | Designations | `/masters/designations` |
|
||||
| [x] | Plants | `/masters/plants` |
|
||||
| [x] | Warehouses | `/masters/warehouses` |
|
||||
| [x] | Locations | `/masters/locations` | Unified plants + warehouses (`type`: `plant` \| `warehouse`) |
|
||||
| [x] | Plants | `/masters/plants` | Alias — plant locations only |
|
||||
| [x] | Warehouses | `/masters/warehouses` | Alias — warehouse locations only |
|
||||
| [x] | Document Series | `/masters/document-series` |
|
||||
|
||||
**Masters total:** 14 modules × 5 endpoints = **70 APIs** [x]
|
||||
|
||||
@ -171,13 +171,13 @@ model asset_transfers {
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
||||
departments_asset_transfers_from_department_idTodepartments departments? @relation("asset_transfers_from_department_idTodepartments", fields: [from_department_id], references: [id], onUpdate: NoAction)
|
||||
plants_asset_transfers_from_plant_idToplants plants? @relation("asset_transfers_from_plant_idToplants", fields: [from_plant_id], references: [id], onUpdate: NoAction)
|
||||
from_plant locations? @relation("TransferFromPlant", fields: [from_plant_id], references: [id], onUpdate: NoAction)
|
||||
users_asset_transfers_from_user_idTousers users? @relation("asset_transfers_from_user_idTousers", fields: [from_user_id], references: [id], onUpdate: NoAction)
|
||||
warehouses_asset_transfers_from_warehouse_idTowarehouses warehouses? @relation("asset_transfers_from_warehouse_idTowarehouses", fields: [from_warehouse_id], references: [id], onUpdate: NoAction)
|
||||
from_warehouse locations? @relation("TransferFromWarehouse", fields: [from_warehouse_id], references: [id], onUpdate: NoAction)
|
||||
departments_asset_transfers_to_department_idTodepartments departments? @relation("asset_transfers_to_department_idTodepartments", fields: [to_department_id], references: [id], onUpdate: NoAction)
|
||||
plants_asset_transfers_to_plant_idToplants plants? @relation("asset_transfers_to_plant_idToplants", fields: [to_plant_id], references: [id], onUpdate: NoAction)
|
||||
to_plant locations? @relation("TransferToPlant", fields: [to_plant_id], references: [id], onUpdate: NoAction)
|
||||
users_asset_transfers_to_user_idTousers users? @relation("asset_transfers_to_user_idTousers", fields: [to_user_id], references: [id], onUpdate: NoAction)
|
||||
warehouses_asset_transfers_to_warehouse_idTowarehouses warehouses? @relation("asset_transfers_to_warehouse_idTowarehouses", fields: [to_warehouse_id], references: [id], onUpdate: NoAction)
|
||||
to_warehouse locations? @relation("TransferToWarehouse", fields: [to_warehouse_id], references: [id], onUpdate: NoAction)
|
||||
users_asset_transfers_transferred_byTousers users? @relation("asset_transfers_transferred_byTousers", fields: [transferred_by], references: [id], onUpdate: NoAction)
|
||||
|
||||
@@index([asset_id], map: "idx_asset_transfers_asset_id")
|
||||
@ -232,11 +232,11 @@ model assets {
|
||||
departments departments? @relation(fields: [department_id], references: [id], onUpdate: NoAction)
|
||||
grn grn? @relation(fields: [grn_id], references: [id], onUpdate: NoAction)
|
||||
grn_items grn_items? @relation(fields: [grn_item_id], references: [id], onUpdate: NoAction)
|
||||
plants plants @relation(fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
plant locations @relation("AssetPlantLocation", fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
purchase_orders purchase_orders? @relation(fields: [po_id], references: [id], onUpdate: NoAction)
|
||||
users_assets_updated_byTousers users? @relation("assets_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||
vendors_assets_vendor_idTovendors vendors? @relation("assets_vendor_idTovendors", fields: [vendor_id], references: [id], onUpdate: NoAction)
|
||||
warehouses warehouses? @relation(fields: [warehouse_id], references: [id], onUpdate: NoAction)
|
||||
warehouse locations? @relation("AssetWarehouseLocation", fields: [warehouse_id], references: [id], onUpdate: NoAction)
|
||||
|
||||
@@index([asset_category_id], map: "idx_assets_category_id")
|
||||
@@index([department_id], map: "idx_assets_dept_id")
|
||||
@ -375,7 +375,7 @@ model grn {
|
||||
users_grn_received_byTousers users? @relation("grn_received_byTousers", fields: [received_by], references: [id], onUpdate: NoAction)
|
||||
users_grn_updated_byTousers users? @relation("grn_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
|
||||
warehouses warehouses @relation(fields: [warehouse_id], references: [id], onUpdate: NoAction)
|
||||
warehouse locations @relation("GRNWarehouseLocation", fields: [warehouse_id], references: [id], onUpdate: NoAction)
|
||||
grn_attachments grn_attachments[]
|
||||
grn_items grn_items[]
|
||||
|
||||
@ -576,30 +576,42 @@ model permissions {
|
||||
@@unique([module_id, action])
|
||||
}
|
||||
|
||||
model plants {
|
||||
id BigInt @id @default(autoincrement())
|
||||
code String @unique @db.VarChar(20)
|
||||
name String @db.VarChar(150)
|
||||
gstin String? @db.VarChar(15)
|
||||
address String?
|
||||
city String? @db.VarChar(100)
|
||||
state String? @db.VarChar(100)
|
||||
pincode String? @db.VarChar(10)
|
||||
phone String? @db.VarChar(15)
|
||||
is_active Boolean @default(true)
|
||||
created_by BigInt?
|
||||
updated_by BigInt?
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
deleted_at DateTime? @db.Timestamptz(6)
|
||||
asset_transfers_asset_transfers_from_plant_idToplants asset_transfers[] @relation("asset_transfers_from_plant_idToplants")
|
||||
asset_transfers_asset_transfers_to_plant_idToplants asset_transfers[] @relation("asset_transfers_to_plant_idToplants")
|
||||
assets assets[]
|
||||
users_plants_created_byTousers users? @relation("plants_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction, map: "fk_plants_created_by")
|
||||
users_plants_updated_byTousers users? @relation("plants_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction, map: "fk_plants_updated_by")
|
||||
purchase_orders purchase_orders[]
|
||||
users_users_plant_idToplants users[] @relation("users_plant_idToplants")
|
||||
warehouses warehouses[]
|
||||
model locations {
|
||||
id BigInt @id @default(autoincrement())
|
||||
type String @db.VarChar(20)
|
||||
code String @db.VarChar(30)
|
||||
name String @db.VarChar(150)
|
||||
parent_id BigInt?
|
||||
gstin String? @db.VarChar(15)
|
||||
address String?
|
||||
city String? @db.VarChar(100)
|
||||
state String? @db.VarChar(100)
|
||||
pincode String? @db.VarChar(10)
|
||||
phone String? @db.VarChar(15)
|
||||
location String? @db.VarChar(200)
|
||||
is_active Boolean @default(true)
|
||||
created_by BigInt?
|
||||
updated_by BigInt?
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
deleted_at DateTime? @db.Timestamptz(6)
|
||||
transfers_from_plant asset_transfers[] @relation("TransferFromPlant")
|
||||
transfers_to_plant asset_transfers[] @relation("TransferToPlant")
|
||||
transfers_from_wh asset_transfers[] @relation("TransferFromWarehouse")
|
||||
transfers_to_wh asset_transfers[] @relation("TransferToWarehouse")
|
||||
assets_plant assets[] @relation("AssetPlantLocation")
|
||||
assets_warehouse assets[] @relation("AssetWarehouseLocation")
|
||||
grn_warehouse grn[] @relation("GRNWarehouseLocation")
|
||||
parent locations? @relation("LocationHierarchy", fields: [parent_id], references: [id], onUpdate: NoAction)
|
||||
children locations[] @relation("LocationHierarchy")
|
||||
po_plant purchase_orders[] @relation("POPlantLocation")
|
||||
po_warehouse purchase_orders[] @relation("POWarehouseLocation")
|
||||
users_plant users[] @relation("UserPlantLocation")
|
||||
users_created users? @relation("locations_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction, map: "fk_locations_created_by")
|
||||
users_updated users? @relation("locations_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction, map: "fk_locations_updated_by")
|
||||
|
||||
@@index([type], map: "idx_locations_type")
|
||||
@@index([parent_id], map: "idx_locations_parent_id")
|
||||
}
|
||||
|
||||
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
|
||||
@ -706,10 +718,10 @@ model purchase_orders {
|
||||
purchase_orders purchase_orders? @relation("purchase_ordersTopurchase_orders", fields: [parent_po_id], references: [id], onUpdate: NoAction)
|
||||
other_purchase_orders purchase_orders[] @relation("purchase_ordersTopurchase_orders")
|
||||
payment_terms payment_terms? @relation(fields: [payment_term_id], references: [id], onUpdate: NoAction)
|
||||
plants plants @relation(fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
plant locations @relation("POPlantLocation", fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
users_purchase_orders_updated_byTousers users? @relation("purchase_orders_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
|
||||
warehouses warehouses? @relation(fields: [warehouse_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
||||
warehouse locations? @relation("POWarehouseLocation", fields: [warehouse_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
||||
|
||||
@@index([created_at], map: "idx_po_created_at")
|
||||
@@index([po_date], map: "idx_po_po_date")
|
||||
@ -846,8 +858,8 @@ model users {
|
||||
password_reset_tokens password_reset_tokens[]
|
||||
payment_terms_payment_terms_created_byTousers payment_terms[] @relation("payment_terms_created_byTousers")
|
||||
payment_terms_payment_terms_updated_byTousers payment_terms[] @relation("payment_terms_updated_byTousers")
|
||||
plants_plants_created_byTousers plants[] @relation("plants_created_byTousers")
|
||||
plants_plants_updated_byTousers plants[] @relation("plants_updated_byTousers")
|
||||
locations_locations_created_byTousers locations[] @relation("locations_created_byTousers")
|
||||
locations_locations_updated_byTousers locations[] @relation("locations_updated_byTousers")
|
||||
po_approvals po_approvals[]
|
||||
po_attachments po_attachments[]
|
||||
purchase_orders_purchase_orders_created_byTousers purchase_orders[] @relation("purchase_orders_created_byTousers")
|
||||
@ -862,7 +874,7 @@ model users {
|
||||
other_users_users_created_byTousers users[] @relation("users_created_byTousers")
|
||||
departments departments? @relation(fields: [department_id], references: [id], onUpdate: NoAction)
|
||||
designations designations? @relation(fields: [designation_id], references: [id], onUpdate: NoAction)
|
||||
plants_users_plant_idToplants plants? @relation("users_plant_idToplants", fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
plant_location locations? @relation("UserPlantLocation", fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
users_users_reporting_toTousers users? @relation("users_reporting_toTousers", fields: [reporting_to], references: [id], onUpdate: NoAction)
|
||||
other_users_users_reporting_toTousers users[] @relation("users_reporting_toTousers")
|
||||
users_users_updated_byTousers users? @relation("users_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||
@ -877,8 +889,6 @@ model users {
|
||||
vendor_item_mapping_vendor_item_mapping_updated_byTousers vendor_item_mapping[] @relation("vendor_item_mapping_updated_byTousers")
|
||||
vendors_vendors_created_byTousers vendors[] @relation("vendors_created_byTousers")
|
||||
vendors_vendors_updated_byTousers vendors[] @relation("vendors_updated_byTousers")
|
||||
warehouses_warehouses_created_byTousers warehouses[] @relation("warehouses_created_byTousers")
|
||||
warehouses_warehouses_updated_byTousers warehouses[] @relation("warehouses_updated_byTousers")
|
||||
}
|
||||
|
||||
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
|
||||
@ -995,25 +1005,3 @@ model vendors {
|
||||
|
||||
@@index([vendor_name(ops: raw("gin_trgm_ops"))], map: "idx_vendors_name_trgm", type: Gin)
|
||||
}
|
||||
|
||||
model warehouses {
|
||||
id BigInt @id @default(autoincrement())
|
||||
code String @unique @db.VarChar(30)
|
||||
name String @db.VarChar(150)
|
||||
plant_id BigInt
|
||||
location String? @db.VarChar(200)
|
||||
is_active Boolean @default(true)
|
||||
created_by BigInt?
|
||||
updated_by BigInt?
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
deleted_at DateTime? @db.Timestamptz(6)
|
||||
asset_transfers_asset_transfers_from_warehouse_idTowarehouses asset_transfers[] @relation("asset_transfers_from_warehouse_idTowarehouses")
|
||||
asset_transfers_asset_transfers_to_warehouse_idTowarehouses asset_transfers[] @relation("asset_transfers_to_warehouse_idTowarehouses")
|
||||
assets assets[]
|
||||
grn grn[]
|
||||
purchase_orders purchase_orders[]
|
||||
users_warehouses_created_byTousers users? @relation("warehouses_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
|
||||
plants plants @relation(fields: [plant_id], references: [id], onUpdate: NoAction)
|
||||
users_warehouses_updated_byTousers users? @relation("warehouses_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ CREATE VIEW v_asset_expiry_alerts AS
|
||||
FROM asset_amc_contracts amc
|
||||
JOIN assets a ON a.id = amc.asset_id
|
||||
JOIN asset_categories ac_cat ON ac_cat.id = a.asset_category_id
|
||||
JOIN plants p ON p.id = a.plant_id
|
||||
JOIN locations p ON p.id = a.plant_id AND p.type = 'plant'
|
||||
LEFT JOIN departments d ON d.id = a.department_id
|
||||
LEFT JOIN vendors v ON v.id = amc.vendor_id
|
||||
WHERE amc.is_active = TRUE AND amc.deleted_at IS NULL AND a.deleted_at IS NULL
|
||||
@ -48,7 +48,7 @@ UNION ALL
|
||||
FROM asset_insurance_policies ins
|
||||
JOIN assets a ON a.id = ins.asset_id
|
||||
JOIN asset_categories ac_cat ON ac_cat.id = a.asset_category_id
|
||||
JOIN plants p ON p.id = a.plant_id
|
||||
JOIN locations p ON p.id = a.plant_id AND p.type = 'plant'
|
||||
LEFT JOIN departments d ON d.id = a.department_id
|
||||
WHERE ins.is_active = TRUE AND ins.deleted_at IS NULL AND a.deleted_at IS NULL
|
||||
AND (ins.policy_end_date - CURRENT_DATE) <= 90
|
||||
@ -67,7 +67,7 @@ UNION ALL
|
||||
END
|
||||
FROM assets a
|
||||
JOIN asset_categories ac_cat ON ac_cat.id = a.asset_category_id
|
||||
JOIN plants p ON p.id = a.plant_id
|
||||
JOIN locations p ON p.id = a.plant_id AND p.type = 'plant'
|
||||
LEFT JOIN departments d ON d.id = a.department_id
|
||||
WHERE a.warranty_expiry_date IS NOT NULL AND a.deleted_at IS NULL
|
||||
AND (a.warranty_expiry_date - CURRENT_DATE) <= 90;
|
||||
@ -96,7 +96,7 @@ SELECT
|
||||
amc.end_date AS amc_end_date
|
||||
FROM assets a
|
||||
JOIN asset_categories ac_cat ON ac_cat.id = a.asset_category_id
|
||||
JOIN plants p ON p.id = a.plant_id
|
||||
JOIN locations p ON p.id = a.plant_id AND p.type = 'plant'
|
||||
LEFT JOIN departments d ON d.id = a.department_id
|
||||
JOIN LATERAL (
|
||||
SELECT * FROM asset_service_visits
|
||||
@ -145,9 +145,9 @@ SELECT
|
||||
a.created_at
|
||||
FROM assets a
|
||||
JOIN asset_categories ac ON ac.id = a.asset_category_id
|
||||
JOIN plants p ON p.id = a.plant_id
|
||||
JOIN locations p ON p.id = a.plant_id AND p.type = 'plant'
|
||||
LEFT JOIN departments d ON d.id = a.department_id
|
||||
LEFT JOIN warehouses w ON w.id = a.warehouse_id
|
||||
LEFT JOIN locations w ON w.id = a.warehouse_id AND w.type = 'warehouse'
|
||||
LEFT JOIN users u ON u.id = a.assigned_to_user_id
|
||||
LEFT JOIN vendors v ON v.id = a.vendor_id
|
||||
LEFT JOIN asset_amc_contracts amc
|
||||
|
||||
148
scripts/patch-locations-merge.sql
Normal file
148
scripts/patch-locations-merge.sql
Normal file
@ -0,0 +1,148 @@
|
||||
-- Merge plants + warehouses into locations (type: plant | warehouse)
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS locations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('plant', 'warehouse')),
|
||||
code VARCHAR(30) NOT NULL,
|
||||
name VARCHAR(150) NOT NULL,
|
||||
parent_id BIGINT REFERENCES locations(id),
|
||||
gstin VARCHAR(15),
|
||||
address TEXT,
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(100),
|
||||
pincode VARCHAR(10),
|
||||
phone VARCHAR(15),
|
||||
location VARCHAR(200),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_by BIGINT,
|
||||
updated_by BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_locations_code_active
|
||||
ON locations (code)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_locations_type ON locations (type);
|
||||
CREATE INDEX IF NOT EXISTS idx_locations_parent_id ON locations (parent_id);
|
||||
|
||||
-- Migrate plants (preserve ids) when legacy table exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'plants'
|
||||
) THEN
|
||||
INSERT INTO locations (
|
||||
id, type, code, name, gstin, address, city, state, pincode, phone,
|
||||
is_active, created_by, updated_by, created_at, updated_at, deleted_at
|
||||
)
|
||||
OVERRIDING SYSTEM VALUE
|
||||
SELECT
|
||||
id, 'plant', code, name, gstin, address, city, state, pincode, phone,
|
||||
is_active, created_by, updated_by, created_at, updated_at, deleted_at
|
||||
FROM plants
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Migrate warehouses (preserve ids; plant_id -> parent_id)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'warehouses'
|
||||
) THEN
|
||||
INSERT INTO locations (
|
||||
id, type, code, name, parent_id, location,
|
||||
is_active, created_by, updated_by, created_at, updated_at, deleted_at
|
||||
)
|
||||
OVERRIDING SYSTEM VALUE
|
||||
SELECT
|
||||
id, 'warehouse', code, name, plant_id, location,
|
||||
is_active, created_by, updated_by, created_at, updated_at, deleted_at
|
||||
FROM warehouses
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('locations', 'id'),
|
||||
GREATEST(COALESCE((SELECT MAX(id) FROM locations), 1), 1)
|
||||
);
|
||||
|
||||
-- Drop FKs referencing plants / warehouses
|
||||
DO $$
|
||||
DECLARE r RECORD;
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'plants') THEN
|
||||
FOR r IN
|
||||
SELECT c.conname, c.conrelid::regclass AS tbl
|
||||
FROM pg_constraint c
|
||||
WHERE c.contype = 'f' AND c.confrelid = 'public.plants'::regclass
|
||||
LOOP
|
||||
EXECUTE format('ALTER TABLE %s DROP CONSTRAINT IF EXISTS %I', r.tbl, r.conname);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'warehouses') THEN
|
||||
FOR r IN
|
||||
SELECT c.conname, c.conrelid::regclass AS tbl
|
||||
FROM pg_constraint c
|
||||
WHERE c.contype = 'f' AND c.confrelid = 'public.warehouses'::regclass
|
||||
LOOP
|
||||
EXECUTE format('ALTER TABLE %s DROP CONSTRAINT IF EXISTS %I', r.tbl, r.conname);
|
||||
END LOOP;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Point existing FK columns to locations (idempotent)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_plant_id_fkey') THEN
|
||||
ALTER TABLE users ADD CONSTRAINT users_plant_id_fkey FOREIGN KEY (plant_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'assets_plant_id_fkey') THEN
|
||||
ALTER TABLE assets ADD CONSTRAINT assets_plant_id_fkey FOREIGN KEY (plant_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'assets_warehouse_id_fkey') THEN
|
||||
ALTER TABLE assets ADD CONSTRAINT assets_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'purchase_orders_plant_id_fkey') THEN
|
||||
ALTER TABLE purchase_orders ADD CONSTRAINT purchase_orders_plant_id_fkey FOREIGN KEY (plant_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'purchase_orders_warehouse_id_fkey') THEN
|
||||
ALTER TABLE purchase_orders ADD CONSTRAINT purchase_orders_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'grn_warehouse_id_fkey') THEN
|
||||
ALTER TABLE grn ADD CONSTRAINT grn_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'asset_transfers_from_plant_id_fkey') THEN
|
||||
ALTER TABLE asset_transfers ADD CONSTRAINT asset_transfers_from_plant_id_fkey FOREIGN KEY (from_plant_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'asset_transfers_to_plant_id_fkey') THEN
|
||||
ALTER TABLE asset_transfers ADD CONSTRAINT asset_transfers_to_plant_id_fkey FOREIGN KEY (to_plant_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'asset_transfers_from_warehouse_id_fkey') THEN
|
||||
ALTER TABLE asset_transfers ADD CONSTRAINT asset_transfers_from_warehouse_id_fkey FOREIGN KEY (from_warehouse_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'asset_transfers_to_warehouse_id_fkey') THEN
|
||||
ALTER TABLE asset_transfers ADD CONSTRAINT asset_transfers_to_warehouse_id_fkey FOREIGN KEY (to_warehouse_id) REFERENCES locations(id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Drop views that depend on plants/warehouses, then drop legacy tables
|
||||
DROP VIEW IF EXISTS v_purchase_orders CASCADE;
|
||||
DROP VIEW IF EXISTS v_grn CASCADE;
|
||||
DROP VIEW IF EXISTS v_assets CASCADE;
|
||||
DROP VIEW IF EXISTS v_asset_expiry_alerts CASCADE;
|
||||
DROP VIEW IF EXISTS v_asset_next_service CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS warehouses;
|
||||
DROP TABLE IF EXISTS plants;
|
||||
|
||||
COMMIT;
|
||||
33
scripts/patch-locations-views.sql
Normal file
33
scripts/patch-locations-views.sql
Normal file
@ -0,0 +1,33 @@
|
||||
-- Recreate PO/GRN views after locations merge
|
||||
|
||||
DROP VIEW IF EXISTS v_purchase_orders CASCADE;
|
||||
DROP VIEW IF EXISTS v_grn CASCADE;
|
||||
|
||||
CREATE VIEW v_purchase_orders AS
|
||||
SELECT
|
||||
po.*,
|
||||
pl.code AS plant_code,
|
||||
pl.name AS plant_name,
|
||||
wh.code AS warehouse_code,
|
||||
wh.name AS warehouse_name,
|
||||
v.vendor_code,
|
||||
v.vendor_name
|
||||
FROM purchase_orders po
|
||||
JOIN locations pl ON pl.id = po.plant_id AND pl.type = 'plant'
|
||||
LEFT JOIN locations wh ON wh.id = po.warehouse_id AND wh.type = 'warehouse'
|
||||
JOIN vendors v ON v.id = po.vendor_id
|
||||
WHERE po.deleted_at IS NULL;
|
||||
|
||||
CREATE VIEW v_grn AS
|
||||
SELECT
|
||||
g.*,
|
||||
po.po_number,
|
||||
v.vendor_code,
|
||||
v.vendor_name,
|
||||
wh.code AS warehouse_code,
|
||||
wh.name AS warehouse_name
|
||||
FROM grn g
|
||||
JOIN purchase_orders po ON po.id = g.po_id
|
||||
JOIN vendors v ON v.id = g.vendor_id
|
||||
JOIN locations wh ON wh.id = g.warehouse_id AND wh.type = 'warehouse'
|
||||
WHERE g.deleted_at IS NULL;
|
||||
@ -3,8 +3,7 @@ const ApiError = require('../../utils/ApiError');
|
||||
|
||||
const SOFT_DELETE_TABLES = new Set([
|
||||
'vendors',
|
||||
'plants',
|
||||
'warehouses',
|
||||
'locations',
|
||||
'departments',
|
||||
'users',
|
||||
'purchase_orders',
|
||||
|
||||
@ -4,12 +4,12 @@ const auditLog = require('../../utils/auditLog');
|
||||
const { getPagination } = require('../../utils/pagination');
|
||||
const { nextDocumentNumber } = require('../../utils/generateCode');
|
||||
const { DISPOSAL_STATUSES } = require('./assets.constants');
|
||||
const { assertPlant, assertWarehouse } = require('../../utils/locations');
|
||||
const repository = require('./assets.repository');
|
||||
|
||||
const SOFT_DELETE_TABLES = new Set([
|
||||
'vendors',
|
||||
'plants',
|
||||
'warehouses',
|
||||
'locations',
|
||||
'departments',
|
||||
'users',
|
||||
'purchase_orders',
|
||||
@ -20,9 +20,9 @@ const SOFT_DELETE_TABLES = new Set([
|
||||
|
||||
const assetInclude = {
|
||||
asset_categories: { select: { id: true, code: true, name: true, code_prefix: true } },
|
||||
plants: { select: { id: true, code: true, name: true } },
|
||||
plant: { select: { id: true, code: true, name: true } },
|
||||
departments: { select: { id: true, name: true } },
|
||||
warehouses: { select: { id: true, code: true, name: true } },
|
||||
warehouse: { select: { id: true, code: true, name: true } },
|
||||
users_assets_assigned_to_user_idTousers: {
|
||||
select: { id: true, full_name: true, employee_code: true },
|
||||
},
|
||||
@ -38,18 +38,14 @@ const assetDetailInclude = {
|
||||
};
|
||||
|
||||
const transferInclude = {
|
||||
plants_asset_transfers_from_plant_idToplants: { select: { id: true, code: true, name: true } },
|
||||
plants_asset_transfers_to_plant_idToplants: { select: { id: true, code: true, name: true } },
|
||||
from_plant: { select: { id: true, code: true, name: true } },
|
||||
to_plant: { select: { id: true, code: true, name: true } },
|
||||
departments_asset_transfers_from_department_idTodepartments: { select: { id: true, name: true } },
|
||||
departments_asset_transfers_to_department_idTodepartments: { select: { id: true, name: true } },
|
||||
users_asset_transfers_from_user_idTousers: { select: { id: true, full_name: true } },
|
||||
users_asset_transfers_to_user_idTousers: { select: { id: true, full_name: true } },
|
||||
warehouses_asset_transfers_from_warehouse_idTowarehouses: {
|
||||
select: { id: true, code: true, name: true },
|
||||
},
|
||||
warehouses_asset_transfers_to_warehouse_idTowarehouses: {
|
||||
select: { id: true, code: true, name: true },
|
||||
},
|
||||
from_warehouse: { select: { id: true, code: true, name: true } },
|
||||
to_warehouse: { select: { id: true, code: true, name: true } },
|
||||
users_asset_transfers_transferred_byTousers: { select: { id: true, full_name: true } },
|
||||
};
|
||||
|
||||
@ -65,9 +61,9 @@ const sanitizeAsset = (asset) => {
|
||||
if (!asset) return null;
|
||||
const {
|
||||
asset_categories,
|
||||
plants,
|
||||
plant,
|
||||
departments,
|
||||
warehouses,
|
||||
warehouse,
|
||||
users_assets_assigned_to_user_idTousers,
|
||||
vendors_assets_vendor_idTovendors,
|
||||
purchase_orders,
|
||||
@ -80,9 +76,9 @@ const sanitizeAsset = (asset) => {
|
||||
return {
|
||||
...rest,
|
||||
asset_category: asset_categories || null,
|
||||
plant: plants || null,
|
||||
plant: plant || null,
|
||||
department: departments || null,
|
||||
warehouse: warehouses || null,
|
||||
warehouse: warehouse || null,
|
||||
assigned_to_user: users_assets_assigned_to_user_idTousers || null,
|
||||
vendor: vendors_assets_vendor_idTovendors || null,
|
||||
purchase_order: purchase_orders || null,
|
||||
@ -90,9 +86,7 @@ const sanitizeAsset = (asset) => {
|
||||
created_by_user: users_assets_created_byTousers || null,
|
||||
updated_by_user: users_assets_updated_byTousers || null,
|
||||
asset_categories: undefined,
|
||||
plants: undefined,
|
||||
departments: undefined,
|
||||
warehouses: undefined,
|
||||
users_assets_assigned_to_user_idTousers: undefined,
|
||||
vendors_assets_vendor_idTovendors: undefined,
|
||||
purchase_orders: undefined,
|
||||
@ -104,28 +98,28 @@ const sanitizeAsset = (asset) => {
|
||||
const sanitizeTransfer = (row) => {
|
||||
if (!row) return null;
|
||||
const {
|
||||
plants_asset_transfers_from_plant_idToplants,
|
||||
plants_asset_transfers_to_plant_idToplants,
|
||||
from_plant,
|
||||
to_plant,
|
||||
departments_asset_transfers_from_department_idTodepartments,
|
||||
departments_asset_transfers_to_department_idTodepartments,
|
||||
users_asset_transfers_from_user_idTousers,
|
||||
users_asset_transfers_to_user_idTousers,
|
||||
warehouses_asset_transfers_from_warehouse_idTowarehouses,
|
||||
warehouses_asset_transfers_to_warehouse_idTowarehouses,
|
||||
from_warehouse,
|
||||
to_warehouse,
|
||||
users_asset_transfers_transferred_byTousers,
|
||||
...rest
|
||||
} = row;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
from_plant: plants_asset_transfers_from_plant_idToplants || null,
|
||||
to_plant: plants_asset_transfers_to_plant_idToplants || null,
|
||||
from_plant: from_plant || null,
|
||||
to_plant: to_plant || null,
|
||||
from_department: departments_asset_transfers_from_department_idTodepartments || null,
|
||||
to_department: departments_asset_transfers_to_department_idTodepartments || null,
|
||||
from_user: users_asset_transfers_from_user_idTousers || null,
|
||||
to_user: users_asset_transfers_to_user_idTousers || null,
|
||||
from_warehouse: warehouses_asset_transfers_from_warehouse_idTowarehouses || null,
|
||||
to_warehouse: warehouses_asset_transfers_to_warehouse_idTowarehouses || null,
|
||||
from_warehouse: from_warehouse || null,
|
||||
to_warehouse: to_warehouse || null,
|
||||
transferred_by_user: users_asset_transfers_transferred_byTousers || null,
|
||||
};
|
||||
};
|
||||
@ -155,11 +149,10 @@ const normalizeAssetPayload = async (payload, { isCreate = false } = {}) => {
|
||||
payload.asset_category_id,
|
||||
'asset_category_id'
|
||||
);
|
||||
await assertReference('plants', payload.plant_id, 'plant_id');
|
||||
await assertPlant(payload.plant_id, 'plant_id');
|
||||
if (payload.department_id)
|
||||
await assertReference('departments', payload.department_id, 'department_id');
|
||||
if (payload.warehouse_id)
|
||||
await assertReference('warehouses', payload.warehouse_id, 'warehouse_id');
|
||||
if (payload.warehouse_id) await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
||||
if (payload.assigned_to_user_id)
|
||||
await assertReference('users', payload.assigned_to_user_id, 'assigned_to_user_id');
|
||||
if (payload.vendor_id) await assertReference('vendors', payload.vendor_id, 'vendor_id');
|
||||
@ -402,14 +395,12 @@ const transferAsset = async (id, payload, userId, requestId) => {
|
||||
throw new ApiError(409, 'Cannot transfer disposed or scrapped assets');
|
||||
}
|
||||
|
||||
if (payload.to_plant_id) await assertReference('plants', payload.to_plant_id, 'to_plant_id');
|
||||
if (payload.to_plant_id) await assertPlant(payload.to_plant_id, 'to_plant_id');
|
||||
if (payload.to_department_id) {
|
||||
await assertReference('departments', payload.to_department_id, 'to_department_id');
|
||||
}
|
||||
if (payload.to_user_id) await assertReference('users', payload.to_user_id, 'to_user_id');
|
||||
if (payload.to_warehouse_id) {
|
||||
await assertReference('warehouses', payload.to_warehouse_id, 'to_warehouse_id');
|
||||
}
|
||||
if (payload.to_warehouse_id) await assertWarehouse(payload.to_warehouse_id, 'to_warehouse_id');
|
||||
|
||||
const transfer = {
|
||||
transfer_date: toDateOnly(payload.transfer_date),
|
||||
|
||||
@ -110,7 +110,7 @@ const getMe = async (userId) => {
|
||||
},
|
||||
departments: { select: { id: true, name: true } },
|
||||
designations: { select: { id: true, name: true } },
|
||||
plants_users_plant_idToplants: { select: { id: true, code: true, name: true } },
|
||||
plant_location: { select: { id: true, code: true, name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
@ -138,7 +138,7 @@ const getMe = async (userId) => {
|
||||
role: roles[0] || null,
|
||||
department: user.departments,
|
||||
designation: user.designations,
|
||||
plant: user.plants_users_plant_idToplants,
|
||||
plant: user.plant_location,
|
||||
permissions,
|
||||
};
|
||||
};
|
||||
|
||||
@ -5,11 +5,12 @@ const { getPagination } = require('../../utils/pagination');
|
||||
const { nextDocumentNumber } = require('../../utils/generateCode');
|
||||
const { buildSimplePdf } = require('../../utils/simplePdf');
|
||||
const repository = require('./grn.repository');
|
||||
const { assertWarehouse } = require('../../utils/locations');
|
||||
|
||||
const grnListInclude = {
|
||||
purchase_orders: { select: { id: true, po_number: true, status: true } },
|
||||
vendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
||||
warehouses: { select: { id: true, code: true, name: true } },
|
||||
warehouse: { select: { id: true, code: true, name: true } },
|
||||
users_grn_received_byTousers: { select: { id: true, full_name: true } },
|
||||
users_grn_created_byTousers: { select: { id: true, full_name: true } },
|
||||
};
|
||||
@ -46,7 +47,7 @@ const sanitizeGrn = (row) => {
|
||||
const {
|
||||
purchase_orders,
|
||||
vendors,
|
||||
warehouses,
|
||||
warehouse,
|
||||
users_grn_received_byTousers,
|
||||
users_grn_created_byTousers,
|
||||
users_grn_quality_checked_byTousers,
|
||||
@ -60,7 +61,7 @@ const sanitizeGrn = (row) => {
|
||||
...rest,
|
||||
purchase_order: purchase_orders || null,
|
||||
vendor: vendors || null,
|
||||
warehouse: warehouses || null,
|
||||
warehouse: warehouse || null,
|
||||
received_by_user: users_grn_received_byTousers || null,
|
||||
created_by_user: users_grn_created_byTousers || null,
|
||||
quality_checked_by_user: users_grn_quality_checked_byTousers || null,
|
||||
@ -78,6 +79,7 @@ const sanitizeGrn = (row) => {
|
||||
purchase_orders: undefined,
|
||||
vendors: undefined,
|
||||
warehouses: undefined,
|
||||
warehouse: undefined,
|
||||
users_grn_received_byTousers: undefined,
|
||||
users_grn_created_byTousers: undefined,
|
||||
users_grn_quality_checked_byTousers: undefined,
|
||||
@ -242,10 +244,7 @@ const buildHeaderData = (payload, po, userId) => ({
|
||||
|
||||
const createGrn = async (payload, userId, requestId) => {
|
||||
const po = await getReceivablePoOrThrow(payload.po_id);
|
||||
const warehouse = await prisma.warehouses.findFirst({
|
||||
where: { id: BigInt(payload.warehouse_id), deleted_at: null, is_active: true },
|
||||
});
|
||||
if (!warehouse) throw new ApiError(422, 'Invalid warehouse_id');
|
||||
await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
||||
|
||||
const { builtItems, assetPlans } = await validateAndBuildItems(po, payload.items);
|
||||
const header = buildHeaderData(payload, po, userId);
|
||||
@ -316,10 +315,7 @@ const updateGrn = async (id, payload, userId, requestId) => {
|
||||
}
|
||||
|
||||
if (payload.warehouse_id) {
|
||||
const warehouse = await prisma.warehouses.findFirst({
|
||||
where: { id: BigInt(payload.warehouse_id), deleted_at: null, is_active: true },
|
||||
});
|
||||
if (!warehouse) throw new ApiError(422, 'Invalid warehouse_id');
|
||||
await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
||||
@ -14,6 +14,7 @@ router.use('/designations', require('./designations/designations.routes'));
|
||||
router.use('/document-series', require('./document-series/document-series.routes'));
|
||||
router.use('/item-subcategories', require('./item-subcategories/item-subcategories.routes'));
|
||||
router.use('/items', require('./items/items.routes'));
|
||||
router.use('/locations', require('./locations/locations.routes'));
|
||||
router.use('/warehouses', require('./warehouses/warehouses.routes'));
|
||||
router.use('/plants', require('./plants/plants.routes'));
|
||||
|
||||
|
||||
30
src/modules/masters/locations/locations.controller.js
Normal file
30
src/modules/masters/locations/locations.controller.js
Normal file
@ -0,0 +1,30 @@
|
||||
const asyncHandler = require('../../../utils/asyncHandler');
|
||||
const ApiResponse = require('../../../utils/ApiResponse');
|
||||
const service = require('./locations.service');
|
||||
|
||||
const create = asyncHandler(async (req, res) => {
|
||||
const data = await service.createLocation(req.body, req.user?.id, req.id);
|
||||
res.status(201).json(new ApiResponse(201, data, 'Location created successfully'));
|
||||
});
|
||||
|
||||
const list = asyncHandler(async (req, res) => {
|
||||
const result = await service.listLocations(req.query);
|
||||
res.json(new ApiResponse(200, result.data, 'Locations list fetched', result.meta));
|
||||
});
|
||||
|
||||
const getOne = asyncHandler(async (req, res) => {
|
||||
const data = await service.getLocationById(req.params.id);
|
||||
res.json(new ApiResponse(200, data, 'Location fetched'));
|
||||
});
|
||||
|
||||
const update = asyncHandler(async (req, res) => {
|
||||
const data = await service.updateLocationById(req.params.id, req.body, req.user?.id, req.id);
|
||||
res.json(new ApiResponse(200, data, 'Location updated successfully'));
|
||||
});
|
||||
|
||||
const remove = asyncHandler(async (req, res) => {
|
||||
await service.deleteLocationById(req.params.id, req.user?.id, req.id);
|
||||
res.json(new ApiResponse(200, null, 'Location deleted successfully'));
|
||||
});
|
||||
|
||||
module.exports = { create, list, getOne, update, remove };
|
||||
21
src/modules/masters/locations/locations.routes.js
Normal file
21
src/modules/masters/locations/locations.routes.js
Normal file
@ -0,0 +1,21 @@
|
||||
const express = require('express');
|
||||
const authenticate = require('../../../middlewares/auth.middleware');
|
||||
const authorize = require('../../../middlewares/rbac.middleware');
|
||||
const validate = require('../../../middlewares/validate.middleware');
|
||||
const controller = require('./locations.controller');
|
||||
const {
|
||||
createLocationSchema,
|
||||
updateLocationSchema,
|
||||
listQuerySchema,
|
||||
} = require('./locations.validation');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authenticate);
|
||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||
router.post('/', authorize('MASTERS', 'create'), validate(createLocationSchema), controller.create);
|
||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateLocationSchema), controller.update);
|
||||
router.delete('/:id', authorize('MASTERS', 'delete'), controller.remove);
|
||||
|
||||
module.exports = router;
|
||||
274
src/modules/masters/locations/locations.service.js
Normal file
274
src/modules/masters/locations/locations.service.js
Normal file
@ -0,0 +1,274 @@
|
||||
const prisma = require('../../../config/prisma');
|
||||
const ApiError = require('../../../utils/ApiError');
|
||||
const auditLog = require('../../../utils/auditLog');
|
||||
const { getPagination } = require('../../../utils/pagination');
|
||||
const {
|
||||
LOCATION_TYPES,
|
||||
assertPlant,
|
||||
toPlantResponse,
|
||||
toWarehouseResponse,
|
||||
toLocationResponse,
|
||||
} = require('../../../utils/locations');
|
||||
|
||||
const TABLE_NAME = 'locations';
|
||||
|
||||
const normalizeString = (value) => {
|
||||
if (value === null || value === undefined) return value;
|
||||
return String(value).trim();
|
||||
};
|
||||
|
||||
const buildPlantData = (payload) => ({
|
||||
type: LOCATION_TYPES.PLANT,
|
||||
code: payload.code ? String(payload.code).trim().toUpperCase() : undefined,
|
||||
name: normalizeString(payload.name),
|
||||
gstin: normalizeString(payload.gstin),
|
||||
address: normalizeString(payload.address),
|
||||
city: normalizeString(payload.city),
|
||||
state: normalizeString(payload.state),
|
||||
pincode: normalizeString(payload.pincode),
|
||||
phone: normalizeString(payload.phone),
|
||||
is_active: payload.is_active,
|
||||
parent_id: null,
|
||||
location: null,
|
||||
});
|
||||
|
||||
const buildWarehouseData = (payload) => ({
|
||||
type: LOCATION_TYPES.WAREHOUSE,
|
||||
code: payload.code ? String(payload.code).trim().toUpperCase() : undefined,
|
||||
name: normalizeString(payload.name),
|
||||
parent_id: payload.parent_id ?? payload.plant_id ?? null,
|
||||
location: normalizeString(payload.location),
|
||||
is_active: payload.is_active,
|
||||
gstin: null,
|
||||
address: null,
|
||||
city: null,
|
||||
state: null,
|
||||
pincode: null,
|
||||
phone: null,
|
||||
});
|
||||
|
||||
const warehouseInclude = { parent: { select: { id: true, code: true, name: true } } };
|
||||
|
||||
const assertUniqueCode = async (code, excludeId = null) => {
|
||||
if (!code) return;
|
||||
const existing = await prisma.locations.findFirst({
|
||||
where: {
|
||||
code,
|
||||
deleted_at: null,
|
||||
...(excludeId ? { id: { not: BigInt(excludeId) } } : {}),
|
||||
},
|
||||
});
|
||||
if (existing) throw new ApiError(409, 'Location code already exists');
|
||||
};
|
||||
|
||||
const createLocation = async (type, payload, userId, requestId) => {
|
||||
const data =
|
||||
type === LOCATION_TYPES.PLANT ? buildPlantData(payload) : buildWarehouseData(payload);
|
||||
|
||||
if (!data.code || !data.name) throw new ApiError(422, 'code and name are required');
|
||||
await assertUniqueCode(data.code);
|
||||
|
||||
if (type === LOCATION_TYPES.WAREHOUSE) {
|
||||
if (!data.parent_id) throw new ApiError(422, 'parent_id (plant) is required for warehouse');
|
||||
await assertPlant(data.parent_id, 'parent_id');
|
||||
data.parent_id = BigInt(data.parent_id);
|
||||
}
|
||||
|
||||
data.created_by = userId ? BigInt(userId) : null;
|
||||
data.updated_by = userId ? BigInt(userId) : null;
|
||||
|
||||
const created = await prisma.locations.create({
|
||||
data,
|
||||
include: type === LOCATION_TYPES.WAREHOUSE ? warehouseInclude : undefined,
|
||||
});
|
||||
|
||||
const response =
|
||||
type === LOCATION_TYPES.PLANT ? toPlantResponse(created) : toWarehouseResponse(created);
|
||||
|
||||
await auditLog({
|
||||
tableName: TABLE_NAME,
|
||||
recordId: created.id,
|
||||
action: 'CREATE',
|
||||
oldValue: null,
|
||||
newValue: response,
|
||||
userId,
|
||||
requestId,
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const listLocations = async (query, type = null) => {
|
||||
const { page, limit, skip } = getPagination(query);
|
||||
|
||||
const where = {
|
||||
deleted_at: null,
|
||||
...(type ? { type } : {}),
|
||||
...(query.parent_id ? { parent_id: BigInt(query.parent_id) } : {}),
|
||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||
...(query.search
|
||||
? {
|
||||
OR: [
|
||||
{ code: { contains: query.search, mode: 'insensitive' } },
|
||||
{ name: { contains: query.search, mode: 'insensitive' } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const [rows, total] = await Promise.all([
|
||||
prisma.locations.findMany({
|
||||
where,
|
||||
include: type === LOCATION_TYPES.WAREHOUSE ? warehouseInclude : undefined,
|
||||
orderBy: { created_at: 'desc' },
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.locations.count({ where }),
|
||||
]);
|
||||
|
||||
const mapRow = (row) => {
|
||||
if (type === LOCATION_TYPES.PLANT) return toPlantResponse(row);
|
||||
if (type === LOCATION_TYPES.WAREHOUSE) return toWarehouseResponse(row);
|
||||
return toLocationResponse(row);
|
||||
};
|
||||
|
||||
return { data: rows.map(mapRow), meta: { page, limit, total } };
|
||||
};
|
||||
|
||||
const getLocationById = async (id, type = null) => {
|
||||
const row = await prisma.locations.findFirst({
|
||||
where: {
|
||||
id: BigInt(id),
|
||||
deleted_at: null,
|
||||
...(type ? { type } : {}),
|
||||
},
|
||||
include: warehouseInclude,
|
||||
});
|
||||
if (!row) throw new ApiError(404, type ? `${type} not found` : 'Location not found');
|
||||
|
||||
if (type === LOCATION_TYPES.PLANT) return toPlantResponse(row);
|
||||
if (type === LOCATION_TYPES.WAREHOUSE) return toWarehouseResponse(row);
|
||||
return toLocationResponse(row);
|
||||
};
|
||||
|
||||
const updateLocation = async (id, type, payload, userId, requestId) => {
|
||||
const existing = await prisma.locations.findFirst({
|
||||
where: { id: BigInt(id), deleted_at: null, type },
|
||||
include: warehouseInclude,
|
||||
});
|
||||
if (!existing) throw new ApiError(404, `${type} not found`);
|
||||
|
||||
const data =
|
||||
type === LOCATION_TYPES.PLANT ? buildPlantData(payload) : buildWarehouseData(payload);
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (data[key] === undefined) delete data[key];
|
||||
});
|
||||
|
||||
if (data.code) await assertUniqueCode(data.code, id);
|
||||
|
||||
if (type === LOCATION_TYPES.WAREHOUSE && data.parent_id !== undefined) {
|
||||
if (!data.parent_id) throw new ApiError(422, 'parent_id (plant) is required for warehouse');
|
||||
await assertPlant(data.parent_id, 'parent_id');
|
||||
data.parent_id = BigInt(data.parent_id);
|
||||
}
|
||||
|
||||
data.updated_by = userId ? BigInt(userId) : null;
|
||||
|
||||
const updated = await prisma.locations.update({
|
||||
where: { id: BigInt(id) },
|
||||
data,
|
||||
include: type === LOCATION_TYPES.WAREHOUSE ? warehouseInclude : undefined,
|
||||
});
|
||||
|
||||
const oldValue =
|
||||
type === LOCATION_TYPES.PLANT ? toPlantResponse(existing) : toWarehouseResponse(existing);
|
||||
const newValue =
|
||||
type === LOCATION_TYPES.PLANT ? toPlantResponse(updated) : toWarehouseResponse(updated);
|
||||
|
||||
await auditLog({
|
||||
tableName: TABLE_NAME,
|
||||
recordId: id,
|
||||
action: 'UPDATE',
|
||||
oldValue,
|
||||
newValue,
|
||||
userId,
|
||||
requestId,
|
||||
});
|
||||
|
||||
return newValue;
|
||||
};
|
||||
|
||||
const deleteLocation = async (id, type, userId, requestId) => {
|
||||
const existing = await prisma.locations.findFirst({
|
||||
where: { id: BigInt(id), deleted_at: null, type },
|
||||
include: warehouseInclude,
|
||||
});
|
||||
if (!existing) throw new ApiError(404, `${type} not found`);
|
||||
|
||||
await prisma.locations.update({
|
||||
where: { id: BigInt(id) },
|
||||
data: { deleted_at: new Date(), updated_by: userId ? BigInt(userId) : null },
|
||||
});
|
||||
|
||||
const oldValue =
|
||||
type === LOCATION_TYPES.PLANT ? toPlantResponse(existing) : toWarehouseResponse(existing);
|
||||
|
||||
await auditLog({
|
||||
tableName: TABLE_NAME,
|
||||
recordId: id,
|
||||
action: 'DELETE',
|
||||
oldValue,
|
||||
newValue: { deleted_at: new Date() },
|
||||
userId,
|
||||
requestId,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteLocationById = async (id, userId, requestId) => {
|
||||
const existing = await prisma.locations.findFirst({
|
||||
where: { id: BigInt(id), deleted_at: null },
|
||||
include: warehouseInclude,
|
||||
});
|
||||
if (!existing) throw new ApiError(404, 'Location not found');
|
||||
return deleteLocation(id, existing.type, userId, requestId);
|
||||
};
|
||||
|
||||
const updateLocationById = async (id, payload, userId, requestId) => {
|
||||
const existing = await prisma.locations.findFirst({
|
||||
where: { id: BigInt(id), deleted_at: null },
|
||||
});
|
||||
if (!existing) throw new ApiError(404, 'Location not found');
|
||||
return updateLocation(id, existing.type, payload, userId, requestId);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
LOCATION_TYPES,
|
||||
createPlant: (payload, userId, requestId) =>
|
||||
createLocation(LOCATION_TYPES.PLANT, payload, userId, requestId),
|
||||
listPlants: (query) => listLocations(query, LOCATION_TYPES.PLANT),
|
||||
getPlantById: (id) => getLocationById(id, LOCATION_TYPES.PLANT),
|
||||
updatePlant: (id, payload, userId, requestId) =>
|
||||
updateLocation(id, LOCATION_TYPES.PLANT, payload, userId, requestId),
|
||||
deletePlant: (id, userId, requestId) =>
|
||||
deleteLocation(id, LOCATION_TYPES.PLANT, userId, requestId),
|
||||
createWarehouse: (payload, userId, requestId) =>
|
||||
createLocation(LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
||||
listWarehouses: (query) => listLocations(query, LOCATION_TYPES.WAREHOUSE),
|
||||
getWarehouseById: (id) => getLocationById(id, LOCATION_TYPES.WAREHOUSE),
|
||||
updateWarehouse: (id, payload, userId, requestId) =>
|
||||
updateLocation(id, LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
||||
deleteWarehouse: (id, userId, requestId) =>
|
||||
deleteLocation(id, LOCATION_TYPES.WAREHOUSE, userId, requestId),
|
||||
createLocation: (payload, userId, requestId) => {
|
||||
if (!payload.type) throw new ApiError(422, 'type is required');
|
||||
if (![LOCATION_TYPES.PLANT, LOCATION_TYPES.WAREHOUSE].includes(payload.type)) {
|
||||
throw new ApiError(422, 'type must be plant or warehouse');
|
||||
}
|
||||
return createLocation(payload.type, payload, userId, requestId);
|
||||
},
|
||||
listLocations: (query) => listLocations(query),
|
||||
getLocationById: (id) => getLocationById(id),
|
||||
updateLocationById,
|
||||
deleteLocationById,
|
||||
};
|
||||
94
src/modules/masters/locations/locations.validation.js
Normal file
94
src/modules/masters/locations/locations.validation.js
Normal file
@ -0,0 +1,94 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const locationFields = {
|
||||
code: Joi.string().max(30).optional(),
|
||||
name: Joi.string().max(150).optional(),
|
||||
gstin: Joi.string().max(15).allow(null, '').optional(),
|
||||
address: Joi.string().allow(null, '').optional(),
|
||||
city: Joi.string().max(100).allow(null, '').optional(),
|
||||
state: Joi.string().max(100).allow(null, '').optional(),
|
||||
pincode: Joi.string().max(10).allow(null, '').optional(),
|
||||
phone: Joi.string().max(15).allow(null, '').optional(),
|
||||
location: Joi.string().max(200).allow(null, '').optional(),
|
||||
parent_id: Joi.number().integer().positive().optional(),
|
||||
plant_id: Joi.number().integer().positive().optional(),
|
||||
is_active: Joi.boolean().optional(),
|
||||
};
|
||||
|
||||
const createPlantSchema = Joi.object({
|
||||
code: Joi.string().max(30).required(),
|
||||
name: Joi.string().max(150).required(),
|
||||
gstin: locationFields.gstin,
|
||||
address: locationFields.address,
|
||||
city: locationFields.city,
|
||||
state: locationFields.state,
|
||||
pincode: locationFields.pincode,
|
||||
phone: locationFields.phone,
|
||||
is_active: Joi.boolean().default(true),
|
||||
});
|
||||
|
||||
const updatePlantSchema = createPlantSchema.fork(['code', 'name'], (schema) => schema.optional()).min(1);
|
||||
|
||||
const createWarehouseSchema = Joi.object({
|
||||
code: Joi.string().max(30).required(),
|
||||
name: Joi.string().max(150).required(),
|
||||
plant_id: Joi.number().integer().positive().required(),
|
||||
location: locationFields.location,
|
||||
is_active: Joi.boolean().default(true),
|
||||
});
|
||||
|
||||
const updateWarehouseSchema = createWarehouseSchema
|
||||
.fork(['code', 'name', 'plant_id'], (schema) => schema.optional())
|
||||
.min(1);
|
||||
|
||||
const createLocationSchema = Joi.object({
|
||||
type: Joi.string().valid('plant', 'warehouse').required(),
|
||||
code: Joi.string().max(30).required(),
|
||||
name: Joi.string().max(150).required(),
|
||||
parent_id: Joi.when('type', {
|
||||
is: 'warehouse',
|
||||
then: Joi.number().integer().positive().required(),
|
||||
otherwise: Joi.number().integer().positive().allow(null).optional(),
|
||||
}),
|
||||
gstin: locationFields.gstin,
|
||||
address: locationFields.address,
|
||||
city: locationFields.city,
|
||||
state: locationFields.state,
|
||||
pincode: locationFields.pincode,
|
||||
phone: locationFields.phone,
|
||||
location: locationFields.location,
|
||||
is_active: Joi.boolean().default(true),
|
||||
});
|
||||
|
||||
const updateLocationSchema = Joi.object({
|
||||
code: Joi.string().max(30).optional(),
|
||||
name: Joi.string().max(150).optional(),
|
||||
parent_id: Joi.number().integer().positive().optional(),
|
||||
gstin: locationFields.gstin,
|
||||
address: locationFields.address,
|
||||
city: locationFields.city,
|
||||
state: locationFields.state,
|
||||
pincode: locationFields.pincode,
|
||||
phone: locationFields.phone,
|
||||
location: locationFields.location,
|
||||
is_active: Joi.boolean().optional(),
|
||||
}).min(1);
|
||||
|
||||
const listQuerySchema = Joi.object({
|
||||
page: Joi.number().integer().min(1).default(1),
|
||||
limit: Joi.number().integer().min(1).max(100).default(20),
|
||||
search: Joi.string().allow('').optional(),
|
||||
is_active: Joi.boolean().optional(),
|
||||
type: Joi.string().valid('plant', 'warehouse').optional(),
|
||||
parent_id: Joi.number().integer().positive().optional(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createPlantSchema,
|
||||
updatePlantSchema,
|
||||
createWarehouseSchema,
|
||||
updateWarehouseSchema,
|
||||
createLocationSchema,
|
||||
updateLocationSchema,
|
||||
listQuerySchema,
|
||||
};
|
||||
@ -1,86 +1,9 @@
|
||||
const { buildMasterService } = require('../_shared/master.factory');
|
||||
|
||||
const config = {
|
||||
modelName: 'plants',
|
||||
tableName: 'plants',
|
||||
uniqueField: 'code',
|
||||
softDelete: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'string',
|
||||
uppercase: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'gstin',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'pincode',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
type: 'boolean',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'created_by',
|
||||
type: 'int',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'updated_by',
|
||||
type: 'int',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const service = buildMasterService(config);
|
||||
const service = require('../locations/locations.service');
|
||||
|
||||
module.exports = {
|
||||
createPlants: service.createOne,
|
||||
listPlants: service.list,
|
||||
getPlantsById: service.getOne,
|
||||
updatePlants: service.updateOne,
|
||||
deletePlants: service.removeOne,
|
||||
createPlants: service.createPlant,
|
||||
listPlants: service.listPlants,
|
||||
getPlantsById: service.getPlantById,
|
||||
updatePlants: service.updatePlant,
|
||||
deletePlants: service.deletePlant,
|
||||
};
|
||||
|
||||
@ -1,62 +1,9 @@
|
||||
const { buildMasterService } = require('../_shared/master.factory');
|
||||
|
||||
const config = {
|
||||
modelName: 'warehouses',
|
||||
tableName: 'warehouses',
|
||||
uniqueField: 'code',
|
||||
softDelete: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'string',
|
||||
uppercase: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'plant_id',
|
||||
type: 'int',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
type: 'boolean',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'created_by',
|
||||
type: 'int',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
name: 'updated_by',
|
||||
type: 'int',
|
||||
uppercase: false,
|
||||
searchable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const service = buildMasterService(config);
|
||||
const service = require('../locations/locations.service');
|
||||
|
||||
module.exports = {
|
||||
createWarehouses: service.createOne,
|
||||
listWarehouses: service.list,
|
||||
getWarehousesById: service.getOne,
|
||||
updateWarehouses: service.updateOne,
|
||||
deleteWarehouses: service.removeOne,
|
||||
createWarehouses: service.createWarehouse,
|
||||
listWarehouses: service.listWarehouses,
|
||||
getWarehousesById: service.getWarehouseById,
|
||||
updateWarehouses: service.updateWarehouse,
|
||||
deleteWarehouses: service.deleteWarehouse,
|
||||
};
|
||||
|
||||
@ -14,11 +14,12 @@ const {
|
||||
} = require('./purchase-orders.constants');
|
||||
const { computeLineAmounts, computeHeaderTotals } = require('./purchase-orders.calculations');
|
||||
const repository = require('./purchase-orders.repository');
|
||||
const { assertPlant, assertWarehouse } = require('../../utils/locations');
|
||||
|
||||
const poListInclude = {
|
||||
vendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
||||
plants: { select: { id: true, code: true, name: true } },
|
||||
warehouses: { select: { id: true, code: true, name: true } },
|
||||
plant: { select: { id: true, code: true, name: true } },
|
||||
warehouse: { select: { id: true, code: true, name: true } },
|
||||
brands: { select: { id: true, code: true, name: true } },
|
||||
users_purchase_orders_created_byTousers: { select: { id: true, full_name: true } },
|
||||
};
|
||||
@ -57,8 +58,8 @@ const sanitizePo = (po) => {
|
||||
if (!po) return null;
|
||||
const {
|
||||
vendors,
|
||||
plants,
|
||||
warehouses,
|
||||
plant,
|
||||
warehouse,
|
||||
brands,
|
||||
payment_terms,
|
||||
delivery_terms,
|
||||
@ -73,8 +74,8 @@ const sanitizePo = (po) => {
|
||||
return {
|
||||
...rest,
|
||||
vendor: vendors || null,
|
||||
plant: plants || null,
|
||||
warehouse: warehouses || null,
|
||||
plant: plant || null,
|
||||
warehouse: warehouse || null,
|
||||
brand: brands || null,
|
||||
payment_term: payment_terms || null,
|
||||
delivery_term: delivery_terms || null,
|
||||
@ -102,7 +103,9 @@ const sanitizePo = (po) => {
|
||||
po_approvals: undefined,
|
||||
vendors: undefined,
|
||||
plants: undefined,
|
||||
plant: undefined,
|
||||
warehouses: undefined,
|
||||
warehouse: undefined,
|
||||
brands: undefined,
|
||||
payment_terms: undefined,
|
||||
delivery_terms: undefined,
|
||||
@ -120,8 +123,7 @@ const assertStatus = (po, allowedStatuses, action) => {
|
||||
|
||||
const SOFT_DELETE_TABLES = new Set([
|
||||
'vendors',
|
||||
'plants',
|
||||
'warehouses',
|
||||
'locations',
|
||||
'payment_terms',
|
||||
'delivery_terms',
|
||||
'items',
|
||||
@ -206,9 +208,8 @@ const validateAndBuildItems = async (items) => {
|
||||
|
||||
const buildHeaderData = async (payload, builtItems, userId) => {
|
||||
await assertReference('vendors', payload.vendor_id, 'vendor_id');
|
||||
await assertReference('plants', payload.plant_id, 'plant_id');
|
||||
if (payload.warehouse_id)
|
||||
await assertReference('warehouses', payload.warehouse_id, 'warehouse_id');
|
||||
await assertPlant(payload.plant_id, 'plant_id');
|
||||
if (payload.warehouse_id) await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
||||
if (payload.brand_id)
|
||||
await assertReference('brands', payload.brand_id, 'brand_id', { requireActive: false });
|
||||
if (payload.payment_term_id)
|
||||
|
||||
@ -5,6 +5,7 @@ const ApiError = require('../../utils/ApiError');
|
||||
const auditLog = require('../../utils/auditLog');
|
||||
const { getPagination } = require('../../utils/pagination');
|
||||
const { encrypt, decrypt, blindIndex } = require('../../utils/encryption');
|
||||
const { assertPlant } = require('../../utils/locations');
|
||||
const { extractUserRoles } = require('../../utils/userPermissions');
|
||||
|
||||
const userInclude = {
|
||||
@ -13,7 +14,7 @@ const userInclude = {
|
||||
},
|
||||
departments: { select: { id: true, name: true } },
|
||||
designations: { select: { id: true, name: true } },
|
||||
plants_users_plant_idToplants: { select: { id: true, code: true, name: true } },
|
||||
plant_location: { select: { id: true, code: true, name: true } },
|
||||
users_users_reporting_toTousers: { select: { id: true, full_name: true, employee_code: true } },
|
||||
};
|
||||
|
||||
@ -26,7 +27,7 @@ const sanitizeUser = (user) => {
|
||||
if (!user) return null;
|
||||
const {
|
||||
user_roles,
|
||||
plants_users_plant_idToplants,
|
||||
plant_location,
|
||||
users_users_reporting_toTousers,
|
||||
departments,
|
||||
designations,
|
||||
@ -44,7 +45,7 @@ const sanitizeUser = (user) => {
|
||||
role: roles[0] || null,
|
||||
department: departments || null,
|
||||
designation: designations || null,
|
||||
plant: plants_users_plant_idToplants || null,
|
||||
plant: plant_location || null,
|
||||
reporting_manager: users_users_reporting_toTousers || null,
|
||||
};
|
||||
};
|
||||
@ -129,10 +130,7 @@ const assertFk = async (payload) => {
|
||||
if (!row) throw new ApiError(422, 'Invalid designation_id');
|
||||
}
|
||||
if (payload.plant_id) {
|
||||
const row = await prisma.plants.findFirst({
|
||||
where: { id: BigInt(payload.plant_id), deleted_at: null },
|
||||
});
|
||||
if (!row) throw new ApiError(422, 'Invalid plant_id');
|
||||
await assertPlant(payload.plant_id, 'plant_id');
|
||||
}
|
||||
if (payload.reporting_to) {
|
||||
const row = await prisma.users.findFirst({
|
||||
|
||||
96
src/utils/locations.js
Normal file
96
src/utils/locations.js
Normal file
@ -0,0 +1,96 @@
|
||||
const prisma = require('../config/prisma');
|
||||
const ApiError = require('./ApiError');
|
||||
|
||||
const LOCATION_TYPES = Object.freeze({
|
||||
PLANT: 'plant',
|
||||
WAREHOUSE: 'warehouse',
|
||||
});
|
||||
|
||||
const assertLocation = async (id, expectedType, label, { requireActive = true } = {}) => {
|
||||
if (!id) return null;
|
||||
|
||||
const row = await prisma.locations.findFirst({
|
||||
where: {
|
||||
id: BigInt(id),
|
||||
type: expectedType,
|
||||
deleted_at: null,
|
||||
},
|
||||
include: expectedType === LOCATION_TYPES.WAREHOUSE
|
||||
? { parent: { select: { id: true, code: true, name: true } } }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (!row) throw new ApiError(422, `Invalid ${label}`);
|
||||
if (requireActive && !row.is_active) throw new ApiError(422, `${label} is inactive`);
|
||||
return row;
|
||||
};
|
||||
|
||||
const assertPlant = (id, label = 'plant_id') =>
|
||||
assertLocation(id, LOCATION_TYPES.PLANT, label);
|
||||
|
||||
const assertWarehouse = (id, label = 'warehouse_id') =>
|
||||
assertLocation(id, LOCATION_TYPES.WAREHOUSE, label);
|
||||
|
||||
const locationListSelect = { id: true, code: true, name: true, type: true };
|
||||
|
||||
const plantListSelect = { id: true, code: true, name: true };
|
||||
|
||||
const warehouseListSelect = {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
parent_id: true,
|
||||
location: true,
|
||||
parent: { select: plantListSelect },
|
||||
};
|
||||
|
||||
const toPlantResponse = (row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
gstin: row.gstin,
|
||||
address: row.address,
|
||||
city: row.city,
|
||||
state: row.state,
|
||||
pincode: row.pincode,
|
||||
phone: row.phone,
|
||||
is_active: row.is_active,
|
||||
created_by: row.created_by,
|
||||
updated_by: row.updated_by,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
deleted_at: row.deleted_at,
|
||||
});
|
||||
|
||||
const toWarehouseResponse = (row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
plant_id: row.parent_id,
|
||||
plant: row.parent || null,
|
||||
location: row.location,
|
||||
is_active: row.is_active,
|
||||
created_by: row.created_by,
|
||||
updated_by: row.updated_by,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
deleted_at: row.deleted_at,
|
||||
});
|
||||
|
||||
const toLocationResponse = (row) => {
|
||||
if (row.type === LOCATION_TYPES.PLANT) return { ...toPlantResponse(row), type: row.type };
|
||||
return { ...toWarehouseResponse(row), type: row.type, parent_id: row.parent_id };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
LOCATION_TYPES,
|
||||
assertLocation,
|
||||
assertPlant,
|
||||
assertWarehouse,
|
||||
locationListSelect,
|
||||
plantListSelect,
|
||||
warehouseListSelect,
|
||||
toPlantResponse,
|
||||
toWarehouseResponse,
|
||||
toLocationResponse,
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user