Add PO additional attachments spec
This commit is contained in:
parent
dfe52a62d9
commit
e19bb7dbf7
1 changed files with 160 additions and 0 deletions
|
|
@ -0,0 +1,160 @@
|
|||
# PO additional attachments
|
||||
|
||||
**Goal:** Let the operator attach extra files (supplier quote, drawing, NDA, etc.) to a Purchase Order. The PO email to the supplier includes them alongside the PO PDF. The existing `documents` field (versioned PDFs) and the new `additional_attachments` field stay cleanly separate.
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- Add an `additional_attachments` file field to the `purchase_orders` collection. Same shape as `documents` (file, multi, no PB cap, any mime type).
|
||||
- Migration: add the field via `scripts/migrations/2026-07-09-po-additional-attachments.ts`. Idempotent (PB's add-field migration is a no-op on re-run).
|
||||
- New server route: `POST /api/v1/purchase-orders/[id]/attachments/upload` — multipart, admin-auth, returns the updated `additional_attachments` array.
|
||||
- New server route: `DELETE /api/v1/purchase-orders/[id]/attachments/[filename]` — removes a single token from `additional_attachments`.
|
||||
- Order detail UI: in the **Send PO** modal, an "Additional attachments" panel above the message textarea with:
|
||||
- List of currently-attached files (filename, size, remove button)
|
||||
- Multi-file picker to upload more
|
||||
- Disabled while the modal is loading the line count
|
||||
- Modal: list reads from `po.additional_attachments`; refresh after upload/delete.
|
||||
- New MCP tool: `purchase_orders.add_attachment` (POST a single file via base64, like `attachments.upload_for_quote`).
|
||||
- Updated MCP tool: `purchase_orders.list` returns the attachment count per PO so the agent can see at a glance.
|
||||
- Updated `purchase-orders-service.ts` send-email path: pass the `additional_attachments` array to the n8n webhook so the supplier gets them.
|
||||
- n8n is out of scope for this repo. The webhook payload now includes `additional_attachments: [ { filename, token, content_type, size_bytes }, ... ]` — the workflow needs to read these and attach them. We surface this in the spec so the operator can wire it.
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Anything that touches `documents` / `documents_history` (the versioned-PDF flow).
|
||||
- Changing the existing `buildDefaultPoEmailBody` template. The body still says "(N lines)" — the attachments travel as files, not in the body text.
|
||||
- Auto-attaching the same supplier quote to every send. Each send picks from whatever is currently attached.
|
||||
- Versioning the additional attachments. They live with the PO; if the operator wants to track "which quote did we send with v3", that's a future feature.
|
||||
|
||||
## Current State
|
||||
|
||||
`purchase_orders.documents` is a multi-file field used for versioned PO PDFs. `documents_history` is a JSON array that tracks each version's `file` (human-readable name), `token` (PB-stored slug), `generatedAt`, `generatedBy`, `reason`, `status`. The two are kept in sync by the POST handler of `/api/v1/purchase-orders/[id]/pdf`.
|
||||
|
||||
The Send PO modal currently pre-populates the email body with the line count (we just fixed the count source). It has no concept of additional attachments.
|
||||
|
||||
## Data Model
|
||||
|
||||
### New field on `purchase_orders`
|
||||
|
||||
```
|
||||
additional_attachments
|
||||
type: file
|
||||
required: false
|
||||
maxSelect: 0 (unlimited)
|
||||
maxSize: 0 (no PB cap; the upload route enforces 25 MB per file to match the rest of the app)
|
||||
mimeTypes: null
|
||||
thumbs: null
|
||||
protected: false
|
||||
```
|
||||
|
||||
Rationale: identical shape to `documents` so the existing `files.getURL` and `/api/v1/files/purchase_orders/<id>/<token>` proxy whitelist work without changes. The proxy already accepts `purchase_orders`.
|
||||
|
||||
### Why a separate field and not the rename-into-`documents` shortcut
|
||||
|
||||
If we put additional files in `documents` and renamed them with a prefix, we'd break the `documents ↔ documents_history` invariant that the PDF download flow depends on. Operators regenerating POs risk dropping the attachment or having n8n pick the wrong file as the PDF. A separate field is a clean split.
|
||||
|
||||
## Behavior
|
||||
|
||||
### Operator workflow
|
||||
|
||||
1. On the order detail page, open the **Send PO** modal for a PO that needs supporting context (supplier quote, spec sheet, etc.).
|
||||
2. In the **Additional attachments** panel, click "Attach files" → multi-file picker.
|
||||
3. Files upload via `POST /api/v1/purchase-orders/{poId}/attachments/upload`. The panel refreshes to show the new list.
|
||||
4. Existing files can be removed via the trash icon → `DELETE /api/v1/purchase-orders/{poId}/attachments/[filename]`.
|
||||
5. When the operator hits **Send**, the n8n webhook receives the new payload (see below) and the supplier gets the PO PDF plus all currently-attached files.
|
||||
|
||||
### Upload route (POST)
|
||||
|
||||
- Multipart form: `file` (required, File).
|
||||
- Admin PB (bypasses `createRule`).
|
||||
- Inserts into `additional_attachments` while preserving the existing array. PB SDK's FormData update replaces the field, so we read first, append the new token, and write back.
|
||||
- Returns `{ ok: true, attachments: [{ filename, token, content_type, size_bytes }] }`.
|
||||
- 400 if no file, 500 on PB failure.
|
||||
|
||||
### Delete route (DELETE)
|
||||
|
||||
- Path: `DELETE /api/v1/purchase-orders/{poId}/attachments/[filename]`
|
||||
- The `[filename]` is the PB-stored token (matches the `documents_history[].token` pattern and the proxy URL path).
|
||||
- Admin PB. Read the current `additional_attachments`, filter out the matching token, write back.
|
||||
- Returns `{ ok: true, attachments: [...] }`.
|
||||
- 404 if the token isn't found in the array.
|
||||
|
||||
### Send-email payload change
|
||||
|
||||
The `/api/v1/purchase-orders/[id]/send-email` route currently sends:
|
||||
|
||||
```
|
||||
{
|
||||
po_number, supplier_email, cc, bcc, subject, message,
|
||||
pdf_token, pdf_filename, total_amount
|
||||
}
|
||||
```
|
||||
|
||||
We add:
|
||||
|
||||
```
|
||||
additional_attachments: [
|
||||
{ filename, token, content_type, size_bytes },
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
Empty array if nothing attached. n8n reads the field and attaches each token to the outbound SMTP message via the PB file proxy (or its own admin PB client — operator's choice).
|
||||
|
||||
## Testing
|
||||
|
||||
### Upload route (`route.test.ts`)
|
||||
|
||||
1. POST without a file → 400.
|
||||
2. POST with a single file → 201, response includes the new token, PB update is called with the appended array.
|
||||
3. POST preserves existing attachments (read-modify-write pattern).
|
||||
4. Reject files over 25 MB at the route level (matches the quote-attachments cap).
|
||||
5. POST is admin-auth-gated (no admin → 500 with the existing `getAdminPocketBase` failure shape).
|
||||
|
||||
### Delete route (`route.test.ts`)
|
||||
|
||||
1. DELETE on an existing token → 200, token removed from the array.
|
||||
2. DELETE on a missing token → 404.
|
||||
3. DELETE preserves the rest of the array.
|
||||
|
||||
### UI
|
||||
|
||||
Order detail page: existing modal still works; new attachments panel renders with empty state, "Attach files" button, and the upload list. The send-time test from the previous fix is unchanged.
|
||||
|
||||
### MCP
|
||||
|
||||
1. `tools/list` includes `purchase_orders.add_attachment`.
|
||||
2. `purchase_orders.add_attachment` accepts `{ po_id, filename, base64, content_type? }` and returns the same shape as the REST route.
|
||||
3. `purchase_orders.list` (new) returns an `attachment_count` field per PO.
|
||||
|
||||
## Migration
|
||||
|
||||
`scripts/migrations/2026-07-09-po-additional-attachments.ts`:
|
||||
|
||||
- Read `purchase_orders` schema from PB.
|
||||
- If `additional_attachments` field doesn't exist, add it via PB's field-add API.
|
||||
- Log `[po-additional-attachments] created field on purchase_orders` or `[po-additional-attachments] already present`.
|
||||
|
||||
Idempotent on re-run.
|
||||
|
||||
Add `npm run pb:migrate:po-additional-attachments` to `package.json` (same `npx tsx` convention as every other migration).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Operator can upload and remove additional attachments from the Send PO modal.
|
||||
- The Send PO webhook payload includes the additional attachments so n8n can attach them.
|
||||
- Existing versioned-PDF flow is untouched.
|
||||
- All targeted tests pass; full suite non-regressive.
|
||||
- Migration is idempotent.
|
||||
|
||||
## Risk / Rollback
|
||||
|
||||
- **Schema risk:** adding a file field with no constraints is safe — PB accepts empty arrays on existing rows. No data migration needed.
|
||||
- **Rollback:** remove the field via the PB admin UI. Drop the routes, the UI panel, and the MCP tool. The n8n workflow ignores the new payload field if absent.
|
||||
|
||||
## Operator-facing note (separate doc, post-implementation)
|
||||
|
||||
A short note for whoever maintains the n8n workflow:
|
||||
|
||||
> The Send PO webhook now includes `additional_attachments: [{ filename, token, content_type, size_bytes }, ...]`. Read each entry, fetch the file from the PB file proxy (`/api/files/purchase_orders/<poId>/<token>` with admin auth), and attach it to the outbound SMTP message. Empty array means no extras.
|
||||
Loading…
Reference in a new issue