diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a283187..3edb3c2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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) } diff --git a/scripts/patch-drop-brands.sql b/scripts/patch-drop-brands.sql new file mode 100644 index 0000000..bda772d --- /dev/null +++ b/scripts/patch-drop-brands.sql @@ -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 diff --git a/scripts/patch-hsn-codes-gst-rate.sql b/scripts/patch-hsn-codes-gst-rate.sql new file mode 100644 index 0000000..d995ad4 --- /dev/null +++ b/scripts/patch-hsn-codes-gst-rate.sql @@ -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; diff --git a/scripts/patch-item-categories-type.sql b/scripts/patch-item-categories-type.sql new file mode 100644 index 0000000..83644ed --- /dev/null +++ b/scripts/patch-item-categories-type.sql @@ -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; diff --git a/scripts/patch-purchase-orders-drop-po-type.sql b/scripts/patch-purchase-orders-drop-po-type.sql new file mode 100644 index 0000000..610af68 --- /dev/null +++ b/scripts/patch-purchase-orders-drop-po-type.sql @@ -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'; diff --git a/src/docs/completed-routes.yaml b/src/docs/completed-routes.yaml index 192b541..322f63d 100644 --- a/src/docs/completed-routes.yaml +++ b/src/docs/completed-routes.yaml @@ -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: '4–8 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] diff --git a/src/docs/purchase-orders-routes.yaml b/src/docs/purchase-orders-routes.yaml index 18d3ffe..8257854 100644 --- a/src/docs/purchase-orders-routes.yaml +++ b/src/docs/purchase-orders-routes.yaml @@ -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 } } diff --git a/src/modules/masters/brands/brands.controller.js b/src/modules/masters/brands/brands.controller.js deleted file mode 100644 index c5a4117..0000000 --- a/src/modules/masters/brands/brands.controller.js +++ /dev/null @@ -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 }; - diff --git a/src/modules/masters/brands/brands.routes.js b/src/modules/masters/brands/brands.routes.js deleted file mode 100644 index ff4cbab..0000000 --- a/src/modules/masters/brands/brands.routes.js +++ /dev/null @@ -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; diff --git a/src/modules/masters/brands/brands.service.js b/src/modules/masters/brands/brands.service.js deleted file mode 100644 index ce839a1..0000000 --- a/src/modules/masters/brands/brands.service.js +++ /dev/null @@ -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, -}; diff --git a/src/modules/masters/brands/brands.validation.js b/src/modules/masters/brands/brands.validation.js deleted file mode 100644 index a0aafc5..0000000 --- a/src/modules/masters/brands/brands.validation.js +++ /dev/null @@ -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 }; diff --git a/src/modules/masters/hsn-codes/hsn-codes.service.js b/src/modules/masters/hsn-codes/hsn-codes.service.js index 6054f03..7c8603e 100644 --- a/src/modules/masters/hsn-codes/hsn-codes.service.js +++ b/src/modules/masters/hsn-codes/hsn-codes.service.js @@ -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); diff --git a/src/modules/masters/hsn-codes/hsn-codes.validation.js b/src/modules/masters/hsn-codes/hsn-codes.validation.js index ff2b57c..fad0cb2 100644 --- a/src/modules/masters/hsn-codes/hsn-codes.validation.js +++ b/src/modules/masters/hsn-codes/hsn-codes.validation.js @@ -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); diff --git a/src/modules/masters/index.js b/src/modules/masters/index.js index 9c34935..62c4be9 100644 --- a/src/modules/masters/index.js +++ b/src/modules/masters/index.js @@ -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')); diff --git a/src/modules/masters/item-categories/item-categories.constants.js b/src/modules/masters/item-categories/item-categories.constants.js new file mode 100644 index 0000000..90c6229 --- /dev/null +++ b/src/modules/masters/item-categories/item-categories.constants.js @@ -0,0 +1,3 @@ +const CATEGORY_TYPES = ['STOCK', 'ASSET']; + +module.exports = { CATEGORY_TYPES }; diff --git a/src/modules/masters/item-categories/item-categories.service.js b/src/modules/masters/item-categories/item-categories.service.js index 56e4717..1225951 100644 --- a/src/modules/masters/item-categories/item-categories.service.js +++ b/src/modules/masters/item-categories/item-categories.service.js @@ -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', diff --git a/src/modules/masters/item-categories/item-categories.validation.js b/src/modules/masters/item-categories/item-categories.validation.js index b9e5005..228ec19 100644 --- a/src/modules/masters/item-categories/item-categories.validation.js +++ b/src/modules/masters/item-categories/item-categories.validation.js @@ -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, +}; diff --git a/src/modules/masters/items/items.service.js b/src/modules/masters/items/items.service.js index cc24075..5a04d94 100644 --- a/src/modules/masters/items/items.service.js +++ b/src/modules/masters/items/items.service.js @@ -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' }, diff --git a/src/modules/masters/items/items.validation.js b/src/modules/masters/items/items.validation.js index b5dbb6e..9c79bfe 100644 --- a/src/modules/masters/items/items.validation.js +++ b/src/modules/masters/items/items.validation.js @@ -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(), diff --git a/src/modules/purchase-orders/purchase-orders.constants.js b/src/modules/purchase-orders/purchase-orders.constants.js index 49cb6fd..9637017 100644 --- a/src/modules/purchase-orders/purchase-orders.constants.js +++ b/src/modules/purchase-orders/purchase-orders.constants.js @@ -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, diff --git a/src/modules/purchase-orders/purchase-orders.service.js b/src/modules/purchase-orders/purchase-orders.service.js index 6bef021..9654db1 100644 --- a/src/modules/purchase-orders/purchase-orders.service.js +++ b/src/modules/purchase-orders/purchase-orders.service.js @@ -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: diff --git a/src/modules/purchase-orders/purchase-orders.validation.js b/src/modules/purchase-orders/purchase-orders.validation.js index ceaf54a..e36311e 100644 --- a/src/modules/purchase-orders/purchase-orders.validation.js +++ b/src/modules/purchase-orders/purchase-orders.validation.js @@ -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(), diff --git a/src/utils/mapDbError.js b/src/utils/mapDbError.js index fe7c1b1..63deeb1 100644 --- a/src/utils/mapDbError.js +++ b/src/utils/mapDbError.js @@ -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; diff --git a/src/utils/pdf/templates/po.template.js b/src/utils/pdf/templates/po.template.js index 434b7ff..eb02ebf 100644 --- a/src/utils/pdf/templates/po.template.js +++ b/src/utils/pdf/templates/po.template.js @@ -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()) => {