Items code auto generated and hs_code masted added

This commit is contained in:
Gowtham M 2026-07-10 14:47:18 +05:30
parent 4bab5f8859
commit a434216fdb
14 changed files with 567 additions and 154 deletions

View File

@ -75,6 +75,7 @@ erp-backend/
│ │ │ ├── items/
│ │ │ ├── brands/
│ │ │ ├── gst-rates/
│ │ │ ├── hsn-codes/
│ │ │ ├── warehouses/
│ │ │ ├── payment-terms/
│ │ │ ├── delivery-terms/
@ -1193,6 +1194,7 @@ router.use('/item-subcategories', require('./item-subcategories/item-subcategori
router.use('/items', require('./items/items.routes'));
router.use('/brands', require('./brands/brands.routes'));
router.use('/gst-rates', require('./gst-rates/gst-rates.routes'));
router.use('/hsn-codes', require('./hsn-codes/hsn-codes.routes'));
router.use('/warehouses', require('./warehouses/warehouses.routes'));
router.use('/payment-terms', require('./payment-terms/payment-terms.routes'));
router.use('/delivery-terms', require('./delivery-terms/delivery-terms.routes'));
@ -1271,6 +1273,7 @@ BigInt.prototype.toJSON = function () {
/api/v1/masters/items
/api/v1/masters/brands
/api/v1/masters/gst-rates
/api/v1/masters/hsn-codes
/api/v1/masters/warehouses
/api/v1/masters/payment-terms
/api/v1/masters/delivery-terms

View File

@ -164,9 +164,10 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
| [x] | UOM | `/masters/uom` |
| [x] | Item Categories | `/masters/item-categories` | Optional asset defaults: `code_prefix`, `default_useful_life_years`, `default_depreciation_method` |
| [x] | Item Subcategories | `/masters/item-subcategories` | Filter: `item_category_id` — shared by items + assets |
| [x] | Items | `/masters/items` |
| [x] | Items | `/masters/items` | `item_code` auto via `ITEM` document series; HSN via `hsn_code_id` |
| [x] | Brands | `/masters/brands` |
| [x] | GST Rates | `/masters/gst-rates` |
| [x] | HSN Codes | `/masters/hsn-codes` | Search by `code` or `description` |
| [x] | Payment Terms | `/masters/payment-terms` |
| [x] | Delivery Terms | `/masters/delivery-terms` |
| [~] | Asset Categories | removed — use Item Categories |
@ -178,7 +179,7 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
| [x] | Warehouses | `/masters/warehouses` | Alias — warehouse locations only |
| [x] | Document Series | `/masters/document-series` |
**Masters total:** 12 modules × 5 endpoints (+ plants/warehouses aliases) — asset category masters retired; assets use item categories
**Masters total:** 13 modules × 5 endpoints (+ plants/warehouses aliases)
---

View File

@ -0,0 +1,12 @@
-- Remove items.hsn_code column; HSN is referenced via hsn_code_id -> hsn_codes master
BEGIN;
ALTER TABLE items
DROP COLUMN IF EXISTS hsn_code;
COMMIT;
-- Verify:
-- SELECT column_name FROM information_schema.columns
-- WHERE table_name = 'items' AND column_name LIKE '%hsn%';

View File

@ -0,0 +1,33 @@
-- Items: ITEM document series for auto item_code generation
-- Run before deploying BE that auto-generates item_code via document_series.
BEGIN;
INSERT INTO document_series (code, prefix, current_number, padding, description, is_active)
SELECT
'ITEM',
'ITM-',
COALESCE((
SELECT COUNT(*)::int
FROM items
WHERE deleted_at IS NULL
), 0),
5,
'Item master code',
TRUE
WHERE NOT EXISTS (
SELECT 1 FROM document_series WHERE code = 'ITEM'
);
UPDATE document_series ds
SET current_number = GREATEST(
ds.current_number,
COALESCE((SELECT COUNT(*)::int FROM items WHERE deleted_at IS NULL), 0)
),
updated_at = NOW()
WHERE ds.code = 'ITEM';
COMMIT;
-- Verify:
-- SELECT code, prefix, current_number, padding FROM document_series WHERE code = 'ITEM';

View File

@ -0,0 +1,40 @@
-- Seed common HSN/SAC codes for items and purchase orders
-- Idempotent: upserts on code
INSERT INTO hsn_codes (code, description, is_active)
VALUES
('27101990', 'Mineral oils and preparations', TRUE),
('28289040', 'Chlorides, bromides and iodides', TRUE),
('29051100', 'Methanol (methyl alcohol)', TRUE),
('34029099', 'Organic surface-active agents (other)', TRUE),
('39011010', 'Polyethylene, specific gravity < 0.94', TRUE),
('39201019', 'Plastic plates, sheets, film (other)', TRUE),
('39232990', 'Plastic sacks and bags (other)', TRUE),
('40103999', 'Transmission belts and V-belts (other)', TRUE),
('48191000', 'Cartons, boxes and cases of corrugated paper', TRUE),
('72142090', 'Bars and rods of iron or non-alloy steel (other)', TRUE),
('73181500', 'Screws and bolts with nuts or washers', TRUE),
('73269099', 'Other articles of iron or steel', TRUE),
('84148090', 'Air pumps, compressors and fans (parts/other)', TRUE),
('84198990', 'Machinery for treatment of materials by temperature (other)', TRUE),
('84219900', 'Parts for filtering or purifying machinery', TRUE),
('84283300', 'Conveyors (other)', TRUE),
('84748000', 'Machinery for crushing, grinding or mixing', TRUE),
('84799090', 'Parts of machines of heading 8479', TRUE),
('84818090', 'Taps, cocks, valves and similar appliances (other)', TRUE),
('85015210', 'AC motors, multi-phase, output <= 750 W', TRUE),
('85044090', 'Static converters (other)', TRUE),
('85369090', 'Electrical apparatus for switching (other)', TRUE),
('85444999', 'Electric conductors, insulated (other)', TRUE),
('87089900', 'Parts and accessories of motor vehicles', TRUE),
('998719', 'Maintenance and repair services of machinery', TRUE),
('998313', 'IT design and development services', TRUE)
ON CONFLICT (code) DO UPDATE
SET
description = EXCLUDED.description,
is_active = EXCLUDED.is_active,
updated_at = NOW();
-- Verify:
-- SELECT COUNT(*) FROM hsn_codes WHERE is_active = TRUE;
-- SELECT id, code, description FROM hsn_codes ORDER BY code;

View File

@ -6,6 +6,7 @@ tags:
- name: Items
- name: Brands
- name: GST Rates
- name: HSN Codes
- name: Payment Terms
- name: Delivery Terms
- name: Departments
@ -115,40 +116,45 @@ components:
is_active: { type: boolean, example: true }
ItemsCreateBody:
type: object
required: [item_code, item_name, item_category_id, uom_id, is_asset_item, min_order_qty, reorder_level]
required: [item_name, item_category_id, uom_id]
properties:
item_code: { type: string, example: 'RM-LABSA' }
item_name: { type: string, example: 'LABSA' }
item_category_id: { type: integer, example: 1 }
item_subcategory_id: { type: integer, example: 1 }
item_subcategory_id: { type: integer, nullable: true, example: 1 }
uom_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, example: 1 }
gst_rate_id: { type: integer, example: 4 }
brand_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, nullable: true, example: 1 }
gst_rate_id: { type: integer, nullable: true, example: 4 }
brand_id: { type: integer, nullable: true, example: 1 }
is_asset_item: { type: boolean, example: false }
description: { type: string, example: 'Raw material' }
specification: { type: string, example: '96% purity' }
min_order_qty: { type: number, example: 100.0 }
reorder_level: { type: number, example: 500.0 }
min_order_qty: { type: number, nullable: true, example: 100.0 }
reorder_level: { type: number, nullable: true, example: 500.0 }
is_active: { type: boolean, example: true }
ItemsUpdateBody:
type: object
minProperties: 1
properties:
item_code: { type: string, example: 'RM-LABSA' }
item_name: { type: string, example: 'LABSA' }
item_category_id: { type: integer, example: 1 }
item_subcategory_id: { type: integer, example: 1 }
item_subcategory_id: { type: integer, nullable: true, example: 1 }
uom_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, example: 1 }
gst_rate_id: { type: integer, example: 4 }
brand_id: { type: integer, example: 1 }
hsn_code_id: { type: integer, nullable: true, example: 1 }
gst_rate_id: { type: integer, nullable: true, example: 4 }
brand_id: { type: integer, nullable: true, example: 1 }
is_asset_item: { type: boolean, example: false }
description: { type: string, example: 'Raw material' }
specification: { type: string, example: '96% purity' }
min_order_qty: { type: number, example: 100.0 }
reorder_level: { type: number, example: 500.0 }
min_order_qty: { type: number, nullable: true, example: 100.0 }
reorder_level: { type: number, nullable: true, example: 500.0 }
is_active: { type: boolean, example: true }
ItemsResponse:
type: object
properties:
id: { type: string, example: '1' }
item_code: { type: string, example: 'ITM-00006', description: Auto-generated on create via ITEM document series; read-only }
item_name: { type: string, example: 'LABSA' }
hsn_code_id: { type: integer, nullable: true, example: 1 }
BrandsCreateBody:
type: object
required: [code, name, brand_type]
@ -185,6 +191,20 @@ components:
rate_pct: { type: number, example: 18 }
description: { type: string, example: 'GST 18%' }
is_active: { type: boolean, example: true }
HsnCodesCreateBody:
type: object
required: [code]
properties:
code: { type: string, example: '34029099', description: '48 digit HSN/SAC code' }
description: { type: string, example: 'Organic surface-active agents' }
is_active: { type: boolean, example: true }
HsnCodesUpdateBody:
type: object
minProperties: 1
properties:
code: { type: string, example: '34029099' }
description: { type: string, example: 'Organic surface-active agents' }
is_active: { type: boolean, example: true }
PaymentTermsCreateBody:
type: object
required: [code, name, credit_days]
@ -909,6 +929,75 @@ paths:
description: Deleted
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
/masters/hsn-codes:
get:
tags: [HSN Codes]
summary: List HSN Codes
parameters:
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: limit
schema: { type: integer, default: 20, maximum: 100 }
- in: query
name: search
schema: { type: string, description: Search by code or description }
- in: query
name: is_active
schema: { type: boolean }
responses:
"200":
description: List fetched
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
post:
tags: [HSN Codes]
summary: Create HSN Code
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/HsnCodesCreateBody" }
responses:
"201":
description: Created
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"409": { description: Duplicate code }
/masters/hsn-codes/{id}:
parameters:
- in: path
name: id
required: true
schema: { type: string }
get:
tags: [HSN Codes]
summary: Get HSN Code by id
responses:
"200":
description: Fetched
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
put:
tags: [HSN Codes]
summary: Update HSN Code
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/HsnCodesUpdateBody" }
responses:
"200":
description: Updated
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
delete:
tags: [HSN Codes]
summary: Delete HSN Code
responses:
"200":
description: Deleted
content: { application/json: { schema: { $ref: "#/components/schemas/ApiResponse" } } }
"404": { description: Not found }
/masters/payment-terms:
get:
tags: [Payment Terms]

