741 lines
16 KiB
Markdown
741 lines
16 KiB
Markdown
# Document API Documentation
|
|
|
|
## Overview
|
|
|
|
The Document API allows authenticated users to generate documents asynchronously (PDF, Excel, etc.) from provided data. The system uses a queue-based architecture for reliable, scalable document generation.
|
|
|
|
**Key Features:**
|
|
- ✅ Non-blocking requests (returns immediately with jobId)
|
|
- ✅ Job status polling to track progress
|
|
- ✅ Automatic retries with exponential backoff
|
|
- ✅ Support for multiple document types and formats
|
|
- ✅ Concurrent document generation
|
|
- ✅ Case-insensitive document names with whitespace and punctuation normalization
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Client Request → Queue Job → Return jobId (202)
|
|
↓
|
|
Worker processes
|
|
↓
|
|
Client polls status
|
|
↓
|
|
Job complete → Download file
|
|
```
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### 1. Generate Document
|
|
|
|
**Endpoint:** `POST /api/document/generate`
|
|
|
|
**URL:** `http://localhost:3070/api/document/generate`
|
|
|
|
**Authentication:** Required ✓
|
|
|
|
**Authorization:** Required - User must have one of the following roles:
|
|
- `admin`
|
|
- `management`
|
|
- `team_head`
|
|
- `user`
|
|
|
|
**HTTP Status:** `202 Accepted`
|
|
|
|
#### Request Headers
|
|
```
|
|
Content-Type: application/json
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
#### Request Body
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `document` | string | Yes | Type of document (e.g., "INVOICE", "DISPATCHNOTE", "DELIVERYNOTE") |
|
|
| `documentType` | string | Yes | Output format (e.g., "pdf", "excel") |
|
|
| `documentData` | object | Yes | Data object for the document |
|
|
|
|
#### Response (202 Accepted)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Document generation started",
|
|
"jobId": "1"
|
|
}
|
|
```
|
|
|
|
#### Error Responses
|
|
|
|
**400 Bad Request:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "document, documentType, and documentData are required"
|
|
}
|
|
```
|
|
|
|
**500 Server Error:**
|
|
```json
|
|
|
|
{
|
|
"success": false,
|
|
"message": "Error message describing the issue"
|
|
}
|
|
```
|
|
|
|
### Supported Document Values
|
|
|
|
You can send the document name in any case. Spaces, underscores, and hyphens are ignored by the generator.
|
|
|
|
- `PRECOST`
|
|
- `INVOICE`
|
|
- `DISPATCHNOTE`
|
|
- `DELIVERYNOTE`
|
|
- `CUSTOMDOCUMENT`
|
|
- `CREDITNOTE`
|
|
- `PO`
|
|
|
|
|
|
## Sample Request Bodies
|
|
|
|
All examples below are for `POST /api/document/generate` with `documentType: "pdf"`.
|
|
|
|
### PreCost
|
|
|
|
```json
|
|
{
|
|
"document": "PRECOST",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"vessel_name": "MV OCEAN STAR",
|
|
"supplyPort": "DUBAI",
|
|
"clientRfqNum": "RFQ-2026-001",
|
|
"omsRfqNum": "OMS-RFQ-0001",
|
|
"date": "08/07/2026",
|
|
"items": [
|
|
{
|
|
"name": "Marine Rope",
|
|
"description": "High strength rope",
|
|
"qty": 10,
|
|
"unitPrice": 12.5
|
|
},
|
|
{
|
|
"name": "Safety Gloves",
|
|
"description": "Blue gloves",
|
|
"qty": 5,
|
|
"unitPrice": 8
|
|
}
|
|
],
|
|
"sub_total": 125,
|
|
"discount": 0,
|
|
"additionalCharges": 0,
|
|
"total_cost": 125
|
|
}
|
|
}
|
|
```
|
|
|
|
### Invoice
|
|
|
|
```json
|
|
{
|
|
"document": "INVOICE",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"jobReference": "JOB-2026-001",
|
|
"poNumber": "PO-20254161",
|
|
"date": "08/07/2026",
|
|
"pageLabel": "Page 01 of 02",
|
|
"billToName": "SUNRICH SHIP CHANDLERS L.L.C",
|
|
"billToAddress": "BUSINESS BAY - ASPECT TOWER, 22ND FLOOR, OFFICE NO. 2201, P.O. BOX NO 39976, DUBAI",
|
|
"vesselName": "MV OCEAN STAR",
|
|
"purposeOfCall": "SUPPLY OF PROVISIONS",
|
|
"supplyDate": "08/07/2026",
|
|
"grt": "12345",
|
|
"imoNumber": "9876543",
|
|
"portCountry": "DUBAI / UAE",
|
|
"items": [
|
|
{
|
|
"description": "Marine rope",
|
|
"remarks": "Delivered as requested",
|
|
"quantity": 10,
|
|
"unit_price": 12.5
|
|
},
|
|
{
|
|
"description": "Safety gloves",
|
|
"remarks": "Blue color",
|
|
"quantity": 5,
|
|
"unit_price": 8
|
|
}
|
|
],
|
|
"subtotal": 165,
|
|
"tax": 0,
|
|
"discount": 0,
|
|
"total": 165,
|
|
"paymentTerms": "Payment due within 30 days",
|
|
"notes": "Please verify quantities upon delivery."
|
|
}
|
|
}
|
|
```
|
|
|
|
### Dispatch Note
|
|
|
|
```json
|
|
{
|
|
"document": "DISPATCHNOTE",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"referenceNumber": "DN/2026/001",
|
|
"date": "08/07/2026",
|
|
"vessel": "MV OCEAN STAR",
|
|
"captain": "CAPT. JOHN DOE",
|
|
"cook": "SAMUEL",
|
|
"agent": "OCEANIC AGENT LTD",
|
|
"details": "Delivery of provisions and stores",
|
|
"placeOfDelivery": "DUBAI PORT",
|
|
"eta": "08/07/2026 08:00",
|
|
"etd": "08/07/2026 18:00",
|
|
"numberOfCrew": 18,
|
|
"nextMainOrderIn": "24 HRS",
|
|
"port": "DUBAI",
|
|
"company": "OCEANIC SHIP CHANDLERS (PVT) LTD",
|
|
"fileNo": "FILE-2026-04",
|
|
"items": [
|
|
{
|
|
"product": "Rice",
|
|
"qty": 10,
|
|
"unit": "BAGS",
|
|
"supp": "OMS",
|
|
"poNo": "PO-001",
|
|
"receivedQty": 10,
|
|
"issuedQty": 10,
|
|
"expDate": "12/07/2026",
|
|
"receivedTime": "09:15",
|
|
"date": "08/07/2026",
|
|
"rejects": "",
|
|
"fatCon": "",
|
|
"remark": "Delivered"
|
|
}
|
|
],
|
|
"showSignatures": true,
|
|
"preparedBy": "Admin User",
|
|
"approvedBy": "Manager User"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Delivery Note
|
|
|
|
```json
|
|
{
|
|
"document": "DELIVERYNOTE",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"referenceNumber": "DN/OSC/2602/01",
|
|
"date": "08/07/2026",
|
|
"billToName": "SUNRICH SHIP CHANDLERS L.L.C",
|
|
"billToAddress": "BUSINESS BAY - ASPECT TOWER, 22ND FLOOR, OFFICE NO. 2201, P.O. BOX NO 39976, DUBAI",
|
|
"supplyDate": "08/07/2026",
|
|
"poNumber": "PO-20254161",
|
|
"items": [
|
|
{
|
|
"description": "Marine rope",
|
|
"remarks": "Delivered as requested",
|
|
"unit": "PCS",
|
|
"quantity": 10
|
|
},
|
|
{
|
|
"description": "Safety gloves",
|
|
"remarks": "Blue color",
|
|
"unit": "BOX",
|
|
"quantity": 5
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Custom Document
|
|
|
|
```json
|
|
{
|
|
"document": "CUSTOMDOCUMENT",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"title": "CUSTOM DOCUMENT",
|
|
"date": "08/07/2026",
|
|
"telePhone": "+94 112 083 206",
|
|
"emailAddress": "shipsupply@oceanicmsol.com",
|
|
"officeAddressLine1": "Level 5D, Valiant Towers, Nawala Mawatha",
|
|
"officeAddressLine2": "Colombo 02",
|
|
"officeAddressLine3": "SRI LANKA",
|
|
"directorOfCustoms": "The Director of Customs",
|
|
"vehicleNo": "WPLO 9767",
|
|
"permitNo": "MV20231214006001 / Permit No. MP20240717017006",
|
|
"chiefSecurityOfficer": "Chief Security Officer",
|
|
"exportGate": "Export Gate",
|
|
"slpa": "SLPA",
|
|
"gateNo": "Gate No. 03",
|
|
"permissionText": "Please grant permission to pass these Customs entry papers to supply goods to the vessel",
|
|
"companyName": "Oceanic Maritime Solutions (Pvt) Ltd",
|
|
"subtitle": "LICENSED SHIPCHANDLERS",
|
|
"sectionCode": "SC/FORM/06",
|
|
"items": [
|
|
{
|
|
"quantity": 10,
|
|
"unit": "PCS",
|
|
"description": "Marine rope",
|
|
"rate": 12.5,
|
|
"total": 125,
|
|
"confirmOrderRate": ""
|
|
},
|
|
{
|
|
"quantity": 5,
|
|
"unit": "PCS",
|
|
"description": "Safety gloves",
|
|
"rate": 8,
|
|
"total": 40,
|
|
"confirmOrderRate": ""
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Credit Note
|
|
|
|
```json
|
|
{
|
|
"document": "CREDITNOTE",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"companyName": "GREEK-LANKA MARITIME SERVICES (PVT) LTD",
|
|
"companyAddressLine1": "No. 56/2, Dharmapala Mw, Kotte",
|
|
"companyAddressLine2": "Sri Jayewardenepura",
|
|
"companyAddressLine3": "Sri Lanka",
|
|
"companyPostal": "Postal Code : 10100",
|
|
"companyContact": "Tel:+94 11 2083206 / Mobile (24/7) : +94 777 232 271",
|
|
"companyBR": "BR No : PV 0022630",
|
|
"companyLicense": "License No : SA00317-2024",
|
|
"crnNo": "GLMS/COLOMBO/474/CRN01",
|
|
"date": "26/07/2024",
|
|
"billToName": "Master & Owner of MV TEAM VENTURES",
|
|
"billToDetails": "VRIDHI MARITIME SHIP MANAGEMENT & OPERATION LLC\nOffice No: 2004, The Prism Tower, Dubai\n20th Floor, Business Bay,\nDubai - PO Box 29583.",
|
|
"vesselName": "MV \"TEAM VENTURES\"",
|
|
"grt": "25,543 MT",
|
|
"port": "REPAIRS AT COLOMBO DOCK YARD",
|
|
"imoNumber": "9339765",
|
|
"nameOfAgent": "29/04/2024 AT 21:00 HRS.LT",
|
|
"portCountry": "COLOMBO / SRI LANKA",
|
|
"items": [
|
|
{
|
|
"description": "SIGN OFF C/E MR. DHANSINGH BHAURYAL AND M/S J/E MR. SENTHIL",
|
|
"amount": 50
|
|
},
|
|
{
|
|
"description": "VISA + Handling Charges - M/S M/S MR. SENTHIL",
|
|
"amount": 100
|
|
},
|
|
{
|
|
"description": "Transport from Airport to Dockyard",
|
|
"amount": 70
|
|
}
|
|
],
|
|
"totalAmount": 220,
|
|
"approvedBy": "GLMS Finance Dept.",
|
|
"approvedByLabel": "Approved by GLMS Finance Dept.",
|
|
"departmentLabel": "GLMS - Accounts Department"
|
|
}
|
|
}
|
|
```
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Get Job Status
|
|
|
|
**Endpoint:** `GET /api/document/job/:jobId/status`
|
|
|
|
**URL:** `http://localhost:3070/api/document/job/1/status`
|
|
|
|
**Authentication:** Required ✓
|
|
|
|
**Authorization:** Required
|
|
|
|
**HTTP Status:** `200 OK`
|
|
|
|
#### Request Headers
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
#### Response (Waiting/Active)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"jobId": "1",
|
|
"state": "waiting",
|
|
"result": null,
|
|
"error": null,
|
|
"attempts": 0,
|
|
"stacktrace": []
|
|
}
|
|
```
|
|
|
|
#### Response (Completed)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"jobId": "83",
|
|
"state": "completed",
|
|
"result": {
|
|
"fileName": "precost-1781614132061.pdf",
|
|
"mimeType": "application/pdf",
|
|
"size": 220008,
|
|
"s3Key": "uploads/16adf29b-c0c2-45f9-8808-d6e15cd3d755.pdf",
|
|
"documentId": "16adf29b-c0c2-45f9-8808-d6e15cd3d755"
|
|
},
|
|
"error": null,
|
|
"attempts": 1,
|
|
"stacktrace": []
|
|
}
|
|
```
|
|
|
|
#### Response (Failed)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"jobId": "1",
|
|
"state": "failed",
|
|
"result": null,
|
|
"error": "Error message explaining the failure",
|
|
"attempts": 3,
|
|
"stacktrace": ["stack trace line 1", "stack trace line 2"]
|
|
}
|
|
```
|
|
|
|
#### Job States
|
|
|
|
| State | Meaning | Next State |
|
|
|-------|---------|-----------|
|
|
| `waiting` | Job queued, waiting for worker | `active` |
|
|
| `active` | Worker currently processing | `completed` or `failed` |
|
|
| `completed` | Job done, result available | (final) |
|
|
| `failed` | Job failed after 3 retries | (final) |
|
|
| `delayed` | Scheduled for later processing | `waiting` |
|
|
|
|
#### Error Responses
|
|
|
|
**404 Not Found:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Job not found"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Download Document
|
|
|
|
**Endpoint:** `GET /api/document/download/:uuid`
|
|
|
|
**URL:** `http://localhost:3070/api/document/download/16adf29b-c0c2-45f9-8808-d6e15cd3d755`
|
|
|
|
**Authentication:** Required ✓
|
|
|
|
**Authorization:** Required
|
|
|
|
**HTTP Status:** `200 OK`
|
|
|
|
#### Request Headers
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
#### Response (Success)
|
|
|
|
Returns the binary file with appropriate headers:
|
|
|
|
| Header | Value |
|
|
|--------|-------|
|
|
| `Content-Type` | `application/pdf` or `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
|
| `Content-Disposition` | `attachment; filename="[fileName]"` |
|
|
|
|
**Response Body:** Binary file content
|
|
|
|
#### Error Responses
|
|
|
|
**400 Bad Request:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "fileName is required"
|
|
}
|
|
```
|
|
|
|
**404 Not Found:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Document not found"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Cancel Job
|
|
|
|
**Endpoint:** `DELETE /api/document/job/:jobId`
|
|
|
|
**URL:** `http://localhost:3070/api/document/job/1`
|
|
|
|
**Authentication:** Required ✓
|
|
|
|
**Authorization:** Required
|
|
|
|
**HTTP Status:** `200 OK`
|
|
|
|
#### Request Headers
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
#### Response (Success)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Job cancelled successfully",
|
|
"jobId": "1"
|
|
}
|
|
```
|
|
|
|
#### Error Responses
|
|
|
|
**404 Not Found:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Job not found"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Document Data Notes
|
|
|
|
The generator is tolerant of different request shapes and common field aliases. For example, it accepts both `documentData` and nested `data`, and it normalizes document names by removing spaces, underscores, and hyphens.
|
|
|
|
If a document does not use every field in a sample payload, you can omit the unused keys.
|
|
|
|
---
|
|
|
|
## Complete Workflow Example
|
|
|
|
### 1. Generate Document
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3070/api/document/generate \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
-d '{
|
|
"document": "precost",
|
|
"documentType": "pdf",
|
|
"documentData": {
|
|
"customerName": "Acme Corp",
|
|
"items": [
|
|
{"name": "Item 1", "description": "Test", "qty": 2, "unitPrice": 100}
|
|
]
|
|
}
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Document generation started",
|
|
"jobId": "1"
|
|
}
|
|
```
|
|
|
|
### 2. Check Job Status
|
|
|
|
```bash
|
|
curl http://localhost:3070/api/document/job/1/status \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
```
|
|
|
|
**Polling Response (Still Processing):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"jobId": "1",
|
|
"state": "active"
|
|
}
|
|
```
|
|
|
|
**Polling Response (Completed):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"jobId": "1",
|
|
"state": "completed",
|
|
"result": {
|
|
"fileName": "precost-1715339340000.pdf"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Download File
|
|
|
|
```bash
|
|
curl http://localhost:3070/api/document/download/16adf29b-c0c2-45f9-8808-d6e15cd3d755 \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
-o my-document.pdf
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript/Fetch Example
|
|
|
|
```javascript
|
|
async function generateAndDownloadDocument(token) {
|
|
// Step 1: Generate document
|
|
const generateRes = await fetch("/api/document/generate", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${token}`,
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({
|
|
document: "precost",
|
|
documentType: "pdf",
|
|
documentData: {
|
|
customerName: "Acme Corp",
|
|
items: [
|
|
{ name: "Item 1", description: "Test", qty: 2, unitPrice: 100 }
|
|
]
|
|
}
|
|
})
|
|
});
|
|
|
|
const { jobId } = await generateRes.json();
|
|
console.log("Job queued:", jobId);
|
|
|
|
// Step 2: Poll for completion
|
|
let isComplete = false;
|
|
let result = null;
|
|
|
|
while (!isComplete) {
|
|
const statusRes = await fetch(`/api/document/job/${jobId}/status`, {
|
|
headers: { "Authorization": `Bearer ${token}` }
|
|
});
|
|
|
|
const status = await statusRes.json();
|
|
console.log("Job state:", status.state);
|
|
|
|
if (status.state === "completed") {
|
|
result = status.result;
|
|
isComplete = true;
|
|
} else if (status.state === "failed") {
|
|
throw new Error(`Job failed: ${status.error}`);
|
|
} else {
|
|
// Wait 2 seconds before polling again
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
}
|
|
}
|
|
|
|
// Step 3: Download the file
|
|
const downloadRes = await fetch(`/api/document/download/${result.fileName}`, {
|
|
headers: { "Authorization": `Bearer ${token}` }
|
|
});
|
|
|
|
const blob = await downloadRes.blob();
|
|
|
|
// Trigger browser download
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = result.fileName;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
console.log("Download complete!");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Important Notes
|
|
|
|
### Authentication & Authorization
|
|
- All endpoints require JWT token in `Authorization: Bearer <token>` header
|
|
- User must have one of these roles: `admin`, `management`, `team_head`, `user`
|
|
|
|
### Polling Strategy
|
|
- Poll status every 2-5 seconds
|
|
- Max recommended: 60 attempts (5 minutes timeout)
|
|
- Move to download immediately when state is `completed`
|
|
|
|
### Error Handling
|
|
- Jobs automatically retry up to 3 times with exponential backoff
|
|
- Failed jobs remain in queue for debugging
|
|
- Check `error` and `stacktrace` fields for failure details
|
|
|
|
### File Management
|
|
- Generated files stored in `/app/documents/`
|
|
- File names auto-generated: `{document}-{timestamp}.{ext}`
|
|
- Always sanitize fileName in download requests
|
|
|
|
### Supported Formats
|
|
- **PDF:** `documentType: "pdf"`
|
|
- **Excel:** `documentType: "excel"`
|
|
|
|
### Performance
|
|
- Worker concurrency: 2 concurrent jobs
|
|
- Timeout per job: 5 minutes
|
|
- Retry attempts: 3 with exponential backoff
|
|
|
|
---
|
|
|
|
## Common Response Codes
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| `202` | Accepted - Job queued successfully |
|
|
| `200` | OK - Successful operation |
|
|
| `400` | Bad Request - Invalid input |
|
|
| `401` | Unauthorized - Missing/invalid token |
|
|
| `403` | Forbidden - Insufficient permissions |
|
|
| `404` | Not Found - Job or file not found |
|
|
| `500` | Server Error - Internal error |
|
|
|
|
---
|
|
|
|
## Migration Notes
|
|
|
|
**If upgrading from synchronous API:**
|
|
|
|
**Old (Synchronous):**
|
|
- Request returned file immediately
|
|
- Long request timeout
|
|
- Blocking operation
|
|
|
|
**New (Asynchronous):**
|
|
- Request returns jobId immediately (202)
|
|
- Poll `/api/document/job/{jobId}/status` for progress
|
|
- Non-blocking, scalable architecture
|
|
- Automatic retries built-in
|
|
|