Functional Requirements
FR-001: Batch File Upload
Priority: HIGH
Related BR: BR-003
Description
Users shall upload batch invoice files through RESTful API with automatic vendor format detection, file validation, and storage in organization-specific blob containers.
API Specification
Endpoint: POST /v1/organizations/{organizationId}/batches
Request:
POST /v1/organizations/123e4567-e89b-12d3-a456-426614174000/batches
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG...
Content-Type: multipart/form-data
file: [XML binary]
metadata: {
"batchName": "Invoice_November_2025",
"priority": "normal"
}
Response (201 Created):
{
"success": true,
"data": {
"batchId": "550e8400-e29b-41d4-a716-446655440000",
"status": "uploaded",
"uploadedAt": "2025-11-21T10:30:00Z",
"fileInfo": {
"fileName": "invoices_nov.xml",
"fileSize": 15728640,
"detectedFormat": "GASEL"
},
"blobPath": "acme-batches-2025/11/21/550e8400.../source.xml"
}
}
Validation Rules
| Field | Rule | Error Code | Message |
|---|---|---|---|
| file | Required | VALIDATION_ERROR | File is required |
| file.size | 1KB ≤ size ≤ 100MB | FILE_TOO_LARGE | File must be 1KB-100MB |
| file.contentType | application/xml or text/xml | INVALID_CONTENT_TYPE | File must be XML |
| file.content | Well-formed XML | INVALID_XML | XML not well-formed. Line {line}, Column {column} |
| metadata.batchName | 1-255 chars, no / \ | VALIDATION_ERROR | Batch name: 1-255 chars, no path separators |
| metadata.priority | "normal" or "high" | VALIDATION_ERROR | Priority must be 'normal' or 'high' |
Processing Steps
- Validate authentication (OAuth 2.0 JWT token)
- Verify user has BatchOperator role
- Validate file size (1KB - 100MB)
- Validate content type (XML)
- Quick parse: Check XML well-formedness
- Detect vendor format (namespace analysis)
- Generate UUID batch ID
- Store XML in blob:
{org}-batches-{year}/{month}/{day}/{id}/source.xml - Calculate SHA-256 checksum
- Create batch metadata JSON
- Return 201 Created with batch details
Acceptance Criteria
| # | Criterion | Test | Expected Result |
|---|---|---|---|
| 1 | Accepts XML up to 100MB | Upload 100MB file | 201 Created |
| 2 | Validates well-formed XML | Upload malformed XML | 400 INVALID_XML |
| 3 | Stores in org-specific container | Verify blob path | {org}/2025/11/21/{id}/source.xml |
| 4 | Returns UUID batch ID | Check format | Valid UUID v4 |
| 5 | Detects GASEL format | Upload GASEL sample | detectedFormat: "GASEL" |
| 6 | Detects XELLENT format | Upload XELLENT sample | detectedFormat: "XELLENT" |
| 7 | Detects ZYNERGY format | Upload ZYNERGY sample | detectedFormat: "ZYNERGY" |
| 8 | Calculates SHA-256 checksum | Verify checksum | Matches file |
| 9 | Requires BatchOperator role | Upload without role | 403 ACCESS_DENIED |
| 10 | Rate limited (10/hour/org) | Upload 11 files | 11th returns 429 |
FR-002: Batch Processing Initiation
Priority: HIGH
Related BR: BR-003
Description
Users shall initiate batch processing through API, which enqueues the batch to batch-upload-queue for asynchronous processing by ParserService.
API Specification
Endpoint: POST /v1/organizations/{orgId}/batches/{batchId}/start
Request:
{
"validationMode": "strict"
}
Response (202 Accepted):
{
"success": true,
"data": {
"batchId": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"queuedAt": "2025-11-21T10:35:00Z",
"estimatedProcessingTime": "15-30 minutes",
"queuePosition": 2
}
}
Processing Flow
- Validate batch exists and status is "uploaded"
- Create message in
batch-upload-queue - Update batch status to "queued"
- Return 202 Accepted with queue position
- ParserService picks up asynchronously
Validation Rules
| Check | Rule | Error Code | HTTP | Action |
|---|---|---|---|---|
| Batch exists | Must exist in blob | RESOURCE_NOT_FOUND | 404 | Return error |
| Batch ownership | Must belong to org | ACCESS_DENIED | 403 | Return error |
| Batch status | Must be "uploaded" | PROCESSING_ERROR | 422 | Return current status |
| Organization active | isActive = true | ORGANIZATION_INACTIVE | 422 | Return error |
| Queue capacity | Depth < 10,000 | SERVICE_UNAVAILABLE | 503 | Return retry-after |
| User permission | BatchOperator+ role | ACCESS_DENIED | 403 | Return error |
Acceptance Criteria
| # | Criterion | Test | Expected Result |
|---|---|---|---|
| 1 | Only "uploaded" batches start | Start processing batch | 409 if already processing |
| 2 | Creates queue message | Verify message | Present in batch-upload-queue |
| 3 | Updates status to "queued" | Check metadata | status: "queued" |
| 4 | Returns estimated time | Empty vs full queue | Time varies |
| 5 | Idempotent (safe duplicates) | Call /start twice | Both return 202, process once |
| 6 | Returns queue position | Verify accuracy | Matches queue depth |
| 7 | Requires BatchOperator role | Call without role | 403 ACCESS_DENIED |
| 8 | Supports validation modes | Set lenient mode | Mode in queue message |
| 9 | Queue full returns 503 | Depth >10,000 | 503 with retry-after |
FR-003: Parser Service (XML → JSON)
Priority: CRITICAL
Related BR: BR-002, BR-003
Description
ParserService listens to batch-upload-queue, downloads XML, detects vendor format, validates against XSD, parses to canonical JSON, and enqueues 32-item batches to batch-items-queue.
Processing Steps
- Dequeue message from
batch-upload-queue - Download XML:
{org}-batches-{year}/{month}/{day}/{id}/source.xml - Detect vendor format (namespace + structure analysis)
- Load vendor-specific schema mapping
- Validate XML against vendor XSD schema
- Parse XML using XPath expressions
- Transform each invoice to canonical JSON
- Store JSON:
{org}-invoices-{year}/{month}/{day}/{invoice-id}.json - Group invoices into 32-item batches
- Enqueue to
batch-items-queue - Update batch metadata (totalItems, vendorCode, status="processing")
- Delete message from
batch-upload-queue
Canonical JSON Schema
{
"invoiceId": "uuid",
"invoiceNumber": "2025-11-001",
"invoiceDate": "2025-11-06",
"dueDate": "2025-11-20",
"currency": "SEK",
"periodStart": "2025-10-01",
"periodEnd": "2025-10-31",
"customer": {
"customerId": "020624-2380",
"fullName": "Medeni Schröder",
"email": "muntaser.af@zavann.net",
"phone": "09193538799",
"address": {
"street": "Strandbo 63B",
"city": "Växjö",
"postalCode": "352 58",
"country": "SE"
}
},
"invoiceDetails": {
"subTotal": 599.42,
"taxAmount": 149.86,
"totalAmount": 749.28,
"lineItems": [...]
},
"delivery": {
"meteringPointId": "735999756427205424",
"gridArea": "SE4",
"gridOwner": "Växjö Energi Elnät AB",
"consumption": 420
},
"sourceMetadata": {
"vendorCode": "GASEL",
"vendorVersion": "1.0",
"parsedAt": "2025-11-21T10:35:45Z"
}
}
Acceptance Criteria
| # | Criterion | Test | Expected Result |
|---|---|---|---|
| 1 | Listens to batch-upload-queue | Send message | Dequeued within 30s |
| 2 | Downloads batch XML | Verify download logged | File downloaded |
| 3 | Detects GASEL (100% accuracy) | 50 GASEL samples | All detected |
| 4 | Detects XELLENT (100% accuracy) | 50 XELLENT samples | All detected |
| 5 | Detects ZYNERGY (100% accuracy) | 50 ZYNERGY samples | All detected |
| 6 | Loads correct schema mapping | Verify mapping file | Correct vendor mapping |
| 7 | Validates XML against XSD | Invalid XML | Validation errors logged |
| 8 | Parses GASEL via XPath | Parse sample | All fields extracted |
| 9 | Transforms to canonical JSON | Verify schema | All fields present |
| 10 | Stores JSON in correct path | Check blob | {org}/invoices/2025/11/21/{id}.json |
| 11 | Groups into 32-item batches | 100 invoices | 4 messages (32+32+32+4) |
| 12 | Enqueues to batch-items-queue | Verify messages | Messages present |
| 13 | Updates batch metadata | Check metadata | totalItems, vendorCode set |
| 14 | Deletes from batch-upload-queue | Verify removal | Message gone |
| 15 | Retries 3× on errors | Force blob error | 3 retries logged |
| 16 | Moves to poison queue (3 fails) | Force permanent error | In poison queue |
| 17 | Performance: 10K in <2 min | Performance test | ≤ 120 seconds |