View File

@ -0,0 +1,30 @@
const asyncHandler = require('../../../utils/asyncHandler');
const ApiResponse = require('../../../utils/ApiResponse');
const service = require('./hsn-codes.service');
const create = asyncHandler(async (req, res) => {
const data = await service.createHsnCodes(req.body, req.user?.id, req.id);
res.status(201).json(new ApiResponse(201, data, 'hsn_codes created successfully'));
});
const list = asyncHandler(async (req, res) => {
const result = await service.listHsnCodes(req.query);
res.json(new ApiResponse(200, result.data, 'hsn_codes list fetched', result.meta));
});
const getOne = asyncHandler(async (req, res) => {
const data = await service.getHsnCodesById(req.params.id);
res.json(new ApiResponse(200, data, 'hsn_codes fetched'));
});
const update = asyncHandler(async (req, res) => {
const data = await service.updateHsnCodes(req.params.id, req.body, req.user?.id, req.id);
res.json(new ApiResponse(200, data, 'hsn_codes updated successfully'));
});
const remove = asyncHandler(async (req, res) => {
await service.deleteHsnCodes(req.params.id, req.user?.id, req.id);
res.json(new ApiResponse(200, null, 'hsn_codes deleted successfully'));
});
module.exports = { create, list, getOne, update, remove };

