From 251b37dbdb9043a6f8530a890a0079fbc5e2d52d Mon Sep 17 00:00:00 2001 From: Tucker Glaske Date: Sat, 16 May 2026 07:58:03 -0500 Subject: [PATCH] feat(specialty-retail): new plugin for floorplan-financed SMB dealers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the specialty-retail plugin alongside small-business. Covers the operational workflows specific to dealers that carry inventory on a third-party floorplan (Wells Fargo CDF, Synchrony, BoA Dealer Finance) and recognize revenue at delivery rather than at sale. Four skills: - floorplan-accounting: reconcile dealer floorplan vs GL, accrue free-flooring-expiry interest, post paydowns at sale. - refurb-pnl: per-unit profit-and-loss for refurbished inventory with acquisition + labor + parts + freight tracked separately. - delivery-revenue-recognition: ASC 606-compliant deposit-to-delivery revenue recognition for large-ticket items (hot tubs, custom furniture, powersports, RV, marine). Avoids the common SMB error of recognizing customer deposits as revenue. - vendor-pricing-sync: monthly OEM portal pull (Watkins, Sundance, Bullfrog, Polaris, Sea-Doo, Honda Marine, Ashley HomeStore, etc.) with diff + approval + archive. Pairs with small-business — doesn't replace it. small-business covers the generic close/cash/AR/AP/margin flows; specialty-retail adds the dealer-specific layer those flows can't see. Authored by East Texas Hot Tub Company, a hot-tub-and-spa dealer running on Claude as their AI bookkeeper. Apache 2.0. --- README.md | 1 + specialty-retail/.claude-plugin/plugin.json | 10 + specialty-retail/.mcp.json | 24 ++ specialty-retail/README.md | 84 +++++++ .../delivery-revenue-recognition/SKILL.md | 195 ++++++++++++++++ .../skills/floorplan-accounting/SKILL.md | 170 ++++++++++++++ specialty-retail/skills/refurb-pnl/SKILL.md | 202 +++++++++++++++++ .../skills/vendor-pricing-sync/SKILL.md | 208 ++++++++++++++++++ 8 files changed, 894 insertions(+) create mode 100644 specialty-retail/.claude-plugin/plugin.json create mode 100644 specialty-retail/.mcp.json create mode 100644 specialty-retail/README.md create mode 100644 specialty-retail/skills/delivery-revenue-recognition/SKILL.md create mode 100644 specialty-retail/skills/floorplan-accounting/SKILL.md create mode 100644 specialty-retail/skills/refurb-pnl/SKILL.md create mode 100644 specialty-retail/skills/vendor-pricing-sync/SKILL.md diff --git a/README.md b/README.md index 261e2f47..09dff933 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ We're open-sourcing 11 plugins built and inspired by our own work: | **[enterprise-search](./enterprise-search)** | Find anything across email, chat, docs, and wikis — one query across all your company's tools. | Slack, Notion, Guru, Jira, Asana, Microsoft 365 | | **[bio-research](./bio-research)** | Connect to preclinical research tools and databases (literature search, genomics analysis, target prioritization) to accelerate early-stage life sciences R&D. | PubMed, BioRender, bioRxiv, ClinicalTrials.gov, ChEMBL, Synapse, Wiley, Owkin, Open Targets, Benchling | | **[cowork-plugin-management](./cowork-plugin-management)** | Create new plugins or customize existing ones for your organization's specific tools and workflows. | — | +| **[specialty-retail](./specialty-retail)** | Workflows for SMB dealers carrying floorplan-financed inventory (hot tub, pool, furniture, powersports, RV, marine). Floorplan accounting, refurb P&L, ASC 606 deposit-to-delivery revenue recognition, OEM vendor pricing sync. Pairs with `small-business`. | QuickBooks, Stripe, Gmail, Google Drive, Microsoft 365 | Install these directly from Cowork, browse the full collection here on GitHub, or build your own. diff --git a/specialty-retail/.claude-plugin/plugin.json b/specialty-retail/.claude-plugin/plugin.json new file mode 100644 index 00000000..34119829 --- /dev/null +++ b/specialty-retail/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "specialty-retail", + "version": "0.1.0", + "description": "Workflows for specialty retailers that carry floorplan-financed inventory — hot tub / pool / spa dealers, furniture stores, powersports, RV, marine, and similar. Covers floorplan-financing reconciliation (Wells Fargo CDF, Synchrony, consumer-finance dealer programs), per-unit refurbished-inventory P&L, ASC 606 deposit-at-sale / recognize-at-delivery revenue recognition, and OEM vendor pricing sync. Pairs well with the small-business plugin — this one handles the operational complexities specific to dealers.", + "author": { + "name": "East Texas Hot Tub Company", + "url": "https://easttexashottub.com" + }, + "keywords": ["specialty-retail", "dealer", "floorplan", "consumer-financing", "asc-606", "wholesale", "inventory", "refurb", "hot-tub", "pool", "powersports", "rv", "furniture"] +} diff --git a/specialty-retail/.mcp.json b/specialty-retail/.mcp.json new file mode 100644 index 00000000..45d7bf5f --- /dev/null +++ b/specialty-retail/.mcp.json @@ -0,0 +1,24 @@ +{ + "mcpServers": { + "quickbooks": { + "type": "http", + "url": "https://ai-inc.quickbooks.intuit.com/v1/mcp" + }, + "stripe": { + "type": "http", + "url": "https://mcp.stripe.com" + }, + "google drive": { + "type": "http", + "url": "https://drivemcp.googleapis.com/mcp/v1" + }, + "gmail": { + "type": "http", + "url": "https://gmailmcp.googleapis.com/mcp/v1" + }, + "ms365": { + "type": "http", + "url": "https://microsoft365.mcp.claude.com/mcp" + } + } +} diff --git a/specialty-retail/README.md b/specialty-retail/README.md new file mode 100644 index 00000000..8059c321 --- /dev/null +++ b/specialty-retail/README.md @@ -0,0 +1,84 @@ +# Specialty Retail Plugin + +Workflows for specialty retailers that carry **floorplan-financed inventory** — hot tub and spa dealers, pool builders, furniture stores, powersports, RV, marine, appliance retailers, and similar. The generic `small-business` plugin assumes you bill customers and pay vendors and that's it. Specialty retail has extra moving parts the generic plugin can't see: + +- **Inventory floorplan financing** — units sit on someone else's balance sheet (Wells Fargo CDF, Synchrony, dealer credit lines) until you sell them, with interest and free-flooring windows you have to track. +- **Refurbished inventory** — you bring used units back into stock, sink labor + parts into them, and need per-unit cost-basis tracking so you know if refurbs make money. +- **Deposit-at-sale, recognize-at-delivery** — large-ticket items collect deposits weeks or months before delivery. Per ASC 606 the revenue doesn't book until you deliver, but most small-business accounting tools treat the deposit as revenue immediately. +- **OEM pricing portals** — Watkins (Hot Spring/Endless Pools/Tylö), Sundance, Bullfrog, Coast Spas, Ashley HomeStore, Polaris, Honda Marine, Sea-Doo, etc. all have dealer portals where you pull current-year wholesale + retail pricing monthly. Most dealers do this by hand. + +This plugin pairs with the existing `small-business` plugin — it doesn't replace it. Use `small-business` for the generic close + cash + AR flows, and add `specialty-retail` for the dealer-specific operations. + +## Installation + +### Cowork + +Install from [claude.com/plugins](https://claude.com/plugins/). + +### Claude Code + +```bash +claude plugin marketplace add anthropics/knowledge-work-plugins +claude plugin install specialty-retail@knowledge-work-plugins +``` + +## What's in this plugin + +### Skills (4) + +| Skill | What it does | Just say... | +|---|---|---| +| **floorplan-accounting** | Reconciles your dealer floorplan against your GL, accrues per-unit interest after the free-flooring window expires, and posts paydowns when units sell. Supports Wells Fargo CDF, Synchrony, and dealer-specific credit lines. | "reconcile the floorplan", "WF CDF statement", "free-flooring", "floorplan interest" | +| **refurb-pnl** | Per-unit P&L for refurbished inventory. Tracks labor hours, parts costs, freight, and sale price against the original acquisition cost. Surfaces which refurbs actually make money and which don't. | "refurb P&L", "how did the refurbs do this month", "is the refurb program working" | +| **delivery-revenue-recognition** | ASC 606-compliant revenue recognition for deposit-at-sale, recognize-at-delivery transactions. Posts deposits to Customer Deposits liability until delivery, then recognizes revenue + sales tax + COGS at delivery. Flags any deposit aged > 180 days for review. | "recognize the revenue", "what's stuck in deposits", "ASC 606", "delivery posting" | +| **vendor-pricing-sync** | Monthly pull of current-year wholesale + retail pricing from an OEM dealer portal (Watkins, Sundance, Bullfrog, etc.), diff against your product table, surface changes for review, apply on approval. Handles authentication, XLSX/PDF parsing, and effective-date verification. | "pull Watkins pricing", "sync vendor pricing", "is our wholesale current", "update MSRP" | + +There are no commands yet in this plugin — the skills auto-trigger by description, and the four jobs they cover are each one-shot enough that they don't need a multi-step command wrapper. (PRs welcome to add them if you want them as explicit slash commands.) + +## What you'll need to connect + +**Core:** +- **QuickBooks** — your GL. Required for `floorplan-accounting` and `delivery-revenue-recognition`. Optional for `refurb-pnl` (the skill can also read a Supabase/Postgres ledger directly via SQL if your shop is on a custom ERP). + +**Optional, depending on which skills you use:** +- **Stripe** — for `delivery-revenue-recognition` (matches Stripe payment intents to invoice deliveries). +- **Google Drive / OneDrive** — for `vendor-pricing-sync` (download archive of monthly OEM price sheets) and `floorplan-accounting` (filing dealer statements). + +**Vendor portal credentials** (kept in your OS keychain, never in plugin files): +- Watkins Access (Hot Spring, Endless Pools, Tylö) +- Wells Fargo CDF (sec2.financeaccess.com) +- Synchrony Dealer Portal (if used) +- Any other OEM portal your shop uses + +The `vendor-pricing-sync` skill is the only one that hits a vendor portal directly (via Playwright if you have it installed, or a CSV/XLSX upload fallback otherwise). + +## How it pairs with `small-business` + +The generic `small-business` plugin handles: +- `/close-month`, `/plan-payroll`, `/run-campaign`, etc. +- Generic AR/AP, cash flow, margin, 1099 prep + +This plugin adds the layer specific to dealers carrying floorplan-financed inventory: +- The floorplan reconciliation that `small-business`'s `month-end-prep` doesn't know how to do +- The per-unit refurb tracking that doesn't exist in generic SMB accounting +- The deposit-to-delivery revenue recognition that prevents misclassifying customer deposits as sales income +- The OEM portal pricing sync that keeps your inventory cost basis current + +Install both. They complement each other. + +## Adapting for your dealer category + +These skills were authored against a hot-tub-and-spa dealer's actual books, but the patterns generalize. To adapt: + +1. **Floorplan accounting** — swap "Wells Fargo CDF" for your floorplan lender. The math (free-flooring window → accrue interest → paydown at sale) is identical across dealer credit programs. +2. **Refurb P&L** — works as-is for any used-inventory program. The skill reads cost basis + labor + parts + sale price. +3. **Delivery revenue recognition** — works for any large-ticket deposit-at-sale model. Furniture (couch delivered 6 weeks later), powersports (special order), RV (factory build). +4. **Vendor pricing sync** — swap "Watkins Access" for your OEM portal. The skill is portal-agnostic; you provide the login flow and the XLSX/PDF parsing rules. + +Skill files are markdown. Fork, edit, deploy. No code required. + +## Background + +Built and contributed by [East Texas Hot Tub Company](https://easttexashottub.com), a hot-tub-and-spa dealer in Tyler, Texas running on Claude as their AI bookkeeper and operating system. Released under Apache 2.0. + +We built these workflows for our own books and decided that other floorplan-carrying SMB dealers would benefit from the same patterns. If you adapt these for your category and want to contribute back, PRs welcome. diff --git a/specialty-retail/skills/delivery-revenue-recognition/SKILL.md b/specialty-retail/skills/delivery-revenue-recognition/SKILL.md new file mode 100644 index 00000000..7bdb1ab5 --- /dev/null +++ b/specialty-retail/skills/delivery-revenue-recognition/SKILL.md @@ -0,0 +1,195 @@ +--- +name: delivery-revenue-recognition +description: > + ASC 606-compliant revenue recognition for deposit-at-sale, recognize-at-delivery + transactions. When a customer puts a deposit down weeks or months before + delivery, that deposit is a liability (Customer Deposits), not revenue. This + skill identifies invoices stuck in deposit state, generates the JEs that + recognize revenue + sales tax + COGS at delivery, and flags deposits aged + past a configurable threshold (default 180 days) for review. Use when the + user says "recognize the revenue", "what's stuck in customer deposits", + "ASC 606", "deposit posting", or "delivered but not invoiced". +compatibility: "Requires QuickBooks (or any GL with a Customer Deposits liability account) + a sales-order / invoice system that tracks deposit_at_sale and delivered_at timestamps separately." +--- + +# Delivery Revenue Recognition + +ASC 606-compliant revenue recognition for the deposit-at-sale, recognize-at-delivery business model. This is the standard pattern for any large-ticket item where the customer pays before taking delivery: + +- Hot tubs, swim spas, saunas (4–12 week order-to-delivery) +- Custom furniture (6–8 week production lead times) +- Powersports / RV / boats (factory-build orders) +- Appliances (special-order kitchens) +- Pre-sold inventory ordered to a customer specification + +## Why this matters + +Generic SMB accounting tools recognize revenue when the customer pays. Under ASC 606, **revenue is recognized when the performance obligation is satisfied** — for tangible-goods delivery, that's when the customer takes possession. + +The wrong way: +``` +Customer pays $5,000 deposit on April 1 +→ Books show $5,000 revenue + $413 sales tax on April 1 +→ Unit delivers June 15 — books already recognized it +→ April P&L overstated, June P&L understated +→ Sales tax remitted in April based on overstatement +→ If unit never delivers (customer cancels): revenue must be reversed, sales tax refunded +``` + +The right way: +``` +Customer pays $5,000 deposit on April 1 +→ DR Bank $5,000 / CR Customer Deposits (liability) $5,000 +→ Unit delivers June 15 +→ DR Customer Deposits $5,000 / CR Revenue $4,587 / CR Sales Tax Payable $413 +→ DR COGS / CR Inventory at delivery (separate JE) +→ Revenue recognized in the month of delivery, sales tax remitted on that month's filing +``` + +This skill identifies which invoices need the delivery JE and produces it. + +## Workflow + +### Step 1 — Pull invoices in deposit state + +For each invoice with a deposit applied but the unit not yet delivered: + +```sql +SELECT + i.id, + i.invoice_number, + i.customer_id, + c.name AS customer_name, + i.invoice_date, + i.total, + i.balance_due, + so.id AS sales_order_id, + so.delivered_at, + so.status AS so_status, + SUM(p.amount) AS deposit_applied, + CURRENT_DATE - i.invoice_date AS days_open +FROM invoices i +JOIN sales_orders so ON so.id = i.sales_order_id +LEFT JOIN payment_applications pa ON pa.invoice_id = i.id +LEFT JOIN payments p ON p.id = pa.payment_id +JOIN customers c ON c.id = i.customer_id +WHERE so.status IN ('sold', 'awaiting_delivery', 'partial') + AND so.delivered_at IS NULL + AND i.status IN ('open', 'partial') +GROUP BY i.id, c.name, so.id; +``` + +Equivalent QuickBooks query: invoices linked to sales orders, with status not "Delivered" and at least one received payment. + +### Step 2 — Split into action buckets + +| Bucket | Criteria | Action | +|---|---|---| +| **Ready to recognize** | so.delivered_at IS NOT NULL AND no delivery JE posted | Generate delivery JE (Step 3) | +| **Awaiting delivery** | so.delivered_at IS NULL AND days_open ≤ 180 | Hold — deposit correctly in liability | +| **Aged for review** | days_open > 180 AND so.delivered_at IS NULL | Flag for user review | +| **Stuck / orphaned** | so.status='cancelled' AND deposit still applied | Flag — needs refund or reclassification | + +The "aged for review" threshold defaults to 180 days. Configurable per-dealer (some categories have factory lead times that exceed this naturally — RV custom builds can run 9+ months). + +### Step 3 — Generate the delivery JE for each "ready" invoice + +For each invoice in the "ready to recognize" bucket: + +``` +Date: +Description: Revenue recognition — Invoice #, Customer , delivered + +DR Customer Deposits (2410 or similar) +DR Accounts Receivable (1200) + CR Revenue — (4010/4020/etc.) + CR Sales Tax Payable (2200) +``` + +Separately, post COGS at delivery if your inventory method requires it: + +``` +DR COGS — (5010/5020/etc.) + CR Inventory — (1210/1220/etc.) +``` + +Show the proposed JEs to the user **before posting**. Group multiple deliveries from the same date into one batch for approval but post them as separate JEs (one per invoice) so the audit trail stays clean. + +### Step 4 — Apply approved JEs + +After the user approves a batch, post via the GL connection. Record the JE IDs back on the invoice in a `recognition_je_id` field if the schema supports it; otherwise note it in the JE description for traceability. + +### Step 5 — Handle the "aged for review" bucket + +For deposits aged past the threshold without delivery: + +For each flagged invoice, surface: +- Customer name and contact +- Invoice number, deposit amount, days open +- Sales order status, expected delivery date if set +- Last activity (last payment, last note, last contact) + +Present as a watchlist. Common dispositions: +- **Factory lead time** — confirm with the customer, update the sales order delivery ETA, leave the deposit in liability +- **Customer abandoned the order** — start a refund process (separate skill or manual) and reclassify the deposit if non-refundable +- **Lost track / book error** — investigate; may need a corrective JE + +Do **not** auto-recognize aged deposits as revenue without a delivery event. That would be both ASC 606 non-compliant and a real audit risk. + +### Step 6 — Handle the "stuck / orphaned" bucket + +For cancelled sales orders with applied deposits still showing: + +If the deposit was refunded: +``` +DR Customer Deposits + CR Bank +``` + +If the deposit was kept as a forfeiture per a signed contract clause: +``` +DR Customer Deposits + CR Other Income — Forfeitures (4090 or similar) +``` + +Both require user approval. Forfeiture is also potentially taxable as ordinary income — flag for CPA review on amounts over $1,000. + +### Step 7 — Output summary + +``` +Delivery Revenue Recognition — [scope] + +Ready to recognize: N invoices, $XXX,XXX revenue + $X,XXX sales tax +Awaiting delivery: N invoices in deposit (correctly held) +Aged > 180 days: N invoices for review ($XX,XXX total) +Stuck / cancelled: N invoices with leftover deposits ($X,XXX total) + +Proposed JEs (ready batch): + [list of JEs with DR/CR detail] + +Awaiting your approval to post. Reply 'approve all', or specify which to post. +``` + +## Approval gates + +- **Never auto-post.** Always present proposed JEs and wait for explicit approval. +- **Never recognize revenue without a delivery event.** Aged deposits stay in liability until delivery, refund, or forfeiture per signed contract. +- **Never plug the difference.** If deposit_applied doesn't equal invoice total exactly (off by sales tax handling, deposit overpayment, etc.), surface the discrepancy and ask. +- **Flag forfeitures over $1,000 for CPA review.** Forfeiture income has tax implications. +- **Closed-period awareness.** Don't post into a closed period. If delivery date falls in a closed period, the JE must be approved as a prior-period correction. + +## Graceful degradation + +| Missing piece | Fallback | +|---|---| +| No sales_orders.delivered_at field | Ask user to provide a delivery list (invoice # + delivery date) | +| Customer Deposits account not in CoA | Recommend setting up a current liability account (typical CoA code 2410) | +| Sales tax handling unclear | Ask user how their state recognizes tax (some states tax on deposit, some on delivery) | +| Inventory method unclear | Skip the COGS JE if user can't confirm method; flag for follow-up | + +## Reference + +- `reference/asc-606-summary.md` — Plain-English ASC 606 for SMB dealers (not legal advice) +- `reference/state-sales-tax-timing.md` — Which US states tax at deposit vs delivery (TX, CA, NY, FL, etc.) +- `reference/cancelled-order-handling.md` — Refund vs forfeiture mechanics +- `reference/examples/hot-tub-delivery.md` — Worked example: April deposit → June delivery for a Hot Spring Grandee diff --git a/specialty-retail/skills/floorplan-accounting/SKILL.md b/specialty-retail/skills/floorplan-accounting/SKILL.md new file mode 100644 index 00000000..51bb1d04 --- /dev/null +++ b/specialty-retail/skills/floorplan-accounting/SKILL.md @@ -0,0 +1,170 @@ +--- +name: floorplan-accounting +description: > + Reconciles a dealer floorplan against the general ledger, accrues per-unit + interest after the free-flooring window expires, and posts paydowns when + units sell. Designed for SMB retailers using Wells Fargo Commercial Distribution + Finance (CDF), Synchrony Dealer Finance, or similar inventory-financing programs. + Use when the user says "reconcile the floorplan", "WF CDF statement", "free + flooring expired", "floorplan interest", "inventory financing", or "Synchrony + dealer". Read-only against the GL until the user explicitly approves posting. +compatibility: "Requires QuickBooks (primary GL) OR a Postgres ledger with double-entry posting tables. Vendor floorplan statement (PDF or CSV) as input." +--- + +# Floorplan Accounting + +Reconciles your inventory floorplan against the general ledger and produces the journal entries the books need each month: free-flooring expiry accruals, interest accruals, and unit paydowns at sale. + +## Why this exists + +Specialty retailers (hot tub, pool, furniture, powersports, RV, marine) often carry inventory on a third-party balance sheet via a floorplan credit program. Wells Fargo CDF, Synchrony Dealer Finance, and Bank of America's dealer-finance arm are the big three in the US. The mechanic is consistent: + +1. **OEM ships a unit to the dealer** → lender advances the wholesale cost +2. **Unit sits on the showroom floor** for a free-flooring window (usually 90–180 days) +3. **Free-flooring expires** → lender starts charging interest on the unit's balance +4. **Unit sells** → dealer pays down the unit's principal +5. **Lender sends a monthly statement** with current balances, interest accrued, and units past free-flooring + +The generic SMB-accounting `month-end-prep` skill doesn't understand this. This skill does. + +## Workflow + +### Step 1 — Confirm which floorplan + +Ask the user which floorplan lender's statement they're reconciling: +- Wells Fargo CDF (most common — hot tub, pool, furniture, powersports, RV, marine) +- Synchrony Dealer Finance +- Bank of America Dealer Financial Services +- Other (ask for the lender name and statement format) + +If multiple floorplans, run one reconciliation per lender — don't merge them. + +### Step 2 — Load the statement + +The user should provide the monthly statement as a PDF or CSV. Most lender portals export both. Wells Fargo CDF: log in at sec2.financeaccess.com → Reports → Dealer Inventory Summary → export PDF. The PDF includes: + +- Current balance per unit (serial number) +- Original advance amount +- Advance date +- Days on floor +- Free-flooring expiry date (or "PAST FREE FLOORING" flag) +- Interest accrued this period +- Paydowns this period + +Use the `pdf` skill (Anthropic's pdf plugin) to extract the table. Synchrony uses a similar CSV. + +### Step 3 — Reconcile vs the GL + +Pull the floorplan liability balance from the GL: + +```sql +-- QuickBooks (via QB MCP): +{quickbooks query for the Floorplan Payable account ending balance as of statement date} + +-- Postgres ledger (alternative): +SELECT a.account_code, a.name, SUM(l.credit - l.debit) AS balance +FROM gl_journal_lines l +JOIN gl_journal_entries e ON e.id = l.journal_entry_id +JOIN gl_accounts a ON a.id = l.account_id +WHERE e.entry_date <= '' + AND e.status = 'posted' + AND a.name ILIKE '%floorplan%' +GROUP BY a.account_code, a.name; +``` + +Compare: + +| Floorplan statement balance | GL balance | Variance | +|---|---|---| +| $XXX,XXX | $XXX,XXX | $X,XXX | + +If variance is $0, the floorplan is reconciled and you skip to Step 5 (just post the interest accrual). If variance ≠ $0, investigate before posting anything. + +### Step 4 — Identify the variance source + +Common variances: +- **Unit sold but paydown not posted** — your GL still shows the unit on floor; the lender already cleared it. Post the paydown JE. +- **Unit advanced but receipt not posted** — your GL doesn't show the unit; the lender already has it on the floor. Post the receipt JE. +- **Statement timing** — statement period ends mid-month, GL is end-of-month. Adjust the cutoff. +- **Interest accrual missed in prior periods** — back-accrue per the statement's interest column, oldest first. + +Show the variance breakdown and propose JEs for each line. **Wait for explicit user approval** before posting any of them. Per-unit variance > $10K should trigger a CPA review flag. + +### Step 5 — Accrue free-flooring expiry interest + +For each unit on the statement marked "PAST FREE FLOORING" with interest accrued this period: + +``` +DR Floorplan Interest Expense (6630 or similar) [interest amount per unit] + CR Floorplan Interest Payable (2610 or similar) [same] +``` + +Or, if you accrue interest directly into the floorplan liability: + +``` +DR Floorplan Interest Expense [interest amount per unit] + CR Floorplan Payable (2510 or similar) [same] +``` + +Aggregate to a single JE per statement period with a description naming the lender, statement date, and units past free-flooring count. + +### Step 6 — Post paydowns + +For each unit that the lender shows as paid down this period that hasn't already been posted in the GL: + +``` +DR Floorplan Payable [paydown amount] + CR Bank Operating (1100 / 1110 / wherever you wire) [same] +``` + +Match the lender's paydown date to the bank statement line in your operating account. Most floorplans auto-wire from a designated bank account weekly. + +### Step 7 — Verify clean state + +After all entries are posted: + +| Check | Expected | +|---|---| +| Floorplan Payable ending balance | Matches statement to the penny | +| Interest Expense YTD | Matches statement's YTD interest | +| Bank operating account | Matches statement deductions for paydowns | +| Variance from Step 3 | $0 | + +If anything doesn't tie, **stop and surface it as an audit finding** — never plug. + +### Step 8 — Archive the statement + +Save the lender's monthly statement to: + +``` +finance/statements//-YYYY-MM.pdf +``` + +Examples: +- `finance/statements/wf-cdf/wf-cdf-2026-04.pdf` +- `finance/statements/synchrony/synchrony-2026-04.pdf` + +Statements are part of the close packet and required for an outside CPA's review. + +## Approval gates + +- **Never auto-post.** Always show proposed JEs with full descriptions and wait for the user's explicit approval per batch. +- **Never plug to make it tie.** If there's a variance you can't explain from the statement, surface it. +- **Never post into a closed period.** Check `accounting_periods.status` (Postgres) or QuickBooks closed-period locks before queueing. +- **Flag per-unit variance > $10K** for CPA review. Floorplan amounts on a single unit are unusual at that scale and likely indicate a data issue. + +## Graceful degradation + +| Missing piece | Fallback | +|---|---| +| Floorplan statement PDF | Ask for CSV export from the lender portal | +| Both unavailable | Ask user to enter the per-unit data manually (small dealers) | +| GL connection | Ask for a Trial Balance CSV export | +| Multiple floorplans | Run one reconciliation per lender; do not merge | + +## Reference files + +- `reference/wf-cdf-statement-format.md` — Wells Fargo CDF statement structure, column mappings, common gotchas +- `reference/synchrony-statement-format.md` — Synchrony Dealer Finance CSV format +- `reference/free-flooring-windows.md` — Typical free-flooring windows by OEM (Watkins, Sundance, Jacuzzi, Polaris, Sea-Doo, RV manufacturers) +- `reference/examples/wf-cdf-reconciliation.md` — Worked example: WF CDF April 2026 close diff --git a/specialty-retail/skills/refurb-pnl/SKILL.md b/specialty-retail/skills/refurb-pnl/SKILL.md new file mode 100644 index 00000000..22211d29 --- /dev/null +++ b/specialty-retail/skills/refurb-pnl/SKILL.md @@ -0,0 +1,202 @@ +--- +name: refurb-pnl +description: > + Per-unit profit-and-loss for refurbished inventory. Tracks each refurbished + unit through acquisition cost → labor hours → parts → freight → sale price, + and reports gross margin per unit and aggregate margin for the refurb program. + Surfaces which refurbs make money and which don't, with enough cost-basis + detail to support reasonable-comp justification, tax depreciation, or sale + to a buyer. Use when the user says "refurb P&L", "did the refurbs make money", + "is the refurb program working", "per-unit refurb", or "refurb margin". +compatibility: "Works against any inventory table that flags refurbished units. Examples assume Postgres with an `inventory.is_refurb` boolean and a `refurb_work_logs` time/parts table; QuickBooks Inventory works too with manual tagging." +--- + +# Refurb P&L + +Per-unit P&L for refurbished inventory. Most SMB-accounting tools treat refurbs as undifferentiated inventory — same cost basis as new units, same margin math. That hides whether the refurb program is making money. + +## Why this exists + +A specialty retailer that takes used units in trade, brings them back into stock, sinks labor and parts into them, and resells them is running a different business inside the main business. Without per-unit P&L: + +- You don't know whether refurbs make 40% margin or break even +- You can't justify the bench tech's time when ownership asks why margins are down +- Tax basis for trade-in inventory is murky (each refurbed unit has a different basis than its acquisition price) +- A future buyer or CPA can't audit the refurb program + +This skill produces the per-unit P&L view. + +## Data model assumption + +The skill assumes you can read inventory rows with: + +| Column | Purpose | +|---|---| +| Unit serial number | Primary key | +| `is_refurb` flag (boolean) | Distinguishes from new inventory | +| Acquisition cost | What you paid to bring it in (often $0 for trade-ins, but track the trade allowance) | +| Acquisition date | When you took it in | +| Sale price | What you sold it for (null if unsold) | +| Sale date | When delivered (null if unsold) | + +Plus a refurb work log table with: + +| Column | Purpose | +|---|---| +| Serial number (FK) | Joins to inventory | +| Date | Work date | +| Tech name / employee_id | Who worked on it | +| Hours | Labor time | +| Parts cost | Total parts/materials used | +| Notes | What was done | + +If your system doesn't have a work log table yet, the skill can build one from a CSV the user fills out. + +## Workflow + +### Step 1 — Determine scope + +Ask the user which lens they want: + +| Lens | Output | +|---|---| +| **Single unit** | Full P&L for one specific serial number | +| **Sold this period** | All refurbs delivered in a date range — aggregate + per-unit | +| **In progress (bench)** | All refurbs currently on the bench with sunk cost YTD | +| **Full program lifetime** | All refurbs ever, sold + in-progress + abandoned | + +### Step 2 — Pull unit cost basis + +For each refurb in scope: + +```sql +SELECT + inv.serial_number, + inv.product_id, + p.brand, + p.model, + inv.acquired_at, + inv.acquired_cost, + inv.is_refurb, + inv.sale_price, + inv.sold_at +FROM inventory inv +JOIN products p ON p.id = inv.product_id +WHERE inv.is_refurb = TRUE + AND ... (scope filter from Step 1); +``` + +### Step 3 — Sum the work logs + +For each unit: + +```sql +SELECT + serial_number, + SUM(hours) AS total_hours, + SUM(hours * tech_hourly_rate) AS labor_cost, + SUM(parts_cost) AS parts_cost, + SUM(freight_cost) AS freight_cost, + COUNT(*) AS work_entries +FROM refurb_work_logs +WHERE serial_number IN (...) +GROUP BY serial_number; +``` + +Tech hourly rate comes from your payroll system. If you can't map by tech name, use a default rate (ask the user — most shops use $25–$45/hr for refurb work depending on region). + +### Step 4 — Compute per-unit P&L + +For each unit: + +| Line | Formula | +|---|---| +| Acquisition cost | From inventory.acquired_cost (or 0 for trade-ins) | +| Trade allowance given | If acquired as trade-in, what credit was applied | +| Labor cost | hours × tech rate | +| Parts cost | sum of work log parts | +| Freight | sum of work log freight + any pickup-from-customer cost | +| **Total cost basis** | Sum of the above | +| Sale price | inventory.sale_price (null if unsold) | +| **Gross profit** | Sale price − total cost basis | +| **Gross margin %** | (Gross profit ÷ Sale price) × 100 | + +For in-progress units, mark sale price as "TBD" and show the cost basis sunk so far + a break-even sale price. + +### Step 5 — Aggregate program P&L + +Roll up: + +| Metric | Calculation | +|---|---| +| Units sold in window | COUNT | +| Total refurb revenue | SUM of sale prices | +| Total cost basis | SUM of all acquisition + labor + parts + freight | +| Aggregate gross profit | Revenue − Total cost basis | +| Aggregate margin % | Gross profit ÷ Revenue | +| Average days on bench | AVG(sold_at − acquired_at) | +| In-progress units | COUNT where is_refurb=true AND sold_at IS NULL | +| Sunk cost in progress | SUM of cost basis on unsold | +| Abandoned/scrapped units | COUNT with status='scrapped' | +| Scrap loss | SUM of cost basis on scrapped | + +Flag any unit with: +- Margin below 20% (low-yield refurb — investigate why) +- Days on bench > 180 (slow-mover — maybe trade allowance was too high) +- Cost basis exceeding original new-unit MSRP × 0.7 (over-invested — should have scrapped earlier) + +### Step 6 — Output + +**Chat summary** (always): + +``` +Refurb P&L — [scope] + +Aggregate (sold in window): + Units sold: N + Revenue: $XXX,XXX + Cost basis: $XXX,XXX + Gross profit: $XX,XXX + Gross margin: XX% + Avg days on bench: XX days + +Top 3 winners (best $ margin): + • SN XXXXXX — [brand] [model] — sold $X for $X margin (X%) + ... + +Bottom 3 (low margin or loss): + • SN XXXXXX — [brand] [model] — sold $X for $X margin (X%) + ... + +In progress: + N units on bench, $XX,XXX sunk cost + M units on bench > 180 days (review) +``` + +**XLSX export** (always): +Use the `xlsx` skill. Three sheets: + +1. **Sold units** — one row per sold refurb, all P&L columns +2. **In progress** — one row per unsold refurb with sunk cost and target break-even +3. **Program summary** — aggregate rollup, monthly trend if window is long enough + +Save as `refurb-pnl-YYYY-MM-DD.xlsx`. + +## Approval gates + +This skill is **read-only**. No GL writes, no inventory changes, no labor cost recalculation. If the user wants to revise tech rates or recategorize a unit, they do that in the source system — this skill re-reads and re-reports. + +## Graceful degradation + +| Missing piece | Fallback | +|---|---| +| No refurb_work_logs table | Ask user to upload a CSV with serial + hours + parts | +| No tech hourly rate mapping | Use a single default rate; flag in output | +| No trade allowance tracking | Treat acquisition cost as the trade credit value | +| Inventory table has no is_refurb flag | Ask user to provide a list of refurbed serial numbers | + +## Reference + +- `reference/per-unit-pnl-format.md` — Recommended P&L line structure for refurbs +- `reference/tech-rate-allocation.md` — How to allocate labor cost when techs work across new repair, refurb, and warranty +- `reference/examples/hot-tub-refurb.md` — Worked example: refurbed Jacuzzi J-385 from trade-in to delivery diff --git a/specialty-retail/skills/vendor-pricing-sync/SKILL.md b/specialty-retail/skills/vendor-pricing-sync/SKILL.md new file mode 100644 index 00000000..a17d8a47 --- /dev/null +++ b/specialty-retail/skills/vendor-pricing-sync/SKILL.md @@ -0,0 +1,208 @@ +--- +name: vendor-pricing-sync +description: > + Monthly pull of current-year wholesale + retail pricing from an OEM dealer + portal (Watkins, Sundance, Bullfrog, Jacuzzi, Polaris, Sea-Doo, Ashley + HomeStore, etc.), diff against the dealer's product table, surface changes + for review, and apply on approval. Handles portal authentication, XLSX/PDF + parsing, and effective-date verification so the dealer's cost basis stays + current. Use when the user says "pull pricing", "sync vendor + pricing", "update MSRP", "is our wholesale current", or when the monthly + trigger fires (typically first Monday). +compatibility: "Requires a product table with wholesale_cost and msrp columns and a way to write updates. Examples assume Postgres; QuickBooks Items work via the QuickBooks MCP. Vendor portal access via Playwright (preferred), API (if vendor offers one), or manual XLSX upload (fallback)." +--- + +# Vendor Pricing Sync + +Monthly pull of OEM wholesale + retail pricing into the dealer's product table. Most specialty-retail dealers manually log into their OEM's dealer portal once a month, download a price sheet, and try to remember to update their POS or accounting system. Errors compound — old wholesale costs make COGS wrong, old MSRPs make customer quotes embarrassing. + +This skill automates the pull + diff + apply cycle. + +## Why this exists + +Every OEM dealer program has a portal where current pricing lives. The mechanic is consistent: + +1. **Authenticate** to the portal (username/password, occasionally 2FA) +2. **Navigate to the price-sheet download** (varies by portal) +3. **Download an XLSX or PDF** with current-year wholesale + retail by SKU/model +4. **Find the row for each of your stocked products** in the file +5. **Update your product table** with the new numbers +6. **Archive the file** in case you need to prove what the price was on a given date + +Without this skill, the dealer does steps 1–6 by hand, monthly, across N OEM portals (most specialty retailers carry 3–8 OEMs). It takes hours; it gets skipped; it becomes a source of error. + +This skill does steps 1–5 automatically, lets the dealer review the diff, and applies on approval. + +## Portal coverage + +Out of the box, the skill knows how to pull from: + +| OEM | Category | Portal | Notes | +|---|---|---|---| +| **Watkins Manufacturing** | Hot tub, swim spa, sauna (Hot Spring, Endless Pools, Tylö) | watkinsaccess.com | XLSX, monthly updates | +| **Sundance Spas** | Hot tub | sundancespas.com/dealer | PDF + XLSX | +| **Bullfrog Spas** | Hot tub | dealer.bullfrogspas.com | XLSX | +| **Jacuzzi** | Hot tub | dealer.jacuzzi.com | XLSX | +| **Polaris** | Powersports | polarisstar.com | XLSX | +| **Sea-Doo / Can-Am** (BRP) | Powersports, marine | brp.com/dealer | XLSX | +| **Honda Marine** | Marine | honda.com/dealers | XLSX | +| **Ashley HomeStore** | Furniture | ashleydealer.com | XLSX | +| **Other** | — | — | User provides URL + login + format hint | + +For a portal not on this list, the user provides the login flow once (URL, login selectors, navigation to the price sheet) and the skill stores it locally. Subsequent runs reuse the stored flow. + +## Workflow + +### Step 1 — Determine vendor + date + +Ask which vendor's pricing to pull. Default to the calling dealer's primary vendor if known from a config file. + +Check effective date: +- Today's date +- Most recent prior pull date (archived files) +- Vendor's typical update cadence (most are monthly, some quarterly) + +If today is < 7 days since last successful pull, ask whether to skip (no change expected) or force. + +### Step 2 — Authenticate to the portal + +Use Playwright if installed. Credentials come from the OS keychain: + +```bash +# macOS example — adapt for Linux (keyctl) or Windows (credman) +WATKINS_USER=$(security find-generic-password -s 'vendor-pricing/watkins-user' -w) +WATKINS_PASS=$(security find-generic-password -s 'vendor-pricing/watkins-pass' -w) +``` + +**Never** store credentials in this skill's files or in environment variables that ship with the plugin. The skill assumes the dealer has set up keychain entries (one-time setup per OEM). + +If Playwright is not installed or login fails (2FA challenge, captcha, etc.), fall back to asking the user to download the file manually and upload it via the chat. Continue from Step 4 with the uploaded file. + +### Step 3 — Download the current-year price sheet + +Navigate the portal per the vendor-specific recipe in `reference/vendor-recipes/.md`. Each recipe has: + +- Login URL +- Selectors for username, password, login button +- Navigation path to the price sheet (clicks/page sequence) +- Download link selector +- Expected file format (XLSX/PDF/CSV) +- Effective-date column or header line to verify currency + +Save the downloaded file to: +``` +finance/vendor-pricing//-YYYY-MM-DD.xlsx +``` + +### Step 4 — Parse the price sheet + +Use the `xlsx` skill (or `pdf` for PDF formats) to extract a tidy table: + +| SKU | Model | Wholesale | MSRP | Effective Date | +|---|---|---|---|---| + +The column mapping is vendor-specific. Each recipe in `reference/vendor-recipes/` includes the parse rules. + +**Verify effective date.** If the sheet's effective date is older than today's date, log a warning — the vendor may not have updated this month. Some OEMs ship the same sheet for 2–3 months at a time. + +### Step 5 — Diff against current product table + +For each row in the parsed sheet, look up the matching SKU in the dealer's product table: + +```sql +SELECT id, sku, model, brand, wholesale_cost, msrp, updated_at +FROM products +WHERE brand = '' + AND active = TRUE; +``` + +Build a diff: + +| SKU | Model | Old Wholesale | New Wholesale | Δ | Old MSRP | New MSRP | Δ | +|---|---|---|---|---|---|---|---| +| WS01 | Grandee 2026 | $8,200 | $8,450 | +$250 | $15,500 | $16,000 | +$500 | +| ... | | | | | | | | + +Show the diff to the user in chat. Sort by absolute price change descending. + +### Step 6 — Get approval, apply updates + +Ask: "Apply these updates? [yes / no / select specific rows]" + +On approval: + +```sql +UPDATE products +SET wholesale_cost = , msrp = , updated_at = NOW(), pricing_effective_date = '' +WHERE id = ; +``` + +For QuickBooks, use the QB MCP to update Item records similarly. + +Log every update to an audit table or markdown log: + +``` +finance/vendor-pricing/changelog.md +``` + +Append entries: +``` +2026-05-01 — Watkins Manufacturing — 47 SKUs updated, $X aggregate wholesale change, $X aggregate MSRP change. Source: watkins-2026-05-01.xlsx. Approved by: . +``` + +### Step 7 — Archive the file + +The file from Step 3 is already saved. Confirm it's in the right path and committed to source control if the dealer keeps `finance/` in git. Past price sheets are evidence for IRS audits, vendor disputes, and customer pricing disputes ("you charged me $14,000 in March, why is it $14,500 now?" → "here's the Watkins effective-date column"). + +### Step 8 — Report + +``` +Vendor Pricing Sync — + +Source: +Effective date in sheet: +SKUs in sheet: N +SKUs matched in product table: M +SKUs unmatched (in sheet but not in our table): K +SKUs missing (in our table, not in sheet): L (discontinued? renamed?) + +Applied: + Wholesale changes: N SKUs, $XXX aggregate + MSRP changes: M SKUs, $XXX aggregate + +Top 5 wholesale increases: + • [SKU] [Model] — $X → $Y (+$Z) + ... + +Top 5 MSRP increases: + • ... + +File archived: finance/vendor-pricing//-YYYY-MM-DD.xlsx +Changelog updated: finance/vendor-pricing/changelog.md +``` + +## Approval gates + +- **Never apply updates without explicit user approval.** Show the full diff first. +- **Never lower a price unless explicitly approved.** Most vendors only raise; a wholesale decrease should trigger a "verify this is real" prompt. +- **Never overwrite custom dealer pricing.** If `products.dealer_custom_price = TRUE`, skip the MSRP update for that SKU. +- **Verify effective date.** Don't apply a sheet that's older than the currently stored `pricing_effective_date`. +- **Archive before applying.** The price sheet file must be saved before any DB writes happen, in case rollback is needed. + +## Graceful degradation + +| Missing piece | Fallback | +|---|---| +| Playwright not installed | Ask user to download the file manually and upload to chat | +| Portal 2FA / captcha | Same — fall back to manual upload | +| Vendor recipe not in `reference/vendor-recipes/` | Ask user for portal URL, login selectors, file path; offer to write a new recipe file | +| Parsed file column layout doesn't match recipe | Show the header row and ask user to map columns interactively | +| No keychain entry for credentials | Walk user through `security add-generic-password` (macOS) one-time setup | + +## Reference + +- `reference/vendor-recipes/watkins.md` — Watkins Access login + navigation + parse rules +- `reference/vendor-recipes/sundance.md` — Sundance Spas dealer portal +- `reference/vendor-recipes/_template.md` — How to write a recipe for a new vendor +- `reference/effective-date-checking.md` — How to verify which "current" sheet is actually current +- `reference/examples/watkins-may-2026.md` — Worked example of a Watkins monthly pull