WFDASH-WEBAPP/pb_quote_attachments.schema.json
SJCoetz 33591959c2 feat: add quote email intake and atomic numbering
This commit implements the full inbound quote email processing workflow and atomic quote numbering:
- Add PocketBase collections for monthly atomic quote counters, email attachments, and threaded replies
- Add idempotent migration script to set up new collections and add deduplication field to existing quotes table
- Implement `/api/v1/quotes/next-number` endpoint for properly formatted Q-YYYY-MM-NNN quote numbers
- Add MCP tools for idempotent quote creation, reply threading, and message ID lookup
- Add end-to-end and concurrent smoke tests for pipeline validation
- Update TypeScript types for new quote-related entities
- Update project documentation and package.json scripts
- Mark quote email intake as complete in the parity backlog
- Add n8n v2 workflows for automated email monitoring and error alerts
2026-06-22 13:58:24 +02:00

177 lines
6.3 KiB
JSON

{
"_comment_top": "WFDASH — PocketBase schema snippet for the quote_attachments collection. This collection backs the v5 Catch and Create attachment upload pipeline. Import via Settings → Import collections in the PB admin UI. The collection id (pbc_quote_attachments_NEW) is a placeholder — PB will assign its own 15-char id on import; if you need to pin it, replace it after import and update all workflow HTTP URLs that reference /api/collections/quote_attachments/records/.",
"collections": [
{
"_comment": "quote_attachments — one row per uploaded file. quote is OPTIONAL so the Catch and Create pipeline can POST the file BEFORE the quote record exists; a downstream PATCH links them.",
"id": "pbc_quote_attachments_NEW",
"name": "quote_attachments",
"type": "base",
"listRule": "@request.auth.id != ''",
"viewRule": "@request.auth.id != ''",
"createRule": "@request.auth.id != ''",
"updateRule": "@request.auth.id != ''",
"deleteRule": "@request.auth.id != ''",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text_qa_id",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate_qa_created",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate_qa_updated",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"_comment": "The actual file. multipart upload via PocketBase's standard file field. Allowed mime types omitted — set them to whatever your RFQ vendors send (pdf, png, jpg, docx, csv, xlsx, etc).",
"hidden": false,
"id": "file_qa_file",
"maxSelect": 1,
"maxSize": 52428800,
"mimeTypes": ["application/pdf", "image/png", "image/jpeg", "image/webp", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "text/plain", "application/json", "application/octet-stream"],
"name": "file",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": null,
"type": "file"
},
{
"_comment": "Link to the quote this attachment belongs to. Optional — populated AFTER the quote record is created, via the PATCH Attachment (set quote) step in v5.",
"cascadeDelete": false,
"collectionId": "pbc_2527524235",
"hidden": false,
"id": "relation_qa_quote",
"maxSelect": 1,
"minSelect": 0,
"name": "quote",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"_comment": "Email Message-ID. Part of the unique composite index so re-firing the workflow on the same email (e.g. retry, IMAP re-poll) doesn't upload duplicates.",
"autogeneratePattern": "",
"hidden": false,
"id": "text_qa_message_id",
"max": 0,
"min": 0,
"name": "email_message_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text_qa_filename",
"max": 0,
"min": 0,
"name": "filename",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text_qa_content_type",
"max": 0,
"min": 0,
"name": "content_type",
"pattern": "",
"presentable": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number_qa_size",
"max": null,
"min": null,
"name": "size_bytes",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"_comment": "pdf / image / doc / other — convenience classification so the dashboard can render appropriate previews without sniffing the file.",
"hidden": false,
"id": "select_qa_kind",
"maxSelect": 0,
"name": "kind",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": ["pdf", "image", "doc", "other"]
},
{
"hidden": false,
"id": "date_qa_uploaded",
"max": "",
"min": "",
"name": "uploaded_at",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"_comment": "Provenance. Which n8n workflow uploaded this. Lets you tell apart v4 / v5 / manual uploads in the dashboard.",
"autogeneratePattern": "",
"hidden": false,
"id": "text_qa_uploaded_by",
"max": 0,
"min": 0,
"name": "uploaded_by",
"pattern": "",
"presentable": false,
"required": false,
"system": false,
"type": "text"
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_qa_msg_filename` ON `quote_attachments` (`email_message_id`, `filename`) WHERE `email_message_id` != '' AND `filename` != ''",
"CREATE INDEX `idx_qa_quote` ON `quote_attachments` (`quote`)"
],
"system": false
}
]
}