GWM : brand removed , gst added in hs code , type added in item category , po type removed in purchase order

This commit is contained in:
Gowtham M 2026-07-16 14:05:14 +05:30
parent 75f1758c01
commit e0e3077019
24 changed files with 527 additions and 659 deletions

View File

@ -9,127 +9,127 @@ datasource db {
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model asset_attachments {
id BigInt @id @default(autoincrement())
asset_id BigInt
attachment_type String @default("DOCUMENT") @db.VarChar(50)
file_name String @db.VarChar(255)
file_path String @db.VarChar(500)
file_type String? @db.VarChar(100)
file_size Int?
uploaded_by BigInt?
amc_contract_id BigInt?
service_visit_id BigInt?
insurance_id BigInt?
created_at DateTime @default(now()) @db.Timestamptz(6)
asset_amc_contracts asset_amc_contracts? @relation(fields: [amc_contract_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
id BigInt @id @default(autoincrement())
asset_id BigInt
attachment_type String @default("DOCUMENT") @db.VarChar(50)
file_name String @db.VarChar(255)
file_path String @db.VarChar(500)
file_type String? @db.VarChar(100)
file_size Int?
uploaded_by BigInt?
amc_contract_id BigInt?
service_visit_id BigInt?
insurance_id BigInt?
created_at DateTime @default(now()) @db.Timestamptz(6)
asset_amc_contracts asset_amc_contracts? @relation(fields: [amc_contract_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
asset_insurance_policies asset_insurance_policies? @relation(fields: [insurance_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
asset_service_visits asset_service_visits? @relation(fields: [service_visit_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users users? @relation(fields: [uploaded_by], references: [id], onUpdate: NoAction)
asset_service_visits asset_service_visits? @relation(fields: [service_visit_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users users? @relation(fields: [uploaded_by], references: [id], onUpdate: NoAction)
}
model asset_amc_contracts {
id BigInt @id @default(autoincrement())
asset_id BigInt
vendor_id BigInt
contract_no String? @db.VarChar(100)
contract_type String @default("COMPREHENSIVE") @db.VarChar(30)
start_date DateTime @db.Date
end_date DateTime @db.Date
renewal_date DateTime? @db.Date
annual_cost Decimal @default(0) @db.Decimal(12, 2)
payment_frequency String @default("ANNUAL") @db.VarChar(20)
service_frequency String? @db.VarChar(20)
visits_per_year Int?
contact_person String? @db.VarChar(150)
contact_phone String? @db.VarChar(15)
contact_email String? @db.VarChar(150)
scope_of_work String?
exclusions String?
remarks String?
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_attachments asset_attachments[]
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_amc_contracts_created_byTousers users? @relation("asset_amc_contracts_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_amc_contracts_updated_byTousers users? @relation("asset_amc_contracts_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
asset_service_visits asset_service_visits[]
id BigInt @id @default(autoincrement())
asset_id BigInt
vendor_id BigInt
contract_no String? @db.VarChar(100)
contract_type String @default("COMPREHENSIVE") @db.VarChar(30)
start_date DateTime @db.Date
end_date DateTime @db.Date
renewal_date DateTime? @db.Date
annual_cost Decimal @default(0) @db.Decimal(12, 2)
payment_frequency String @default("ANNUAL") @db.VarChar(20)
service_frequency String? @db.VarChar(20)
visits_per_year Int?
contact_person String? @db.VarChar(150)
contact_phone String? @db.VarChar(15)
contact_email String? @db.VarChar(150)
scope_of_work String?
exclusions String?
remarks String?
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_attachments asset_attachments[]
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_amc_contracts_created_byTousers users? @relation("asset_amc_contracts_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_amc_contracts_updated_byTousers users? @relation("asset_amc_contracts_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
asset_service_visits asset_service_visits[]
@@index([asset_id], map: "idx_amc_asset_id")
@@index([vendor_id], map: "idx_amc_vendor_id")
}
model asset_insurance_policies {
id BigInt @id @default(autoincrement())
asset_id BigInt
policy_no String @db.VarChar(100)
insurer_name String @db.VarChar(200)
insurer_branch String? @db.VarChar(150)
insurer_contact String? @db.VarChar(150)
insurer_phone String? @db.VarChar(15)
insurer_email String? @db.VarChar(150)
policy_type String @default("FIRE_AND_ALLIED") @db.VarChar(50)
sum_insured Decimal @default(0) @db.Decimal(15, 2)
annual_premium Decimal @default(0) @db.Decimal(12, 2)
policy_start_date DateTime @db.Date
policy_end_date DateTime @db.Date
renewal_date DateTime? @db.Date
is_auto_renewal Boolean @default(false)
premium_paid Boolean @default(false)
premium_paid_date DateTime? @db.Date
remarks String?
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_attachments asset_attachments[]
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_insurance_policies_created_byTousers users? @relation("asset_insurance_policies_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_insurance_policies_updated_byTousers users? @relation("asset_insurance_policies_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
id BigInt @id @default(autoincrement())
asset_id BigInt
policy_no String @db.VarChar(100)
insurer_name String @db.VarChar(200)
insurer_branch String? @db.VarChar(150)
insurer_contact String? @db.VarChar(150)
insurer_phone String? @db.VarChar(15)
insurer_email String? @db.VarChar(150)
policy_type String @default("FIRE_AND_ALLIED") @db.VarChar(50)
sum_insured Decimal @default(0) @db.Decimal(15, 2)
annual_premium Decimal @default(0) @db.Decimal(12, 2)
policy_start_date DateTime @db.Date
policy_end_date DateTime @db.Date
renewal_date DateTime? @db.Date
is_auto_renewal Boolean @default(false)
premium_paid Boolean @default(false)
premium_paid_date DateTime? @db.Date
remarks String?
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_attachments asset_attachments[]
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_insurance_policies_created_byTousers users? @relation("asset_insurance_policies_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_insurance_policies_updated_byTousers users? @relation("asset_insurance_policies_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
@@index([asset_id], map: "idx_insurance_asset_id")
}
model asset_service_visits {
id BigInt @id @default(autoincrement())
asset_id BigInt
amc_contract_id BigInt?
visit_type String @db.VarChar(30)
visit_date DateTime @db.Date
visit_number Int?
complaint_no String? @db.VarChar(50)
complaint_date DateTime? @db.Date
complaint_desc String?
engineer_name String? @db.VarChar(150)
engineer_phone String? @db.VarChar(15)
vendor_id BigInt?
work_done String?
parts_replaced String?
next_service_date DateTime? @db.Date
status String @default("COMPLETED") @db.VarChar(20)
downtime_hours Decimal? @default(0) @db.Decimal(6, 2)
service_cost Decimal? @default(0) @db.Decimal(12, 2)
is_under_amc Boolean @default(false)
asset_condition_after String? @db.VarChar(20)
remarks String?
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_attachments asset_attachments[]
asset_amc_contracts asset_amc_contracts? @relation(fields: [amc_contract_id], references: [id], onUpdate: NoAction)
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_service_visits_created_byTousers users? @relation("asset_service_visits_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_service_visits_updated_byTousers users? @relation("asset_service_visits_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
vendors vendors? @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
id BigInt @id @default(autoincrement())
asset_id BigInt
amc_contract_id BigInt?
visit_type String @db.VarChar(30)
visit_date DateTime @db.Date
visit_number Int?
complaint_no String? @db.VarChar(50)
complaint_date DateTime? @db.Date
complaint_desc String?
engineer_name String? @db.VarChar(150)
engineer_phone String? @db.VarChar(15)
vendor_id BigInt?
work_done String?
parts_replaced String?
next_service_date DateTime? @db.Date
status String @default("COMPLETED") @db.VarChar(20)
downtime_hours Decimal? @default(0) @db.Decimal(6, 2)
service_cost Decimal? @default(0) @db.Decimal(12, 2)
is_under_amc Boolean @default(false)
asset_condition_after String? @db.VarChar(20)
remarks String?
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_attachments asset_attachments[]
asset_amc_contracts asset_amc_contracts? @relation(fields: [amc_contract_id], references: [id], onUpdate: NoAction)
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
users_asset_service_visits_created_byTousers users? @relation("asset_service_visits_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_asset_service_visits_updated_byTousers users? @relation("asset_service_visits_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
vendors vendors? @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
@@index([asset_id], map: "idx_service_asset_id")
@@index([amc_contract_id], map: "idx_service_amc_id")
@ -153,13 +153,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)
from_plant locations? @relation("TransferFromPlant", 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)
from_warehouse locations? @relation("TransferFromWarehouse", 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)
to_plant locations? @relation("TransferToPlant", 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)
to_warehouse locations? @relation("TransferToWarehouse", 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")
@ -167,61 +167,61 @@ model asset_transfers {
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model assets {
id BigInt @id @default(autoincrement())
asset_code String @unique @db.VarChar(30)
asset_name String @db.VarChar(200)
id BigInt @id @default(autoincrement())
asset_code String @unique @db.VarChar(30)
asset_name String @db.VarChar(200)
item_category_id BigInt
item_subcategory_id BigInt?
brand_model String? @db.VarChar(200)
manufacturer String? @db.VarChar(200)
serial_number String? @db.VarChar(100)
part_number String? @db.VarChar(100)
brand_model String? @db.VarChar(200)
manufacturer String? @db.VarChar(200)
serial_number String? @db.VarChar(100)
part_number String? @db.VarChar(100)
plant_id BigInt
department_id BigInt?
warehouse_id BigInt?
location_detail String? @db.VarChar(200)
location_detail String? @db.VarChar(200)
assigned_to_user_id BigInt?
vendor_id BigInt?
po_id BigInt?
grn_id BigInt?
grn_item_id BigInt?
purchase_date DateTime? @db.Date
purchase_cost Decimal @default(0) @db.Decimal(15, 4)
purchase_date DateTime? @db.Date
purchase_cost Decimal @default(0) @db.Decimal(15, 4)
useful_life_years Int?
depreciation_method String? @db.VarChar(20)
depreciation_rate Decimal? @db.Decimal(8, 4)
salvage_value Decimal? @default(0) @db.Decimal(15, 4)
warranty_expiry_date DateTime? @db.Date
condition String @default("NEW") @db.VarChar(20)
status String @default("IN_USE") @db.VarChar(30)
qr_code_value String? @db.VarChar(100)
disposal_date DateTime? @db.Date
depreciation_method String? @db.VarChar(20)
depreciation_rate Decimal? @db.Decimal(8, 4)
salvage_value Decimal? @default(0) @db.Decimal(15, 4)
warranty_expiry_date DateTime? @db.Date
condition String @default("NEW") @db.VarChar(20)
status String @default("IN_USE") @db.VarChar(30)
qr_code_value String? @db.VarChar(100)
disposal_date DateTime? @db.Date
disposal_reason String?
disposal_value Decimal? @db.Decimal(15, 4)
disposal_value Decimal? @db.Decimal(15, 4)
remarks String?
is_active Boolean @default(true)
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_amc_contracts asset_amc_contracts[]
created_at DateTime @default(now()) @db.Timestamptz(6)
updated_at DateTime @default(now()) @db.Timestamptz(6)
deleted_at DateTime? @db.Timestamptz(6)
asset_amc_contracts asset_amc_contracts[]
asset_attachments asset_attachments[]
asset_insurance_policies asset_insurance_policies[]
asset_service_visits asset_service_visits[]
asset_transfers asset_transfers[]
item_categories item_categories @relation(fields: [item_category_id], references: [id], onUpdate: NoAction)
item_subcategories item_subcategories? @relation(fields: [item_subcategory_id], references: [id], onUpdate: NoAction)
users_assets_assigned_to_user_idTousers users? @relation("assets_assigned_to_user_idTousers", fields: [assigned_to_user_id], references: [id], onUpdate: NoAction)
users_assets_created_byTousers users? @relation("assets_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
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)
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)
warehouse locations? @relation("AssetWarehouseLocation", fields: [warehouse_id], references: [id], onUpdate: NoAction)
item_categories item_categories @relation(fields: [item_category_id], references: [id], onUpdate: NoAction)
item_subcategories item_subcategories? @relation(fields: [item_subcategory_id], references: [id], onUpdate: NoAction)
users_assets_assigned_to_user_idTousers users? @relation("assets_assigned_to_user_idTousers", fields: [assigned_to_user_id], references: [id], onUpdate: NoAction)
users_assets_created_byTousers users? @relation("assets_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
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)
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)
warehouse locations? @relation("AssetWarehouseLocation", fields: [warehouse_id], references: [id], onUpdate: NoAction)
@@index([item_category_id], map: "idx_assets_item_category_id")
@@index([item_subcategory_id], map: "idx_assets_item_subcategory_id")
@ -247,26 +247,6 @@ model audit_logs {
}
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model brands {
id BigInt @id @default(autoincrement())
code String @unique @db.VarChar(30)
name String @db.VarChar(150)
brand_type String @default("OWN") @db.VarChar(20)
contact_person String? @db.VarChar(150)
phone String? @db.VarChar(15)
email String? @db.VarChar(150)
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)
users_brands_created_byTousers users? @relation("brands_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
users_brands_updated_byTousers users? @relation("brands_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
items items[]
purchase_orders purchase_orders[]
}
model delivery_terms {
id BigInt @id @default(autoincrement())
code String @unique @db.VarChar(30)
@ -361,7 +341,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)
warehouse locations @relation("GRNWarehouseLocation", 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[]
@ -423,6 +403,7 @@ model gst_rates {
is_active Boolean @default(true)
created_at DateTime @default(now()) @db.Timestamptz(6)
updated_at DateTime @default(now()) @db.Timestamptz(6)
hsn_codes hsn_codes[]
items items[]
purchase_order_items purchase_order_items[]
}
@ -431,9 +412,11 @@ model hsn_codes {
id BigInt @id @default(autoincrement())
code String @unique @db.VarChar(20)
description String?
gst_rate_id BigInt?
is_active Boolean @default(true)
created_at DateTime @default(now()) @db.Timestamptz(6)
updated_at DateTime @default(now()) @db.Timestamptz(6)
gst_rates gst_rates? @relation(fields: [gst_rate_id], references: [id], onUpdate: NoAction)
items items[]
purchase_order_items purchase_order_items[]
}
@ -442,6 +425,7 @@ model item_categories {
id BigInt @id @default(autoincrement())
code String @unique @db.VarChar(30)
name String @db.VarChar(100)
category_type String @default("STOCK") @db.VarChar(10)
code_prefix String? @unique @db.VarChar(10)
default_useful_life_years Int?
default_depreciation_method String? @db.VarChar(20)
@ -485,7 +469,6 @@ model items {
uom_id BigInt
hsn_code_id BigInt?
gst_rate_id BigInt?
brand_id BigInt?
is_asset_item Boolean @default(false)
description String?
specification String?
@ -498,7 +481,6 @@ model items {
updated_at DateTime @default(now()) @db.Timestamptz(6)
deleted_at DateTime? @db.Timestamptz(6)
grn_items grn_items[]
brands brands? @relation(fields: [brand_id], references: [id], onUpdate: NoAction)
users_items_created_byTousers users? @relation("items_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
gst_rates gst_rates? @relation(fields: [gst_rate_id], references: [id], onUpdate: NoAction)
hsn_codes hsn_codes? @relation(fields: [hsn_code_id], references: [id], onUpdate: NoAction)
@ -568,38 +550,38 @@ model permissions {
}
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")
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")
@ -673,11 +655,9 @@ model purchase_orders {
id BigInt @id @default(autoincrement())
po_number String @unique @db.VarChar(30)
po_date DateTime @db.Date
po_type String @db.VarChar(30)
vendor_id BigInt
plant_id BigInt
warehouse_id BigInt?
brand_id BigInt?
payment_term_id BigInt?
delivery_term_id BigInt?
expected_delivery_date DateTime? @db.Date
@ -707,16 +687,15 @@ model purchase_orders {
po_approvals po_approvals[]
po_attachments po_attachments[]
purchase_order_items purchase_order_items[]
brands brands? @relation(fields: [brand_id], references: [id], onUpdate: NoAction)
users_purchase_orders_created_byTousers users? @relation("purchase_orders_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
delivery_terms delivery_terms? @relation(fields: [delivery_term_id], references: [id], onUpdate: NoAction)
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)
plant locations @relation("POPlantLocation", 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)
warehouse locations? @relation("POWarehouseLocation", 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")
@ -795,94 +774,92 @@ model uom {
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model users {
id BigInt @id @default(autoincrement())
employee_code String @unique @db.VarChar(30)
full_name String @db.VarChar(200)
email String @unique @db.VarChar(200)
mobile String? @db.VarChar(255)
mobile_index String? @db.VarChar(64)
password_hash String @db.VarChar(255)
avatar_path String? @db.VarChar(500)
department_id BigInt?
designation_id BigInt?
plant_id BigInt?
reporting_to BigInt?
status String @default("active") @db.VarChar(20)
is_active Boolean @default(true)
failed_login_attempts Int @default(0)
locked_until DateTime? @db.Timestamptz(6)
last_login_at DateTime? @db.Timestamptz(6)
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_attachments asset_attachments[]
asset_amc_contracts_asset_amc_contracts_created_byTousers asset_amc_contracts[] @relation("asset_amc_contracts_created_byTousers")
asset_amc_contracts_asset_amc_contracts_updated_byTousers asset_amc_contracts[] @relation("asset_amc_contracts_updated_byTousers")
id BigInt @id @default(autoincrement())
employee_code String @unique @db.VarChar(30)
full_name String @db.VarChar(200)
email String @unique @db.VarChar(200)
mobile String? @db.VarChar(255)
mobile_index String? @db.VarChar(64)
password_hash String @db.VarChar(255)
avatar_path String? @db.VarChar(500)
department_id BigInt?
designation_id BigInt?
plant_id BigInt?
reporting_to BigInt?
status String @default("active") @db.VarChar(20)
is_active Boolean @default(true)
failed_login_attempts Int @default(0)
locked_until DateTime? @db.Timestamptz(6)
last_login_at DateTime? @db.Timestamptz(6)
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_attachments asset_attachments[]
asset_amc_contracts_asset_amc_contracts_created_byTousers asset_amc_contracts[] @relation("asset_amc_contracts_created_byTousers")
asset_amc_contracts_asset_amc_contracts_updated_byTousers asset_amc_contracts[] @relation("asset_amc_contracts_updated_byTousers")
asset_insurance_policies_asset_insurance_policies_created_byTousers asset_insurance_policies[] @relation("asset_insurance_policies_created_byTousers")
asset_insurance_policies_asset_insurance_policies_updated_byTousers asset_insurance_policies[] @relation("asset_insurance_policies_updated_byTousers")
asset_service_visits_asset_service_visits_created_byTousers asset_service_visits[] @relation("asset_service_visits_created_byTousers")
asset_service_visits_asset_service_visits_updated_byTousers asset_service_visits[] @relation("asset_service_visits_updated_byTousers")
asset_transfers_asset_transfers_from_user_idTousers asset_transfers[] @relation("asset_transfers_from_user_idTousers")
asset_transfers_asset_transfers_to_user_idTousers asset_transfers[] @relation("asset_transfers_to_user_idTousers")
asset_transfers_asset_transfers_transferred_byTousers asset_transfers[] @relation("asset_transfers_transferred_byTousers")
assets_assets_assigned_to_user_idTousers assets[] @relation("assets_assigned_to_user_idTousers")
assets_assets_created_byTousers assets[] @relation("assets_created_byTousers")
assets_assets_updated_byTousers assets[] @relation("assets_updated_byTousers")
audit_logs audit_logs[]
brands_brands_created_byTousers brands[] @relation("brands_created_byTousers")
brands_brands_updated_byTousers brands[] @relation("brands_updated_byTousers")
delivery_terms_delivery_terms_created_byTousers delivery_terms[] @relation("delivery_terms_created_byTousers")
delivery_terms_delivery_terms_updated_byTousers delivery_terms[] @relation("delivery_terms_updated_byTousers")
document_series_document_series_created_byTousers document_series[] @relation("document_series_created_byTousers")
document_series_document_series_updated_byTousers document_series[] @relation("document_series_updated_byTousers")
grn_grn_cancelled_byTousers grn[] @relation("grn_cancelled_byTousers")
grn_grn_created_byTousers grn[] @relation("grn_created_byTousers")
grn_grn_quality_checked_byTousers grn[] @relation("grn_quality_checked_byTousers")
grn_grn_received_byTousers grn[] @relation("grn_received_byTousers")
grn_grn_updated_byTousers grn[] @relation("grn_updated_byTousers")
grn_attachments grn_attachments[]
item_categories_item_categories_created_byTousers item_categories[] @relation("item_categories_created_byTousers")
item_categories_item_categories_updated_byTousers item_categories[] @relation("item_categories_updated_byTousers")
item_subcategories_item_subcategories_created_byTousers item_subcategories[] @relation("item_subcategories_created_byTousers")
item_subcategories_item_subcategories_updated_byTousers item_subcategories[] @relation("item_subcategories_updated_byTousers")
items_items_created_byTousers items[] @relation("items_created_byTousers")
items_items_updated_byTousers items[] @relation("items_updated_byTousers")
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")
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")
purchase_orders_purchase_orders_updated_byTousers purchase_orders[] @relation("purchase_orders_updated_byTousers")
refresh_tokens refresh_tokens[]
user_roles user_roles[]
roles_roles_created_byTousers roles[] @relation("roles_created_byTousers")
roles_roles_updated_byTousers roles[] @relation("roles_updated_byTousers")
uom_uom_created_byTousers uom[] @relation("uom_created_byTousers")
uom_uom_updated_byTousers uom[] @relation("uom_updated_byTousers")
users_users_created_byTousers users? @relation("users_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
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)
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)
other_users_users_updated_byTousers users[] @relation("users_updated_byTousers")
vendor_addresses_vendor_addresses_created_byTousers vendor_addresses[] @relation("vendor_addresses_created_byTousers")
vendor_addresses_vendor_addresses_updated_byTousers vendor_addresses[] @relation("vendor_addresses_updated_byTousers")
vendor_bank_details_vendor_bank_details_created_byTousers vendor_bank_details[] @relation("vendor_bank_details_created_byTousers")
vendor_bank_details_vendor_bank_details_updated_byTousers vendor_bank_details[] @relation("vendor_bank_details_updated_byTousers")
vendor_contacts_vendor_contacts_created_byTousers vendor_contacts[] @relation("vendor_contacts_created_byTousers")
vendor_contacts_vendor_contacts_updated_byTousers vendor_contacts[] @relation("vendor_contacts_updated_byTousers")
vendor_item_mapping_vendor_item_mapping_created_byTousers vendor_item_mapping[] @relation("vendor_item_mapping_created_byTousers")
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")
asset_service_visits_asset_service_visits_created_byTousers asset_service_visits[] @relation("asset_service_visits_created_byTousers")
asset_service_visits_asset_service_visits_updated_byTousers asset_service_visits[] @relation("asset_service_visits_updated_byTousers")
asset_transfers_asset_transfers_from_user_idTousers asset_transfers[] @relation("asset_transfers_from_user_idTousers")
asset_transfers_asset_transfers_to_user_idTousers asset_transfers[] @relation("asset_transfers_to_user_idTousers")
asset_transfers_asset_transfers_transferred_byTousers asset_transfers[] @relation("asset_transfers_transferred_byTousers")
assets_assets_assigned_to_user_idTousers assets[] @relation("assets_assigned_to_user_idTousers")
assets_assets_created_byTousers assets[] @relation("assets_created_byTousers")
assets_assets_updated_byTousers assets[] @relation("assets_updated_byTousers")
audit_logs audit_logs[]
delivery_terms_delivery_terms_created_byTousers delivery_terms[] @relation("delivery_terms_created_byTousers")
delivery_terms_delivery_terms_updated_byTousers delivery_terms[] @relation("delivery_terms_updated_byTousers")
document_series_document_series_created_byTousers document_series[] @relation("document_series_created_byTousers")
document_series_document_series_updated_byTousers document_series[] @relation("document_series_updated_byTousers")
grn_grn_cancelled_byTousers grn[] @relation("grn_cancelled_byTousers")
grn_grn_created_byTousers grn[] @relation("grn_created_byTousers")
grn_grn_quality_checked_byTousers grn[] @relation("grn_quality_checked_byTousers")
grn_grn_received_byTousers grn[] @relation("grn_received_byTousers")
grn_grn_updated_byTousers grn[] @relation("grn_updated_byTousers")
grn_attachments grn_attachments[]
item_categories_item_categories_created_byTousers item_categories[] @relation("item_categories_created_byTousers")
item_categories_item_categories_updated_byTousers item_categories[] @relation("item_categories_updated_byTousers")
item_subcategories_item_subcategories_created_byTousers item_subcategories[] @relation("item_subcategories_created_byTousers")
item_subcategories_item_subcategories_updated_byTousers item_subcategories[] @relation("item_subcategories_updated_byTousers")
items_items_created_byTousers items[] @relation("items_created_byTousers")
items_items_updated_byTousers items[] @relation("items_updated_byTousers")
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")
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")
purchase_orders_purchase_orders_updated_byTousers purchase_orders[] @relation("purchase_orders_updated_byTousers")
refresh_tokens refresh_tokens[]
user_roles user_roles[]
roles_roles_created_byTousers roles[] @relation("roles_created_byTousers")
roles_roles_updated_byTousers roles[] @relation("roles_updated_byTousers")
uom_uom_created_byTousers uom[] @relation("uom_created_byTousers")
uom_uom_updated_byTousers uom[] @relation("uom_updated_byTousers")
users_users_created_byTousers users? @relation("users_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
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)
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)
other_users_users_updated_byTousers users[] @relation("users_updated_byTousers")
vendor_addresses_vendor_addresses_created_byTousers vendor_addresses[] @relation("vendor_addresses_created_byTousers")
vendor_addresses_vendor_addresses_updated_byTousers vendor_addresses[] @relation("vendor_addresses_updated_byTousers")
vendor_bank_details_vendor_bank_details_created_byTousers vendor_bank_details[] @relation("vendor_bank_details_created_byTousers")
vendor_bank_details_vendor_bank_details_updated_byTousers vendor_bank_details[] @relation("vendor_bank_details_updated_byTousers")
vendor_contacts_vendor_contacts_created_byTousers vendor_contacts[] @relation("vendor_contacts_created_byTousers")
vendor_contacts_vendor_contacts_updated_byTousers vendor_contacts[] @relation("vendor_contacts_updated_byTousers")
vendor_item_mapping_vendor_item_mapping_created_byTousers vendor_item_mapping[] @relation("vendor_item_mapping_created_byTousers")
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")
}
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
@ -968,36 +945,36 @@ model vendor_item_mapping {
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model vendors {
id BigInt @id @default(autoincrement())
vendor_code String @unique @db.VarChar(30)
vendor_name String @db.VarChar(200)
vendor_type String @db.VarChar(30)
gst_treatment String? @db.VarChar(30)
source_of_supply String? @db.VarChar(100)
gstin String? @db.VarChar(15)
pan String? @db.VarChar(10)
payment_term_id BigInt?
credit_period_days Int @default(0)
status String @default("active") @db.VarChar(20)
remarks String?
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)
assets_assets_vendor_idTovendors assets[] @relation("assets_vendor_idTovendors")
asset_amc_contracts asset_amc_contracts[]
asset_service_visits asset_service_visits[]
grn grn[]
purchase_orders purchase_orders[]
vendor_addresses vendor_addresses[]
vendor_bank_details vendor_bank_details[]
vendor_contacts vendor_contacts[]
vendor_item_mapping vendor_item_mapping[]
users_vendors_created_byTousers users? @relation("vendors_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
payment_terms payment_terms? @relation(fields: [payment_term_id], references: [id], onUpdate: NoAction)
users_vendors_updated_byTousers users? @relation("vendors_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
id BigInt @id @default(autoincrement())
vendor_code String @unique @db.VarChar(30)
vendor_name String @db.VarChar(200)
vendor_type String @db.VarChar(30)
gst_treatment String? @db.VarChar(30)
source_of_supply String? @db.VarChar(100)
gstin String? @db.VarChar(15)
pan String? @db.VarChar(10)
payment_term_id BigInt?
credit_period_days Int @default(0)
status String @default("active") @db.VarChar(20)
remarks String?
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)
assets_assets_vendor_idTovendors assets[] @relation("assets_vendor_idTovendors")
asset_amc_contracts asset_amc_contracts[]
asset_service_visits asset_service_visits[]
grn grn[]
purchase_orders purchase_orders[]
vendor_addresses vendor_addresses[]
vendor_bank_details vendor_bank_details[]
vendor_contacts vendor_contacts[]
vendor_item_mapping vendor_item_mapping[]
users_vendors_created_byTousers users? @relation("vendors_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
payment_terms payment_terms? @relation(fields: [payment_term_id], references: [id], onUpdate: NoAction)
users_vendors_updated_byTousers users? @relation("vendors_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
@@index([vendor_name(ops: raw("gin_trgm_ops"))], map: "idx_vendors_name_trgm", type: Gin)
}

View File

@ -0,0 +1,25 @@
-- Remove brand_id from items & purchase_orders and drop the brands master table.
-- WARNING: destructive. Export brand data first if it must be retained.
-- Idempotent: safe to re-run.
BEGIN;
-- 0) Drop dependent view (selects purchase_orders.*); recreated by patch-purchase-orders-drop-po-type.sql
DROP VIEW IF EXISTS v_purchase_orders CASCADE;
-- 1) Drop FK constraints (name may vary by DB; drop defensively)
ALTER TABLE items DROP CONSTRAINT IF EXISTS items_brand_id_fkey;
ALTER TABLE purchase_orders DROP CONSTRAINT IF EXISTS purchase_orders_brand_id_fkey;
-- 2) Drop columns
ALTER TABLE items DROP COLUMN IF EXISTS brand_id;
ALTER TABLE purchase_orders DROP COLUMN IF EXISTS brand_id;
-- 3) Drop the brands master (CASCADE removes its check/unique constraints and user FKs)
DROP TABLE IF EXISTS brands CASCADE;
COMMIT;
-- Verify:
-- SELECT column_name FROM information_schema.columns WHERE table_name = 'items' AND column_name = 'brand_id';
-- SELECT to_regclass('public.brands'); -- expect NULL

View File

@ -0,0 +1,32 @@
-- Link HSN codes to a default GST rate
-- Adds hsn_codes.gst_rate_id (nullable) FK -> gst_rates(id)
-- Idempotent: safe to re-run.
BEGIN;
ALTER TABLE hsn_codes
ADD COLUMN IF NOT EXISTS gst_rate_id BIGINT;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'hsn_codes_gst_rate_id_fkey'
) THEN
ALTER TABLE hsn_codes
ADD CONSTRAINT hsn_codes_gst_rate_id_fkey
FOREIGN KEY (gst_rate_id) REFERENCES gst_rates(id);
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_hsn_codes_gst_rate_id ON hsn_codes (gst_rate_id);
-- Optional backfill: default existing HSN codes to the 18% slab (adjust per business rules)
-- UPDATE hsn_codes h
-- SET gst_rate_id = g.id
-- FROM gst_rates g
-- WHERE g.rate_pct = 18 AND g.is_active = TRUE AND h.gst_rate_id IS NULL;
COMMIT;
-- Verify:
-- SELECT h.code, g.rate_pct FROM hsn_codes h LEFT JOIN gst_rates g ON g.id = h.gst_rate_id ORDER BY h.code;

View File

@ -0,0 +1,41 @@
-- Add category_type to item_categories: STOCK | ASSET
-- STOCK categories are shown in the Items module; ASSET categories in the Assets module.
-- Idempotent: safe to re-run.
BEGIN;
ALTER TABLE item_categories
ADD COLUMN IF NOT EXISTS category_type VARCHAR(10);
-- Backfill existing rows: treat categories carrying asset defaults as ASSET, else STOCK
UPDATE item_categories
SET category_type = CASE
WHEN code_prefix IS NOT NULL
OR default_useful_life_years IS NOT NULL
OR default_depreciation_method IS NOT NULL
THEN 'ASSET'
ELSE 'STOCK'
END
WHERE category_type IS NULL;
ALTER TABLE item_categories
ALTER COLUMN category_type SET DEFAULT 'STOCK';
ALTER TABLE item_categories
ALTER COLUMN category_type SET NOT NULL;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'item_categories_category_type_check'
) THEN
ALTER TABLE item_categories
ADD CONSTRAINT item_categories_category_type_check
CHECK (category_type IN ('STOCK', 'ASSET'));
END IF;
END $$;
COMMIT;
-- Verify:
-- SELECT category_type, COUNT(*) FROM item_categories WHERE deleted_at IS NULL GROUP BY 1;

View File

@ -0,0 +1,32 @@
-- Remove po_type from purchase_orders.
-- PO classification is now derived from the linked vendor (vendors.vendor_type).
-- Recreates v_purchase_orders (which selects po.*) so it no longer exposes po_type.
-- Idempotent: safe to re-run.
BEGIN;
-- The view selects po.* so it must be dropped before the column can be removed
DROP VIEW IF EXISTS v_purchase_orders CASCADE;
ALTER TABLE purchase_orders DROP COLUMN IF EXISTS po_type;
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,
v.vendor_type
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;
COMMIT;
-- Verify:
-- SELECT column_name FROM information_schema.columns WHERE table_name = 'purchase_orders' AND column_name = 'po_type';

View File

@ -4,7 +4,6 @@ tags:
- name: Item Categories
- name: Item Subcategories
- name: Items
- name: Brands
- name: GST Rates
- name: HSN Codes
- name: Payment Terms
@ -109,10 +108,11 @@ components:
is_active: { type: boolean, example: true }
ItemCategoriesCreateBody:
type: object
required: [code, name]
required: [code, name, category_type]
properties:
code: { type: string, example: 'RAW' }
name: { type: string, example: 'Raw Material' }
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK, description: 'STOCK categories are shown in the Items module; ASSET categories in the Assets module' }
code_prefix: { type: string, nullable: true, example: 'MCH', description: Optional; used for asset code series when category is used for assets }
default_useful_life_years: { type: integer, nullable: true, example: 15 }
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, OTHER], example: SLM }
@ -123,6 +123,7 @@ components:
properties:
code: { type: string, example: 'RAW' }
name: { type: string, example: 'Raw Material' }
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK }
code_prefix: { type: string, nullable: true, example: 'MCH' }
default_useful_life_years: { type: integer, nullable: true, example: 15 }
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, OTHER], example: SLM }
@ -168,7 +169,6 @@ components:
uom_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, nullable: true, example: 1 }
gst_rate_id: { type: integer, nullable: true, example: 4 }
brand_id: { type: integer, nullable: true, example: 1 }
is_asset_item: { type: boolean, example: false }
description: { type: string, example: 'Raw material' }
specification: { type: string, example: '96% purity' }
@ -185,7 +185,6 @@ components:
uom_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, nullable: true, example: 1 }
gst_rate_id: { type: integer, nullable: true, example: 4 }
brand_id: { type: integer, nullable: true, example: 1 }
is_asset_item: { type: boolean, example: false }
description: { type: string, example: 'Raw material' }
specification: { type: string, example: '96% purity' }
@ -237,35 +236,6 @@ components:
id: { type: string }
rate_pct: { type: number }
description: { type: string, nullable: true }
brand:
type: object
nullable: true
properties:
id: { type: string }
code: { type: string }
name: { type: string }
BrandsCreateBody:
type: object
required: [code, name, brand_type]
properties:
code: { type: string, example: 'OWN01' }
name: { type: string, example: 'Bharat Clean' }
brand_type: { type: string, enum: [OWN, OEM, THIRD_PARTY], example: OWN }
contact_person: { type: string, example: 'Ravi' }
phone: { type: string, example: '9876543210' }
email: { type: string, example: 'ops@bharaterp.com' }
is_active: { type: boolean, example: true }
BrandsUpdateBody:
type: object
minProperties: 1
properties:
code: { type: string, example: 'OWN01' }
name: { type: string, example: 'Bharat Clean' }
brand_type: { type: string, enum: [OWN, OEM, THIRD_PARTY], example: OWN }
contact_person: { type: string, example: 'Ravi' }
phone: { type: string, example: '9876543210' }
email: { type: string, example: 'ops@bharaterp.com' }
is_active: { type: boolean, example: true }
GstRatesCreateBody:
type: object
required: [rate_pct]
@ -286,6 +256,7 @@ components:
properties:
code: { type: string, example: '34029099', description: '48 digit HSN/SAC code' }
description: { type: string, example: 'Organic surface-active agents' }
gst_rate_id: { type: integer, nullable: true, example: 4, description: 'Default GST rate for this HSN' }
is_active: { type: boolean, example: true }
HsnCodesUpdateBody:
type: object
@ -293,6 +264,7 @@ components:
properties:
code: { type: string, example: '34029099' }
description: { type: string, example: 'Organic surface-active agents' }
gst_rate_id: { type: integer, nullable: true, example: 4 }
is_active: { type: boolean, example: true }
PaymentTermsCreateBody:
type: object
@ -771,6 +743,10 @@ paths:
- in: query
name: is_active
schema: { type: boolean }
- in: query
name: category_type
description: Filter by category type (STOCK for Items module, ASSET for Assets module)
schema: { type: string, enum: [STOCK, ASSET] }
responses:
"200":
description: List fetched
@ -982,75 +958,6 @@ paths:
description: Deleted
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
/masters/brands:
get:
tags: [Brands]
summary: List Brands
parameters:
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: limit
schema: { type: integer, default: 20, maximum: 100 }
- in: query
name: search
schema: { type: string }
- in: query
name: is_active
schema: { type: boolean }
responses:
"200":
description: List fetched
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
post:
tags: [Brands]
summary: Create Brand
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/BrandsCreateBody" }
responses:
"201":
description: Created
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"409": { description: Duplicate key }
/masters/brands/{id}:
parameters:
- in: path
name: id
required: true
schema: { type: string }
get:
tags: [Brands]
summary: Get Brand by id
responses:
"200":
description: Fetched
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
put:
tags: [Brands]
summary: Update Brand
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/BrandsUpdateBody" }
responses:
"200":
description: Updated
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
delete:
tags: [Brands]
summary: Delete Brand
responses:
"200":
description: Deleted
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
/masters/gst-rates:
get:
tags: [GST Rates]

View File

@ -19,17 +19,12 @@ components:
remarks: { type: string, example: 'Urgent line' }
PurchaseOrdersCreateBody:
type: object
required: [po_date, po_type, vendor_id, plant_id, items]
required: [po_date, vendor_id, plant_id, items]
properties:
po_date: { type: string, format: date, example: '2026-06-18' }
po_type:
type: string
enum: [RAW_MATERIAL, PACKING_MATERIAL, ASSET_CAPITAL, SERVICE, GENERAL]
example: RAW_MATERIAL
vendor_id: { type: integer, example: 1 }
plant_id: { type: integer, example: 1 }
warehouse_id: { type: integer, nullable: true, example: 1 }
brand_id: { type: integer, nullable: true, example: 1 }
payment_term_id: { type: integer, nullable: true, example: 1 }
delivery_term_id: { type: integer, nullable: true, example: 1 }
expected_delivery_date: { type: string, format: date, nullable: true, example: '2026-07-01' }
@ -49,13 +44,9 @@ components:
minProperties: 1
properties:
po_date: { type: string, format: date, example: '2026-06-18' }
po_type:
type: string
enum: [RAW_MATERIAL, PACKING_MATERIAL, ASSET_CAPITAL, SERVICE, GENERAL]
vendor_id: { type: integer, example: 1 }
plant_id: { type: integer, example: 1 }
warehouse_id: { type: integer, nullable: true }
brand_id: { type: integer, nullable: true }
payment_term_id: { type: integer, nullable: true }
delivery_term_id: { type: integer, nullable: true }
expected_delivery_date: { type: string, format: date, nullable: true }
@ -107,7 +98,7 @@ paths:
parameters:
- { name: search, in: query, schema: { type: string, example: 'PO/2026' } }
- { name: status, in: query, schema: { type: string, example: DRAFT } }
- { name: po_type, in: query, schema: { type: string, example: RAW_MATERIAL } }
- { name: vendor_type, in: query, schema: { type: string, example: RAW_MATERIAL } }
- { name: vendor_id, in: query, schema: { type: integer, example: 1 } }
- { name: plant_id, in: query, schema: { type: integer, example: 1 } }
- { name: date_from, in: query, schema: { type: string, format: date } }
@ -127,7 +118,7 @@ paths:
- { name: limit, in: query, schema: { type: integer, example: 20 } }
- { name: search, in: query, schema: { type: string, example: 'PO/2026' } }
- { name: status, in: query, schema: { type: string, example: DRAFT } }
- { name: po_type, in: query, schema: { type: string, example: RAW_MATERIAL } }
- { name: vendor_type, in: query, schema: { type: string, example: RAW_MATERIAL } }
- { name: vendor_id, in: query, schema: { type: integer, example: 1 } }
- { name: plant_id, in: query, schema: { type: integer, example: 1 } }
- { name: date_from, in: query, schema: { type: string, format: date } }

View File

@ -1,38 +0,0 @@
const asyncHandler = require('../../../utils/asyncHandler');
const ApiResponse = require('../../../utils/ApiResponse');
const service = require('./brands.service');
const create = asyncHandler(async (req, res) => {
const data = await service.createBrands(req.body, req.user?.id, req.id);
res.status(201).json(new ApiResponse(201, data, 'brands created successfully'));
});
const list = asyncHandler(async (req, res) => {
const result = await service.listBrands(req.query);
res.json(new ApiResponse(200, result.data, 'brands list fetched', result.meta));
});
const getOne = asyncHandler(async (req, res) => {
const data = await service.getBrandsById(req.params.id);
res.json(new ApiResponse(200, data, 'brands fetched'));
});
const update = asyncHandler(async (req, res) => {
const data = await service.updateBrands(req.params.id, req.body, req.user?.id, req.id);
res.json(new ApiResponse(200, data, 'brands updated successfully'));
});
const remove = asyncHandler(async (req, res) => {
await service.deleteBrands(req.params.id, req.user?.id, req.id);
res.json(new ApiResponse(200, null, 'brands deleted successfully'));
});
const exportCsv = asyncHandler(async (req, res) => {
const csv = await service.exportBrands(req.query);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="brands-export.csv"');
res.send(csv);
});
module.exports = { create, list, exportCsv, getOne, update, remove };

View File

@ -1,23 +0,0 @@
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('./brands.controller');
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./brands.validation');
const router = express.Router();
router.use(authenticate);
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
router.get(
'/export',
authorize('MASTERS', 'export'),
validate(exportQuerySchema, 'query'),
controller.exportCsv
);
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
router.delete('/:id', authorize('MASTERS', 'delete'), controller.remove);
module.exports = router;

View File

@ -1,75 +0,0 @@
const { buildMasterService } = require('../_shared/master.factory');
const config = {
modelName: 'brands',
tableName: 'brands',
uniqueField: 'code',
softDelete: true,
fields: [
{
name: 'code',
type: 'string',
uppercase: true,
searchable: true,
},
{
name: 'name',
type: 'string',
uppercase: false,
searchable: true,
},
{
name: 'brand_type',
type: 'string',
uppercase: false,
searchable: false,
},
{
name: 'contact_person',
type: 'string',
uppercase: false,
searchable: false,
},
{
name: 'phone',
type: 'string',
uppercase: false,
searchable: false,
},
{
name: 'email',
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);
module.exports = {
createBrands: service.createOne,
listBrands: service.list,
exportBrands: service.exportCsv,
getBrandsById: service.getOne,
updateBrands: service.updateOne,
deleteBrands: service.removeOne,
};

View File

@ -1,28 +0,0 @@
const Joi = require('joi');
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
const BRAND_TYPES = ['OWN', 'OEM', 'THIRD_PARTY'];
const createSchema = Joi.object({
code: masterCode({ max: 30, required: true }),
name: masterName({ max: 150, required: true }),
brand_type: Joi.string()
.valid(...BRAND_TYPES)
.default('OWN'),
contact_person: masterName({ max: 100 }).allow(null),
phone: Joi.string().max(20).allow(null, '').optional(),
email: Joi.string().email().max(200).allow(null, '').optional(),
is_active: Joi.boolean().default(true),
});
const updateSchema = Joi.object({
code: masterCode({ max: 30 }),
name: masterName({ max: 150 }),
brand_type: Joi.string().valid(...BRAND_TYPES).optional(),
contact_person: masterName({ max: 100 }).allow(null),
phone: Joi.string().max(20).allow(null, '').optional(),
email: Joi.string().email().max(200).allow(null, '').optional(),
is_active: Joi.boolean().optional(),
}).min(1);
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema, BRAND_TYPES };

View File

@ -1,10 +1,27 @@
const { buildMasterService } = require('../_shared/master.factory');
const toDecimal = (value) => (value !== null && value !== undefined ? Number(value) : value);
const mapRow = (row) => {
if (!row) return row;
const { gst_rates, ...rest } = row;
return {
...rest,
gst_rate: gst_rates
? { ...gst_rates, rate_pct: toDecimal(gst_rates.rate_pct) }
: null,
};
};
const config = {
modelName: 'hsn_codes',
tableName: 'hsn_codes',
uniqueField: 'code',
softDelete: false,
include: {
gst_rates: { select: { id: true, rate_pct: true, description: true } },
},
mapRow,
fields: [
{
name: 'code',
@ -18,6 +35,13 @@ const config = {
uppercase: false,
searchable: true,
},
{
name: 'gst_rate_id',
type: 'int',
asBigInt: true,
uppercase: false,
searchable: false,
},
{
name: 'is_active',
type: 'boolean',
@ -25,6 +49,13 @@ const config = {
searchable: false,
},
],
exportColumns: [
{ key: 'code', header: 'Code' },
{ key: 'description', header: 'Description' },
{ key: (row) => (row.gst_rate ? row.gst_rate.rate_pct : ''), header: 'GST %' },
{ key: 'is_active', header: 'Is Active' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },
],
};
const service = buildMasterService(config);

View File

@ -13,12 +13,14 @@ const hsnCodeField = Joi.string()
const createSchema = Joi.object({
code: hsnCodeField.required(),
description: Joi.string().allow(null, '').optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
is_active: Joi.boolean().optional(),
});
const updateSchema = Joi.object({
code: hsnCodeField.optional(),
description: Joi.string().allow(null, '').optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
is_active: Joi.boolean().optional(),
}).min(1);

View File

@ -4,7 +4,6 @@ const router = express.Router();
router.use('/uom', require('./uom/uom.routes'));
router.use('/item-categories', require('./item-categories/item-categories.routes'));
router.use('/brands', require('./brands/brands.routes'));
router.use('/gst-rates', require('./gst-rates/gst-rates.routes'));
router.use('/hsn-codes', require('./hsn-codes/hsn-codes.routes'));
router.use('/payment-terms', require('./payment-terms/payment-terms.routes'));

View File

@ -0,0 +1,3 @@
const CATEGORY_TYPES = ['STOCK', 'ASSET'];
module.exports = { CATEGORY_TYPES };

View File

@ -20,6 +20,13 @@ const config = {
uppercase: false,
searchable: true,
},
{
name: 'category_type',
type: 'string',
uppercase: true,
searchable: false,
filterable: true,
},
{
name: 'code_prefix',
type: 'string',

View File

@ -1,10 +1,15 @@
const Joi = require('joi');
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
const { DEPRECIATION_METHODS } = require('../../assets/assets.constants');
const { CATEGORY_TYPES } = require('./item-categories.constants');
const createSchema = Joi.object({
code: masterCode({ max: 30 }),
name: masterName({ max: 150 }),
category_type: Joi.string()
.uppercase()
.valid(...CATEGORY_TYPES)
.required(),
code_prefix: masterCode({ max: 10 }).allow(null, ''),
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
default_depreciation_method: Joi.string()
@ -17,6 +22,10 @@ const createSchema = Joi.object({
const updateSchema = Joi.object({
code: masterCode({ max: 30 }),
name: masterName({ max: 150 }),
category_type: Joi.string()
.uppercase()
.valid(...CATEGORY_TYPES)
.optional(),
code_prefix: masterCode({ max: 10 }).allow(null, ''),
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
default_depreciation_method: Joi.string()
@ -26,4 +35,21 @@ const updateSchema = Joi.object({
is_active: Joi.boolean().optional(),
}).min(1);
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
const itemCategoriesListQuerySchema = listQuerySchema.keys({
category_type: Joi.string()
.uppercase()
.valid(...CATEGORY_TYPES)
.optional(),
});
const exportQuerySchema = itemCategoriesListQuerySchema.keys({
page: Joi.strip(),
limit: Joi.strip(),
});
module.exports = {
createSchema,
updateSchema,
listQuerySchema: itemCategoriesListQuerySchema,
exportQuerySchema,
};

View File

@ -14,7 +14,6 @@ const SOFT_DELETE_TABLES = new Set([
'item_categories',
'item_subcategories',
'uom',
'brands',
]);
const itemInclude = {
@ -23,7 +22,6 @@ const itemInclude = {
hsn_codes: { select: { id: true, code: true, description: true } },
uom: { select: { id: true, code: true, name: true } },
gst_rates: { select: { id: true, rate_pct: true, description: true } },
brands: { select: { id: true, code: true, name: true } },
};
const toDecimal = (value) =>
@ -37,7 +35,6 @@ const sanitizeItem = (row) => {
hsn_codes,
uom,
gst_rates,
brands,
...rest
} = row;
@ -52,12 +49,10 @@ const sanitizeItem = (row) => {
gst_rate: gst_rates
? { ...gst_rates, rate_pct: toDecimal(gst_rates.rate_pct) }
: null,
brand: brands || null,
item_categories: undefined,
item_subcategories: undefined,
hsn_codes: undefined,
gst_rates: undefined,
brands: undefined,
};
};
@ -86,7 +81,6 @@ const normalizePayload = (payload, { isCreate = false } = {}) => {
'uom_id',
'hsn_code_id',
'gst_rate_id',
'brand_id',
];
for (const field of bigintFields) {
if (data[field] !== undefined && data[field] !== null && data[field] !== '') {
@ -169,9 +163,6 @@ const validateItemPayload = async (payload, { isCreate = false } = {}) => {
if (payload.uom_id) await assertReference('uom', payload.uom_id, 'uom_id');
if (payload.hsn_code_id !== undefined) await assertHsnCode(payload.hsn_code_id);
if (payload.gst_rate_id !== undefined) await assertGstRate(payload.gst_rate_id);
if (payload.brand_id) {
await assertReference('brands', payload.brand_id, 'brand_id', { requireActive: false });
}
};
const createItems = async (payload, userId, requestId) => {
@ -250,7 +241,6 @@ const exportItems = async (query) => {
{ key: (row) => row.uom?.code || '', header: 'UOM' },
{ key: (row) => row.hsn_code?.code || '', header: 'HSN Code' },
{ key: (row) => (row.gst_rate ? row.gst_rate.rate_pct : ''), header: 'GST %' },
{ key: (row) => row.brand?.name || '', header: 'Brand' },
{ key: 'is_asset_item', header: 'Is Asset Item' },
{ key: 'is_active', header: 'Is Active' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },

View File

@ -8,7 +8,6 @@ const createSchema = Joi.object({
uom_id: Joi.number().integer().positive().required(),
hsn_code_id: Joi.number().integer().positive().allow(null).optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
brand_id: Joi.number().integer().positive().allow(null).optional(),
is_asset_item: Joi.boolean().default(false),
description: Joi.string().allow(null, '').optional(),
specification: Joi.string().allow(null, '').optional(),
@ -24,7 +23,6 @@ const updateSchema = Joi.object({
uom_id: Joi.number().integer().positive().optional(),
hsn_code_id: Joi.number().integer().positive().allow(null).optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
brand_id: Joi.number().integer().positive().allow(null).optional(),
is_asset_item: Joi.boolean().optional(),
description: Joi.string().allow(null, '').optional(),
specification: Joi.string().allow(null, '').optional(),

View File

@ -1,4 +1,4 @@
const PO_TYPES = ['RAW_MATERIAL', 'PACKING_MATERIAL', 'ASSET_CAPITAL', 'SERVICE', 'GENERAL'];
const VENDOR_TYPES = ['RAW_MATERIAL', 'PACKING_MATERIAL', 'ASSET_CAPITAL', 'SERVICE', 'GENERAL'];
const PO_STATUSES = [
'DRAFT',
@ -26,7 +26,7 @@ const AMENDABLE_STATUSES = ['APPROVED', 'SENT_TO_VENDOR', 'PARTIALLY_RECEIVED'];
const DELETABLE_STATUSES = ['DRAFT', 'REJECTED'];
module.exports = {
PO_TYPES,
VENDOR_TYPES,
PO_STATUSES,
EDITABLE_STATUSES,
SUBMITTABLE_STATUSES,

View File

@ -22,10 +22,9 @@ const { assertPlant, assertWarehouse } = require('../../utils/locations');
const { sanitizeAttachment } = require('./po.attachments.service');
const poListInclude = {
vendors: { select: { id: true, vendor_code: true, vendor_name: true } },
vendors: { select: { id: true, vendor_code: true, vendor_name: true, vendor_type: 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 } },
};
@ -66,6 +65,7 @@ const poPdfInclude = {
id: true,
vendor_code: true,
vendor_name: true,
vendor_type: true,
gstin: true,
vendor_addresses: {
where: { is_active: true },
@ -108,7 +108,6 @@ const sanitizePo = (po) => {
vendors,
plant,
warehouse,
brands,
payment_terms,
delivery_terms,
users_purchase_orders_created_byTousers,
@ -125,7 +124,6 @@ const sanitizePo = (po) => {
vendor: vendors || null,
plant: plant || null,
warehouse: warehouse || null,
brand: brands || null,
payment_term: payment_terms || null,
delivery_term: delivery_terms || null,
created_by_user: users_purchase_orders_created_byTousers || null,
@ -155,7 +153,6 @@ const sanitizePo = (po) => {
vendors: undefined,
plants: undefined,
warehouses: undefined,
brands: undefined,
payment_terms: undefined,
delivery_terms: undefined,
users_purchase_orders_created_byTousers: undefined,
@ -259,8 +256,6 @@ const buildHeaderData = async (payload, builtItems, userId) => {
await assertReference('vendors', payload.vendor_id, 'vendor_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)
await assertReference('payment_terms', payload.payment_term_id, 'payment_term_id');
if (payload.delivery_term_id)
@ -283,11 +278,9 @@ const buildHeaderData = async (payload, builtItems, userId) => {
return {
po_date: toDateOnly(payload.po_date),
po_type: payload.po_type,
vendor_id: BigInt(payload.vendor_id),
plant_id: BigInt(payload.plant_id),
warehouse_id: payload.warehouse_id ? BigInt(payload.warehouse_id) : null,
brand_id: payload.brand_id ? BigInt(payload.brand_id) : null,
payment_term_id: payload.payment_term_id ? BigInt(payload.payment_term_id) : null,
delivery_term_id: payload.delivery_term_id ? BigInt(payload.delivery_term_id) : null,
expected_delivery_date: payload.expected_delivery_date
@ -349,7 +342,7 @@ const buildPoPdfPayload = (po, company) => {
po_date: po.po_date,
expected_delivery_date: po.expected_delivery_date,
status: humanizeLabel(po.status),
po_type: humanizeLabel(po.po_type),
vendor_type: humanizeLabel(po.vendor?.vendor_type),
revision_no: po.revision_no ?? 0,
payment_term: po.payment_term?.name || null,
delivery_term: po.delivery_term?.name || null,
@ -371,7 +364,6 @@ const buildPoPdfPayload = (po, company) => {
state: po.plant?.state || '',
pincode: po.plant?.pincode || '',
},
brand: { name: po.brand?.name || '-' },
warehouse: { name: po.warehouse?.name || '-' },
items: (po.items || []).map((line) => {
const orderedQty = toNum(line.ordered_qty);
@ -433,8 +425,8 @@ const createPurchaseOrder = async (payload, userId, requestId) => {
const buildPurchaseOrdersWhere = (query) => ({
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.po_type ? { po_type: query.po_type } : {}),
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
...(query.vendor_type ? { vendors: { vendor_type: query.vendor_type } } : {}),
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
...(query.search ? { po_number: { contains: query.search, mode: 'insensitive' } } : {}),
...(query.date_from || query.date_to
@ -476,14 +468,13 @@ const exportPurchaseOrders = async (query) => {
[
{ key: 'po_number', header: 'PO Number' },
{ key: 'po_date', header: 'PO Date', type: 'date' },
{ key: 'po_type', header: 'Type' },
{ key: (row) => row.vendor?.vendor_type || '', header: 'Vendor Type' },
{ key: 'status', header: 'Status' },
{ key: 'revision_no', header: 'Revision' },
{ key: (row) => row.vendor?.vendor_code || '', header: 'Vendor Code' },
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor Name' },
{ key: (row) => row.plant?.name || '', header: 'Plant' },
{ key: (row) => row.warehouse?.name || '', header: 'Warehouse' },
{ key: (row) => row.brand?.name || '', header: 'Brand' },
{ key: 'expected_delivery_date', header: 'Expected Delivery', type: 'date' },
{ key: 'sub_total', header: 'Sub Total' },
{ key: 'tax_total', header: 'Tax Total' },
@ -504,11 +495,9 @@ const updatePurchaseOrder = async (id, payload, userId, requestId) => {
const merged = {
po_date: payload.po_date ?? existing.po_date,
po_type: payload.po_type ?? existing.po_type,
vendor_id: payload.vendor_id ?? existing.vendor_id,
plant_id: payload.plant_id ?? existing.plant_id,
warehouse_id: payload.warehouse_id !== undefined ? payload.warehouse_id : existing.warehouse_id,
brand_id: payload.brand_id !== undefined ? payload.brand_id : existing.brand_id,
payment_term_id:
payload.payment_term_id !== undefined ? payload.payment_term_id : existing.payment_term_id,
delivery_term_id:
@ -775,11 +764,9 @@ const amendPurchaseOrder = async (id, payload, userId, requestId) => {
const merged = {
po_date: payload.po_date ?? existing.po_date,
po_type: payload.po_type ?? existing.po_type,
vendor_id: payload.vendor_id ?? existing.vendor_id,
plant_id: payload.plant_id ?? existing.plant_id,
warehouse_id: payload.warehouse_id !== undefined ? payload.warehouse_id : existing.warehouse_id,
brand_id: payload.brand_id !== undefined ? payload.brand_id : existing.brand_id,
payment_term_id:
payload.payment_term_id !== undefined ? payload.payment_term_id : existing.payment_term_id,
delivery_term_id:

View File

@ -1,5 +1,5 @@
const Joi = require('joi');
const { PO_TYPES, PO_STATUSES } = require('./purchase-orders.constants');
const { PO_STATUSES, VENDOR_TYPES } = require('./purchase-orders.constants');
const poItemSchema = Joi.object({
item_id: Joi.number().integer().positive().required(),
@ -16,13 +16,9 @@ const poItemSchema = Joi.object({
const poHeaderFields = {
po_date: Joi.date().iso().required(),
po_type: Joi.string()
.valid(...PO_TYPES)
.required(),
vendor_id: Joi.number().integer().positive().required(),
plant_id: Joi.number().integer().positive().required(),
warehouse_id: Joi.number().integer().positive().allow(null).optional(),
brand_id: Joi.number().integer().positive().allow(null).optional(),
payment_term_id: Joi.number().integer().positive().allow(null).optional(),
delivery_term_id: Joi.number().integer().positive().allow(null).optional(),
expected_delivery_date: Joi.date().iso().allow(null).optional(),
@ -51,11 +47,9 @@ const createPurchaseOrderSchema = Joi.object({
const updatePurchaseOrderSchema = Joi.object({
po_date: poHeaderFields.po_date.optional(),
po_type: poHeaderFields.po_type.optional(),
vendor_id: poHeaderFields.vendor_id.optional(),
plant_id: poHeaderFields.plant_id.optional(),
warehouse_id: poHeaderFields.warehouse_id.optional(),
brand_id: poHeaderFields.brand_id.optional(),
payment_term_id: poHeaderFields.payment_term_id.optional(),
delivery_term_id: poHeaderFields.delivery_term_id.optional(),
expected_delivery_date: poHeaderFields.expected_delivery_date.optional(),
@ -72,11 +66,9 @@ const updatePurchaseOrderSchema = Joi.object({
const amendPurchaseOrderSchema = Joi.object({
po_date: poHeaderFields.po_date.optional(),
po_type: poHeaderFields.po_type.optional(),
vendor_id: poHeaderFields.vendor_id.optional(),
plant_id: poHeaderFields.plant_id.optional(),
warehouse_id: poHeaderFields.warehouse_id.optional(),
brand_id: poHeaderFields.brand_id.optional(),
payment_term_id: poHeaderFields.payment_term_id.optional(),
delivery_term_id: poHeaderFields.delivery_term_id.optional(),
expected_delivery_date: poHeaderFields.expected_delivery_date.optional(),
@ -98,8 +90,8 @@ const listPurchaseOrdersQuerySchema = Joi.object({
status: Joi.string()
.valid(...PO_STATUSES)
.optional(),
po_type: Joi.string()
.valid(...PO_TYPES)
vendor_type: Joi.string()
.valid(...VENDOR_TYPES)
.optional(),
vendor_id: Joi.number().integer().positive().optional(),
plant_id: Joi.number().integer().positive().optional(),

View File

@ -8,12 +8,10 @@ const PG_CODES = {
};
const CONSTRAINT_MESSAGES = {
brands_brand_type_check: 'Invalid brand type. Allowed values: OWN, OEM, THIRD_PARTY.',
item_categories_category_type_check: 'Invalid category type. Allowed values: STOCK, ASSET.',
};
const TABLE_MESSAGES = {
brands: 'Invalid brand data. Please check required fields and allowed values.',
};
const TABLE_MESSAGES = {};
const extractPgCode = (err) => {
const fromCause = err?.cause?.code;

View File

@ -18,7 +18,7 @@ const getDummyPoData = () => ({
po_date: '2026-07-07',
expected_delivery_date: '2026-07-21',
status: 'Approved',
po_type: 'Raw material',
vendor_type: 'Raw material',
revision_no: 1,
payment_term: '30 days credit',
delivery_term: 'FOB (freight on board)',
@ -41,7 +41,6 @@ const getDummyPoData = () => ({
state: 'Tamil Nadu',
pincode: '600032',
},
brand: { name: 'Tata Tiscon' },
warehouse: { name: 'Main warehouse' },
items: [
{
@ -122,7 +121,6 @@ const generatePoHtml = (inputData = getDummyPoData()) => {
const po = data.po || {};
const vendor = data.vendor || {};
const shipTo = data.ship_to || {};
const brand = data.brand || {};
const warehouse = data.warehouse || {};
const totals = data.totals || {};
@ -471,12 +469,8 @@ const generatePoHtml = (inputData = getDummyPoData()) => {
<div class="class-row">
<div>
<div class="label">PO type</div>
<div class="value">${escapeHtml(po.po_type || '-')}</div>
</div>
<div>
<div class="label">Brand</div>
<div class="value">${escapeHtml(brand.name || '-')}</div>
<div class="label">Vendor type</div>
<div class="value">${escapeHtml(po.vendor_type || '-')}</div>
</div>
<div>
<div class="label">Warehouse</div>