Reject reason added for PO

This commit is contained in:
Gowtham M 2026-07-22 18:39:00 +05:30
parent 70deacb969
commit 7516354ce5
6 changed files with 24 additions and 6 deletions

View File

@ -239,7 +239,7 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
| [x] | DELETE | `/purchase-orders/:id` | delete | Soft delete |
| [x] | POST | `/purchase-orders/:id/submit` | edit | Submit for approval |
| [x] | POST | `/purchase-orders/:id/approve` | approve | Approve PO |
| [x] | POST | `/purchase-orders/:id/reject` | approve | Reject PO |
| [x] | POST | `/purchase-orders/:id/reject` | approve | Reject PO (`reject_reason` required; stored on PO) |
| [x] | POST | `/purchase-orders/:id/amend` | edit | Amend PO |
| [x] | POST | `/purchase-orders/:id/cancel` | edit | Cancel PO |
| [x] | GET | `/purchase-orders/:id/pdf` | view | PDF export |

View File

@ -711,6 +711,7 @@ model purchase_orders {
parent_po_id BigInt?
terms_and_conditions String?
remarks String?
reject_reason String?
is_active Boolean @default(true)
created_by BigInt?
updated_by BigInt?

View File

@ -0,0 +1,4 @@
-- Persist PO rejection reason on purchase_orders header
ALTER TABLE purchase_orders
ADD COLUMN IF NOT EXISTS reject_reason TEXT;

View File

@ -67,9 +67,10 @@ components:
remarks: { type: string, example: 'Approved for procurement' }
PurchaseOrdersRejectBody:
type: object
required: [remarks]
required: [reject_reason]
properties:
remarks: { type: string, example: 'Rates not competitive' }
reject_reason: { type: string, example: 'Rates not competitive' }
remarks: { type: string, example: 'Rates not competitive', description: 'Legacy alias for reject_reason' }
PoAttachmentResponse:
type: object
properties:
@ -247,6 +248,7 @@ paths:
post:
tags: [Purchase Orders]
summary: Reject purchase order
description: Requires reject_reason. Stored on purchase_orders.reject_reason and cleared on resubmit/approve.
parameters:
- { name: id, in: path, required: true, schema: { type: string, example: '1' } }
requestBody:

View File

@ -697,6 +697,7 @@ const submitPurchaseOrder = async (id, payload, userId, requestId) => {
where: { id: BigInt(id) },
data: {
status: 'PENDING_APPROVAL',
reject_reason: null,
remarks: payload.remarks ?? existing.remarks,
updated_by: userId ? BigInt(userId) : null,
},
@ -739,6 +740,7 @@ const approvePurchaseOrder = async (id, payload, userId, requestId) => {
where: { id: BigInt(id) },
data: {
status: 'APPROVED',
reject_reason: null,
updated_by: userId ? BigInt(userId) : null,
},
include: poDetailInclude,
@ -765,12 +767,15 @@ const rejectPurchaseOrder = async (id, payload, userId, requestId) => {
const pendingApproval = existing.po_approvals.find((row) => row.status === 'PENDING');
if (!pendingApproval) throw new ApiError(409, 'No pending approval step found');
const rejectReason = String(payload.reject_reason || payload.remarks || '').trim();
if (!rejectReason) throw new ApiError(422, 'reject_reason is required');
const updated = await prisma.$transaction(async (tx) => {
await tx.po_approvals.update({
where: { id: pendingApproval.id },
data: {
status: 'REJECTED',
remarks: payload.remarks,
remarks: rejectReason,
approver_user_id: userId ? BigInt(userId) : null,
acted_at: new Date(),
},
@ -780,6 +785,7 @@ const rejectPurchaseOrder = async (id, payload, userId, requestId) => {
where: { id: BigInt(id) },
data: {
status: 'REJECTED',
reject_reason: rejectReason,
updated_by: userId ? BigInt(userId) : null,
},
include: poDetailInclude,

View File

@ -111,8 +111,13 @@ const workflowRemarksSchema = Joi.object({
});
const rejectPurchaseOrderSchema = Joi.object({
remarks: Joi.string().trim().min(1).required(),
});
reject_reason: Joi.string().trim().min(1).optional(),
remarks: Joi.string().trim().min(1).optional(), // legacy alias for reject_reason
})
.or('reject_reason', 'remarks')
.messages({
'object.missing': 'reject_reason is required',
});
module.exports = {
createPurchaseOrderSchema,