Replace the free-text Project Code input with a native <select> carrying a fixed list of project codes (Petronet LNG Cochin, COMACOE Trombay, Haldia Reach, Haldia MMT, COMACOE Mandvi) plus an empty "— none —" option, across all three PO forms (new / edit / manager-edit). - Add a shared PROJECT_CODES constant in lib/validations/po.ts as the single source of truth. - Add a reusable <ProjectCodeField> (mirrors <DeliveryLocationField>): plain HTML select keeping name="projectCode" so the server actions are unchanged. - The field stays optional; projectCode remains a nullable free-text snapshot (no schema/migration, no validation tightening) so legacy/imported values are not rejected. On edit, a current value not in the list is preserved as a leading "(current)" option so it is never silently dropped. - Add unit tests for the new field. Fixes #124 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const lineItemSchema = z.object({
|
|
name: z.string().min(1, "Item name is required"),
|
|
description: z.string().optional(),
|
|
quantity: z.coerce.number().positive("Quantity must be positive"),
|
|
unit: z.string().min(1, "Unit is required"),
|
|
size: z.string().optional(),
|
|
unitPrice: z.coerce.number().nonnegative("Unit price must be non-negative"),
|
|
gstRate: z.coerce.number().min(0).max(1).default(0.18),
|
|
productId: z.string().optional(),
|
|
accountId: z.string().optional(),
|
|
});
|
|
|
|
export const TC_FIXED_LINE =
|
|
"Please quote this purchase order no. for further communications and invoices pertaining to this indent.";
|
|
|
|
export const TC_FIXED_LINE_2 =
|
|
"We encourage bulk packaging and avoid plastic. No asbestos to be used in any product or packing material.";
|
|
|
|
/**
|
|
* Fixed list of selectable Project Codes (issue #124). The PO `projectCode`
|
|
* column stays a nullable free-text snapshot — this list only constrains how
|
|
* the value is picked in the three PO forms (so legacy / imported values are
|
|
* never rejected). Extend here to add a code everywhere at once.
|
|
*/
|
|
export const PROJECT_CODES = [
|
|
"Petronet LNG Cochin",
|
|
"COMACOE Trombay",
|
|
"Haldia Reach",
|
|
"Haldia MMT",
|
|
"COMACOE Mandvi",
|
|
] as const;
|
|
|
|
export const TC_DEFAULTS = {
|
|
tcDelivery: "Within 4 to 5 days",
|
|
tcDispatch: "To be transported to site address as above. Freight Supplier's A/C",
|
|
tcInspection: "NA",
|
|
tcTransitInsurance: "NA",
|
|
tcPaymentTerms: "Within 30 days from delivery.",
|
|
tcOthers: "",
|
|
};
|
|
|
|
export const createPoSchema = z.object({
|
|
title: z.string().min(1, "Title is required").max(200),
|
|
vesselId: z.string().min(1, "Cost Centre is required"),
|
|
accountId: z.string().min(1, "Accounting Code is required"),
|
|
companyId: z.string().optional(),
|
|
poDate: z.string().optional(),
|
|
projectCode: z.string().optional(),
|
|
dateRequired: z.string().optional(),
|
|
vendorId: z.string().optional(),
|
|
currency: z.string().default("INR"),
|
|
piQuotationNo: z.string().optional(),
|
|
piQuotationDate: z.string().optional(),
|
|
requisitionNo: z.string().optional(),
|
|
requisitionDate: z.string().optional(),
|
|
placeOfDelivery: z.string().optional(),
|
|
tcDelivery: z.string().optional(),
|
|
tcDispatch: z.string().optional(),
|
|
tcInspection: z.string().optional(),
|
|
tcTransitInsurance: z.string().optional(),
|
|
tcPaymentTerms: z.string().optional(),
|
|
tcOthers: z.string().optional(),
|
|
lineItems: z.array(lineItemSchema).min(1, "At least one line item is required"),
|
|
});
|
|
|
|
export const approvePoSchema = z.object({
|
|
note: z.string().optional(),
|
|
// Absolute advance amount the Manager wants paid first (issue #92). The UI
|
|
// slider works in whole percent of totalAmount; the resolved amount is what we
|
|
// persist. Validated against the PO total in the action. Omitted ⇒ full payment.
|
|
suggestedAdvancePayment: z.coerce
|
|
.number()
|
|
.nonnegative("Advance payment cannot be negative")
|
|
.optional(),
|
|
});
|
|
|
|
export const rejectPoSchema = z.object({
|
|
note: z.string().min(1, "A rejection reason is required"),
|
|
});
|
|
|
|
export const requestEditsSchema = z.object({
|
|
note: z.string().min(1, "Please specify what edits are needed"),
|
|
});
|
|
|
|
export const processPaymentSchema = z.object({
|
|
paymentRef: z.string().min(1, "Payment reference is required"),
|
|
paymentAmount: z.number().positive("Payment amount must be greater than 0").optional(),
|
|
paymentDate: z.coerce
|
|
.date({ required_error: "Payment date is required", invalid_type_error: "Payment date is required" })
|
|
.refine((d) => {
|
|
// Not in the future — compare against end of today (local)
|
|
const endOfToday = new Date();
|
|
endOfToday.setHours(23, 59, 59, 999);
|
|
return d.getTime() <= endOfToday.getTime();
|
|
}, "Payment date cannot be in the future"),
|
|
});
|
|
|
|
export const confirmReceiptSchema = z.object({
|
|
notes: z.string().optional(),
|
|
});
|
|
|
|
export type CreatePoInput = z.infer<typeof createPoSchema>;
|
|
export type LineItemInput = z.infer<typeof lineItemSchema>;
|