Skip to content

Requisition Business Rules

This document outlines the business rules for requisitions in the PRS system.

Requisition Entity

A requisition is a formal request for goods or materials.

Requisition Types

  • OFM: Owner Furnished Material
  • Non-OFM: Non-Owner Furnished Material
  • OFM-TOM: Owner Furnished Material - Transfer of Material
  • Non-OFM-TOM: Non-Owner Furnished Material - Transfer of Material

Requisition Status Flow

Text Only
1
2
3
DRAFT → FOR_RS_APPROVAL → ASSIGNING → ASSIGNED → RS_IN_PROGRESS → CLOSED_RS; 
RS_CANCELLED; 
RS_REJECTED
  • RS can be REJECTED during FOR APPROVAL status
  • RS can be CANCELLED by the requester if the most progressed/advanced status of its PURCHASE ORDER/S is FOR_SENDING
  • RS will be set to RS_IN_PROGRESS once a canvass is submitted
  • RS will be set to CLOSED_RS when all POs are closed and no items left to canvass

Requisition Fields

Field Description Validation Rules
rsNumber Requisition number Auto-generated based on company code
rsLetter Requisition letter Auto-generated
category Requisition category Must be one of the defined categories
type Requisition type Must be one of the defined types
companyId Company ID Required
departmentId Department ID Required
projectId Project ID Optional
dateRequired Required date Required, must be a valid date
deliverTo Delivery address Required, max 100 characters
purpose Purpose of requisition Required, max 100 characters
chargeTo Charge to entity Must be one of: supplier, project, association, company
chargeToId ID of charge to entity Required if chargeTo is specified
status Current status Must be one of the defined statuses
createdBy User ID of creator Required
assignedTo User ID of assignee Required for certain statuses

Business Rules

1. Requisition Creation

  • A requisition can be created as a draft or submitted immediately
  • If not a draft, all required fields must be filled
  • If not a draft, at least one item must be added
  • RS Number is auto-generated based on company code
  • RS Letter is auto-generated
  • Creator is set to the current user
  • Status is set to DRAFT based on isDraft flag
JavaScript
// Example code for RS Number generation
async function generateRSNumberCode(companyCode, isDraft) {
  const rsLetter = 'A'; // Default letter

  if (isDraft) {
    return { rsNumber: null, rsLetter };
  }

  const latestRS = await requisitionRepository.findOne({
    where: {
      companyCode,
      rsNumber: { [Sequelize.Op.not]: null },
    },
    order: [['rsNumber', 'DESC']],
  });

  let rsNumber = '00001';

  if (latestRS && latestRS.rsNumber) {
    const latestNumber = parseInt(latestRS.rsNumber, 10);
    rsNumber = (latestNumber + 1).toString().padStart(5, '0');
  }

  return { rsNumber, rsLetter };
}

2. Requisition Submission

  • All required fields must be filled
  • At least one item must be added
  • Upon submission, status changes to FOR_RS_APPROVAL
  • Approvers are assigned based on category, project, department and quantity (GFQs)

3. Requisition Approval

  • Only FOR_RS_APPROVAL requisitions can be approved
  • User must be an assigned approver for the requisition
  • If all approvers approve, status changes to ASSIGNING
  • Status will remain as FOR APPROVAL until fully approved
  • Additional approvers can be added during the approval process
  • Approvers can input a note during approval
  • Approvers can edit item quantity or delete an item from the table
JavaScript
// Example approval logic
async function approveRequisition(requisitionId, approverId, transaction) {
  const requisition = await requisitionRepository.findOne({
    where: { id: requisitionId },
    include: [{ association: 'requisitionApprovers' }],
    transaction,
  });

  if (requisition.status !== 'SUBMITTED' && requisition.status !== 'PARTIALLY_APPROVED') {
    throw new Error('Requisition cannot be approved');
  }

  const approver = requisition.requisitionApprovers.find(a => a.userId === approverId);

  if (!approver) {
    throw new Error('User is not an approver for this requisition');
  }

  if (approver.status === 'APPROVED') {
    throw new Error('User has already approved this requisition');
  }

  await approver.update({ status: 'APPROVED' }, { transaction });

  const allApprovers = requisition.requisitionApprovers;
  const pendingApprovers = allApprovers.filter(a => a.status !== 'APPROVED');

  if (pendingApprovers.length === 0) {
    await requisition.update({ status: 'APPROVED' }, { transaction });
  } else {
    await requisition.update({ status: 'PARTIALLY_APPROVED' }, { transaction });
  }

  return requisition;
}

4. Requisition Rejection

  • Only FOR_APPROVAL requisitions can be rejected
  • User must be an assigned approver for the requisition
  • Rejection requires a reason/comment
  • Upon rejection, status changes to RS_REJECTED
  • Requester should be able to edit RS once rejected
  • Can resubmit RS and will undergo approval process again with status FOR_RS_APPROVAL

5. Requisition Assignment

  • Only fully approved requisitions can be assigned with status ASSIGNING
  • Only Purchasing Head and Purchasing Staff can assign an RS to himself or another staff
  • Upon assignment, status changes to ASSIGNED
  • Once assigned, only Purchasing Head can re-assign / update assignments for various statuses

6. Requisition Items

  • Adding of items will be based on RS_TYPE
  • Each requisition must have at least one item (if not a draft)
  • Items must have a valid quantity, unit, and description
  • Items can have notes (optional)
  • Items can be added, updated, or deleted while the requisition is in DRAFT status
  • Steelbars are shown in a seperate tab
  • OFM Items are filtered based on user's trade and project
  • Requested quantity will be deducted from item's Remaining GFQ once submitted

Validation Rules

Detailed validation rules are defined in the Zod schema:

JavaScript
const createRequisitionSchema = z
  .object({
    isDraft: z.enum(['true']).transform(value => value === 'true'),
    type: z.enum(REQUISITION_REQUEST_TYPES),
    companyId: createNumberSchema('Company ID'),
    departmentId: createNumberSchema('Department ID'),
    projectId: createNumberSchema('Project ID').optional(),
    dateRequired: z.string().datetime(),
    deliverTo: z.string().min(1, 'Deliver to is required'),
    purpose: z.string()
      .min(1, 'Purpose must not be empty')
      .max(100, 'Purpose maximum 100 characters')
      .regex(
        /^[a-zA-Z0-9Ññ!@#$%^&*()_+\-=\\[\]{};':"\\|,.<>\/?`~ \r\n]*$/,
        'Purpose contains invalid characters.'
      ),
    chargeTo: z.enum(REQUISITION_CHARGE_TO).optional(),
    chargeToId: createNumberSchema('Charge To ID').optional(),
    items: z.array(
      z.object({
        id: createNumberSchema('Item ID').optional(),
        quantity: createNumberSchema('Quantity'),
        unit: z.string().min(1, 'Unit is required'),
        notes: z.string().max(500, 'Maximum of 500 characters').optional(),
      })
    ).optional(),
    comment: z.string()
      .max(100, 'Comment maximum 100 characters')
      .regex(
        /^[a-zA-Z0-9Ññ!@#$%^&*()_+\-=\\[\]{};':"\\|,.<>\/?`~ \r\n]*$/,
        'Comment contains invalid characters.'
      )
      .optional(),
  })
  .strict();