View File

@ -0,0 +1,17 @@
const express = require('express');
const authenticate = require('../../../middlewares/auth.middleware');
const authorize = require('../../../middlewares/rbac.middleware');
const validate = require('../../../middlewares/validate.middleware');
const controller = require('./hsn-codes.controller');
const { createSchema, updateSchema, listQuerySchema } = require('./hsn-codes.validation');
const router = express.Router();
router.use(authenticate);
router.get('/', authorize('MASTERS', 'view'), validate(listQuerySchema, 'query'), controller.list);
router.get('/:id', authorize('MASTERS', 'view'), controller.getOne);
router.post('/', authorize('MASTERS', 'create'), validate(createSchema), controller.create);
router.put('/:id', authorize('MASTERS', 'edit'), validate(updateSchema), controller.update);
router.delete('/:id', authorize('MASTERS', 'delete'), controller.remove);
module.exports = router;

View File

@ -0,0 +1,38 @@
const { buildMasterService } = require('../_shared/master.factory');
const config = {
modelName: 'hsn_codes',
tableName: 'hsn_codes',
uniqueField: 'code',
softDelete: false,
fields: [
{
name: 'code',
type: 'string',
uppercase: false,
searchable: true,
},
{
name: 'description',
type: 'string',
uppercase: false,
searchable: true,
},
{
name: 'is_active',
type: 'boolean',
uppercase: false,
searchable: false,
},
],
};
const service = buildMasterService(config);
module.exports = {
createHsnCodes: service.createOne,
listHsnCodes: service.list,
getHsnCodesById: service.getOne,
updateHsnCodes: service.updateOne,
deleteHsnCodes: service.removeOne,
};

