vendor bank details , grn location , visit no removed in asset service visit
This commit is contained in:
parent
1cd5826ea5
commit
c7543d2e3b
@ -329,7 +329,7 @@ flowchart TD
|
|||||||
| `asset_code` | `document_series` via `ASSET_{category.code}` |
|
| `asset_code` | `document_series` via `ASSET_{category.code}` |
|
||||||
| `asset_name` | Item name (+ `#N` if qty > 1) |
|
| `asset_name` | Item name (+ `#N` if qty > 1) |
|
||||||
| `item_category_id` / `item_subcategory_id` | From item master (optional GRN line override) |
|
| `item_category_id` / `item_subcategory_id` | From item master (optional GRN line override) |
|
||||||
| `location_id` | From PO `shipping_id` (plant or warehouse) |
|
| `location_id` | From PO `shipping_id` (plant or warehouse); same source as GRN `location_id` |
|
||||||
| `vendor_id`, `po_id`, `grn_id`, `grn_item_id` | From GRN |
|
| `vendor_id`, `po_id`, `grn_id`, `grn_item_id` | From GRN |
|
||||||
| `purchase_date` | `grn.grn_date` |
|
| `purchase_date` | `grn.grn_date` |
|
||||||
| `purchase_cost` | `grn_items.rate` |
|
| `purchase_cost` | `grn_items.rate` |
|
||||||
|
|||||||
@ -103,7 +103,6 @@ model asset_service_visits {
|
|||||||
amc_contract_id BigInt?
|
amc_contract_id BigInt?
|
||||||
visit_type String @db.VarChar(30)
|
visit_type String @db.VarChar(30)
|
||||||
visit_date DateTime @db.Date
|
visit_date DateTime @db.Date
|
||||||
visit_number Int?
|
|
||||||
complaint_no String? @db.VarChar(50)
|
complaint_no String? @db.VarChar(50)
|
||||||
complaint_date DateTime? @db.Date
|
complaint_date DateTime? @db.Date
|
||||||
complaint_desc String?
|
complaint_desc String?
|
||||||
@ -335,7 +334,7 @@ model grn {
|
|||||||
grn_date DateTime @db.Date
|
grn_date DateTime @db.Date
|
||||||
po_id BigInt
|
po_id BigInt
|
||||||
vendor_id BigInt
|
vendor_id BigInt
|
||||||
warehouse_id BigInt
|
location_id BigInt
|
||||||
vendor_invoice_no String? @db.VarChar(100)
|
vendor_invoice_no String? @db.VarChar(100)
|
||||||
vendor_invoice_date DateTime? @db.Date
|
vendor_invoice_date DateTime? @db.Date
|
||||||
vendor_invoice_amount Decimal? @db.Decimal(15, 4)
|
vendor_invoice_amount Decimal? @db.Decimal(15, 4)
|
||||||
@ -363,13 +362,14 @@ model grn {
|
|||||||
users_grn_received_byTousers users? @relation("grn_received_byTousers", fields: [received_by], references: [id], onUpdate: NoAction)
|
users_grn_received_byTousers users? @relation("grn_received_byTousers", fields: [received_by], references: [id], onUpdate: NoAction)
|
||||||
users_grn_updated_byTousers users? @relation("grn_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
users_grn_updated_byTousers users? @relation("grn_updated_byTousers", fields: [updated_by], references: [id], onUpdate: NoAction)
|
||||||
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
|
vendors vendors @relation(fields: [vendor_id], references: [id], onUpdate: NoAction)
|
||||||
warehouse locations @relation("GRNWarehouseLocation", fields: [warehouse_id], references: [id], onUpdate: NoAction)
|
location locations @relation("GRNLocation", fields: [location_id], references: [id], onUpdate: NoAction)
|
||||||
grn_attachments grn_attachments[]
|
grn_attachments grn_attachments[]
|
||||||
grn_items grn_items[]
|
grn_items grn_items[]
|
||||||
|
|
||||||
@@index([grn_date], map: "idx_grn_grn_date")
|
@@index([grn_date], map: "idx_grn_grn_date")
|
||||||
@@index([po_id], map: "idx_grn_po_id")
|
@@index([po_id], map: "idx_grn_po_id")
|
||||||
@@index([vendor_id], map: "idx_grn_vendor_id")
|
@@index([vendor_id], map: "idx_grn_vendor_id")
|
||||||
|
@@index([location_id], map: "idx_grn_location_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
model grn_attachments {
|
model grn_attachments {
|
||||||
@ -609,7 +609,7 @@ model locations {
|
|||||||
transfers_from asset_transfers[] @relation("TransferFromLocation")
|
transfers_from asset_transfers[] @relation("TransferFromLocation")
|
||||||
transfers_to asset_transfers[] @relation("TransferToLocation")
|
transfers_to asset_transfers[] @relation("TransferToLocation")
|
||||||
assets_location assets[] @relation("AssetLocation")
|
assets_location assets[] @relation("AssetLocation")
|
||||||
grn_warehouse grn[] @relation("GRNWarehouseLocation")
|
grn_location grn[] @relation("GRNLocation")
|
||||||
parent locations? @relation("LocationHierarchy", fields: [parent_id], references: [id], onUpdate: NoAction)
|
parent locations? @relation("LocationHierarchy", fields: [parent_id], references: [id], onUpdate: NoAction)
|
||||||
children locations[] @relation("LocationHierarchy")
|
children locations[] @relation("LocationHierarchy")
|
||||||
po_billing purchase_orders[] @relation("POBillingLocation")
|
po_billing purchase_orders[] @relation("POBillingLocation")
|
||||||
|
|||||||
127
scripts/patch-grn-location-bank-primary-drop-visit-number.sql
Normal file
127
scripts/patch-grn-location-bank-primary-drop-visit-number.sql
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
-- 1) GRN: warehouse_id → location_id (from PO shipping_id)
|
||||||
|
-- 2) Vendor bank details: only one active primary per vendor
|
||||||
|
-- 3) asset_service_visits: drop visit_number
|
||||||
|
-- Idempotent: safe to re-run.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 1. GRN warehouse_id → location_id
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
DROP VIEW IF EXISTS v_grn CASCADE;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'grn' AND column_name = 'warehouse_id'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'grn' AND column_name = 'location_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE grn RENAME COLUMN warehouse_id TO location_id;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
ALTER TABLE grn ADD COLUMN IF NOT EXISTS location_id BIGINT;
|
||||||
|
|
||||||
|
-- Prefer PO shipping_id; fall back to existing location_id for any orphan rows
|
||||||
|
UPDATE grn g
|
||||||
|
SET location_id = po.shipping_id
|
||||||
|
FROM purchase_orders po
|
||||||
|
WHERE po.id = g.po_id
|
||||||
|
AND po.shipping_id IS NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE grn ALTER COLUMN location_id SET NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE grn DROP CONSTRAINT IF EXISTS grn_warehouse_id_fkey;
|
||||||
|
ALTER TABLE grn DROP CONSTRAINT IF EXISTS grn_location_id_fkey;
|
||||||
|
ALTER TABLE grn
|
||||||
|
ADD CONSTRAINT grn_location_id_fkey FOREIGN KEY (location_id) REFERENCES locations(id);
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_grn_warehouse_id;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_grn_location_id ON grn(location_id);
|
||||||
|
|
||||||
|
CREATE VIEW v_grn AS
|
||||||
|
SELECT
|
||||||
|
g.*,
|
||||||
|
po.po_number,
|
||||||
|
v.vendor_code,
|
||||||
|
v.vendor_name,
|
||||||
|
loc.code AS location_code,
|
||||||
|
loc.name AS location_name,
|
||||||
|
loc.type AS location_type
|
||||||
|
FROM grn g
|
||||||
|
JOIN purchase_orders po ON po.id = g.po_id
|
||||||
|
JOIN vendors v ON v.id = g.vendor_id
|
||||||
|
JOIN locations loc ON loc.id = g.location_id
|
||||||
|
WHERE g.deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 2. Vendor bank: one active primary per vendor
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Demote duplicates (keep lowest id as primary)
|
||||||
|
WITH ranked AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY vendor_id ORDER BY id ASC) AS rn
|
||||||
|
FROM vendor_bank_details
|
||||||
|
WHERE is_primary = TRUE AND is_active = TRUE
|
||||||
|
)
|
||||||
|
UPDATE vendor_bank_details vbd
|
||||||
|
SET is_primary = FALSE
|
||||||
|
FROM ranked r
|
||||||
|
WHERE vbd.id = r.id
|
||||||
|
AND r.rn > 1;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS uq_vendor_bank_one_primary;
|
||||||
|
CREATE UNIQUE INDEX uq_vendor_bank_one_primary
|
||||||
|
ON vendor_bank_details (vendor_id)
|
||||||
|
WHERE is_primary = TRUE AND is_active = TRUE;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 3. Drop visit_number from asset_service_visits
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
DROP VIEW IF EXISTS v_asset_next_service CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE asset_service_visits DROP COLUMN IF EXISTS visit_number;
|
||||||
|
|
||||||
|
CREATE VIEW v_asset_next_service AS
|
||||||
|
SELECT
|
||||||
|
a.id AS asset_id,
|
||||||
|
a.asset_code,
|
||||||
|
a.asset_name,
|
||||||
|
ac_cat.name AS category_name,
|
||||||
|
loc.name AS location_name,
|
||||||
|
d.name AS department_name,
|
||||||
|
sv.id AS last_visit_id,
|
||||||
|
sv.visit_date AS last_service_date,
|
||||||
|
sv.visit_type AS last_visit_type,
|
||||||
|
sv.next_service_date,
|
||||||
|
(sv.next_service_date - CURRENT_DATE) AS days_to_next_service,
|
||||||
|
CASE
|
||||||
|
WHEN sv.next_service_date < CURRENT_DATE THEN 'OVERDUE'
|
||||||
|
WHEN (sv.next_service_date - CURRENT_DATE) <= 7 THEN 'DUE_THIS_WEEK'
|
||||||
|
WHEN (sv.next_service_date - CURRENT_DATE) <= 30 THEN 'DUE_THIS_MONTH'
|
||||||
|
ELSE 'UPCOMING'
|
||||||
|
END AS service_status,
|
||||||
|
v.vendor_name AS service_vendor,
|
||||||
|
amc.contract_no AS amc_contract_no,
|
||||||
|
amc.end_date AS amc_end_date
|
||||||
|
FROM assets a
|
||||||
|
JOIN item_categories ac_cat ON ac_cat.id = a.item_category_id
|
||||||
|
JOIN locations loc ON loc.id = a.location_id
|
||||||
|
LEFT JOIN departments d ON d.id = a.department_id
|
||||||
|
JOIN LATERAL (
|
||||||
|
SELECT id, visit_date, visit_type, next_service_date, vendor_id
|
||||||
|
FROM asset_service_visits
|
||||||
|
WHERE asset_id = a.id AND deleted_at IS NULL AND next_service_date IS NOT NULL
|
||||||
|
ORDER BY visit_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) sv ON TRUE
|
||||||
|
LEFT JOIN vendors v ON v.id = sv.vendor_id
|
||||||
|
LEFT JOIN asset_amc_contracts amc
|
||||||
|
ON amc.asset_id = a.id AND amc.is_active = TRUE AND amc.deleted_at IS NULL
|
||||||
|
WHERE a.deleted_at IS NULL AND a.status NOT IN ('DISPOSED','SCRAPPED');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@ -207,7 +207,6 @@ components:
|
|||||||
visit_type: { type: string, enum: [PREVENTIVE, BREAKDOWN, INSPECTION, INSTALLATION, CALIBRATION, OTHER] }
|
visit_type: { type: string, enum: [PREVENTIVE, BREAKDOWN, INSPECTION, INSTALLATION, CALIBRATION, OTHER] }
|
||||||
visit_date: { type: string, format: date }
|
visit_date: { type: string, format: date }
|
||||||
amc_contract_id: { type: integer, nullable: true }
|
amc_contract_id: { type: integer, nullable: true }
|
||||||
visit_number: { type: integer, nullable: true }
|
|
||||||
complaint_no: { type: string, example: CMP-001 }
|
complaint_no: { type: string, example: CMP-001 }
|
||||||
complaint_date: { type: string, format: date, nullable: true }
|
complaint_date: { type: string, format: date, nullable: true }
|
||||||
complaint_desc: { type: string }
|
complaint_desc: { type: string }
|
||||||
@ -230,7 +229,6 @@ components:
|
|||||||
visit_type: { type: string, enum: [PREVENTIVE, BREAKDOWN, INSPECTION, INSTALLATION, CALIBRATION, OTHER] }
|
visit_type: { type: string, enum: [PREVENTIVE, BREAKDOWN, INSPECTION, INSTALLATION, CALIBRATION, OTHER] }
|
||||||
visit_date: { type: string, format: date }
|
visit_date: { type: string, format: date }
|
||||||
amc_contract_id: { type: integer, nullable: true }
|
amc_contract_id: { type: integer, nullable: true }
|
||||||
visit_number: { type: integer, nullable: true }
|
|
||||||
complaint_no: { type: string }
|
complaint_no: { type: string }
|
||||||
complaint_date: { type: string, format: date, nullable: true }
|
complaint_date: { type: string, format: date, nullable: true }
|
||||||
complaint_desc: { type: string }
|
complaint_desc: { type: string }
|
||||||
|
|||||||
@ -23,11 +23,12 @@ components:
|
|||||||
item_subcategory_id: { type: integer, nullable: true, example: 1, description: Optional override; defaults from PO item master when omitted }
|
item_subcategory_id: { type: integer, nullable: true, example: 1, description: Optional override; defaults from PO item master when omitted }
|
||||||
GrnCreateBody:
|
GrnCreateBody:
|
||||||
type: object
|
type: object
|
||||||
required: [grn_date, po_id, warehouse_id, items]
|
required: [grn_date, po_id, items]
|
||||||
|
description: |
|
||||||
|
`location_id` is set automatically from the PO `shipping_id` (not accepted in the request body).
|
||||||
properties:
|
properties:
|
||||||
grn_date: { type: string, format: date, example: '2026-06-18' }
|
grn_date: { type: string, format: date, example: '2026-06-18' }
|
||||||
po_id: { type: integer, example: 1 }
|
po_id: { type: integer, example: 1 }
|
||||||
warehouse_id: { type: integer, example: 1 }
|
|
||||||
vendor_invoice_no: { type: string, example: INV-2026-001 }
|
vendor_invoice_no: { type: string, example: INV-2026-001 }
|
||||||
vendor_invoice_date: { type: string, format: date, nullable: true }
|
vendor_invoice_date: { type: string, format: date, nullable: true }
|
||||||
vendor_invoice_amount: { type: number, example: 12000 }
|
vendor_invoice_amount: { type: number, example: 12000 }
|
||||||
@ -45,7 +46,6 @@ components:
|
|||||||
minProperties: 1
|
minProperties: 1
|
||||||
properties:
|
properties:
|
||||||
grn_date: { type: string, format: date }
|
grn_date: { type: string, format: date }
|
||||||
warehouse_id: { type: integer }
|
|
||||||
vendor_invoice_no: { type: string }
|
vendor_invoice_no: { type: string }
|
||||||
vendor_invoice_date: { type: string, format: date, nullable: true }
|
vendor_invoice_date: { type: string, format: date, nullable: true }
|
||||||
vendor_invoice_amount: { type: number }
|
vendor_invoice_amount: { type: number }
|
||||||
@ -90,7 +90,7 @@ paths:
|
|||||||
- { name: status, in: query, schema: { type: string, enum: [POSTED, CANCELLED] } }
|
- { name: status, in: query, schema: { type: string, enum: [POSTED, CANCELLED] } }
|
||||||
- { name: po_id, in: query, schema: { type: integer } }
|
- { name: po_id, in: query, schema: { type: integer } }
|
||||||
- { name: vendor_id, in: query, schema: { type: integer } }
|
- { name: vendor_id, in: query, schema: { type: integer } }
|
||||||
- { name: warehouse_id, in: query, schema: { type: integer } }
|
- { name: location_id, in: query, schema: { type: integer } }
|
||||||
- { name: date_from, in: query, schema: { type: string, format: date } }
|
- { name: date_from, in: query, schema: { type: string, format: date } }
|
||||||
- { name: date_to, in: query, schema: { type: string, format: date } }
|
- { name: date_to, in: query, schema: { type: string, format: date } }
|
||||||
responses:
|
responses:
|
||||||
@ -110,7 +110,7 @@ paths:
|
|||||||
- { name: status, in: query, schema: { type: string, enum: [POSTED, CANCELLED] } }
|
- { name: status, in: query, schema: { type: string, enum: [POSTED, CANCELLED] } }
|
||||||
- { name: po_id, in: query, schema: { type: integer } }
|
- { name: po_id, in: query, schema: { type: integer } }
|
||||||
- { name: vendor_id, in: query, schema: { type: integer } }
|
- { name: vendor_id, in: query, schema: { type: integer } }
|
||||||
- { name: warehouse_id, in: query, schema: { type: integer } }
|
- { name: location_id, in: query, schema: { type: integer } }
|
||||||
- { name: date_from, in: query, schema: { type: string, format: date } }
|
- { name: date_from, in: query, schema: { type: string, format: date } }
|
||||||
- { name: date_to, in: query, schema: { type: string, format: date } }
|
- { name: date_to, in: query, schema: { type: string, format: date } }
|
||||||
responses:
|
responses:
|
||||||
|
|||||||
@ -113,7 +113,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum: [CURRENT, SAVINGS, OVERDRAFT]
|
enum: [CURRENT, SAVINGS, OVERDRAFT]
|
||||||
example: CURRENT
|
example: CURRENT
|
||||||
is_primary: { type: boolean, example: true }
|
is_primary:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
description: Only one active primary bank account per vendor. Setting true clears primary on others.
|
||||||
is_active: { type: boolean, example: true }
|
is_active: { type: boolean, example: true }
|
||||||
VendorBankDetailsUpdateBody:
|
VendorBankDetailsUpdateBody:
|
||||||
type: object
|
type: object
|
||||||
@ -127,7 +130,10 @@ components:
|
|||||||
account_type:
|
account_type:
|
||||||
type: string
|
type: string
|
||||||
enum: [CURRENT, SAVINGS, OVERDRAFT]
|
enum: [CURRENT, SAVINGS, OVERDRAFT]
|
||||||
is_primary: { type: boolean, example: true }
|
is_primary:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
description: Only one active primary bank account per vendor. Setting true clears primary on others.
|
||||||
is_active: { type: boolean, example: true }
|
is_active: { type: boolean, example: true }
|
||||||
VendorItemMappingsCreateBody:
|
VendorItemMappingsCreateBody:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@ -42,7 +42,6 @@ const buildVisitData = async (payload, assetId) => {
|
|||||||
amc_contract_id: payload.amc_contract_id ? BigInt(payload.amc_contract_id) : null,
|
amc_contract_id: payload.amc_contract_id ? BigInt(payload.amc_contract_id) : null,
|
||||||
visit_type: payload.visit_type,
|
visit_type: payload.visit_type,
|
||||||
visit_date: toDateOnly(payload.visit_date),
|
visit_date: toDateOnly(payload.visit_date),
|
||||||
visit_number: payload.visit_number ?? null,
|
|
||||||
complaint_no: payload.complaint_no || null,
|
complaint_no: payload.complaint_no || null,
|
||||||
complaint_date: payload.complaint_date ? toDateOnly(payload.complaint_date) : null,
|
complaint_date: payload.complaint_date ? toDateOnly(payload.complaint_date) : null,
|
||||||
complaint_desc: payload.complaint_desc || null,
|
complaint_desc: payload.complaint_desc || null,
|
||||||
@ -125,7 +124,6 @@ const updateServiceVisit = async (assetId, visitId, payload, userId, requestId)
|
|||||||
payload.amc_contract_id !== undefined ? payload.amc_contract_id : existing.amc_contract_id,
|
payload.amc_contract_id !== undefined ? payload.amc_contract_id : existing.amc_contract_id,
|
||||||
visit_type: payload.visit_type ?? existing.visit_type,
|
visit_type: payload.visit_type ?? existing.visit_type,
|
||||||
visit_date: payload.visit_date ?? existing.visit_date,
|
visit_date: payload.visit_date ?? existing.visit_date,
|
||||||
visit_number: payload.visit_number !== undefined ? payload.visit_number : existing.visit_number,
|
|
||||||
complaint_no: payload.complaint_no !== undefined ? payload.complaint_no : existing.complaint_no,
|
complaint_no: payload.complaint_no !== undefined ? payload.complaint_no : existing.complaint_no,
|
||||||
complaint_date:
|
complaint_date:
|
||||||
payload.complaint_date !== undefined ? payload.complaint_date : existing.complaint_date,
|
payload.complaint_date !== undefined ? payload.complaint_date : existing.complaint_date,
|
||||||
|
|||||||
@ -213,7 +213,6 @@ const serviceVisitSchema = Joi.object({
|
|||||||
.required(),
|
.required(),
|
||||||
visit_date: Joi.date().iso().required(),
|
visit_date: Joi.date().iso().required(),
|
||||||
amc_contract_id: Joi.number().integer().positive().allow(null).optional(),
|
amc_contract_id: Joi.number().integer().positive().allow(null).optional(),
|
||||||
visit_number: Joi.number().integer().min(1).allow(null).optional(),
|
|
||||||
complaint_no: Joi.string().max(50).allow(null, '').optional(),
|
complaint_no: Joi.string().max(50).allow(null, '').optional(),
|
||||||
complaint_date: Joi.date().iso().allow(null).optional(),
|
complaint_date: Joi.date().iso().allow(null).optional(),
|
||||||
complaint_desc: Joi.string().allow(null, '').optional(),
|
complaint_desc: Joi.string().allow(null, '').optional(),
|
||||||
|
|||||||
@ -9,13 +9,13 @@ const { formatDate } = require('../../utils/pdf/helpers/formatDate');
|
|||||||
const { getCompanyForDocuments } = require('../settings/settings.service');
|
const { getCompanyForDocuments } = require('../settings/settings.service');
|
||||||
const { rowsToCsv } = require('../../utils/csv');
|
const { rowsToCsv } = require('../../utils/csv');
|
||||||
const repository = require('./grn.repository');
|
const repository = require('./grn.repository');
|
||||||
const { assertWarehouse } = require('../../utils/locations');
|
const { assertAnyLocation } = require('../../utils/locations');
|
||||||
const { sanitizeAttachment } = require('./grn.attachments.service');
|
const { sanitizeAttachment } = require('./grn.attachments.service');
|
||||||
|
|
||||||
const grnListInclude = {
|
const grnListInclude = {
|
||||||
purchase_orders: { select: { id: true, po_number: true, status: true } },
|
purchase_orders: { select: { id: true, po_number: true, status: true } },
|
||||||
vendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
vendors: { select: { id: true, vendor_code: true, vendor_name: true } },
|
||||||
warehouse: { select: { id: true, code: true, name: true } },
|
location: { select: { id: true, code: true, name: true, type: true } },
|
||||||
users_grn_received_byTousers: { select: { id: true, full_name: true } },
|
users_grn_received_byTousers: { select: { id: true, full_name: true } },
|
||||||
users_grn_created_byTousers: { select: { id: true, full_name: true } },
|
users_grn_created_byTousers: { select: { id: true, full_name: true } },
|
||||||
};
|
};
|
||||||
@ -67,11 +67,12 @@ const grnPdfInclude = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
warehouse: {
|
location: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
code: true,
|
code: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
type: true,
|
||||||
address: true,
|
address: true,
|
||||||
city: true,
|
city: true,
|
||||||
state: true,
|
state: true,
|
||||||
@ -95,7 +96,7 @@ const sanitizeGrn = (row) => {
|
|||||||
const {
|
const {
|
||||||
purchase_orders,
|
purchase_orders,
|
||||||
vendors,
|
vendors,
|
||||||
warehouse,
|
location,
|
||||||
users_grn_received_byTousers,
|
users_grn_received_byTousers,
|
||||||
users_grn_created_byTousers,
|
users_grn_created_byTousers,
|
||||||
users_grn_quality_checked_byTousers,
|
users_grn_quality_checked_byTousers,
|
||||||
@ -110,7 +111,7 @@ const sanitizeGrn = (row) => {
|
|||||||
...rest,
|
...rest,
|
||||||
purchase_order: purchase_orders || null,
|
purchase_order: purchase_orders || null,
|
||||||
vendor: vendors || null,
|
vendor: vendors || null,
|
||||||
warehouse: warehouse || null,
|
location: location || null,
|
||||||
received_by_user: users_grn_received_byTousers || null,
|
received_by_user: users_grn_received_byTousers || null,
|
||||||
created_by_user: users_grn_created_byTousers || null,
|
created_by_user: users_grn_created_byTousers || null,
|
||||||
quality_checked_by_user: users_grn_quality_checked_byTousers || null,
|
quality_checked_by_user: users_grn_quality_checked_byTousers || null,
|
||||||
@ -129,7 +130,6 @@ const sanitizeGrn = (row) => {
|
|||||||
grn_attachments: undefined,
|
grn_attachments: undefined,
|
||||||
purchase_orders: undefined,
|
purchase_orders: undefined,
|
||||||
vendors: undefined,
|
vendors: undefined,
|
||||||
warehouses: undefined,
|
|
||||||
users_grn_received_byTousers: undefined,
|
users_grn_received_byTousers: undefined,
|
||||||
users_grn_created_byTousers: undefined,
|
users_grn_created_byTousers: undefined,
|
||||||
users_grn_quality_checked_byTousers: undefined,
|
users_grn_quality_checked_byTousers: undefined,
|
||||||
@ -219,11 +219,11 @@ const buildGrnPdfPayload = (grn, company) => {
|
|||||||
pincode: vendorAddress?.pincode || '',
|
pincode: vendorAddress?.pincode || '',
|
||||||
},
|
},
|
||||||
warehouse: {
|
warehouse: {
|
||||||
name: grn.warehouse?.name || '-',
|
name: grn.location?.name || '-',
|
||||||
address: grn.warehouse?.address || '',
|
address: grn.location?.address || '',
|
||||||
city: grn.warehouse?.city || '',
|
city: grn.location?.city || '',
|
||||||
state: grn.warehouse?.state || '',
|
state: grn.location?.state || '',
|
||||||
pincode: grn.warehouse?.pincode || '',
|
pincode: grn.location?.pincode || '',
|
||||||
},
|
},
|
||||||
items,
|
items,
|
||||||
generated_at: `${formatDate(new Date(), { style: 'datetime' })} IST`,
|
generated_at: `${formatDate(new Date(), { style: 'datetime' })} IST`,
|
||||||
@ -388,7 +388,7 @@ const buildHeaderData = (payload, po, userId) => ({
|
|||||||
grn_date: toDateOnly(payload.grn_date),
|
grn_date: toDateOnly(payload.grn_date),
|
||||||
po_id: po.id,
|
po_id: po.id,
|
||||||
vendor_id: po.vendor_id,
|
vendor_id: po.vendor_id,
|
||||||
warehouse_id: BigInt(payload.warehouse_id),
|
location_id: BigInt(po.shipping_id),
|
||||||
vendor_invoice_no: payload.vendor_invoice_no || null,
|
vendor_invoice_no: payload.vendor_invoice_no || null,
|
||||||
vendor_invoice_date: payload.vendor_invoice_date ? toDateOnly(payload.vendor_invoice_date) : null,
|
vendor_invoice_date: payload.vendor_invoice_date ? toDateOnly(payload.vendor_invoice_date) : null,
|
||||||
vendor_invoice_amount: payload.vendor_invoice_amount ?? null,
|
vendor_invoice_amount: payload.vendor_invoice_amount ?? null,
|
||||||
@ -402,7 +402,10 @@ const buildHeaderData = (payload, po, userId) => ({
|
|||||||
|
|
||||||
const createGrn = async (payload, userId, requestId) => {
|
const createGrn = async (payload, userId, requestId) => {
|
||||||
const po = await getReceivablePoOrThrow(payload.po_id);
|
const po = await getReceivablePoOrThrow(payload.po_id);
|
||||||
await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
if (!po.shipping_id) {
|
||||||
|
throw new ApiError(422, 'PO shipping location is required to create GRN');
|
||||||
|
}
|
||||||
|
await assertAnyLocation(po.shipping_id, 'shipping_id');
|
||||||
|
|
||||||
const { builtItems, assetPlans } = await validateAndBuildItems(po, payload.items);
|
const { builtItems, assetPlans } = await validateAndBuildItems(po, payload.items);
|
||||||
const header = buildHeaderData(payload, po, userId);
|
const header = buildHeaderData(payload, po, userId);
|
||||||
@ -436,7 +439,7 @@ const buildGrnsWhere = (query) => ({
|
|||||||
...(query.status ? { status: query.status } : {}),
|
...(query.status ? { status: query.status } : {}),
|
||||||
...(query.po_id ? { po_id: BigInt(query.po_id) } : {}),
|
...(query.po_id ? { po_id: BigInt(query.po_id) } : {}),
|
||||||
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
|
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
|
||||||
...(query.warehouse_id ? { warehouse_id: BigInt(query.warehouse_id) } : {}),
|
...(query.location_id ? { location_id: BigInt(query.location_id) } : {}),
|
||||||
...(query.search ? { grn_number: { contains: query.search, mode: 'insensitive' } } : {}),
|
...(query.search ? { grn_number: { contains: query.search, mode: 'insensitive' } } : {}),
|
||||||
...(query.date_from || query.date_to
|
...(query.date_from || query.date_to
|
||||||
? {
|
? {
|
||||||
@ -493,7 +496,7 @@ const exportGrns = async (query) => {
|
|||||||
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
|
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
|
||||||
{ key: (row) => row.vendor?.vendor_code || '', header: 'Vendor Code' },
|
{ key: (row) => row.vendor?.vendor_code || '', header: 'Vendor Code' },
|
||||||
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor Name' },
|
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor Name' },
|
||||||
{ key: (row) => row.warehouse?.name || '', header: 'Warehouse' },
|
{ key: (row) => row.location?.name || '', header: 'Location' },
|
||||||
{ key: 'vendor_invoice_no', header: 'Vendor Invoice No' },
|
{ key: 'vendor_invoice_no', header: 'Vendor Invoice No' },
|
||||||
{ key: 'vendor_invoice_date', header: 'Vendor Invoice Date', type: 'date' },
|
{ key: 'vendor_invoice_date', header: 'Vendor Invoice Date', type: 'date' },
|
||||||
{ key: 'vendor_invoice_amount', header: 'Vendor Invoice Amount' },
|
{ key: 'vendor_invoice_amount', header: 'Vendor Invoice Amount' },
|
||||||
@ -515,13 +518,8 @@ const updateGrn = async (id, payload, userId, requestId) => {
|
|||||||
throw new ApiError(409, 'Only POSTED GRN can be updated');
|
throw new ApiError(409, 'Only POSTED GRN can be updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.warehouse_id) {
|
|
||||||
await assertWarehouse(payload.warehouse_id, 'warehouse_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
...(payload.grn_date !== undefined ? { grn_date: toDateOnly(payload.grn_date) } : {}),
|
...(payload.grn_date !== undefined ? { grn_date: toDateOnly(payload.grn_date) } : {}),
|
||||||
...(payload.warehouse_id !== undefined ? { warehouse_id: BigInt(payload.warehouse_id) } : {}),
|
|
||||||
...(payload.vendor_invoice_no !== undefined
|
...(payload.vendor_invoice_no !== undefined
|
||||||
? { vendor_invoice_no: payload.vendor_invoice_no || null }
|
? { vendor_invoice_no: payload.vendor_invoice_no || null }
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
@ -21,7 +21,6 @@ const grnItemSchema = Joi.object({
|
|||||||
const createGrnSchema = Joi.object({
|
const createGrnSchema = Joi.object({
|
||||||
grn_date: Joi.date().iso().required(),
|
grn_date: Joi.date().iso().required(),
|
||||||
po_id: Joi.number().integer().positive().required(),
|
po_id: Joi.number().integer().positive().required(),
|
||||||
warehouse_id: Joi.number().integer().positive().required(),
|
|
||||||
vendor_invoice_no: Joi.string().max(100).allow(null, '').optional(),
|
vendor_invoice_no: Joi.string().max(100).allow(null, '').optional(),
|
||||||
vendor_invoice_date: Joi.date().iso().allow(null).optional(),
|
vendor_invoice_date: Joi.date().iso().allow(null).optional(),
|
||||||
vendor_invoice_amount: Joi.number().min(0).allow(null).optional(),
|
vendor_invoice_amount: Joi.number().min(0).allow(null).optional(),
|
||||||
@ -36,7 +35,6 @@ const createGrnSchema = Joi.object({
|
|||||||
|
|
||||||
const updateGrnSchema = Joi.object({
|
const updateGrnSchema = Joi.object({
|
||||||
grn_date: Joi.date().iso().optional(),
|
grn_date: Joi.date().iso().optional(),
|
||||||
warehouse_id: Joi.number().integer().positive().optional(),
|
|
||||||
vendor_invoice_no: Joi.string().max(100).allow(null, '').optional(),
|
vendor_invoice_no: Joi.string().max(100).allow(null, '').optional(),
|
||||||
vendor_invoice_date: Joi.date().iso().allow(null).optional(),
|
vendor_invoice_date: Joi.date().iso().allow(null).optional(),
|
||||||
vendor_invoice_amount: Joi.number().min(0).allow(null).optional(),
|
vendor_invoice_amount: Joi.number().min(0).allow(null).optional(),
|
||||||
@ -57,7 +55,7 @@ const listGrnQuerySchema = Joi.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
po_id: Joi.number().integer().positive().optional(),
|
po_id: Joi.number().integer().positive().optional(),
|
||||||
vendor_id: Joi.number().integer().positive().optional(),
|
vendor_id: Joi.number().integer().positive().optional(),
|
||||||
warehouse_id: Joi.number().integer().positive().optional(),
|
location_id: Joi.number().integer().positive().optional(),
|
||||||
date_from: Joi.date().iso().optional(),
|
date_from: Joi.date().iso().optional(),
|
||||||
date_to: Joi.date().iso().optional(),
|
date_to: Joi.date().iso().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
32
src/modules/vendors/vendors.service.js
vendored
32
src/modules/vendors/vendors.service.js
vendored
@ -459,10 +459,29 @@ const listBankDetails = async (vendorId) => {
|
|||||||
return rows.map(sanitizeBankDetail);
|
return rows.map(sanitizeBankDetail);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearOtherPrimaryBankDetails = async (tx, vendorId, exceptId = null) => {
|
||||||
|
await tx.vendor_bank_details.updateMany({
|
||||||
|
where: {
|
||||||
|
vendor_id: BigInt(vendorId),
|
||||||
|
is_primary: true,
|
||||||
|
is_active: true,
|
||||||
|
...(exceptId ? { id: { not: BigInt(exceptId) } } : {}),
|
||||||
|
},
|
||||||
|
data: { is_primary: false },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const createBankDetail = async (vendorId, payload, userId, requestId) => {
|
const createBankDetail = async (vendorId, payload, userId, requestId) => {
|
||||||
await getVendorOrThrow(vendorId);
|
await getVendorOrThrow(vendorId);
|
||||||
|
|
||||||
const created = await prisma.vendor_bank_details.create({
|
const isPrimary = payload.is_primary ?? false;
|
||||||
|
|
||||||
|
const created = await prisma.$transaction(async (tx) => {
|
||||||
|
if (isPrimary) {
|
||||||
|
await clearOtherPrimaryBankDetails(tx, vendorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.vendor_bank_details.create({
|
||||||
data: {
|
data: {
|
||||||
bank_name: payload.bank_name,
|
bank_name: payload.bank_name,
|
||||||
branch: payload.branch ?? null,
|
branch: payload.branch ?? null,
|
||||||
@ -471,13 +490,14 @@ const createBankDetail = async (vendorId, payload, userId, requestId) => {
|
|||||||
ifsc: payload.ifsc,
|
ifsc: payload.ifsc,
|
||||||
account_holder_name: payload.account_holder_name,
|
account_holder_name: payload.account_holder_name,
|
||||||
account_type: payload.account_type ?? 'CURRENT',
|
account_type: payload.account_type ?? 'CURRENT',
|
||||||
is_primary: payload.is_primary ?? false,
|
is_primary: isPrimary,
|
||||||
is_active: payload.is_active ?? true,
|
is_active: payload.is_active ?? true,
|
||||||
vendor_id: BigInt(vendorId),
|
vendor_id: BigInt(vendorId),
|
||||||
created_by: userId ? BigInt(userId) : null,
|
created_by: userId ? BigInt(userId) : null,
|
||||||
updated_by: userId ? BigInt(userId) : null,
|
updated_by: userId ? BigInt(userId) : null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await auditLog({
|
await auditLog({
|
||||||
tableName: 'vendor_bank_details',
|
tableName: 'vendor_bank_details',
|
||||||
@ -513,10 +533,16 @@ const updateBankDetail = async (vendorId, bankDetailId, payload, userId, request
|
|||||||
data.account_number_index = blindIndex(payload.account_number);
|
data.account_number_index = blindIndex(payload.account_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await prisma.vendor_bank_details.update({
|
const updated = await prisma.$transaction(async (tx) => {
|
||||||
|
if (payload.is_primary === true) {
|
||||||
|
await clearOtherPrimaryBankDetails(tx, vendorId, bankDetailId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.vendor_bank_details.update({
|
||||||
where: { id: BigInt(bankDetailId) },
|
where: { id: BigInt(bankDetailId) },
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await auditLog({
|
await auditLog({
|
||||||
tableName: 'vendor_bank_details',
|
tableName: 'vendor_bank_details',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user