Compare commits
168 Commits
b07f771d98
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
091f98e1f9 | ||
|
|
25f568f225 | ||
|
|
4e54ecc32f | ||
|
|
ab7ff3eea5 | ||
|
|
f8fc6be370 | ||
|
|
b27f68b8d5 | ||
|
|
d9bdbd8e18 | ||
|
|
281941c7ee | ||
|
|
7eb9dd02a7 | ||
|
|
3a520564a7 | ||
|
|
6f2bea9773 | ||
|
|
e50631c46a | ||
|
|
76c68e0311 | ||
|
|
04862e8a28 | ||
|
|
cdc47554ed | ||
|
|
77b84ac11b | ||
|
|
b92a396934 | ||
|
|
8225061dfa | ||
|
|
fe4cceeffa | ||
|
|
a99f9aa5ee | ||
|
|
ca60500c07 | ||
|
|
d17cadabf0 | ||
|
|
df74d702af | ||
|
|
ada22a583f | ||
|
|
009562913c | ||
|
|
0593b70354 | ||
|
|
26fe41e7d4 | ||
|
|
2802fcf738 | ||
|
|
153b980e2b | ||
|
|
6cad69cb86 | ||
|
|
27badff570 | ||
|
|
a63fbe1558 | ||
|
|
49013c64fb | ||
|
|
ba6f39375a | ||
|
|
cbed74e5eb | ||
|
|
2730c455f5 | ||
|
|
669ba0fd8a | ||
|
|
8e172132e7 | ||
|
|
d3c5c25865 | ||
|
|
f8586611c9 | ||
|
|
28220f0732 | ||
|
|
edcc325483 | ||
|
|
37f1f7e8a3 | ||
|
|
0f10c490cd | ||
|
|
e166fae57b | ||
|
|
488243cd75 | ||
|
|
6cf826268b | ||
|
|
c8deef1482 | ||
|
|
55ac05667c | ||
|
|
4da123c2d3 | ||
|
|
8c6718e352 | ||
|
|
9d58f5f61e | ||
|
|
06df9745a0 | ||
|
|
3aa11eaffc | ||
|
|
c2590a99ff | ||
|
|
215e393bdb | ||
|
|
1780b383b9 | ||
|
|
a6ff3054bc | ||
|
|
b3a86cd4b9 | ||
|
|
23ac3284cb | ||
|
|
83c2b42aad | ||
|
|
22e217a16c | ||
|
|
3310b12754 | ||
|
|
eac337c058 | ||
|
|
655b767127 | ||
|
|
9ebf89bde2 | ||
|
|
191a9c82be | ||
|
|
00981a502a | ||
|
|
d75198be9f | ||
|
|
d009a1ef50 | ||
|
|
9001b6fc51 | ||
|
|
a24ef15a02 | ||
|
|
7fdab094fc | ||
|
|
c2646f59c4 | ||
|
|
152ed86c3a | ||
|
|
21754c1660 | ||
|
|
145b424760 | ||
|
|
a68bf2eae7 | ||
|
|
bc7c771f20 | ||
|
|
1ed414c6fb | ||
|
|
7d27db69c6 | ||
|
|
d891002c84 | ||
|
|
e0eacc2530 | ||
|
|
c637f82ae2 | ||
|
|
7cafab1b9f | ||
|
|
c96f27b96c | ||
|
|
406cac1362 | ||
|
|
13fd0712d9 | ||
|
|
1414ef2c1c | ||
|
|
42e8fe3d21 | ||
|
|
bad73fcea8 | ||
|
|
94249ba67d | ||
|
|
2abd859a29 | ||
|
|
98cb42d2e5 | ||
|
|
878d05685c | ||
|
|
bd2c037a97 | ||
|
|
44636e47fb | ||
|
|
06c49ecec6 | ||
|
|
37deaedf0d | ||
|
|
30f7f18472 | ||
|
|
66e9749853 | ||
|
|
c9be68a575 | ||
|
|
19d692afe7 | ||
|
|
0351dcd497 | ||
|
|
03fd3d7c1c | ||
|
|
f4c9ed3d24 | ||
|
|
ef885c66dc | ||
|
|
148aa5cba8 | ||
|
|
661c8ae227 | ||
|
|
a24a1ddf1a | ||
|
|
f05cacec22 | ||
|
|
9239ee2822 | ||
|
|
4733885211 | ||
|
|
8e708bf2c4 | ||
|
|
caf240daec | ||
|
|
4bed8ab2c5 | ||
|
|
50c209b8d3 | ||
|
|
65a1c4b17e | ||
|
|
91d3a3f9d1 | ||
|
|
70f855d91b | ||
|
|
85eddba546 | ||
|
|
48d3e48e61 | ||
|
|
f07e1bcce1 | ||
|
|
e7c6960de9 | ||
|
|
ad64b0b4c9 | ||
|
|
cd763fa1d7 | ||
|
|
f40f44aafd | ||
|
|
63bf271725 | ||
|
|
974b8a5152 | ||
|
|
0a32ed2da7 | ||
|
|
e4681a58c6 | ||
|
|
135cbd3a5c | ||
|
|
3182ca3c39 | ||
|
|
677e460438 | ||
|
|
c7b794f604 | ||
|
|
64c61dcca8 | ||
|
|
649b75d4a1 | ||
|
|
8aa817b1a0 | ||
|
|
80d1cc5639 | ||
|
|
2db789d7dd | ||
|
|
7a02382623 | ||
|
|
169e97af02 | ||
|
|
3c959771ae | ||
|
|
449f29fc7f | ||
|
|
3c2fb22346 | ||
|
|
3a41370189 | ||
|
|
d6513ff7ab | ||
|
|
457d9b7dbf | ||
|
|
c85a9bbf82 | ||
|
|
5b399fbdda | ||
|
|
b5416d242c | ||
|
|
fdbbd2852a | ||
|
|
be109c9c79 | ||
|
|
78d633f63f | ||
|
|
95cb73d91a | ||
|
|
0d85063b5e | ||
|
|
765a0a4c82 | ||
|
|
daf1235e20 | ||
|
|
3d4f003aba | ||
|
|
6c6fb8d2a4 | ||
|
|
1b1bebdcd8 | ||
|
|
e0d1998811 | ||
|
|
bc3f584851 | ||
|
|
105909470f | ||
|
|
6e67fc5ce3 | ||
|
|
fd9d4e775b | ||
|
|
2de5491693 | ||
|
|
671820427a |
2797
docs/superpowers/plans/2026-05-12-nexa-coa-setup.md
Normal file
2797
docs/superpowers/plans/2026-05-12-nexa-coa-setup.md
Normal file
File diff suppressed because it is too large
Load Diff
2801
docs/superpowers/plans/2026-05-13-nfc-clock-kiosk-plan.md
Normal file
2801
docs/superpowers/plans/2026-05-13-nfc-clock-kiosk-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
552
docs/superpowers/specs/2026-05-12-nexa-coa-design.md
Normal file
552
docs/superpowers/specs/2026-05-12-nexa-coa-design.md
Normal file
@@ -0,0 +1,552 @@
|
||||
# Nexa Systems Inc — Chart of Accounts & Accounting Setup Design
|
||||
|
||||
**Date**: 2026-05-12
|
||||
**Target**: odoo-nexa production instance, database `nexamain`
|
||||
**Status**: Design — pending implementation plan
|
||||
|
||||
## 1. Context
|
||||
|
||||
Nexa Systems Inc is a Canadian CCPC providing IT services: custom software development, custom ERP, business apps, hosting, custom websites, and custom web apps. Operations are Canada-wide with planned global expansion. Workforce: solo founder today (Gurpreet, Canadian), hiring plan favours Canadian T4/T4A with occasional India contractors for burst capacity. Nexa will pursue SR&ED tax credits.
|
||||
|
||||
**Current state (as of 2026-05-12)**:
|
||||
- Odoo 19 Enterprise, l10n_ca localization loaded
|
||||
- 426 GL accounts (most unused — generic Canadian template bloat)
|
||||
- 49 active taxes with duplicates
|
||||
- 14 journals incl. 7 bank accounts (overprovisioned)
|
||||
- 776 journal entries, 125 invoices, data 2020-01-01 to 2026-05-04
|
||||
- **Historical Odoo data is NOT authoritative** — accountant has filed externally on Excel-based records. Past will be reconciled later.
|
||||
- All prior years filed with CRA. Fiscal year-end Dec 31.
|
||||
|
||||
**CRA registration & filing cadence**:
|
||||
- **Business Number / HST account**: `741224877` (currently stored as 9-digit BN root only on company record; needs to be updated to full 15-char format `741224877 RT0001` for Odoo's Canadian tax reports to validate cleanly).
|
||||
- **GST/HST filing**: annual. Return due **3 months after fiscal year-end** (March 31).
|
||||
- **T2 corporate income tax filing**: annual. Return due **6 months after fiscal year-end** (June 30). Balance owing due 3 months after year-end (March 31) for CCPCs eligible for SBD; 2 months otherwise.
|
||||
- **HST instalments**: annual filers must remit quarterly instalments if their net tax for the prior year was ≥ $3,000. Track via account 118200 GST/HST Instalments Paid.
|
||||
- **T2 instalments**: monthly or quarterly instalments required if Part I tax owing in prior year ≥ $3,000.
|
||||
|
||||
**Goals**:
|
||||
1. **CRA compliance** — clean tax handling, T2 Schedule 125 alignment, audit-ready
|
||||
2. **Tax savings** — SR&ED claim infrastructure from day 1, zero-rated export handling, CCA structure
|
||||
3. **Automation** — fiscal positions, default accounts, bank feeds, subscription billing
|
||||
4. **Ease of use** — invoicing is one-click after customer/product selection
|
||||
|
||||
**Scope**: Chart of accounts structure + tax/fiscal-position setup + analytic plans + automation hooks. **Out of scope**: bank feed onboarding (separate sub-project), CCA custom module (defer until volume warrants), historical data reconciliation (separate sub-project when accountant records arrive).
|
||||
|
||||
## 2. Approach
|
||||
|
||||
**Approach #2 — Hybrid**: keep l10n_ca's 6-digit code scheme (Canadian accountants recognize it), aggressively curate (~370 unused accounts archived, ~20 renamed, ~70 added), supplement with three analytic plans for finer reporting without GL proliferation.
|
||||
|
||||
**Rejected alternatives**:
|
||||
- *Surgical* — keep all 426 accounts unchanged. Rejected: bookkeeping burden, no IT-services shape.
|
||||
- *Clean slate (custom 4-digit)* — toss l10n_ca. Rejected: accountants would have to learn it; loses pre-mapped CRA tax structure.
|
||||
|
||||
## 3. Code Skeleton
|
||||
|
||||
```
|
||||
1xxxxx ASSETS
|
||||
111xxx Cash & cash equivalents
|
||||
112xxx Accounts receivable
|
||||
113xxx Prepaid expenses
|
||||
114xxx Other current assets
|
||||
115xxx Due from shareholder / related parties
|
||||
118xxx Tax assets (HST ITC, instalments)
|
||||
151xxx Capital assets — cost
|
||||
154xxx Accumulated depreciation (contra)
|
||||
|
||||
2xxxxx LIABILITIES
|
||||
211xxx Accounts payable
|
||||
213xxx HST/GST/QST collected
|
||||
214xxx Net tax payable
|
||||
215xxx Source deductions payable
|
||||
216xxx Corporate income tax payable
|
||||
221xxx Due to shareholder
|
||||
222xxx Due to related parties
|
||||
251xxx Long-term debt
|
||||
|
||||
3xxxxx EQUITY
|
||||
311xxx Share capital + contributed surplus
|
||||
321xxx Retained earnings + dividends
|
||||
|
||||
4xxxxx REVENUE (by service line — jurisdiction handled by tax codes, not by account)
|
||||
411xxx Recurring revenue (SaaS, hosting, support)
|
||||
412xxx Project revenue (custom dev, web app, website, ERP)
|
||||
413xxx Services (consulting, training, support hourly)
|
||||
414xxx Reseller revenue (third-party software/hardware)
|
||||
419xxx Sales adjustments (discounts, returns, bad debt recovery)
|
||||
|
||||
5xxxxx DIRECT COSTS (COGS)
|
||||
511xxx Infrastructure & hosting costs
|
||||
512xxx Project direct costs (subcontractors, project software, project travel)
|
||||
513xxx Cost of resold goods
|
||||
519xxx COGS adjustments
|
||||
|
||||
6xxxxx OPERATING EXPENSES
|
||||
611xxx Personnel — internal staff (T4)
|
||||
612xxx Personnel — contract (T4A non-project)
|
||||
621xxx Office & facilities
|
||||
631xxx Technology — operating (internal SaaS subs)
|
||||
641xxx Marketing & sales
|
||||
651xxx Professional fees
|
||||
661xxx Insurance
|
||||
671xxx Travel & entertainment
|
||||
681xxx Training & development
|
||||
691xxx Banking & finance charges
|
||||
699xxx Other (bad debt, donations, fines, FX losses, depreciation)
|
||||
|
||||
7xxxxx Other income (interest, FX gains)
|
||||
8xxxxx Other expenses (rare; mostly absorbed in 691/699)
|
||||
```
|
||||
|
||||
**Three analytic plans** (orthogonal tagging, applied per journal line):
|
||||
|
||||
| Plan | Required On | Purpose |
|
||||
|---|---|---|
|
||||
| **Project** | revenue, COGS, project costs | Project P&L, customer profitability, WIP, billable-hour realization |
|
||||
| **Department** | payroll, OpEx | Departmental P&L, overhead allocation |
|
||||
| **SR&ED Tag** | labour, contractors, materials (R&D) | T661 SR&ED claim — eligibility classification |
|
||||
|
||||
## 4. Revenue Accounts (4xxxxx)
|
||||
|
||||
```
|
||||
Recurring Revenue
|
||||
411100 SaaS Subscription Revenue
|
||||
411200 Hosting & Infrastructure Revenue
|
||||
411300 Support & Maintenance Contracts
|
||||
411400 Domain/SSL/Renewal Pass-through Revenue
|
||||
411500 Setup / Onboarding Fees
|
||||
|
||||
Project Revenue (one-time, milestone-billed)
|
||||
412100 Custom Software Development
|
||||
412200 Custom Web Application Development
|
||||
412300 Custom Website Development
|
||||
412400 ERP Implementation & Customization
|
||||
412500 Mobile App Development ← reserved for future
|
||||
412600 Business App / Integration Work
|
||||
|
||||
Services (hourly, retainer)
|
||||
413100 Consulting & Advisory
|
||||
413200 Training & Workshops
|
||||
413300 Technical Support — Per-incident / Hourly
|
||||
|
||||
Reseller / Pass-through
|
||||
414100 Third-party Software Resale (M365, Adobe)
|
||||
414200 Hardware Resale
|
||||
|
||||
Adjustments (contra-revenue)
|
||||
419100 Sales Discounts
|
||||
419200 Sales Returns & Refunds
|
||||
419300 Bad Debt Recovery
|
||||
```
|
||||
|
||||
**Design rule**: one revenue account per service line. Jurisdiction (ON/Atlantic/QC/export/etc.) tracked entirely through tax codes and fiscal positions, NOT duplicate accounts.
|
||||
|
||||
## 5. Direct Costs / COGS (5xxxxx)
|
||||
|
||||
```
|
||||
Infrastructure & Hosting
|
||||
511100 Cloud Infrastructure (AWS, Hetzner, OVH, DigitalOcean, Linode)
|
||||
511110 CDN & Edge Services (Cloudflare, Fastly)
|
||||
511120 Backup & Storage Services
|
||||
511130 Database & Backend Services (Supabase, hosted Postgres, Redis)
|
||||
511140 Monitoring & Observability (customer-facing only)
|
||||
511150 SSL Certificates & Domains (wholesale for resale)
|
||||
511160 DNS & Email Hosting (wholesale)
|
||||
|
||||
Third-party APIs & Per-transaction Costs
|
||||
511200 Third-party API Costs (Twilio, SendGrid, OpenAI)
|
||||
511210 Per-customer Licensing & Royalties
|
||||
|
||||
Note: 511100–511160 are shared between SaaS revenue (411100) and Hosting revenue (411200).
|
||||
Allocation to specific revenue line happens via the Project analytic plan, not separate accounts.
|
||||
|
||||
Project Direct Costs
|
||||
512100 Subcontracted Labour — Canadian (T4A) ← SR&ED-eligible
|
||||
512110 Subcontracted Labour — Foreign ← NOT SR&ED-eligible
|
||||
512200 Project-specific Software & Licenses
|
||||
512300 Project Travel & Onsite (rebilled)
|
||||
512400 Project Hardware (passed through)
|
||||
|
||||
Resold Goods & Services
|
||||
513100 Cost of Software Resold
|
||||
513200 Cost of Hardware Resold
|
||||
|
||||
Adjustments
|
||||
519100 COGS Adjustments / Write-offs
|
||||
```
|
||||
|
||||
**Design choices**:
|
||||
- **Salaries in OpEx, not COGS** — keeps SR&ED tracking clean; allocation to projects via Project analytic plan.
|
||||
- **Stripe/merchant fees in OpEx (691200)** — re-class to COGS later if SaaS revenue dominates.
|
||||
- **Canadian vs Foreign subcontractor split** — critical for SR&ED (80% × 35% = 28% credit on CA arm's length; 0% on foreign).
|
||||
|
||||
## 6. Operating Expenses (6xxxxx)
|
||||
|
||||
```
|
||||
Personnel — Internal Staff (T4)
|
||||
611100 Salaries & Wages — Development ← SR&ED-eligible base
|
||||
611200 Salaries & Wages — Sales & Marketing
|
||||
611300 Salaries & Wages — Admin & Operations
|
||||
611400 Salary — Shareholder/Officer (Gurpreet) ← 75% SR&ED cap (specified employee)
|
||||
611500 Employer CPP / QPP Contributions
|
||||
611600 Employer EI Premiums
|
||||
611700 Employer Health Tax (EHT/QHST)
|
||||
611800 WCB / WSIB Premiums
|
||||
611900 Employee Benefits (health, dental, group)
|
||||
611950 Bonuses & Incentives
|
||||
611960 Vacation Pay Accrual
|
||||
|
||||
Personnel — Contract (non-project)
|
||||
612100 Contract Labour — Canadian (admin/marketing/freelance)
|
||||
612200 Contract Labour — Foreign
|
||||
|
||||
Office & Facilities
|
||||
621100 Rent — Commercial Office
|
||||
621200 Home Office — Business Portion ← own account; allocated %
|
||||
621300 Utilities — Commercial
|
||||
621400 Internet & Phone — Business
|
||||
621500 Office Supplies & Consumables
|
||||
621600 Cleaning & Maintenance
|
||||
621700 Office Snacks & Refreshments
|
||||
|
||||
Technology — Operating
|
||||
631100 Software — Productivity (M365, Slack, Notion, Linear, GitHub)
|
||||
631200 Software — Development Tools (Cursor, Figma, IDEs)
|
||||
631300 Software — Internal Infrastructure
|
||||
631400 Software — Security & IT
|
||||
631500 Software — Sales & Marketing
|
||||
|
||||
Marketing & Sales
|
||||
641100 Advertising — Digital Ads
|
||||
641200 Advertising — Content / SEO
|
||||
641300 Trade Shows & Conferences
|
||||
641400 Promotional Items / Branded Swag
|
||||
641500 Website — Own (nexasystems.ca)
|
||||
|
||||
Professional Fees
|
||||
651100 Legal Fees — General
|
||||
651200 Accounting & Bookkeeping
|
||||
651300 Tax Preparation (T2, T1, GST/HST)
|
||||
651400 Business Consulting
|
||||
|
||||
Insurance
|
||||
661100 Commercial General Liability
|
||||
661200 Professional Liability / E&O
|
||||
661300 Cyber Liability
|
||||
661400 Property Insurance
|
||||
661500 Directors & Officers Insurance
|
||||
|
||||
Travel & Entertainment
|
||||
671100 Travel — Flights, Hotels, Ground Transport
|
||||
671200 Meals & Entertainment — 50% Deductible ← own account; 50% adjustment at year-end
|
||||
671300 Vehicle — Operating
|
||||
671400 Mileage Reimbursement — Personal Vehicle
|
||||
|
||||
Training & Development
|
||||
681100 Conferences & Seminars
|
||||
681200 Courses & Certifications
|
||||
681300 Books & Publications
|
||||
681400 Professional Memberships & Dues
|
||||
|
||||
Banking & Finance
|
||||
691100 Bank Service Charges
|
||||
691200 Merchant Processing Fees (Stripe, PayPal, Square)
|
||||
691300 Wire Transfer & FX Fees
|
||||
691400 Interest Expense — Bank Loans / LOC
|
||||
691500 Interest Expense — Credit Cards
|
||||
691600 Late Payment Penalties — Non-deductible
|
||||
|
||||
Other
|
||||
699100 Bad Debt Expense
|
||||
699200 Donations & Sponsorships
|
||||
699300 Penalties & Fines — Non-deductible
|
||||
699400 Realized FX Losses
|
||||
699500 Depreciation / CCA Expense
|
||||
```
|
||||
|
||||
**Notable design decisions**:
|
||||
- Salaries split by function (dev/sales/admin) — so SR&ED proxy applies cleanly to dev only.
|
||||
- Owner/Shareholder salary isolated (611400) — for T2 Schedule 11 (Compensation of Officers) and CRA reasonableness defence.
|
||||
- Non-deductible items isolated (691600, 699300) — prevents accidental deduction.
|
||||
- Meals & Entertainment own account (671200) — accountant applies the 50% adjustment cleanly.
|
||||
- Home office own account (621200) — business-use % applied to the whole account.
|
||||
|
||||
## 7. Capital Assets & CCA (1xxxxx + asset module)
|
||||
|
||||
```
|
||||
Capital Assets — Cost
|
||||
151100 Computer Hardware & Equipment (CCA Class 50, 55% DB)
|
||||
151200 Office Furniture & Equipment (CCA Class 8, 20% DB)
|
||||
151300 Vehicles (CCA Class 10 / 10.1)
|
||||
151400 Leasehold Improvements (CCA Class 13, SL)
|
||||
151500 Acquired Software/Intangibles (CCA Class 14.1, 5% DB)
|
||||
151600 Tools & Small Equipment <$500 (CCA Class 12, 100% Y1)
|
||||
|
||||
Accumulated Depreciation (contra)
|
||||
154100 Acc. Dep — Computer Hardware
|
||||
154200 Acc. Dep — Office Furniture
|
||||
154300 Acc. Dep — Vehicles
|
||||
154400 Acc. Dep — Leasehold Improvements
|
||||
154500 Acc. Dep — Acquired Software
|
||||
```
|
||||
|
||||
**Asset model approach**: book straight-line depreciation in Odoo for financial reporting (clean monthly journal); maintain CCA schedule separately for T2 filing. CCA rates: Class 50 effective 82.5% Y1 (with AccII through 2027); Class 14.1 software 100% Y1; Class 12 small tools 100% Y1.
|
||||
|
||||
## 8. Tax Accounts (1xxxxx + 2xxxxx)
|
||||
|
||||
```
|
||||
Tax Assets
|
||||
118100 HST/GST Input Tax Credit (ITC) Receivable
|
||||
118200 HST/GST Instalments Paid
|
||||
118300 QST Input Tax Refund Receivable
|
||||
|
||||
Tax Liabilities
|
||||
213100 HST/GST Collected on Sales ← single bucket; tax report breaks down by code
|
||||
213500 QST Collected
|
||||
214100 Net HST/GST Payable
|
||||
215100 Source Deductions Payable — Federal Tax
|
||||
215200 Source Deductions Payable — CPP
|
||||
215300 Source Deductions Payable — EI
|
||||
216100 Corporate Income Tax — Federal Payable
|
||||
216200 Corporate Income Tax — Provincial Payable
|
||||
216300 Corporate Tax Instalments Paid (contra)
|
||||
```
|
||||
|
||||
## 9. Shareholder, Associated Corporations & Equity
|
||||
|
||||
**Associated corporations** (Gurpreet >25% owner of each → ITA s.256 associated group):
|
||||
- Nexa Systems Inc (this company)
|
||||
- Westin Healthcare Inc
|
||||
- Divine Mobility Inc
|
||||
|
||||
**Treatment**: Westin and Divine are **regular Customers and Vendors of Nexa**, NOT slush accounts. Their transactions flow through normal AR/AP. They get partner records tagged `Related Party — Associated Corporation` for disclosure tracking. The "Due To/From Related Party" GL buckets exist only for true intercompany loans (cash moved between the corps' bank accounts without an invoice).
|
||||
|
||||
```
|
||||
Due From — Assets
|
||||
115100 Due From Shareholder — Gurpreet
|
||||
115900 Due From Associated Corporations (intercompany loans only — NOT customer AR)
|
||||
|
||||
Due To — Liabilities
|
||||
221100 Due To Shareholder — Gurpreet (short-term, <1 year)
|
||||
221200 Shareholder Loan — Gurpreet (long-term, with commercial terms)
|
||||
222900 Due To Associated Corporations (intercompany loans only — NOT vendor AP)
|
||||
|
||||
Equity
|
||||
311100 Share Capital — Common Shares
|
||||
311200 Share Capital — Preferred Shares (placeholder)
|
||||
311300 Contributed Surplus
|
||||
321100 Retained Earnings — Current Year
|
||||
321200 Retained Earnings — Prior Years
|
||||
321900 Dividends Declared (contra)
|
||||
```
|
||||
|
||||
**Partner setup** (under Contacts, not GL accounts):
|
||||
- `Westin Healthcare Inc` → partner with both Customer and Vendor flags; tagged `RP-Associated`
|
||||
- `Divine Mobility Inc` → partner with both Customer and Vendor flags; tagged `RP-Associated`
|
||||
- Nexa invoices Westin/Divine like any client → AR in 112xxx, revenue in 4xxxxx, HST 13% (Ontario)
|
||||
- Westin/Divine bill Nexa → AP in 211xxx, expense in 6xxxxx / COGS in 5xxxxx
|
||||
|
||||
**Intercompany compliance flags (CRITICAL — drives major tax decisions)**:
|
||||
|
||||
1. **Small Business Deduction (SBD) sharing — ITA s.125(5.1)**: The $500k federal SBD limit is **shared across all associated corporations**. If Nexa, Westin, and Divine are each profitable, they collectively get **one** $500k pool, not three. The corps must file Schedule 23 (T2) allocating the limit. Strategy: allocate the limit to whichever corp has the highest taxable income each year.
|
||||
|
||||
2. **SR&ED expenditure limit shared — ITA s.127(10.2)**: The $3M expenditure limit for the 35% refundable ITC is also shared across the associated group. Same Schedule 23 mechanism. Nexa being the dev shop probably consumes most/all of it.
|
||||
|
||||
3. **Transfer pricing — ITA s.247**: Services between related corps must be priced at fair market value. Nexa invoicing Westin at $50/hr while billing arm's-length clients $150/hr will be scrutinized. Document the rate methodology. Penalty for non-compliance is 10% of the adjustment.
|
||||
|
||||
4. **Subsection 15(2) shareholder loans**: outstanding >1 year past FY end → taxable to Gurpreet personally.
|
||||
|
||||
5. **T2 Schedule 9** (Related and Associated Corporations) must be filed by Nexa listing Westin and Divine.
|
||||
|
||||
6. **GAAR risk**: aggressive intercompany pricing or loan arrangements designed primarily for tax benefit can be challenged under general anti-avoidance rules.
|
||||
|
||||
## 10. Analytic Plans
|
||||
|
||||
### 10.1 Project Plan
|
||||
- One analytic account per customer engagement
|
||||
- Naming: `PRJ-{YYYY}-{CUST}-{SHORTNAME}` (e.g., `PRJ-2026-WESTIN-ERP`)
|
||||
- Required on revenue, COGS, project costs
|
||||
- Linked to Odoo Project module for time tracking → automatic GL posting
|
||||
|
||||
### 10.2 Department Plan
|
||||
- `DEPT-DEV` — Development
|
||||
- `DEPT-SALES` — Sales & Marketing
|
||||
- `DEPT-ADMIN` — Admin & Operations
|
||||
- `DEPT-HOSTING` — Hosting Operations (optional future split)
|
||||
- Required on payroll, OpEx
|
||||
|
||||
### 10.3 SR&ED Tag Plan
|
||||
- `SRED-T4-DEV-SALARY` — T4 dev employees on R&D (full proxy 55%)
|
||||
- `SRED-SPECIFIED-EMPLOYEE` — Gurpreet/officers (75% basic salary cap)
|
||||
- `SRED-CONTRACTOR-CA-ARM-LENGTH` — Canadian arm's length (80% eligible)
|
||||
- `SRED-CONTRACTOR-CA-NON-ARM-LENGTH` — affiliated CA contractors
|
||||
- `SRED-MATERIALS-CONSUMED` — R&D materials
|
||||
- `SRED-OVERHEAD-PROXY-BASIS` — direct labour basis
|
||||
- `NOT-ELIGIBLE` — default
|
||||
|
||||
**T661 generation at year-end**: filter analytic report on SR&ED tag → eligible salaries + 55% proxy + 80% contractor + materials = total qualified expenditures × 35% refundable ITC.
|
||||
|
||||
## 11. Tax Setup & Fiscal Positions
|
||||
|
||||
**Consolidated active taxes** (~14, down from 49):
|
||||
|
||||
| Tax | Rate | Sale / Purchase | Applies |
|
||||
|---|---|---|---|
|
||||
| HST 13% Ontario | 13% | Both | ON |
|
||||
| HST 15% Atlantic | 15% | Both | NB, NS, PE, NL |
|
||||
| GST 5% | 5% | Both | AB, MB, SK, BC, YT, NT, NU |
|
||||
| GST 5% + PST 7% BC | 12% group | Both | BC (goods, rare for services) |
|
||||
| GST 5% + PST 7% MB | 12% group | Both | MB |
|
||||
| GST 5% + PST 6% SK | 11% group | Both | SK |
|
||||
| GST 5% + QST 9.975% QC | 14.975% group | Both | QC |
|
||||
| Zero-rated Export | 0% | Sale | US, EU, ROW |
|
||||
| Tax Exempt | 0% | Sale | Cert-holders |
|
||||
|
||||
**Fiscal Positions** (auto-applied based on customer billing address):
|
||||
|
||||
| Position | Customer Location | Auto-Substitute Default Tax |
|
||||
|---|---|---|
|
||||
| CA — Ontario (default) | ON | HST 13% |
|
||||
| CA — Atlantic | NB/NS/PE/NL | HST 15% |
|
||||
| CA — Quebec | QC | GST 5% + QST 9.975% |
|
||||
| CA — BC | BC | GST 5% (PST per-product) |
|
||||
| CA — Prairies / Territories | AB/MB/SK/YT/NT/NU | GST 5% |
|
||||
| Export — US | United States | 0% Zero-rated |
|
||||
| Export — International | Outside CA/US | 0% Zero-rated |
|
||||
| Tax Exempt | Tagged customers | 0% |
|
||||
|
||||
**Invoice flow**: customer → fiscal position auto-applies → product picks default tax → fiscal position substitutes → no manual tax decisions.
|
||||
|
||||
**Export advantage**: zero-rated sales charge no HST but retain ITC claims on all related inputs. For a small shop with 30% US revenue, this is ~$5–15k/year in recovered HST.
|
||||
|
||||
## 12. Cleanup Plan
|
||||
|
||||
### Phase 1 — Archive (~370 accounts)
|
||||
- Every l10n_ca account NOT in the keep-list (built from Sections 4–9).
|
||||
- Constraint: Odoo blocks archiving accounts with postings. Archive zero-history only.
|
||||
- Accounts with history we no longer want: stop posting; they go to $0 going forward.
|
||||
|
||||
### Phase 2 — Rename (~20 accounts)
|
||||
|
||||
| Old | New |
|
||||
|---|---|
|
||||
| 1400 Transferred to Gurpreet | 221100 Due To Shareholder — Gurpreet |
|
||||
| 1505 Sent to India | 612200 Contract Labour — Foreign |
|
||||
| 1580 Transferred to Westin | ARCHIVE — Westin is an associated corp, future transactions go through normal AR/AP via partner record `Westin Healthcare Inc` |
|
||||
| 1590 Transferred to Divine | ARCHIVE — Divine is an associated corp, future transactions go through normal AR/AP via partner record `Divine Mobility Inc` |
|
||||
| 1600 Transferred to Manpreet | ARCHIVE — Manpreet is an employee of another company, not a related party of Nexa; historical transactions to be re-classified by accountant during reconciliation |
|
||||
| 1500 Food & Entertainment | 671200 Meals & Entertainment — 50% Deductible |
|
||||
| 1501 Office Expenses | 621500 Office Supplies & Consumables |
|
||||
| 411000 Inside Sales | ARCHIVE (replaced by 412xxx) |
|
||||
| 412000 Harmonized Provinces Sales | ARCHIVE (jurisdiction = tax codes) |
|
||||
| 413000 Non-Harmonized Provinces Sales | ARCHIVE |
|
||||
| 414000 International Sales | ARCHIVE |
|
||||
| 12000 Abdul & Future Mobility | ARCHIVE (use partner subledger) |
|
||||
| 12001 MSI Account | ARCHIVE |
|
||||
| 110010 Bank Fee | 691100 Bank Service Charges |
|
||||
| 511100 Inside Purchases | ARCHIVE |
|
||||
|
||||
### Phase 3 — Add (~70 new accounts)
|
||||
All per Sections 4–9.
|
||||
|
||||
### Phase 4 — Bank Consolidation
|
||||
Current 8 bank journals (BMO, RBC, RBC VISA, Scotia ×3, Bank, Cash). Audit; archive inactive. Target: ≤5 active (primary operating, USD for future global, LOC, 1–2 credit cards).
|
||||
|
||||
### Phase 5 — Lock Prior Periods
|
||||
Set `fiscalyear_lock_date = 2025-12-31`. Blocks postings to closed periods. Forces all 2026 work into new structure.
|
||||
|
||||
## 13. Automation Hooks
|
||||
|
||||
### Product Categories with Default Accounts
|
||||
|
||||
| Product Category | Default Income | Default COGS | Default Tax |
|
||||
|---|---|---|---|
|
||||
| Services / SaaS Subscription | 411100 | — | per fiscal position |
|
||||
| Services / Hosting | 411200 | — | per fiscal position |
|
||||
| Services / Support Contract | 411300 | — | per fiscal position |
|
||||
| Services / Custom Software Dev | 412100 | — | per fiscal position |
|
||||
| Services / Web App Dev | 412200 | — | per fiscal position |
|
||||
| Services / Website Dev | 412300 | — | per fiscal position |
|
||||
| Services / ERP Implementation | 412400 | — | per fiscal position |
|
||||
| Services / Consulting | 413100 | — | per fiscal position |
|
||||
| Services / Training | 413200 | — | per fiscal position |
|
||||
| Services / Setup Fee | 411500 | — | per fiscal position |
|
||||
| Resale / Software | 414100 | 513100 | per fiscal position |
|
||||
| Resale / Hardware | 414200 | 513200 | per fiscal position |
|
||||
|
||||
### Bank Reconciliation Rules
|
||||
|
||||
| Pattern (description contains) | Auto-categorize To | Tax |
|
||||
|---|---|---|
|
||||
| `AMAZON WEB SERVICES`, `AWS` | 511100 Cloud Infrastructure | HST 13% ITC |
|
||||
| `HETZNER`, `OVH`, `DIGITALOCEAN`, `LINODE` | 511100 | 0% foreign |
|
||||
| `CLOUDFLARE`, `FASTLY` | 511110 CDN | mixed |
|
||||
| `GITHUB`, `JETBRAINS`, `CURSOR` | 631200 Software — Dev Tools | HST 13% ITC |
|
||||
| `MICROSOFT`, `SLACK`, `NOTION`, `LINEAR` | 631100 Software — Productivity | HST 13% ITC |
|
||||
| `STRIPE PAYOUT` | AR receipts journal | — |
|
||||
| `STRIPE FEE` | 691200 Merchant Processing | exempt |
|
||||
| `GOOGLE ADS`, `LINKEDIN ADS` | 641100 Advertising | HST 13% ITC |
|
||||
|
||||
### Bank Feeds (Plaid via Odoo Enterprise)
|
||||
Daily auto-import → bank reconciliation rules → ~70% of transactions auto-categorized.
|
||||
|
||||
### Subscription Module
|
||||
Already installed. Use for SaaS/Hosting/Support contracts: recurring invoices, Stripe auto-charge, MRR/ARR/churn dashboards.
|
||||
|
||||
### Default Journals
|
||||
- Customer Invoices → `INV`
|
||||
- Vendor Bills → `BILL`
|
||||
- Bank feeds → respective bank journals
|
||||
- HR Expenses → `EXP` (add if missing)
|
||||
- Misc → `MISC`
|
||||
- Exchange Difference → `EXCH`
|
||||
|
||||
## 14. Out-of-Scope (Future Sub-Projects)
|
||||
|
||||
- **Historical reconciliation** — load accountant's Excel records into new structure (requires accountant docs).
|
||||
- **Custom CCA module** — only if asset count grows; until then, accountant maintains CCA schedule separately.
|
||||
- **Multi-currency setup** — add USD bank + currency-rate-live config when first US client signs.
|
||||
- **Payroll system** — when first T4 employee is hired; integrate with Wagepoint/Payworks/ADP or Odoo Payroll.
|
||||
- **Approval workflows** — purchase approval, expense approval limits.
|
||||
- **Inventory** — N/A unless reselling hardware regularly.
|
||||
|
||||
## 15. Tax-Saving Opportunities Enabled
|
||||
|
||||
| Opportunity | Mechanism | Estimated Annual Value | Notes |
|
||||
|---|---|---|---|
|
||||
| SR&ED ITC | Analytic SR&ED tag + T661 filing | $30k–$100k (refundable) | **$3M expenditure limit SHARED across Nexa/Westin/Divine — allocate to Nexa via S23** |
|
||||
| Zero-rated exports | Fiscal position for US/international | $5–15k recovered HST on inputs | Per-company |
|
||||
| Small Business Deduction (SBD) | Federal 9% on first $500k taxable income | ~$30k/yr if hitting threshold | **$500k limit SHARED across associated group — allocate to highest-income corp via S23** |
|
||||
| CCA Class 50 + AccII | 82.5% Y1 deduction on computers/servers | Time-value, front-loads deductions | Per-company |
|
||||
| Quick Method GST/HST | If <$400k sales, simpler method | $500–2k/yr cash if eligible | **LIKELY UNAVAILABLE — Quick Method $400k threshold applies to associated-group totals; Nexa + Westin + Divine combined revenue probably exceeds limit. Re-verify with accountant.** |
|
||||
| OIDMTC (Ontario Interactive Digital Media) | If building interactive media products | 35–40% of eligible labour | Strict eligibility test; need to verify product fits |
|
||||
| Apprenticeship Job Creation TC | 10% of eligible apprentice wages, max $2k/yr per apprentice | Per apprentice hired | Activates when first apprentice T4 employee hired |
|
||||
| Intercompany cost recovery | Bill associated corps for shared services (back-office, hosting, IT) | Allocates expenses to highest-tax-rate corp | Requires arm's-length pricing documentation |
|
||||
|
||||
## 16. Risks & Open Questions
|
||||
|
||||
1. **Associated corporation tax planning** — Westin Healthcare Inc, Divine Mobility Inc, and Nexa Systems Inc share the $500k SBD limit and the $3M SR&ED expenditure limit. Yearly Schedule 23 allocation decision needs accountant input. Recommendation: allocate SR&ED limit primarily to Nexa (dev shop); allocate SBD to whichever corp has highest taxable income each year.
|
||||
2. **Transfer pricing on intercompany services** — Nexa billing Westin/Divine must be at fair market value. Document hourly rate methodology and apply consistently across all clients. Penalty: 10% of any adjustment.
|
||||
3. **Past data backposting** — once accountant records arrive, mapping old transactions into new structure requires care to avoid breaking the post-2025-12-31 lock.
|
||||
4. **BC PST on software services** — BC PST exempts custom software developed for a specific customer; off-the-shelf software and certain SaaS subscriptions ARE taxable. For Nexa's mix (most work is custom dev = exempt; SaaS sold off-the-shelf to BC customers = taxable at 7%), each BC customer/product combo needs review. Default to "GST only" for custom dev; flag SaaS-to-BC for review at first sale.
|
||||
5. **Quebec QST registration** — required if Nexa has QC customers and revenue >$30k. Confirm registration status. If not yet registered and you start taking QC clients, registration with Revenu Québec is separate from CRA.
|
||||
8. **HST filing cadence review** — currently annual. Once revenue clears $1.5M (combined Nexa-only, not associated group), CRA may auto-move you to **quarterly** filing. Monitor and update filing cadence in tax report config when it happens.
|
||||
6. **Specified employee SR&ED math** — Gurpreet's salary cap is 75%, no bonus inclusion. Accountant must apply at T661 time.
|
||||
7. **Multi-company Odoo (future sub-project)** — Westin and Divine currently run on separate Odoo databases (odoo-westin, odoo-mobility). Future option: migrate all three into one multi-company nexamain database to enable auto-mirrored intercompany invoices (Nexa invoices Westin → auto-creates Bill in Westin's books). Major data-migration effort; only worth it once intercompany volume justifies the effort.
|
||||
|
||||
## 17. Acceptance Criteria
|
||||
|
||||
- [ ] All 11 sections of CoA approved and present in odoo-nexa nexamain DB
|
||||
- [ ] ≥370 unused accounts archived
|
||||
- [ ] 14 active taxes (down from 49)
|
||||
- [ ] 8 fiscal positions configured with auto-detection
|
||||
- [ ] 3 analytic plans created (Project, Department, SR&ED Tag) with seed analytic accounts
|
||||
- [ ] Product categories created with default accounts
|
||||
- [ ] Bank reconciliation rules created
|
||||
- [ ] Fiscal year locked at 2025-12-31
|
||||
- [ ] Company HST/BN number stored in full 15-char form (`741224877 RT0001`)
|
||||
- [ ] HST report config set to **annual filer**, fiscal-year-end Dec 31, deadline March 31
|
||||
- [ ] Westin Healthcare Inc and Divine Mobility Inc partner records created with Customer + Vendor flags, tagged `RP-Associated`
|
||||
- [ ] Test invoice flows through correctly for: ON customer (HST 13%), US customer (Zero-rated), QC customer (GST+QST)
|
||||
- [ ] Test vendor bill creates correct ITC for: Canadian vendor (HST ITC), foreign vendor (no ITC)
|
||||
- [ ] Test intercompany invoice: Nexa → Westin generates proper AR + 13% HST collected (Westin is Ontario-based)
|
||||
- [ ] Bank consolidation complete; ≤5 active bank journals
|
||||
300
docs/superpowers/specs/2026-05-13-nfc-clock-kiosk-design.md
Normal file
300
docs/superpowers/specs/2026-05-13-nfc-clock-kiosk-design.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# NFC Clock Kiosk — Design
|
||||
|
||||
**Date:** 2026-05-13
|
||||
**Module:** `fusion_clock`
|
||||
**Status:** Approved design — pending implementation plan
|
||||
**Pilot scope:** 1 station per company
|
||||
|
||||
## Problem
|
||||
|
||||
`fusion_clock` already supports shared-device clock-in/out via a PIN kiosk at `/fusion_clock/kiosk`. Shop-floor employees find name search + PIN entry slow, and shared PINs make buddy-punching trivial. The company is rolling out Ubiquiti UniFi Access NFC readers for door entry, so every employee already carries an NFC card. We want a "tap-and-go" kiosk that:
|
||||
|
||||
- Takes ~2 seconds (vs ~10 seconds for name search + PIN)
|
||||
- Reuses the same physical Ubiquiti-issued card the employee uses for doors
|
||||
- Works with gloves, dirty hands, or wet hands (touchscreens fail here)
|
||||
- Captures a silent photo at every tap so managers can spot-check buddy-punching attempts
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Tap-to-clock**: NFC card tap on a wall-mounted Android tablet → attendance state toggles in Odoo within ~1 second of the tap
|
||||
2. **Single-credential**: same card the employee uses for door access also clocks them in
|
||||
3. **Silent photo verification**: front camera snaps a frame on every tap; manager dashboard shows photos for spot-check
|
||||
4. **Self-contained kiosk**: lockable into a single-purpose device, no escape, auto-restart on crash, no Odoo navbar visible
|
||||
5. **Reuses existing fusion_clock backend**: geofencing, penalty rules, activity log, attendance lifecycle — all unchanged
|
||||
6. **One-time setup**: enroll once, then employees never touch a setup flow again
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Multi-station / multi-zone clocking (future — pilot is 1 station per company)
|
||||
- Per-station geolocation (one location per company; tablet is implicitly at the company location)
|
||||
- Offline mode (v1 fails loudly on network loss; offline replay is future work)
|
||||
- Phone-as-credential support (NFC HCE on Android is fragile; iPhone NFC is closed)
|
||||
- QR code alternate credential (deferred to v1.1 if iPhone-only employees push back)
|
||||
- Native Android kiosk app (overkill for a 1-2 station pilot; Web NFC is sufficient)
|
||||
|
||||
## Architecture decision
|
||||
|
||||
**Option B: Separate kiosk page, shared backend.**
|
||||
|
||||
A new route `/fusion_clock/kiosk/nfc` and a new lean template optimized for tap-and-go. The new controller (`controllers/clock_nfc_kiosk.py`) calls into the existing `FusionClockAPI` helpers (`_verify_location`, `_attendance_action_change`, `_log_activity`, `_check_and_create_penalty`, `_apply_break_deduction`) so all geofencing/penalty/activity logic is shared with the PIN kiosk. The existing `/fusion_clock/kiosk` route is untouched.
|
||||
|
||||
**Why not extend the existing kiosk (Option A):** existing PIN kiosk page would get tap-mode JS interleaved with PIN-mode JS, increasing the regression surface for both modes.
|
||||
|
||||
**Why not native Android app (Option C):** maintaining a Kotlin app + Play Console signing/distribution doubles the dev effort for marginal UX gain. Web NFC + Chrome kiosk is production-proven (gyms, warehouses, healthcare check-in).
|
||||
|
||||
## Hardware decision
|
||||
|
||||
**Per company:** 1× Samsung Galaxy Tab Active 5 Pro (10.1") on an official Samsung Pogo charging dock, wall-mounted. Reasoning:
|
||||
|
||||
- Built-in NFC antenna on the back, dead-center
|
||||
- IP68, MIL-STD-810H, drop-resistant (shop-floor durable)
|
||||
- Replaceable battery (avoids battery-swelling failure mode in 24/7-tethered devices)
|
||||
- Knox enables true kiosk lockdown
|
||||
- Pogo dock = magnetic constant power, no cable to yank
|
||||
- 10.1" screen visible from a few feet away (vs 8" on regular Active 5)
|
||||
|
||||
Cards: same Ubiquiti-issued NFC cards employees already carry. Web NFC reads the card's UID via `NDEFReader`'s `serialNumber` field, which works on raw MIFARE access cards even though they have no NDEF data.
|
||||
|
||||
## Data model
|
||||
|
||||
### `hr.employee` — new field
|
||||
- `x_fclk_nfc_card_uid` — `Char`, indexed, unique constraint when not null
|
||||
- Stores card UID as canonical hex (uppercase, colon-separated, MSB first), e.g., `04:A2:B5:62:C1:80`
|
||||
- Editable by HR managers; visible on the employee form in the existing "Clock Settings" section near the existing PIN field
|
||||
|
||||
### `res.company` — new field
|
||||
- `x_fclk_nfc_kiosk_location_id` — `Many2one` to `fusion.clock.location`
|
||||
- Designates which fusion.clock.location is bound to the NFC kiosk for this company
|
||||
- Required when `fusion_clock.enable_nfc_kiosk = True`; the tap endpoint returns `no_location_configured` if it's empty
|
||||
- Editable in the NFC Clock Kiosk settings section (per-company since this is multi-company-aware)
|
||||
|
||||
### `hr.attendance` — new fields
|
||||
- `x_fclk_check_in_photo` — `Binary`, `attachment=True`. Frame captured at clock-in.
|
||||
- `x_fclk_check_out_photo` — `Binary`, `attachment=True`. Frame captured at clock-out.
|
||||
- `x_fclk_clock_source` — extend existing `Selection` field to include `'nfc_kiosk'`.
|
||||
|
||||
### `ir.config_parameter` — new entries
|
||||
- `fusion_clock.enable_nfc_kiosk` — Boolean, default `False`. Master switch.
|
||||
- `fusion_clock.nfc_photo_required` — Boolean, default `True`. If False, photo is best-effort and tap still succeeds without one.
|
||||
- `fusion_clock.nfc_enroll_password` — Char, default empty. Short password the manager types to enter Enroll Mode on the kiosk. If empty, falls back to manager-group membership of the kiosk service user.
|
||||
- `fusion_clock.nfc_kiosk_debug` — Boolean, default `False`. Enables a hidden mock-tap keyboard shortcut for development.
|
||||
|
||||
### `res.config.settings` — new view section
|
||||
"NFC Clock Kiosk" section in the Clock settings page exposing the four `ir.config_parameter` toggles above.
|
||||
|
||||
**No new models.** All data piggybacks on existing `hr.employee`, `hr.attendance`, `fusion.clock.activity.log`.
|
||||
|
||||
## Backend — controller and endpoints
|
||||
|
||||
**New file:** `controllers/clock_nfc_kiosk.py`
|
||||
|
||||
All endpoints under `/fusion_clock/kiosk/nfc/...`. All require `fusion_clock.group_fusion_clock_manager` on the logged-in kiosk service user. All gated on `fusion_clock.enable_nfc_kiosk == 'True'`.
|
||||
|
||||
**Kiosk service user:** an Odoo `res.users` record created per-company specifically for the tablet to log in as. Member of `fusion_clock.group_fusion_clock_manager`. Long random password stored in the tablet's saved-credentials. Distinct from any human user so its session can be revoked independently if the tablet is stolen. Setup is documented in the provisioning script below; no new code creates this user (it's a manual one-time creation in HR Settings).
|
||||
|
||||
### `GET /fusion_clock/kiosk/nfc` — page render
|
||||
- Renders the NFC kiosk QWeb template
|
||||
- Resolves the kiosk's location from `request.env.company.x_fclk_nfc_kiosk_location_id` and passes its name to the template for display ("Clock at: Westin Plant 1")
|
||||
- Returns redirect to `/my` if the kiosk is disabled or the user lacks the manager group
|
||||
|
||||
### `POST /fusion_clock/kiosk/nfc/tap` — clock toggle
|
||||
- `type='jsonrpc'`, `auth='user'`
|
||||
- Input: `{ card_uid: "04:A2:B5:62:C1:80", photo_b64: "data:image/jpeg;base64,..." (optional) }`
|
||||
- Logic:
|
||||
1. Normalize UID (uppercase, colon-separated, reject malformed input)
|
||||
2. Lookup `hr.employee` by `x_fclk_nfc_card_uid` (sudo). Not found → `{error: "card_unknown", message: "Card not enrolled"}`. Log to `fusion.clock.activity.log` with the unknown UID.
|
||||
3. If `x_fclk_enable_clock` is False → `{error: "clock_disabled"}`
|
||||
4. Resolve location from `request.env.company.x_fclk_nfc_kiosk_location_id`. If empty → `{error: "no_location_configured"}`
|
||||
5. Server-side debounce: if same UID was tapped within the last 5 seconds, return `{error: "debounce"}` silently
|
||||
6. Call `FusionClockAPI._attendance_action_change(geo_info)` with `geo_info = { browser: 'nfc_kiosk', ip_address: <remote_addr>, latitude: 0, longitude: 0 }` to toggle attendance state
|
||||
7. Write `x_fclk_clock_source = 'nfc_kiosk'`, `x_fclk_location_id = <resolved>`, distance fields = 0
|
||||
8. If `photo_b64` present, decode and save to `x_fclk_check_in_photo` (clock-in) or `x_fclk_check_out_photo` (clock-out)
|
||||
9. If `nfc_photo_required = True` and photo is missing/decode-failed → reject the tap with `{error: "photo_required"}`
|
||||
10. Reuse `_check_and_create_penalty`, `_apply_break_deduction`, `_log_activity` calls (same as PIN kiosk)
|
||||
11. Return `{ success: true, action: 'clock_in' | 'clock_out', employee_name, employee_avatar_url, message, net_hours_today }`
|
||||
|
||||
### `POST /fusion_clock/kiosk/nfc/enroll` — card enrollment
|
||||
- `type='jsonrpc'`, `auth='user'`
|
||||
- Input: `{ employee_id: 42, card_uid: "04:A2:B5:62:C1:80", enroll_password: "1234" }`
|
||||
- Logic:
|
||||
1. Verify `enroll_password` matches `fusion_clock.nfc_enroll_password` (or accept if config is empty AND caller is in manager group)
|
||||
2. Normalize UID
|
||||
3. Check no other employee has this UID → `{error: "card_already_assigned", existing_employee: "<name>"}`
|
||||
4. Write `x_fclk_nfc_card_uid` on the target employee
|
||||
5. Log to `fusion.clock.activity.log` ("Manager X enrolled card UID Y to employee Z")
|
||||
6. Return `{ success: true, employee_name, card_uid }`
|
||||
|
||||
### `POST /fusion_clock/kiosk/nfc/employee_search` — pick employee for enroll
|
||||
- Reuses the existing `/fusion_clock/kiosk/search` controller method by importing it; does not duplicate logic.
|
||||
|
||||
## Frontend — kiosk page UX
|
||||
|
||||
**Files:**
|
||||
- `views/kiosk_nfc_templates.xml` — QWeb template for the page
|
||||
- `static/src/js/fusion_clock_nfc_kiosk.js` — Web NFC + camera + state machine
|
||||
- `static/src/css/nfc_kiosk.css` — high-contrast shop-floor styling (always dark)
|
||||
|
||||
**Visual:** always-dark, high-contrast, no Odoo navbar. Shop-floor lighting washes out light backgrounds.
|
||||
|
||||
### State machine
|
||||
|
||||
```
|
||||
┌─── (3s timeout) ─────────────────────────┐
|
||||
▼ │
|
||||
┌─────────────────────────┐ tap detected ┌────────────────────┐
|
||||
│ IDLE │ ────────────────► │ PROCESSING │
|
||||
│ "Tap card to clock │ │ spinner, "Reading"│
|
||||
│ in or out" │ └────────────────────┘
|
||||
│ big clock, date, │ │
|
||||
│ company name │ success / error
|
||||
└─────────────────────────┘ ▼
|
||||
▲ ┌─────────────────────────┐
|
||||
│ │ RESULT │
|
||||
│ │ green: "Welcome John, │
|
||||
└─── (3s) ──────────────────│ CLOCKED IN, 8:02 AM" │
|
||||
│ red: "Card not │
|
||||
│ enrolled" │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### IDLE state
|
||||
- Top: company name + current time (HH:MM, updates every second) + date
|
||||
- Center: large NFC icon + "Tap your card to clock in or out", subtle pulse animation
|
||||
- Bottom-right corner: tiny "⚙" icon (gateway to Enroll Mode)
|
||||
|
||||
### PROCESSING state
|
||||
- Brief spinner + "Reading card…"
|
||||
- Mostly imperceptible at typical network latency
|
||||
|
||||
### RESULT state — success
|
||||
- Green panel
|
||||
- Large employee avatar on the left
|
||||
- "John Smith" — name in big text
|
||||
- "CLOCKED IN at 8:02 AM" or "CLOCKED OUT — 8.1h today"
|
||||
- Auto-return to IDLE after 3s
|
||||
|
||||
### RESULT state — error
|
||||
- Red panel
|
||||
- `card_unknown` → "Card not recognized. See your manager."
|
||||
- `network_error` → "No connection. Please try again."
|
||||
- `debounce` → silent (no UI change to avoid double-tap confusion)
|
||||
- `photo_required` → "Camera unavailable. Ask IT to check the kiosk."
|
||||
- Auto-return to IDLE after 4s
|
||||
|
||||
### Web NFC implementation
|
||||
- One-time activation button on first page load: "Tap here to enable NFC reader" (Web NFC requires a user gesture before `scan()` is permitted)
|
||||
- After activation, `NDEFReader.scan()` runs continuously
|
||||
- `reading` event fires for any tap; we extract `event.serialNumber` (works for raw MIFARE access cards even with no NDEF data)
|
||||
- UID format: hex bytes joined by colons, uppercased
|
||||
- If `scan()` throws, restart with a 1-second backoff
|
||||
|
||||
### Camera implementation
|
||||
- `getUserMedia({ video: { facingMode: 'user' } })` activated alongside NFC
|
||||
- Hidden `<video>` element streams continuously
|
||||
- On tap, grab one frame to a `<canvas>`, encode as JPEG quality 0.7 (~30–60 KB), POST as base64 in the same JSON payload as the UID
|
||||
- If `nfc_photo_required = True` and camera is unavailable → tap is rejected ("Camera unavailable") rather than silently degrading
|
||||
|
||||
### Enroll Mode
|
||||
- Tap the bottom-right "⚙" → on-screen numpad password entry → match against `fusion_clock.nfc_enroll_password` → enter Enroll Mode
|
||||
- Enroll Mode UI:
|
||||
1. Search input → employee list (uses `/fusion_clock/kiosk/nfc/employee_search`)
|
||||
2. Manager picks employee → "Now tap John Smith's card on the back of the tablet"
|
||||
3. Tap detected → POST to `/enroll` → "✓ Card 04:A2:B5:62:C1:80 enrolled to John Smith. Enroll another?"
|
||||
4. "Done" button → exit Enroll Mode → back to IDLE
|
||||
- 60-second inactivity timeout in Enroll Mode → auto-exit to IDLE (so an unattended kiosk doesn't stay open in admin mode)
|
||||
|
||||
### One-time setup flow (first load on a new tablet)
|
||||
1. "Welcome to Fusion Clock NFC Kiosk." — large tap-to-continue button (this gesture activates Web NFC)
|
||||
2. Browser permission prompts: NFC, then Camera. Page text guides the manager through each.
|
||||
3. Test prompt: "Tap any card to verify reader is working" → shows the UID detected → "Reader OK ✓"
|
||||
4. "Setup complete." → enters IDLE
|
||||
- After setup, page auto-resumes IDLE on every reload (Web NFC permission is sticky per origin, so no re-prompts)
|
||||
|
||||
### Mock-tap debug mode
|
||||
- Gated by `fusion_clock.nfc_kiosk_debug = True`
|
||||
- When enabled, hidden keyboard shortcut `Ctrl+Shift+T` fires a mock tap with a configurable UID stored in localStorage
|
||||
- Off in production; useful for dev iteration on the UI state machine without hardware, and for support troubleshooting
|
||||
|
||||
## Edge cases & failure modes
|
||||
|
||||
| Scenario | Behavior |
|
||||
|---|---|
|
||||
| Card not enrolled | Red screen "Card not recognized. See your manager." Activity logged with the unknown UID. No attendance change. |
|
||||
| Employee disabled (`x_fclk_enable_clock=False`) | "Clock disabled for this account." Activity logged. |
|
||||
| Card lost/damaged | Manager opens employee form, clears `x_fclk_nfc_card_uid`, issues new card, re-enrolls via kiosk Enroll Mode. |
|
||||
| Card already assigned during enroll | "This card is already assigned to Jane Doe. Unenroll first." No silent overwrite. |
|
||||
| Tablet offline / WiFi drops | Fail loudly: "No connection. Use the portal on your phone." No local cache in v1. |
|
||||
| Same card tapped twice within 5s | Server-side debounce. Second tap silently ignored. |
|
||||
| MIFARE clone attack | UIDs can be cloned with cheap hardware. Mitigation = the photo. Manager dashboard surfaces photos for spot-check. Cards alone are not treated as secure. |
|
||||
| Tablet stolen | Knox remote wipe + revoke kiosk service user credentials in Odoo (instantly invalidates that tablet's session). |
|
||||
| Power outage | Tab Active battery covers brief outages. Full reboot → Chrome+Fully Kiosk auto-launch the kiosk URL. Setup is sticky → goes straight to IDLE. |
|
||||
| Tablet clock drift | Irrelevant. All timestamps come from `fields.Datetime.now()` server-side. Tablet clock is for display only. |
|
||||
| UID format mismatch (Ubiquiti vs Web NFC byte order) | Normalize on the server: uppercase, colon-separated, MSB first. Reject malformed UIDs at the endpoint. |
|
||||
| Camera unavailable while `nfc_photo_required=True` | Tap rejected with "Camera unavailable" — forces a real fix instead of silent degradation. |
|
||||
|
||||
## Hardware checklist (per company)
|
||||
|
||||
- Samsung Galaxy Tab Active 5 Pro (10.1") — ~$700 USD
|
||||
- Samsung official Pogo charging dock — ~$100
|
||||
- Wall mount bracket compatible with Tab Active 5 Pro (The Joy Factory, Maclocks, or Heckler) — ~$80
|
||||
- USB-C 30W PSU + cable — ~$25
|
||||
- Fully Kiosk Browser commercial license (~€10 one-time) OR Samsung Knox Configure (~$30/year/device)
|
||||
- "TAP HERE" decal for the back of the tablet — DIY/printed sticker
|
||||
|
||||
**Total**: ~$915 per company, one-time.
|
||||
|
||||
## Provisioning script (one-time per tablet)
|
||||
|
||||
**Prerequisite — Odoo side (one-time per company):**
|
||||
- Create a `res.users` named e.g. `kiosk-westin@<domain>`, member of `fusion_clock.group_fusion_clock_manager`
|
||||
- Generate a long random password; store it in a password manager
|
||||
- Set `res.company.x_fclk_nfc_kiosk_location_id` for that company to the desired `fusion.clock.location`
|
||||
- Toggle `fusion_clock.enable_nfc_kiosk = True` and `fusion_clock.nfc_photo_required` per policy
|
||||
- Set `fusion_clock.nfc_enroll_password` to a 4-digit Enroll Mode password
|
||||
|
||||
**Tablet side:**
|
||||
1. Factory reset
|
||||
2. Sign in with company Google account
|
||||
3. Install Fully Kiosk Browser from Play Store
|
||||
4. In Fully Kiosk: set kiosk URL → `https://<odoo-domain>/fusion_clock/kiosk/nfc`, enable "hide bars", "auto-restart on crash", "keep screen on while charging", "auto-reload daily at 3am"
|
||||
5. Open kiosk URL once in normal Chrome → log in as the kiosk service user (saved credentials) → walk through the one-time setup flow (activate NFC, allow camera, test-tap a card)
|
||||
6. Lock tablet into kiosk mode via Fully Kiosk's "Start Kiosk" button
|
||||
7. Mount on dock
|
||||
|
||||
## Testing plan
|
||||
|
||||
### Python unit tests (`tests/test_clock_nfc_kiosk.py`)
|
||||
- Tap with valid UID → attendance toggled, photo saved, activity logged
|
||||
- Tap with unknown UID → `card_unknown` error, no attendance row
|
||||
- Tap when `x_fclk_enable_clock=False` → `clock_disabled` error
|
||||
- Double-tap same UID within 5s → second is debounced
|
||||
- Enroll with conflicting UID → `card_already_assigned`, no overwrite
|
||||
- Enroll with wrong password → 403
|
||||
- Tap with no `fusion.clock.location` configured for company → `no_location_configured`
|
||||
- UID normalization: lowercase input → stored uppercase
|
||||
|
||||
### Manual smoke tests (real tablet or Android phone for dev)
|
||||
- Cold boot → IDLE within 5s
|
||||
- Tap → RESULT within 1s
|
||||
- Photo attached to attendance record (verify in backend)
|
||||
- Enroll Mode password gate works; 60s timeout exits cleanly
|
||||
- WiFi disconnect → tap shows "No connection"; reconnect → tap works again
|
||||
- Tap own card 5x in fast succession → only one state change (debounce holds)
|
||||
|
||||
### Dev shortcut
|
||||
- Test the entire flow on any Android phone with NFC + Chrome before touching tablet hardware
|
||||
- For pre-card testing: use any contactless credit/debit card or transit pass (Web NFC reads only the UID, not card data — safe)
|
||||
- Mock-tap debug mode (`Ctrl+Shift+T`) lets the UI state machine be tested without any hardware
|
||||
|
||||
### Soak test (before declaring pilot ready)
|
||||
- 24h continuous on the dock
|
||||
- Periodic taps every few hours
|
||||
- Verify Chrome memory stable (DevTools), NFC reader still active, no zombie permissions prompts
|
||||
|
||||
## Future considerations
|
||||
|
||||
- **Offline mode** — local IndexedDB cache + replay queue when network returns. Adds complexity (conflict resolution, clock-skew handling) for marginal benefit at 1 station. Defer until pilot proves it's a real problem.
|
||||
- **Multi-station** — if a single station becomes a bottleneck at shift change, add a second tablet at the same company. No code changes needed; just provision another tablet pointing at the same URL.
|
||||
- **QR-code-on-portal alternate credential** — for iPhone-only employees who don't want to carry a card. Adds `BarcodeDetector` to the kiosk page alongside `NDEFReader`, plus a "My Clock Code" page in the portal that shows a rotating short-lived QR. Defer to v1.1.
|
||||
- **Ubiquiti webhook integration** — subscribe to UniFi Access tap events on a designated "clock door" reader so an entry tap doubles as clock-in. Saves the tablet purchase but loses the photo verification and the screen feedback. Probably not worth it but easy to add later.
|
||||
- **Native Android kiosk app** — only if the pilot scales to 50+ stations and Web NFC's quirks become operationally painful. Today, not worth it.
|
||||
File diff suppressed because it is too large
Load Diff
BIN
fusion_accounting/.DS_Store
vendored
Normal file
BIN
fusion_accounting/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
fusion_accounting/fusion_accounting/.DS_Store
vendored
Normal file
BIN
fusion_accounting/fusion_accounting/.DS_Store
vendored
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
BIN
fusion_accounting/fusion_accounting_ai/.DS_Store
vendored
Normal file
BIN
fusion_accounting/fusion_accounting_ai/.DS_Store
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user