View File

@ -0,0 +1,31 @@
const Joi = require('joi');
const HSN_CODE_REGEX = /^[0-9]{4,8}$/;
const HSN_CODE_MESSAGE = 'HSN/SAC code must be 4 to 8 digits';
const hsnCodeField = Joi.string()
.trim()
.max(20)
.pattern(HSN_CODE_REGEX)
.messages({ 'string.pattern.base': HSN_CODE_MESSAGE });
const createSchema = Joi.object({
code: hsnCodeField.required(),
description: Joi.string().allow(null, '').optional(),
is_active: Joi.boolean().optional(),
});
const updateSchema = Joi.object({
code: hsnCodeField.optional(),
description: Joi.string().allow(null, '').optional(),
is_active: Joi.boolean().optional(),
}).min(1);
const listQuerySchema = Joi.object({
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(20),
search: Joi.string().allow('').optional(),
is_active: Joi.boolean().optional(),
});
module.exports = { createSchema, updateSchema, listQuerySchema };

View File

@ -6,6 +6,7 @@ router.use('/uom', require('./uom/uom.routes'));
router.use('/item-categories', require('./item-categories/item-categories.routes'));
router.use('/brands', require('./brands/brands.routes'));
router.use('/gst-rates', require('./gst-rates/gst-rates.routes'));
router.use('/hsn-codes', require('./hsn-codes/hsn-codes.routes'));
router.use('/payment-terms', require('./payment-terms/payment-terms.routes'));
router.use('/delivery-terms', require('./delivery-terms/delivery-terms.routes'));
router.use('/departments', require('./departments/departments.routes'));

