GWM : favicon upload , gsitn update in company , excell export api error fixed
This commit is contained in:
parent
969647333f
commit
710b0b33c6
@ -309,9 +309,10 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
|
|||||||
|
|
||||||
| Status | Method | Endpoint | RBAC | Notes |
|
| Status | Method | Endpoint | RBAC | Notes |
|
||||||
|--------|--------|----------|------|-------|
|
|--------|--------|----------|------|-------|
|
||||||
| [x] | GET | `/settings/company` | view | Company profile (org name, GSTIN, contact, address, logo URL) |
|
| [x] | GET | `/settings/company` | view | Company profile (org name, GSTIN, contact, address, logo/favicon URLs) |
|
||||||
| [x] | PUT | `/settings/company` | edit | Update company profile |
|
| [x] | PUT | `/settings/company` | edit | Update company profile |
|
||||||
| [x] | POST | `/settings/company/logo` | edit | Upload logo (`multipart/form-data`, field `logo`) |
|
| [x] | POST | `/settings/company/logo` | edit | Upload logo (`multipart/form-data`, field `logo`) |
|
||||||
|
| [x] | POST | `/settings/company/favicon` | edit | Upload favicon (`multipart/form-data`, field `favicon`) |
|
||||||
| [x] | GET | `/settings/email` | view | SMTP settings (`has_smtp_password` flag; password never returned) |
|
| [x] | GET | `/settings/email` | view | SMTP settings (`has_smtp_password` flag; password never returned) |
|
||||||
| [x] | PUT | `/settings/email` | edit | Update SMTP settings (password encrypted at rest) |
|
| [x] | PUT | `/settings/email` | edit | Update SMTP settings (password encrypted at rest) |
|
||||||
|
|
||||||
@ -319,6 +320,8 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
|
|||||||
|
|
||||||
**DB patch:** `scripts/patch-company-gstin.sql` — adds `company.gstin` for PDF document headers.
|
**DB patch:** `scripts/patch-company-gstin.sql` — adds `company.gstin` for PDF document headers.
|
||||||
|
|
||||||
|
**DB patch:** `scripts/patch-company-favicon.sql` — adds `company.favicon_path`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Audit Logs (`/audit-logs`) — module: `AUDIT_LOGS`
|
### Audit Logs (`/audit-logs`) — module: `AUDIT_LOGS`
|
||||||
|
|||||||
@ -1009,6 +1009,7 @@ model company {
|
|||||||
email String? @db.VarChar(200)
|
email String? @db.VarChar(200)
|
||||||
website String? @db.VarChar(255)
|
website String? @db.VarChar(255)
|
||||||
logo_path String? @db.VarChar(500)
|
logo_path String? @db.VarChar(500)
|
||||||
|
favicon_path String? @db.VarChar(500)
|
||||||
address String?
|
address String?
|
||||||
city String? @db.VarChar(100)
|
city String? @db.VarChar(100)
|
||||||
state String? @db.VarChar(100)
|
state String? @db.VarChar(100)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS company (
|
|||||||
email VARCHAR(200),
|
email VARCHAR(200),
|
||||||
website VARCHAR(255),
|
website VARCHAR(255),
|
||||||
logo_path VARCHAR(500),
|
logo_path VARCHAR(500),
|
||||||
|
favicon_path VARCHAR(500),
|
||||||
address TEXT,
|
address TEXT,
|
||||||
city VARCHAR(100),
|
city VARCHAR(100),
|
||||||
state VARCHAR(100),
|
state VARCHAR(100),
|
||||||
|
|||||||
4
scripts/patch-company-favicon.sql
Normal file
4
scripts/patch-company-favicon.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-- Add favicon_path to company profile (browser tab / app icon)
|
||||||
|
|
||||||
|
ALTER TABLE company
|
||||||
|
ADD COLUMN IF NOT EXISTS favicon_path VARCHAR(500);
|
||||||
@ -67,6 +67,26 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Logo updated
|
description: Logo updated
|
||||||
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
||||||
|
/settings/company/favicon:
|
||||||
|
post:
|
||||||
|
tags: [Settings]
|
||||||
|
summary: Upload company favicon
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [favicon]
|
||||||
|
properties:
|
||||||
|
favicon:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
description: JPEG, PNG, or WebP image
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Favicon updated
|
||||||
|
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
|
||||||
/settings/email:
|
/settings/email:
|
||||||
get:
|
get:
|
||||||
tags: [Settings]
|
tags: [Settings]
|
||||||
|
|||||||
@ -2,6 +2,10 @@ const prisma = require('../../../config/prisma');
|
|||||||
const ApiError = require('../../../utils/ApiError');
|
const ApiError = require('../../../utils/ApiError');
|
||||||
const auditLog = require('../../../utils/auditLog');
|
const auditLog = require('../../../utils/auditLog');
|
||||||
const { getPagination } = require('../../../utils/pagination');
|
const { getPagination } = require('../../../utils/pagination');
|
||||||
|
const { parseId } = require('../../../utils/parseId');
|
||||||
|
const { rowsToCsv } = require('../../../utils/csv');
|
||||||
|
|
||||||
|
const AUDIT_FIELD_NAMES = new Set(['created_by', 'updated_by']);
|
||||||
|
|
||||||
const normalizePayload = (payload, fields) => {
|
const normalizePayload = (payload, fields) => {
|
||||||
const out = { ...payload };
|
const out = { ...payload };
|
||||||
@ -22,6 +26,17 @@ const normalizePayload = (payload, fields) => {
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultExportColumns = (fields, columns) => {
|
||||||
|
if (columns && columns.length) return columns;
|
||||||
|
return [
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
...fields
|
||||||
|
.filter((f) => !AUDIT_FIELD_NAMES.has(f.name))
|
||||||
|
.map((f) => ({ key: f.name, header: f.name })),
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const buildMasterService = ({
|
const buildMasterService = ({
|
||||||
modelName,
|
modelName,
|
||||||
tableName,
|
tableName,
|
||||||
@ -30,9 +45,36 @@ const buildMasterService = ({
|
|||||||
softDelete = true,
|
softDelete = true,
|
||||||
include = undefined,
|
include = undefined,
|
||||||
mapRow = (row) => row,
|
mapRow = (row) => row,
|
||||||
|
exportColumns = null,
|
||||||
}) => {
|
}) => {
|
||||||
const model = prisma[modelName];
|
const model = prisma[modelName];
|
||||||
const withInclude = include ? { include } : {};
|
const withInclude = include ? { include } : {};
|
||||||
|
const csvColumns = defaultExportColumns(fields, exportColumns);
|
||||||
|
|
||||||
|
const buildListWhere = (query) => {
|
||||||
|
const searchFields = fields.filter((f) => f.searchable).map((f) => f.name);
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
...(softDelete ? { deleted_at: null } : {}),
|
||||||
|
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||||
|
...(query.search && searchFields.length
|
||||||
|
? {
|
||||||
|
OR: searchFields.map((name) => ({
|
||||||
|
[name]: { contains: query.search, mode: 'insensitive' },
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const f of fields) {
|
||||||
|
if (!f.filterable || query[f.name] === undefined || query[f.name] === null || query[f.name] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
where[f.name] = f.type === 'int' ? BigInt(query[f.name]) : query[f.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return where;
|
||||||
|
};
|
||||||
|
|
||||||
const createOne = async (payload, userId, requestId) => {
|
const createOne = async (payload, userId, requestId) => {
|
||||||
const data = normalizePayload(payload, fields);
|
const data = normalizePayload(payload, fields);
|
||||||
@ -68,26 +110,7 @@ const buildMasterService = ({
|
|||||||
|
|
||||||
const list = async (query) => {
|
const list = async (query) => {
|
||||||
const { page, limit, skip } = getPagination(query);
|
const { page, limit, skip } = getPagination(query);
|
||||||
const searchFields = fields.filter((f) => f.searchable).map((f) => f.name);
|
const where = buildListWhere(query);
|
||||||
|
|
||||||
const where = {
|
|
||||||
...(softDelete ? { deleted_at: null } : {}),
|
|
||||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
|
||||||
...(query.search && searchFields.length
|
|
||||||
? {
|
|
||||||
OR: searchFields.map((name) => ({
|
|
||||||
[name]: { contains: query.search, mode: 'insensitive' },
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const f of fields) {
|
|
||||||
if (!f.filterable || query[f.name] === undefined || query[f.name] === null || query[f.name] === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
where[f.name] = f.type === 'int' ? BigInt(query[f.name]) : query[f.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [rows, total] = await Promise.all([
|
const [rows, total] = await Promise.all([
|
||||||
model.findMany({ where, orderBy: { created_at: 'desc' }, skip, take: limit, ...withInclude }),
|
model.findMany({ where, orderBy: { created_at: 'desc' }, skip, take: limit, ...withInclude }),
|
||||||
@ -97,9 +120,19 @@ const buildMasterService = ({
|
|||||||
return { data: rows.map(mapRow), meta: { page, limit, total } };
|
return { data: rows.map(mapRow), meta: { page, limit, total } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportCsv = async (query) => {
|
||||||
|
const where = buildListWhere(query);
|
||||||
|
const rows = await model.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
...withInclude,
|
||||||
|
});
|
||||||
|
return rowsToCsv(csvColumns, rows.map(mapRow));
|
||||||
|
};
|
||||||
|
|
||||||
const getOne = async (id) => {
|
const getOne = async (id) => {
|
||||||
const one = await model.findFirst({
|
const one = await model.findFirst({
|
||||||
where: { id: BigInt(id), ...(softDelete ? { deleted_at: null } : {}) },
|
where: { id: parseId(id), ...(softDelete ? { deleted_at: null } : {}) },
|
||||||
...withInclude,
|
...withInclude,
|
||||||
});
|
});
|
||||||
if (!one) throw new ApiError(404, `${tableName} not found`);
|
if (!one) throw new ApiError(404, `${tableName} not found`);
|
||||||
@ -107,12 +140,13 @@ const buildMasterService = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateOne = async (id, payload, userId, requestId) => {
|
const updateOne = async (id, payload, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getOne(id);
|
const existing = await getOne(id);
|
||||||
const data = normalizePayload(payload, fields);
|
const data = normalizePayload(payload, fields);
|
||||||
if (fields.some((f) => f.name === 'updated_by'))
|
if (fields.some((f) => f.name === 'updated_by'))
|
||||||
data.updated_by = userId ? BigInt(userId) : null;
|
data.updated_by = userId ? BigInt(userId) : null;
|
||||||
|
|
||||||
const updated = await model.update({ where: { id: BigInt(id) }, data, ...withInclude });
|
const updated = await model.update({ where: { id: idBigInt }, data, ...withInclude });
|
||||||
const mapped = mapRow(updated);
|
const mapped = mapRow(updated);
|
||||||
|
|
||||||
await auditLog({
|
await auditLog({
|
||||||
@ -129,10 +163,11 @@ const buildMasterService = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeOne = async (id, userId, requestId) => {
|
const removeOne = async (id, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getOne(id);
|
const existing = await getOne(id);
|
||||||
if (softDelete) {
|
if (softDelete) {
|
||||||
await model.update({
|
await model.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data: {
|
data: {
|
||||||
deleted_at: new Date(),
|
deleted_at: new Date(),
|
||||||
...(fields.some((f) => f.name === 'updated_by')
|
...(fields.some((f) => f.name === 'updated_by')
|
||||||
@ -141,7 +176,7 @@ const buildMasterService = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await model.delete({ where: { id: BigInt(id) } });
|
await model.delete({ where: { id: idBigInt } });
|
||||||
}
|
}
|
||||||
|
|
||||||
await auditLog({
|
await auditLog({
|
||||||
@ -155,7 +190,7 @@ const buildMasterService = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createOne, list, getOne, updateOne, removeOne };
|
return { createOne, list, exportCsv, getOne, updateOne, removeOne };
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { buildMasterService };
|
module.exports = { buildMasterService };
|
||||||
|
|||||||
@ -69,6 +69,15 @@ const listQuerySchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Strip pagination for CSV export (same filters as list). */
|
||||||
|
const toExportQuerySchema = (schema = listQuerySchema) =>
|
||||||
|
schema.keys({
|
||||||
|
page: Joi.strip(),
|
||||||
|
limit: Joi.strip(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportQuerySchema = toExportQuerySchema(listQuerySchema);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
MASTER_CODE_REGEX,
|
MASTER_CODE_REGEX,
|
||||||
MASTER_NAME_REGEX,
|
MASTER_NAME_REGEX,
|
||||||
@ -77,4 +86,6 @@ module.exports = {
|
|||||||
masterCode,
|
masterCode,
|
||||||
masterName,
|
masterName,
|
||||||
listQuerySchema,
|
listQuerySchema,
|
||||||
|
exportQuerySchema,
|
||||||
|
toExportQuerySchema,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'brands deleted successfully'));
|
res.json(new ApiResponse(200, null, 'brands deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportBrands(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="brands-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./brands.controller');
|
const controller = require('./brands.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./brands.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./brands.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -68,6 +68,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createBrands: service.createOne,
|
createBrands: service.createOne,
|
||||||
listBrands: service.list,
|
listBrands: service.list,
|
||||||
|
exportBrands: service.exportCsv,
|
||||||
getBrandsById: service.getOne,
|
getBrandsById: service.getOne,
|
||||||
updateBrands: service.updateOne,
|
updateBrands: service.updateOne,
|
||||||
deleteBrands: service.removeOne,
|
deleteBrands: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const BRAND_TYPES = ['OWN', 'OEM', 'THIRD_PARTY'];
|
const BRAND_TYPES = ['OWN', 'OEM', 'THIRD_PARTY'];
|
||||||
|
|
||||||
@ -25,4 +25,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema, BRAND_TYPES };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema, BRAND_TYPES };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'delivery_terms deleted successfully'));
|
res.json(new ApiResponse(200, null, 'delivery_terms deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportDeliveryTerms(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="delivery-terms-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./delivery-terms.controller');
|
const controller = require('./delivery-terms.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./delivery-terms.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./delivery-terms.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createDeliveryTerms: service.createOne,
|
createDeliveryTerms: service.createOne,
|
||||||
listDeliveryTerms: service.list,
|
listDeliveryTerms: service.list,
|
||||||
|
exportDeliveryTerms: service.exportCsv,
|
||||||
getDeliveryTermsById: service.getOne,
|
getDeliveryTermsById: service.getOne,
|
||||||
updateDeliveryTerms: service.updateOne,
|
updateDeliveryTerms: service.updateOne,
|
||||||
deleteDeliveryTerms: service.removeOne,
|
deleteDeliveryTerms: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
code: masterCode({ max: 30 }),
|
code: masterCode({ max: 30 }),
|
||||||
@ -15,4 +15,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'departments deleted successfully'));
|
res.json(new ApiResponse(200, null, 'departments deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportDepartments(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="departments-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./departments.controller');
|
const controller = require('./departments.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./departments.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./departments.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createDepartments: service.createOne,
|
createDepartments: service.createOne,
|
||||||
listDepartments: service.list,
|
listDepartments: service.list,
|
||||||
|
exportDepartments: service.exportCsv,
|
||||||
getDepartmentsById: service.getOne,
|
getDepartmentsById: service.getOne,
|
||||||
updateDepartments: service.updateOne,
|
updateDepartments: service.updateOne,
|
||||||
deleteDepartments: service.removeOne,
|
deleteDepartments: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
name: masterName({ max: 150 }),
|
name: masterName({ max: 150 }),
|
||||||
@ -11,4 +11,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'designations deleted successfully'));
|
res.json(new ApiResponse(200, null, 'designations deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportDesignations(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="designations-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./designations.controller');
|
const controller = require('./designations.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./designations.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./designations.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createDesignations: service.createOne,
|
createDesignations: service.createOne,
|
||||||
listDesignations: service.list,
|
listDesignations: service.list,
|
||||||
|
exportDesignations: service.exportCsv,
|
||||||
getDesignationsById: service.getOne,
|
getDesignationsById: service.getOne,
|
||||||
updateDesignations: service.updateOne,
|
updateDesignations: service.updateOne,
|
||||||
deleteDesignations: service.removeOne,
|
deleteDesignations: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
name: masterName({ max: 150 }),
|
name: masterName({ max: 150 }),
|
||||||
@ -11,4 +11,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'document_series deleted successfully'));
|
res.json(new ApiResponse(200, null, 'document_series deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportDocumentSeries(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="document-series-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./document-series.controller');
|
const controller = require('./document-series.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./document-series.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./document-series.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -62,6 +62,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createDocumentSeries: service.createOne,
|
createDocumentSeries: service.createOne,
|
||||||
listDocumentSeries: service.list,
|
listDocumentSeries: service.list,
|
||||||
|
exportDocumentSeries: service.exportCsv,
|
||||||
getDocumentSeriesById: service.getOne,
|
getDocumentSeriesById: service.getOne,
|
||||||
updateDocumentSeries: service.updateOne,
|
updateDocumentSeries: service.updateOne,
|
||||||
deleteDocumentSeries: service.removeOne,
|
deleteDocumentSeries: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
code: masterCode({ max: 30 }),
|
code: masterCode({ max: 30 }),
|
||||||
@ -19,4 +19,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'gst_rates deleted successfully'));
|
res.json(new ApiResponse(200, null, 'gst_rates deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportGstRates(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="gst-rates-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./gst-rates.controller');
|
const controller = require('./gst-rates.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./gst-rates.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./gst-rates.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createGstRates: service.createOne,
|
createGstRates: service.createOne,
|
||||||
listGstRates: service.list,
|
listGstRates: service.list,
|
||||||
|
exportGstRates: service.exportCsv,
|
||||||
getGstRatesById: service.getOne,
|
getGstRatesById: service.getOne,
|
||||||
updateGstRates: service.updateOne,
|
updateGstRates: service.updateOne,
|
||||||
deleteGstRates: service.removeOne,
|
deleteGstRates: service.removeOne,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
|
const { listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
rate_pct: Joi.number().precision(2).optional(),
|
rate_pct: Joi.number().precision(2).optional(),
|
||||||
@ -8,11 +9,6 @@ const createSchema = Joi.object({
|
|||||||
|
|
||||||
const updateSchema = createSchema.min(1);
|
const updateSchema = createSchema.min(1);
|
||||||
|
|
||||||
const listQuerySchema = Joi.object({
|
const exportQuerySchema = toExportQuerySchema(listQuerySchema);
|
||||||
page: Joi.number().integer().min(1).default(1),
|
|
||||||
limit: Joi.number().integer().min(1).max(100).default(20),
|
|
||||||
search: Joi.string().allow('').optional(),
|
|
||||||
is_active: Joi.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'hsn_codes deleted successfully'));
|
res.json(new ApiResponse(200, null, 'hsn_codes deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportHsnCodes(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="hsn-codes-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./hsn-codes.controller');
|
const controller = require('./hsn-codes.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./hsn-codes.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./hsn-codes.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createHsnCodes: service.createOne,
|
createHsnCodes: service.createOne,
|
||||||
listHsnCodes: service.list,
|
listHsnCodes: service.list,
|
||||||
|
exportHsnCodes: service.exportCsv,
|
||||||
getHsnCodesById: service.getOne,
|
getHsnCodesById: service.getOne,
|
||||||
updateHsnCodes: service.updateOne,
|
updateHsnCodes: service.updateOne,
|
||||||
deleteHsnCodes: service.removeOne,
|
deleteHsnCodes: service.removeOne,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
|
const { listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const HSN_CODE_REGEX = /^[0-9]{4,8}$/;
|
const HSN_CODE_REGEX = /^[0-9]{4,8}$/;
|
||||||
const HSN_CODE_MESSAGE = 'HSN/SAC code must be 4 to 8 digits';
|
const HSN_CODE_MESSAGE = 'HSN/SAC code must be 4 to 8 digits';
|
||||||
@ -21,11 +22,6 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
const listQuerySchema = Joi.object({
|
const exportQuerySchema = toExportQuerySchema(listQuerySchema);
|
||||||
page: Joi.number().integer().min(1).default(1),
|
|
||||||
limit: Joi.number().integer().min(1).max(100).default(20),
|
|
||||||
search: Joi.string().allow('').optional(),
|
|
||||||
is_active: Joi.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'item_categories deleted successfully'));
|
res.json(new ApiResponse(200, null, 'item_categories deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportItemCategories(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="item-categories-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./item-categories.controller');
|
const controller = require('./item-categories.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./item-categories.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./item-categories.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -77,6 +77,7 @@ module.exports = {
|
|||||||
createItemCategories: (payload, userId, requestId) =>
|
createItemCategories: (payload, userId, requestId) =>
|
||||||
service.createOne(normalizeAssetDefaults(payload), userId, requestId),
|
service.createOne(normalizeAssetDefaults(payload), userId, requestId),
|
||||||
listItemCategories: service.list,
|
listItemCategories: service.list,
|
||||||
|
exportItemCategories: service.exportCsv,
|
||||||
getItemCategoriesById: service.getOne,
|
getItemCategoriesById: service.getOne,
|
||||||
updateItemCategories: (id, payload, userId, requestId) =>
|
updateItemCategories: (id, payload, userId, requestId) =>
|
||||||
service.updateOne(id, normalizeAssetDefaults(payload), userId, requestId),
|
service.updateOne(id, normalizeAssetDefaults(payload), userId, requestId),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
const { DEPRECIATION_METHODS } = require('../../assets/assets.constants');
|
const { DEPRECIATION_METHODS } = require('../../assets/assets.constants');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
@ -26,4 +26,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'item_subcategories deleted successfully'));
|
res.json(new ApiResponse(200, null, 'item_subcategories deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportItemSubcategories(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="item-subcategories-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./item-subcategories.controller');
|
const controller = require('./item-subcategories.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./item-subcategories.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./item-subcategories.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -20,6 +20,15 @@ const config = {
|
|||||||
softDelete: true,
|
softDelete: true,
|
||||||
include: subcategoryInclude,
|
include: subcategoryInclude,
|
||||||
mapRow: sanitizeSubcategory,
|
mapRow: sanitizeSubcategory,
|
||||||
|
exportColumns: [
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'code', header: 'Code' },
|
||||||
|
{ key: 'name', header: 'Name' },
|
||||||
|
{ key: (row) => row.item_category?.code || '', header: 'Category Code' },
|
||||||
|
{ key: (row) => row.item_category?.name || '', header: 'Category Name' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
],
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'item_category_id',
|
name: 'item_category_id',
|
||||||
@ -67,6 +76,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createItemSubcategories: service.createOne,
|
createItemSubcategories: service.createOne,
|
||||||
listItemSubcategories: service.list,
|
listItemSubcategories: service.list,
|
||||||
|
exportItemSubcategories: service.exportCsv,
|
||||||
getItemSubcategoriesById: service.getOne,
|
getItemSubcategoriesById: service.getOne,
|
||||||
updateItemSubcategories: service.updateOne,
|
updateItemSubcategories: service.updateOne,
|
||||||
deleteItemSubcategories: service.removeOne,
|
deleteItemSubcategories: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema: baseListQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema: baseListQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
item_category_id: Joi.number().integer().positive().required(),
|
item_category_id: Joi.number().integer().positive().required(),
|
||||||
@ -19,4 +19,6 @@ const listQuerySchema = baseListQuerySchema.keys({
|
|||||||
item_category_id: Joi.number().integer().positive().optional(),
|
item_category_id: Joi.number().integer().positive().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
const exportQuerySchema = toExportQuerySchema(listQuerySchema);
|
||||||
|
|
||||||
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'items deleted successfully'));
|
res.json(new ApiResponse(200, null, 'items deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportItems(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="items-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./items.controller');
|
const controller = require('./items.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./items.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./items.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -3,6 +3,8 @@ const ApiError = require('../../../utils/ApiError');
|
|||||||
const auditLog = require('../../../utils/auditLog');
|
const auditLog = require('../../../utils/auditLog');
|
||||||
const { getPagination } = require('../../../utils/pagination');
|
const { getPagination } = require('../../../utils/pagination');
|
||||||
const { nextDocumentNumber } = require('../../../utils/generateCode');
|
const { nextDocumentNumber } = require('../../../utils/generateCode');
|
||||||
|
const { parseId } = require('../../../utils/parseId');
|
||||||
|
const { rowsToCsv } = require('../../../utils/csv');
|
||||||
|
|
||||||
const ITEM_SERIES_CODE = 'ITEM';
|
const ITEM_SERIES_CODE = 'ITEM';
|
||||||
|
|
||||||
@ -200,10 +202,7 @@ const createItems = async (payload, userId, requestId) => {
|
|||||||
return sanitizeItem(created);
|
return sanitizeItem(created);
|
||||||
};
|
};
|
||||||
|
|
||||||
const listItems = async (query) => {
|
const buildItemsWhere = (query) => ({
|
||||||
const { page, limit, skip } = getPagination(query);
|
|
||||||
|
|
||||||
const where = {
|
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||||
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
|
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
|
||||||
@ -214,7 +213,11 @@ const listItems = async (query) => {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
});
|
||||||
|
|
||||||
|
const listItems = async (query) => {
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
const where = buildItemsWhere(query);
|
||||||
|
|
||||||
const [rows, total] = await Promise.all([
|
const [rows, total] = await Promise.all([
|
||||||
prisma.items.findMany({
|
prisma.items.findMany({
|
||||||
@ -230,9 +233,36 @@ const listItems = async (query) => {
|
|||||||
return { data: rows.map(sanitizeItem), meta: { page, limit, total } };
|
return { data: rows.map(sanitizeItem), meta: { page, limit, total } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportItems = async (query) => {
|
||||||
|
const rows = await prisma.items.findMany({
|
||||||
|
where: buildItemsWhere(query),
|
||||||
|
include: itemInclude,
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return rowsToCsv(
|
||||||
|
[
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'item_code', header: 'Item Code' },
|
||||||
|
{ key: 'item_name', header: 'Item Name' },
|
||||||
|
{ key: (row) => row.item_category?.code || '', header: 'Category Code' },
|
||||||
|
{ key: (row) => row.item_category?.name || '', header: 'Category Name' },
|
||||||
|
{ key: (row) => row.item_subcategory?.code || '', header: 'Subcategory Code' },
|
||||||
|
{ key: (row) => row.uom?.code || '', header: 'UOM' },
|
||||||
|
{ key: (row) => row.hsn_code?.code || '', header: 'HSN Code' },
|
||||||
|
{ key: (row) => (row.gst_rate ? row.gst_rate.rate_pct : ''), header: 'GST %' },
|
||||||
|
{ key: (row) => row.brand?.name || '', header: 'Brand' },
|
||||||
|
{ key: 'is_asset_item', header: 'Is Asset Item' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
],
|
||||||
|
rows.map(sanitizeItem)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getItemsById = async (id) => {
|
const getItemsById = async (id) => {
|
||||||
const one = await prisma.items.findFirst({
|
const one = await prisma.items.findFirst({
|
||||||
where: { id: BigInt(id), deleted_at: null },
|
where: { id: parseId(id), deleted_at: null },
|
||||||
include: itemInclude,
|
include: itemInclude,
|
||||||
});
|
});
|
||||||
if (!one) throw new ApiError(404, 'items not found');
|
if (!one) throw new ApiError(404, 'items not found');
|
||||||
@ -240,6 +270,7 @@ const getItemsById = async (id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateItems = async (id, payload, userId, requestId) => {
|
const updateItems = async (id, payload, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getItemsById(id);
|
const existing = await getItemsById(id);
|
||||||
const data = normalizePayload(payload);
|
const data = normalizePayload(payload);
|
||||||
|
|
||||||
@ -251,7 +282,7 @@ const updateItems = async (id, payload, userId, requestId) => {
|
|||||||
data.updated_by = userId ? BigInt(userId) : null;
|
data.updated_by = userId ? BigInt(userId) : null;
|
||||||
|
|
||||||
const updated = await prisma.items.update({
|
const updated = await prisma.items.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data,
|
data,
|
||||||
include: itemInclude,
|
include: itemInclude,
|
||||||
});
|
});
|
||||||
@ -270,10 +301,11 @@ const updateItems = async (id, payload, userId, requestId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteItems = async (id, userId, requestId) => {
|
const deleteItems = async (id, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getItemsById(id);
|
const existing = await getItemsById(id);
|
||||||
|
|
||||||
await prisma.items.update({
|
await prisma.items.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data: {
|
data: {
|
||||||
deleted_at: new Date(),
|
deleted_at: new Date(),
|
||||||
updated_by: userId ? BigInt(userId) : null,
|
updated_by: userId ? BigInt(userId) : null,
|
||||||
@ -294,6 +326,7 @@ const deleteItems = async (id, userId, requestId) => {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createItems,
|
createItems,
|
||||||
listItems,
|
listItems,
|
||||||
|
exportItems,
|
||||||
getItemsById,
|
getItemsById,
|
||||||
updateItems,
|
updateItems,
|
||||||
deleteItems,
|
deleteItems,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterName, listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
item_name: masterName({ max: 200, required: true }),
|
item_name: masterName({ max: 200, required: true }),
|
||||||
@ -37,4 +37,6 @@ const listQuerySchemaExtended = listQuerySchema.keys({
|
|||||||
item_category_id: Joi.number().integer().positive().optional(),
|
item_category_id: Joi.number().integer().positive().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema: listQuerySchemaExtended };
|
const exportQuerySchema = toExportQuerySchema(listQuerySchemaExtended);
|
||||||
|
|
||||||
|
module.exports = { createSchema, updateSchema, listQuerySchema: listQuerySchemaExtended, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'Location deleted successfully'));
|
res.json(new ApiResponse(200, null, 'Location deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportLocations(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="locations-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,19 @@ const {
|
|||||||
createLocationSchema,
|
createLocationSchema,
|
||||||
updateLocationSchema,
|
updateLocationSchema,
|
||||||
listQuerySchema,
|
listQuerySchema,
|
||||||
|
exportQuerySchema,
|
||||||
} = require('./locations.validation');
|
} = require('./locations.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createLocationSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createLocationSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateLocationSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateLocationSchema), controller.update);
|
||||||
|
|||||||
@ -2,6 +2,8 @@ const prisma = require('../../../config/prisma');
|
|||||||
const ApiError = require('../../../utils/ApiError');
|
const ApiError = require('../../../utils/ApiError');
|
||||||
const auditLog = require('../../../utils/auditLog');
|
const auditLog = require('../../../utils/auditLog');
|
||||||
const { getPagination } = require('../../../utils/pagination');
|
const { getPagination } = require('../../../utils/pagination');
|
||||||
|
const { parseId } = require('../../../utils/parseId');
|
||||||
|
const { rowsToCsv } = require('../../../utils/csv');
|
||||||
const {
|
const {
|
||||||
LOCATION_TYPES,
|
LOCATION_TYPES,
|
||||||
assertPlant,
|
assertPlant,
|
||||||
@ -55,7 +57,7 @@ const assertUniqueCode = async (code, excludeId = null) => {
|
|||||||
where: {
|
where: {
|
||||||
code,
|
code,
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
...(excludeId ? { id: { not: BigInt(excludeId) } } : {}),
|
...(excludeId ? { id: { not: parseId(excludeId) } } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (existing) throw new ApiError(409, 'Location code already exists');
|
if (existing) throw new ApiError(409, 'Location code already exists');
|
||||||
@ -98,10 +100,8 @@ const createLocation = async (type, payload, userId, requestId) => {
|
|||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listLocations = async (query, type = null) => {
|
const buildLocationsWhere = (query, type = null) => {
|
||||||
const { page, limit, skip } = getPagination(query);
|
|
||||||
const effectiveType = type || query.type || null;
|
const effectiveType = type || query.type || null;
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
...(effectiveType ? { type: effectiveType } : {}),
|
...(effectiveType ? { type: effectiveType } : {}),
|
||||||
@ -116,6 +116,12 @@ const listLocations = async (query, type = null) => {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
return { where, effectiveType };
|
||||||
|
};
|
||||||
|
|
||||||
|
const listLocations = async (query, type = null) => {
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
const { where, effectiveType } = buildLocationsWhere(query, type);
|
||||||
|
|
||||||
const [rows, total] = await Promise.all([
|
const [rows, total] = await Promise.all([
|
||||||
prisma.locations.findMany({
|
prisma.locations.findMany({
|
||||||
@ -140,10 +146,62 @@ const listLocations = async (query, type = null) => {
|
|||||||
return { data: rows.map(mapRow), meta: { page, limit, total } };
|
return { data: rows.map(mapRow), meta: { page, limit, total } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportLocations = async (query, type = null) => {
|
||||||
|
const { where, effectiveType } = buildLocationsWhere(query, type);
|
||||||
|
const rows = await prisma.locations.findMany({
|
||||||
|
where,
|
||||||
|
include:
|
||||||
|
effectiveType === LOCATION_TYPES.WAREHOUSE || !effectiveType
|
||||||
|
? warehouseInclude
|
||||||
|
: undefined,
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapRow = (row) => {
|
||||||
|
if (effectiveType === LOCATION_TYPES.PLANT) return toPlantResponse(row);
|
||||||
|
if (effectiveType === LOCATION_TYPES.WAREHOUSE) return toWarehouseResponse(row);
|
||||||
|
return toLocationResponse(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns =
|
||||||
|
effectiveType === LOCATION_TYPES.WAREHOUSE
|
||||||
|
? [
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'code', header: 'Code' },
|
||||||
|
{ key: 'name', header: 'Name' },
|
||||||
|
{ key: (row) => row.parent?.code || row.plant?.code || '', header: 'Plant Code' },
|
||||||
|
{ key: (row) => row.parent?.name || row.plant?.name || '', header: 'Plant Name' },
|
||||||
|
{ key: 'location', header: 'Location' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
]
|
||||||
|
: effectiveType === LOCATION_TYPES.PLANT
|
||||||
|
? [
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'code', header: 'Code' },
|
||||||
|
{ key: 'name', header: 'Name' },
|
||||||
|
{ key: 'gstin', header: 'GSTIN' },
|
||||||
|
{ key: 'city', header: 'City' },
|
||||||
|
{ key: 'state', header: 'State' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'type', header: 'Type' },
|
||||||
|
{ key: 'code', header: 'Code' },
|
||||||
|
{ key: 'name', header: 'Name' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return rowsToCsv(columns, rows.map(mapRow));
|
||||||
|
};
|
||||||
|
|
||||||
const getLocationById = async (id, type = null) => {
|
const getLocationById = async (id, type = null) => {
|
||||||
const row = await prisma.locations.findFirst({
|
const row = await prisma.locations.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: BigInt(id),
|
id: parseId(id),
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
...(type ? { type } : {}),
|
...(type ? { type } : {}),
|
||||||
},
|
},
|
||||||
@ -157,8 +215,9 @@ const getLocationById = async (id, type = null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateLocation = async (id, type, payload, userId, requestId) => {
|
const updateLocation = async (id, type, payload, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await prisma.locations.findFirst({
|
const existing = await prisma.locations.findFirst({
|
||||||
where: { id: BigInt(id), deleted_at: null, type },
|
where: { id: idBigInt, deleted_at: null, type },
|
||||||
include: warehouseInclude,
|
include: warehouseInclude,
|
||||||
});
|
});
|
||||||
if (!existing) throw new ApiError(404, `${type} not found`);
|
if (!existing) throw new ApiError(404, `${type} not found`);
|
||||||
@ -180,7 +239,7 @@ const updateLocation = async (id, type, payload, userId, requestId) => {
|
|||||||
data.updated_by = userId ? BigInt(userId) : null;
|
data.updated_by = userId ? BigInt(userId) : null;
|
||||||
|
|
||||||
const updated = await prisma.locations.update({
|
const updated = await prisma.locations.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data,
|
data,
|
||||||
include: type === LOCATION_TYPES.WAREHOUSE ? warehouseInclude : undefined,
|
include: type === LOCATION_TYPES.WAREHOUSE ? warehouseInclude : undefined,
|
||||||
});
|
});
|
||||||
@ -204,14 +263,15 @@ const updateLocation = async (id, type, payload, userId, requestId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteLocation = async (id, type, userId, requestId) => {
|
const deleteLocation = async (id, type, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await prisma.locations.findFirst({
|
const existing = await prisma.locations.findFirst({
|
||||||
where: { id: BigInt(id), deleted_at: null, type },
|
where: { id: idBigInt, deleted_at: null, type },
|
||||||
include: warehouseInclude,
|
include: warehouseInclude,
|
||||||
});
|
});
|
||||||
if (!existing) throw new ApiError(404, `${type} not found`);
|
if (!existing) throw new ApiError(404, `${type} not found`);
|
||||||
|
|
||||||
await prisma.locations.update({
|
await prisma.locations.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data: { deleted_at: new Date(), updated_by: userId ? BigInt(userId) : null },
|
data: { deleted_at: new Date(), updated_by: userId ? BigInt(userId) : null },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -231,7 +291,7 @@ const deleteLocation = async (id, type, userId, requestId) => {
|
|||||||
|
|
||||||
const deleteLocationById = async (id, userId, requestId) => {
|
const deleteLocationById = async (id, userId, requestId) => {
|
||||||
const existing = await prisma.locations.findFirst({
|
const existing = await prisma.locations.findFirst({
|
||||||
where: { id: BigInt(id), deleted_at: null },
|
where: { id: parseId(id), deleted_at: null },
|
||||||
include: warehouseInclude,
|
include: warehouseInclude,
|
||||||
});
|
});
|
||||||
if (!existing) throw new ApiError(404, 'Location not found');
|
if (!existing) throw new ApiError(404, 'Location not found');
|
||||||
@ -240,7 +300,7 @@ const deleteLocationById = async (id, userId, requestId) => {
|
|||||||
|
|
||||||
const updateLocationById = async (id, payload, userId, requestId) => {
|
const updateLocationById = async (id, payload, userId, requestId) => {
|
||||||
const existing = await prisma.locations.findFirst({
|
const existing = await prisma.locations.findFirst({
|
||||||
where: { id: BigInt(id), deleted_at: null },
|
where: { id: parseId(id), deleted_at: null },
|
||||||
});
|
});
|
||||||
if (!existing) throw new ApiError(404, 'Location not found');
|
if (!existing) throw new ApiError(404, 'Location not found');
|
||||||
return updateLocation(id, existing.type, payload, userId, requestId);
|
return updateLocation(id, existing.type, payload, userId, requestId);
|
||||||
@ -251,6 +311,7 @@ module.exports = {
|
|||||||
createPlant: (payload, userId, requestId) =>
|
createPlant: (payload, userId, requestId) =>
|
||||||
createLocation(LOCATION_TYPES.PLANT, payload, userId, requestId),
|
createLocation(LOCATION_TYPES.PLANT, payload, userId, requestId),
|
||||||
listPlants: (query) => listLocations(query, LOCATION_TYPES.PLANT),
|
listPlants: (query) => listLocations(query, LOCATION_TYPES.PLANT),
|
||||||
|
exportPlants: (query) => exportLocations(query, LOCATION_TYPES.PLANT),
|
||||||
getPlantById: (id) => getLocationById(id, LOCATION_TYPES.PLANT),
|
getPlantById: (id) => getLocationById(id, LOCATION_TYPES.PLANT),
|
||||||
updatePlant: (id, payload, userId, requestId) =>
|
updatePlant: (id, payload, userId, requestId) =>
|
||||||
updateLocation(id, LOCATION_TYPES.PLANT, payload, userId, requestId),
|
updateLocation(id, LOCATION_TYPES.PLANT, payload, userId, requestId),
|
||||||
@ -259,6 +320,7 @@ module.exports = {
|
|||||||
createWarehouse: (payload, userId, requestId) =>
|
createWarehouse: (payload, userId, requestId) =>
|
||||||
createLocation(LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
createLocation(LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
||||||
listWarehouses: (query) => listLocations(query, LOCATION_TYPES.WAREHOUSE),
|
listWarehouses: (query) => listLocations(query, LOCATION_TYPES.WAREHOUSE),
|
||||||
|
exportWarehouses: (query) => exportLocations(query, LOCATION_TYPES.WAREHOUSE),
|
||||||
getWarehouseById: (id) => getLocationById(id, LOCATION_TYPES.WAREHOUSE),
|
getWarehouseById: (id) => getLocationById(id, LOCATION_TYPES.WAREHOUSE),
|
||||||
updateWarehouse: (id, payload, userId, requestId) =>
|
updateWarehouse: (id, payload, userId, requestId) =>
|
||||||
updateLocation(id, LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
updateLocation(id, LOCATION_TYPES.WAREHOUSE, payload, userId, requestId),
|
||||||
@ -272,6 +334,7 @@ module.exports = {
|
|||||||
return createLocation(payload.type, payload, userId, requestId);
|
return createLocation(payload.type, payload, userId, requestId);
|
||||||
},
|
},
|
||||||
listLocations: (query) => listLocations(query),
|
listLocations: (query) => listLocations(query),
|
||||||
|
exportLocations: (query) => exportLocations(query),
|
||||||
getLocationById: (id) => getLocationById(id),
|
getLocationById: (id) => getLocationById(id),
|
||||||
updateLocationById,
|
updateLocationById,
|
||||||
deleteLocationById,
|
deleteLocationById,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, toExportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const locationFields = {
|
const locationFields = {
|
||||||
gstin: Joi.string().max(15).allow(null, '').optional(),
|
gstin: Joi.string().max(15).allow(null, '').optional(),
|
||||||
@ -92,6 +92,8 @@ const listLocationsQuerySchema = listQuerySchema.keys({
|
|||||||
parent_id: Joi.number().integer().positive().optional(),
|
parent_id: Joi.number().integer().positive().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const exportQuerySchema = toExportQuerySchema(listLocationsQuerySchema);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createPlantSchema,
|
createPlantSchema,
|
||||||
updatePlantSchema,
|
updatePlantSchema,
|
||||||
@ -100,4 +102,5 @@ module.exports = {
|
|||||||
createLocationSchema,
|
createLocationSchema,
|
||||||
updateLocationSchema,
|
updateLocationSchema,
|
||||||
listQuerySchema: listLocationsQuerySchema,
|
listQuerySchema: listLocationsQuerySchema,
|
||||||
|
exportQuerySchema,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'payment_terms deleted successfully'));
|
res.json(new ApiResponse(200, null, 'payment_terms deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportPaymentTerms(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="payment-terms-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./payment-terms.controller');
|
const controller = require('./payment-terms.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./payment-terms.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./payment-terms.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -56,6 +56,7 @@ const service = buildMasterService(config);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createPaymentTerms: service.createOne,
|
createPaymentTerms: service.createOne,
|
||||||
listPaymentTerms: service.list,
|
listPaymentTerms: service.list,
|
||||||
|
exportPaymentTerms: service.exportCsv,
|
||||||
getPaymentTermsById: service.getOne,
|
getPaymentTermsById: service.getOne,
|
||||||
updatePaymentTerms: service.updateOne,
|
updatePaymentTerms: service.updateOne,
|
||||||
deletePaymentTerms: service.removeOne,
|
deletePaymentTerms: service.removeOne,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createSchema = Joi.object({
|
const createSchema = Joi.object({
|
||||||
code: masterCode({ max: 30 }),
|
code: masterCode({ max: 30 }),
|
||||||
@ -17,4 +17,4 @@ const updateSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'plants deleted successfully'));
|
res.json(new ApiResponse(200, null, 'plants deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportPlants(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="plants-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./plants.controller');
|
const controller = require('./plants.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./plants.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./plants.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ const service = require('../locations/locations.service');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createPlants: service.createPlant,
|
createPlants: service.createPlant,
|
||||||
listPlants: service.listPlants,
|
listPlants: service.listPlants,
|
||||||
|
exportPlants: service.exportPlants,
|
||||||
getPlantsById: service.getPlantById,
|
getPlantsById: service.getPlantById,
|
||||||
updatePlants: service.updatePlant,
|
updatePlants: service.updatePlant,
|
||||||
deletePlants: service.deletePlant,
|
deletePlants: service.deletePlant,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const {
|
|||||||
createPlantSchema: createSchema,
|
createPlantSchema: createSchema,
|
||||||
updatePlantSchema: updateSchema,
|
updatePlantSchema: updateSchema,
|
||||||
listQuerySchema,
|
listQuerySchema,
|
||||||
|
exportQuerySchema,
|
||||||
} = require('../locations/locations.validation');
|
} = require('../locations/locations.validation');
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'UOM deleted successfully'));
|
res.json(new ApiResponse(200, null, 'UOM deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportUom(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="uom-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./uom.controller');
|
const controller = require('./uom.controller');
|
||||||
const { createUomSchema, updateUomSchema, listUomQuerySchema } = require('./uom.validation');
|
const { createUomSchema, updateUomSchema, listUomQuerySchema, exportUomQuerySchema } = require('./uom.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -15,6 +15,12 @@ router.get(
|
|||||||
validate(listUomQuerySchema, 'query'),
|
validate(listUomQuerySchema, 'query'),
|
||||||
controller.list
|
controller.list
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportUomQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createUomSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createUomSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateUomSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateUomSchema), controller.update);
|
||||||
|
|||||||
@ -2,6 +2,21 @@ const prisma = require('../../../config/prisma');
|
|||||||
const ApiError = require('../../../utils/ApiError');
|
const ApiError = require('../../../utils/ApiError');
|
||||||
const auditLog = require('../../../utils/auditLog');
|
const auditLog = require('../../../utils/auditLog');
|
||||||
const { getPagination } = require('../../../utils/pagination');
|
const { getPagination } = require('../../../utils/pagination');
|
||||||
|
const { parseId } = require('../../../utils/parseId');
|
||||||
|
const { rowsToCsv } = require('../../../utils/csv');
|
||||||
|
|
||||||
|
const buildUomWhere = (query) => ({
|
||||||
|
deleted_at: null,
|
||||||
|
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||||
|
...(query.search
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ code: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
{ name: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
|
||||||
const createUom = async (payload, userId, requestId) => {
|
const createUom = async (payload, userId, requestId) => {
|
||||||
const exists = await prisma.uom.findFirst({
|
const exists = await prisma.uom.findFirst({
|
||||||
@ -32,18 +47,7 @@ const createUom = async (payload, userId, requestId) => {
|
|||||||
|
|
||||||
const listUom = async (query) => {
|
const listUom = async (query) => {
|
||||||
const { page, limit, skip } = getPagination(query);
|
const { page, limit, skip } = getPagination(query);
|
||||||
const where = {
|
const where = buildUomWhere(query);
|
||||||
deleted_at: null,
|
|
||||||
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
|
||||||
...(query.search
|
|
||||||
? {
|
|
||||||
OR: [
|
|
||||||
{ code: { contains: query.search, mode: 'insensitive' } },
|
|
||||||
{ name: { contains: query.search, mode: 'insensitive' } },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.uom.findMany({ where, orderBy: { created_at: 'desc' }, skip, take: limit }),
|
prisma.uom.findMany({ where, orderBy: { created_at: 'desc' }, skip, take: limit }),
|
||||||
@ -53,16 +57,34 @@ const listUom = async (query) => {
|
|||||||
return { data, meta: { page, limit, total } };
|
return { data, meta: { page, limit, total } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportUom = async (query) => {
|
||||||
|
const rows = await prisma.uom.findMany({
|
||||||
|
where: buildUomWhere(query),
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
});
|
||||||
|
return rowsToCsv(
|
||||||
|
[
|
||||||
|
{ key: 'id', header: 'ID' },
|
||||||
|
{ key: 'code', header: 'Code' },
|
||||||
|
{ key: 'name', header: 'Name' },
|
||||||
|
{ key: 'is_active', header: 'Is Active' },
|
||||||
|
{ key: 'created_at', header: 'Created At' },
|
||||||
|
],
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getUomById = async (id) => {
|
const getUomById = async (id) => {
|
||||||
const uom = await prisma.uom.findFirst({ where: { id: BigInt(id), deleted_at: null } });
|
const uom = await prisma.uom.findFirst({ where: { id: parseId(id), deleted_at: null } });
|
||||||
if (!uom) throw new ApiError(404, 'UOM not found');
|
if (!uom) throw new ApiError(404, 'UOM not found');
|
||||||
return uom;
|
return uom;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUom = async (id, payload, userId, requestId) => {
|
const updateUom = async (id, payload, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getUomById(id);
|
const existing = await getUomById(id);
|
||||||
const updated = await prisma.uom.update({
|
const updated = await prisma.uom.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data: {
|
data: {
|
||||||
...payload,
|
...payload,
|
||||||
...(payload.code ? { code: payload.code.toUpperCase().trim() } : {}),
|
...(payload.code ? { code: payload.code.toUpperCase().trim() } : {}),
|
||||||
@ -84,9 +106,10 @@ const updateUom = async (id, payload, userId, requestId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteUom = async (id, userId, requestId) => {
|
const deleteUom = async (id, userId, requestId) => {
|
||||||
|
const idBigInt = parseId(id);
|
||||||
const existing = await getUomById(id);
|
const existing = await getUomById(id);
|
||||||
await prisma.uom.update({
|
await prisma.uom.update({
|
||||||
where: { id: BigInt(id) },
|
where: { id: idBigInt },
|
||||||
data: { deleted_at: new Date(), updated_by: userId ? BigInt(userId) : null },
|
data: { deleted_at: new Date(), updated_by: userId ? BigInt(userId) : null },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,4 +124,4 @@ const deleteUom = async (id, userId, requestId) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { createUom, listUom, getUomById, updateUom, deleteUom };
|
module.exports = { createUom, listUom, exportUom, getUomById, updateUom, deleteUom };
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
|
const { masterCode, masterName, listQuerySchema, exportQuerySchema } = require('../_shared/masters.validation');
|
||||||
|
|
||||||
const createUomSchema = Joi.object({
|
const createUomSchema = Joi.object({
|
||||||
code: masterCode({ max: 20, required: true }),
|
code: masterCode({ max: 20, required: true }),
|
||||||
@ -13,4 +13,4 @@ const updateUomSchema = Joi.object({
|
|||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
}).min(1);
|
}).min(1);
|
||||||
|
|
||||||
module.exports = { createUomSchema, updateUomSchema, listUomQuerySchema: listQuerySchema };
|
module.exports = { createUomSchema, updateUomSchema, listUomQuerySchema: listQuerySchema, exportUomQuerySchema: exportQuerySchema };
|
||||||
|
|||||||
@ -27,4 +27,12 @@ const remove = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, null, 'warehouses deleted successfully'));
|
res.json(new ApiResponse(200, null, 'warehouses deleted successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { create, list, getOne, update, remove };
|
const exportCsv = asyncHandler(async (req, res) => {
|
||||||
|
const csv = await service.exportWarehouses(req.query);
|
||||||
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="warehouses-export.csv"');
|
||||||
|
res.send(csv);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { create, list, exportCsv, getOne, update, remove };
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,18 @@ const authenticate = require('../../../middlewares/auth.middleware');
|
|||||||
const authorize = require('../../../middlewares/rbac.middleware');
|
const authorize = require('../../../middlewares/rbac.middleware');
|
||||||
const validate = require('../../../middlewares/validate.middleware');
|
const validate = require('../../../middlewares/validate.middleware');
|
||||||
const controller = require('./warehouses.controller');
|
const controller = require('./warehouses.controller');
|
||||||
const { createSchema, updateSchema, listQuerySchema } = require('./warehouses.validation');
|
const { createSchema, updateSchema, listQuerySchema, exportQuerySchema } = require('./warehouses.validation');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(authenticate);
|
router.use(authenticate);
|
||||||
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
|
||||||
|
router.get(
|
||||||
|
'/export',
|
||||||
|
authorize('MASTERS', 'export'),
|
||||||
|
validate(exportQuerySchema, 'query'),
|
||||||
|
controller.exportCsv
|
||||||
|
);
|
||||||
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
|
||||||
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
|
||||||
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ const service = require('../locations/locations.service');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createWarehouses: service.createWarehouse,
|
createWarehouses: service.createWarehouse,
|
||||||
listWarehouses: service.listWarehouses,
|
listWarehouses: service.listWarehouses,
|
||||||
|
exportWarehouses: service.exportWarehouses,
|
||||||
getWarehousesById: service.getWarehouseById,
|
getWarehousesById: service.getWarehouseById,
|
||||||
updateWarehouses: service.updateWarehouse,
|
updateWarehouses: service.updateWarehouse,
|
||||||
deleteWarehouses: service.deleteWarehouse,
|
deleteWarehouses: service.deleteWarehouse,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const {
|
|||||||
createWarehouseSchema: createSchema,
|
createWarehouseSchema: createSchema,
|
||||||
updateWarehouseSchema: updateSchema,
|
updateWarehouseSchema: updateSchema,
|
||||||
listQuerySchema,
|
listQuerySchema,
|
||||||
|
exportQuerySchema,
|
||||||
} = require('../locations/locations.validation');
|
} = require('../locations/locations.validation');
|
||||||
|
|
||||||
module.exports = { createSchema, updateSchema, listQuerySchema };
|
module.exports = { createSchema, updateSchema, listQuerySchema, exportQuerySchema };
|
||||||
|
|||||||
@ -17,6 +17,11 @@ const uploadCompanyLogo = asyncHandler(async (req, res) => {
|
|||||||
res.json(new ApiResponse(200, data, 'Company logo updated successfully'));
|
res.json(new ApiResponse(200, data, 'Company logo updated successfully'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const uploadCompanyFavicon = asyncHandler(async (req, res) => {
|
||||||
|
const data = await service.updateCompanyFavicon(req.file, req.user?.id, req.id);
|
||||||
|
res.json(new ApiResponse(200, data, 'Company favicon updated successfully'));
|
||||||
|
});
|
||||||
|
|
||||||
const getEmailSettings = asyncHandler(async (_req, res) => {
|
const getEmailSettings = asyncHandler(async (_req, res) => {
|
||||||
const data = await service.getEmailSettings();
|
const data = await service.getEmailSettings();
|
||||||
res.json(new ApiResponse(200, data, 'Email settings fetched'));
|
res.json(new ApiResponse(200, data, 'Email settings fetched'));
|
||||||
@ -31,6 +36,7 @@ module.exports = {
|
|||||||
getCompany,
|
getCompany,
|
||||||
updateCompany,
|
updateCompany,
|
||||||
uploadCompanyLogo,
|
uploadCompanyLogo,
|
||||||
|
uploadCompanyFavicon,
|
||||||
getEmailSettings,
|
getEmailSettings,
|
||||||
updateEmailSettings,
|
updateEmailSettings,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,6 +18,12 @@ router.post(
|
|||||||
upload.single('logo'),
|
upload.single('logo'),
|
||||||
controller.uploadCompanyLogo
|
controller.uploadCompanyLogo
|
||||||
);
|
);
|
||||||
|
router.post(
|
||||||
|
'/company/favicon',
|
||||||
|
authorize('SETTINGS', 'edit'),
|
||||||
|
upload.single('favicon'),
|
||||||
|
controller.uploadCompanyFavicon
|
||||||
|
);
|
||||||
|
|
||||||
router.get('/email', authorize('SETTINGS', 'view'), controller.getEmailSettings);
|
router.get('/email', authorize('SETTINGS', 'view'), controller.getEmailSettings);
|
||||||
router.put('/email', authorize('SETTINGS', 'edit'), validate(emailUpdateSchema), controller.updateEmailSettings);
|
router.put('/email', authorize('SETTINGS', 'edit'), validate(emailUpdateSchema), controller.updateEmailSettings);
|
||||||
|
|||||||
@ -9,12 +9,20 @@ const { encrypt, decrypt } = require('../../utils/encryption');
|
|||||||
const COMPANY_ID = BigInt(1);
|
const COMPANY_ID = BigInt(1);
|
||||||
const EMAIL_SETTINGS_ID = BigInt(1);
|
const EMAIL_SETTINGS_ID = BigInt(1);
|
||||||
|
|
||||||
const buildLogoUrl = (logoPath) => {
|
const buildPublicUrl = (filePath) => {
|
||||||
if (!logoPath) return null;
|
if (!filePath) return null;
|
||||||
const normalized = logoPath.replace(/\\/g, '/');
|
const normalized = filePath.replace(/\\/g, '/');
|
||||||
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unlinkIfExists = (relativePath) => {
|
||||||
|
if (!relativePath) return;
|
||||||
|
const absolute = path.resolve(process.cwd(), relativePath);
|
||||||
|
if (fs.existsSync(absolute)) {
|
||||||
|
fs.unlinkSync(absolute);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const sanitizeCompany = (row) => {
|
const sanitizeCompany = (row) => {
|
||||||
if (!row) return null;
|
if (!row) return null;
|
||||||
return {
|
return {
|
||||||
@ -25,7 +33,9 @@ const sanitizeCompany = (row) => {
|
|||||||
email: row.email,
|
email: row.email,
|
||||||
website: row.website,
|
website: row.website,
|
||||||
logo_path: row.logo_path,
|
logo_path: row.logo_path,
|
||||||
logo_url: buildLogoUrl(row.logo_path),
|
logo_url: buildPublicUrl(row.logo_path),
|
||||||
|
favicon_path: row.favicon_path,
|
||||||
|
favicon_url: buildPublicUrl(row.favicon_path),
|
||||||
address: row.address,
|
address: row.address,
|
||||||
city: row.city,
|
city: row.city,
|
||||||
state: row.state,
|
state: row.state,
|
||||||
@ -106,13 +116,7 @@ const updateCompanyLogo = async (file, userId, requestId) => {
|
|||||||
if (!file) throw new ApiError(400, 'Logo file is required');
|
if (!file) throw new ApiError(400, 'Logo file is required');
|
||||||
|
|
||||||
const existing = await getOrCreateCompany();
|
const existing = await getOrCreateCompany();
|
||||||
|
unlinkIfExists(existing.logo_path);
|
||||||
if (existing.logo_path) {
|
|
||||||
const oldPath = path.resolve(process.cwd(), existing.logo_path);
|
|
||||||
if (fs.existsSync(oldPath)) {
|
|
||||||
fs.unlinkSync(oldPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logoPath = path.join(env.UPLOAD_DIR, file.filename).replace(/\\/g, '/');
|
const logoPath = path.join(env.UPLOAD_DIR, file.filename).replace(/\\/g, '/');
|
||||||
|
|
||||||
@ -137,6 +141,35 @@ const updateCompanyLogo = async (file, userId, requestId) => {
|
|||||||
return sanitizeCompany(updated);
|
return sanitizeCompany(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateCompanyFavicon = async (file, userId, requestId) => {
|
||||||
|
if (!file) throw new ApiError(400, 'Favicon file is required');
|
||||||
|
|
||||||
|
const existing = await getOrCreateCompany();
|
||||||
|
unlinkIfExists(existing.favicon_path);
|
||||||
|
|
||||||
|
const faviconPath = path.join(env.UPLOAD_DIR, file.filename).replace(/\\/g, '/');
|
||||||
|
|
||||||
|
const updated = await prisma.company.update({
|
||||||
|
where: { id: COMPANY_ID },
|
||||||
|
data: {
|
||||||
|
favicon_path: faviconPath,
|
||||||
|
updated_by: userId ? BigInt(userId) : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await auditLog({
|
||||||
|
tableName: 'company',
|
||||||
|
recordId: COMPANY_ID,
|
||||||
|
action: 'UPDATE',
|
||||||
|
oldValue: { favicon_path: existing.favicon_path },
|
||||||
|
newValue: { favicon_path: updated.favicon_path },
|
||||||
|
userId,
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return sanitizeCompany(updated);
|
||||||
|
};
|
||||||
|
|
||||||
const getEmailSettings = async () => sanitizeEmailSettings(await getOrCreateEmailSettings());
|
const getEmailSettings = async () => sanitizeEmailSettings(await getOrCreateEmailSettings());
|
||||||
|
|
||||||
const updateEmailSettings = async (payload, userId, requestId) => {
|
const updateEmailSettings = async (payload, userId, requestId) => {
|
||||||
@ -185,7 +218,8 @@ const getCompanyForDocuments = async () => {
|
|||||||
phone: row.mobile || '',
|
phone: row.mobile || '',
|
||||||
email: row.email || '',
|
email: row.email || '',
|
||||||
website: row.website || '',
|
website: row.website || '',
|
||||||
logo_url: buildLogoUrl(row.logo_path),
|
logo_url: buildPublicUrl(row.logo_path),
|
||||||
|
favicon_url: buildPublicUrl(row.favicon_path),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,6 +240,7 @@ module.exports = {
|
|||||||
getCompany,
|
getCompany,
|
||||||
updateCompany,
|
updateCompany,
|
||||||
updateCompanyLogo,
|
updateCompanyLogo,
|
||||||
|
updateCompanyFavicon,
|
||||||
getEmailSettings,
|
getEmailSettings,
|
||||||
updateEmailSettings,
|
updateEmailSettings,
|
||||||
getCompanyForDocuments,
|
getCompanyForDocuments,
|
||||||
|
|||||||
38
src/utils/csv.js
Normal file
38
src/utils/csv.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const escapeCsv = (value) => {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
const str = String(value);
|
||||||
|
if (/[",\n\r]/.test(str)) return `"${str.replace(/"/g, '""')}"`;
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCsvCell = (value) => {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
if (typeof value === 'bigint') return value.toString();
|
||||||
|
if (value instanceof Date) return value.toISOString();
|
||||||
|
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (typeof value.toJSON === 'function') return formatCsvCell(value.toJSON());
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<{ key: string|Function, header: string }>} columns
|
||||||
|
* @param {object[]} rows
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const rowsToCsv = (columns, rows) => {
|
||||||
|
const header = columns.map((col) => escapeCsv(col.header)).join(',');
|
||||||
|
const lines = rows.map((row) =>
|
||||||
|
columns
|
||||||
|
.map((col) => {
|
||||||
|
const raw = typeof col.key === 'function' ? col.key(row) : row[col.key];
|
||||||
|
return escapeCsv(formatCsvCell(raw));
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
);
|
||||||
|
return [header, ...lines].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { escapeCsv, formatCsvCell, rowsToCsv };
|
||||||
11
src/utils/parseId.js
Normal file
11
src/utils/parseId.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const ApiError = require('./ApiError');
|
||||||
|
|
||||||
|
/** @param {string|number|bigint} id */
|
||||||
|
const parseId = (id) => {
|
||||||
|
if (id === undefined || id === null) throw new ApiError(400, 'Invalid id');
|
||||||
|
const str = String(id).trim();
|
||||||
|
if (!/^\d+$/.test(str)) throw new ApiError(400, 'Invalid id');
|
||||||
|
return BigInt(str);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { parseId };
|
||||||
@ -281,6 +281,9 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
table.items thead th.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
table.items tbody td {
|
table.items tbody td {
|
||||||
padding: 9px 3px;
|
padding: 9px 3px;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
|||||||
@ -276,6 +276,9 @@ const generatePoHtml = (inputData = getDummyPoData()) => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
table.items thead th.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
table.items tbody td {
|
table.items tbody td {
|
||||||
padding: 10px 4px;
|
padding: 10px 4px;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user