WFDASH-WEBAPP/docs/quotes-outbound-attachments-deploy.md

99 lines
4.5 KiB
Markdown
Raw Normal View History

feat(quotes): split attachments into internal vs outbound with operator upload UI Splits the quote attachment model along the seam that actually matters: 'operator-only references' (drawings the operator consulted, supplier emails they filed away) vs 'customer-facing files' (supplier quotes, drawings, NDAs that need to ride along on the email). Schema (PB): new `internal_attachments` + `outbound_attachments` fields on the `quotes` collection, maxSelect 10 each. Existing `attachments` field kept for back-compat but is no longer the source of truth for outbound emails. API: - GET/POST/DELETE /api/v1/quotes/[id]/internal-attachments[/{filename}] - GET/POST/DELETE /api/v1/quotes/[id]/outbound-attachments[/{filename}] - POST /api/v1/quotes/[id]/attachments/upload kept for legacy `attachments` MCP: `quotes.send_email` description now correctly states `selectedAttachmentIds` are PB file tokens from `outbound_attachments` (supplier quotes, drawings, NDAs), not IDs of the legacy `attachments` collection. Frontend: `ProcessOutboundAttachmentsCard` component lets the operator upload/remove customer-facing files during the process page; approval + process pages now read the new fields. Outbound Send workflow (deployed as `PIEAkPYYYZLGl6Pj`): - Old workflow (id `WF_Email_Send_SMTP`) and stale repo file (`WFDASH — Quote Outbound Send.json`) replaced. - New revision intersects `quotes.outbound_attachments` with `email_outbound_attachment_ids`, downloads each from `/api/files/quotes/<id>/<token>`, and emits `options.attachments: [{name, content, type}]` consumed by `Send Email (SMTP)` (PO Send pattern). - Failure path flipped from `send_failed` to `mail_failed` so `retryQuoteEmail` can find failed sends (it only accepts the latter). Previously failed sends were stranded. Email Monitor (v5) workflow fixes: - Reply-branch auth: any authenticated request now passes through; pre-fix bug rejected branches that the IMAP trigger hadn't seen yet. - Reply detection: bulletproof subject regex (/Q-?\d{4}-?\d{2}-?\d{3}/) accepts both legacy `Q-2026-09-246` and current `Q202609246` formats; canonicalised digits-only for PB `~` substring match. Delivery notes: new pricing endpoint at /api/v1/delivery-notes/items/[id]/pricing + service-layer methods. Snapshots kept for forensics: PRE-FIX-REPLY-{BRANCH-AUTH,DETECTION}.json and push-payload.baseline.json are the previously-deployed workflow shapes, retained so we can diff/rollback if regression hits. Verified: - PUT workflow PIEAkPYYYZLGl6Pj -> 200, active, 25 nodes - quotes-service.test.ts: 117 pass (4 pre-existing failures unchanged) - mcp/route.test.ts: 118 pass (2 pre-existing failures unchanged) - node --check on new Code node JS passes Docs: docs/quotes-outbound-attachments-deploy.md covers the deploy procedure; docs/api-mcp-layer.md description sync.
2026-09-09 11:43:56 +00:00
# Quote Outbound Send — operator-uploaded attachments fix
Deployed 2026-09-09 alongside the `quotes.outbound_attachments` /
`quotes.internal_attachments` storage split (the migration that
moved operator-uploaded files off the legacy `quote_attachments`
collection's `direction` enum).
## What the deployed workflow does now
The active n8n workflow is `WFDASH — Quote Outbound Send
(push-payload + outbound-attachments)` (id `PIEAkPYYYZLGl6Pj`,
previously named `…(push-payload)`).
The chain still ends in `Send Email (SMTP)` with the auto-generated
PDF attached — same as before — **plus** a new `Build attachments
list` Code node that runs between `HTTP Request` (PDF download) and
the SMTP node. It:
1. Reads `quotes.outbound_attachments` (the PB file field on the
parent quote row) and `email_outbound_attachment_ids` (the JSON
array of operator-selected tokens persisted by
`sendQuoteEmail`).
2. Intersects the two so only files the operator explicitly marked
for the customer get attached. Defends against drift between the
modal snapshot and the send.
3. Downloads each token from
`https://<PB>/api/files/quotes/<quoteId>/<token>` via
`this.helpers.httpRequest`.
4. Returns a single item with `attachments: [{name, content (base64),
type}, …]` on the JSON — same shape the deployed `WFDASH —
Purchase Order Send` workflow consumes.
5. Per-download failures are skipped (not raised) so a single bad
token doesn't abort the whole send. Operator sees the final count
via `attachmentCount`.
The SMTP node now consumes the array via
`options.attachments={{ $json.attachments && $json.attachments.length
? $json.attachments : [] }}` — matching the PO Send pattern. The
previous `options.fileAttachments=data` (PDF only) is removed.
## Bonus fix: failure status `send_failed` → `mail_failed`
The deployed `Update quote - Send Failed` node previously flipped a
failed send to status `send_failed`. The dashboard's `retryQuoteEmail`
only accepts `mail_failed`, so failed sends were stranded — operator
couldn't retry from the UI. The failure path now flips to
`mail_failed`. Stale `send_failed` rows (if any pre-existed) can be
re-categorised with a one-shot PB update if the operator wants the
tab badge to count them.
## Why the repo file was deleted
`n8n_workfows/WFDASH — Quote Outbound Send.json` (the pre-existing
repo file) was **stale** — it referenced a non-existent MCP tool
(`quotes.get_send_payload`), the old row-ID model
(`selected_attachment_ids` + `pdf_attachment_id`), `$env.PB_ADMIN_TOKEN`
(which the docker-compose blocks via
`N8N_BLOCK_ENV_ACCESS_IN_NODE=true`), and field names that had
been renamed (`email_message_id`, `email_delivered`, …). It did
**not** match the deployed workflow and would have misled anyone
debugging the system. Deleted.
A clean snapshot of the previously-deployed workflow is preserved at
`n8n_workfows/WFDASH — Quote Outbound Send (push-payload).baseline.json`
for diff/rollback reference. The post-fix deployed workflow is in
`n8n_workfows/WFDASH — Quote Outbound Send
(with-outbound-attachments).json`.
## MCP `quotes.send_email` description
Updated to spell out the new field semantics: "`selectedAttachmentIds`
is a list of PB file tokens from `quotes.outbound_attachments`
(operator-uploaded customer-facing files — supplier quotes, drawings,
NDAs). The PDF is always attached separately from
`quote_attachments` kind="pdf"`. Also added a per-property
description on the input schema and updated the `docs/api-mcp-layer.md`
table to match.
## Verification
| Layer | Result |
|---|---|
| Deployed workflow via n8n REST | PUT returned HTTP 200, `active=true`, node count 24 → 25, new connections verified |
| `quotes-service.test.ts` | 117 pass, 4 pre-existing failures (stale-PDF auto-regen — unrelated, baseline fails too) |
| `mcp/route.test.ts` | 118 pass, 2 pre-existing failures (unrelated, baseline fails too) |
| Code node JS syntax | `node --check` passes |
## Files touched
- **Deleted**: `n8n_workfows/WFDASH — Quote Outbound Send.json`
- **Added**:
- `n8n_workfows/WFDASH — Quote Outbound Send (push-payload).baseline.json`
- `n8n_workfows/WFDASH — Quote Outbound Send (with-outbound-attachments).json`
- `docs/quotes-outbound-attachments-deploy.md`
- **Modified**:
- `src/app/api/mcp/route.ts` (tool description + `selectedAttachmentIds` schema property)
- `src/app/api/v1/quotes/[id]/send-email/route.ts` (docstring only)
- `docs/api-mcp-layer.md` (table row)
- **Deployed**: workflow `PIEAkPYYYZLGl6Pj` in the live n8n instance