View File

@ -1,116 +1,232 @@
const { buildMasterService } = require('../_shared/master.factory');
const prisma = require('../../../config/prisma');
const ApiError = require('../../../utils/ApiError');
const auditLog = require('../../../utils/auditLog');
const { getPagination } = require('../../../utils/pagination');
const { nextDocumentNumber } = require('../../../utils/generateCode');
const config = {
modelName: 'items',
tableName: 'items',
uniqueField: 'item_code',
softDelete: true,
fields: [
{
name: 'item_code',
type: 'string',
uppercase: true,
searchable: true,
},
{
name: 'item_name',
type: 'string',
uppercase: false,
searchable: true,
},
{
name: 'item_category_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'item_subcategory_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'uom_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'hsn_code_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'gst_rate_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'brand_id',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'is_asset_item',
type: 'boolean',
uppercase: false,
searchable: false,
},
{
name: 'description',
type: 'string',
uppercase: false,
searchable: false,
},
{
name: 'specification',
type: 'string',
uppercase: false,
searchable: false,
},
{
name: 'min_order_qty',
type: 'decimal',
uppercase: false,
searchable: false,
},
{
name: 'reorder_level',
type: 'decimal',
uppercase: false,
searchable: false,
},
{
name: 'is_active',
type: 'boolean',
uppercase: false,
searchable: false,
},
{
name: 'created_by',
type: 'int',
uppercase: false,
searchable: false,
},
{
name: 'updated_by',
type: 'int',
uppercase: false,
searchable: false,
},
],
const ITEM_SERIES_CODE = 'ITEM';
const SEARCH_FIELDS = ['item_code', 'item_name'];
const normalizeString = (value) => {
if (value === undefined || value === null) return value;
const str = String(value).trim();
return str === '' ? null : str;
};
const service = buildMasterService(config);
const normalizePayload = (payload, { isCreate = false } = {}) => {
const data = { ...payload };
if (Object.prototype.hasOwnProperty.call(data, 'item_name') && data.item_name !== undefined) {
data.item_name = String(data.item_name).trim();
}
if (Object.prototype.hasOwnProperty.call(data, 'description')) {
data.description = normalizeString(data.description);
}
if (Object.prototype.hasOwnProperty.call(data, 'specification')) {
data.specification = normalizeString(data.specification);
}
const bigintFields = [
'item_category_id',
'item_subcategory_id',
'uom_id',
'hsn_code_id',
'gst_rate_id',
'brand_id',
];
for (const field of bigintFields) {
if (data[field] !== undefined && data[field] !== null && data[field] !== '') {
data[field] = BigInt(data[field]);
} else if (data[field] === '' || data[field] === null) {
data[field] = null;
}
}
if (data.min_order_qty !== undefined && data.min_order_qty !== null) {
data.min_order_qty = Number(data.min_order_qty);
}
if (data.reorder_level !== undefined && data.reorder_level !== null) {
data.reorder_level = Number(data.reorder_level);
}
if (!isCreate) {
delete data.item_code;
}
return data;
};
const assertReference = async (table, id, label, { requireActive = true } = {}) => {
if (!id) return null;
const row = await prisma[table].findFirst({
where: { id: BigInt(id), deleted_at: null },
});
if (!row) throw new ApiError(422, `Invalid ${label}`);
if (requireActive && row.is_active === false) throw new ApiError(422, `${label} is inactive`);
return row;
};
const assertHsnCode = async (hsnCodeId) => {
if (!hsnCodeId) return null;
const row = await prisma.hsn_codes.findFirst({
where: { id: BigInt(hsnCodeId) },
});
if (!row) throw new ApiError(422, 'Invalid hsn_code_id');
if (row.is_active === false) throw new ApiError(422, 'hsn_code_id is inactive');
return row;
};
const assertItemSubcategory = async (subcategoryId, categoryId) => {
const subcategory = await assertReference('item_subcategories', subcategoryId, 'item_subcategory_id');
if (subcategory.item_category_id.toString() !== BigInt(categoryId).toString()) {
throw new ApiError(422, 'item_subcategory_id does not belong to the selected item_category_id');
}
return subcategory;
};
const validateItemPayload = async (payload, { isCreate = false } = {}) => {
if (isCreate) {
if (!payload.item_name) throw new ApiError(422, 'item_name is required');
if (!payload.item_category_id) throw new ApiError(422, 'item_category_id is required');
if (!payload.uom_id) throw new ApiError(422, 'uom_id is required');
}
if (payload.item_category_id) {
await assertReference('item_categories', payload.item_category_id, 'item_category_id');
}
if (payload.item_subcategory_id) {
const categoryId = payload.item_category_id;
if (!categoryId) throw new ApiError(422, 'item_category_id is required when item_subcategory_id is set');
await assertItemSubcategory(payload.item_subcategory_id, categoryId);
}
if (payload.uom_id) await assertReference('uom', payload.uom_id, 'uom_id');
if (payload.hsn_code_id !== undefined) await assertHsnCode(payload.hsn_code_id);
if (payload.gst_rate_id) {
await assertReference('gst_rates', payload.gst_rate_id, 'gst_rate_id', { requireActive: false });
}
if (payload.brand_id) {
await assertReference('brands', payload.brand_id, 'brand_id', { requireActive: false });
}
};
const createItems = async (payload, userId, requestId) => {
const data = normalizePayload(payload, { isCreate: true });
await validateItemPayload(data, { isCreate: true });
data.item_code = await nextDocumentNumber(ITEM_SERIES_CODE);
data.created_by = userId ? BigInt(userId) : null;
data.updated_by = userId ? BigInt(userId) : null;
if (data.is_asset_item === undefined) data.is_asset_item = false;
if (data.is_active === undefined) data.is_active = true;
const created = await prisma.items.create({ data });
await auditLog({
tableName: 'items',
recordId: created.id,
action: 'CREATE',
oldValue: null,
newValue: created,
userId,
requestId,
});
return created;
};
const listItems = async (query) => {
const { page, limit, skip } = getPagination(query);
const where = {
deleted_at: null,
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
...(query.search
? {
OR: SEARCH_FIELDS.map((name) => ({
[name]: { contains: query.search, mode: 'insensitive' },
})),
}
: {}),
};
const [data, total] = await Promise.all([
prisma.items.findMany({
where,
orderBy: { created_at: 'desc' },
skip,
take: limit,
}),
prisma.items.count({ where }),
]);
return { data, meta: { page, limit, total } };
};
const getItemsById = async (id) => {
const one = await prisma.items.findFirst({
where: { id: BigInt(id), deleted_at: null },
});
if (!one) throw new ApiError(404, 'items not found');
return one;
};
const updateItems = async (id, payload, userId, requestId) => {
const existing = await getItemsById(id);
const data = normalizePayload(payload);
if (data.item_category_id === undefined) data.item_category_id = existing.item_category_id;
if (data.item_subcategory_id === undefined) data.item_subcategory_id = existing.item_subcategory_id;
await validateItemPayload(data);
data.updated_by = userId ? BigInt(userId) : null;
const updated = await prisma.items.update({
where: { id: BigInt(id) },
data,
});
await auditLog({
tableName: 'items',
recordId: id,
action: 'UPDATE',
oldValue: existing,
newValue: updated,
userId,
requestId,
});
return updated;
};
const deleteItems = async (id, userId, requestId) => {
const existing = await getItemsById(id);
await prisma.items.update({
where: { id: BigInt(id) },
data: {
deleted_at: new Date(),
updated_by: userId ? BigInt(userId) : null,
},
});
await auditLog({
tableName: 'items',
recordId: id,
action: 'DELETE',
oldValue: existing,
newValue: { deleted_at: new Date() },
userId,
requestId,
});
};
module.exports = {
createItems: service.createOne,
listItems: service.list,
getItemsById: service.getOne,
updateItems: service.updateOne,
deleteItems: service.removeOne,
createItems,
listItems,
getItemsById,
updateItems,
deleteItems,
};

View File

@ -1,38 +1,40 @@
const Joi = require('joi');
const { masterCode, masterName, listQuerySchema } = require('../_shared/masters.validation');
const { masterName, listQuerySchema } = require('../_shared/masters.validation');
const createSchema = Joi.object({
item_code: masterCode({ max: 30 }),
item_name: masterName({ max: 200 }),
item_category_id: Joi.number().integer().optional(),
item_subcategory_id: Joi.number().integer().optional(),
uom_id: Joi.number().integer().optional(),
hsn_code_id: Joi.number().integer().optional(),
gst_rate_id: Joi.number().integer().optional(),
brand_id: Joi.number().integer().optional(),
is_asset_item: Joi.boolean().optional(),
description: Joi.string().allow('').optional(),
specification: Joi.string().allow('').optional(),
min_order_qty: Joi.number().precision(4).optional(),
reorder_level: Joi.number().precision(4).optional(),
item_name: masterName({ max: 200, required: true }),
item_category_id: Joi.number().integer().positive().required(),
item_subcategory_id: Joi.number().integer().positive().allow(null).optional(),
uom_id: Joi.number().integer().positive().required(),
hsn_code_id: Joi.number().integer().positive().allow(null).optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
brand_id: Joi.number().integer().positive().allow(null).optional(),
is_asset_item: Joi.boolean().default(false),
description: Joi.string().allow(null, '').optional(),
specification: Joi.string().allow(null, '').optional(),
min_order_qty: Joi.number().precision(4).allow(null).optional(),
reorder_level: Joi.number().precision(4).allow(null).optional(),
is_active: Joi.boolean().optional(),
});
const updateSchema = Joi.object({
item_code: masterCode({ max: 30 }),
item_name: masterName({ max: 200 }),
item_category_id: Joi.number().integer().optional(),
item_subcategory_id: Joi.number().integer().optional(),
uom_id: Joi.number().integer().optional(),
hsn_code_id: Joi.number().integer().optional(),
gst_rate_id: Joi.number().integer().optional(),
brand_id: Joi.number().integer().optional(),
item_category_id: Joi.number().integer().positive().optional(),
item_subcategory_id: Joi.number().integer().positive().allow(null).optional(),
uom_id: Joi.number().integer().positive().optional(),
hsn_code_id: Joi.number().integer().positive().allow(null).optional(),
gst_rate_id: Joi.number().integer().positive().allow(null).optional(),
brand_id: Joi.number().integer().positive().allow(null).optional(),
is_asset_item: Joi.boolean().optional(),
description: Joi.string().allow('').optional(),
specification: Joi.string().allow('').optional(),
min_order_qty: Joi.number().precision(4).optional(),
reorder_level: Joi.number().precision(4).optional(),
description: Joi.string().allow(null, '').optional(),
specification: Joi.string().allow(null, '').optional(),
min_order_qty: Joi.number().precision(4).allow(null).optional(),
reorder_level: Joi.number().precision(4).allow(null).optional(),
is_active: Joi.boolean().optional(),
}).min(1);
module.exports = { createSchema, updateSchema, listQuerySchema };
const listQuerySchemaExtended = listQuerySchema.keys({
item_category_id: Joi.number().integer().positive().optional(),
});
module.exports = { createSchema, updateSchema, listQuerySchema: listQuerySchemaExtended };

View File

@ -33,7 +33,7 @@ const poDetailInclude = {
purchase_order_items: {
orderBy: { line_no: 'asc' },
include: {
items: { select: { id: true, item_code: true, item_name: true, is_asset_item: true } },
items: { select: { id: true, item_code: true, item_name: true, is_asset_item: true, hsn_code_id: true } },
uom: { select: { id: true, code: true, name: true } },
gst_rates: { select: { id: true, rate_pct: true, description: true } },
hsn_codes: { select: { id: true, code: true, description: true } },