GWM : depreciation report
This commit is contained in:
parent
0a52e1202c
commit
44c920d0c5
20
scripts/patch-reports-module.sql
Normal file
20
scripts/patch-reports-module.sql
Normal file
@ -0,0 +1,20 @@
|
||||
-- Reports module — RBAC for read-only report APIs
|
||||
|
||||
INSERT INTO modules (code, name, sort_order, is_active)
|
||||
VALUES ('REPORTS', 'Reports', 90, true)
|
||||
ON CONFLICT (code) DO UPDATE SET name = EXCLUDED.name, sort_order = EXCLUDED.sort_order, is_active = true;
|
||||
|
||||
INSERT INTO permissions (module_id, action, is_active)
|
||||
SELECT m.id, a.action, true
|
||||
FROM modules m
|
||||
CROSS JOIN (VALUES ('view'), ('export')) AS a(action)
|
||||
WHERE m.code = 'REPORTS'
|
||||
ON CONFLICT (module_id, action) DO UPDATE SET is_active = true;
|
||||
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.action IN ('view', 'export')
|
||||
JOIN modules m ON m.id = p.module_id AND m.code = 'REPORTS'
|
||||
WHERE r.name = 'Super Admin' AND r.deleted_at IS NULL
|
||||
ON CONFLICT (role_id, permission_id) DO NOTHING;
|
||||
245
src/docs/reports-routes.yaml
Normal file
245
src/docs/reports-routes.yaml
Normal file
@ -0,0 +1,245 @@
|
||||
tags:
|
||||
- name: Reports
|
||||
|
||||
components:
|
||||
schemas:
|
||||
DepreciationSummary:
|
||||
type: object
|
||||
properties:
|
||||
asset_count: { type: integer, example: 42 }
|
||||
total_purchase_cost: { type: number, example: 1250000 }
|
||||
total_accumulated_depreciation: { type: number, example: 320000 }
|
||||
total_book_value: { type: number, example: 930000 }
|
||||
total_annual_depreciation: { type: number, example: 85000 }
|
||||
AssetDepreciationReportItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, example: "15" }
|
||||
asset_code: { type: string, example: IT-LAP-00012 }
|
||||
asset_name: { type: string, example: Dell Latitude 5540 }
|
||||
status: { type: string, example: IN_USE }
|
||||
purchase_date: { type: string, format: date, nullable: true }
|
||||
purchase_cost: { type: number, example: 75000 }
|
||||
salvage_value: { type: number, example: 5000 }
|
||||
useful_life_years: { type: integer, nullable: true, example: 5 }
|
||||
depreciation_method: { type: string, nullable: true, example: SLM }
|
||||
depreciation_rate: { type: number, nullable: true, example: 17.3333 }
|
||||
item_category:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
item_subcategory:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
plant:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
department:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
id: { type: string }
|
||||
name: { type: string }
|
||||
depreciation:
|
||||
type: object
|
||||
properties:
|
||||
depreciation_method: { type: string, nullable: true }
|
||||
depreciation_rate: { type: number, nullable: true }
|
||||
annual_depreciation: { type: number }
|
||||
accumulated_depreciation: { type: number }
|
||||
book_value: { type: number }
|
||||
years_elapsed: { type: number }
|
||||
purchase_cost: { type: number }
|
||||
salvage_value: { type: number }
|
||||
useful_life_years: { type: integer, nullable: true }
|
||||
DepreciationReportFilterOptions:
|
||||
type: object
|
||||
properties:
|
||||
plants:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
departments:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
name: { type: string }
|
||||
item_categories:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
item_subcategories:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
item_category_id: { type: string }
|
||||
depreciation_methods:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
value: { type: string }
|
||||
label: { type: string }
|
||||
statuses:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
value: { type: string }
|
||||
label: { type: string }
|
||||
|
||||
paths:
|
||||
/reports/assets/depreciation/filters:
|
||||
get:
|
||||
tags: [Reports]
|
||||
summary: Filter dropdowns for asset depreciation report
|
||||
responses:
|
||||
"200":
|
||||
description: Filter options fetched
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ApiResponse"
|
||||
- type: object
|
||||
properties:
|
||||
data: { $ref: "#/components/schemas/DepreciationReportFilterOptions" }
|
||||
/reports/assets/depreciation/export:
|
||||
get:
|
||||
tags: [Reports]
|
||||
summary: Export asset depreciation report as CSV
|
||||
parameters:
|
||||
- in: query
|
||||
name: search
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: status
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: depreciation_method
|
||||
schema: { type: string, enum: [SLM, WDV, OTHER] }
|
||||
- in: query
|
||||
name: item_category_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: item_subcategory_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: plant_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: department_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: is_active
|
||||
schema: { type: boolean }
|
||||
- in: query
|
||||
name: as_of_date
|
||||
schema: { type: string, format: date }
|
||||
description: Depreciation calculation date (defaults to today)
|
||||
- in: query
|
||||
name: purchase_date_from
|
||||
schema: { type: string, format: date }
|
||||
- in: query
|
||||
name: purchase_date_to
|
||||
schema: { type: string, format: date }
|
||||
responses:
|
||||
"200":
|
||||
description: CSV export
|
||||
content:
|
||||
text/csv:
|
||||
schema: { type: string }
|
||||
/reports/assets/depreciation:
|
||||
get:
|
||||
tags: [Reports]
|
||||
summary: Paginated asset depreciation report
|
||||
description: |
|
||||
Lists assets with computed depreciation (annual, accumulated, book value).
|
||||
`meta.summary` aggregates totals across all rows matching the current filters.
|
||||
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 }
|
||||
description: Search asset code, name, or serial number
|
||||
- in: query
|
||||
name: status
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: depreciation_method
|
||||
schema: { type: string, enum: [SLM, WDV, OTHER] }
|
||||
- in: query
|
||||
name: item_category_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: item_subcategory_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: plant_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: department_id
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: is_active
|
||||
schema: { type: boolean }
|
||||
- in: query
|
||||
name: as_of_date
|
||||
schema: { type: string, format: date }
|
||||
- in: query
|
||||
name: purchase_date_from
|
||||
schema: { type: string, format: date }
|
||||
- in: query
|
||||
name: purchase_date_to
|
||||
schema: { type: string, format: date }
|
||||
responses:
|
||||
"200":
|
||||
description: Asset depreciation report
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ApiResponse"
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/AssetDepreciationReportItem" }
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
page: { type: integer }
|
||||
limit: { type: integer }
|
||||
total: { type: integer }
|
||||
as_of_date: { type: string, format: date-time }
|
||||
summary: { $ref: "#/components/schemas/DepreciationSummary" }
|
||||
256
src/modules/reports/reports-assets-depreciation.service.js
Normal file
256
src/modules/reports/reports-assets-depreciation.service.js
Normal file
@ -0,0 +1,256 @@
|
||||
const prisma = require('../../config/prisma');
|
||||
const { getPagination } = require('../../utils/pagination');
|
||||
const { calculateDepreciation, round4 } = require('../assets/assets.depreciation');
|
||||
|
||||
const depreciationInclude = {
|
||||
item_categories: { select: { id: true, code: true, name: true } },
|
||||
item_subcategories: { select: { id: true, code: true, name: true } },
|
||||
plant: { select: { id: true, code: true, name: true } },
|
||||
departments: { select: { id: true, name: true } },
|
||||
};
|
||||
|
||||
const buildDepreciationWhere = (query) => {
|
||||
const where = {
|
||||
deleted_at: null,
|
||||
...(query.status ? { status: query.status } : {}),
|
||||
...(query.depreciation_method ? { depreciation_method: query.depreciation_method } : {}),
|
||||
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
|
||||
...(query.item_subcategory_id
|
||||
? { item_subcategory_id: BigInt(query.item_subcategory_id) }
|
||||
: {}),
|
||||
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
|
||||
...(query.department_id ? { department_id: BigInt(query.department_id) } : {}),
|
||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||
...(query.search
|
||||
? {
|
||||
OR: [
|
||||
{ asset_code: { contains: query.search, mode: 'insensitive' } },
|
||||
{ asset_name: { contains: query.search, mode: 'insensitive' } },
|
||||
{ serial_number: { contains: query.search, mode: 'insensitive' } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (query.purchase_date_from || query.purchase_date_to) {
|
||||
where.purchase_date = {};
|
||||
if (query.purchase_date_from) where.purchase_date.gte = new Date(query.purchase_date_from);
|
||||
if (query.purchase_date_to) where.purchase_date.lte = new Date(query.purchase_date_to);
|
||||
}
|
||||
|
||||
return where;
|
||||
};
|
||||
|
||||
const toNumber = (value) =>
|
||||
value !== null && value !== undefined ? Number(value) : value === null ? null : 0;
|
||||
|
||||
const sanitizeDepreciationRow = (asset, asOfDate) => {
|
||||
const purchaseCost = toNumber(asset.purchase_cost);
|
||||
const salvageValue = toNumber(asset.salvage_value);
|
||||
const depreciationRate = toNumber(asset.depreciation_rate);
|
||||
|
||||
const depreciation = calculateDepreciation({
|
||||
depreciation_method: asset.depreciation_method,
|
||||
depreciation_rate: depreciationRate,
|
||||
purchase_cost: purchaseCost,
|
||||
salvage_value: salvageValue,
|
||||
useful_life_years: asset.useful_life_years,
|
||||
purchase_date: asset.purchase_date,
|
||||
as_of_date: asOfDate,
|
||||
});
|
||||
|
||||
return {
|
||||
id: asset.id,
|
||||
asset_code: asset.asset_code,
|
||||
asset_name: asset.asset_name,
|
||||
status: asset.status,
|
||||
purchase_date: asset.purchase_date,
|
||||
purchase_cost: purchaseCost,
|
||||
salvage_value: salvageValue,
|
||||
useful_life_years: asset.useful_life_years,
|
||||
depreciation_method: asset.depreciation_method,
|
||||
depreciation_rate: depreciationRate,
|
||||
item_category: asset.item_categories || null,
|
||||
item_subcategory: asset.item_subcategories || null,
|
||||
plant: asset.plant || null,
|
||||
department: asset.departments || null,
|
||||
depreciation,
|
||||
};
|
||||
};
|
||||
|
||||
const computeSummary = (rows, asOfDate) => {
|
||||
let totalPurchaseCost = 0;
|
||||
let totalAccumulatedDepreciation = 0;
|
||||
let totalBookValue = 0;
|
||||
let totalAnnualDepreciation = 0;
|
||||
|
||||
for (const asset of rows) {
|
||||
const item = sanitizeDepreciationRow(asset, asOfDate);
|
||||
totalPurchaseCost = round4(totalPurchaseCost + item.purchase_cost);
|
||||
totalAccumulatedDepreciation = round4(
|
||||
totalAccumulatedDepreciation + item.depreciation.accumulated_depreciation
|
||||
);
|
||||
totalBookValue = round4(totalBookValue + item.depreciation.book_value);
|
||||
totalAnnualDepreciation = round4(
|
||||
totalAnnualDepreciation + item.depreciation.annual_depreciation
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
asset_count: rows.length,
|
||||
total_purchase_cost: totalPurchaseCost,
|
||||
total_accumulated_depreciation: totalAccumulatedDepreciation,
|
||||
total_book_value: totalBookValue,
|
||||
total_annual_depreciation: totalAnnualDepreciation,
|
||||
};
|
||||
};
|
||||
|
||||
const listAssetDepreciation = async (query) => {
|
||||
const { page, limit, skip } = getPagination(query);
|
||||
const asOfDate = query.as_of_date ? new Date(query.as_of_date) : new Date();
|
||||
const where = buildDepreciationWhere(query);
|
||||
|
||||
const [rows, total, summaryRows] = await Promise.all([
|
||||
prisma.assets.findMany({
|
||||
where,
|
||||
include: depreciationInclude,
|
||||
orderBy: [{ asset_code: 'asc' }],
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.assets.count({ where }),
|
||||
prisma.assets.findMany({
|
||||
where,
|
||||
include: depreciationInclude,
|
||||
}),
|
||||
]);
|
||||
|
||||
const summary = computeSummary(summaryRows, asOfDate);
|
||||
|
||||
return {
|
||||
data: rows.map((row) => sanitizeDepreciationRow(row, asOfDate)),
|
||||
meta: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
as_of_date: asOfDate.toISOString(),
|
||||
summary,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getDepreciationFilterOptions = async () => {
|
||||
const [plants, departments, categories, subcategories] = await Promise.all([
|
||||
prisma.locations.findMany({
|
||||
where: { deleted_at: null, is_active: true, type: 'plant' },
|
||||
select: { id: true, code: true, name: true },
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
prisma.departments.findMany({
|
||||
where: { deleted_at: null, is_active: true },
|
||||
select: { id: true, name: true },
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
prisma.item_categories.findMany({
|
||||
where: { deleted_at: null, is_active: true },
|
||||
select: { id: true, code: true, name: true },
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
prisma.item_subcategories.findMany({
|
||||
where: { deleted_at: null, is_active: true },
|
||||
select: { id: true, code: true, name: true, item_category_id: true },
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
plants,
|
||||
departments,
|
||||
item_categories: categories,
|
||||
item_subcategories: subcategories,
|
||||
depreciation_methods: [
|
||||
{ value: 'SLM', label: 'Straight Line Method (SLM)' },
|
||||
{ value: 'WDV', label: 'Written Down Value (WDV)' },
|
||||
{ value: 'OTHER', label: 'Other' },
|
||||
],
|
||||
statuses: [
|
||||
{ value: 'IN_USE', label: 'In Use' },
|
||||
{ value: 'IDLE', label: 'Idle' },
|
||||
{ value: 'UNDER_MAINTENANCE', label: 'Under Maintenance' },
|
||||
{ value: 'DISPOSED', label: 'Disposed' },
|
||||
{ value: 'SCRAPPED', label: 'Scrapped' },
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const escapeCsv = (value) => {
|
||||
const text = String(value ?? '');
|
||||
if (/[",\n]/.test(text)) return `"${text.replace(/"/g, '""')}"`;
|
||||
return text;
|
||||
};
|
||||
|
||||
const exportAssetDepreciation = async (query) => {
|
||||
const asOfDate = query.as_of_date ? new Date(query.as_of_date) : new Date();
|
||||
const where = buildDepreciationWhere(query);
|
||||
|
||||
const rows = await prisma.assets.findMany({
|
||||
where,
|
||||
include: depreciationInclude,
|
||||
orderBy: [{ asset_code: 'asc' }],
|
||||
});
|
||||
|
||||
const header = [
|
||||
'Asset Code',
|
||||
'Asset Name',
|
||||
'Status',
|
||||
'Category',
|
||||
'Subcategory',
|
||||
'Plant',
|
||||
'Department',
|
||||
'Purchase Date',
|
||||
'Purchase Cost',
|
||||
'Salvage Value',
|
||||
'Useful Life (Years)',
|
||||
'Depreciation Method',
|
||||
'Depreciation Rate (%)',
|
||||
'Annual Depreciation',
|
||||
'Accumulated Depreciation',
|
||||
'Book Value',
|
||||
'Years Elapsed',
|
||||
'As Of Date',
|
||||
];
|
||||
|
||||
const lines = rows.map((row) => {
|
||||
const item = sanitizeDepreciationRow(row, asOfDate);
|
||||
return [
|
||||
item.asset_code,
|
||||
item.asset_name,
|
||||
item.status,
|
||||
item.item_category?.name || '',
|
||||
item.item_subcategory?.name || '',
|
||||
item.plant?.name || '',
|
||||
item.department?.name || '',
|
||||
item.purchase_date ? new Date(item.purchase_date).toISOString().slice(0, 10) : '',
|
||||
item.purchase_cost,
|
||||
item.salvage_value,
|
||||
item.useful_life_years ?? '',
|
||||
item.depreciation_method || '',
|
||||
item.depreciation_rate ?? '',
|
||||
item.depreciation.annual_depreciation,
|
||||
item.depreciation.accumulated_depreciation,
|
||||
item.depreciation.book_value,
|
||||
item.depreciation.years_elapsed,
|
||||
asOfDate.toISOString().slice(0, 10),
|
||||
]
|
||||
.map(escapeCsv)
|
||||
.join(',');
|
||||
});
|
||||
|
||||
return [header.join(','), ...lines].join('\n');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
listAssetDepreciation,
|
||||
getDepreciationFilterOptions,
|
||||
exportAssetDepreciation,
|
||||
};
|
||||
29
src/modules/reports/reports.controller.js
Normal file
29
src/modules/reports/reports.controller.js
Normal file
@ -0,0 +1,29 @@
|
||||
const asyncHandler = require('../../utils/asyncHandler');
|
||||
const ApiResponse = require('../../utils/ApiResponse');
|
||||
const depreciationService = require('./reports-assets-depreciation.service');
|
||||
|
||||
const listAssetDepreciation = asyncHandler(async (req, res) => {
|
||||
const result = await depreciationService.listAssetDepreciation(req.query);
|
||||
res.json(new ApiResponse(200, result.data, 'Asset depreciation report fetched', result.meta));
|
||||
});
|
||||
|
||||
const depreciationFilters = asyncHandler(async (_req, res) => {
|
||||
const data = await depreciationService.getDepreciationFilterOptions();
|
||||
res.json(new ApiResponse(200, data, 'Asset depreciation filter options fetched'));
|
||||
});
|
||||
|
||||
const exportAssetDepreciation = asyncHandler(async (req, res) => {
|
||||
const csv = await depreciationService.exportAssetDepreciation(req.query);
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="asset-depreciation-report.csv"'
|
||||
);
|
||||
res.send(csv);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
listAssetDepreciation,
|
||||
depreciationFilters,
|
||||
exportAssetDepreciation,
|
||||
};
|
||||
33
src/modules/reports/reports.routes.js
Normal file
33
src/modules/reports/reports.routes.js
Normal file
@ -0,0 +1,33 @@
|
||||
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('./reports.controller');
|
||||
const {
|
||||
depreciationReportQuerySchema,
|
||||
exportDepreciationReportQuerySchema,
|
||||
} = require('./reports.validation');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authenticate);
|
||||
|
||||
router.get(
|
||||
'/assets/depreciation/filters',
|
||||
authorize('REPORTS', 'view'),
|
||||
controller.depreciationFilters
|
||||
);
|
||||
router.get(
|
||||
'/assets/depreciation/export',
|
||||
authorize('REPORTS', 'export'),
|
||||
validate(exportDepreciationReportQuerySchema, 'query'),
|
||||
controller.exportAssetDepreciation
|
||||
);
|
||||
router.get(
|
||||
'/assets/depreciation',
|
||||
authorize('REPORTS', 'view'),
|
||||
validate(depreciationReportQuerySchema, 'query'),
|
||||
controller.listAssetDepreciation
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
39
src/modules/reports/reports.validation.js
Normal file
39
src/modules/reports/reports.validation.js
Normal file
@ -0,0 +1,39 @@
|
||||
const Joi = require('joi');
|
||||
const { ASSET_STATUSES } = require('../assets/assets.constants');
|
||||
|
||||
const depreciationReportQuerySchema = Joi.object({
|
||||
page: Joi.number().integer().min(1).default(1),
|
||||
limit: Joi.number().integer().min(1).max(100).default(20),
|
||||
search: Joi.string().max(100).trim().allow('').optional(),
|
||||
status: Joi.string()
|
||||
.valid(...ASSET_STATUSES)
|
||||
.optional(),
|
||||
depreciation_method: Joi.string().valid('SLM', 'WDV', 'OTHER').optional(),
|
||||
item_category_id: Joi.number().integer().positive().optional(),
|
||||
item_subcategory_id: Joi.number().integer().positive().optional(),
|
||||
plant_id: Joi.number().integer().positive().optional(),
|
||||
department_id: Joi.number().integer().positive().optional(),
|
||||
is_active: Joi.boolean().optional(),
|
||||
as_of_date: Joi.date().iso().optional(),
|
||||
purchase_date_from: Joi.date().iso().optional(),
|
||||
purchase_date_to: Joi.date().iso().optional(),
|
||||
}).custom((value, helpers) => {
|
||||
if (
|
||||
value.purchase_date_from &&
|
||||
value.purchase_date_to &&
|
||||
new Date(value.purchase_date_from) > new Date(value.purchase_date_to)
|
||||
) {
|
||||
return helpers.message('purchase_date_from must be before or equal to purchase_date_to');
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
const exportDepreciationReportQuerySchema = depreciationReportQuerySchema.keys({
|
||||
page: Joi.strip(),
|
||||
limit: Joi.strip(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
depreciationReportQuerySchema,
|
||||
exportDepreciationReportQuerySchema,
|
||||
};
|
||||
@ -11,6 +11,7 @@ router.use('/grn', require('../../modules/grn/grn.routes'));
|
||||
router.use('/assets', require('../../modules/assets/assets.routes'));
|
||||
router.use('/settings', require('../../modules/settings/settings.routes'));
|
||||
router.use('/audit-logs', require('../../modules/audit-logs/audit-logs.routes'));
|
||||
router.use('/reports', require('../../modules/reports/reports.routes'));
|
||||
router.use('/masters', require('../../modules/masters'));
|
||||
|
||||
router.get('/healthz', (req, res) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user