GoodsReceivedNotePage Implementation Plan
Overview
This document outlines the implementation plan for creating a GoodsReceivedNotePage following the existing patterns established by the PurchaseOrderPage in the FlowPOS workspace.
Context
A Goods Received Note (GRN) is a document used in inventory and procurement processes to record the receipt of goods from a supplier. It is typically issued by the receiving department or warehouse team when goods are delivered and physically checked.
Key Purposes of a GRN
- Confirmation of delivery – It confirms that the goods listed on a purchase order have been received.
- Verification – It is used to check whether the items received match the purchase order in terms of quantity and quality.
- Inventory update – It helps update stock levels in the inventory management system.
- Accounts payable support – It supports invoice verification before payment is made to the supplier.
Typical Fields in a GRN
- GRN number
- Date of receipt
- Supplier name (from linked Purchase Order)
- Purchase Order number
- List of items received (including item description, quantity, and unit)
- Condition of goods
- Signature of the person receiving the goods
- Notes on damages or discrepancies (if any)
1. File Structure
apps/frontend-pwa/src/
├── components/forms/
│ ├── goods-received-note/
│ │ ├── GoodsReceivedNoteForm.tsx
│ │ ├── GoodsReceivedNoteSearch.tsx
│ │ ├── GoodsReceivedNoteList.tsx
│ │ ├── GoodsReceivedNoteItemRow.tsx
│ │ └── GoodsReceivedNoteTotals.tsx
│ └── GoodsReceivedNotePage.tsx
├── services/
│ └── goodsReceivedNoteService.ts
└── types/
└── goodsReceivedNote.ts
2. Type Definitions (types/goodsReceivedNote.ts)
Based on the backend DTO and the curl example, we need to define:
// Core interfaces
interface GoodsReceivedItem {
id?: string;
productId: string;
product?: Product;
quantity: number;
unitPrice: number;
total: number;
notes?: string;
batchNumber?: string;
serialNumber?: string;
inventoryDetails?: InventoryDetail[];
taxes?: TaxDetail[];
}
interface GoodsReceivedNote {
id?: string;
businessId: string;
status: GoodsReceivedNoteStatus;
purchaseOrderId: string;
receivedDate: string;
locationId: string;
locationName: string;
totalAmount: number;
exchangeRate: number;
totalBaseAmount: number;
currencyId: string;
goodsReceivedDetail: {
items: GoodsReceivedItem[];
};
paymentDetail: Record<string, any>;
note?: string;
createdBy?: string;
createdAt?: string;
updatedAt?: string;
}
interface GoodsReceivedNoteFormData {
id?: string;
purchaseOrderId: string;
receivedDate: Date;
status: GoodsReceivedNoteStatus;
items: GoodsReceivedItem[];
note?: string;
currencyId: string;
}
3. Service Layer (services/goodsReceivedNoteService.ts)
Following the same pattern as purchaseOrderService.ts:
// Core CRUD operations
- getGoodsReceivedNotes()
- getGoodsReceivedNoteById()
- createGoodsReceivedNote()
- updateGoodsReceivedNote()
- deleteGoodsReceivedNote()
- searchGoodsReceivedNotes()
- getGoodsReceivedNotesByPurchaseOrder()
// PDF operations (if needed)
- generateGoodsReceivedNotePDF()
- downloadGoodsReceivedNotePDF()
- getGoodsReceivedNotePrintPreview()
4. Main Page Component (GoodsReceivedNotePage.tsx)
Following the same pattern as PurchaseOrderPage.tsx:
Key Features
- Tab-based interface (Search existing GRNs / Create new GRN)
- Form submission handling with error management
- Loading states and user feedback
- Integration with authentication and business context
- Success/error toast notifications
State Management
isSubmitting- form submission stateerror- error handlingcreatedGoodsReceivedNote- newly created GRN- Form reference for reset functionality
5. Form Component (GoodsReceivedNoteForm.tsx)
Following the same pattern as PurchaseOrderForm.tsx:
Key Features
- Purchase Order selector (to link GRN to existing PO)
- Date picker for received date
- Status selector (DRAFT, SUBMITTED, REVIEWED)
- Items list with product selection, quantities, and pricing
- Currency selection
- Notes field
- Form validation using Zod schema
- Item management (add/remove items)
Form Schema
const goodsReceivedNoteSchema = z.object({
id: z.string().optional(),
purchaseOrderId: z.string().min(1, "Purchase Order is required"),
receivedDate: z.date({ required_error: "Received date is required" }),
status: z.nativeEnum(GoodsReceivedNoteStatus),
items: z.array(z.object({
productId: z.string().min(1, "Product is required"),
quantity: z.number().min(1, "Quantity must be at least 1"),
unitPrice: z.number().min(0, "Unit price must be positive"),
notes: z.string().optional(),
batchNumber: z.string().optional(),
serialNumber: z.string().optional(),
})).min(1, "At least one item is required"),
note: z.string().optional(),
currencyId: z.string().min(1, "Currency is required"),
});
6. Search Component (GoodsReceivedNoteSearch.tsx)
Following the same pattern as PurchaseOrderSearch.tsx:
Key Features
- Search by GRN number, supplier, or purchase order
- Date range filtering
- Status filtering
- Pagination
- Results display with key information
- Selection functionality
7. List and Item Components
GoodsReceivedNoteList.tsx- Container for itemsGoodsReceivedNoteItemRow.tsx- Individual item row with product selector, quantities, pricingGoodsReceivedNoteTotals.tsx- Summary calculations
8. Key Differences from PurchaseOrderPage
GRN-Specific Features
- Purchase Order Linking: GRNs are always linked to an existing Purchase Order
- Received Date: Focus on when goods were actually received vs. expected delivery
- Status Flow: DRAFT → SUBMITTED → REVIEWED (different from PO statuses)
- Inventory Integration: GRNs directly affect inventory levels when submitted
- Batch/Serial Numbers: Support for tracking individual items
- Tax Handling: Support for tax calculations per item
- Inventory Details: Support for multiple batch/serial numbers per item
Form Differences
- No supplier selection (comes from linked PO)
- No delivery date (uses received date instead)
- No payment terms (handled by PO)
- No shipping/billing addresses (from PO)
- Added batch/serial number fields
- Added inventory details support
9. Integration Points
Required Components
PurchaseOrderSelector- to select linked POProductSelector- for item selectionCurrencySelector- for currency selectionLocationSelector- for receiving locationInventoryDetailBatch- for batch number managementInventoryDetailSerial- for serial number management
Context Dependencies
useAuth()- for authenticationuseCurrentBusiness()- for business contextuseCurrentLocation()- for location contextuseBusinessCurrencies()- for currency optionsuseFetchUser()- for user information
10. API Integration
Backend Endpoints to Use
POST /goods-received-notes- Create GRNGET /goods-received-notes- List GRNs with paginationGET /goods-received-notes/:id- Get specific GRNPATCH /goods-received-notes/:id- Update GRNDELETE /goods-received-notes/:id- Delete GRNGET /goods-received-notes/purchase-order/:purchaseOrderId- Get GRNs by POPUT /goods-received-notes/:id/submit- Submit GRN
Example API Request (from curl)
{
"businessId": "e67fc0f8-446b-4608-ad44-c83126a84b87",
"createdBy": "f04b2250-34a0-409c-bb57-612d863851c9",
"status": "submitted",
"purchaseOrderId": "88d61ded-19b4-4176-afed-8d57d2923115",
"receivedDate": "2025-07-05T02:38:07.165Z",
"locationId": "8ae12985-6ff3-44e7-a4cf-71602a042ae2",
"locationName": "ANTIGUA",
"totalAmount": 95.00,
"exchangeRate": 7.90,
"totalBaseAmount": 84.821427,
"currencyId": "92a7a47e-7ef5-4b16-8b74-0a18bdbaf7b2",
"goodsReceivedDetail": {
"items": [
{
"id": "72175622-a8cb-474f-98b2-1be4857dffe1",
"businessId": "e67fc0f8-446b-4608-ad44-c83126a84b87",
"status": "ACTIVE",
"createdAt": "2025-07-05T02:38:07.165Z",
"createdBy": "f04b2250-34a0-409c-bb57-612d863851c9",
"locationId": "8ae12985-6ff3-44e7-a4cf-71602a042ae2",
"locationName": "ANTIGUA",
"productId": "3e44dcfa-f349-4e9e-be38-828b8377c058",
"productName": "Generic Cotton Chips",
"goodOrService": "B",
"quantity": 9.00,
"unitPrice": 5.00,
"total": 45.00,
"taxes": [
{
"shortName": "IVA",
"taxableUnitCode": "1",
"taxableAmount": 40.178571,
"taxAmount": 4.821429
}
],
"inventoryDetails": [
{
"batchNumber": "89967",
"quantity": 3
},
{
"batchNumber": "89973",
"quantity": 6
}
],
"product": {
"id": "3e44dcfa-f349-4e9e-be38-828b8377c058",
"name": "Generic Cotton Chips"
}
}
]
},
"paymentDetail": {}
}
11. Translation Keys Needed
// Add to i18n files
goodsReceivedNote: {
title: "Goods Received Note",
newDocument: "New GRN",
searchExisting: "Search Existing GRNs",
document: "GRN Document",
selectPurchaseOrder: "Select Purchase Order",
receivedDate: "Received Date",
status: "Status",
items: "Received Items",
addItem: "Add Item",
notes: "Notes",
save: "Save GRN",
update: "Update GRN",
successCreate: "GRN created successfully",
successUpdate: "GRN updated successfully",
error: "Failed to save GRN",
invalidItems: "Please check all items are valid",
statuses: {
draft: "Draft",
submitted: "Submitted",
reviewed: "Reviewed"
}
}
12. Implementation Steps
- Create Type Definitions - Define all interfaces and types
- Create Service Layer - Implement API service functions
- Create Form Components - Build the form structure and validation
- Create Search Components - Implement search and listing functionality
- Create Main Page - Integrate all components and handle state
- Add Translations - Add all required translation keys
- Testing - Test all CRUD operations and edge cases
- Integration Testing - Test with existing Purchase Order functionality
13. Considerations
Performance
- Implement proper loading states
- Use pagination for large datasets
- Optimize re-renders with proper memoization
User Experience
- Clear error messages and validation feedback
- Intuitive form flow
- Responsive design for mobile devices
- Keyboard navigation support
Data Integrity
- Validate all required fields
- Ensure proper data types
- Handle API errors gracefully
- Maintain data consistency with linked Purchase Orders
Security
- Validate user permissions
- Sanitize all inputs
- Handle sensitive data appropriately
- Implement proper authentication checks
This plan provides a comprehensive foundation for creating a GoodsReceivedNotePage that follows the existing patterns in the codebase while addressing the specific requirements of GRN functionality.