Asset module maintanance module emabled
This commit is contained in:
parent
983505d6cf
commit
355f5a9f6d
@ -176,10 +176,14 @@ model assets {
|
|||||||
department_id BigInt?
|
department_id BigInt?
|
||||||
location_detail String? @db.VarChar(200)
|
location_detail String? @db.VarChar(200)
|
||||||
assigned_to_user_id BigInt?
|
assigned_to_user_id BigInt?
|
||||||
|
maintenance_incharge_user_id BigInt?
|
||||||
|
maintenance_frequency_in_days Int?
|
||||||
|
maintenance_checklist_json Json? @db.JsonB
|
||||||
vendor_id BigInt?
|
vendor_id BigInt?
|
||||||
po_id BigInt?
|
po_id BigInt?
|
||||||
grn_id BigInt?
|
grn_id BigInt?
|
||||||
grn_item_id BigInt?
|
grn_item_id BigInt?
|
||||||
|
commencement_date DateTime? @db.Date
|
||||||
purchase_date DateTime? @db.Date
|
purchase_date DateTime? @db.Date
|
||||||
purchase_cost Decimal @default(0) @db.Decimal(15, 4)
|
purchase_cost Decimal @default(0) @db.Decimal(15, 4)
|
||||||
useful_life_years Int?
|
useful_life_years Int?
|
||||||
@ -203,11 +207,13 @@ model assets {
|
|||||||
asset_amc_contracts asset_amc_contracts[]
|
asset_amc_contracts asset_amc_contracts[]
|
||||||
asset_attachments asset_attachments[]
|
asset_attachments asset_attachments[]
|
||||||
asset_insurance_policies asset_insurance_policies[]
|
asset_insurance_policies asset_insurance_policies[]
|
||||||
|
asset_maintenance_logs asset_maintenance_logs[]
|
||||||
asset_service_visits asset_service_visits[]
|
asset_service_visits asset_service_visits[]
|
||||||
asset_transfers asset_transfers[]
|
asset_transfers asset_transfers[]
|
||||||
item_categories item_categories @relation(fields: [item_category_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)
|
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_assigned_to_user_idTousers users? @relation("assets_assigned_to_user_idTousers", fields: [assigned_to_user_id], references: [id], onUpdate: NoAction)
|
||||||
|
users_assets_maintenance_incharge_idTousers users? @relation("assets_maintenance_incharge_idTousers", fields: [maintenance_incharge_user_id], references: [id], onUpdate: NoAction)
|
||||||
users_assets_created_byTousers users? @relation("assets_created_byTousers", fields: [created_by], 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)
|
departments departments? @relation(fields: [department_id], references: [id], onUpdate: NoAction)
|
||||||
grn grn? @relation(fields: [grn_id], references: [id], onUpdate: NoAction)
|
grn grn? @relation(fields: [grn_id], references: [id], onUpdate: NoAction)
|
||||||
@ -221,6 +227,28 @@ model assets {
|
|||||||
@@index([item_subcategory_id], map: "idx_assets_item_subcategory_id")
|
@@index([item_subcategory_id], map: "idx_assets_item_subcategory_id")
|
||||||
@@index([department_id], map: "idx_assets_dept_id")
|
@@index([department_id], map: "idx_assets_dept_id")
|
||||||
@@index([location_id], map: "idx_assets_location_id")
|
@@index([location_id], map: "idx_assets_location_id")
|
||||||
|
@@index([maintenance_incharge_user_id], map: "idx_assets_maintenance_incharge")
|
||||||
|
}
|
||||||
|
|
||||||
|
model asset_maintenance_logs {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
asset_id BigInt
|
||||||
|
performed_by BigInt?
|
||||||
|
performed_date DateTime @db.Date
|
||||||
|
next_due_date DateTime? @db.Date
|
||||||
|
checklist_json Json? @db.JsonB
|
||||||
|
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)
|
||||||
|
assets assets @relation(fields: [asset_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
||||||
|
users_asset_maintenance_logs_performed_byTousers users? @relation("asset_maintenance_logs_performed_byTousers", fields: [performed_by], references: [id], onUpdate: NoAction)
|
||||||
|
users_asset_maintenance_logs_created_byTousers users? @relation("asset_maintenance_logs_created_byTousers", fields: [created_by], references: [id], onUpdate: NoAction)
|
||||||
|
|
||||||
|
@@index([asset_id], map: "idx_asset_maintenance_logs_asset_id")
|
||||||
|
@@index([performed_by], map: "idx_asset_maintenance_logs_performed_by")
|
||||||
}
|
}
|
||||||
|
|
||||||
model audit_logs {
|
model audit_logs {
|
||||||
@ -466,6 +494,7 @@ model items {
|
|||||||
is_asset_item Boolean @default(false)
|
is_asset_item Boolean @default(false)
|
||||||
description String?
|
description String?
|
||||||
specification String?
|
specification String?
|
||||||
|
tags Json? @db.JsonB
|
||||||
min_order_qty Decimal? @db.Decimal(15, 4)
|
min_order_qty Decimal? @db.Decimal(15, 4)
|
||||||
reorder_level Decimal? @db.Decimal(15, 4)
|
reorder_level Decimal? @db.Decimal(15, 4)
|
||||||
is_active Boolean @default(true)
|
is_active Boolean @default(true)
|
||||||
@ -795,12 +824,15 @@ model users {
|
|||||||
asset_amc_contracts_asset_amc_contracts_updated_byTousers asset_amc_contracts[] @relation("asset_amc_contracts_updated_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_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_insurance_policies_asset_insurance_policies_updated_byTousers asset_insurance_policies[] @relation("asset_insurance_policies_updated_byTousers")
|
||||||
|
asset_maintenance_logs_asset_maintenance_logs_performed_byTousers asset_maintenance_logs[] @relation("asset_maintenance_logs_performed_byTousers")
|
||||||
|
asset_maintenance_logs_asset_maintenance_logs_created_byTousers asset_maintenance_logs[] @relation("asset_maintenance_logs_created_byTousers")
|
||||||
asset_service_visits_asset_service_visits_created_byTousers asset_service_visits[] @relation("asset_service_visits_created_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_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_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_to_user_idTousers asset_transfers[] @relation("asset_transfers_to_user_idTousers")
|
||||||
asset_transfers_asset_transfers_transferred_byTousers asset_transfers[] @relation("asset_transfers_transferred_byTousers")
|
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_assigned_to_user_idTousers assets[] @relation("assets_assigned_to_user_idTousers")
|
||||||
|
assets_assets_maintenance_incharge_idTousers assets[] @relation("assets_maintenance_incharge_idTousers")
|
||||||
assets_assets_created_byTousers assets[] @relation("assets_created_byTousers")
|
assets_assets_created_byTousers assets[] @relation("assets_created_byTousers")
|
||||||
assets_assets_updated_byTousers assets[] @relation("assets_updated_byTousers")
|
assets_assets_updated_byTousers assets[] @relation("assets_updated_byTousers")
|
||||||
audit_logs audit_logs[]
|
audit_logs audit_logs[]
|
||||||
|
|||||||
58
scripts/patch-items-tags-assets-maintenance.sql
Normal file
58
scripts/patch-items-tags-assets-maintenance.sql
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
-- Items tags + Assets maintenance / commencement fields.
|
||||||
|
-- Idempotent: safe to re-run.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 1. items.tags (JSONB array of strings)
|
||||||
|
ALTER TABLE items ADD COLUMN IF NOT EXISTS tags JSONB;
|
||||||
|
|
||||||
|
-- 2. assets maintenance + commencement columns
|
||||||
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS maintenance_incharge_user_id BIGINT;
|
||||||
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS maintenance_frequency_in_days INTEGER;
|
||||||
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS maintenance_checklist_json JSONB;
|
||||||
|
ALTER TABLE assets ADD COLUMN IF NOT EXISTS commencement_date DATE;
|
||||||
|
|
||||||
|
-- Backfill commencement_date from purchase_date for existing assets
|
||||||
|
UPDATE assets
|
||||||
|
SET commencement_date = purchase_date
|
||||||
|
WHERE commencement_date IS NULL
|
||||||
|
AND purchase_date IS NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE assets DROP CONSTRAINT IF EXISTS fk_assets_maintenance_incharge;
|
||||||
|
ALTER TABLE assets
|
||||||
|
ADD CONSTRAINT fk_assets_maintenance_incharge
|
||||||
|
FOREIGN KEY (maintenance_incharge_user_id) REFERENCES users(id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_assets_maintenance_incharge
|
||||||
|
ON assets(maintenance_incharge_user_id);
|
||||||
|
|
||||||
|
-- 3. asset_maintenance_logs table
|
||||||
|
CREATE TABLE IF NOT EXISTS asset_maintenance_logs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
asset_id BIGINT NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
|
||||||
|
performed_by BIGINT REFERENCES users(id),
|
||||||
|
performed_date DATE NOT NULL,
|
||||||
|
next_due_date DATE,
|
||||||
|
checklist_json JSONB,
|
||||||
|
remarks TEXT,
|
||||||
|
created_by BIGINT,
|
||||||
|
updated_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_asset_maintenance_logs_asset_id
|
||||||
|
ON asset_maintenance_logs(asset_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_asset_maintenance_logs_performed_by
|
||||||
|
ON asset_maintenance_logs(performed_by);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- Verify:
|
||||||
|
-- SELECT column_name FROM information_schema.columns
|
||||||
|
-- WHERE table_name='items' AND column_name='tags';
|
||||||
|
-- SELECT column_name FROM information_schema.columns
|
||||||
|
-- WHERE table_name='assets'
|
||||||
|
-- AND column_name IN ('maintenance_incharge_user_id','maintenance_frequency_in_days','maintenance_checklist_json','commencement_date');
|
||||||
|
-- SELECT to_regclass('public.asset_maintenance_logs');
|
||||||
52
src/constants/indianStates.js
Normal file
52
src/constants/indianStates.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Indian states / UTs — shared by vendor source_of_supply and location.state.
|
||||||
|
* Keep values identical so GST intra/inter-state comparisons stay consistent.
|
||||||
|
*/
|
||||||
|
const INDIAN_STATE_OPTIONS = [
|
||||||
|
{ value: 'Andhra Pradesh', label: 'Andhra Pradesh' },
|
||||||
|
{ value: 'Arunachal Pradesh', label: 'Arunachal Pradesh' },
|
||||||
|
{ value: 'Assam', label: 'Assam' },
|
||||||
|
{ value: 'Bihar', label: 'Bihar' },
|
||||||
|
{ value: 'Chhattisgarh', label: 'Chhattisgarh' },
|
||||||
|
{ value: 'Goa', label: 'Goa' },
|
||||||
|
{ value: 'Gujarat', label: 'Gujarat' },
|
||||||
|
{ value: 'Haryana', label: 'Haryana' },
|
||||||
|
{ value: 'Himachal Pradesh', label: 'Himachal Pradesh' },
|
||||||
|
{ value: 'Jharkhand', label: 'Jharkhand' },
|
||||||
|
{ value: 'Karnataka', label: 'Karnataka' },
|
||||||
|
{ value: 'Kerala', label: 'Kerala' },
|
||||||
|
{ value: 'Madhya Pradesh', label: 'Madhya Pradesh' },
|
||||||
|
{ value: 'Maharashtra', label: 'Maharashtra' },
|
||||||
|
{ value: 'Manipur', label: 'Manipur' },
|
||||||
|
{ value: 'Meghalaya', label: 'Meghalaya' },
|
||||||
|
{ value: 'Mizoram', label: 'Mizoram' },
|
||||||
|
{ value: 'Nagaland', label: 'Nagaland' },
|
||||||
|
{ value: 'Odisha', label: 'Odisha' },
|
||||||
|
{ value: 'Punjab', label: 'Punjab' },
|
||||||
|
{ value: 'Rajasthan', label: 'Rajasthan' },
|
||||||
|
{ value: 'Sikkim', label: 'Sikkim' },
|
||||||
|
{ value: 'Tamil Nadu', label: 'Tamil Nadu' },
|
||||||
|
{ value: 'Telangana', label: 'Telangana' },
|
||||||
|
{ value: 'Tripura', label: 'Tripura' },
|
||||||
|
{ value: 'Uttar Pradesh', label: 'Uttar Pradesh' },
|
||||||
|
{ value: 'Uttarakhand', label: 'Uttarakhand' },
|
||||||
|
{ value: 'West Bengal', label: 'West Bengal' },
|
||||||
|
{ value: 'Andaman and Nicobar Islands', label: 'Andaman and Nicobar Islands' },
|
||||||
|
{ value: 'Chandigarh', label: 'Chandigarh' },
|
||||||
|
{
|
||||||
|
value: 'Dadra and Nagar Haveli and Daman and Diu',
|
||||||
|
label: 'Dadra and Nagar Haveli and Daman and Diu',
|
||||||
|
},
|
||||||
|
{ value: 'Delhi', label: 'Delhi' },
|
||||||
|
{ value: 'Jammu and Kashmir', label: 'Jammu and Kashmir' },
|
||||||
|
{ value: 'Ladakh', label: 'Ladakh' },
|
||||||
|
{ value: 'Lakshadweep', label: 'Lakshadweep' },
|
||||||
|
{ value: 'Puducherry', label: 'Puducherry' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const INDIAN_STATE_VALUES = INDIAN_STATE_OPTIONS.map((item) => item.value);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
INDIAN_STATE_OPTIONS,
|
||||||
|
INDIAN_STATE_VALUES,
|
||||||
|
};
|
||||||
@ -18,10 +18,24 @@ components:
|
|||||||
department_id: { type: integer, nullable: true }
|
department_id: { type: integer, nullable: true }
|
||||||
location_detail: { type: string }
|
location_detail: { type: string }
|
||||||
assigned_to_user_id: { type: integer, nullable: true }
|
assigned_to_user_id: { type: integer, nullable: true }
|
||||||
|
maintenance_incharge_user_id: { type: integer, nullable: true, description: 'User responsible for periodic maintenance checklist' }
|
||||||
|
maintenance_frequency_in_days: { type: integer, nullable: true, example: 30, description: 'Days between maintenance checklists' }
|
||||||
|
maintenance_checklist_json:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
description: 'Checklist TEMPLATE stored on the asset'
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required: [key, label]
|
||||||
|
properties:
|
||||||
|
key: { type: string, example: 'oil_level' }
|
||||||
|
label: { type: string, example: 'Check oil level' }
|
||||||
|
required: { type: boolean, example: true }
|
||||||
vendor_id: { type: integer, nullable: true }
|
vendor_id: { type: integer, nullable: true }
|
||||||
po_id: { type: integer, nullable: true }
|
po_id: { type: integer, nullable: true }
|
||||||
grn_id: { type: integer, nullable: true }
|
grn_id: { type: integer, nullable: true }
|
||||||
grn_item_id: { type: integer, nullable: true }
|
grn_item_id: { type: integer, nullable: true }
|
||||||
|
commencement_date: { type: string, format: date, nullable: true, description: 'Asset usage start date — used for depreciation (falls back to purchase_date)' }
|
||||||
purchase_date: { type: string, format: date, nullable: true }
|
purchase_date: { type: string, format: date, nullable: true }
|
||||||
purchase_cost: { type: number, example: 850000 }
|
purchase_cost: { type: number, example: 850000 }
|
||||||
useful_life_years: { type: integer, example: 10 }
|
useful_life_years: { type: integer, example: 10 }
|
||||||
@ -52,10 +66,23 @@ components:
|
|||||||
department_id: { type: integer, nullable: true }
|
department_id: { type: integer, nullable: true }
|
||||||
location_detail: { type: string }
|
location_detail: { type: string }
|
||||||
assigned_to_user_id: { type: integer, nullable: true }
|
assigned_to_user_id: { type: integer, nullable: true }
|
||||||
|
maintenance_incharge_user_id: { type: integer, nullable: true }
|
||||||
|
maintenance_frequency_in_days: { type: integer, nullable: true }
|
||||||
|
maintenance_checklist_json:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required: [key, label]
|
||||||
|
properties:
|
||||||
|
key: { type: string }
|
||||||
|
label: { type: string }
|
||||||
|
required: { type: boolean }
|
||||||
vendor_id: { type: integer, nullable: true }
|
vendor_id: { type: integer, nullable: true }
|
||||||
po_id: { type: integer, nullable: true }
|
po_id: { type: integer, nullable: true }
|
||||||
grn_id: { type: integer, nullable: true }
|
grn_id: { type: integer, nullable: true }
|
||||||
grn_item_id: { type: integer, nullable: true }
|
grn_item_id: { type: integer, nullable: true }
|
||||||
|
commencement_date: { type: string, format: date, nullable: true }
|
||||||
purchase_date: { type: string, format: date, nullable: true }
|
purchase_date: { type: string, format: date, nullable: true }
|
||||||
purchase_cost: { type: number }
|
purchase_cost: { type: number }
|
||||||
useful_life_years: { type: integer }
|
useful_life_years: { type: integer }
|
||||||
@ -80,8 +107,27 @@ components:
|
|||||||
purchase_cost: { type: number, example: 850000 }
|
purchase_cost: { type: number, example: 850000 }
|
||||||
salvage_value: { type: number, example: 50000 }
|
salvage_value: { type: number, example: 50000 }
|
||||||
useful_life_years: { type: integer, example: 10 }
|
useful_life_years: { type: integer, example: 10 }
|
||||||
|
commencement_date: { type: string, format: date, description: 'Preferred depreciation start date' }
|
||||||
purchase_date: { type: string, format: date }
|
purchase_date: { type: string, format: date }
|
||||||
as_of_date: { type: string, format: date }
|
as_of_date: { type: string, format: date }
|
||||||
|
MaintenanceLogCreateBody:
|
||||||
|
type: object
|
||||||
|
required: [performed_date, checklist_json]
|
||||||
|
properties:
|
||||||
|
performed_date: { type: string, format: date }
|
||||||
|
next_due_date: { type: string, format: date, nullable: true, description: 'Optional override; defaults to performed_date + frequency' }
|
||||||
|
remarks: { type: string }
|
||||||
|
checklist_json:
|
||||||
|
type: array
|
||||||
|
minItems: 1
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required: [key, status]
|
||||||
|
properties:
|
||||||
|
key: { type: string, example: 'oil_level' }
|
||||||
|
label: { type: string, example: 'Check oil level' }
|
||||||
|
status: { type: string, enum: [OK, NOT_OK, NA] }
|
||||||
|
remarks: { type: string, nullable: true }
|
||||||
TransferAssetBody:
|
TransferAssetBody:
|
||||||
type: object
|
type: object
|
||||||
required: [transfer_date]
|
required: [transfer_date]
|
||||||
@ -420,6 +466,26 @@ paths:
|
|||||||
'200':
|
'200':
|
||||||
description: Service alerts fetched
|
description: Service alerts fetched
|
||||||
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
|
/assets/maintenance/my:
|
||||||
|
get:
|
||||||
|
tags: [Assets]
|
||||||
|
summary: List assets assigned to me as maintenance incharge
|
||||||
|
description: |
|
||||||
|
Returns assets where `maintenance_incharge_user_id` equals the logged-in user.
|
||||||
|
Use `due_only=true` to show only assets whose next checklist is due/overdue.
|
||||||
|
Each asset includes a `maintenance` summary (`is_due`, `next_due_date`, `checklist`, etc.).
|
||||||
|
parameters:
|
||||||
|
- { name: page, in: query, schema: { type: integer } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer } }
|
||||||
|
- { name: search, in: query, schema: { type: string } }
|
||||||
|
- { name: due_only, in: query, schema: { type: boolean } }
|
||||||
|
- { name: status, in: query, schema: { type: string } }
|
||||||
|
- { name: location_id, in: query, schema: { type: integer } }
|
||||||
|
- { name: is_active, in: query, schema: { type: boolean } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Maintenance assets fetched
|
||||||
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
/assets/export:
|
/assets/export:
|
||||||
get:
|
get:
|
||||||
tags: [Assets]
|
tags: [Assets]
|
||||||
@ -435,6 +501,8 @@ paths:
|
|||||||
- { name: item_subcategory_id, in: query, schema: { type: integer } }
|
- { name: item_subcategory_id, in: query, schema: { type: integer } }
|
||||||
- { name: location_id, in: query, schema: { type: integer } }
|
- { name: location_id, in: query, schema: { type: integer } }
|
||||||
- { name: department_id, in: query, schema: { type: integer } }
|
- { name: department_id, in: query, schema: { type: integer } }
|
||||||
|
- { name: maintenance_incharge_user_id, in: query, schema: { type: integer } }
|
||||||
|
- { name: due_only, in: query, schema: { type: boolean }, description: 'When true, only assets whose maintenance is due/overdue' }
|
||||||
- { name: is_active, in: query, schema: { type: boolean } }
|
- { name: is_active, in: query, schema: { type: boolean } }
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@ -456,6 +524,8 @@ paths:
|
|||||||
- { name: item_subcategory_id, in: query, schema: { type: integer } }
|
- { name: item_subcategory_id, in: query, schema: { type: integer } }
|
||||||
- { name: location_id, in: query, schema: { type: integer } }
|
- { name: location_id, in: query, schema: { type: integer } }
|
||||||
- { name: department_id, in: query, schema: { type: integer } }
|
- { name: department_id, in: query, schema: { type: integer } }
|
||||||
|
- { name: maintenance_incharge_user_id, in: query, schema: { type: integer } }
|
||||||
|
- { name: due_only, in: query, schema: { type: boolean } }
|
||||||
- { name: is_active, in: query, schema: { type: boolean } }
|
- { name: is_active, in: query, schema: { type: boolean } }
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@ -527,6 +597,58 @@ paths:
|
|||||||
'200':
|
'200':
|
||||||
description: Transfer history fetched
|
description: Transfer history fetched
|
||||||
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
|
/assets/{id}/maintenance-logs:
|
||||||
|
get:
|
||||||
|
tags: [Assets]
|
||||||
|
summary: List maintenance checklist logs for an asset
|
||||||
|
parameters:
|
||||||
|
- { name: id, in: path, required: true, schema: { type: string } }
|
||||||
|
- { name: page, in: query, schema: { type: integer } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Maintenance logs fetched
|
||||||
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
|
post:
|
||||||
|
tags: [Assets]
|
||||||
|
summary: Submit a maintenance checklist (incharge only)
|
||||||
|
description: |
|
||||||
|
Only the asset's `maintenance_incharge_user_id` may submit.
|
||||||
|
Required checklist keys from `maintenance_checklist_json` must be present with status OK/NOT_OK/NA.
|
||||||
|
`next_due_date` defaults to performed_date + maintenance_frequency_in_days.
|
||||||
|
parameters:
|
||||||
|
- { name: id, in: path, required: true, schema: { type: string } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/MaintenanceLogCreateBody' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Checklist submitted
|
||||||
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
|
'403': { description: Not the maintenance incharge }
|
||||||
|
/assets/{id}/maintenance-logs/{logId}:
|
||||||
|
get:
|
||||||
|
tags: [Assets]
|
||||||
|
summary: Get a maintenance log by id
|
||||||
|
parameters:
|
||||||
|
- { name: id, in: path, required: true, schema: { type: string } }
|
||||||
|
- { name: logId, in: path, required: true, schema: { type: string } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Maintenance log fetched
|
||||||
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
|
delete:
|
||||||
|
tags: [Assets]
|
||||||
|
summary: Soft-delete a maintenance log (incharge only)
|
||||||
|
parameters:
|
||||||
|
- { name: id, in: path, required: true, schema: { type: string } }
|
||||||
|
- { name: logId, in: path, required: true, schema: { type: string } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Maintenance log deleted
|
||||||
|
content: { application/json: { schema: { $ref: '#/components/schemas/ApiResponse' } } }
|
||||||
/assets/{assetId}/attachments:
|
/assets/{assetId}/attachments:
|
||||||
get:
|
get:
|
||||||
tags: [Assets]
|
tags: [Assets]
|
||||||
|
|||||||
@ -170,6 +170,11 @@ components:
|
|||||||
hsn_code_id: { type: integer, nullable: true, example: 1 }
|
hsn_code_id: { type: integer, nullable: true, example: 1 }
|
||||||
gst_rate_id: { type: integer, nullable: true, example: 4 }
|
gst_rate_id: { type: integer, nullable: true, example: 4 }
|
||||||
is_asset_item: { type: boolean, example: false }
|
is_asset_item: { type: boolean, example: false }
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items: { type: string, example: 'critical' }
|
||||||
|
description: 'Free-form text tags (max 50 tags, each max 50 chars)'
|
||||||
description: { type: string, example: 'Raw material' }
|
description: { type: string, example: 'Raw material' }
|
||||||
specification: { type: string, example: '96% purity' }
|
specification: { type: string, example: '96% purity' }
|
||||||
min_order_qty: { type: number, nullable: true, example: 100.0 }
|
min_order_qty: { type: number, nullable: true, example: 100.0 }
|
||||||
@ -186,6 +191,10 @@ components:
|
|||||||
hsn_code_id: { type: integer, nullable: true, example: 1 }
|
hsn_code_id: { type: integer, nullable: true, example: 1 }
|
||||||
gst_rate_id: { type: integer, nullable: true, example: 4 }
|
gst_rate_id: { type: integer, nullable: true, example: 4 }
|
||||||
is_asset_item: { type: boolean, example: false }
|
is_asset_item: { type: boolean, example: false }
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items: { type: string, example: 'critical' }
|
||||||
description: { type: string, example: 'Raw material' }
|
description: { type: string, example: 'Raw material' }
|
||||||
specification: { type: string, example: '96% purity' }
|
specification: { type: string, example: '96% purity' }
|
||||||
min_order_qty: { type: number, nullable: true, example: 100.0 }
|
min_order_qty: { type: number, nullable: true, example: 100.0 }
|
||||||
@ -1372,6 +1381,17 @@ paths:
|
|||||||
description: Deleted
|
description: Deleted
|
||||||
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
||||||
"404": { description: Not found }
|
"404": { description: Not found }
|
||||||
|
/masters/locations/states:
|
||||||
|
get:
|
||||||
|
tags: [Locations]
|
||||||
|
summary: List Indian state / UT options for location.state
|
||||||
|
description: |
|
||||||
|
Same values as `GET /vendors/source-of-supply`.
|
||||||
|
Use for plant/location state dropdowns so GST place-of-supply comparisons stay consistent.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: State options fetched
|
||||||
|
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
||||||
/masters/locations:
|
/masters/locations:
|
||||||
get:
|
get:
|
||||||
tags: [Locations]
|
tags: [Locations]
|
||||||
|
|||||||
299
src/modules/assets/assets-maintenance.service.js
Normal file
299
src/modules/assets/assets-maintenance.service.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
const prisma = require('../../config/prisma');
|
||||||
|
const ApiError = require('../../utils/ApiError');
|
||||||
|
const auditLog = require('../../utils/auditLog');
|
||||||
|
const { getPagination, isDropdownCall } = require('../../utils/pagination');
|
||||||
|
const { MAINTENANCE_CHECKLIST_STATUSES } = require('./assets.constants');
|
||||||
|
const { toDateOnly, getAssetOrThrow } = require('./assets.helpers');
|
||||||
|
const { sanitizeAsset } = require('./assets.service');
|
||||||
|
|
||||||
|
const logInclude = {
|
||||||
|
users_asset_maintenance_logs_performed_byTousers: {
|
||||||
|
select: { id: true, full_name: true, employee_code: true },
|
||||||
|
},
|
||||||
|
users_asset_maintenance_logs_created_byTousers: {
|
||||||
|
select: { id: true, full_name: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const assetListInclude = {
|
||||||
|
item_categories: { select: { id: true, code: true, name: true } },
|
||||||
|
item_subcategories: { select: { id: true, code: true, name: true } },
|
||||||
|
location: { select: { id: true, code: true, name: true, type: true } },
|
||||||
|
departments: { select: { id: true, name: true } },
|
||||||
|
users_assets_assigned_to_user_idTousers: {
|
||||||
|
select: { id: true, full_name: true, employee_code: true },
|
||||||
|
},
|
||||||
|
users_assets_maintenance_incharge_idTousers: {
|
||||||
|
select: { id: true, full_name: true, employee_code: true },
|
||||||
|
},
|
||||||
|
asset_maintenance_logs: {
|
||||||
|
where: { deleted_at: null },
|
||||||
|
orderBy: [{ performed_date: 'desc' }, { created_at: 'desc' }],
|
||||||
|
take: 1,
|
||||||
|
select: { id: true, performed_date: true, next_due_date: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeLog = (row) => {
|
||||||
|
if (!row) return null;
|
||||||
|
const {
|
||||||
|
users_asset_maintenance_logs_performed_byTousers,
|
||||||
|
users_asset_maintenance_logs_created_byTousers,
|
||||||
|
...rest
|
||||||
|
} = row;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
performed_by_user: users_asset_maintenance_logs_performed_byTousers || null,
|
||||||
|
created_by_user: users_asset_maintenance_logs_created_byTousers || null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const startOfUtcDay = (value) => {
|
||||||
|
const d = new Date(value);
|
||||||
|
if (Number.isNaN(d.getTime())) return null;
|
||||||
|
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDays = (value, days) => {
|
||||||
|
const base = startOfUtcDay(value);
|
||||||
|
if (!base || days === null || days === undefined) return null;
|
||||||
|
base.setUTCDate(base.getUTCDate() + Number(days));
|
||||||
|
return base;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeChecklistTemplate = (template) => {
|
||||||
|
if (template === null || template === undefined) return null;
|
||||||
|
if (!Array.isArray(template)) {
|
||||||
|
throw new ApiError(422, 'maintenance_checklist_json must be an array');
|
||||||
|
}
|
||||||
|
const keys = new Set();
|
||||||
|
return template.map((item, index) => {
|
||||||
|
const key = String(item.key || '').trim();
|
||||||
|
const label = String(item.label || '').trim();
|
||||||
|
if (!key) throw new ApiError(422, `Checklist item ${index + 1}: key is required`);
|
||||||
|
if (!label) throw new ApiError(422, `Checklist item ${index + 1}: label is required`);
|
||||||
|
if (keys.has(key)) throw new ApiError(422, `Duplicate checklist key: ${key}`);
|
||||||
|
keys.add(key);
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
required: item.required !== false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateChecklistResults = (template, results) => {
|
||||||
|
if (!Array.isArray(results) || !results.length) {
|
||||||
|
throw new ApiError(422, 'checklist_json is required and must contain at least one item');
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateItems = Array.isArray(template) ? template : [];
|
||||||
|
const templateMap = new Map(templateItems.map((item) => [item.key, item]));
|
||||||
|
const resultKeys = new Set();
|
||||||
|
const normalized = [];
|
||||||
|
|
||||||
|
for (const row of results) {
|
||||||
|
const key = String(row.key || '').trim();
|
||||||
|
if (!key) throw new ApiError(422, 'Each checklist result requires a key');
|
||||||
|
if (resultKeys.has(key)) throw new ApiError(422, `Duplicate checklist result key: ${key}`);
|
||||||
|
resultKeys.add(key);
|
||||||
|
|
||||||
|
if (!MAINTENANCE_CHECKLIST_STATUSES.includes(row.status)) {
|
||||||
|
throw new ApiError(422, `Invalid checklist status for ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateItem = templateMap.get(key);
|
||||||
|
normalized.push({
|
||||||
|
key,
|
||||||
|
label: row.label || templateItem?.label || key,
|
||||||
|
status: row.status,
|
||||||
|
remarks: row.remarks || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of templateItems) {
|
||||||
|
if (item.required !== false && !resultKeys.has(item.key)) {
|
||||||
|
throw new ApiError(422, `Required checklist item missing: ${item.key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
};
|
||||||
|
|
||||||
|
const assertInchargeOrThrow = (asset, userId) => {
|
||||||
|
if (!userId) throw new ApiError(403, 'Authentication required');
|
||||||
|
if (
|
||||||
|
!asset.maintenance_incharge_user_id ||
|
||||||
|
asset.maintenance_incharge_user_id.toString() !== BigInt(userId).toString()
|
||||||
|
) {
|
||||||
|
throw new ApiError(403, 'Only the maintenance incharge can perform this action');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assets assigned to the logged-in user as maintenance incharge.
|
||||||
|
* Optional due_only=true returns only assets whose next maintenance is due/overdue.
|
||||||
|
*/
|
||||||
|
const listMyMaintenanceAssets = async (query, userId) => {
|
||||||
|
if (!userId) throw new ApiError(403, 'Authentication required');
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
deleted_at: null,
|
||||||
|
maintenance_incharge_user_id: BigInt(userId),
|
||||||
|
...(query.status ? { status: query.status } : {}),
|
||||||
|
...(query.location_id ? { location_id: BigInt(query.location_id) } : {}),
|
||||||
|
...(query.is_active !== undefined ? { is_active: query.is_active } : { is_active: true }),
|
||||||
|
...(query.search
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ asset_code: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
{ asset_name: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
{ serial_number: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows = await prisma.assets.findMany({
|
||||||
|
where,
|
||||||
|
include: assetListInclude,
|
||||||
|
orderBy: { asset_code: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = rows.map(sanitizeAsset);
|
||||||
|
|
||||||
|
if (query.due_only === true) {
|
||||||
|
data = data.filter((row) => row.maintenance?.is_due === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDropdownCall(query)) {
|
||||||
|
return { data, meta: { page: 1, limit: data.length, total: data.length, dropdown: true } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
const total = data.length;
|
||||||
|
const paged = data.slice(skip, skip + limit);
|
||||||
|
return { data: paged, meta: { page, limit, total } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const listMaintenanceLogs = async (assetId, query) => {
|
||||||
|
await getAssetOrThrow(assetId);
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
const where = { asset_id: BigInt(assetId), deleted_at: null };
|
||||||
|
|
||||||
|
const [rows, total] = await Promise.all([
|
||||||
|
prisma.asset_maintenance_logs.findMany({
|
||||||
|
where,
|
||||||
|
include: logInclude,
|
||||||
|
orderBy: [{ performed_date: 'desc' }, { created_at: 'desc' }],
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
prisma.asset_maintenance_logs.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { data: rows.map(sanitizeLog), meta: { page, limit, total } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMaintenanceLog = async (assetId, logId) => {
|
||||||
|
await getAssetOrThrow(assetId);
|
||||||
|
const row = await prisma.asset_maintenance_logs.findFirst({
|
||||||
|
where: {
|
||||||
|
id: BigInt(logId),
|
||||||
|
asset_id: BigInt(assetId),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
include: logInclude,
|
||||||
|
});
|
||||||
|
if (!row) throw new ApiError(404, 'Maintenance log not found');
|
||||||
|
return sanitizeLog(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMaintenanceLog = async (assetId, payload, userId, requestId) => {
|
||||||
|
const asset = await getAssetOrThrow(assetId);
|
||||||
|
assertInchargeOrThrow(asset, userId);
|
||||||
|
|
||||||
|
if (!asset.maintenance_frequency_in_days) {
|
||||||
|
throw new ApiError(422, 'Asset has no maintenance_frequency_in_days configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = normalizeChecklistTemplate(asset.maintenance_checklist_json);
|
||||||
|
if (!template || !template.length) {
|
||||||
|
throw new ApiError(422, 'Asset has no maintenance_checklist_json configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
const checklist = validateChecklistResults(template, payload.checklist_json);
|
||||||
|
const performedDate = toDateOnly(payload.performed_date);
|
||||||
|
const nextDueDate = payload.next_due_date
|
||||||
|
? toDateOnly(payload.next_due_date)
|
||||||
|
: addDays(performedDate, asset.maintenance_frequency_in_days);
|
||||||
|
|
||||||
|
const created = await prisma.asset_maintenance_logs.create({
|
||||||
|
data: {
|
||||||
|
asset_id: BigInt(assetId),
|
||||||
|
performed_by: userId ? BigInt(userId) : null,
|
||||||
|
performed_date: performedDate,
|
||||||
|
next_due_date: nextDueDate,
|
||||||
|
checklist_json: checklist,
|
||||||
|
remarks: payload.remarks || null,
|
||||||
|
created_by: userId ? BigInt(userId) : null,
|
||||||
|
updated_by: userId ? BigInt(userId) : null,
|
||||||
|
},
|
||||||
|
include: logInclude,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sanitized = sanitizeLog(created);
|
||||||
|
await auditLog({
|
||||||
|
tableName: 'asset_maintenance_logs',
|
||||||
|
recordId: created.id,
|
||||||
|
action: 'CREATE',
|
||||||
|
oldValue: null,
|
||||||
|
newValue: sanitized,
|
||||||
|
userId,
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMaintenanceLog = async (assetId, logId, userId, requestId) => {
|
||||||
|
const asset = await getAssetOrThrow(assetId);
|
||||||
|
assertInchargeOrThrow(asset, userId);
|
||||||
|
|
||||||
|
const existing = await prisma.asset_maintenance_logs.findFirst({
|
||||||
|
where: {
|
||||||
|
id: BigInt(logId),
|
||||||
|
asset_id: BigInt(assetId),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
include: logInclude,
|
||||||
|
});
|
||||||
|
if (!existing) throw new ApiError(404, 'Maintenance log not found');
|
||||||
|
|
||||||
|
const deleted = await prisma.asset_maintenance_logs.update({
|
||||||
|
where: { id: existing.id },
|
||||||
|
data: {
|
||||||
|
deleted_at: new Date(),
|
||||||
|
updated_by: userId ? BigInt(userId) : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await auditLog({
|
||||||
|
tableName: 'asset_maintenance_logs',
|
||||||
|
recordId: logId,
|
||||||
|
action: 'DELETE',
|
||||||
|
oldValue: sanitizeLog(existing),
|
||||||
|
newValue: deleted,
|
||||||
|
userId,
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
listMyMaintenanceAssets,
|
||||||
|
listMaintenanceLogs,
|
||||||
|
getMaintenanceLog,
|
||||||
|
createMaintenanceLog,
|
||||||
|
deleteMaintenanceLog,
|
||||||
|
normalizeChecklistTemplate,
|
||||||
|
};
|
||||||
@ -63,6 +63,12 @@ const VISIT_CONDITION_AFTER_OPTIONS = toOptions([
|
|||||||
['NEEDS_REPLACEMENT', 'Needs Replacement'],
|
['NEEDS_REPLACEMENT', 'Needs Replacement'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const MAINTENANCE_CHECKLIST_STATUS_OPTIONS = toOptions([
|
||||||
|
['OK', 'OK'],
|
||||||
|
['NOT_OK', 'Not OK'],
|
||||||
|
['NA', 'Not Applicable'],
|
||||||
|
]);
|
||||||
|
|
||||||
const INSURANCE_POLICY_TYPE_OPTIONS = toOptions([
|
const INSURANCE_POLICY_TYPE_OPTIONS = toOptions([
|
||||||
['FIRE_AND_ALLIED', 'Fire and Allied'],
|
['FIRE_AND_ALLIED', 'Fire and Allied'],
|
||||||
['MACHINERY_BREAKDOWN', 'Machinery Breakdown'],
|
['MACHINERY_BREAKDOWN', 'Machinery Breakdown'],
|
||||||
@ -94,6 +100,7 @@ const VISIT_TYPES = VISIT_TYPE_OPTIONS.map((o) => o.value);
|
|||||||
const VISIT_STATUSES = VISIT_STATUS_OPTIONS.map((o) => o.value);
|
const VISIT_STATUSES = VISIT_STATUS_OPTIONS.map((o) => o.value);
|
||||||
const VISIT_CONDITION_AFTER = VISIT_CONDITION_AFTER_OPTIONS.map((o) => o.value);
|
const VISIT_CONDITION_AFTER = VISIT_CONDITION_AFTER_OPTIONS.map((o) => o.value);
|
||||||
const INSURANCE_POLICY_TYPES = INSURANCE_POLICY_TYPE_OPTIONS.map((o) => o.value);
|
const INSURANCE_POLICY_TYPES = INSURANCE_POLICY_TYPE_OPTIONS.map((o) => o.value);
|
||||||
|
const MAINTENANCE_CHECKLIST_STATUSES = MAINTENANCE_CHECKLIST_STATUS_OPTIONS.map((o) => o.value);
|
||||||
|
|
||||||
const ALERT_TYPES = ['AMC', 'INSURANCE', 'WARRANTY'];
|
const ALERT_TYPES = ['AMC', 'INSURANCE', 'WARRANTY'];
|
||||||
const ALERT_LEVELS = ['EXPIRED', 'CRITICAL', 'WARNING', 'INFO'];
|
const ALERT_LEVELS = ['EXPIRED', 'CRITICAL', 'WARNING', 'INFO'];
|
||||||
@ -109,6 +116,7 @@ const getAssetDropdownOptions = () => ({
|
|||||||
service_frequencies: SERVICE_FREQUENCY_OPTIONS,
|
service_frequencies: SERVICE_FREQUENCY_OPTIONS,
|
||||||
visit_statuses: VISIT_STATUS_OPTIONS,
|
visit_statuses: VISIT_STATUS_OPTIONS,
|
||||||
visit_conditions_after: VISIT_CONDITION_AFTER_OPTIONS,
|
visit_conditions_after: VISIT_CONDITION_AFTER_OPTIONS,
|
||||||
|
maintenance_checklist_statuses: MAINTENANCE_CHECKLIST_STATUS_OPTIONS,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -124,6 +132,7 @@ module.exports = {
|
|||||||
VISIT_STATUSES,
|
VISIT_STATUSES,
|
||||||
VISIT_CONDITION_AFTER,
|
VISIT_CONDITION_AFTER,
|
||||||
INSURANCE_POLICY_TYPES,
|
INSURANCE_POLICY_TYPES,
|
||||||
|
MAINTENANCE_CHECKLIST_STATUSES,
|
||||||
ALERT_TYPES,
|
ALERT_TYPES,
|
||||||
ALERT_LEVELS,
|
ALERT_LEVELS,
|
||||||
SERVICE_ALERT_STATUSES,
|
SERVICE_ALERT_STATUSES,
|
||||||
@ -136,5 +145,6 @@ module.exports = {
|
|||||||
VISIT_STATUS_OPTIONS,
|
VISIT_STATUS_OPTIONS,
|
||||||
VISIT_CONDITION_AFTER_OPTIONS,
|
VISIT_CONDITION_AFTER_OPTIONS,
|
||||||
INSURANCE_POLICY_TYPE_OPTIONS,
|
INSURANCE_POLICY_TYPE_OPTIONS,
|
||||||
|
MAINTENANCE_CHECKLIST_STATUS_OPTIONS,
|
||||||
getAssetDropdownOptions,
|
getAssetDropdownOptions,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const amcService = require('./assets-amc.service');
|
|||||||
const visitsService = require('./assets-service-visits.service');
|
const visitsService = require('./assets-service-visits.service');
|
||||||
const insuranceService = require('./assets-insurance.service');
|
const insuranceService = require('./assets-insurance.service');
|
||||||
const alertsService = require('./assets-alerts.service');
|
const alertsService = require('./assets-alerts.service');
|
||||||
|
const maintenanceService = require('./assets-maintenance.service');
|
||||||
const attachmentService = require('./assets.attachments.service');
|
const attachmentService = require('./assets.attachments.service');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
@ -280,6 +281,41 @@ const removeAttachment = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'Asset attachment deleted successfully'));
|
res.json(new ApiResponse(200, null, 'Asset attachment deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const listMyMaintenance = asyncHandler(async (req, res) => {
|
||||||
|
const result = await maintenanceService.listMyMaintenanceAssets(req.query, req.user?.id);
|
||||||
|
res.json(new ApiResponse(200, result.data, 'Maintenance assets fetched', result.meta));
|
||||||
|
});
|
||||||
|
|
||||||
|
const listMaintenanceLogs = asyncHandler(async (req, res) => {
|
||||||
|
const result = await maintenanceService.listMaintenanceLogs(req.params.id, req.query);
|
||||||
|
res.json(new ApiResponse(200, result.data, 'Maintenance logs fetched', result.meta));
|
||||||
|
});
|
||||||
|
|
||||||
|
const getMaintenanceLog = asyncHandler(async (req, res) => {
|
||||||
|
const data = await maintenanceService.getMaintenanceLog(req.params.id, req.params.logId);
|
||||||
|
res.json(new ApiResponse(200, data, 'Maintenance log fetched'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMaintenanceLog = asyncHandler(async (req, res) => {
|
||||||
|
const data = await maintenanceService.createMaintenanceLog(
|
||||||
|
req.params.id,
|
||||||
|
req.body,
|
||||||
|
req.user?.id,
|
||||||
|
req.id
|
||||||
|
);
|
||||||
|
res.status(201).json(new ApiResponse(201, data, 'Maintenance checklist submitted successfully'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeMaintenanceLog = asyncHandler(async (req, res) => {
|
||||||
|
await maintenanceService.deleteMaintenanceLog(
|
||||||
|
req.params.id,
|
||||||
|
req.params.logId,
|
||||||
|
req.user?.id,
|
||||||
|
req.id
|
||||||
|
);
|
||||||
|
res.json(new ApiResponse(200, null, 'Maintenance log deleted successfully'));
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
create,
|
create,
|
||||||
list,
|
list,
|
||||||
@ -323,4 +359,9 @@ module.exports = {
|
|||||||
getAttachment,
|
getAttachment,
|
||||||
downloadAttachment,
|
downloadAttachment,
|
||||||
removeAttachment,
|
removeAttachment,
|
||||||
|
listMyMaintenance,
|
||||||
|
listMaintenanceLogs,
|
||||||
|
getMaintenanceLog,
|
||||||
|
createMaintenanceLog,
|
||||||
|
removeMaintenanceLog,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -66,6 +66,7 @@ const calculateDepreciation = ({
|
|||||||
purchase_cost: cost,
|
purchase_cost: cost,
|
||||||
salvage_value: salvage,
|
salvage_value: salvage,
|
||||||
useful_life_years: lifeYears,
|
useful_life_years: lifeYears,
|
||||||
|
commencement_date: commencementDate,
|
||||||
purchase_date: purchaseDate,
|
purchase_date: purchaseDate,
|
||||||
as_of_date: asOfDate,
|
as_of_date: asOfDate,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
@ -79,7 +80,10 @@ const calculateDepreciation = ({
|
|||||||
useful_life_years: lifeYears,
|
useful_life_years: lifeYears,
|
||||||
});
|
});
|
||||||
|
|
||||||
const elapsed = yearsElapsed(purchaseDate, asOfDate || new Date());
|
// Depreciation starts from the asset's usage start date (commencement_date).
|
||||||
|
// Fall back to purchase_date for assets created before this field existed.
|
||||||
|
const depreciationStartDate = commencementDate || purchaseDate;
|
||||||
|
const elapsed = yearsElapsed(depreciationStartDate, asOfDate || new Date());
|
||||||
const cappedYears =
|
const cappedYears =
|
||||||
lifeYears && Number(lifeYears) > 0 ? Math.min(elapsed, Number(lifeYears)) : elapsed;
|
lifeYears && Number(lifeYears) > 0 ? Math.min(elapsed, Number(lifeYears)) : elapsed;
|
||||||
|
|
||||||
@ -129,6 +133,7 @@ const calculateDepreciation = ({
|
|||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
useful_life_years: lifeYears ?? null,
|
useful_life_years: lifeYears ?? null,
|
||||||
|
depreciation_start_date: depreciationStartDate || null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,9 @@ const {
|
|||||||
depreciationCalculateSchema,
|
depreciationCalculateSchema,
|
||||||
expiryAlertsQuerySchema,
|
expiryAlertsQuerySchema,
|
||||||
serviceAlertsQuerySchema,
|
serviceAlertsQuerySchema,
|
||||||
|
myMaintenanceAssetsQuerySchema,
|
||||||
|
createMaintenanceLogSchema,
|
||||||
|
listMaintenanceLogsQuerySchema,
|
||||||
} = require('./assets.validation');
|
} = require('./assets.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -35,6 +38,12 @@ router.get(
|
|||||||
validate(exportAssetsQuerySchema, 'query'),
|
validate(exportAssetsQuerySchema, 'query'),
|
||||||
controller.exportCsv
|
controller.exportCsv
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/maintenance/my',
|
||||||
|
authorize('ASSET', 'view'),
|
||||||
|
validate(myMaintenanceAssetsQuerySchema, 'query'),
|
||||||
|
controller.listMyMaintenance
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
authorize('ASSET', 'view'),
|
authorize('ASSET', 'view'),
|
||||||
@ -182,6 +191,29 @@ router.post(
|
|||||||
controller.transfer
|
controller.transfer
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:id/maintenance-logs',
|
||||||
|
authorize('ASSET', 'view'),
|
||||||
|
validate(listMaintenanceLogsQuerySchema, 'query'),
|
||||||
|
controller.listMaintenanceLogs
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/:id/maintenance-logs',
|
||||||
|
authorize('ASSET', 'edit'),
|
||||||
|
validate(createMaintenanceLogSchema),
|
||||||
|
controller.createMaintenanceLog
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/:id/maintenance-logs/:logId',
|
||||||
|
authorize('ASSET', 'view'),
|
||||||
|
controller.getMaintenanceLog
|
||||||
|
);
|
||||||
|
router.delete(
|
||||||
|
'/:id/maintenance-logs/:logId',
|
||||||
|
authorize('ASSET', 'edit'),
|
||||||
|
controller.removeMaintenanceLog
|
||||||
|
);
|
||||||
|
|
||||||
router.get('/:id', authorize('ASSET', 'view'), controller.getOne);
|
router.get('/:id', authorize('ASSET', 'view'), controller.getOne);
|
||||||
router.put('/:id', authorize('ASSET', 'edit'), validate(updateAssetSchema), controller.update);
|
router.put('/:id', authorize('ASSET', 'edit'), validate(updateAssetSchema), controller.update);
|
||||||
router.delete('/:id', authorize('ASSET', 'delete'), controller.remove);
|
router.delete('/:id', authorize('ASSET', 'delete'), controller.remove);
|
||||||
|
|||||||
@ -32,6 +32,15 @@ const assetInclude = {
|
|||||||
users_assets_assigned_to_user_idTousers: {
|
users_assets_assigned_to_user_idTousers: {
|
||||||
select: { id: true, full_name: true, employee_code: true },
|
select: { id: true, full_name: true, employee_code: true },
|
||||||
},
|
},
|
||||||
|
users_assets_maintenance_incharge_idTousers: {
|
||||||
|
select: { id: true, full_name: true, employee_code: true },
|
||||||
|
},
|
||||||
|
asset_maintenance_logs: {
|
||||||
|
where: { deleted_at: null },
|
||||||
|
orderBy: [{ performed_date: 'desc' }, { created_at: 'desc' }],
|
||||||
|
take: 1,
|
||||||
|
select: { id: true, performed_date: true, next_due_date: true },
|
||||||
|
},
|
||||||
vendors_assets_vendor_idTovendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
vendors_assets_vendor_idTovendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
||||||
purchase_orders: { select: { id: true, po_number: true } },
|
purchase_orders: { select: { id: true, po_number: true } },
|
||||||
grn: { select: { id: true, grn_number: true } },
|
grn: { select: { id: true, grn_number: true } },
|
||||||
@ -61,6 +70,52 @@ const transferInclude = {
|
|||||||
|
|
||||||
const assetSeriesCode = (categoryCode) => `ASSET_${categoryCode}`;
|
const assetSeriesCode = (categoryCode) => `ASSET_${categoryCode}`;
|
||||||
|
|
||||||
|
const startOfUtcDay = (value) => {
|
||||||
|
const d = new Date(value);
|
||||||
|
if (Number.isNaN(d.getTime())) return null;
|
||||||
|
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDays = (value, days) => {
|
||||||
|
const base = startOfUtcDay(value);
|
||||||
|
if (!base || days === null || days === undefined) return null;
|
||||||
|
base.setUTCDate(base.getUTCDate() + Number(days));
|
||||||
|
return base;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Derive maintenance due state from the configured frequency, the latest log and
|
||||||
|
// the asset's usage start date (commencement_date, falling back to purchase_date).
|
||||||
|
const buildMaintenanceSummary = (rest, logs) => {
|
||||||
|
const frequency =
|
||||||
|
rest.maintenance_frequency_in_days !== null && rest.maintenance_frequency_in_days !== undefined
|
||||||
|
? Number(rest.maintenance_frequency_in_days)
|
||||||
|
: null;
|
||||||
|
const lastLog = Array.isArray(logs) && logs.length ? logs[0] : null;
|
||||||
|
const lastMaintenanceDate = lastLog?.performed_date ?? null;
|
||||||
|
const baseline = lastMaintenanceDate || rest.commencement_date || rest.purchase_date || null;
|
||||||
|
|
||||||
|
let nextDueDate = lastLog?.next_due_date ?? null;
|
||||||
|
if (!nextDueDate && frequency && baseline) {
|
||||||
|
nextDueDate = addDays(baseline, frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = startOfUtcDay(new Date());
|
||||||
|
const isDue = Boolean(frequency && nextDueDate && new Date(nextDueDate) <= today);
|
||||||
|
const daysUntilDue =
|
||||||
|
nextDueDate && today
|
||||||
|
? Math.round((new Date(nextDueDate).getTime() - today.getTime()) / 86400000)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
frequency_in_days: frequency,
|
||||||
|
checklist: rest.maintenance_checklist_json ?? null,
|
||||||
|
last_maintenance_date: lastMaintenanceDate,
|
||||||
|
next_due_date: nextDueDate,
|
||||||
|
is_due: isDue,
|
||||||
|
days_until_due: daysUntilDue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const sanitizeAsset = (asset) => {
|
const sanitizeAsset = (asset) => {
|
||||||
if (!asset) return null;
|
if (!asset) return null;
|
||||||
const {
|
const {
|
||||||
@ -69,6 +124,8 @@ const sanitizeAsset = (asset) => {
|
|||||||
location,
|
location,
|
||||||
departments,
|
departments,
|
||||||
users_assets_assigned_to_user_idTousers,
|
users_assets_assigned_to_user_idTousers,
|
||||||
|
users_assets_maintenance_incharge_idTousers,
|
||||||
|
asset_maintenance_logs,
|
||||||
vendors_assets_vendor_idTovendors,
|
vendors_assets_vendor_idTovendors,
|
||||||
purchase_orders,
|
purchase_orders,
|
||||||
grn,
|
grn,
|
||||||
@ -101,6 +158,8 @@ const sanitizeAsset = (asset) => {
|
|||||||
location: location || null,
|
location: location || null,
|
||||||
department: departments || null,
|
department: departments || null,
|
||||||
assigned_to_user: users_assets_assigned_to_user_idTousers || null,
|
assigned_to_user: users_assets_assigned_to_user_idTousers || null,
|
||||||
|
maintenance_incharge_user: users_assets_maintenance_incharge_idTousers || null,
|
||||||
|
maintenance: buildMaintenanceSummary(rest, asset_maintenance_logs),
|
||||||
vendor: vendors_assets_vendor_idTovendors || null,
|
vendor: vendors_assets_vendor_idTovendors || null,
|
||||||
purchase_order: purchase_orders || null,
|
purchase_order: purchase_orders || null,
|
||||||
grn: grn || null,
|
grn: grn || null,
|
||||||
@ -113,12 +172,15 @@ const sanitizeAsset = (asset) => {
|
|||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
useful_life_years: rest.useful_life_years,
|
useful_life_years: rest.useful_life_years,
|
||||||
|
commencement_date: rest.commencement_date,
|
||||||
purchase_date: rest.purchase_date,
|
purchase_date: rest.purchase_date,
|
||||||
}),
|
}),
|
||||||
item_categories: undefined,
|
item_categories: undefined,
|
||||||
item_subcategories: undefined,
|
item_subcategories: undefined,
|
||||||
departments: undefined,
|
departments: undefined,
|
||||||
users_assets_assigned_to_user_idTousers: undefined,
|
users_assets_assigned_to_user_idTousers: undefined,
|
||||||
|
users_assets_maintenance_incharge_idTousers: undefined,
|
||||||
|
asset_maintenance_logs: undefined,
|
||||||
vendors_assets_vendor_idTovendors: undefined,
|
vendors_assets_vendor_idTovendors: undefined,
|
||||||
purchase_orders: undefined,
|
purchase_orders: undefined,
|
||||||
users_assets_created_byTousers: undefined,
|
users_assets_created_byTousers: undefined,
|
||||||
@ -187,6 +249,33 @@ const normalizeAssetPayload = async (payload, { isCreate = false } = {}) => {
|
|||||||
await assertReference('departments', payload.department_id, 'department_id');
|
await assertReference('departments', payload.department_id, 'department_id');
|
||||||
if (payload.assigned_to_user_id)
|
if (payload.assigned_to_user_id)
|
||||||
await assertReference('users', payload.assigned_to_user_id, 'assigned_to_user_id');
|
await assertReference('users', payload.assigned_to_user_id, 'assigned_to_user_id');
|
||||||
|
if (payload.maintenance_incharge_user_id)
|
||||||
|
await assertReference(
|
||||||
|
'users',
|
||||||
|
payload.maintenance_incharge_user_id,
|
||||||
|
'maintenance_incharge_user_id'
|
||||||
|
);
|
||||||
|
|
||||||
|
let checklistJson = null;
|
||||||
|
if (payload.maintenance_checklist_json !== undefined) {
|
||||||
|
if (payload.maintenance_checklist_json === null) {
|
||||||
|
checklistJson = null;
|
||||||
|
} else if (!Array.isArray(payload.maintenance_checklist_json)) {
|
||||||
|
throw new ApiError(422, 'maintenance_checklist_json must be an array');
|
||||||
|
} else {
|
||||||
|
const keys = new Set();
|
||||||
|
checklistJson = payload.maintenance_checklist_json.map((item, index) => {
|
||||||
|
const key = String(item.key || '').trim();
|
||||||
|
const label = String(item.label || '').trim();
|
||||||
|
if (!key) throw new ApiError(422, `Checklist item ${index + 1}: key is required`);
|
||||||
|
if (!label) throw new ApiError(422, `Checklist item ${index + 1}: label is required`);
|
||||||
|
if (keys.has(key)) throw new ApiError(422, `Duplicate checklist key: ${key}`);
|
||||||
|
keys.add(key);
|
||||||
|
return { key, label, required: item.required !== false };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (payload.vendor_id) await assertReference('vendors', payload.vendor_id, 'vendor_id');
|
if (payload.vendor_id) await assertReference('vendors', payload.vendor_id, 'vendor_id');
|
||||||
if (payload.po_id) await assertReference('purchase_orders', payload.po_id, 'po_id');
|
if (payload.po_id) await assertReference('purchase_orders', payload.po_id, 'po_id');
|
||||||
if (payload.grn_id) await assertReference('grn', payload.grn_id, 'grn_id');
|
if (payload.grn_id) await assertReference('grn', payload.grn_id, 'grn_id');
|
||||||
@ -234,10 +323,22 @@ const normalizeAssetPayload = async (payload, { isCreate = false } = {}) => {
|
|||||||
department_id: payload.department_id ? BigInt(payload.department_id) : null,
|
department_id: payload.department_id ? BigInt(payload.department_id) : null,
|
||||||
location_detail: payload.location_detail || null,
|
location_detail: payload.location_detail || null,
|
||||||
assigned_to_user_id: payload.assigned_to_user_id ? BigInt(payload.assigned_to_user_id) : null,
|
assigned_to_user_id: payload.assigned_to_user_id ? BigInt(payload.assigned_to_user_id) : null,
|
||||||
|
maintenance_incharge_user_id: payload.maintenance_incharge_user_id
|
||||||
|
? BigInt(payload.maintenance_incharge_user_id)
|
||||||
|
: null,
|
||||||
|
maintenance_frequency_in_days:
|
||||||
|
payload.maintenance_frequency_in_days !== undefined &&
|
||||||
|
payload.maintenance_frequency_in_days !== null &&
|
||||||
|
payload.maintenance_frequency_in_days !== ''
|
||||||
|
? Number(payload.maintenance_frequency_in_days)
|
||||||
|
: null,
|
||||||
|
maintenance_checklist_json:
|
||||||
|
payload.maintenance_checklist_json !== undefined ? checklistJson : null,
|
||||||
vendor_id: payload.vendor_id ? BigInt(payload.vendor_id) : null,
|
vendor_id: payload.vendor_id ? BigInt(payload.vendor_id) : null,
|
||||||
po_id: payload.po_id ? BigInt(payload.po_id) : null,
|
po_id: payload.po_id ? BigInt(payload.po_id) : null,
|
||||||
grn_id: payload.grn_id ? BigInt(payload.grn_id) : null,
|
grn_id: payload.grn_id ? BigInt(payload.grn_id) : null,
|
||||||
grn_item_id: payload.grn_item_id ? BigInt(payload.grn_item_id) : null,
|
grn_item_id: payload.grn_item_id ? BigInt(payload.grn_item_id) : null,
|
||||||
|
commencement_date: payload.commencement_date ? toDateOnly(payload.commencement_date) : null,
|
||||||
purchase_date: payload.purchase_date ? toDateOnly(payload.purchase_date) : null,
|
purchase_date: payload.purchase_date ? toDateOnly(payload.purchase_date) : null,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
useful_life_years: usefulLifeYears,
|
useful_life_years: usefulLifeYears,
|
||||||
@ -307,6 +408,9 @@ const buildAssetsWhere = (query) => ({
|
|||||||
: {}),
|
: {}),
|
||||||
...(query.location_id ? { location_id: BigInt(query.location_id) } : {}),
|
...(query.location_id ? { location_id: BigInt(query.location_id) } : {}),
|
||||||
...(query.department_id ? { department_id: BigInt(query.department_id) } : {}),
|
...(query.department_id ? { department_id: BigInt(query.department_id) } : {}),
|
||||||
|
...(query.maintenance_incharge_user_id
|
||||||
|
? { maintenance_incharge_user_id: BigInt(query.maintenance_incharge_user_id) }
|
||||||
|
: {}),
|
||||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||||
...(query.search
|
...(query.search
|
||||||
? {
|
? {
|
||||||
@ -330,10 +434,27 @@ const listAssets = async (query) => {
|
|||||||
include: assetInclude,
|
include: assetInclude,
|
||||||
orderBy: { created_at: 'desc' },
|
orderBy: { created_at: 'desc' },
|
||||||
});
|
});
|
||||||
const data = rows.map(sanitizeAsset);
|
let data = rows.map(sanitizeAsset);
|
||||||
|
if (query.due_only === true) {
|
||||||
|
data = data.filter((row) => row.maintenance?.is_due === true);
|
||||||
|
}
|
||||||
return { data, meta: { page: 1, limit: data.length, total: data.length, dropdown: true } };
|
return { data, meta: { page: 1, limit: data.length, total: data.length, dropdown: true } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.due_only === true) {
|
||||||
|
const rows = await prisma.assets.findMany({
|
||||||
|
where,
|
||||||
|
include: assetInclude,
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
});
|
||||||
|
const due = rows.map(sanitizeAsset).filter((row) => row.maintenance?.is_due === true);
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
return {
|
||||||
|
data: due.slice(skip, skip + limit),
|
||||||
|
meta: { page, limit, total: due.length },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const { page, limit, skip } = getPagination(query);
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
|
||||||
const [rows, total] = await Promise.all([
|
const [rows, total] = await Promise.all([
|
||||||
@ -372,9 +493,16 @@ const exportAssets = async (query) => {
|
|||||||
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor' },
|
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor' },
|
||||||
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
|
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
|
||||||
{ key: (row) => row.grn?.grn_number || '', header: 'GRN Number' },
|
{ key: (row) => row.grn?.grn_number || '', header: 'GRN Number' },
|
||||||
|
{ key: 'commencement_date', header: 'Commencement Date', type: 'date' },
|
||||||
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
||||||
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
||||||
{ key: 'warranty_expiry_date', header: 'Warranty Expiry', type: 'date' },
|
{ key: 'warranty_expiry_date', header: 'Warranty Expiry', type: 'date' },
|
||||||
|
{
|
||||||
|
key: (row) => row.maintenance_incharge_user?.full_name || '',
|
||||||
|
header: 'Maintenance Incharge',
|
||||||
|
},
|
||||||
|
{ key: (row) => row.maintenance?.frequency_in_days ?? '', header: 'Maintenance Freq (days)' },
|
||||||
|
{ key: (row) => row.maintenance?.next_due_date || '', header: 'Maintenance Next Due', type: 'date' },
|
||||||
{ key: 'is_active', header: 'Active' },
|
{ key: 'is_active', header: 'Active' },
|
||||||
{ key: 'created_at', header: 'Created At', type: 'datetime' },
|
{ key: 'created_at', header: 'Created At', type: 'datetime' },
|
||||||
],
|
],
|
||||||
@ -407,10 +535,26 @@ const updateAsset = async (id, payload, userId, requestId) => {
|
|||||||
payload.assigned_to_user_id !== undefined
|
payload.assigned_to_user_id !== undefined
|
||||||
? payload.assigned_to_user_id
|
? payload.assigned_to_user_id
|
||||||
: existing.assigned_to_user_id,
|
: existing.assigned_to_user_id,
|
||||||
|
maintenance_incharge_user_id:
|
||||||
|
payload.maintenance_incharge_user_id !== undefined
|
||||||
|
? payload.maintenance_incharge_user_id
|
||||||
|
: existing.maintenance_incharge_user_id,
|
||||||
|
maintenance_frequency_in_days:
|
||||||
|
payload.maintenance_frequency_in_days !== undefined
|
||||||
|
? payload.maintenance_frequency_in_days
|
||||||
|
: existing.maintenance_frequency_in_days,
|
||||||
|
maintenance_checklist_json:
|
||||||
|
payload.maintenance_checklist_json !== undefined
|
||||||
|
? payload.maintenance_checklist_json
|
||||||
|
: existing.maintenance_checklist_json,
|
||||||
vendor_id: payload.vendor_id !== undefined ? payload.vendor_id : existing.vendor_id,
|
vendor_id: payload.vendor_id !== undefined ? payload.vendor_id : existing.vendor_id,
|
||||||
po_id: payload.po_id !== undefined ? payload.po_id : existing.po_id,
|
po_id: payload.po_id !== undefined ? payload.po_id : existing.po_id,
|
||||||
grn_id: payload.grn_id !== undefined ? payload.grn_id : existing.grn_id,
|
grn_id: payload.grn_id !== undefined ? payload.grn_id : existing.grn_id,
|
||||||
grn_item_id: payload.grn_item_id !== undefined ? payload.grn_item_id : existing.grn_item_id,
|
grn_item_id: payload.grn_item_id !== undefined ? payload.grn_item_id : existing.grn_item_id,
|
||||||
|
commencement_date:
|
||||||
|
payload.commencement_date !== undefined
|
||||||
|
? payload.commencement_date
|
||||||
|
: existing.commencement_date,
|
||||||
purchase_date:
|
purchase_date:
|
||||||
payload.purchase_date !== undefined ? payload.purchase_date : existing.purchase_date,
|
payload.purchase_date !== undefined ? payload.purchase_date : existing.purchase_date,
|
||||||
purchase_cost: payload.purchase_cost ?? Number(existing.purchase_cost),
|
purchase_cost: payload.purchase_cost ?? Number(existing.purchase_cost),
|
||||||
@ -613,4 +757,5 @@ module.exports = {
|
|||||||
listVisitConditionsAfter,
|
listVisitConditionsAfter,
|
||||||
listAssetOptions,
|
listAssetOptions,
|
||||||
previewDepreciation,
|
previewDepreciation,
|
||||||
|
sanitizeAsset,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,10 +10,26 @@ const {
|
|||||||
VISIT_STATUSES,
|
VISIT_STATUSES,
|
||||||
VISIT_CONDITION_AFTER,
|
VISIT_CONDITION_AFTER,
|
||||||
INSURANCE_POLICY_TYPES,
|
INSURANCE_POLICY_TYPES,
|
||||||
|
MAINTENANCE_CHECKLIST_STATUSES,
|
||||||
ALERT_TYPES,
|
ALERT_TYPES,
|
||||||
SERVICE_ALERT_STATUSES,
|
SERVICE_ALERT_STATUSES,
|
||||||
} = require('./assets.constants');
|
} = require('./assets.constants');
|
||||||
|
|
||||||
|
const checklistTemplateItemSchema = Joi.object({
|
||||||
|
key: Joi.string().trim().max(50).required(),
|
||||||
|
label: Joi.string().trim().max(200).required(),
|
||||||
|
required: Joi.boolean().default(true),
|
||||||
|
}).unknown(true);
|
||||||
|
|
||||||
|
const checklistResultItemSchema = Joi.object({
|
||||||
|
key: Joi.string().trim().max(50).required(),
|
||||||
|
label: Joi.string().trim().max(200).optional(),
|
||||||
|
status: Joi.string()
|
||||||
|
.valid(...MAINTENANCE_CHECKLIST_STATUSES)
|
||||||
|
.required(),
|
||||||
|
remarks: Joi.string().allow(null, '').max(500).optional(),
|
||||||
|
}).unknown(true);
|
||||||
|
|
||||||
const assetFields = {
|
const assetFields = {
|
||||||
asset_name: Joi.string().max(200).required(),
|
asset_name: Joi.string().max(200).required(),
|
||||||
item_category_id: Joi.number().integer().positive().required(),
|
item_category_id: Joi.number().integer().positive().required(),
|
||||||
@ -26,10 +42,18 @@ const assetFields = {
|
|||||||
department_id: Joi.number().integer().positive().allow(null).optional(),
|
department_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
location_detail: Joi.string().max(200).allow(null, '').optional(),
|
location_detail: Joi.string().max(200).allow(null, '').optional(),
|
||||||
assigned_to_user_id: Joi.number().integer().positive().allow(null).optional(),
|
assigned_to_user_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
|
maintenance_incharge_user_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
|
maintenance_frequency_in_days: Joi.number().integer().min(1).max(3650).allow(null).optional(),
|
||||||
|
maintenance_checklist_json: Joi.array()
|
||||||
|
.items(checklistTemplateItemSchema)
|
||||||
|
.max(100)
|
||||||
|
.allow(null)
|
||||||
|
.optional(),
|
||||||
vendor_id: Joi.number().integer().positive().allow(null).optional(),
|
vendor_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
po_id: Joi.number().integer().positive().allow(null).optional(),
|
po_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
grn_id: Joi.number().integer().positive().allow(null).optional(),
|
grn_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
grn_item_id: Joi.number().integer().positive().allow(null).optional(),
|
grn_item_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
|
commencement_date: Joi.date().iso().allow(null).optional(),
|
||||||
purchase_date: Joi.date().iso().allow(null).optional(),
|
purchase_date: Joi.date().iso().allow(null).optional(),
|
||||||
purchase_cost: Joi.number().min(0).default(0),
|
purchase_cost: Joi.number().min(0).default(0),
|
||||||
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
@ -68,10 +92,14 @@ const updateAssetSchema = Joi.object({
|
|||||||
department_id: assetFields.department_id,
|
department_id: assetFields.department_id,
|
||||||
location_detail: assetFields.location_detail,
|
location_detail: assetFields.location_detail,
|
||||||
assigned_to_user_id: assetFields.assigned_to_user_id,
|
assigned_to_user_id: assetFields.assigned_to_user_id,
|
||||||
|
maintenance_incharge_user_id: assetFields.maintenance_incharge_user_id,
|
||||||
|
maintenance_frequency_in_days: assetFields.maintenance_frequency_in_days,
|
||||||
|
maintenance_checklist_json: assetFields.maintenance_checklist_json,
|
||||||
vendor_id: assetFields.vendor_id,
|
vendor_id: assetFields.vendor_id,
|
||||||
po_id: assetFields.po_id,
|
po_id: assetFields.po_id,
|
||||||
grn_id: assetFields.grn_id,
|
grn_id: assetFields.grn_id,
|
||||||
grn_item_id: assetFields.grn_item_id,
|
grn_item_id: assetFields.grn_item_id,
|
||||||
|
commencement_date: assetFields.commencement_date,
|
||||||
purchase_date: assetFields.purchase_date,
|
purchase_date: assetFields.purchase_date,
|
||||||
purchase_cost: assetFields.purchase_cost.optional(),
|
purchase_cost: assetFields.purchase_cost.optional(),
|
||||||
useful_life_years: assetFields.useful_life_years,
|
useful_life_years: assetFields.useful_life_years,
|
||||||
@ -103,6 +131,8 @@ const listAssetsQuerySchema = Joi.object({
|
|||||||
item_subcategory_id: Joi.number().integer().positive().optional(),
|
item_subcategory_id: Joi.number().integer().positive().optional(),
|
||||||
location_id: Joi.number().integer().positive().optional(),
|
location_id: Joi.number().integer().positive().optional(),
|
||||||
department_id: Joi.number().integer().positive().optional(),
|
department_id: Joi.number().integer().positive().optional(),
|
||||||
|
maintenance_incharge_user_id: Joi.number().integer().positive().optional(),
|
||||||
|
due_only: Joi.boolean().optional(),
|
||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -282,10 +312,35 @@ const depreciationCalculateSchema = Joi.object({
|
|||||||
purchase_cost: Joi.number().min(0).default(0),
|
purchase_cost: Joi.number().min(0).default(0),
|
||||||
salvage_value: Joi.number().min(0).default(0),
|
salvage_value: Joi.number().min(0).default(0),
|
||||||
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
|
commencement_date: Joi.date().iso().allow(null).optional(),
|
||||||
purchase_date: Joi.date().iso().allow(null).optional(),
|
purchase_date: Joi.date().iso().allow(null).optional(),
|
||||||
as_of_date: Joi.date().iso().allow(null).optional(),
|
as_of_date: Joi.date().iso().allow(null).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const myMaintenanceAssetsQuerySchema = Joi.object({
|
||||||
|
page: Joi.number().integer().min(1).default(1),
|
||||||
|
limit: Joi.number().integer().min(1).max(100).default(20),
|
||||||
|
search: Joi.string().allow('').optional(),
|
||||||
|
due_only: Joi.boolean().optional(),
|
||||||
|
status: Joi.string()
|
||||||
|
.valid(...ASSET_STATUSES)
|
||||||
|
.optional(),
|
||||||
|
location_id: Joi.number().integer().positive().optional(),
|
||||||
|
is_active: Joi.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMaintenanceLogSchema = Joi.object({
|
||||||
|
performed_date: Joi.date().iso().required(),
|
||||||
|
checklist_json: Joi.array().items(checklistResultItemSchema).min(1).required(),
|
||||||
|
remarks: Joi.string().allow(null, '').optional(),
|
||||||
|
next_due_date: Joi.date().iso().allow(null).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const listMaintenanceLogsQuerySchema = Joi.object({
|
||||||
|
page: Joi.number().integer().min(1).default(1),
|
||||||
|
limit: Joi.number().integer().min(1).max(100).default(20),
|
||||||
|
});
|
||||||
|
|
||||||
const expiryAlertsQuerySchema = Joi.object({
|
const expiryAlertsQuerySchema = Joi.object({
|
||||||
page: Joi.number().integer().min(1).default(1),
|
page: Joi.number().integer().min(1).default(1),
|
||||||
limit: Joi.number().integer().min(1).max(100).default(20),
|
limit: Joi.number().integer().min(1).max(100).default(20),
|
||||||
@ -320,6 +375,9 @@ module.exports = {
|
|||||||
updateInsurancePolicySchema,
|
updateInsurancePolicySchema,
|
||||||
renewInsurancePolicySchema,
|
renewInsurancePolicySchema,
|
||||||
depreciationCalculateSchema,
|
depreciationCalculateSchema,
|
||||||
|
myMaintenanceAssetsQuerySchema,
|
||||||
|
createMaintenanceLogSchema,
|
||||||
|
listMaintenanceLogsQuerySchema,
|
||||||
expiryAlertsQuerySchema,
|
expiryAlertsQuerySchema,
|
||||||
serviceAlertsQuerySchema,
|
serviceAlertsQuerySchema,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -90,6 +90,17 @@ const normalizePayload = (payload, { isCreate = false } = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, 'tags')) {
|
||||||
|
if (Array.isArray(data.tags)) {
|
||||||
|
const cleaned = data.tags
|
||||||
|
.map((tag) => (tag === null || tag === undefined ? '' : String(tag).trim()))
|
||||||
|
.filter((tag) => tag !== '');
|
||||||
|
data.tags = Array.from(new Set(cleaned));
|
||||||
|
} else if (data.tags === null) {
|
||||||
|
data.tags = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.min_order_qty !== undefined && data.min_order_qty !== null) {
|
if (data.min_order_qty !== undefined && data.min_order_qty !== null) {
|
||||||
data.min_order_qty = Number(data.min_order_qty);
|
data.min_order_qty = Number(data.min_order_qty);
|
||||||
}
|
}
|
||||||
@ -253,6 +264,7 @@ const exportItems = async (query) => {
|
|||||||
{ key: (row) => row.uom?.code || '', header: 'UOM' },
|
{ key: (row) => row.uom?.code || '', header: 'UOM' },
|
||||||
{ key: (row) => row.hsn_code?.code || '', header: 'HSN Code' },
|
{ key: (row) => row.hsn_code?.code || '', header: 'HSN Code' },
|
||||||
{ key: (row) => (row.gst_rate ? row.gst_rate.rate_pct : ''), header: 'GST %' },
|
{ key: (row) => (row.gst_rate ? row.gst_rate.rate_pct : ''), header: 'GST %' },
|
||||||
|
{ key: (row) => (Array.isArray(row.tags) ? row.tags.join(', ') : ''), header: 'Tags' },
|
||||||
{ key: 'is_asset_item', header: 'Is Asset Item' },
|
{ key: 'is_asset_item', header: 'Is Asset Item' },
|
||||||
{ key: 'is_active', header: 'Is Active' },
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
{ key: 'created_at', header: 'Created At', type: 'datetime' },
|
{ key: 'created_at', header: 'Created At', type: 'datetime' },
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const createSchema = Joi.object({
|
|||||||
is_asset_item: Joi.boolean().default(false),
|
is_asset_item: Joi.boolean().default(false),
|
||||||
description: Joi.string().allow(null, '').optional(),
|
description: Joi.string().allow(null, '').optional(),
|
||||||
specification: Joi.string().allow(null, '').optional(),
|
specification: Joi.string().allow(null, '').optional(),
|
||||||
|
tags: Joi.array().items(Joi.string().trim().max(50)).max(50).allow(null).optional(),
|
||||||
min_order_qty: Joi.number().precision(4).allow(null).optional(),
|
min_order_qty: Joi.number().precision(4).allow(null).optional(),
|
||||||
reorder_level: Joi.number().precision(4).allow(null).optional(),
|
reorder_level: Joi.number().precision(4).allow(null).optional(),
|
||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
@ -26,6 +27,7 @@ const updateSchema = Joi.object({
|
|||||||
is_asset_item: Joi.boolean().optional(),
|
is_asset_item: Joi.boolean().optional(),
|
||||||
description: Joi.string().allow(null, '').optional(),
|
description: Joi.string().allow(null, '').optional(),
|
||||||
specification: Joi.string().allow(null, '').optional(),
|
specification: Joi.string().allow(null, '').optional(),
|
||||||
|
tags: Joi.array().items(Joi.string().trim().max(50)).max(50).allow(null).optional(),
|
||||||
min_order_qty: Joi.number().precision(4).allow(null).optional(),
|
min_order_qty: Joi.number().precision(4).allow(null).optional(),
|
||||||
reorder_level: Joi.number().precision(4).allow(null).optional(),
|
reorder_level: Joi.number().precision(4).allow(null).optional(),
|
||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
|
|||||||
12
src/modules/masters/locations/locations.constants.js
Normal file
12
src/modules/masters/locations/locations.constants.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const {
|
||||||
|
INDIAN_STATE_OPTIONS,
|
||||||
|
INDIAN_STATE_VALUES,
|
||||||
|
} = require('../../../constants/indianStates');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
INDIAN_STATE_OPTIONS,
|
||||||
|
INDIAN_STATE_VALUES,
|
||||||
|
/** Alias used by location state dropdown API */
|
||||||
|
LOCATION_STATE_OPTIONS: INDIAN_STATE_OPTIONS,
|
||||||
|
LOCATION_STATE_VALUES: INDIAN_STATE_VALUES,
|
||||||
|
};
|
||||||
@ -34,5 +34,10 @@ const exportCsv = asyncHandler(async (req, res) => {
|
|||||||
res.send(csv);
|
res.send(csv);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, exportCsv, getOne, update, remove };
|
const listStateOptions = asyncHandler(async (_req, res) => {
|
||||||
|
const data = service.listStateOptions();
|
||||||
|
res.json(new ApiResponse(200, data, 'Location state options fetched'));
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove, listStateOptions };
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const {
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
|
router.get('/states', authorize('MASTERS', 'view'), controller.listStateOptions);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
router.get(
|
router.get(
|
||||||
'/export',
|
'/export',
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const {
|
|||||||
toWarehouseResponse,
|
toWarehouseResponse,
|
||||||
toLocationResponse,
|
toLocationResponse,
|
||||||
} = require('../../../utils/locations');
|
} = require('../../../utils/locations');
|
||||||
|
const { LOCATION_STATE_OPTIONS } = require('./locations.constants');
|
||||||
|
|
||||||
const TABLE_NAME = 'locations';
|
const TABLE_NAME = 'locations';
|
||||||
|
|
||||||
@ -347,4 +348,5 @@ module.exports = {
|
|||||||
getLocationById: (id) => getLocationById(id),
|
getLocationById: (id) => getLocationById(id),
|
||||||
updateLocationById,
|
updateLocationById,
|
||||||
deleteLocationById,
|
deleteLocationById,
|
||||||
|
listStateOptions: () => LOCATION_STATE_OPTIONS,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
const { LOCATION_STATE_VALUES } = require('./locations.constants');
|
||||||
|
|
||||||
const locationFields = {
|
const locationFields = {
|
||||||
gstin: Joi.string().max(15).allow(null, '').optional(),
|
gstin: Joi.string().max(15).allow(null, '').optional(),
|
||||||
address: Joi.string().allow(null, '').optional(),
|
address: Joi.string().allow(null, '').optional(),
|
||||||
city: Joi.string().max(100).allow(null, '').optional(),
|
city: Joi.string().max(100).allow(null, '').optional(),
|
||||||
state: Joi.string().max(100).allow(null, '').optional(),
|
state: Joi.string()
|
||||||
|
.valid(...LOCATION_STATE_VALUES)
|
||||||
|
.allow(null, '')
|
||||||
|
.optional(),
|
||||||
pincode: Joi.string().max(10).allow(null, '').optional(),
|
pincode: Joi.string().max(10).allow(null, '').optional(),
|
||||||
phone: Joi.string().max(15).allow(null, '').optional(),
|
phone: Joi.string().max(15).allow(null, '').optional(),
|
||||||
location: Joi.string().max(200).allow(null, '').optional(),
|
location: Joi.string().max(200).allow(null, '').optional(),
|
||||||
|
|||||||
@ -56,6 +56,7 @@ const sanitizeDepreciationRow = (asset, asOfDate) => {
|
|||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
useful_life_years: asset.useful_life_years,
|
useful_life_years: asset.useful_life_years,
|
||||||
|
commencement_date: asset.commencement_date,
|
||||||
purchase_date: asset.purchase_date,
|
purchase_date: asset.purchase_date,
|
||||||
as_of_date: asOfDate,
|
as_of_date: asOfDate,
|
||||||
});
|
});
|
||||||
@ -65,6 +66,7 @@ const sanitizeDepreciationRow = (asset, asOfDate) => {
|
|||||||
asset_code: asset.asset_code,
|
asset_code: asset.asset_code,
|
||||||
asset_name: asset.asset_name,
|
asset_name: asset.asset_name,
|
||||||
status: asset.status,
|
status: asset.status,
|
||||||
|
commencement_date: asset.commencement_date,
|
||||||
purchase_date: asset.purchase_date,
|
purchase_date: asset.purchase_date,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
@ -203,6 +205,7 @@ const exportAssetDepreciation = async (query) => {
|
|||||||
{ key: (row) => row.item_subcategory?.name || '', header: 'Subcategory' },
|
{ key: (row) => row.item_subcategory?.name || '', header: 'Subcategory' },
|
||||||
{ key: (row) => row.location?.name || '', header: 'Location' },
|
{ key: (row) => row.location?.name || '', header: 'Location' },
|
||||||
{ key: (row) => row.department?.name || '', header: 'Department' },
|
{ key: (row) => row.department?.name || '', header: 'Department' },
|
||||||
|
{ key: 'commencement_date', header: 'Commencement Date', type: 'date' },
|
||||||
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
||||||
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
||||||
{ key: 'salvage_value', header: 'Salvage Value' },
|
{ key: 'salvage_value', header: 'Salvage Value' },
|
||||||
|
|||||||
43
src/modules/vendors/vendors.constants.js
vendored
43
src/modules/vendors/vendors.constants.js
vendored
@ -9,47 +9,12 @@ const GST_TREATMENTS = [
|
|||||||
{ value: 'OTHER', label: 'Other' },
|
{ value: 'OTHER', label: 'Other' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const SOURCE_OF_SUPPLY_OPTIONS = [
|
const {
|
||||||
{ value: 'Andhra Pradesh', label: 'Andhra Pradesh' },
|
INDIAN_STATE_OPTIONS: SOURCE_OF_SUPPLY_OPTIONS,
|
||||||
{ value: 'Arunachal Pradesh', label: 'Arunachal Pradesh' },
|
INDIAN_STATE_VALUES: SOURCE_OF_SUPPLY_VALUES,
|
||||||
{ value: 'Assam', label: 'Assam' },
|
} = require('../../constants/indianStates');
|
||||||
{ value: 'Bihar', label: 'Bihar' },
|
|
||||||
{ value: 'Chhattisgarh', label: 'Chhattisgarh' },
|
|
||||||
{ value: 'Goa', label: 'Goa' },
|
|
||||||
{ value: 'Gujarat', label: 'Gujarat' },
|
|
||||||
{ value: 'Haryana', label: 'Haryana' },
|
|
||||||
{ value: 'Himachal Pradesh', label: 'Himachal Pradesh' },
|
|
||||||
{ value: 'Jharkhand', label: 'Jharkhand' },
|
|
||||||
{ value: 'Karnataka', label: 'Karnataka' },
|
|
||||||
{ value: 'Kerala', label: 'Kerala' },
|
|
||||||
{ value: 'Madhya Pradesh', label: 'Madhya Pradesh' },
|
|
||||||
{ value: 'Maharashtra', label: 'Maharashtra' },
|
|
||||||
{ value: 'Manipur', label: 'Manipur' },
|
|
||||||
{ value: 'Meghalaya', label: 'Meghalaya' },
|
|
||||||
{ value: 'Mizoram', label: 'Mizoram' },
|
|
||||||
{ value: 'Nagaland', label: 'Nagaland' },
|
|
||||||
{ value: 'Odisha', label: 'Odisha' },
|
|
||||||
{ value: 'Punjab', label: 'Punjab' },
|
|
||||||
{ value: 'Rajasthan', label: 'Rajasthan' },
|
|
||||||
{ value: 'Sikkim', label: 'Sikkim' },
|
|
||||||
{ value: 'Tamil Nadu', label: 'Tamil Nadu' },
|
|
||||||
{ value: 'Telangana', label: 'Telangana' },
|
|
||||||
{ value: 'Tripura', label: 'Tripura' },
|
|
||||||
{ value: 'Uttar Pradesh', label: 'Uttar Pradesh' },
|
|
||||||
{ value: 'Uttarakhand', label: 'Uttarakhand' },
|
|
||||||
{ value: 'West Bengal', label: 'West Bengal' },
|
|
||||||
{ value: 'Andaman and Nicobar Islands', label: 'Andaman and Nicobar Islands' },
|
|
||||||
{ value: 'Chandigarh', label: 'Chandigarh' },
|
|
||||||
{ value: 'Dadra and Nagar Haveli and Daman and Diu', label: 'Dadra and Nagar Haveli and Daman and Diu' },
|
|
||||||
{ value: 'Delhi', label: 'Delhi' },
|
|
||||||
{ value: 'Jammu and Kashmir', label: 'Jammu and Kashmir' },
|
|
||||||
{ value: 'Ladakh', label: 'Ladakh' },
|
|
||||||
{ value: 'Lakshadweep', label: 'Lakshadweep' },
|
|
||||||
{ value: 'Puducherry', label: 'Puducherry' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const GST_TREATMENT_VALUES = GST_TREATMENTS.map((item) => item.value);
|
const GST_TREATMENT_VALUES = GST_TREATMENTS.map((item) => item.value);
|
||||||
const SOURCE_OF_SUPPLY_VALUES = SOURCE_OF_SUPPLY_OPTIONS.map((item) => item.value);
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GST_TREATMENTS,
|
GST_TREATMENTS,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user