9138 lines
400 KiB
JSON
9138 lines
400 KiB
JSON
{
|
||
"updatedAt": "2026-07-01T13:25:53.463Z",
|
||
"createdAt": "2026-05-08T17:09:50.845Z",
|
||
"id": "eoMLOlRefzddaMQs",
|
||
"name": "MoneyBot-read only",
|
||
"active": true,
|
||
"isArchived": false,
|
||
"nodes": [
|
||
{
|
||
"parameters": {
|
||
"content": "## Internal link using basic auth\n\nhttps://Masttr0!:Masttr0!@moneybot.masttro.com/webhook/672bf651-91e6-41de-b897-13e54c0e1f10/chat\n\nhttps://moneybot.masttro.com/webhook/moneybot-readonly/chat",
|
||
"height": 240,
|
||
"width": 464
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16000,
|
||
3984
|
||
],
|
||
"id": "d03c3c99-ddd5-4d62-a364-1db45756e92b",
|
||
"name": "Sticky Note"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🤖 Core — Trigger · Agent · LLMs · Memory\n\nEntry point and the AI brain. Connect your preferred LLM (Sonnet, Haiku, or Ollama) to the agent. Simple Memory provides per-session context.",
|
||
"height": 800,
|
||
"width": 1868,
|
||
"color": 3
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15584,
|
||
4288
|
||
],
|
||
"id": "830b99a8-bf8c-4944-aaa8-ee857f93a6be",
|
||
"name": "Sticky Core"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"options": {
|
||
"systemMessage": "=Current datetime is {{ $now }}\n\nYou are MoneyBot, a finance automation assistant for Masttro.\n\n## ENTITIES\nMUS (US, realm 9341452327761715, USD) · MMX (Mexico, realm 9341452328017372, USD) · MSAG (Switzerland, realm 9341452373926753, CHF) · MHAG (Switzerland holding, realm 9341452561201469, CHF — JE only) · MGC (Global, realm 9130354300064436, USD — JE only)\n\n## TOOL SELECTION\n| Intent | Tool |\n|---|---|\n| Open/unpaid invoices | Postgres: view_invoices_open |\n| All invoices by date | ENTITY - Query QBO (QBQL) |\n| Payments received | Postgres: view_payments |\n| GL / revenue / expenses / P&L | Postgres: AI_view_general_ledger |\n| Account ID lookup | Postgres: chartofaccounts — always before JE |\n| HubSpot closed-won deals | Postgres: view_hubspot_closedwon |\n| HubSpot owner lookup | Postgres: hubspot_owners |\n| HubSpot all deals/companies | Fallback if not in view_hubspot_closedwon |\n| Billing schedule | Postgres: view_hubspot_closedwon |\n| Next invoice number | ENTITY - Get latest invoice DocNumber |\n| Find customer | ENTITY - Find customer by name |\n| Find item/product | ENTITY - Find item by name |\n| Ad-hoc QBO query | ENTITY - Query QBO (QBQL) |\n| Customer record by ID | ENTITY - Get a customer |\n| Create / delete / reclass JE | ENTITY - Create/delete Journal Entry |\n| Export to CSV/Excel | Export_File |\n| Contract terms / renewal dates | pgvector store first, fallback Contract_Direct_Lookup |\n| DSO / days to pay | view_payments JOIN view_invoices_open |\n| Account balances / BS / assets / liabilities / equity | Postgres: view_balancesheet |\n| Find tickets | Freshdesk - finance tickets |\n| Update ticket status / priority | Freshdesk - Update Ticket |\n| Add note / internal comment | Freshdesk - Add Note to Ticket |\n| Create new ticket | Freshdesk - Create Ticket |\n| Download invoice as PDF | Download Invoice PDF |\n| Apply credit memo to invoice | ENTITY - Apply Credit Memo to Invoice |\n\nNever dump all invoices, customers, or payments from QBO. Always use Postgres views for list queries.\n\n## POSTGRES RULES\n- AI_view_general_ledger: \"Date\" is a native DATE — always use DATE_TRUNC or CURRENT_DATE, never text strings\n- ALL mixed-case columns must be double-quoted: \"Date\", \"Account\", \"AccountType\", \"AccountSubType\", \"Classification\", \"Transaction_Type\", \"Memo_Description\", \"CustomerName\", \"Customer_Id\", \"Vendor\", \"Vendor_Id\", \"Account_Id\", \"Class\", \"Department\"\n- Lowercase (no quotes): company_id, realm_id, amount, debit, credit, exch_rate, currency\n- Never SELECT * without a WHERE clause — always use ILIKE for case-insensitive searches\n\n## QBQL RULES\nQBQL is NOT SQL. Forbidden: IN(...), OR, LIKE, ILIKE, nested parentheses, subqueries, JOINs, GROUP BY, aggregates.\nUse ORDERBY (one word), MAXRESULTS (not LIMIT, cap 100), single quotes only.\nDate filters: TxnDate >= 'YYYY-MM-DD' is valid. Derive dates from $now — never hardcode year literals.\nMultiple values: run separate calls, never chain OR.\n\nFor all-invoices-by-date, run one call per AR entity (MUS, MMX, MSAG):\n SELECT Id, DocNumber, TxnDate, CustomerRef, TotalAmt, Balance FROM Invoice\n WHERE TxnDate >= '{{ $now.toFormat(\"yyyy-MM-01\") }}' MAXRESULTS 100\n\n## QBO REFERENCE ID RULES — APPLIES TO ALL QBQL QUERIES AND WRITE OPERATIONS\nNever use a customer or item NAME as a filter value for ID-typed fields in QBQL.\nCustomerRef, ItemRef, VendorRef are reference fields — they require numeric IDs, not names.\n\nWRONG: SELECT * FROM Invoice WHERE CustomerRef = 'Ritholtz Wealth Management LLC'\nRIGHT: \n 1. SELECT Id FROM Customer WHERE DisplayName = 'Ritholtz Wealth Management LLC'\n 2. SELECT * FROM Invoice WHERE CustomerRef = '<numeric Id from step 1>'\n\nThis applies to QBQL reads as much as write payloads — resolve the Id first, always.\n\n## INVOICE CREATION WORKFLOW\n1. Find customer → get customerId + BillAddr.CountrySubDivisionCode\n2. Find item → get itemId\n3. Get latest DocNumber → +1 → collision-check CreditMemo + Estimate → use first clear number\n4. Tax code:\n - MUS: state TX or NY → 'TAX'; all others → 'NON'; unknown → 'NON'. Never omit.\n - MMX: always 'NON'\n - MSAG: query TaxCode table for Swiss VAT id\n5. Confirm details with user before creating\n\n## INVOICE SEND / UPDATE RULES\nSend: always use a literal email address — look up PrimaryEmailAddr if user says \"use the one on file\".\nUpdate (sparse): fetch SyncToken → confirm changes → copy lines verbatim from fetched array, substitute only what changed → call Update.\n\n## JOURNAL ENTRY RULES\n- Debits must equal credits — verify before posting\n- Always look up account IDs via chartofaccounts first; always populate Department\n- To modify a JE: delete existing → create corrected. Confirm debit/credit table before posting.\n- operation=delete is a URL parameter — never include in JSON body\n- MHAG/MGC: JE and Query QBO only — no invoice or customer tools\n- Entity currencies: MUS→USD · MMX→USD · MSAG→CHF · MHAG→CHF · MGC→USD\n- DocNumber format: JE-YYYYMM-NNN (max 13 chars). Reclass suffix: append \"-R\" (must stay ≤21 chars total)\n\n## RECLASS JOURNAL ENTRY\n1. Fetch Id + SyncToken via QBQL\n2. Look up accounts via chartofaccounts\n3. Confirm debit/credit table with user\n4. Delete original\n5. Post corrected (DocNumber = original + \"-R\")\n6. Confirm new JE Id\n\n## CURRENCY\nHome currencies: MUS/MMX/MGC=USD · MSAG/MHAG=CHF\nAfter any GL query with MSAG/MHAG rows: call Get FX Rate (fromCurrency=CHF), multiply by rates.USD, report USD only with note \"converted from CHF at X CHF/USD\".\nFor cross-currency deal totals: use amount_in_home_currency.\n\n## CONTRACT RETRIEVAL\n1. pgvector first — use specific clause language, one query per term\n2. If pgvector returns wrong client → Contract_Direct_Lookup\n3. If not found after both: \"This detail was not found in the retrieved contract sections.\"\n\n## BILLING SCHEDULE\nQuery view_hubspot_closedwon. Verify via pgvector if data looks wrong.\nBilling rules: Monthly→one row/month · Quarterly→every 3 months · Semi-annual→every 6 months · Annual→anniversary · NULL term→check pgvector, do not default to Annual.\n\n## LARGE DATASET HANDLING\nPhase 1 — collect silently: all filtering in SQL, LIMIT 50 OFFSET N until 0 rows.\nPhase 2 — present once: markdown tables, max 25 rows. Rows >25: show first 25 + offer export. Rows >100: skip table, call Export_File immediately.\n\n## FILE EXPORT\nFilename: report_type_YYYY-MM-DD (snake_case, derive from $now). Pass filename (no extension) + sql.\nReturn the message field verbatim. Never prefix contracts with schema.\n\n## FRESHDESK\nSearch by keyword: pass raw keyword only — e.g. acacia\nSearch by person: Contact Lookup → get email → requester:'email@domain.com' AND status:2 OR requester:'email@domain.com' AND status:3\nSingle quotes around all string values. Never use parentheses in query strings.\nStatus codes: 2=Open · 3=Pending · 4=Resolved · 5=Closed\nNotes default to private — ask if customer-visible. Always retrieve ticket Id before writing. Post in HTML format.\nConfirm before closing or resolving.\n\n## MICROSOFT TEAMS\nSend only when explicitly asked. Never auto-send after a query.\nIdentity: userId 94387110-efa9-4e7f-8f18-3332df67c0a9 · email mnolan@masttro.com\n1:1 DM: Get Teams DM Chats → find non-self member → Send Teams DM (once)\nGroup: Get Teams Group Chats → match topic/member → Send Teams DM (once)\nChannel: Get Teams List → teamId → Get Teams Channels → channelId → Send Teams Channel Message (once)\nNever guess a chatId. Any send response = done, never retry. Plain text only, max 10 rows.\n\n## OUTPUT FORMAT\n- Natural language and markdown only — no XML, tool syntax, or parameter tags\n- Never surface tool names or selection reasoning\n- Never hardcode year literals in SQL — always derive from NOW()\n- After a successful write action: clean confirmation summary only, no intermediate narration",
|
||
"maxIterations": 100,
|
||
"passthroughBinaryImages": true,
|
||
"batching": {
|
||
"batchSize": 1,
|
||
"delayBetweenBatches": 2000
|
||
}
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||
"typeVersion": 3.1,
|
||
"position": [
|
||
16400,
|
||
4448
|
||
],
|
||
"id": "614a4991-0062-4cbe-bf33-25e8ab667b10",
|
||
"name": "AI Agent"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇲🇽 MMX — QuickBooks Mexico (MXN)\nRealm: 9341452328017372\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Default currency is MXN. Pass currency override if needed.",
|
||
"height": 580,
|
||
"width": 1636,
|
||
"color": 4
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
6080
|
||
],
|
||
"id": "e6717b22-9345-41a2-81e4-05eaae719e9e",
|
||
"name": "Sticky MMX"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const c = $fromAI('currency', 'ISO currency code. MMX default is USD. Pass empty string to accept entity default.', 'string');\n const taxCodeMap = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' };\n\n const resolveTaxCode = (id) => taxCodeMap[String(id)] || 'NON';\n\n const p = {\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: JSON.parse($fromAI('lines', 'JSON array string of line items.', 'string')).map(l => ({\n Amount: l.amount ?? (l.qty * l.unitPrice),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty,\n UnitPrice: l.unitPrice,\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6512
|
||
],
|
||
"id": "482352cf-27e8-47a7-8194-02294478dfa3",
|
||
"name": "MMX - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MMX invoice by email (realm 9341452328017372). ALWAYS have a literal email address before calling. Pass invoiceId (QBO numeric Id) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber)', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6512
|
||
],
|
||
"id": "52a8cffc-6914-44b2-8d49-44d163df253a",
|
||
"name": "MMX - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MMX invoice (realm 9341452328017372). Requires invoiceId and syncToken from a prior QBQL query. Sparse update — only fields passed are changed.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new TxnDate YYYY-MM-DD, empty to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new DueDate YYYY-MM-DD, empty to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo, empty to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items, empty string to skip', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6512
|
||
],
|
||
"id": "8d926a05-86f0-40b6-96c1-e14fd1484394",
|
||
"name": "MMX - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MMX for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6512
|
||
],
|
||
"id": "8b351de9-5f60-43d7-b95c-3d9ff4a6c51c",
|
||
"name": "MMX - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MMX. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16864,
|
||
6400
|
||
],
|
||
"id": "976533e1-d0d6-477f-b8b8-81567759e930",
|
||
"name": "MMX - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇨🇭 MSAG — QuickBooks Switzerland (CHF)\nRealm: 9341452373926753\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Swiss VAT: 8.1% standard · 2.6% reduced · 3.8% lodging · 0% export\n> Always convert CHF→USD via Get FX Rate before reporting.",
|
||
"height": 580,
|
||
"width": 1620,
|
||
"color": 6
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
6720
|
||
],
|
||
"id": "07586a4c-d4ef-4ec6-af83-241ae97685c2",
|
||
"name": "Sticky MSAG"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MSAG customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6832
|
||
],
|
||
"id": "3a01b6d9-217f-451c-bb4d-0748c3a4fba7",
|
||
"name": "MSAG - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MSAG item(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6832
|
||
],
|
||
"id": "ab6b639a-3991-460b-a5da-e3f5c3a1968e",
|
||
"name": "MSAG - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MSAG. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6832
|
||
],
|
||
"id": "c9dea8ca-b9bf-4dd7-a7e5-09e2ac05c5cd",
|
||
"name": "MSAG - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MSAG QuickBooks (realm 9341452373926753). ALWAYS include WHERE and MAXRESULTS. Use SELECT Id, Name, Description, Active FROM TaxCode WHERE Active = true MAXRESULTS 50 to discover Swiss VAT codes.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6832
|
||
],
|
||
"id": "7fdec87b-3bbd-4a33-8dc9-b4de53054520",
|
||
"name": "MSAG - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
6992
|
||
],
|
||
"id": "05ff2df0-96b4-482c-9120-0c81af2e1cde",
|
||
"name": "MSAG - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
6992
|
||
],
|
||
"id": "dcd00d78-c71e-479a-b1d3-61230b07cf01",
|
||
"name": "MSAG - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Invoice date in YYYY-MM-DD format', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: (() => {\n const raw = $fromAI('lines', 'JSON array of line items. Each object must have: amount (number), itemId (string), description (string), qty (number), unitPrice (number), taxCodeId (string — use the QBO TaxCode Id from the TaxCode query, never null)', 'string');\n const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;\n return parsed.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: (l.taxCodeId && String(l.taxCodeId).trim()) ? String(l.taxCodeId).trim() : 'NON' }\n }\n }));\n })()\n}) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
7152
|
||
],
|
||
"id": "eed664c9-00ff-466c-ba83-4e9dfa929552",
|
||
"name": "MSAG - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MSAG invoice by email (realm 9341452373926753). ALWAYS have a literal email address before calling — look it up from the customer record if needed. Pass invoiceId (QBO numeric Id, not DocNumber) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber) from a prior query', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
7152
|
||
],
|
||
"id": "47d35382-faf3-4b3d-a29c-a057e98f59cd",
|
||
"name": "MSAG - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MSAG invoice (realm 9341452373926753). Requires invoiceId and syncToken from a prior QBQL query. Sparse update — only fields passed are changed.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new TxnDate YYYY-MM-DD, empty to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new DueDate YYYY-MM-DD, empty to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo, empty to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items, empty string to skip', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
7152
|
||
],
|
||
"id": "1fddb639-ac49-48c0-af37-988dd09a1ef7",
|
||
"name": "MSAG - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MSAG for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
7152
|
||
],
|
||
"id": "e07b778b-2fab-48e0-9660-f022df3887d0",
|
||
"name": "MSAG - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MSAG. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16624,
|
||
7152
|
||
],
|
||
"id": "595d91fb-ef4a-4d06-891d-3e67306659d3",
|
||
"name": "MSAG - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🗄 Postgres — Data Warehouse (preferred read path)\nCredential: Postgres n8n-quickbooks\n\nviews: open invoices · payments · general ledger · hubspot_deals\n\n> Always use these views for list queries — never dump from QBO directly.\n> Quote mixed-case column names.\n> Revenue in GL is negative — use ABS(SUM(amount)).",
|
||
"height": 260,
|
||
"width": 1588
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
7360
|
||
],
|
||
"id": "f6a2fe13-fafb-443a-9512-ce4907bdd22d",
|
||
"name": "Sticky Postgres"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the view_invoices_open Postgres view. Returns UNPAID AR invoices only (balance > 0) — paid invoices excluded. For all invoices by date use ENTITY - Query QBO (QBQL) instead.\n\nAll columns lowercase, no quoting needed.\nColumns: invoice_key, company_id, docnumber, txndate, customerref_name, duedate, totalamt, balance, currencyref, exch_rate\n\nCRITICAL column types:\n txndate — native DATE. Never compare to text string.\n RIGHT: txndate = CURRENT_DATE\n RIGHT: txndate >= CURRENT_DATE - INTERVAL '7 days'\n WRONG: txndate = '2026-05-22' ← TYPE ERROR\n duedate — native DATE. Same rules.\n RIGHT: duedate < CURRENT_DATE\n WRONG: duedate < '2026-05-22' ← TYPE ERROR\n totalamt, balance — numeric\n All others — text\n\nExamples:\n -- Overdue 30+ days\n SELECT company_id, customerref_name, docnumber, duedate, balance, currencyref\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n AND duedate <= CURRENT_DATE - INTERVAL '30 days'\n ORDER BY duedate ASC LIMIT 100\n\n -- Due in next 30 days\n SELECT company_id, customerref_name, docnumber, duedate, balance\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n AND duedate >= CURRENT_DATE AND duedate <= CURRENT_DATE + INTERVAL '30 days'\n ORDER BY duedate ASC LIMIT 100\n\n -- AR aging summary\n SELECT company_id, customerref_name, SUM(balance) AS total_balance\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n GROUP BY company_id, customerref_name\n ORDER BY total_balance DESC LIMIT 20",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_invoices_open. MUST include WHERE and LIMIT (<=500). txndate and duedate are native DATE — use CURRENT_DATE arithmetic, never quoted strings.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16224,
|
||
7456
|
||
],
|
||
"id": "f0df476e-8098-4f20-8a0b-5ed08eb2dc14",
|
||
"name": "Postgres Quickbooks open invoices",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the view_payments Postgres view for QuickBooks payments across all entities.\n\nCRITICAL: ALL columns except company_id are mixed-case — MUST be double-quoted.\n\nColumn types:\n \"TxnDate\" — native DATE. Never compare to text string.\n RIGHT: \"TxnDate\" >= CURRENT_DATE - INTERVAL '7 days'\n WRONG: \"TxnDate\" >= '2026-05-13' ← TYPE ERROR\n \"TotalAmt\" — numeric\n \"IsCustomerPayment\" — boolean\n All others — text\n\nColumns: company_id, \"TxnDate\", \"CustomerRef_Name\", \"TotalAmt\", \"CurrencyRef\", \"IsCustomerPayment\", \"PaymentMethodDerived\", \"PaymentRefNum\"\n\nExample:\n SELECT company_id, \"TxnDate\", \"CustomerRef_Name\", \"TotalAmt\", \"CurrencyRef\"\n FROM view_payments\n WHERE \"IsCustomerPayment\" = true\n AND \"TxnDate\" >= CURRENT_DATE - INTERVAL '7 days'\n AND company_id IN ('MUS','MMX','MSAG')\n ORDER BY \"TxnDate\" DESC LIMIT 100\n\nDSO: both \"TxnDate\" and txndate (from view_invoices_open) are native DATE — subtraction works directly, no cast needed.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_payments. MUST include WHERE and LIMIT (<=500). All mixed-case columns must be double-quoted. \"TxnDate\" is native DATE — use CURRENT_DATE arithmetic, never quoted strings.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16384,
|
||
7456
|
||
],
|
||
"id": "a00978e0-c87f-4a9b-b1ab-b206523a0a15",
|
||
"name": "Postgres Quickbooks customer payments",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the AI_view_general_ledger Postgres view for GL, P&L, revenue, expenses.\n\"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE arithmetic only, never quoted strings.\nRevenue posts negative — use ABS(SUM(amount)).\nFor expenses: SUM(amount) for net (handles reversals). Never SUM(\"Debit\") alone.\nMust include WHERE and LIMIT (<=500). Double-quote all mixed-case columns.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against \"AI_view_general_ledger\". Must include WHERE and LIMIT (<=500). \"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE arithmetic, NEVER quoted string literals like \\'2026-01-01\\'. Double-quote all mixed-case column names.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16656,
|
||
7456
|
||
],
|
||
"id": "cc2e4931-5daf-4c5a-b959-8752835d444f",
|
||
"name": "Postgres Quickbooks general ledger",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query HubSpot deals and closed-won data from Postgres. Use view_hubspot_closedwon for closed-won deals and billing schedules. Use hubspot_deals for pipeline/win-loss analysis.\n\n### view_hubspot_closedwon\nAll lowercase. Already filtered to is_closed_won = true — do NOT add again.\ncontract_start_date / contract_end_date are native DATE — no cast needed.\nALWAYS include deal_url when presenting results.\n\nColumns: deal_id, deal_name, amount, amount_in_home_currency, deal_currency_code,\n close_date, contract_start_date, contract_end_date, contract_term,\n primary_associated_company, company_name, icon_alias, icon_id, icon_client_name,\n client_tier, health_status, renewal_date, churn_notice_date, churn_effective_date,\n onboarding_owner, client_success_manager, masttro_invoicing_region,\n new_pricing_model, deal_url, owner_id\n\n### hubspot_deals\nAll lowercase. Key columns: deal_id, deal_name, amount, amount_in_home_currency,\n close_date, deal_stage, pipeline, owner_id, is_closed_won, is_closed_lost,\n deal_type, contract_start_date, contract_end_date, contract_term,\n churn_date, churn_notice_date, primary_associated_company\nNote: no deal_url or deal_currency_code here — use view_hubspot_closedwon for those.\nColumn is deal_name — never dealname.\namount_in_home_currency is USD — use for cross-currency aggregations.\n\n### hubspot_owners\nColumns: owner_id, firstname, lastname, email\nJOIN: LEFT JOIN hubspot_owners o ON d.owner_id = o.owner_id\n\nBilling schedule standard query:\n SELECT deal_name, primary_associated_company, contract_term,\n contract_start_date, contract_end_date,\n amount, amount_in_home_currency, deal_currency_code, deal_id, deal_url\n FROM view_hubspot_closedwon\n WHERE contract_start_date <= (DATE_TRUNC('year', NOW()) + INTERVAL '1 year - 1 day')::date\n AND (contract_end_date >= DATE_TRUNC('year', NOW())::date OR contract_end_date IS NULL)\n ORDER BY contract_start_date ASC",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_hubspot_closedwon, hubspot_deals, or hubspot_owners. MUST include WHERE and LIMIT (<=500).', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17152,
|
||
4912
|
||
],
|
||
"id": "35c66d65-cc08-4358-946e-5762e372f41c",
|
||
"name": "Hubspot deals",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🏦 MHAG & MGC — Journal Entry Only\nMHAG realm: 9341452561201469 (CHF)\nMGC realm: 9130354300064436 (USD)\n\nThese entities have **no invoice or customer tools**.\nAvailable: Create Journal Entry · delete Journal Entry · Query QBO (QBQL)\n\n> Use Query QBO to find Account IDs before posting.\n> MHAG amounts are CHF — convert via Get FX Rate before reporting.",
|
||
"height": 260,
|
||
"width": 1684,
|
||
"color": 7
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
7712
|
||
],
|
||
"id": "d59faace-9090-4754-bdb6-8959ae321347",
|
||
"name": "Sticky MHAG MGC"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MHAG for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16736,
|
||
7792
|
||
],
|
||
"id": "3b159143-6489-4afa-923d-25784b80d894",
|
||
"name": "MHAG - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MHAG QuickBooks (realm 9341452561201469). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16176,
|
||
7792
|
||
],
|
||
"id": "82a645c5-0635-4810-9912-0831f6dfc6f6",
|
||
"name": "MHAG - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MHAG. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16928,
|
||
7792
|
||
],
|
||
"id": "8a310b53-142a-4d42-867c-d692fd8edd4d",
|
||
"name": "MHAG - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MGC for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16368,
|
||
7792
|
||
],
|
||
"id": "c8e3f85b-6de4-4cee-bf17-e6273d4a83e1",
|
||
"name": "MGC - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MGC QuickBooks (realm 9130354300064436). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16560,
|
||
7792
|
||
],
|
||
"id": "930fb4f2-58f1-4494-bcd7-175802124a82",
|
||
"name": "MGC - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MGC. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17120,
|
||
7792
|
||
],
|
||
"id": "4036d440-4e01-4ec7-bf11-d4ff0b157343",
|
||
"name": "MGC - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"mode": "retrieve-as-tool",
|
||
"toolDescription": "Contract terms, payment schedules, billing frequency, SLA, termination clauses, effective dates — Postgres PGVector contract store.\n\nCRITICAL: Always use specific clause language in queries, NOT generic client+contract phrases.\nGOOD queries: \"semi-annual invoice payment schedule\", \"annual license fee payment timing\", \"termination notice period\"\nBAD queries: \"49 management contract terms\", \"client contract\"\n\nUse this tool first for any contract lookup. If results return chunks from the wrong client, immediately fall back to Contract_Direct_Lookup.",
|
||
"tableName": "contracts",
|
||
"topK": 10,
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.vectorStorePGVector",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
17184,
|
||
4704
|
||
],
|
||
"id": "2f902fe9-23ea-4ebb-b953-c22f98cb9ee1",
|
||
"name": "Postgres PGVector Store",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "YrHntSw7KF7Za0tK",
|
||
"name": "Postgres vectors"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.embeddingsMistralCloud",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17296,
|
||
4912
|
||
],
|
||
"id": "545fd8be-c97c-41f2-9d72-963bbbc15c74",
|
||
"name": "mistral-embed",
|
||
"credentials": {
|
||
"mistralCloudApi": {
|
||
"id": "lHc1GsczHEq7NL55",
|
||
"name": "Mistral - contracts vector"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Direct SQL fallback for contract lookups. Use when pgvector returns chunks from the wrong client.\n\nTable: contracts (never vectors.contracts or public.contracts)\nColumns: id, text, metadata, client_name, contract_type, effective_date, entity, currency, section_heading, page_number, filename\nUse page_number = 1 to get one row per contract.\n\nExample:\n SELECT text, section_heading, page_number FROM contracts\n WHERE client_name ILIKE '%49 Management%'\n ORDER BY page_number ASC, id ASC LIMIT 50\n\nFor contract list:\n SELECT client_name, contract_type, entity, currency, effective_date\n FROM contracts WHERE page_number = 1\n ORDER BY client_name ASC LIMIT 200",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against the contracts table. Always filter by client_name using ILIKE. Always include ORDER BY page_number ASC, id ASC and a LIMIT.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16992,
|
||
4912
|
||
],
|
||
"id": "710e5a0d-2688-4335-80a8-74bf56246f7a",
|
||
"name": "Contract_Direct_Lookup",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Look up QuickBooks account IDs from the chartofaccounts Postgres table. ALWAYS use this before creating any journal entry — never use QBQL Account queries for ID lookups.\n\nKey columns: company_id, account_id (text — pass as-is in AccountRef), \"Name\", \"AcctNum\", \"Active\", \"Classification\", \"AccountType\"\n\nRules:\n- account_id is text — never cast it\n- Always filter by company_id\n- For multi-entity JEs: run one query per entity or use company_id IN (...)\n\nLookup by AcctNum:\n SELECT company_id, account_id, \"Name\", \"AcctNum\", \"Classification\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND \"AcctNum\" ILIKE '%41440%'\n LIMIT 10\n\nLookup by name:\n SELECT company_id, account_id, \"Name\", \"AcctNum\", \"Classification\", \"AccountType\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND \"Name\" ILIKE '%Bonus Expense%'\n LIMIT 10\n\nLookup multiple AcctNums at once:\n SELECT company_id, account_id, \"Name\", \"AcctNum\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND (\"AcctNum\" ILIKE '%41440%' OR \"AcctNum\" ILIKE '%11210%')\n LIMIT 20\n\nLookup across entities:\n SELECT company_id, account_id, \"Name\", \"AcctNum\"\n FROM chartofaccounts\n WHERE company_id IN ('MUS','MMX','MSAG') AND \"Active\" = true AND \"AcctNum\" ILIKE '%41440%'\n LIMIT 20",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against chartofaccounts. Always filter by company_id. Always include LIMIT. Double-quote mixed-case columns: \"Name\", \"AcctNum\", \"Active\", \"Classification\", \"AccountType\". account_id is lowercase and unquoted.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16912,
|
||
7456
|
||
],
|
||
"id": "2def1f16-acee-43f9-a6b3-29e86331cac7",
|
||
"name": "Quickbooks Chart of Accounts MGC MUS MMX MSAG MHAG",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/chats?$filter=chatType eq 'oneOnOne'&$expand=members&$select=id,chatType&$top=50",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17584,
|
||
4768
|
||
],
|
||
"id": "df489b7b-148f-400d-a1c0-1763df7e6b7a",
|
||
"name": "Get Teams DM Chats — returns all 1:1 chats with id and members. Search members for displayName or email matching the target person. Use chatId with Send Teams DM.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/chats?$filter=chatType eq 'group'&$expand=members&$select=id,chatType,topic&$top=50",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17856,
|
||
4928
|
||
],
|
||
"id": "537dd0af-08ea-4de3-b457-0c1378ccab70",
|
||
"name": "Get Teams Group Chats — returns all group chats with id, topic, and members. Match by topic name or member displayName. Use chatId with Send Teams DM.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/joinedTeams?$select=id,displayName",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17584,
|
||
4928
|
||
],
|
||
"id": "217303ac-0fc1-44f8-b035-ed629cfc734a",
|
||
"name": "Get Teams List — returns all joined teams with id and displayName. Call this first to resolve a team name to a teamId before calling Get Teams Channels.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "=https://graph.microsoft.com/v1.0/teams/{{ $fromAI('teamId', 'The team GUID from Get Teams List matching the target team displayName', 'string') }}/channels?$select=id,displayName",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17824,
|
||
4768
|
||
],
|
||
"id": "49d75f49-785a-49f1-a28a-2b623d2d1faf",
|
||
"name": "Get Teams Channels — given a teamId from Get Teams List, returns all channels with id and displayName. Use channelId and teamId together with Send Teams Channel Message.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "chatMessage",
|
||
"chatId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('chatId', 'The Teams chat GUID resolved from Get Teams DM Chats or Get Teams Group Chats. For 1:1 chats, find the chat where the member whose userId is NOT 94387110-efa9-4e7f-8f18-3332df67c0a9 matches the target by email or displayName. Never guess — always resolve first.', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"message": "={{ $fromAI('message', 'Plain text message to send. No markdown tables. Use line breaks for structure. Summarize large datasets and offer export links rather than pasting rows.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.microsoftTeamsTool",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
18016,
|
||
4752
|
||
],
|
||
"id": "7680071f-4d20-45b1-903c-cf4d3d56bf9d",
|
||
"name": "Send Teams DM",
|
||
"webhookId": "d0bf3351-3696-4ea0-adbd-ddec03926981",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "channelMessage",
|
||
"teamId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('teamId', 'The team GUID from Get Teams List matching the target team displayName', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"channelId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('channelId', 'The channel GUID from Get Teams Channels matching the target channel displayName', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"message": "={{ $fromAI('message', 'Plain text message to post. No markdown tables. Use line breaks for structure. Summarize large datasets and offer export links rather than pasting rows.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.microsoftTeamsTool",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
18048,
|
||
4912
|
||
],
|
||
"id": "03b2b68c-19a6-4cbe-ac06-ee2e3749691b",
|
||
"name": "Send Teams Channel Message",
|
||
"webhookId": "fb75f216-5c71-4ea0-867b-90abb819a51c",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {
|
||
"PrimaryEmailAddr": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Primary_Email_Address', ``, 'string') }}",
|
||
"PrimaryPhone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Primary_Phone', ``, 'string') }}"
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16896,
|
||
6800
|
||
],
|
||
"id": "589485a7-2896-4635-8324-713bc9e2d93f",
|
||
"name": "MSAG - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the \"view_balancesheet\" Postgres view for balance sheet data — Assets, Liabilities, and Equity as of a specific date.\n\nThis view represents CUMULATIVE balances (sum of all transactions up to a point in time), not period activity. Use this for: account balances, net assets, total liabilities, equity position, cash balances, AR/AP balances, and any question about what something IS WORTH or OWES as of a date.\n\nFull column list:\n company_id (text), realm_id (text), report_name (text),\n start_period (text), end_period (text), currency (text),\n accounting_method (text), report_run_at (timestamptz),\n section_path (text), row_group (text), row_type (text),\n row_label (text), account_id (text), depth (integer),\n col_title (text), col_start_date (text), col_end_date (text),\n col_amount (numeric), \"Account Key\" (text), account_name (text)\n\nKEY CONCEPTS:\n - col_amount is the balance AS OF col_end_date (cumulative, not period delta)\n - section_path identifies the BS section: 'Assets', 'Liabilities', 'Equity'\n - row_type = 'Data' for actual account rows; always filter row_type = 'Data' to avoid double-counting totals/headers\n - row_label = human-readable account name\n - depth = hierarchy level (lower = summary, higher = detail)\n - col_end_date is TEXT stored as YYYY-MM-DD — always cast with ::date for comparisons\n\nDATE FILTERING (col_end_date is TEXT — always cast):\n RIGHT: col_end_date::date <= CURRENT_DATE\n RIGHT: col_end_date::date = DATE_TRUNC('month', NOW())::date - INTERVAL '1 day'\n WRONG: col_end_date <= '2026-01-01' ← text comparison, wrong sort order\n\nTo get the most recent snapshot only:\n Use ORDER BY report_run_at DESC LIMIT N, or filter col_end_date::date = (SELECT MAX(col_end_date::date) FROM view_balancesheet WHERE company_id = '...')\n\nCOMMON QUERY TEMPLATES:\n\nLatest balance sheet detail by section:\n SELECT company_id, section_path, row_label, col_amount, col_end_date\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n ORDER BY company_id, section_path, depth, row_label\n LIMIT 500\n\nTotal assets / liabilities / equity summary:\n SELECT company_id, section_path, SUM(col_amount) AS total\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n GROUP BY company_id, section_path\n ORDER BY company_id, section_path\n LIMIT 100\n\nSpecific account balance (e.g. cash):\n SELECT company_id, row_label, col_amount, col_end_date\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND row_label ILIKE '%cash%'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n ORDER BY company_id, col_end_date::date DESC\n LIMIT 100\n\nCURRENCY:\n MSAG and MHAG report in CHF. After any query returning CHF rows: call Get FX Rate (fromCurrency=CHF),\n multiply col_amount by rate, report in USD with note \"converted from CHF at X CHF/USD\".\n\nDO NOT use this view for: revenue, expenses, P&L, or period-over-period income — use AI_view_general_ledger instead.\nDO NOT SUM rows where row_type != 'Data' — header and total rows double-count.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against \"view_balancesheet\". Must include WHERE and LIMIT (<=500). col_end_date is TEXT — always cast with ::date for date comparisons. Double-quote mixed-case columns like \"Account Key\". Always filter row_type = \\'Data\\' unless intentionally pulling summary totals.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17088,
|
||
7456
|
||
],
|
||
"id": "8177c53a-6296-4b5a-a383-bc1fc1787d59",
|
||
"name": "Postgres Quickbooks balance sheet",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MSAG (realm 9341452373926753, default currency CHF).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MSAG - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MSAG')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (CHF)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\nAfter posting, call Get FX Rate (fromCurrency=CHF) to convert the amount to USD for any reporting.\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16784,
|
||
7008
|
||
],
|
||
"id": "ec41cb54-6c99-4ddc-be76-bfb4fe61371c",
|
||
"name": "MSAG - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MSAG (realm 9341452373926753). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MSAG - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16640,
|
||
6864
|
||
],
|
||
"id": "19fde7fa-7a54-49f0-bbcc-45f7b23c4d81",
|
||
"name": "MSAG - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MMX (realm 9341452328017372). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MMX - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16992,
|
||
6480
|
||
],
|
||
"id": "439ff7c5-f393-4637-a3c7-1902e2d1194b",
|
||
"name": "MMX - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MUS (realm 9341452327761715). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MUS - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16992,
|
||
5824
|
||
],
|
||
"id": "b7152cba-ef41-4dcb-8779-1c9993e3c2c8",
|
||
"name": "MUS - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MMX (realm 9341452328017372, default currency USD).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MMX - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MMX')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (USD)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16704,
|
||
6480
|
||
],
|
||
"id": "001d5dd2-5824-4219-95ca-c6f1fc855044",
|
||
"name": "MMX - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MUS (realm 9341452327761715, default currency USD).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MUS - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MUS')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (USD)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16688,
|
||
5776
|
||
],
|
||
"id": "dea99ea2-8e0c-4718-beb1-2f5eff72b58a",
|
||
"name": "MUS - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🛠 Shared Tools — FX · EML · Collections · Risk Scoring · File Export\n\nCross-entity utilities available to the agent at all times.",
|
||
"height": 240,
|
||
"width": 1832,
|
||
"color": 5
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
5136
|
||
],
|
||
"id": "cd2b86b1-7002-4c75-a85f-82fc0daf7ab5",
|
||
"name": "Sticky Other Tools"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
5712
|
||
],
|
||
"id": "c8fe2dc6-a7f3-4919-a41e-c318fef0ac51",
|
||
"name": "MUS - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
5712
|
||
],
|
||
"id": "5ff329b4-a21a-4d36-9bc0-2f6434ca15cb",
|
||
"name": "MUS - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16752,
|
||
5520
|
||
],
|
||
"id": "a99476ad-e564-4cb3-bbd6-c869ca3df54b",
|
||
"name": "MUS - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const TAXABLE_STATES = ['TX', 'NY'];\n const c = $fromAI('currency', 'ISO currency code. MUS default is USD. Pass empty string to accept entity default.', 'string');\n const customerState = $fromAI('customerState', 'Two-letter US state code from customer BillAddr.CountrySubDivisionCode. Pass empty string if unknown.', 'string');\n\n const stateTaxCode = TAXABLE_STATES.includes((customerState || '').toUpperCase().trim()) ? 'TAX' : 'NON';\n\n const resolveTaxCode = (id) => {\n const map = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' };\n return map[String(id)] || stateTaxCode;\n };\n\n const p = {\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: JSON.parse($fromAI('lines', 'JSON array string of line items.', 'string')).map(l => ({\n Amount: l.amount ?? (l.qty * l.unitPrice),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty,\n UnitPrice: l.unitPrice,\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5872
|
||
],
|
||
"id": "3677c975-990e-4f83-9392-20afb217334d",
|
||
"name": "MUS - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MUS invoice by email (realm 9341452327761715). ALWAYS have a literal email address before calling — look it up from the customer record if needed. Pass invoiceId (the QBO numeric Id, not DocNumber) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber) from a prior query', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
5872
|
||
],
|
||
"id": "33c6c517-4056-4493-a1cc-ae381492feb1",
|
||
"name": "MUS - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MUS invoice (realm 9341452327761715). Requires invoiceId and syncToken from a prior QBQL query. Uses sparse update — only fields you pass are changed. Pass lines as full replacement line array if changing line items.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query — required for updates', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new transaction date YYYY-MM-DD, empty string to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new due date YYYY-MM-DD, empty string to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo/note, empty string to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items (same format as Create Invoice). Pass empty string to leave lines unchanged.', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
5872
|
||
],
|
||
"id": "50204815-05ef-44ba-b4c8-6bd270632103",
|
||
"name": "MUS - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MUS for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Each line: account_id, posting_type (Debit|Credit), amount, description, entity_type, entity_id, department_id, class_id. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
5872
|
||
],
|
||
"id": "cfb624f1-4949-41ac-8f37-e657302347b1",
|
||
"name": "MUS - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MUS. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16848,
|
||
5712
|
||
],
|
||
"id": "6be9cf50-13a3-4729-ae7c-fca8f475e339",
|
||
"name": "MUS - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Exports query results to a CSV file and returns a download URL. Pass a complete SELECT SQL query and a filename.\n\nParameters:\n- filename: snake_case name without extension, e.g. hosting_expense_2026-05-23\n- sql: complete SELECT query — no LIMIT unless intentional. Follow all column quoting rules for each view.\n\nNever assemble rows in memory for export — always pass the SQL directly.\nFor GL exports: \"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE, never quoted string literals.",
|
||
"method": "POST",
|
||
"url": "http://10.116.0.2:5679/webhook/export-insert",
|
||
"sendBody": true,
|
||
"bodyParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "filename",
|
||
"value": "={{ $fromAI('filename', 'CSV filename without extension, snake_case, e.g. hosting_expense_2026-05-23') }}"
|
||
},
|
||
{
|
||
"name": "sql",
|
||
"value": "={{ $fromAI('sql', 'Complete SELECT SQL query to execute against the Postgres data warehouse') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.4,
|
||
"position": [
|
||
16464,
|
||
5232
|
||
],
|
||
"id": "818bb6ad-6bb5-420e-9998-39123c65a3f0",
|
||
"name": "Export_File"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MUS QuickBooks (realm 9341452327761715). ALWAYS include WHERE and MAXRESULTS.\n\nQBQL RULES: NEVER use IN(...), OR, nested parentheses, subqueries, JOINs, GROUP BY, aggregates.\nUse ORDERBY (not ORDER BY). Use MAXRESULTS (not LIMIT). Single quotes only.\nException: TxnDate date filters with = or >= and ISO date strings are valid.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
5552
|
||
],
|
||
"id": "d1aa28ce-a737-49a1-ac0f-f769972bd48f",
|
||
"name": "MUS - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MUS. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
5552
|
||
],
|
||
"id": "b4989e6a-a3d0-44dd-acdd-e8bcc1defa4b",
|
||
"name": "MUS - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MUS item(s) (products/services) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name to search for', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
5552
|
||
],
|
||
"id": "1c447868-154d-44d9-8b68-87c90e945f91",
|
||
"name": "MUS - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MUS customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches with Id, DisplayName, CompanyName, PrimaryEmailAddr.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name to search for', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5552
|
||
],
|
||
"id": "0354e1d0-97ef-4a40-9968-18da85b07a92",
|
||
"name": "MUS - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const { customer_id, entity } = $input.item.json;\n\nif (!customer_id || !entity) {\n return [{ json: { error: 'customer_id and entity are required' } }];\n}\n\nconst company_id = entity;\n\nconst arResult = await $db.executeQuery(\n `SELECT\n COALESCE(SUM(balance), 0)::NUMERIC AS open_ar,\n COALESCE(MAX(CURRENT_DATE - duedate), 0) AS max_dpd,\n COUNT(*) FILTER (WHERE duedate < CURRENT_DATE) AS overdue_count\n FROM view_invoices_open\n WHERE customerref_name ILIKE $1\n AND company_id = $2`,\n [`%${customer_id}%`, company_id]\n);\n\nconst paymentResult = await $db.executeQuery(\n `SELECT COUNT(*) AS late_payments\n FROM view_payments p\n JOIN view_invoices_open i\n ON p.company_id = i.company_id\n AND p.\"CustomerRef_Name\" = i.customerref_name\n WHERE p.\"CustomerRef_Name\" ILIKE $1\n AND p.company_id = $2\n AND p.\"IsCustomerPayment\" = true\n AND p.\"TxnDate\" >= CURRENT_DATE - INTERVAL '6 months'\n AND p.\"TxnDate\" > i.duedate`,\n [`%${customer_id}%`, company_id]\n);\n\nconst row = arResult.rows[0];\nconst openAr = parseFloat(row.open_ar) || 0;\nconst maxDpd = parseInt(row.max_dpd) || 0;\nconst overdueCount = parseInt(row.overdue_count) || 0;\nconst latePayments = parseInt(paymentResult.rows[0].late_payments) || 0;\n\nlet score = 0;\nconst flags = [];\n\nif (maxDpd > 90) { score += 40; flags.push('90+ days past due'); }\nelse if (maxDpd > 60) { score += 25; flags.push('60–90 days past due'); }\nelse if (maxDpd > 30) { score += 10; flags.push('30–60 days past due'); }\n\nif (openAr > 100000) { score += 25; flags.push(`High open AR: $${openAr.toLocaleString()}`); }\nelse if (openAr > 50000) { score += 15; flags.push(`Elevated open AR: $${openAr.toLocaleString()}`); }\nelse if (openAr > 10000) { score += 5; flags.push(`Moderate open AR: $${openAr.toLocaleString()}`); }\n\nif (overdueCount > 5) { score += 20; flags.push(`${overdueCount} overdue invoices`); }\nelse if (overdueCount > 2) { score += 10; flags.push(`${overdueCount} overdue invoices`); }\nelse if (overdueCount > 0) { score += 5; flags.push(`${overdueCount} overdue invoice`); }\n\nif (latePayments > 5) { score += 15; flags.push(`${latePayments} late payments in last 6 months`); }\nelse if (latePayments > 2) { score += 8; flags.push(`${latePayments} late payments in last 6 months`); }\nelse if (latePayments > 0) { score += 3; flags.push(`${latePayments} late payment in last 6 months`); }\n\nconst rating = score >= 60 ? 'HIGH' : score >= 30 ? 'MEDIUM' : 'LOW';\n\nconst action =\n rating === 'HIGH' ? 'Escalate to finance leadership. Use formal tone in any outreach.' :\n rating === 'MEDIUM' ? 'Send collection reminder. Flag for follow-up in 5 business days.' :\n 'Standard collections cycle. Friendly reminder tone appropriate.';\n\nreturn [{ json: {\n customer_id,\n company_id,\n score,\n rating,\n flags,\n action,\n detail: {\n open_ar: openAr,\n max_dpd: maxDpd,\n overdue_invoices: overdueCount,\n late_payments_6mo: latePayments\n }\n}}];"
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
16240,
|
||
5232
|
||
],
|
||
"id": "f5d62a0f-c0e3-4391-a40b-24484be8a2bf",
|
||
"name": "score_customer_risk"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"workflowId": {
|
||
"__rl": true,
|
||
"value": "H7My4glPDt56gXJz",
|
||
"mode": "list",
|
||
"cachedResultUrl": "/workflow/H7My4glPDt56gXJz",
|
||
"cachedResultName": "Collections Emails"
|
||
},
|
||
"workflowInputs": {
|
||
"mappingMode": "defineBelow",
|
||
"value": {},
|
||
"matchingColumns": [],
|
||
"schema": [],
|
||
"attemptToConvertTypes": false,
|
||
"convertFieldsToString": false
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
16064,
|
||
5216
|
||
],
|
||
"id": "da32d790-a179-416b-83ff-0281932b67f4",
|
||
"name": "Call 'MoneyBot — Collections Emails'",
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"name": "Generate_EML_File",
|
||
"description": "Generates a .eml email file and saves it to /files/emails/. Use when the user asks to create an email file, export an email, generate a .eml, or draft a payment reminder or invoice email as a downloadable file. Pass: to (recipient), from (sender), subject, body (plain text), cc (optional). Returns the saved file path.",
|
||
"workflowId": {
|
||
"__rl": true,
|
||
"value": "QWWFHJ57u8UvM11y",
|
||
"mode": "list",
|
||
"cachedResultUrl": "/workflow/QWWFHJ57u8UvM11y",
|
||
"cachedResultName": "Sub-Workflow EML generator"
|
||
},
|
||
"workflowInputs": {
|
||
"mappingMode": "defineBelow",
|
||
"value": {
|
||
"to": "={{ $fromAI('to', 'Recipient email address', 'string') }}",
|
||
"from": "={{ $fromAI('from', 'Sender email address', 'string') }}",
|
||
"subject": "={{ $fromAI('subject', 'Email subject line', 'string') }}",
|
||
"body": "={{ $fromAI('body', 'Plain text email body content', 'string') }}",
|
||
"cc": "={{ $fromAI('cc', 'Optional CC email address, empty string if none', 'string') }}"
|
||
},
|
||
"matchingColumns": [],
|
||
"schema": [
|
||
{
|
||
"id": "to",
|
||
"displayName": "to",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "from",
|
||
"displayName": "from",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "subject",
|
||
"displayName": "subject",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "body",
|
||
"displayName": "body",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "cc",
|
||
"displayName": "cc",
|
||
"required": false,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
}
|
||
],
|
||
"attemptToConvertTypes": false,
|
||
"convertFieldsToString": true
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
15872,
|
||
5232
|
||
],
|
||
"id": "0a54f690-895c-47ef-90c3-3c328a1429d5",
|
||
"name": "Generate EML File",
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Fetch current exchange rates using exchangerate-api.com. Pass fromCurrency (e.g. CHF, USD) and the response contains rates to all major currencies. Use to convert MSAG/MHAG CHF amounts to USD for reporting.",
|
||
"url": "=https://api.exchangerate-api.com/v4/latest/{{ $fromAI('fromCurrency', 'Source currency code (e.g. CHF, USD, EUR)', 'string') || 'USD' }}",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "User-Agent",
|
||
"value": "n8n-workflow"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5232
|
||
],
|
||
"id": "69a1e4e4-3b8b-46e8-b42f-39d741760c07",
|
||
"name": "Get FX Rate"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16784,
|
||
6192
|
||
],
|
||
"id": "80640cac-e62e-40e7-aaf2-94408e5010f7",
|
||
"name": "MMX - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
6352
|
||
],
|
||
"id": "692b658b-b4b5-4fae-ac94-d1494847fa84",
|
||
"name": "MMX - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
6352
|
||
],
|
||
"id": "3363a89a-35d6-41d8-948a-b6fe9294d8d2",
|
||
"name": "MMX - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MMX QuickBooks (realm 9341452328017372). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6192
|
||
],
|
||
"id": "ebb80d71-b6ed-430c-bea9-32d7b636e543",
|
||
"name": "MMX - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MMX. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6192
|
||
],
|
||
"id": "636e1e14-dd99-4708-8bf8-c75a6197058f",
|
||
"name": "MMX - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MMX item(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6192
|
||
],
|
||
"id": "72157b53-e739-47ad-87b4-a41aba1c7e9e",
|
||
"name": "MMX - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MMX customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6192
|
||
],
|
||
"id": "cb91a632-0272-4ac7-88d3-23a9e73159db",
|
||
"name": "MMX - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇺🇸 MUS — QuickBooks US (USD)\nRealm: 9341452327761715\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Always look up TaxCode before creating invoices.",
|
||
"height": 580,
|
||
"width": 1652,
|
||
"color": 2
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
5440
|
||
],
|
||
"id": "c1bb76f7-c40e-48bc-a442-da54d7647010",
|
||
"name": "Sticky MUS"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# Freshdesk",
|
||
"height": 544,
|
||
"width": 656,
|
||
"color": 3
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17344,
|
||
5488
|
||
],
|
||
"id": "7cea2fb8-749f-40db-9760-5e634ac9de96",
|
||
"name": "Sticky Note1"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://masttro.freshdesk.com/api/v2/contacts/autocomplete",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "term",
|
||
"value": "={{ $fromAI('contact_name', 'The name or partial name of the contact to search for. Examples: \"elsa peretti\", \"john\", \"smith\". Do not include field prefixes.', 'string') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.4,
|
||
"position": [
|
||
17408,
|
||
5648
|
||
],
|
||
"id": "a65faea6-3bef-4603-83c0-221173d303e2",
|
||
"name": "Freshdesk Contact Lookup - Finance",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Get many tickets in Freshdesk for Finance.\nWhen looking at open tickets, use status 2 and status 3 and check all batches.",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {
|
||
"include": [
|
||
"description",
|
||
"company",
|
||
"stats",
|
||
"requester"
|
||
]
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.freshdeskTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17520,
|
||
5808
|
||
],
|
||
"id": "b8cb4668-edd7-477c-9aa6-5b158ec16678",
|
||
"name": "Freshdesk - finance tickets",
|
||
"credentials": {
|
||
"freshdeskApi": {
|
||
"id": "CjhwO4a2iGx7J4v0",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Get many tickets in Freshdesk for customer support team.\nWhen looking at open tickets, use status 2 and status 3 and check all batches.",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {
|
||
"include": [
|
||
"description",
|
||
"company",
|
||
"stats",
|
||
"requester"
|
||
]
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.freshdeskTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17632,
|
||
5648
|
||
],
|
||
"id": "99daa1c9-ebc7-4d89-85bb-357fc65681f8",
|
||
"name": "Freshdesk - general support",
|
||
"credentials": {
|
||
"freshdeskApi": {
|
||
"id": "eU5oyV62tEJdY6dV",
|
||
"name": "Freshdesk - general support"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"authentication": "appToken",
|
||
"resource": "deal",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"filters": {}
|
||
},
|
||
"type": "n8n-nodes-base.hubspotTool",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
17072,
|
||
5200
|
||
],
|
||
"id": "129384cf-27a0-46c6-aee3-c451c4506353",
|
||
"name": "Hubspot all deals search",
|
||
"credentials": {
|
||
"hubspotAppToken": {
|
||
"id": "DJFxH3uski1VveCl",
|
||
"name": "HubSpot account"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"authentication": "appToken",
|
||
"resource": "company",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.hubspotTool",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
16896,
|
||
5200
|
||
],
|
||
"id": "c9e8afe7-9b53-4b70-8783-eb5f56842275",
|
||
"name": "Hubspot all companies search",
|
||
"credentials": {
|
||
"hubspotAppToken": {
|
||
"id": "DJFxH3uski1VveCl",
|
||
"name": "HubSpot account"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "SELECT\n id, excel_row_number, entity, deal_type, invoice_status,\n bill_freq, qb_item_type, close_date, bill_date, hs_customer,\n icon_id, qb_customer, qb_item_description, currency,\n amount_lcy, net_terms, tax, note\nFROM cust_fact_table\nWHERE\n UPPER(TRIM(invoice_status)) = 'UNBILLED'\n AND bill_date BETWEEN '{{ $fromAI('startDate', 'Start of bill date range in YYYY-MM-DD format', 'string') }}'::date\n AND '{{ $fromAI('endDate', 'End of bill date range in YYYY-MM-DD format', 'string') }}'::date\n AND entity IS NOT NULL\n AND amount_lcy IS NOT NULL\n AND amount_lcy <> 0\n AND COALESCE(run_status, '') <> 'created'\n AND COALESCE(on_hold, false) = false\nORDER BY entity, bill_date",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17280,
|
||
5200
|
||
],
|
||
"id": "71698190-a071-4b15-b5c7-2bad605c033f",
|
||
"name": "Read Customer Fact Table for Billing",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MUS customer (realm 9341452327761715). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17088,
|
||
5568
|
||
],
|
||
"id": "1f695e1a-d0a9-45a9-9d57-9e9049b7e13d",
|
||
"name": "MUS - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MMX customer (realm 9341452328017372). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17072,
|
||
6192
|
||
],
|
||
"id": "8e541a9d-1003-4e4d-9d7f-02083fcceac3",
|
||
"name": "MMX - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MSAG customer (realm 9341452373926753). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17104,
|
||
6800
|
||
],
|
||
"id": "74d73667-2416-4e71-a9e7-cb8c9656404b",
|
||
"name": "MSAG - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a Freshdesk Finance ticket. Use to change status, priority, or subject. Status codes: 2=Open, 3=Pending, 4=Resolved, 5=Closed. Always confirm before closing or resolving. Requires numeric ticket ID from a prior lookup.",
|
||
"method": "PUT",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/{{ $fromAI('ticket_id', 'Numeric Freshdesk ticket ID', 'string') }}",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = {}; const status = $fromAI('status', 'Ticket status: 2=Open, 3=Pending, 4=Resolved, 5=Closed. Pass 0 to skip.', 'number'); const priority = $fromAI('priority', 'Priority: 1=Low, 2=Medium, 3=High, 4=Urgent. Pass 0 to skip.', 'number'); const subject = $fromAI('subject', 'Optional new subject. Empty string to skip.', 'string'); if (status && status > 0) p.status = status; if (priority && priority > 0) p.priority = priority; if (subject && subject.trim()) p.subject = subject.trim(); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17696,
|
||
5824
|
||
],
|
||
"id": "dfe291b7-c3c5-41a6-9916-f83200b97b8d",
|
||
"name": "Freshdesk - Update Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Add a note to a Freshdesk Finance ticket. Private notes are internal only (default). Public notes are visible to the requester. Always retrieve ticket ID via search before calling.",
|
||
"method": "POST",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/{{ $fromAI('ticket_id', 'Numeric Freshdesk ticket ID', 'string') }}/notes",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ body: $fromAI('body', 'Note content — plain text or HTML', 'string'), private: $fromAI('private', 'true for internal note, false for customer-visible', 'boolean') ?? true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17840,
|
||
5616
|
||
],
|
||
"id": "8c7fabaa-a457-411e-b569-b24c0ae88fa6",
|
||
"name": "Freshdesk - Add Note to Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 2
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "pdf-mime-check",
|
||
"leftValue": "={{ $json.files?.[0]?.mimeType }}",
|
||
"rightValue": "application/pdf",
|
||
"operator": {
|
||
"type": "string",
|
||
"operation": "equals"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
15792,
|
||
4768
|
||
],
|
||
"id": "7acb2049-f340-43e8-a691-8feccc8be128",
|
||
"name": "PDF upload?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "merge-chatinput",
|
||
"name": "chatInput",
|
||
"value": "={{ $('When chat message received').item.json.chatInput }}\n\n--- Uploaded Document ---\n{{ $json.text }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "pass-session",
|
||
"name": "sessionId",
|
||
"value": "={{ $('When chat message received').item.json.sessionId }}",
|
||
"type": "string"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [
|
||
16176,
|
||
4496
|
||
],
|
||
"id": "4b87f084-42ce-4f0b-9e60-c0343d6419fa",
|
||
"name": "Merge PDF Text"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "pdf",
|
||
"binaryPropertyName": "={{ Object.keys($binary)[0] }}",
|
||
"options": {
|
||
"joinPages": true
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.extractFromFile",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
16000,
|
||
4512
|
||
],
|
||
"id": "2b54f2f0-d4ec-4ff2-9f26-bdd437b361d4",
|
||
"name": "Extract from File"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": false,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 1
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "excel-mime-check",
|
||
"leftValue": "={{ $json.files?.[0]?.mimeType }}",
|
||
"rightValue": "vnd\\.ms-excel|spreadsheetml",
|
||
"operator": {
|
||
"type": "string",
|
||
"operation": "regex"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
15968,
|
||
4768
|
||
],
|
||
"id": "ec0ff3a8-459a-47fd-9a0c-307aa8e90167",
|
||
"name": "Excel upload?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "xlsx",
|
||
"binaryPropertyName": "={{ Object.keys($binary)[0] }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.extractFromFile",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
16128,
|
||
4752
|
||
],
|
||
"id": "c683404a-97b6-4f03-a6f5-827536a28097",
|
||
"name": "Extract from File (XLSX)"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "merge-chatinput-xlsx",
|
||
"name": "chatInput",
|
||
"value": "={{ $('When chat message received').item.json.chatInput }}\n\n--- Uploaded Spreadsheet ---\n{{ JSON.stringify($json, null, 2) }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "pass-session-xlsx",
|
||
"name": "sessionId",
|
||
"value": "={{ $('When chat message received').item.json.sessionId }}",
|
||
"type": "string"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [
|
||
16304,
|
||
4640
|
||
],
|
||
"id": "5db4a7bc-0f54-48a0-8fed-4a64860d0efa",
|
||
"name": "Merge XLSX Data"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a new Freshdesk Finance ticket. Use when the user asks to create, open, or log a new ticket — NOT when adding to an existing one. Required: subject and description. Optional: priority (1=Low, 2=Medium, 3=High, 4=Urgent), status (2=Open default), email (requester email — omit if unknown), cc_emails (array of strings).\\n\\nAlways post description as HTML for good formatting.\\nDo NOT use this to update or comment on existing tickets — use Freshdesk - Update Ticket or Freshdesk - Add Note to Ticket instead.",
|
||
"method": "POST",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { subject: $fromAI('subject', 'Ticket subject line', 'string'), description: $fromAI('description', 'Ticket body — HTML preferred', 'string'), status: $fromAI('status', '2=Open, 3=Pending', 'number') || 2, priority: $fromAI('priority', '1=Low, 2=Medium, 3=High, 4=Urgent', 'number') || 2 }; const email = $fromAI('email', 'Requester email. Empty string for internal tickets.', 'string'); p.email = (email && email.trim()) ? email.trim() : 'mnolan@masttro.com'; return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17856,
|
||
5808
|
||
],
|
||
"id": "6525ca16-86a3-4b47-a5d1-85db4142157e",
|
||
"name": "Freshdesk - Create Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Download a QBO invoice as a PDF and return a clickable download link. Works for MUS, MMX, and MSAG. Pass entity (MUS, MMX, or MSAG) and invoiceId (QBO numeric Id, not DocNumber). Optionally pass a filename. After calling, return the message field verbatim — it contains the clickable download link.",
|
||
"method": "POST",
|
||
"url": "http://10.116.0.2:5679/webhook/invoice-pdf",
|
||
"sendBody": true,
|
||
"bodyParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "entity",
|
||
"value": "={{ $fromAI('entity', 'Entity code: MUS, MMX, or MSAG', 'string') }}"
|
||
},
|
||
{
|
||
"name": "invoiceId",
|
||
"value": "={{ $fromAI('invoiceId', 'QBO numeric Invoice Id (not DocNumber)', 'string') }}"
|
||
},
|
||
{
|
||
"name": "filename",
|
||
"value": "={{ $fromAI('filename', 'Optional filename e.g. invoice_MUS_1234.pdf — empty string for auto', 'string') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16704,
|
||
5216
|
||
],
|
||
"id": "58a640c1-a7ff-4c5e-8ff8-c84180fc81e8",
|
||
"name": "Download Invoice PDF"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MUS (realm 9341452327761715, USD). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MUS - Find customer by name\n 2. Resolve itemId via MUS - Find item by name\n 3. Get next DocNumber: MUS - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. Look up taxCodeId: TX or NY state → 'TAX', all others → 'NON'\n 5. Confirm details with user before posting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const TAXABLE_STATES = ['TX', 'NY'];\n const customerState = $fromAI('customerState', 'Two-letter US state code from customer BillAddr.CountrySubDivisionCode. Empty string if unknown.', 'string');\n const stateTaxCode = TAXABLE_STATES.includes((customerState || '').toUpperCase().trim()) ? 'TAX' : 'NON';\n const resolveTaxCode = (id) => { const map = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' }; return map[String(id)] || stateTaxCode; };\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice, taxCodeId.', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17168,
|
||
5840
|
||
],
|
||
"id": "2b254fe5-6224-466b-80df-a506beea942d",
|
||
"name": "MUS - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MMX (realm 9341452328017372, USD). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MMX - Find customer by name\n 2. Resolve itemId via MMX - Find item by name\n 3. Get next DocNumber: MMX - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. MMX customers are non-US — always pass taxCodeId = 'NON'\n 5. Confirm details with user before posting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice. taxCodeId always NON for MMX.', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: 'NON' }\n }\n }))\n };\n const c = $fromAI('currency', 'ISO currency code. Empty string for entity default (USD).', 'string');\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17152,
|
||
6368
|
||
],
|
||
"id": "a61dde4f-5a00-491a-b3b3-3161b3adf864",
|
||
"name": "MMX - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MSAG (realm 9341452373926753, CHF). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MSAG - Find customer by name\n 2. Resolve itemId via MSAG - Find item by name\n 3. Get next DocNumber: MSAG - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. Query TaxCode (SELECT Id, Name FROM TaxCode WHERE Active = true MAXRESULTS 50) to resolve Swiss VAT code — never hardcode\n 5. Confirm details with user before posting\n 6. After posting, call Get FX Rate (fromCurrency=CHF) to convert amount to USD for reporting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice, taxCodeId (QBO TaxCode Id from TaxCode query — never null or hardcoded).', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: (l.taxCodeId && String(l.taxCodeId).trim()) ? String(l.taxCodeId).trim() : 'NON' }\n }\n }))\n };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17136,
|
||
7120
|
||
],
|
||
"id": "0749b243-fada-44d0-a213-fda59f446ea3",
|
||
"name": "MSAG - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"contextWindowLength": 10
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||
"typeVersion": 1.4,
|
||
"position": [
|
||
16832,
|
||
4928
|
||
],
|
||
"id": "05180f32-ec76-4a12-9549-e40906cb9e80",
|
||
"name": "Simple Memory"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MUS (realm 9341452327761715). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a $0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MUS - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MUS - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16912,
|
||
5488
|
||
],
|
||
"id": "0136e7bc-e3bc-42cd-a20c-b23c2a26b6dc",
|
||
"name": "MUS - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MMX (realm 9341452328017372). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a $0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MMX - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MMX - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16944,
|
||
6112
|
||
],
|
||
"id": "ca1e597e-1b04-4202-9fdd-be57905e7f57",
|
||
"name": "MMX - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MSAG (realm 9341452373926753). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a CHF 0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MSAG - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MSAG - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Then call Get FX Rate (fromCurrency=CHF) to convert any amounts to USD for reporting. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16960,
|
||
7136
|
||
],
|
||
"id": "13fdf991-1331-4e62-8a17-6bd74143be8e",
|
||
"name": "MSAG - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# Microsoft Teams",
|
||
"height": 448,
|
||
"width": 768
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17504,
|
||
4640
|
||
],
|
||
"id": "03baead9-ff4f-47bb-8dd8-48020cfa5834",
|
||
"name": "Sticky Note2"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"public": true,
|
||
"initialMessages": "💰 MoneyBot (READ-ONLY) online.\nI can check QBO customers, find real time FX rates, query Hubspot deals (closed/won), risk score customers with open invoices, create CSV files and query 1,200+ customer contracts and amendments in memory. I can export invoices to PDF.\n\nYou can upload images, XLSX or PDFs for review.",
|
||
"availableInChat": true,
|
||
"agentName": "Moneybot",
|
||
"options": {
|
||
"allowFileUploads": true,
|
||
"allowedFilesMimeTypes": "*"
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||
"typeVersion": 1.4,
|
||
"position": [
|
||
15632,
|
||
4512
|
||
],
|
||
"id": "2d461a9b-bb76-44a1-8f19-82bb3d584a84",
|
||
"name": "When chat message received",
|
||
"webhookId": "moneybot-readonly",
|
||
"alwaysOutputData": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"model": {
|
||
"__rl": true,
|
||
"value": "claude-sonnet-5",
|
||
"mode": "list",
|
||
"cachedResultName": "Claude Sonnet 5"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
16848,
|
||
4480
|
||
],
|
||
"id": "884af69f-cde9-4acd-9191-86e34ca41f3e",
|
||
"name": "Claude",
|
||
"credentials": {
|
||
"anthropicApi": {
|
||
"id": "uJ4J74wLCEWqVd9B",
|
||
"name": "Anthropic - Moneybot"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"model": "gemma4:12b",
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17040,
|
||
4480
|
||
],
|
||
"id": "071ce6d1-5379-4964-8d10-36682cceadc9",
|
||
"name": "Ollama",
|
||
"credentials": {
|
||
"ollamaApi": {
|
||
"id": "PjA3zj5FSQY841F6",
|
||
"name": "Ollama 100.111.46.13 fireball"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
"connections": {
|
||
"MMX - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks open invoices": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks customer payments": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks general ledger": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot deals": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres PGVector Store": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"mistral-embed": {
|
||
"ai_embedding": [
|
||
[
|
||
{
|
||
"node": "Postgres PGVector Store",
|
||
"type": "ai_embedding",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Contract_Direct_Lookup": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Quickbooks Chart of Accounts MGC MUS MMX MSAG MHAG": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams DM Chats — returns all 1:1 chats with id and members. Search members for displayName or email matching the target person. Use chatId with Send Teams DM.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams Group Chats — returns all group chats with id, topic, and members. Match by topic name or member displayName. Use chatId with Send Teams DM.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams List — returns all joined teams with id and displayName. Call this first to resolve a team name to a teamId before calling Get Teams Channels.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams Channels — given a teamId from Get Teams List, returns all channels with id and displayName. Use channelId and teamId together with Send Teams Channel Message.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Send Teams DM": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Send Teams Channel Message": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks balance sheet": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Export_File": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"score_customer_risk": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Call 'MoneyBot — Collections Emails'": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Generate EML File": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get FX Rate": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk Contact Lookup - Finance": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - finance tickets": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - general support": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot all deals search": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot all companies search": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Read Customer Fact Table for Billing": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Update Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Add Note to Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"PDF upload?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Extract from File",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Excel upload?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Merge PDF Text": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Extract from File": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Merge PDF Text",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Excel upload?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Extract from File (XLSX)",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Extract from File (XLSX)": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Merge XLSX Data",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Merge XLSX Data": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Create Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Download Invoice PDF": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Simple Memory": {
|
||
"ai_memory": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_memory",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"When chat message received": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "PDF upload?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Claude": {
|
||
"ai_languageModel": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_languageModel",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
}
|
||
},
|
||
"settings": {
|
||
"binaryMode": "separate",
|
||
"errorWorkflow": "tJSgL4iy63ys2NCA",
|
||
"availableInMCP": false,
|
||
"executionOrder": "v1"
|
||
},
|
||
"staticData": null,
|
||
"meta": {
|
||
"templateCredsSetupCompleted": true
|
||
},
|
||
"nodeGroups": [],
|
||
"pinData": {},
|
||
"versionId": "dd228b98-e8d3-45e7-8ee7-84865b8ba946",
|
||
"activeVersionId": "dd228b98-e8d3-45e7-8ee7-84865b8ba946",
|
||
"triggerCount": 1,
|
||
"shared": [
|
||
{
|
||
"updatedAt": "2026-05-08T17:09:50.845Z",
|
||
"createdAt": "2026-05-08T17:09:50.845Z",
|
||
"role": "workflow:owner",
|
||
"workflowId": "eoMLOlRefzddaMQs",
|
||
"projectId": "X4TTvIwW8wN0Iirz"
|
||
}
|
||
],
|
||
"activeVersion": {
|
||
"updatedAt": "2026-07-01T13:25:57.525Z",
|
||
"createdAt": "2026-07-01T13:25:53.470Z",
|
||
"versionId": "dd228b98-e8d3-45e7-8ee7-84865b8ba946",
|
||
"workflowId": "eoMLOlRefzddaMQs",
|
||
"nodes": [
|
||
{
|
||
"parameters": {
|
||
"content": "## Internal link using basic auth\n\nhttps://Masttr0!:Masttr0!@moneybot.masttro.com/webhook/672bf651-91e6-41de-b897-13e54c0e1f10/chat\n\nhttps://moneybot.masttro.com/webhook/moneybot-readonly/chat",
|
||
"height": 240,
|
||
"width": 464
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16000,
|
||
3984
|
||
],
|
||
"id": "d03c3c99-ddd5-4d62-a364-1db45756e92b",
|
||
"name": "Sticky Note"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🤖 Core — Trigger · Agent · LLMs · Memory\n\nEntry point and the AI brain. Connect your preferred LLM (Sonnet, Haiku, or Ollama) to the agent. Simple Memory provides per-session context.",
|
||
"height": 800,
|
||
"width": 1868,
|
||
"color": 3
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15584,
|
||
4288
|
||
],
|
||
"id": "830b99a8-bf8c-4944-aaa8-ee857f93a6be",
|
||
"name": "Sticky Core"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"options": {
|
||
"systemMessage": "=Current datetime is {{ $now }}\n\nYou are MoneyBot, a finance automation assistant for Masttro.\n\n## ENTITIES\nMUS (US, realm 9341452327761715, USD) · MMX (Mexico, realm 9341452328017372, USD) · MSAG (Switzerland, realm 9341452373926753, CHF) · MHAG (Switzerland holding, realm 9341452561201469, CHF — JE only) · MGC (Global, realm 9130354300064436, USD — JE only)\n\n## TOOL SELECTION\n| Intent | Tool |\n|---|---|\n| Open/unpaid invoices | Postgres: view_invoices_open |\n| All invoices by date | ENTITY - Query QBO (QBQL) |\n| Payments received | Postgres: view_payments |\n| GL / revenue / expenses / P&L | Postgres: AI_view_general_ledger |\n| Account ID lookup | Postgres: chartofaccounts — always before JE |\n| HubSpot closed-won deals | Postgres: view_hubspot_closedwon |\n| HubSpot owner lookup | Postgres: hubspot_owners |\n| HubSpot all deals/companies | Fallback if not in view_hubspot_closedwon |\n| Billing schedule | Postgres: view_hubspot_closedwon |\n| Next invoice number | ENTITY - Get latest invoice DocNumber |\n| Find customer | ENTITY - Find customer by name |\n| Find item/product | ENTITY - Find item by name |\n| Ad-hoc QBO query | ENTITY - Query QBO (QBQL) |\n| Customer record by ID | ENTITY - Get a customer |\n| Create / delete / reclass JE | ENTITY - Create/delete Journal Entry |\n| Export to CSV/Excel | Export_File |\n| Contract terms / renewal dates | pgvector store first, fallback Contract_Direct_Lookup |\n| DSO / days to pay | view_payments JOIN view_invoices_open |\n| Account balances / BS / assets / liabilities / equity | Postgres: view_balancesheet |\n| Find tickets | Freshdesk - finance tickets |\n| Update ticket status / priority | Freshdesk - Update Ticket |\n| Add note / internal comment | Freshdesk - Add Note to Ticket |\n| Create new ticket | Freshdesk - Create Ticket |\n| Download invoice as PDF | Download Invoice PDF |\n| Apply credit memo to invoice | ENTITY - Apply Credit Memo to Invoice |\n\nNever dump all invoices, customers, or payments from QBO. Always use Postgres views for list queries.\n\n## POSTGRES RULES\n- AI_view_general_ledger: \"Date\" is a native DATE — always use DATE_TRUNC or CURRENT_DATE, never text strings\n- ALL mixed-case columns must be double-quoted: \"Date\", \"Account\", \"AccountType\", \"AccountSubType\", \"Classification\", \"Transaction_Type\", \"Memo_Description\", \"CustomerName\", \"Customer_Id\", \"Vendor\", \"Vendor_Id\", \"Account_Id\", \"Class\", \"Department\"\n- Lowercase (no quotes): company_id, realm_id, amount, debit, credit, exch_rate, currency\n- Never SELECT * without a WHERE clause — always use ILIKE for case-insensitive searches\n\n## QBQL RULES\nQBQL is NOT SQL. Forbidden: IN(...), OR, LIKE, ILIKE, nested parentheses, subqueries, JOINs, GROUP BY, aggregates.\nUse ORDERBY (one word), MAXRESULTS (not LIMIT, cap 100), single quotes only.\nDate filters: TxnDate >= 'YYYY-MM-DD' is valid. Derive dates from $now — never hardcode year literals.\nMultiple values: run separate calls, never chain OR.\n\nFor all-invoices-by-date, run one call per AR entity (MUS, MMX, MSAG):\n SELECT Id, DocNumber, TxnDate, CustomerRef, TotalAmt, Balance FROM Invoice\n WHERE TxnDate >= '{{ $now.toFormat(\"yyyy-MM-01\") }}' MAXRESULTS 100\n\n## QBO REFERENCE ID RULES — APPLIES TO ALL QBQL QUERIES AND WRITE OPERATIONS\nNever use a customer or item NAME as a filter value for ID-typed fields in QBQL.\nCustomerRef, ItemRef, VendorRef are reference fields — they require numeric IDs, not names.\n\nWRONG: SELECT * FROM Invoice WHERE CustomerRef = 'Ritholtz Wealth Management LLC'\nRIGHT: \n 1. SELECT Id FROM Customer WHERE DisplayName = 'Ritholtz Wealth Management LLC'\n 2. SELECT * FROM Invoice WHERE CustomerRef = '<numeric Id from step 1>'\n\nThis applies to QBQL reads as much as write payloads — resolve the Id first, always.\n\n## INVOICE CREATION WORKFLOW\n1. Find customer → get customerId + BillAddr.CountrySubDivisionCode\n2. Find item → get itemId\n3. Get latest DocNumber → +1 → collision-check CreditMemo + Estimate → use first clear number\n4. Tax code:\n - MUS: state TX or NY → 'TAX'; all others → 'NON'; unknown → 'NON'. Never omit.\n - MMX: always 'NON'\n - MSAG: query TaxCode table for Swiss VAT id\n5. Confirm details with user before creating\n\n## INVOICE SEND / UPDATE RULES\nSend: always use a literal email address — look up PrimaryEmailAddr if user says \"use the one on file\".\nUpdate (sparse): fetch SyncToken → confirm changes → copy lines verbatim from fetched array, substitute only what changed → call Update.\n\n## JOURNAL ENTRY RULES\n- Debits must equal credits — verify before posting\n- Always look up account IDs via chartofaccounts first; always populate Department\n- To modify a JE: delete existing → create corrected. Confirm debit/credit table before posting.\n- operation=delete is a URL parameter — never include in JSON body\n- MHAG/MGC: JE and Query QBO only — no invoice or customer tools\n- Entity currencies: MUS→USD · MMX→USD · MSAG→CHF · MHAG→CHF · MGC→USD\n- DocNumber format: JE-YYYYMM-NNN (max 13 chars). Reclass suffix: append \"-R\" (must stay ≤21 chars total)\n\n## RECLASS JOURNAL ENTRY\n1. Fetch Id + SyncToken via QBQL\n2. Look up accounts via chartofaccounts\n3. Confirm debit/credit table with user\n4. Delete original\n5. Post corrected (DocNumber = original + \"-R\")\n6. Confirm new JE Id\n\n## CURRENCY\nHome currencies: MUS/MMX/MGC=USD · MSAG/MHAG=CHF\nAfter any GL query with MSAG/MHAG rows: call Get FX Rate (fromCurrency=CHF), multiply by rates.USD, report USD only with note \"converted from CHF at X CHF/USD\".\nFor cross-currency deal totals: use amount_in_home_currency.\n\n## CONTRACT RETRIEVAL\n1. pgvector first — use specific clause language, one query per term\n2. If pgvector returns wrong client → Contract_Direct_Lookup\n3. If not found after both: \"This detail was not found in the retrieved contract sections.\"\n\n## BILLING SCHEDULE\nQuery view_hubspot_closedwon. Verify via pgvector if data looks wrong.\nBilling rules: Monthly→one row/month · Quarterly→every 3 months · Semi-annual→every 6 months · Annual→anniversary · NULL term→check pgvector, do not default to Annual.\n\n## LARGE DATASET HANDLING\nPhase 1 — collect silently: all filtering in SQL, LIMIT 50 OFFSET N until 0 rows.\nPhase 2 — present once: markdown tables, max 25 rows. Rows >25: show first 25 + offer export. Rows >100: skip table, call Export_File immediately.\n\n## FILE EXPORT\nFilename: report_type_YYYY-MM-DD (snake_case, derive from $now). Pass filename (no extension) + sql.\nReturn the message field verbatim. Never prefix contracts with schema.\n\n## FRESHDESK\nSearch by keyword: pass raw keyword only — e.g. acacia\nSearch by person: Contact Lookup → get email → requester:'email@domain.com' AND status:2 OR requester:'email@domain.com' AND status:3\nSingle quotes around all string values. Never use parentheses in query strings.\nStatus codes: 2=Open · 3=Pending · 4=Resolved · 5=Closed\nNotes default to private — ask if customer-visible. Always retrieve ticket Id before writing. Post in HTML format.\nConfirm before closing or resolving.\n\n## MICROSOFT TEAMS\nSend only when explicitly asked. Never auto-send after a query.\nIdentity: userId 94387110-efa9-4e7f-8f18-3332df67c0a9 · email mnolan@masttro.com\n1:1 DM: Get Teams DM Chats → find non-self member → Send Teams DM (once)\nGroup: Get Teams Group Chats → match topic/member → Send Teams DM (once)\nChannel: Get Teams List → teamId → Get Teams Channels → channelId → Send Teams Channel Message (once)\nNever guess a chatId. Any send response = done, never retry. Plain text only, max 10 rows.\n\n## OUTPUT FORMAT\n- Natural language and markdown only — no XML, tool syntax, or parameter tags\n- Never surface tool names or selection reasoning\n- Never hardcode year literals in SQL — always derive from NOW()\n- After a successful write action: clean confirmation summary only, no intermediate narration",
|
||
"maxIterations": 100,
|
||
"passthroughBinaryImages": true,
|
||
"batching": {
|
||
"batchSize": 1,
|
||
"delayBetweenBatches": 2000
|
||
}
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||
"typeVersion": 3.1,
|
||
"position": [
|
||
16400,
|
||
4448
|
||
],
|
||
"id": "614a4991-0062-4cbe-bf33-25e8ab667b10",
|
||
"name": "AI Agent"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇲🇽 MMX — QuickBooks Mexico (MXN)\nRealm: 9341452328017372\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Default currency is MXN. Pass currency override if needed.",
|
||
"height": 580,
|
||
"width": 1636,
|
||
"color": 4
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
6080
|
||
],
|
||
"id": "e6717b22-9345-41a2-81e4-05eaae719e9e",
|
||
"name": "Sticky MMX"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const c = $fromAI('currency', 'ISO currency code. MMX default is USD. Pass empty string to accept entity default.', 'string');\n const taxCodeMap = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' };\n\n const resolveTaxCode = (id) => taxCodeMap[String(id)] || 'NON';\n\n const p = {\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: JSON.parse($fromAI('lines', 'JSON array string of line items.', 'string')).map(l => ({\n Amount: l.amount ?? (l.qty * l.unitPrice),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty,\n UnitPrice: l.unitPrice,\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6512
|
||
],
|
||
"id": "482352cf-27e8-47a7-8194-02294478dfa3",
|
||
"name": "MMX - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MMX invoice by email (realm 9341452328017372). ALWAYS have a literal email address before calling. Pass invoiceId (QBO numeric Id) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber)', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6512
|
||
],
|
||
"id": "52a8cffc-6914-44b2-8d49-44d163df253a",
|
||
"name": "MMX - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MMX invoice (realm 9341452328017372). Requires invoiceId and syncToken from a prior QBQL query. Sparse update — only fields passed are changed.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new TxnDate YYYY-MM-DD, empty to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new DueDate YYYY-MM-DD, empty to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo, empty to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items, empty string to skip', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6512
|
||
],
|
||
"id": "8d926a05-86f0-40b6-96c1-e14fd1484394",
|
||
"name": "MMX - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MMX for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6512
|
||
],
|
||
"id": "8b351de9-5f60-43d7-b95c-3d9ff4a6c51c",
|
||
"name": "MMX - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MMX. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16864,
|
||
6400
|
||
],
|
||
"id": "976533e1-d0d6-477f-b8b8-81567759e930",
|
||
"name": "MMX - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇨🇭 MSAG — QuickBooks Switzerland (CHF)\nRealm: 9341452373926753\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Swiss VAT: 8.1% standard · 2.6% reduced · 3.8% lodging · 0% export\n> Always convert CHF→USD via Get FX Rate before reporting.",
|
||
"height": 580,
|
||
"width": 1620,
|
||
"color": 6
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
6720
|
||
],
|
||
"id": "07586a4c-d4ef-4ec6-af83-241ae97685c2",
|
||
"name": "Sticky MSAG"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MSAG customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6832
|
||
],
|
||
"id": "3a01b6d9-217f-451c-bb4d-0748c3a4fba7",
|
||
"name": "MSAG - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MSAG item(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6832
|
||
],
|
||
"id": "ab6b639a-3991-460b-a5da-e3f5c3a1968e",
|
||
"name": "MSAG - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MSAG. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6832
|
||
],
|
||
"id": "c9dea8ca-b9bf-4dd7-a7e5-09e2ac05c5cd",
|
||
"name": "MSAG - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MSAG QuickBooks (realm 9341452373926753). ALWAYS include WHERE and MAXRESULTS. Use SELECT Id, Name, Description, Active FROM TaxCode WHERE Active = true MAXRESULTS 50 to discover Swiss VAT codes.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6832
|
||
],
|
||
"id": "7fdec87b-3bbd-4a33-8dc9-b4de53054520",
|
||
"name": "MSAG - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
6992
|
||
],
|
||
"id": "05ff2df0-96b4-482c-9120-0c81af2e1cde",
|
||
"name": "MSAG - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
6992
|
||
],
|
||
"id": "dcd00d78-c71e-479a-b1d3-61230b07cf01",
|
||
"name": "MSAG - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Invoice date in YYYY-MM-DD format', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: (() => {\n const raw = $fromAI('lines', 'JSON array of line items. Each object must have: amount (number), itemId (string), description (string), qty (number), unitPrice (number), taxCodeId (string — use the QBO TaxCode Id from the TaxCode query, never null)', 'string');\n const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;\n return parsed.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: (l.taxCodeId && String(l.taxCodeId).trim()) ? String(l.taxCodeId).trim() : 'NON' }\n }\n }));\n })()\n}) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
7152
|
||
],
|
||
"id": "eed664c9-00ff-466c-ba83-4e9dfa929552",
|
||
"name": "MSAG - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MSAG invoice by email (realm 9341452373926753). ALWAYS have a literal email address before calling — look it up from the customer record if needed. Pass invoiceId (QBO numeric Id, not DocNumber) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber) from a prior query', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
7152
|
||
],
|
||
"id": "47d35382-faf3-4b3d-a29c-a057e98f59cd",
|
||
"name": "MSAG - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MSAG invoice (realm 9341452373926753). Requires invoiceId and syncToken from a prior QBQL query. Sparse update — only fields passed are changed.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new TxnDate YYYY-MM-DD, empty to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new DueDate YYYY-MM-DD, empty to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo, empty to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items, empty string to skip', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
7152
|
||
],
|
||
"id": "1fddb639-ac49-48c0-af37-988dd09a1ef7",
|
||
"name": "MSAG - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MSAG for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
7152
|
||
],
|
||
"id": "e07b778b-2fab-48e0-9660-f022df3887d0",
|
||
"name": "MSAG - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MSAG. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16624,
|
||
7152
|
||
],
|
||
"id": "595d91fb-ef4a-4d06-891d-3e67306659d3",
|
||
"name": "MSAG - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🗄 Postgres — Data Warehouse (preferred read path)\nCredential: Postgres n8n-quickbooks\n\nviews: open invoices · payments · general ledger · hubspot_deals\n\n> Always use these views for list queries — never dump from QBO directly.\n> Quote mixed-case column names.\n> Revenue in GL is negative — use ABS(SUM(amount)).",
|
||
"height": 260,
|
||
"width": 1588
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
7360
|
||
],
|
||
"id": "f6a2fe13-fafb-443a-9512-ce4907bdd22d",
|
||
"name": "Sticky Postgres"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the view_invoices_open Postgres view. Returns UNPAID AR invoices only (balance > 0) — paid invoices excluded. For all invoices by date use ENTITY - Query QBO (QBQL) instead.\n\nAll columns lowercase, no quoting needed.\nColumns: invoice_key, company_id, docnumber, txndate, customerref_name, duedate, totalamt, balance, currencyref, exch_rate\n\nCRITICAL column types:\n txndate — native DATE. Never compare to text string.\n RIGHT: txndate = CURRENT_DATE\n RIGHT: txndate >= CURRENT_DATE - INTERVAL '7 days'\n WRONG: txndate = '2026-05-22' ← TYPE ERROR\n duedate — native DATE. Same rules.\n RIGHT: duedate < CURRENT_DATE\n WRONG: duedate < '2026-05-22' ← TYPE ERROR\n totalamt, balance — numeric\n All others — text\n\nExamples:\n -- Overdue 30+ days\n SELECT company_id, customerref_name, docnumber, duedate, balance, currencyref\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n AND duedate <= CURRENT_DATE - INTERVAL '30 days'\n ORDER BY duedate ASC LIMIT 100\n\n -- Due in next 30 days\n SELECT company_id, customerref_name, docnumber, duedate, balance\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n AND duedate >= CURRENT_DATE AND duedate <= CURRENT_DATE + INTERVAL '30 days'\n ORDER BY duedate ASC LIMIT 100\n\n -- AR aging summary\n SELECT company_id, customerref_name, SUM(balance) AS total_balance\n FROM view_invoices_open\n WHERE company_id IN ('MUS','MMX','MSAG')\n GROUP BY company_id, customerref_name\n ORDER BY total_balance DESC LIMIT 20",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_invoices_open. MUST include WHERE and LIMIT (<=500). txndate and duedate are native DATE — use CURRENT_DATE arithmetic, never quoted strings.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16224,
|
||
7456
|
||
],
|
||
"id": "f0df476e-8098-4f20-8a0b-5ed08eb2dc14",
|
||
"name": "Postgres Quickbooks open invoices",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the view_payments Postgres view for QuickBooks payments across all entities.\n\nCRITICAL: ALL columns except company_id are mixed-case — MUST be double-quoted.\n\nColumn types:\n \"TxnDate\" — native DATE. Never compare to text string.\n RIGHT: \"TxnDate\" >= CURRENT_DATE - INTERVAL '7 days'\n WRONG: \"TxnDate\" >= '2026-05-13' ← TYPE ERROR\n \"TotalAmt\" — numeric\n \"IsCustomerPayment\" — boolean\n All others — text\n\nColumns: company_id, \"TxnDate\", \"CustomerRef_Name\", \"TotalAmt\", \"CurrencyRef\", \"IsCustomerPayment\", \"PaymentMethodDerived\", \"PaymentRefNum\"\n\nExample:\n SELECT company_id, \"TxnDate\", \"CustomerRef_Name\", \"TotalAmt\", \"CurrencyRef\"\n FROM view_payments\n WHERE \"IsCustomerPayment\" = true\n AND \"TxnDate\" >= CURRENT_DATE - INTERVAL '7 days'\n AND company_id IN ('MUS','MMX','MSAG')\n ORDER BY \"TxnDate\" DESC LIMIT 100\n\nDSO: both \"TxnDate\" and txndate (from view_invoices_open) are native DATE — subtraction works directly, no cast needed.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_payments. MUST include WHERE and LIMIT (<=500). All mixed-case columns must be double-quoted. \"TxnDate\" is native DATE — use CURRENT_DATE arithmetic, never quoted strings.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16384,
|
||
7456
|
||
],
|
||
"id": "a00978e0-c87f-4a9b-b1ab-b206523a0a15",
|
||
"name": "Postgres Quickbooks customer payments",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the AI_view_general_ledger Postgres view for GL, P&L, revenue, expenses.\n\"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE arithmetic only, never quoted strings.\nRevenue posts negative — use ABS(SUM(amount)).\nFor expenses: SUM(amount) for net (handles reversals). Never SUM(\"Debit\") alone.\nMust include WHERE and LIMIT (<=500). Double-quote all mixed-case columns.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against \"AI_view_general_ledger\". Must include WHERE and LIMIT (<=500). \"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE arithmetic, NEVER quoted string literals like \\'2026-01-01\\'. Double-quote all mixed-case column names.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16656,
|
||
7456
|
||
],
|
||
"id": "cc2e4931-5daf-4c5a-b959-8752835d444f",
|
||
"name": "Postgres Quickbooks general ledger",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query HubSpot deals and closed-won data from Postgres. Use view_hubspot_closedwon for closed-won deals and billing schedules. Use hubspot_deals for pipeline/win-loss analysis.\n\n### view_hubspot_closedwon\nAll lowercase. Already filtered to is_closed_won = true — do NOT add again.\ncontract_start_date / contract_end_date are native DATE — no cast needed.\nALWAYS include deal_url when presenting results.\n\nColumns: deal_id, deal_name, amount, amount_in_home_currency, deal_currency_code,\n close_date, contract_start_date, contract_end_date, contract_term,\n primary_associated_company, company_name, icon_alias, icon_id, icon_client_name,\n client_tier, health_status, renewal_date, churn_notice_date, churn_effective_date,\n onboarding_owner, client_success_manager, masttro_invoicing_region,\n new_pricing_model, deal_url, owner_id\n\n### hubspot_deals\nAll lowercase. Key columns: deal_id, deal_name, amount, amount_in_home_currency,\n close_date, deal_stage, pipeline, owner_id, is_closed_won, is_closed_lost,\n deal_type, contract_start_date, contract_end_date, contract_term,\n churn_date, churn_notice_date, primary_associated_company\nNote: no deal_url or deal_currency_code here — use view_hubspot_closedwon for those.\nColumn is deal_name — never dealname.\namount_in_home_currency is USD — use for cross-currency aggregations.\n\n### hubspot_owners\nColumns: owner_id, firstname, lastname, email\nJOIN: LEFT JOIN hubspot_owners o ON d.owner_id = o.owner_id\n\nBilling schedule standard query:\n SELECT deal_name, primary_associated_company, contract_term,\n contract_start_date, contract_end_date,\n amount, amount_in_home_currency, deal_currency_code, deal_id, deal_url\n FROM view_hubspot_closedwon\n WHERE contract_start_date <= (DATE_TRUNC('year', NOW()) + INTERVAL '1 year - 1 day')::date\n AND (contract_end_date >= DATE_TRUNC('year', NOW())::date OR contract_end_date IS NULL)\n ORDER BY contract_start_date ASC",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against view_hubspot_closedwon, hubspot_deals, or hubspot_owners. MUST include WHERE and LIMIT (<=500).', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17152,
|
||
4912
|
||
],
|
||
"id": "35c66d65-cc08-4358-946e-5762e372f41c",
|
||
"name": "Hubspot deals",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🏦 MHAG & MGC — Journal Entry Only\nMHAG realm: 9341452561201469 (CHF)\nMGC realm: 9130354300064436 (USD)\n\nThese entities have **no invoice or customer tools**.\nAvailable: Create Journal Entry · delete Journal Entry · Query QBO (QBQL)\n\n> Use Query QBO to find Account IDs before posting.\n> MHAG amounts are CHF — convert via Get FX Rate before reporting.",
|
||
"height": 260,
|
||
"width": 1684,
|
||
"color": 7
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
7712
|
||
],
|
||
"id": "d59faace-9090-4754-bdb6-8959ae321347",
|
||
"name": "Sticky MHAG MGC"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MHAG for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16736,
|
||
7792
|
||
],
|
||
"id": "3b159143-6489-4afa-923d-25784b80d894",
|
||
"name": "MHAG - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MHAG QuickBooks (realm 9341452561201469). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16176,
|
||
7792
|
||
],
|
||
"id": "82a645c5-0635-4810-9912-0831f6dfc6f6",
|
||
"name": "MHAG - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MHAG. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452561201469/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16928,
|
||
7792
|
||
],
|
||
"id": "8a310b53-142a-4d42-867c-d692fd8edd4d",
|
||
"name": "MHAG - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "vE8iWZDtmiPKgGao",
|
||
"name": "QuickBooks Online MHAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MGC for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim();\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16368,
|
||
7792
|
||
],
|
||
"id": "c8e3f85b-6de4-4cee-bf17-e6273d4a83e1",
|
||
"name": "MGC - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MGC QuickBooks (realm 9130354300064436). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16560,
|
||
7792
|
||
],
|
||
"id": "930fb4f2-58f1-4494-bcd7-175802124a82",
|
||
"name": "MGC - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MGC. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9130354300064436/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17120,
|
||
7792
|
||
],
|
||
"id": "4036d440-4e01-4ec7-bf11-d4ff0b157343",
|
||
"name": "MGC - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "bm9Oc2QzBM8bx9YW",
|
||
"name": "QuickBooks Online MGC"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"mode": "retrieve-as-tool",
|
||
"toolDescription": "Contract terms, payment schedules, billing frequency, SLA, termination clauses, effective dates — Postgres PGVector contract store.\n\nCRITICAL: Always use specific clause language in queries, NOT generic client+contract phrases.\nGOOD queries: \"semi-annual invoice payment schedule\", \"annual license fee payment timing\", \"termination notice period\"\nBAD queries: \"49 management contract terms\", \"client contract\"\n\nUse this tool first for any contract lookup. If results return chunks from the wrong client, immediately fall back to Contract_Direct_Lookup.",
|
||
"tableName": "contracts",
|
||
"topK": 10,
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.vectorStorePGVector",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
17184,
|
||
4704
|
||
],
|
||
"id": "2f902fe9-23ea-4ebb-b953-c22f98cb9ee1",
|
||
"name": "Postgres PGVector Store",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "YrHntSw7KF7Za0tK",
|
||
"name": "Postgres vectors"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.embeddingsMistralCloud",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17296,
|
||
4912
|
||
],
|
||
"id": "545fd8be-c97c-41f2-9d72-963bbbc15c74",
|
||
"name": "mistral-embed",
|
||
"credentials": {
|
||
"mistralCloudApi": {
|
||
"id": "lHc1GsczHEq7NL55",
|
||
"name": "Mistral - contracts vector"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Direct SQL fallback for contract lookups. Use when pgvector returns chunks from the wrong client.\n\nTable: contracts (never vectors.contracts or public.contracts)\nColumns: id, text, metadata, client_name, contract_type, effective_date, entity, currency, section_heading, page_number, filename\nUse page_number = 1 to get one row per contract.\n\nExample:\n SELECT text, section_heading, page_number FROM contracts\n WHERE client_name ILIKE '%49 Management%'\n ORDER BY page_number ASC, id ASC LIMIT 50\n\nFor contract list:\n SELECT client_name, contract_type, entity, currency, effective_date\n FROM contracts WHERE page_number = 1\n ORDER BY client_name ASC LIMIT 200",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against the contracts table. Always filter by client_name using ILIKE. Always include ORDER BY page_number ASC, id ASC and a LIMIT.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16992,
|
||
4912
|
||
],
|
||
"id": "710e5a0d-2688-4335-80a8-74bf56246f7a",
|
||
"name": "Contract_Direct_Lookup",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Look up QuickBooks account IDs from the chartofaccounts Postgres table. ALWAYS use this before creating any journal entry — never use QBQL Account queries for ID lookups.\n\nKey columns: company_id, account_id (text — pass as-is in AccountRef), \"Name\", \"AcctNum\", \"Active\", \"Classification\", \"AccountType\"\n\nRules:\n- account_id is text — never cast it\n- Always filter by company_id\n- For multi-entity JEs: run one query per entity or use company_id IN (...)\n\nLookup by AcctNum:\n SELECT company_id, account_id, \"Name\", \"AcctNum\", \"Classification\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND \"AcctNum\" ILIKE '%41440%'\n LIMIT 10\n\nLookup by name:\n SELECT company_id, account_id, \"Name\", \"AcctNum\", \"Classification\", \"AccountType\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND \"Name\" ILIKE '%Bonus Expense%'\n LIMIT 10\n\nLookup multiple AcctNums at once:\n SELECT company_id, account_id, \"Name\", \"AcctNum\"\n FROM chartofaccounts\n WHERE company_id = 'MUS' AND \"Active\" = true AND (\"AcctNum\" ILIKE '%41440%' OR \"AcctNum\" ILIKE '%11210%')\n LIMIT 20\n\nLookup across entities:\n SELECT company_id, account_id, \"Name\", \"AcctNum\"\n FROM chartofaccounts\n WHERE company_id IN ('MUS','MMX','MSAG') AND \"Active\" = true AND \"AcctNum\" ILIKE '%41440%'\n LIMIT 20",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against chartofaccounts. Always filter by company_id. Always include LIMIT. Double-quote mixed-case columns: \"Name\", \"AcctNum\", \"Active\", \"Classification\", \"AccountType\". account_id is lowercase and unquoted.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
16912,
|
||
7456
|
||
],
|
||
"id": "2def1f16-acee-43f9-a6b3-29e86331cac7",
|
||
"name": "Quickbooks Chart of Accounts MGC MUS MMX MSAG MHAG",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/chats?$filter=chatType eq 'oneOnOne'&$expand=members&$select=id,chatType&$top=50",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17584,
|
||
4768
|
||
],
|
||
"id": "df489b7b-148f-400d-a1c0-1763df7e6b7a",
|
||
"name": "Get Teams DM Chats — returns all 1:1 chats with id and members. Search members for displayName or email matching the target person. Use chatId with Send Teams DM.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/chats?$filter=chatType eq 'group'&$expand=members&$select=id,chatType,topic&$top=50",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17856,
|
||
4928
|
||
],
|
||
"id": "537dd0af-08ea-4de3-b457-0c1378ccab70",
|
||
"name": "Get Teams Group Chats — returns all group chats with id, topic, and members. Match by topic name or member displayName. Use chatId with Send Teams DM.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://graph.microsoft.com/v1.0/me/joinedTeams?$select=id,displayName",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17584,
|
||
4928
|
||
],
|
||
"id": "217303ac-0fc1-44f8-b035-ed629cfc734a",
|
||
"name": "Get Teams List — returns all joined teams with id and displayName. Call this first to resolve a team name to a teamId before calling Get Teams Channels.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "=https://graph.microsoft.com/v1.0/teams/{{ $fromAI('teamId', 'The team GUID from Get Teams List matching the target team displayName', 'string') }}/channels?$select=id,displayName",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "microsoftTeamsOAuth2Api",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17824,
|
||
4768
|
||
],
|
||
"id": "49d75f49-785a-49f1-a28a-2b623d2d1faf",
|
||
"name": "Get Teams Channels — given a teamId from Get Teams List, returns all channels with id and displayName. Use channelId and teamId together with Send Teams Channel Message.",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "chatMessage",
|
||
"chatId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('chatId', 'The Teams chat GUID resolved from Get Teams DM Chats or Get Teams Group Chats. For 1:1 chats, find the chat where the member whose userId is NOT 94387110-efa9-4e7f-8f18-3332df67c0a9 matches the target by email or displayName. Never guess — always resolve first.', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"message": "={{ $fromAI('message', 'Plain text message to send. No markdown tables. Use line breaks for structure. Summarize large datasets and offer export links rather than pasting rows.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.microsoftTeamsTool",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
18016,
|
||
4752
|
||
],
|
||
"id": "7680071f-4d20-45b1-903c-cf4d3d56bf9d",
|
||
"name": "Send Teams DM",
|
||
"webhookId": "d0bf3351-3696-4ea0-adbd-ddec03926981",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "channelMessage",
|
||
"teamId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('teamId', 'The team GUID from Get Teams List matching the target team displayName', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"channelId": {
|
||
"__rl": true,
|
||
"value": "={{ $fromAI('channelId', 'The channel GUID from Get Teams Channels matching the target channel displayName', 'string') }}",
|
||
"mode": "id"
|
||
},
|
||
"message": "={{ $fromAI('message', 'Plain text message to post. No markdown tables. Use line breaks for structure. Summarize large datasets and offer export links rather than pasting rows.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.microsoftTeamsTool",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
18048,
|
||
4912
|
||
],
|
||
"id": "03b2b68c-19a6-4cbe-ac06-ee2e3749691b",
|
||
"name": "Send Teams Channel Message",
|
||
"webhookId": "fb75f216-5c71-4ea0-867b-90abb819a51c",
|
||
"credentials": {
|
||
"microsoftTeamsOAuth2Api": {
|
||
"id": "VGkMYU2FoGJszbIJ",
|
||
"name": "Microsoft Teams account - Mike"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {
|
||
"PrimaryEmailAddr": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Primary_Email_Address', ``, 'string') }}",
|
||
"PrimaryPhone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Primary_Phone', ``, 'string') }}"
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16896,
|
||
6800
|
||
],
|
||
"id": "589485a7-2896-4635-8324-713bc9e2d93f",
|
||
"name": "MSAG - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Query the \"view_balancesheet\" Postgres view for balance sheet data — Assets, Liabilities, and Equity as of a specific date.\n\nThis view represents CUMULATIVE balances (sum of all transactions up to a point in time), not period activity. Use this for: account balances, net assets, total liabilities, equity position, cash balances, AR/AP balances, and any question about what something IS WORTH or OWES as of a date.\n\nFull column list:\n company_id (text), realm_id (text), report_name (text),\n start_period (text), end_period (text), currency (text),\n accounting_method (text), report_run_at (timestamptz),\n section_path (text), row_group (text), row_type (text),\n row_label (text), account_id (text), depth (integer),\n col_title (text), col_start_date (text), col_end_date (text),\n col_amount (numeric), \"Account Key\" (text), account_name (text)\n\nKEY CONCEPTS:\n - col_amount is the balance AS OF col_end_date (cumulative, not period delta)\n - section_path identifies the BS section: 'Assets', 'Liabilities', 'Equity'\n - row_type = 'Data' for actual account rows; always filter row_type = 'Data' to avoid double-counting totals/headers\n - row_label = human-readable account name\n - depth = hierarchy level (lower = summary, higher = detail)\n - col_end_date is TEXT stored as YYYY-MM-DD — always cast with ::date for comparisons\n\nDATE FILTERING (col_end_date is TEXT — always cast):\n RIGHT: col_end_date::date <= CURRENT_DATE\n RIGHT: col_end_date::date = DATE_TRUNC('month', NOW())::date - INTERVAL '1 day'\n WRONG: col_end_date <= '2026-01-01' ← text comparison, wrong sort order\n\nTo get the most recent snapshot only:\n Use ORDER BY report_run_at DESC LIMIT N, or filter col_end_date::date = (SELECT MAX(col_end_date::date) FROM view_balancesheet WHERE company_id = '...')\n\nCOMMON QUERY TEMPLATES:\n\nLatest balance sheet detail by section:\n SELECT company_id, section_path, row_label, col_amount, col_end_date\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n ORDER BY company_id, section_path, depth, row_label\n LIMIT 500\n\nTotal assets / liabilities / equity summary:\n SELECT company_id, section_path, SUM(col_amount) AS total\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n GROUP BY company_id, section_path\n ORDER BY company_id, section_path\n LIMIT 100\n\nSpecific account balance (e.g. cash):\n SELECT company_id, row_label, col_amount, col_end_date\n FROM view_balancesheet\n WHERE row_type = 'Data'\n AND row_label ILIKE '%cash%'\n AND company_id IN ('MUS','MMX','MSAG','MHAG','MGC')\n AND col_end_date::date <= CURRENT_DATE\n ORDER BY company_id, col_end_date::date DESC\n LIMIT 100\n\nCURRENCY:\n MSAG and MHAG report in CHF. After any query returning CHF rows: call Get FX Rate (fromCurrency=CHF),\n multiply col_amount by rate, report in USD with note \"converted from CHF at X CHF/USD\".\n\nDO NOT use this view for: revenue, expenses, P&L, or period-over-period income — use AI_view_general_ledger instead.\nDO NOT SUM rows where row_type != 'Data' — header and total rows double-count.",
|
||
"operation": "executeQuery",
|
||
"query": "{{ $fromAI('sql', 'A complete SELECT against \"view_balancesheet\". Must include WHERE and LIMIT (<=500). col_end_date is TEXT — always cast with ::date for date comparisons. Double-quote mixed-case columns like \"Account Key\". Always filter row_type = \\'Data\\' unless intentionally pulling summary totals.', 'string') }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17088,
|
||
7456
|
||
],
|
||
"id": "8177c53a-6296-4b5a-a383-bc1fc1787d59",
|
||
"name": "Postgres Quickbooks balance sheet",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MSAG (realm 9341452373926753, default currency CHF).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MSAG - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MSAG')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (CHF)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\nAfter posting, call Get FX Rate (fromCurrency=CHF) to convert the amount to USD for any reporting.\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16784,
|
||
7008
|
||
],
|
||
"id": "ec41cb54-6c99-4ddc-be76-bfb4fe61371c",
|
||
"name": "MSAG - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MSAG (realm 9341452373926753). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MSAG - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16640,
|
||
6864
|
||
],
|
||
"id": "19fde7fa-7a54-49f0-bbcc-45f7b23c4d81",
|
||
"name": "MSAG - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MMX (realm 9341452328017372). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MMX - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16992,
|
||
6480
|
||
],
|
||
"id": "439ff7c5-f393-4637-a3c7-1902e2d1194b",
|
||
"name": "MMX - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Void a customer payment in MUS (realm 9341452327761715). Use to reverse a payment that was posted in error.\n\nQBO does not delete payments — voiding zeroes the amount and marks it void while preserving the transaction record.\n\nSteps (all required):\n 1. Retrieve the payment Id and SyncToken via MUS - Query QBO (QBQL):\n SELECT Id, SyncToken, TxnDate, TotalAmt FROM Payment WHERE Id = '<id>' MAXRESULTS 1\n 2. Present the payment details to user and confirm before voiding\n 3. Call this tool with Id and SyncToken\n\nThe void is irreversible. Always confirm with user first.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/payment?minorversion=65&operation=void",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('payment_id', 'The QBO numeric Payment Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken — retrieve via QBQL first', 'string'), sparse: true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16992,
|
||
5824
|
||
],
|
||
"id": "b7152cba-ef41-4dcb-8779-1c9993e3c2c8",
|
||
"name": "MUS - Void Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MMX (realm 9341452328017372, default currency USD).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MMX - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MMX')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (USD)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16704,
|
||
6480
|
||
],
|
||
"id": "001d5dd2-5824-4219-95ca-c6f1fc855044",
|
||
"name": "MMX - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a customer payment in MUS (realm 9341452327761715, default currency USD).\n\nWorkflow (follow in order):\n 1. Resolve customerId via MUS - Find customer by name\n 2. Get open invoice Ids and balances via Postgres view_invoices_open (filter company_id = 'MUS')\n 3. Validate: totalAmt MUST equal sum of all line amounts — QBO will reject mismatches\n 4. Confirm payment details with user before posting\n\nParameters:\n customerId QBO numeric customer Id\n totalAmt Total payment amount (number) — must match sum of lines\n txnDate Payment date YYYY-MM-DD\n lines JSON array: [{\"invoiceId\": \"123\", \"amount\": 500.00}]\n Pass empty array [] to post as unapplied credit\n currencyRef ISO currency code — empty string for entity default (USD)\n paymentMethodRef Optional QBO payment method Id — empty string to omit\n depositToAccountRef Optional QBO bank account Id — empty string for Undeposited Funds\n privateNote Optional internal memo — empty string to omit\n\nAfter a successful POST (Payment.Id present in response), return \nonly: Payment.Id, TotalAmt, TxnDate, CustomerRef name, and linked \ninvoice Id. Do not re-fetch the payment or invoice. Ignore any \nerror fields present in the same response body — treat them as \nnon-fatal noise and never surface raw error JSON to the user.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/payment?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const customerId = $fromAI('customerId', 'QBO numeric customer Id', 'string');\n const totalAmt = $fromAI('totalAmt', 'Total payment amount as a number. MUST equal sum of all line amounts.', 'number');\n const txnDate = $fromAI('txnDate', 'Payment date YYYY-MM-DD', 'string');\n const currencyRef = $fromAI('currencyRef', 'ISO currency code. Pass empty string for entity default.', 'string');\n const paymentMethodRef = $fromAI('paymentMethodRef', 'Optional QBO payment method Id. Empty string to omit.', 'string');\n const depositToAccountRef = $fromAI('depositToAccountRef', 'Optional QBO bank account Id to deposit to. Empty string for Undeposited Funds.', 'string');\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n const linesRaw = $fromAI('lines', 'JSON array linking payment to invoices: [{\"invoiceId\": \"123\", \"amount\": 500.00}]. Empty array [] for unapplied credit.', 'string');\n const payload = { CustomerRef: { value: String(customerId) }, TotalAmt: Number(totalAmt), TxnDate: txnDate };\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n if (lines && lines.length > 0) { payload.Line = lines.map(l => ({ Amount: Number(l.amount), LinkedTxn: [{ TxnId: String(l.invoiceId), TxnType: 'Invoice' }] })); }\n if (currencyRef && currencyRef.trim()) { payload.CurrencyRef = { value: currencyRef.trim().toUpperCase() }; }\n if (paymentMethodRef && paymentMethodRef.trim()) { payload.PaymentMethodRef = { value: paymentMethodRef.trim() }; }\n if (depositToAccountRef && depositToAccountRef.trim()) { payload.DepositToAccountRef = { value: depositToAccountRef.trim() }; }\n if (privateNote && privateNote.trim()) { payload.PrivateNote = privateNote.trim(); }\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16688,
|
||
5776
|
||
],
|
||
"id": "dea99ea2-8e0c-4718-beb1-2f5eff72b58a",
|
||
"name": "MUS - Create Customer Payment",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🛠 Shared Tools — FX · EML · Collections · Risk Scoring · File Export\n\nCross-entity utilities available to the agent at all times.",
|
||
"height": 240,
|
||
"width": 1832,
|
||
"color": 5
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
5136
|
||
],
|
||
"id": "cd2b86b1-7002-4c75-a85f-82fc0daf7ab5",
|
||
"name": "Sticky Other Tools"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
5712
|
||
],
|
||
"id": "c8fe2dc6-a7f3-4919-a41e-c318fef0ac51",
|
||
"name": "MUS - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
5712
|
||
],
|
||
"id": "5ff329b4-a21a-4d36-9bc0-2f6434ca15cb",
|
||
"name": "MUS - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16752,
|
||
5520
|
||
],
|
||
"id": "a99476ad-e564-4cb3-bbd6-c869ca3df54b",
|
||
"name": "MUS - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const TAXABLE_STATES = ['TX', 'NY'];\n const c = $fromAI('currency', 'ISO currency code. MUS default is USD. Pass empty string to accept entity default.', 'string');\n const customerState = $fromAI('customerState', 'Two-letter US state code from customer BillAddr.CountrySubDivisionCode. Pass empty string if unknown.', 'string');\n\n const stateTaxCode = TAXABLE_STATES.includes((customerState || '').toUpperCase().trim()) ? 'TAX' : 'NON';\n\n const resolveTaxCode = (id) => {\n const map = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' };\n return map[String(id)] || stateTaxCode;\n };\n\n const p = {\n DocNumber: $fromAI('docNumber', 'The next sequential invoice number as a string', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'The numeric QuickBooks customer ID', 'string') },\n Line: JSON.parse($fromAI('lines', 'JSON array string of line items.', 'string')).map(l => ({\n Amount: l.amount ?? (l.qty * l.unitPrice),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty,\n UnitPrice: l.unitPrice,\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5872
|
||
],
|
||
"id": "3677c975-990e-4f83-9392-20afb217334d",
|
||
"name": "MUS - Create Invoice via QuickBooks API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Send a posted MUS invoice by email (realm 9341452327761715). ALWAYS have a literal email address before calling — look it up from the customer record if needed. Pass invoiceId (the QBO numeric Id, not DocNumber) and sendTo email address.",
|
||
"method": "POST",
|
||
"url": "=https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice/{{ $fromAI('invoiceId', 'The QBO numeric Invoice Id (not DocNumber) from a prior query', 'string') }}/send?sendTo={{ $fromAI('sendTo', 'Recipient email address — must be a real address, never empty', 'string') }}&minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/octet-stream"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
5872
|
||
],
|
||
"id": "33c6c517-4056-4493-a1cc-ae381492feb1",
|
||
"name": "MUS - Send Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a posted MUS invoice (realm 9341452327761715). Requires invoiceId and syncToken from a prior QBQL query. Uses sparse update — only fields you pass are changed. Pass lines as full replacement line array if changing line items.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/invoice?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('invoiceId', 'QBO numeric Invoice Id from prior query', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from prior query — required for updates', 'string'), sparse: true }; const txnDate = $fromAI('txnDate', 'Optional new transaction date YYYY-MM-DD, empty string to skip', 'string'); const dueDate = $fromAI('dueDate', 'Optional new due date YYYY-MM-DD, empty string to skip', 'string'); const privateNote = $fromAI('privateNote', 'Optional memo/note, empty string to skip', 'string'); const linesJson = $fromAI('lines', 'Optional JSON array of replacement line items (same format as Create Invoice). Pass empty string to leave lines unchanged.', 'string'); if (txnDate && txnDate.trim()) p.TxnDate = txnDate.trim(); if (dueDate && dueDate.trim()) p.DueDate = dueDate.trim(); if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim(); if (linesJson && linesJson.trim()) p.Line = JSON.parse(linesJson).map(l => ({ Amount: l.amount, DetailType: 'SalesItemLineDetail', Description: l.description, SalesItemLineDetail: { ItemRef: { value: String(l.itemId) }, Qty: l.qty, UnitPrice: l.unitPrice, TaxCodeRef: { value: String(l.taxCodeId) } } })); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
5872
|
||
],
|
||
"id": "50204815-05ef-44ba-b4c8-6bd270632103",
|
||
"name": "MUS - Update Invoice via API",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Creates a Journal Entry in QuickBooks Online MUS for manual financial adjustments. Requires: transaction date and line items (Account ID, Amount, Posting Type). Optional: Doc Number, Notes, Department, or Class IDs. Note: If a Document Number is provided, ensure it is unique; if it already exists in QBO, increment the number by 1 until a unique one is found before finalizing.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/journalentry?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const lines = JSON.parse($fromAI('lines', 'JSON array of journal entry lines. Each line: account_id, posting_type (Debit|Credit), amount, description, entity_type, entity_id, department_id, class_id. Debits MUST equal Credits.', 'string'));\n const txnDate = $fromAI('txn_date', 'Transaction date YYYY-MM-DD', 'string');\n const docNumber = $fromAI('doc_number', 'Optional reference/memo number', 'string');\n const privateNote = $fromAI('private_note', 'Optional internal memo', 'string');\n const currency = $fromAI('currency', 'ISO currency code. Pass empty string to accept default.', 'string');\n const payload = { TxnDate: txnDate, Line: lines.map(l => { const line = { Amount: l.amount, DetailType: 'JournalEntryLineDetail', Description: l.description || '', JournalEntryLineDetail: { PostingType: l.posting_type, AccountRef: { value: String(l.account_id) } } }; if (l.entity_type && l.entity_id) { line.JournalEntryLineDetail.Entity = { Type: l.entity_type, EntityRef: { value: String(l.entity_id) } }; } if (l.department_id && String(l.department_id).trim()) { line.JournalEntryLineDetail.DepartmentRef = { value: String(l.department_id).trim() }; } if (l.class_id && String(l.class_id).trim()) { line.JournalEntryLineDetail.ClassRef = { value: String(l.class_id).trim() }; } return line; }) };\n if (docNumber && docNumber.trim()) payload.DocNumber = docNumber.trim().slice(0, 21);\n if (privateNote && privateNote.trim()) payload.PrivateNote = privateNote.trim();\n if (currency && currency.trim()) payload.CurrencyRef = { value: currency.trim().toUpperCase() };\n return JSON.stringify(payload);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
5872
|
||
],
|
||
"id": "cfb624f1-4949-41ac-8f37-e657302347b1",
|
||
"name": "MUS - Create Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "delete a posted journal entry in MUS. ALWAYS retrieve Id and SyncToken first. Confirm with user before deleting.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/journalentry?minorversion=65&operation=delete",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('je_id', 'The QuickBooks JournalEntry Id', 'string'), SyncToken: $fromAI('sync_token', 'The SyncToken', 'string') }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16848,
|
||
5712
|
||
],
|
||
"id": "6be9cf50-13a3-4729-ae7c-fca8f475e339",
|
||
"name": "MUS - delete Journal Entry",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Exports query results to a CSV file and returns a download URL. Pass a complete SELECT SQL query and a filename.\n\nParameters:\n- filename: snake_case name without extension, e.g. hosting_expense_2026-05-23\n- sql: complete SELECT query — no LIMIT unless intentional. Follow all column quoting rules for each view.\n\nNever assemble rows in memory for export — always pass the SQL directly.\nFor GL exports: \"Date\" is native DATE — use DATE_TRUNC or CURRENT_DATE, never quoted string literals.",
|
||
"method": "POST",
|
||
"url": "http://10.116.0.2:5679/webhook/export-insert",
|
||
"sendBody": true,
|
||
"bodyParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "filename",
|
||
"value": "={{ $fromAI('filename', 'CSV filename without extension, snake_case, e.g. hosting_expense_2026-05-23') }}"
|
||
},
|
||
{
|
||
"name": "sql",
|
||
"value": "={{ $fromAI('sql', 'Complete SELECT SQL query to execute against the Postgres data warehouse') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.4,
|
||
"position": [
|
||
16464,
|
||
5232
|
||
],
|
||
"id": "818bb6ad-6bb5-420e-9998-39123c65a3f0",
|
||
"name": "Export_File"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MUS QuickBooks (realm 9341452327761715). ALWAYS include WHERE and MAXRESULTS.\n\nQBQL RULES: NEVER use IN(...), OR, nested parentheses, subqueries, JOINs, GROUP BY, aggregates.\nUse ORDERBY (not ORDER BY). Use MAXRESULTS (not LIMIT). Single quotes only.\nException: TxnDate date filters with = or >= and ISO date strings are valid.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
5552
|
||
],
|
||
"id": "d1aa28ce-a737-49a1-ac0f-f769972bd48f",
|
||
"name": "MUS - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MUS. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
5552
|
||
],
|
||
"id": "b4989e6a-a3d0-44dd-acdd-e8bcc1defa4b",
|
||
"name": "MUS - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MUS item(s) (products/services) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name to search for', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
5552
|
||
],
|
||
"id": "1c447868-154d-44d9-8b68-87c90e945f91",
|
||
"name": "MUS - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MUS customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches with Id, DisplayName, CompanyName, PrimaryEmailAddr.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name to search for', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5552
|
||
],
|
||
"id": "0354e1d0-97ef-4a40-9968-18da85b07a92",
|
||
"name": "MUS - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const { customer_id, entity } = $input.item.json;\n\nif (!customer_id || !entity) {\n return [{ json: { error: 'customer_id and entity are required' } }];\n}\n\nconst company_id = entity;\n\nconst arResult = await $db.executeQuery(\n `SELECT\n COALESCE(SUM(balance), 0)::NUMERIC AS open_ar,\n COALESCE(MAX(CURRENT_DATE - duedate), 0) AS max_dpd,\n COUNT(*) FILTER (WHERE duedate < CURRENT_DATE) AS overdue_count\n FROM view_invoices_open\n WHERE customerref_name ILIKE $1\n AND company_id = $2`,\n [`%${customer_id}%`, company_id]\n);\n\nconst paymentResult = await $db.executeQuery(\n `SELECT COUNT(*) AS late_payments\n FROM view_payments p\n JOIN view_invoices_open i\n ON p.company_id = i.company_id\n AND p.\"CustomerRef_Name\" = i.customerref_name\n WHERE p.\"CustomerRef_Name\" ILIKE $1\n AND p.company_id = $2\n AND p.\"IsCustomerPayment\" = true\n AND p.\"TxnDate\" >= CURRENT_DATE - INTERVAL '6 months'\n AND p.\"TxnDate\" > i.duedate`,\n [`%${customer_id}%`, company_id]\n);\n\nconst row = arResult.rows[0];\nconst openAr = parseFloat(row.open_ar) || 0;\nconst maxDpd = parseInt(row.max_dpd) || 0;\nconst overdueCount = parseInt(row.overdue_count) || 0;\nconst latePayments = parseInt(paymentResult.rows[0].late_payments) || 0;\n\nlet score = 0;\nconst flags = [];\n\nif (maxDpd > 90) { score += 40; flags.push('90+ days past due'); }\nelse if (maxDpd > 60) { score += 25; flags.push('60–90 days past due'); }\nelse if (maxDpd > 30) { score += 10; flags.push('30–60 days past due'); }\n\nif (openAr > 100000) { score += 25; flags.push(`High open AR: $${openAr.toLocaleString()}`); }\nelse if (openAr > 50000) { score += 15; flags.push(`Elevated open AR: $${openAr.toLocaleString()}`); }\nelse if (openAr > 10000) { score += 5; flags.push(`Moderate open AR: $${openAr.toLocaleString()}`); }\n\nif (overdueCount > 5) { score += 20; flags.push(`${overdueCount} overdue invoices`); }\nelse if (overdueCount > 2) { score += 10; flags.push(`${overdueCount} overdue invoices`); }\nelse if (overdueCount > 0) { score += 5; flags.push(`${overdueCount} overdue invoice`); }\n\nif (latePayments > 5) { score += 15; flags.push(`${latePayments} late payments in last 6 months`); }\nelse if (latePayments > 2) { score += 8; flags.push(`${latePayments} late payments in last 6 months`); }\nelse if (latePayments > 0) { score += 3; flags.push(`${latePayments} late payment in last 6 months`); }\n\nconst rating = score >= 60 ? 'HIGH' : score >= 30 ? 'MEDIUM' : 'LOW';\n\nconst action =\n rating === 'HIGH' ? 'Escalate to finance leadership. Use formal tone in any outreach.' :\n rating === 'MEDIUM' ? 'Send collection reminder. Flag for follow-up in 5 business days.' :\n 'Standard collections cycle. Friendly reminder tone appropriate.';\n\nreturn [{ json: {\n customer_id,\n company_id,\n score,\n rating,\n flags,\n action,\n detail: {\n open_ar: openAr,\n max_dpd: maxDpd,\n overdue_invoices: overdueCount,\n late_payments_6mo: latePayments\n }\n}}];"
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
16240,
|
||
5232
|
||
],
|
||
"id": "f5d62a0f-c0e3-4391-a40b-24484be8a2bf",
|
||
"name": "score_customer_risk"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"workflowId": {
|
||
"__rl": true,
|
||
"value": "H7My4glPDt56gXJz",
|
||
"mode": "list",
|
||
"cachedResultUrl": "/workflow/H7My4glPDt56gXJz",
|
||
"cachedResultName": "Collections Emails"
|
||
},
|
||
"workflowInputs": {
|
||
"mappingMode": "defineBelow",
|
||
"value": {},
|
||
"matchingColumns": [],
|
||
"schema": [],
|
||
"attemptToConvertTypes": false,
|
||
"convertFieldsToString": false
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
16064,
|
||
5216
|
||
],
|
||
"id": "da32d790-a179-416b-83ff-0281932b67f4",
|
||
"name": "Call 'MoneyBot — Collections Emails'",
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"name": "Generate_EML_File",
|
||
"description": "Generates a .eml email file and saves it to /files/emails/. Use when the user asks to create an email file, export an email, generate a .eml, or draft a payment reminder or invoice email as a downloadable file. Pass: to (recipient), from (sender), subject, body (plain text), cc (optional). Returns the saved file path.",
|
||
"workflowId": {
|
||
"__rl": true,
|
||
"value": "QWWFHJ57u8UvM11y",
|
||
"mode": "list",
|
||
"cachedResultUrl": "/workflow/QWWFHJ57u8UvM11y",
|
||
"cachedResultName": "Sub-Workflow EML generator"
|
||
},
|
||
"workflowInputs": {
|
||
"mappingMode": "defineBelow",
|
||
"value": {
|
||
"to": "={{ $fromAI('to', 'Recipient email address', 'string') }}",
|
||
"from": "={{ $fromAI('from', 'Sender email address', 'string') }}",
|
||
"subject": "={{ $fromAI('subject', 'Email subject line', 'string') }}",
|
||
"body": "={{ $fromAI('body', 'Plain text email body content', 'string') }}",
|
||
"cc": "={{ $fromAI('cc', 'Optional CC email address, empty string if none', 'string') }}"
|
||
},
|
||
"matchingColumns": [],
|
||
"schema": [
|
||
{
|
||
"id": "to",
|
||
"displayName": "to",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "from",
|
||
"displayName": "from",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "subject",
|
||
"displayName": "subject",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "body",
|
||
"displayName": "body",
|
||
"required": true,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
},
|
||
{
|
||
"id": "cc",
|
||
"displayName": "cc",
|
||
"required": false,
|
||
"defaultMatch": false,
|
||
"display": true,
|
||
"type": "string",
|
||
"canBeUsedToMatch": true
|
||
}
|
||
],
|
||
"attemptToConvertTypes": false,
|
||
"convertFieldsToString": true
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
15872,
|
||
5232
|
||
],
|
||
"id": "0a54f690-895c-47ef-90c3-3c328a1429d5",
|
||
"name": "Generate EML File",
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Fetch current exchange rates using exchangerate-api.com. Pass fromCurrency (e.g. CHF, USD) and the response contains rates to all major currencies. Use to convert MSAG/MHAG CHF amounts to USD for reporting.",
|
||
"url": "=https://api.exchangerate-api.com/v4/latest/{{ $fromAI('fromCurrency', 'Source currency code (e.g. CHF, USD, EUR)', 'string') || 'USD' }}",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "User-Agent",
|
||
"value": "n8n-workflow"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
5232
|
||
],
|
||
"id": "69a1e4e4-3b8b-46e8-b42f-39d741760c07",
|
||
"name": "Get FX Rate"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "create",
|
||
"displayName": "={{ $fromAI('Display_Name', `The customer's full name or company name`, 'string') }}",
|
||
"additionalFields": {}
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
16784,
|
||
6192
|
||
],
|
||
"id": "80640cac-e62e-40e7-aaf2-94408e5010f7",
|
||
"name": "MMX - Create customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "item",
|
||
"itemId": "={{ $fromAI('Item_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15936,
|
||
6352
|
||
],
|
||
"id": "692b658b-b4b5-4fae-ac94-d1494847fa84",
|
||
"name": "MMX - Get an item in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"customerId": "={{ $fromAI('Customer_ID', ``, 'string') }}"
|
||
},
|
||
"type": "n8n-nodes-base.quickbooksTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15680,
|
||
6352
|
||
],
|
||
"id": "3363a89a-35d6-41d8-948a-b6fe9294d8d2",
|
||
"name": "MMX - Get a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Run an arbitrary narrow QBQL SELECT against MMX QuickBooks (realm 9341452328017372). ALWAYS include WHERE and MAXRESULTS.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "={{ $fromAI('qbql', 'A narrow QBQL SELECT statement. Must include WHERE and MAXRESULTS (<=100).', 'string') }}"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16464,
|
||
6192
|
||
],
|
||
"id": "ebb80d71-b6ed-430c-bea9-32d7b636e543",
|
||
"name": "MMX - Query QBO (QBQL)",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Returns the single most recently created Invoice in MMX. Use to determine next sequential invoice number.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "SELECT DocNumber, TxnDate, MetaData FROM Invoice ORDERBY MetaData.CreateTime DESC MAXRESULTS 1"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16192,
|
||
6192
|
||
],
|
||
"id": "636e1e14-dd99-4708-8bf8-c75a6197058f",
|
||
"name": "MMX - Get latest invoice DocNumber",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MMX item(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, Name, Description, UnitPrice, Type, Active FROM Item WHERE Name LIKE '%{{ $fromAI('name_fragment', 'Substring of the item name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15936,
|
||
6192
|
||
],
|
||
"id": "72157b53-e739-47ad-87b4-a41aba1c7e9e",
|
||
"name": "MMX - Find item by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Find MMX customer(s) by name fragment using QBQL LIKE. Returns up to 20 matches.",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/query",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "query",
|
||
"value": "=SELECT Id, DisplayName, CompanyName, PrimaryEmailAddr, Active FROM Customer WHERE DisplayName LIKE '%{{ $fromAI('name_fragment', 'Substring of the customer name', 'string') }}%' MAXRESULTS 20"
|
||
},
|
||
{
|
||
"name": "minorversion",
|
||
"value": "65"
|
||
}
|
||
]
|
||
},
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
15680,
|
||
6192
|
||
],
|
||
"id": "cb91a632-0272-4ac7-88d3-23a9e73159db",
|
||
"name": "MMX - Find customer by name",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# 🇺🇸 MUS — QuickBooks US (USD)\nRealm: 9341452327761715\n\nRead: Find customer/item, get DocNumber, ad-hoc QBQL\nWrite: Create/update customer · Create/send/update invoice\n\n> Always look up TaxCode before creating invoices.",
|
||
"height": 580,
|
||
"width": 1652,
|
||
"color": 2
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
15632,
|
||
5440
|
||
],
|
||
"id": "c1bb76f7-c40e-48bc-a442-da54d7647010",
|
||
"name": "Sticky MUS"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# Freshdesk",
|
||
"height": 544,
|
||
"width": 656,
|
||
"color": 3
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17344,
|
||
5488
|
||
],
|
||
"id": "7cea2fb8-749f-40db-9760-5e634ac9de96",
|
||
"name": "Sticky Note1"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://masttro.freshdesk.com/api/v2/contacts/autocomplete",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendQuery": true,
|
||
"queryParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "term",
|
||
"value": "={{ $fromAI('contact_name', 'The name or partial name of the contact to search for. Examples: \"elsa peretti\", \"john\", \"smith\". Do not include field prefixes.', 'string') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.4,
|
||
"position": [
|
||
17408,
|
||
5648
|
||
],
|
||
"id": "a65faea6-3bef-4603-83c0-221173d303e2",
|
||
"name": "Freshdesk Contact Lookup - Finance",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Get many tickets in Freshdesk for Finance.\nWhen looking at open tickets, use status 2 and status 3 and check all batches.",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {
|
||
"include": [
|
||
"description",
|
||
"company",
|
||
"stats",
|
||
"requester"
|
||
]
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.freshdeskTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17520,
|
||
5808
|
||
],
|
||
"id": "b8cb4668-edd7-477c-9aa6-5b158ec16678",
|
||
"name": "Freshdesk - finance tickets",
|
||
"credentials": {
|
||
"freshdeskApi": {
|
||
"id": "CjhwO4a2iGx7J4v0",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"descriptionType": "manual",
|
||
"toolDescription": "Get many tickets in Freshdesk for customer support team.\nWhen looking at open tickets, use status 2 and status 3 and check all batches.",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {
|
||
"include": [
|
||
"description",
|
||
"company",
|
||
"stats",
|
||
"requester"
|
||
]
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.freshdeskTool",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17632,
|
||
5648
|
||
],
|
||
"id": "99daa1c9-ebc7-4d89-85bb-357fc65681f8",
|
||
"name": "Freshdesk - general support",
|
||
"credentials": {
|
||
"freshdeskApi": {
|
||
"id": "eU5oyV62tEJdY6dV",
|
||
"name": "Freshdesk - general support"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"authentication": "appToken",
|
||
"resource": "deal",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"filters": {}
|
||
},
|
||
"type": "n8n-nodes-base.hubspotTool",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
17072,
|
||
5200
|
||
],
|
||
"id": "129384cf-27a0-46c6-aee3-c451c4506353",
|
||
"name": "Hubspot all deals search",
|
||
"credentials": {
|
||
"hubspotAppToken": {
|
||
"id": "DJFxH3uski1VveCl",
|
||
"name": "HubSpot account"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"authentication": "appToken",
|
||
"resource": "company",
|
||
"operation": "getAll",
|
||
"returnAll": "=",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.hubspotTool",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
16896,
|
||
5200
|
||
],
|
||
"id": "c9e8afe7-9b53-4b70-8783-eb5f56842275",
|
||
"name": "Hubspot all companies search",
|
||
"credentials": {
|
||
"hubspotAppToken": {
|
||
"id": "DJFxH3uski1VveCl",
|
||
"name": "HubSpot account"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "SELECT\n id, excel_row_number, entity, deal_type, invoice_status,\n bill_freq, qb_item_type, close_date, bill_date, hs_customer,\n icon_id, qb_customer, qb_item_description, currency,\n amount_lcy, net_terms, tax, note\nFROM cust_fact_table\nWHERE\n UPPER(TRIM(invoice_status)) = 'UNBILLED'\n AND bill_date BETWEEN '{{ $fromAI('startDate', 'Start of bill date range in YYYY-MM-DD format', 'string') }}'::date\n AND '{{ $fromAI('endDate', 'End of bill date range in YYYY-MM-DD format', 'string') }}'::date\n AND entity IS NOT NULL\n AND amount_lcy IS NOT NULL\n AND amount_lcy <> 0\n AND COALESCE(run_status, '') <> 'created'\n AND COALESCE(on_hold, false) = false\nORDER BY entity, bill_date",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgresTool",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
17280,
|
||
5200
|
||
],
|
||
"id": "71698190-a071-4b15-b5c7-2bad605c033f",
|
||
"name": "Read Customer Fact Table for Billing",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "NoPrLWbqySMMmIaI",
|
||
"name": "Postgres n8n-quickbooks"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MUS customer (realm 9341452327761715). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17088,
|
||
5568
|
||
],
|
||
"id": "1f695e1a-d0a9-45a9-9d57-9e9049b7e13d",
|
||
"name": "MUS - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MMX customer (realm 9341452328017372). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17072,
|
||
6192
|
||
],
|
||
"id": "8e541a9d-1003-4e4d-9d7f-02083fcceac3",
|
||
"name": "MMX - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a MSAG customer (realm 9341452373926753). Requires Customer_ID and SyncToken from a prior query. Sparse update — only fields passed are changed. Supports: CompanyName, FullyQualifiedName, PrimaryEmailAddr, PrimaryPhone, Notes, BillAddr fields.\n\nMultiple email contacts can be added to PrimaryEmailAddr using comma separation i.e. 1@abc.com, 2@abc.com, 3@abc.com",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/customer?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { Id: $fromAI('Customer_ID', 'The QuickBooks ID of the customer to update', 'string'), SyncToken: $fromAI('SyncToken', 'SyncToken from a prior query — required for updates', 'string'), sparse: true }; const companyName = $fromAI('Company_Name', 'Optional new company name, empty to skip', 'string'); const fqn = $fromAI('Fully_Qualified_Name', 'Optional new fully qualified name, empty to skip', 'string'); const email = $fromAI('Primary_Email_Address', 'Optional new primary email, empty to skip', 'string'); const phone = $fromAI('Primary_Phone', 'Optional new primary phone, empty to skip', 'string'); const notes = $fromAI('Notes', 'Optional customer note (appears on profile and invoices, max 4000 chars), empty to skip', 'string'); const line1 = $fromAI('Line_1', 'Optional billing address line 1, empty to skip', 'string'); const city = $fromAI('City', 'Optional billing address city, empty to skip', 'string'); const postalCode = $fromAI('Postal_Code', 'Optional billing address postal code, empty to skip', 'string'); const state = $fromAI('Country_Subdivision_Code', 'Optional state/province code, empty to skip', 'string'); if (companyName && companyName.trim()) p.CompanyName = companyName.trim(); if (fqn && fqn.trim()) p.FullyQualifiedName = fqn.trim(); if (email && email.trim()) p.PrimaryEmailAddr = { Address: email.trim() }; if (phone && phone.trim()) p.PrimaryPhone = { FreeFormNumber: phone.trim() }; if (notes && notes.trim()) p.Notes = notes.trim(); if (line1 || city || postalCode || state) { p.BillAddr = {}; if (line1 && line1.trim()) p.BillAddr.Line1 = line1.trim(); if (city && city.trim()) p.BillAddr.City = city.trim(); if (postalCode && postalCode.trim()) p.BillAddr.PostalCode = postalCode.trim(); if (state && state.trim()) p.BillAddr.CountrySubDivisionCode = state.trim(); } return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17104,
|
||
6800
|
||
],
|
||
"id": "74d73667-2416-4e71-a9e7-cb8c9656404b",
|
||
"name": "MSAG - Update a customer in QuickBooks Online",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Update a Freshdesk Finance ticket. Use to change status, priority, or subject. Status codes: 2=Open, 3=Pending, 4=Resolved, 5=Closed. Always confirm before closing or resolving. Requires numeric ticket ID from a prior lookup.",
|
||
"method": "PUT",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/{{ $fromAI('ticket_id', 'Numeric Freshdesk ticket ID', 'string') }}",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = {}; const status = $fromAI('status', 'Ticket status: 2=Open, 3=Pending, 4=Resolved, 5=Closed. Pass 0 to skip.', 'number'); const priority = $fromAI('priority', 'Priority: 1=Low, 2=Medium, 3=High, 4=Urgent. Pass 0 to skip.', 'number'); const subject = $fromAI('subject', 'Optional new subject. Empty string to skip.', 'string'); if (status && status > 0) p.status = status; if (priority && priority > 0) p.priority = priority; if (subject && subject.trim()) p.subject = subject.trim(); return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17696,
|
||
5824
|
||
],
|
||
"id": "dfe291b7-c3c5-41a6-9916-f83200b97b8d",
|
||
"name": "Freshdesk - Update Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Add a note to a Freshdesk Finance ticket. Private notes are internal only (default). Public notes are visible to the requester. Always retrieve ticket ID via search before calling.",
|
||
"method": "POST",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/{{ $fromAI('ticket_id', 'Numeric Freshdesk ticket ID', 'string') }}/notes",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ body: $fromAI('body', 'Note content — plain text or HTML', 'string'), private: $fromAI('private', 'true for internal note, false for customer-visible', 'boolean') ?? true }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17840,
|
||
5616
|
||
],
|
||
"id": "8c7fabaa-a457-411e-b569-b24c0ae88fa6",
|
||
"name": "Freshdesk - Add Note to Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 2
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "pdf-mime-check",
|
||
"leftValue": "={{ $json.files?.[0]?.mimeType }}",
|
||
"rightValue": "application/pdf",
|
||
"operator": {
|
||
"type": "string",
|
||
"operation": "equals"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
15792,
|
||
4768
|
||
],
|
||
"id": "7acb2049-f340-43e8-a691-8feccc8be128",
|
||
"name": "PDF upload?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "merge-chatinput",
|
||
"name": "chatInput",
|
||
"value": "={{ $('When chat message received').item.json.chatInput }}\n\n--- Uploaded Document ---\n{{ $json.text }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "pass-session",
|
||
"name": "sessionId",
|
||
"value": "={{ $('When chat message received').item.json.sessionId }}",
|
||
"type": "string"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [
|
||
16176,
|
||
4496
|
||
],
|
||
"id": "4b87f084-42ce-4f0b-9e60-c0343d6419fa",
|
||
"name": "Merge PDF Text"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "pdf",
|
||
"binaryPropertyName": "={{ Object.keys($binary)[0] }}",
|
||
"options": {
|
||
"joinPages": true
|
||
}
|
||
},
|
||
"type": "n8n-nodes-base.extractFromFile",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
16000,
|
||
4512
|
||
],
|
||
"id": "2b54f2f0-d4ec-4ff2-9f26-bdd437b361d4",
|
||
"name": "Extract from File"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": false,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 1
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "excel-mime-check",
|
||
"leftValue": "={{ $json.files?.[0]?.mimeType }}",
|
||
"rightValue": "vnd\\.ms-excel|spreadsheetml",
|
||
"operator": {
|
||
"type": "string",
|
||
"operation": "regex"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.2,
|
||
"position": [
|
||
15968,
|
||
4768
|
||
],
|
||
"id": "ec0ff3a8-459a-47fd-9a0c-307aa8e90167",
|
||
"name": "Excel upload?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "xlsx",
|
||
"binaryPropertyName": "={{ Object.keys($binary)[0] }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.extractFromFile",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
16128,
|
||
4752
|
||
],
|
||
"id": "c683404a-97b6-4f03-a6f5-827536a28097",
|
||
"name": "Extract from File (XLSX)"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "merge-chatinput-xlsx",
|
||
"name": "chatInput",
|
||
"value": "={{ $('When chat message received').item.json.chatInput }}\n\n--- Uploaded Spreadsheet ---\n{{ JSON.stringify($json, null, 2) }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "pass-session-xlsx",
|
||
"name": "sessionId",
|
||
"value": "={{ $('When chat message received').item.json.sessionId }}",
|
||
"type": "string"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [
|
||
16304,
|
||
4640
|
||
],
|
||
"id": "5db4a7bc-0f54-48a0-8fed-4a64860d0efa",
|
||
"name": "Merge XLSX Data"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a new Freshdesk Finance ticket. Use when the user asks to create, open, or log a new ticket — NOT when adding to an existing one. Required: subject and description. Optional: priority (1=Low, 2=Medium, 3=High, 4=Urgent), status (2=Open default), email (requester email — omit if unknown), cc_emails (array of strings).\\n\\nAlways post description as HTML for good formatting.\\nDo NOT use this to update or comment on existing tickets — use Freshdesk - Update Ticket or Freshdesk - Add Note to Ticket instead.",
|
||
"method": "POST",
|
||
"url": "=https://masttro.freshdesk.com/api/v2/tickets/",
|
||
"authentication": "genericCredentialType",
|
||
"genericAuthType": "httpBasicAuth",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => { const p = { subject: $fromAI('subject', 'Ticket subject line', 'string'), description: $fromAI('description', 'Ticket body — HTML preferred', 'string'), status: $fromAI('status', '2=Open, 3=Pending', 'number') || 2, priority: $fromAI('priority', '1=Low, 2=Medium, 3=High, 4=Urgent', 'number') || 2 }; const email = $fromAI('email', 'Requester email. Empty string for internal tickets.', 'string'); p.email = (email && email.trim()) ? email.trim() : 'mnolan@masttro.com'; return JSON.stringify(p); })() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17856,
|
||
5808
|
||
],
|
||
"id": "6525ca16-86a3-4b47-a5d1-85db4142157e",
|
||
"name": "Freshdesk - Create Ticket",
|
||
"credentials": {
|
||
"httpBasicAuth": {
|
||
"id": "IKEtEPEGfb3MGzuf",
|
||
"name": "Freshdesk - Finance"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Download a QBO invoice as a PDF and return a clickable download link. Works for MUS, MMX, and MSAG. Pass entity (MUS, MMX, or MSAG) and invoiceId (QBO numeric Id, not DocNumber). Optionally pass a filename. After calling, return the message field verbatim — it contains the clickable download link.",
|
||
"method": "POST",
|
||
"url": "http://10.116.0.2:5679/webhook/invoice-pdf",
|
||
"sendBody": true,
|
||
"bodyParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "entity",
|
||
"value": "={{ $fromAI('entity', 'Entity code: MUS, MMX, or MSAG', 'string') }}"
|
||
},
|
||
{
|
||
"name": "invoiceId",
|
||
"value": "={{ $fromAI('invoiceId', 'QBO numeric Invoice Id (not DocNumber)', 'string') }}"
|
||
},
|
||
{
|
||
"name": "filename",
|
||
"value": "={{ $fromAI('filename', 'Optional filename e.g. invoice_MUS_1234.pdf — empty string for auto', 'string') }}"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16704,
|
||
5216
|
||
],
|
||
"id": "58a640c1-a7ff-4c5e-8ff8-c84180fc81e8",
|
||
"name": "Download Invoice PDF"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MUS (realm 9341452327761715, USD). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MUS - Find customer by name\n 2. Resolve itemId via MUS - Find item by name\n 3. Get next DocNumber: MUS - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. Look up taxCodeId: TX or NY state → 'TAX', all others → 'NON'\n 5. Confirm details with user before posting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const TAXABLE_STATES = ['TX', 'NY'];\n const customerState = $fromAI('customerState', 'Two-letter US state code from customer BillAddr.CountrySubDivisionCode. Empty string if unknown.', 'string');\n const stateTaxCode = TAXABLE_STATES.includes((customerState || '').toUpperCase().trim()) ? 'TAX' : 'NON';\n const resolveTaxCode = (id) => { const map = { 'TAX': 'TAX', 'NON': 'NON', '1': 'TAX', '2': 'NON' }; return map[String(id)] || stateTaxCode; };\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice, taxCodeId.', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: resolveTaxCode(l.taxCodeId) }\n }\n }))\n };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17168,
|
||
5840
|
||
],
|
||
"id": "2b254fe5-6224-466b-80df-a506beea942d",
|
||
"name": "MUS - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MMX (realm 9341452328017372, USD). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MMX - Find customer by name\n 2. Resolve itemId via MMX - Find item by name\n 3. Get next DocNumber: MMX - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. MMX customers are non-US — always pass taxCodeId = 'NON'\n 5. Confirm details with user before posting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice. taxCodeId always NON for MMX.', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: 'NON' }\n }\n }))\n };\n const c = $fromAI('currency', 'ISO currency code. Empty string for entity default (USD).', 'string');\n if (c && c.trim()) p.CurrencyRef = { value: c.trim().toUpperCase() };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17152,
|
||
6368
|
||
],
|
||
"id": "a61dde4f-5a00-491a-b3b3-3161b3adf864",
|
||
"name": "MMX - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Create a credit memo in MSAG (realm 9341452373926753, CHF). Use when issuing a credit, refund, or adjustment against a customer account — NOT an invoice.\n\nWorkflow:\n 1. Resolve customerId via MSAG - Find customer by name\n 2. Resolve itemId via MSAG - Find item by name\n 3. Get next DocNumber: MSAG - Get latest invoice DocNumber → +1 → collision-check CreditMemo + Estimate\n 4. Query TaxCode (SELECT Id, Name FROM TaxCode WHERE Active = true MAXRESULTS 50) to resolve Swiss VAT code — never hardcode\n 5. Confirm details with user before posting\n 6. After posting, call Get FX Rate (fromCurrency=CHF) to convert amount to USD for reporting\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, TotalAmt, TxnDate, CustomerRef name. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ (() => {\n const linesRaw = $fromAI('lines', 'JSON array of line items. Each: amount, itemId, description, qty, unitPrice, taxCodeId (QBO TaxCode Id from TaxCode query — never null or hardcoded).', 'string');\n const lines = typeof linesRaw === 'string' ? JSON.parse(linesRaw) : linesRaw;\n const p = {\n DocNumber: $fromAI('docNumber', 'Next sequential DocNumber as a string', 'string'),\n TxnDate: $fromAI('txnDate', 'Credit memo date YYYY-MM-DD', 'string'),\n CustomerRef: { value: $fromAI('customerId', 'QBO numeric customer Id', 'string') },\n Line: lines.map(l => ({\n Amount: Number(l.amount),\n DetailType: 'SalesItemLineDetail',\n Description: l.description,\n SalesItemLineDetail: {\n ItemRef: { value: String(l.itemId) },\n Qty: l.qty ?? 1,\n UnitPrice: Number(l.unitPrice),\n TaxCodeRef: { value: (l.taxCodeId && String(l.taxCodeId).trim()) ? String(l.taxCodeId).trim() : 'NON' }\n }\n }))\n };\n const privateNote = $fromAI('privateNote', 'Optional memo. Empty string to omit.', 'string');\n if (privateNote && privateNote.trim()) p.PrivateNote = privateNote.trim();\n return JSON.stringify(p);\n})() }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
17136,
|
||
7120
|
||
],
|
||
"id": "0749b243-fada-44d0-a213-fda59f446ea3",
|
||
"name": "MSAG - Create Credit Memo",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"contextWindowLength": 10
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||
"typeVersion": 1.4,
|
||
"position": [
|
||
16832,
|
||
4928
|
||
],
|
||
"id": "05180f32-ec76-4a12-9549-e40906cb9e80",
|
||
"name": "Simple Memory"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MUS (realm 9341452327761715). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a $0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MUS - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MUS - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452327761715/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16912,
|
||
5488
|
||
],
|
||
"id": "0136e7bc-e3bc-42cd-a20c-b23c2a26b6dc",
|
||
"name": "MUS - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "i6dFL9yjAAhFxsmn",
|
||
"name": "QuickBooks Online MUS"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MMX (realm 9341452328017372). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a $0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MMX - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MMX - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452328017372/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{
|
||
"name": "Content-Type",
|
||
"value": "application/json"
|
||
},
|
||
{
|
||
"name": "Accept",
|
||
"value": "application/json"
|
||
}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16944,
|
||
6112
|
||
],
|
||
"id": "ca1e597e-1b04-4202-9fdd-be57905e7f57",
|
||
"name": "MMX - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "USLCXCphdJqcpr2C",
|
||
"name": "QuickBooks Online MMX"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"toolDescription": "Apply a credit memo to an open invoice in MSAG (realm 9341452373926753). Use when a credit memo exists and needs to be matched against an outstanding invoice — resulting in a CHF 0 payment that closes both.\n\nWorkflow (all steps required):\n 1. Retrieve Credit Memo Id and SyncToken via MSAG - Query QBO (QBQL):\n SELECT Id, SyncToken, DocNumber, TotalAmt, RemainingCredit FROM CreditMemo WHERE Id = '<id>' MAXRESULTS 1\n 2. Retrieve the Invoice Id via Postgres view_invoices_open or MSAG - Query QBO (QBQL)\n 3. Present credit memo and invoice details to user and confirm before applying\n 4. Call this tool with creditMemoId, syncToken, and invoiceId\n\nThis posts a sparse update to the credit memo with a LinkedTxn pointing to the invoice. QBO will net the credit against the invoice balance automatically.\n\nAfter a successful POST (CreditMemo.Id present in response), return only: CreditMemo.Id, DocNumber, RemainingCredit, and the linked Invoice Id. Then call Get FX Rate (fromCurrency=CHF) to convert any amounts to USD for reporting. Do not surface raw response JSON.",
|
||
"method": "POST",
|
||
"url": "https://quickbooks.api.intuit.com/v3/company/9341452373926753/creditmemo?minorversion=65",
|
||
"authentication": "predefinedCredentialType",
|
||
"nodeCredentialType": "quickBooksOAuth2Api",
|
||
"sendHeaders": true,
|
||
"headerParameters": {
|
||
"parameters": [
|
||
{}
|
||
]
|
||
},
|
||
"sendBody": true,
|
||
"contentType": "raw",
|
||
"rawContentType": "application/json",
|
||
"body": "={{ JSON.stringify({ Id: $fromAI('creditMemoId', 'The QBO numeric Credit Memo Id — retrieve via QBQL first', 'string'), SyncToken: $fromAI('syncToken', 'SyncToken from a prior QBQL query — required for updates', 'string'), sparse: true, Line: [{ Amount: $fromAI('amount', 'Amount to apply — must equal the lesser of the credit memo RemainingCredit and the invoice balance', 'number'), LinkedTxn: [{ TxnId: $fromAI('invoiceId', 'The QBO numeric Invoice Id to apply the credit memo against', 'string'), TxnType: 'Invoice' }] }] }) }}",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.httpRequestTool",
|
||
"typeVersion": 4.2,
|
||
"position": [
|
||
16960,
|
||
7136
|
||
],
|
||
"id": "13fdf991-1331-4e62-8a17-6bd74143be8e",
|
||
"name": "MSAG - Apply Credit Memo to Invoice",
|
||
"credentials": {
|
||
"quickBooksOAuth2Api": {
|
||
"id": "Q0bvGHliYfQTGUWW",
|
||
"name": "QuickBooks Online MSAG"
|
||
}
|
||
},
|
||
"disabled": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"content": "# Microsoft Teams",
|
||
"height": 448,
|
||
"width": 768
|
||
},
|
||
"type": "n8n-nodes-base.stickyNote",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17504,
|
||
4640
|
||
],
|
||
"id": "03baead9-ff4f-47bb-8dd8-48020cfa5834",
|
||
"name": "Sticky Note2"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"public": true,
|
||
"initialMessages": "💰 MoneyBot (READ-ONLY) online.\nI can check QBO customers, find real time FX rates, query Hubspot deals (closed/won), risk score customers with open invoices, create CSV files and query 1,200+ customer contracts and amendments in memory. I can export invoices to PDF.\n\nYou can upload images, XLSX or PDFs for review.",
|
||
"availableInChat": true,
|
||
"agentName": "Moneybot",
|
||
"options": {
|
||
"allowFileUploads": true,
|
||
"allowedFilesMimeTypes": "*"
|
||
}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||
"typeVersion": 1.4,
|
||
"position": [
|
||
15632,
|
||
4512
|
||
],
|
||
"id": "2d461a9b-bb76-44a1-8f19-82bb3d584a84",
|
||
"name": "When chat message received",
|
||
"webhookId": "moneybot-readonly",
|
||
"alwaysOutputData": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"model": {
|
||
"__rl": true,
|
||
"value": "claude-sonnet-5",
|
||
"mode": "list",
|
||
"cachedResultName": "Claude Sonnet 5"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
|
||
"typeVersion": 1.3,
|
||
"position": [
|
||
16848,
|
||
4480
|
||
],
|
||
"id": "884af69f-cde9-4acd-9191-86e34ca41f3e",
|
||
"name": "Claude",
|
||
"credentials": {
|
||
"anthropicApi": {
|
||
"id": "uJ4J74wLCEWqVd9B",
|
||
"name": "Anthropic - Moneybot"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"model": "gemma4:12b",
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
17040,
|
||
4480
|
||
],
|
||
"id": "071ce6d1-5379-4964-8d10-36682cceadc9",
|
||
"name": "Ollama",
|
||
"credentials": {
|
||
"ollamaApi": {
|
||
"id": "PjA3zj5FSQY841F6",
|
||
"name": "Ollama 100.111.46.13 fireball"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
"connections": {
|
||
"MMX - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks open invoices": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks customer payments": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks general ledger": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot deals": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MHAG - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MGC - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres PGVector Store": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"mistral-embed": {
|
||
"ai_embedding": [
|
||
[
|
||
{
|
||
"node": "Postgres PGVector Store",
|
||
"type": "ai_embedding",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Contract_Direct_Lookup": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Quickbooks Chart of Accounts MGC MUS MMX MSAG MHAG": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams DM Chats — returns all 1:1 chats with id and members. Search members for displayName or email matching the target person. Use chatId with Send Teams DM.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams Group Chats — returns all group chats with id, topic, and members. Match by topic name or member displayName. Use chatId with Send Teams DM.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams List — returns all joined teams with id and displayName. Call this first to resolve a team name to a teamId before calling Get Teams Channels.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get Teams Channels — given a teamId from Get Teams List, returns all channels with id and displayName. Use channelId and teamId together with Send Teams Channel Message.": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Send Teams DM": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Send Teams Channel Message": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Postgres Quickbooks balance sheet": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Void Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Customer Payment": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Invoice via QuickBooks API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Send Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Update Invoice via API": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - delete Journal Entry": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Export_File": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"score_customer_risk": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Call 'MoneyBot — Collections Emails'": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Generate EML File": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Get FX Rate": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get an item in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Query QBO (QBQL)": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Get latest invoice DocNumber": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Find item by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Find customer by name": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk Contact Lookup - Finance": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - finance tickets": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - general support": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot all deals search": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Hubspot all companies search": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Read Customer Fact Table for Billing": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Update a customer in QuickBooks Online": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Update Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Add Note to Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"PDF upload?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Extract from File",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Excel upload?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Merge PDF Text": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Extract from File": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Merge PDF Text",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Excel upload?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Extract from File (XLSX)",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Extract from File (XLSX)": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Merge XLSX Data",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Merge XLSX Data": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Freshdesk - Create Ticket": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Download Invoice PDF": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Create Credit Memo": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Simple Memory": {
|
||
"ai_memory": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_memory",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MUS - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MMX - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MSAG - Apply Credit Memo to Invoice": {
|
||
"ai_tool": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_tool",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"When chat message received": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "PDF upload?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Claude": {
|
||
"ai_languageModel": [
|
||
[
|
||
{
|
||
"node": "AI Agent",
|
||
"type": "ai_languageModel",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
}
|
||
},
|
||
"nodeGroups": [],
|
||
"authors": "Michael Nolan",
|
||
"name": "Version dd228b98",
|
||
"description": "",
|
||
"autosaved": true
|
||
},
|
||
"tags": []
|
||
} |