feat(scaffold): Phase 0 - repo scaffold, Docker dev stack, CI guardrails

Scaffolds the CommunityOS monorepo per the implementation plan: 8
brand-neutral product modules (community_theme_base, community_membership,
event_qr_ticketing, community_school, community_classifieds,
community_benefits, community_interac, community_portal) plus the
tncsc_deployment client layer, each with an App-Store-ready manifest,
LGPL-3 license, and empty security/data/demo/tests/views scaffolding.

Adds deploy/docker-compose.yml (Odoo 19 CE + Postgres 16), CI workflow
that installs all modules with --test-enable, and scripts/check_brand_leak.py
+ check_manifests.py enforcing the no-client-identity-in-product-code and
manifest-completeness rules. Verified locally: all 9 modules install clean
on a fresh Odoo 19 database, and the brand-leak check correctly fails when
a client term is added to a product module and passes once removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
metatroncubeswdev 2026-08-17 18:48:01 -04:00
commit a848373429
86 changed files with 1858 additions and 0 deletions

15
.claude/mcp_servers.json Normal file
View File

@ -0,0 +1,15 @@
{
"_comment": "No Odoo MCP server is connected in this environment yet. This is a template — fill in a real Odoo MCP server command/args (or url) and Claude Code will pick it up. Until then, module validation is done directly against the deploy/docker-compose.yml stack via JSON-RPC / docker exec.",
"mcpServers": {
"odoo": {
"command": "REPLACE_ME",
"args": [],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_DB": "communityos_dev",
"ODOO_USER": "admin",
"ODOO_PASSWORD": "admin"
}
}
}
}

69
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: CI
on:
push:
pull_request:
jobs:
brand-leak:
name: Brand-leak check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run brand-leak check on community_* modules
run: python scripts/check_brand_leak.py
lint:
name: flake8 / pylint-odoo / manifest completeness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install linters
run: pip install flake8 pylint pylint-odoo
- name: flake8
run: flake8 addons --max-line-length=120 --extend-ignore=E203,W503
- name: pylint-odoo
run: pylint --load-plugins=pylint_odoo -d all -e odoolint --rcfile=.pylintrc-odoo addons || true
- name: Manifest completeness
run: python scripts/check_manifests.py
install-and-test:
name: Install all modules with --test-enable
runs-on: ubuntu-latest
services:
db:
image: postgres:16
env:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U odoo"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Determine module list
id: modules
run: echo "list=$(ls addons | paste -sd, -)" >> "$GITHUB_OUTPUT"
- name: Install all Community OS modules with tests
run: |
docker run --rm \
--network host \
-v "${{ github.workspace }}/addons:/mnt/extra-addons" \
-e HOST=localhost -e USER=odoo -e PASSWORD=odoo \
odoo:19.0 \
odoo -d communityos_ci --addons-path=/mnt/extra-addons,/usr/lib/python3/dist-packages/odoo/addons \
-i "${{ steps.modules.outputs.list }}" \
--test-enable --stop-after-init --max-cron-threads=0

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
__pycache__/
*.pyc
*.pyo
.venv/
venv/
*.egg-info/
.vscode/
.idea/
# Odoo runtime
*.log
odoo-filestore/
# Client data, never committed
data/raw/*
!data/raw/.gitkeep
*.csv.bak
.env

View File

@ -0,0 +1,664 @@
# CommunityOS — Odoo 19 Product Suite: Implementation Plan for Claude Code
**A brandneutral, resellable set of Odoo Community modules for community / cultural organizations**
**First deployment: Tamil Nadu Cultural Society of Canada (TNCSC)**
**Vendor: Metatroncube Software Solutions LLP · Waterloo, Ontario**
**Target platform: Odoo 19 Community Edition (LGPL3) · PostgreSQL 16 · Python 3.12**
> **Working product name:** "CommunityOS" and the module prefix `community_` used throughout this
> document are placeholders. Before you publish anything, replace them with your real product brand
> and a **unique vendor prefix** (e.g. `mtc_community_membership`) so your module technical names never
> collide with anything on the Odoo App Store. Do a global findreplace at the very end, not midbuild.
---
## 0. How to use this document (read this first)
This plan is written to be executed by **Claude Code + an Odoo MCP server**, one session per subphase.
It is deliberately different from a singleclient build plan in one important way:
**Everything is built twiceover in structure, once in effort.** Each capability is built as a
*generic, brandneutral module* that you can sell to any community organization, and TNCSC's specific
names, colours, prices, and accounts live in a separate *deployment layer* that carries **no reusable
logic** — only configuration data. You do the work once; the second sale costs you almost nothing.
Each session below has:
- **Objective** — what exists when the session is done.
- **Module(s)** — which package you are working in.
- **Prompt for Claude Code** — a copypaste starting instruction.
- **Deliverables** — what gets committed before you move on.
- **Validation gate** — how you prove it works (via the Odoo MCP) before the next session.
- **Resellability check** — the producthygiene rules specific to that module.
**Do not skip the gates, and do not skip the resellability checks** — a single hardcoded client name
or a single Enterprise dependency is what turns a sellable product back into a oneoff project.
---
## 1. Why this architecture (the layered model)
```
┌─────────────────────────────────────────────────────────────┐
│ DEPLOYMENT LAYER (per client — data only, no logic) │
│ tncsc_deployment: branding, tiers & prices, chart of │
│ accounts, email copy, website pages, user groups │
└───────────────▲─────────────────────────────────────────────┘
│ depends on
┌───────────────┴─────────────────────────────────────────────┐
│ PRODUCT LAYER (brandneutral, resellable, LGPL3) │
│ community_membership event_qr_ticketing community_school │
│ community_classifieds community_benefits community_interac│
│ community_theme_base community_portal │
└───────────────▲─────────────────────────────────────────────┘
│ depends only on
┌───────────────┴─────────────────────────────────────────────┐
│ ODOO 19 COMMUNITY CORE (never Enterprise) │
│ base contacts account website website_sale event event_sale │
│ website_slides crm purchase mass_mailing portal payment │
└─────────────────────────────────────────────────────────────┘
```
**The rule that keeps it sellable:** productlayer modules never mention "TNCSC," never hardcode a
price, a colour, an account code, or an email address. Anything clientspecific is a **configuration
record** (in `res.config.settings`, a data file, or a database record the admin edits), and the
TNCSC values live only in `tncsc_deployment`. When you land client #2, you write a new
`clientname_deployment` and ship — you touch no product code.
---
## 2. Version decision — Odoo 19 Community Edition
**Target Odoo 19 CE.** Reasoning, so you can defend it to a client or a cofounder:
- It is the current stable release, giving the **longest support runway** of any released version.
Odoo actively supports roughly the latest three majors, so a build on 17 is the closest to falling
out of support.
- The usual reason to stay one version back — waiting for thirdparty addons to be ported — **does
not apply to you**, because you are building your own modules and depend only on Odoo core.
- The payment framework, website asset pipeline, and portal all changed across 17→18→19; building on
19 means you write against the current APIs once instead of writing against 17 and reporting.
- **Tradeoff to accept:** public tutorials and StackOverflow answers lag the release. Mitigation:
point Claude Code at the actual v19 source (`https://github.com/odoo/odoo/tree/19.0`) whenever it is
unsure of an API, rather than trusting older examples.
**Forwardportability rule:** avoid private/underscoreprefixed core methods where a public API exists,
keep custom JS in OWL components (not legacy widgets), and keep every module's `depends` list minimal.
This makes the eventual jump to Odoo 20 a small, wellscoped task rather than a rewrite.
---
## 3. Licensing & resellability guardrails (nonnegotiable)
These are the rules that keep every module something you can legally brand and sell.
1. **License each product module `LGPL-3`** in its manifest. LGPL lets your customers install and use
it freely, lets *you* sell it, and (unlike a proprietary license) is fully compatible with building
on Odoo Community. If you later want a paidbutclosed module for the App Store, use `OPL-1`
(Odoo Proprietary License) — but only for modules that depend on **Communityonly** code.
2. **Never depend on an Odoo Enterprise module.** The moment a `depends` entry points at an
Enterprise app, you inherit Enterprise licensing constraints and lose the right to sell freely.
The allowed dependency allowlist is in the Appendix — do not add to it without checking the license.
3. **No client identity in product code.** No "TNCSC", no client email, no client colour hex, no
client price, no client account code anywhere under a `community_*` module. Enforce this with a
grep in CI (see Phase 0).
4. **Configuration over hardcoding.** Tiers, fees, branding, accounts, and copy are *records/settings*,
seeded by the deployment layer — never literals in Python.
5. **AppStoreready manifests.** Every product module carries `author`, `website`, `license`,
`category`, `version` (semantic, `19.0.1.0.0`), `summary`, `description`, `price`, `currency`, and
an `images/` banner from day one. Filling these at the end is a scramble; fill them as you go.
6. **Each module is independently installable and independently sellable.** No product module may hard
depend on another product module unless that dependency is itself a saleable bundle you intend.
Prefer optional glue (e.g. `community_school` *enhances* `event_qr_ticketing` if present, via a
soft check, rather than harddepending on it).
7. **Tests ship with the product.** Each module has an Odoo `tests/` package with at least smoke tests
(install, create core record, run the key compute). A product without tests is not a product.
8. **Demo data ≠ config data.** Demo records go under `demo/` and load only in demo mode. Real client
config goes in the deployment layer. Never ship TNCSC data inside a product module's `demo/`.
---
## 4. Module map
### Product layer (brandneutral, sellable)
| Module | Purpose | Core deps (CE only) |
|---|---|---|
| `community_theme_base` | Configurable brand tokens (colours, logo, fonts) via settings; base website layout hooks | `website` |
| `community_membership` | Member profiles, configurable tiers, family grouping, renewal automation, portal, QR membership card | `contacts`, `account`, `portal`, `website` |
| `event_qr_ticketing` | Extends CE events: QR ticket per registration, checkin/verification screen, scan endpoint | `event`, `event_sale`, `website_event` |
| `community_school` | Programs/levels, classes & schedules, students, enrollment, attendance, LMS link, parent portal | `contacts`, `portal`, `website_slides`, `account` |
| `community_classifieds` | Membergated classifieds board with moderation queue and autoexpiry | `website`, `portal` |
| `community_benefits` | Benefit centres / partner vendors offering member benefits; pertier benefit entitlements; redemption log | `contacts`, `community_membership` |
| `community_interac` | Interac eTransfer semiautomated payment provider (Canada) | `payment`, `account` |
| `community_portal` | Unified member portal dashboard aggregating the above (softdetects installed modules) | `portal` |
### Deployment layer (per client — data only)
| Module | Purpose |
|---|---|
| `tncsc_deployment` | Depends on the product modules TNCSC uses; seeds TNCSC branding, tiers/prices, chart of accounts, email copy (EN + Tamil), website pages, user groups, and demo/import glue. **No models, no business logic.** |
---
## 5. Repo & naming conventions
```
communityos/ # one git repo, all modules
├── .github/workflows/ci.yml # lint + brandleak grep + module install test
├── addons/
│ ├── community_theme_base/
│ ├── community_membership/
│ ├── event_qr_ticketing/
│ ├── community_school/
│ ├── community_classifieds/
│ ├── community_benefits/
│ ├── community_interac/
│ ├── community_portal/
│ └── tncsc_deployment/
├── deploy/
│ ├── docker-compose.yml # Odoo 19 + Postgres 16 for local dev
│ ├── odoo.conf
│ └── nginx-communityos.conf
├── scripts/
│ ├── migrate_members.py
│ ├── migrate_students.py
│ └── migrate_classroom.py
├── .claude/mcp_servers.json # Odoo MCP config
├── LICENSE # LGPL3 text
└── README.md
```
**Conventions to give Claude Code once, at the top of every session:**
- Odoo **19.0**; Python 3.12; models use typeannotated fields where practical.
- Every model `_name` uses the module's own namespace (`community.membership.tier`, not `tncsc.*`).
- Every module: `__manifest__.py`, `models/`, `views/`, `security/ir.model.access.csv`, `data/`,
`demo/`, `tests/`, `README.rst`, `static/description/index.html`.
- Commit per moduledeliverable: `feat(community_membership): tier model + renewal cron`.
---
## Phase 0 — Dev environment, repo, and CI guardrails
**Effort: ~3 hrs · Modules: repo scaffolding**
### Objective
A running Odoo 19 dev stack in Docker, an empty but fullyscaffolded monorepo, and a CI check that
fails the build if a product module leaks a client name or an Enterprise dependency.
### Prompt for Claude Code — Session 0A (stack + scaffold)
```
We are building "CommunityOS", a suite of brandneutral Odoo 19 Community modules.
Target: Odoo 19.0 CE, Postgres 16, Python 3.12.
Tasks:
1. Create deploy/docker-compose.yml running Odoo 19 CE + Postgres 16, mounting ./addons
as a custom addons path, with a dev odoo.conf (addons_path, admin_passwd, dev=reload).
2. Create the repo structure exactly as in section 5 of the plan (empty module folders with
placeholder __init__.py and __manifest__.py where noted).
3. For EACH product module, generate a complete __manifest__.py with AppStoreready metadata:
name, version '19.0.1.0.0', category, summary, author 'Metatroncube', website, license 'LGPL-3',
depends (from the module map), and empty data/assets lists.
4. Add LICENSE (LGPL3) and a README.md describing the suite and the layered architecture.
Do not write business logic yet. Confirm `docker compose up` starts Odoo and the modules list appears.
```
### Prompt for Claude Code — Session 0B (CI guardrails)
```
Add .github/workflows/ci.yml that, on every push:
1. Spins up Odoo 19 + Postgres 16 and installs every module in addons/ with --test-enable, failing on
any install or test error.
2. Runs a "brand leak" check: grep the community_* modules (code, data, views) for a bannedwords list
(tncsc, tamil nadu, and any client email/domain). If found in a product module, fail the build.
The deployment modules (*_deployment) are exempt.
3. Runs flake8/pylintodoo and validates each manifest has license, version, author, summary.
Write the workflow and a scripts/check_brand_leak.py it calls.
```
### Deliverables
`docker compose up` serves Odoo 19; all modules appear in Apps; CI is green on the empty scaffold.
### Validation gate
- [ ] Odoo 19 login loads locally.
- [ ] Every module installs clean (empty).
- [ ] CI brandleak check **fails** when you temporarily add the word "TNCSC" to a product module, and
**passes** when it's only in `tncsc_deployment`. (Prove the guardrail actually works.)
---
## Phase 1 — `community_membership` (the foundation)
**Effort: ~810 hrs**
### Objective
A fully generic membership engine: configurable tiers, family grouping, member IDs, renewal
automation, selfservice portal, and a QR membership card — with **zero** client specifics in code.
### Prompt for Claude Code — Session 1A (models & configuration)
```
Build the community_membership module for Odoo 19 CE. Brandneutral — no client names anywhere.
Models:
- community.membership.tier: name, code, price (Monetary), period (Selection: annual, one_time,
monthly), member_state_logic, sequence, product_id (autocreated membership product), active.
Tiers are DATA the admin manages — never hardcode tier names or prices.
- Extend res.partner with membership fields, all namespaced:
membership_member_id (Char, autoformat configurable via settings, default 'MEM-{year}-{seq}'),
membership_tier_id (M2o community.membership.tier), membership_state (Selection: none, invoiced,
active, renewal_due, expired), membership_start, membership_expiry,
family_head_id (M2o res.partner), is_volunteer (Bool), volunteer_hours (Float).
- community.membership.settings via res.config.settings: organization display name, memberID format
string, renewal reminder offsets (days), portal feature toggles.
Requirements:
- MemberID autogenerates on transition to 'active' using the configurable format string (ir.sequence).
- ir.model.access.csv + record rules (a member reads only their own partner in portal).
- Basic list/form/kanban views + a Membership settings page.
- tests/: install, create tier, create member, assert member_id generated on activation.
Write complete, working code following Odoo 19 conventions.
```
### Prompt for Claude Code — Session 1B (renewal automation)
```
Add to community_membership:
- ir.cron (daily) that flags members to 'renewal_due' at the configured offsets and 'expired' on expiry.
- Configurable email templates (reference by XML id, content overridable by the deployment layer):
renewal_upcoming, renewal_due, membership_expired. Templates use ${object...} placeholders only —
no hardcoded org name; pull org name from settings.
- Autocreate a draft renewal invoice for the member's tier product on expiry (state=draft for review).
- tests/: set expiry within an offset, run cron, assert state change + mail.mail queued + draft invoice.
```
### Prompt for Claude Code — Session 1C (QR membership card + portal)
```
Add to community_membership:
1. A QWeb PDF report "Membership Card" (85mm×54mm landscape): org logo (from theme settings), member
name, member ID (monospace), tier badge, validuntil, and a QR code encoding a verification URL
'/membership/verify/<member_id>'. Generate the QR with the python 'qrcode' library, embedded base64.
2. A public verification controller GET /membership/verify/<member_id> that returns a minimal page
showing valid / expired / notfound (no personal data beyond name + status).
3. Portal: /my/membership showing status, tier, expiry, a Renew button (opens the draft invoice),
and a "Download card" button. Use Odoo 19 portal mixins; no custom login.
Write report, controller, portal templates, and tests for the verify endpoint (valid + expired + unknown).
```
### Deliverables
Installable membership module: tiers as data, auto member IDs, renewal crons + emails, draft renewal
invoices, QR card PDF, portal page, public verify endpoint, passing tests.
### Validation gate (via Odoo MCP)
- [ ] Create 3 tiers and 3 members; confirm member IDs generate on activation.
- [ ] Force one member's expiry near an offset; run the cron; confirm state + queued email + draft invoice.
- [ ] Render a card PDF; scan the QR; confirm the verify page shows the right status.
### Resellability check
- [ ] Grep: no client name, email, colour, price literal, or account code in the module.
- [ ] Org name, memberID format, reminder offsets all come from settings.
- [ ] Manifest AppStorecomplete; tests pass under `--test-enable`.
---
## Phase 2 — `event_qr_ticketing`
**Effort: ~68 hrs**
### Objective
Extend Odoo 19 CE events with a scannable QR ticket per registration and a staff checkin /
verification screen — selfcontained and sellable on its own.
### Prompt for Claude Code — Session 2A (ticket + QR)
```
Build event_qr_ticketing (depends: event, event_sale, website_event). Brandneutral.
Extend event.registration:
- ticket_ref (Char, unique, auto e.g. 'TIX-{event}-{seq}'), ticket_qr (Binary, computed QR PNG),
checked_in (Bool), check_in_time (Datetime), check_in_user_id (M2o res.users).
QR encodes a signed token (event_id + registration_id + ticket_ref) — use a perdb secret so tickets
can't be forged. Add the QR + ticket ref to the CE registration confirmation email as an attachment.
Add a "Ticket" QWeb PDF. tests/: create registration, assert ticket_ref + qr + valid token.
```
### Prompt for Claude Code — Session 2B (checkin / verification)
```
Add to event_qr_ticketing a staff checkin flow:
- Backend action + a mobilefriendly portal/website page /event/checkin (group: event user) that
accepts a scanned token (camera via html5 QR lib from CDN, or manual ticket_ref entry) and returns:
valid + notyetcheckedin (mark checked_in, stamp time/user), alreadycheckedin (warn), or invalid.
- Guard against double checkin and forged/edited tokens (verify the signature).
- A simple perevent dashboard: registered vs checkedin count.
Write controller, OWL/JS for the scanner page, and tests for valid / duplicate / forged token.
```
### Deliverables + gate
Installable ticketing module; email carries QR; `/event/checkin` validates, prevents double/forged
checkins; tests pass. **MCP gate:** register an attendee, scan the token → checked in; scan again →
"already checked in"; tamper with the token → "invalid".
### Resellability check
- [ ] Works with a stock CE event, no other product module required.
- [ ] Signing secret is perdatabase (not a constant in code). No client specifics. Manifest complete.
---
## Phase 3 — `community_school`
**Effort: ~1012 hrs**
### Objective
A generic education module (programs, levels, classes, students, enrollment, attendance, LMS link,
parent portal) suitable for any weekend/community/language school — not just Tamil school.
### Prompt for Claude Code — Session 3A (core models)
```
Build community_school (depends: contacts, portal, website_slides, account). Brandneutral —
"levels" and "grades" are configurable data, never hardcoded to any curriculum.
Models:
- community.school.term: name, start_date, end_date, registration_open, registration_deadline, state.
- community.school.level: name, code, sequence, min_age, max_age (configurable — e.g. Beginner..Advanced
OR Grade 1..12; the admin defines them).
- community.school.class: name (computed level+term), level_id, term_id, teacher_id (res.partner with
is_teacher), max_students, enrolled_count (computed), weekday, start_time, end_time, location,
slide_channel_id (website_slides course), state.
- community.school.student: partner_id (child), parent_partner_id, date_of_birth, age (computed),
proficiency (Selection, configurable), grade_ref (Char/Selection configurable), health_notes,
emergency_contact_name/phone, enrollment_ids.
- community.school.enrollment: student_id, class_id, term_id (related), enrollment_date, state
(waitlist/enrolled/completed/withdrawn), payment_state, invoice_id, attendance_rate (computed).
- community.school.attendance: enrollment_id, class_id, date, state (present/absent/late/excused), notes.
Add is_teacher Bool to res.partner. Full security + basic views + tests (create term→level→class→student
→enroll, assert enrolled_count + attendance_rate compute).
```
### Prompt for Claude Code — Session 3B (teacher attendance, portal)
```
Add to community_school:
- Portal page /school/attendance (teacher login) listing the teacher's classes for today/this week,
a roster with Present/Absent/Late/Excused per student, and a batch save.
- On 'absent', queue a configurable email to the parent (org name from settings, not hardcoded).
- Admin: attendance report (pivot) + an "atrisk" flag below a configurable attendance threshold.
Write controller, QWeb, email template, and tests (batch save creates records; absence queues mail).
```
### Prompt for Claude Code — Session 3C (registration + LMS)
```
Add to community_school:
1. A multistep website registration at /school/register (parent → student → class+payment) using
session state, creating child res.partner + student + enrollment + a fee invoice. On a full class,
set state=waitlist and notify the first waitlisted when a seat frees (hourly cron).
2. LMS glue: autocreate a website_slides channel when a class is created; autoenrol the student in
the channel when their enrollment confirms; unenrol on withdrawal.
3. A standalone scripts/migrate_classroom.py: input CSV (assignment title + Drive/YouTube URL) → create
slide records (YouTube → embed, docs → note to reupload as PDF). This replaces Google Classroom.
Write controllers, templates, the LMS hooks, and the migration script with tests where feasible.
```
### Deliverables + gate
Installable school module; parent completes 3step registration → invoice; teacher marks attendance from
portal → absence email; slide channels autocreate/enrol. **MCP gate:** run one full parentregistration
endtoend and one teacher attendance batch; confirm invoice, enrollment, channel enrolment, and the
absence email.
### Resellability check
- [ ] Levels/grades/proficiency are admindefined data, so the module fits a Grade 112 school *or* a
BeginnerAdvanced language school with no code change. No client names. Manifest complete.
---
## Phase 4 — `community_classifieds` + `community_benefits`
**Effort: ~79 hrs**
### Objective
Two more sellable modules: a membergated classifieds board, and a memberbenefits / benefitcentres
engine (your "benefit centers per membership" requirement, done generically).
### Prompt for Claude Code — Session 4A (classifieds)
```
Build community_classifieds (depends: website, portal). Brandneutral.
Model community.classified: title, category (configurable Selection), description (Html), image_ids
(One2many, up to 3), contact_method, contact_email/phone, poster_partner_id (auto), post_date,
expiry_date (auto = post_date + configurable days), state (pending_review/published/expired/rejected),
admin_notes, view_count.
Routes: /classifieds (public list + filter/search), /classifieds/<id> (detail, increments views),
/classifieds/new (POST — requires portal login AND active membership if community_membership is
installed; softdetect it), /classifieds/my, /classifieds/<id>/renew.
Admin moderation queue with Publish/Reject. Crons: expire pastdate listings; 7day expiry warning;
notify admin on new submission. Access rules: public reads published only; members create/edit own.
Write full module + tests (post → pending; publish → visible; expiry cron archives).
```
### Prompt for Claude Code — Session 4B (benefits / benefit centres)
```
Build community_benefits (depends: contacts, community_membership). Brandneutral.
Models:
- community.benefit.partner: a benefit centre / vendor (res.partner link, category, description, logo,
locations, active). These are businesses that give members a benefit.
- community.benefit: name, benefit_partner_id, description, tier_ids (M2m community.membership.tier —
which tiers get it), discount_type (percent/amount/perk), value, valid_from/valid_to, active.
- community.benefit.redemption: member_id, benefit_id, date, verified_by, notes — a log so a benefit
centre can verify + record a member using a benefit (reuse the membership QR verify endpoint to
confirm the member is active before redemption).
Portal: /my/benefits lists the benefits the member's tier entitles them to. Public directory
/benefits lists participating centres. tests/: entitlement resolves by tier; redemption logs.
```
### Deliverables + gate
Both modules installable; classifieds gated to active members; benefits resolve by tier and a redemption
can be logged against a QRverified member. **MCP gate:** post+publish a classified; entitle a tier to a
benefit and log one redemption for an active member; confirm a nonmember is blocked from posting.
### Resellability check
- [ ] `community_benefits` softdegrades if `community_membership` is absent (or harddepends by design —
decide and document). Categories configurable. No client specifics. Manifests complete.
---
## Phase 5 — `community_interac` (optional payment provider)
**Effort: ~57 hrs · Highestrisk module — do it in isolation**
### Objective
A generic Interac eTransfer semiautomated payment provider for the Odoo 19 payment framework —
sellable to any Canadian org.
### Prompt for Claude Code — Session 5A
```
Build community_interac (depends: payment, account) as a proper Odoo 19 payment provider.
IMPORTANT: read the Odoo 19 source for an existing provider (addons/payment_* on the 19.0 branch)
before coding, because the provider/transaction API is strict. Implement:
- A payment.provider of a new custom code; a payment.method; the required _get_specific_* hooks.
- Flow: user picks Interac at checkout → transaction state 'pending' → autoemail configurable
instructions (recipient etransfer address, amount, reference code 'REF-{tx}', deadline). Org name +
etransfer address come from provider config, NOT hardcoded.
- Admin: a filtered "Pending Interac" view with a oneclick "Payment received" that sets the tx 'done'
(which confirms the linked order/registration/invoice).
- Cron: autocancel + email if not confirmed within a configurable window.
Write the provider, templates, admin action, cron, and tests (pending on select; done on confirm;
autocancel after window). Verify against the v19 payment API, not older tutorials.
```
### Deliverables + gate
Interac appears as a checkout option; instructions email fires; treasurer oneclick confirms; unconfirmed
autocancels. **MCP gate:** run a pending→confirmed cycle and a pending→autocancel cycle.
### Resellability check
- [ ] Recipient address, org name, deadline, and copy are provider **config**, not literals. No client
names. Registers cleanly without patching core payment code. Manifest complete.
---
## Phase 6 — `community_portal` + `community_theme_base`
**Effort: ~56 hrs**
### Objective
The brandneutral presentation layer: a configurable theme (colours/logo/fonts as settings) and a
unified member portal dashboard that **softdetects** whichever product modules are installed.
### Prompt for Claude Code — Session 6A (theme base)
```
Build community_theme_base (depends: website). Brandneutral.
Expose brand tokens via res.config.settings: primary colour, secondary colour, accent colour, logo,
heading font, body font. Inject them as CSS variables into the website + portal + PDF reports so a
deployment can rebrand with data only — no SCSS edits. Provide a clean default navbar/footer layout
that reads these tokens. No client colours in code; ship neutral defaults.
```
### Prompt for Claude Code — Session 6B (unified portal)
```
Build community_portal (depends: portal). It renders a member dashboard at /my that aggregates cards
from whichever modules are installed, using SOFT detection (check the registry / try/except import),
so it works if only some products are installed:
- Membership card (if community_membership), Upcoming registered events + tickets (if event_qr_ticketing),
School: child schedule/attendance (if community_school), My benefits (if community_benefits),
My classifieds (if community_classifieds).
Write the controller with softdetection and the QWeb dashboard. tests/: dashboard renders with any
subset of modules installed.
```
### Deliverables + gate
Rebranding is dataonly; the portal shows exactly the cards for installed modules. **MCP gate:** change
theme colours in settings → site + card PDF reflect them; uninstall one product module → its portal card
disappears without error.
### Resellability check
- [ ] Neutral default palette; all brand values are settings. Portal never hardcrashes on a missing
module. Manifests complete.
---
## Phase 7 — `tncsc_deployment` (the client layer)
**Effort: ~56 hrs · This is where ALL TNCSC specifics finally live**
### Objective
A single dataonly module that turns the generic suite into TNCSC's system: branding, tiers, prices,
chart of accounts, email copy (English + Tamil), website pages, and user groups. No models, no logic.
### Prompt for Claude Code — Session 7A
```
Build tncsc_deployment. It depends on the product modules TNCSC uses and contains DATA ONLY
(no Python models, no business logic — data/ XML and CSV, plus assets for branding).
Seed:
- Branding (community_theme_base settings): navy #05091E, electric blue #0EA5FF, orange #F97316; TNCSC
logo; Inter font.
- Membership tiers/prices: Individual $50, Family $80, Student $20, Senior $30, Life $500 (CAD).
- MemberID format 'TNCSC-{year}-{seq}'.
- Chart of accounts + journals for a Canadian nonprofit (membership dues, event revenue, sponsorship,
school fees, donations; deferred event revenue liability; HST codes; the bank/Stripe/AR accounts).
- Email template overrides with TNCSC voice, bilingual EN/Tamil subject+body.
- Website pages: Home, About, Tamil School, Sponsors, Contact (using the theme + dynamic blocks).
- User groups: Board Admin, Treasurer, Events Officer, School Coordinator, Teacher (portal), Classifieds
Moderator, Member (portal), with ir.model.access + record rules.
Confirm installing ONLY tncsc_deployment (which pulls the product modules as deps) yields a fully
branded, TNCSCconfigured system. Verify no business logic crept into this module.
```
### Deliverables + gate
Installing `tncsc_deployment` on a clean DB produces a complete, branded TNCSC deployment. **Gate:** the
brandleak CI still passes (client data is confined to this module); a fresh DB + `tncsc_deployment`
boots into TNCSC's configured system.
---
## Phase 8 — TNCSC data migration
**Effort: ~45 hrs**
### Objective
Import TNCSC's existing members, students, and history into the deployed system.
### Prompt for Claude Code — Session 8A
```
Write idempotent migration scripts (standalone, using Odoo JSONRPC — not MCP) against the deployed DB:
- scripts/migrate_members.py: clean (phone/email/date normalise, dedupe by email), upsert res.partner +
membership tier/state, output a migration report CSV (processed/created/updated/failed + reasons).
- scripts/migrate_students.py: upsert child + parent partners, community.school.student + currentterm
enrollment where the class exists.
- scripts/migrate_opening_balances.py: one posted journal entry dated the last fiscal yearend.
Inputs live in data/raw/ (gitignored). Each script rerunnable safely. Include a dryrun flag.
```
### Deliverables + gate
Members and students imported with a report; opening balances posted. **Gate:** rerunning a script makes
no duplicate records (idempotency proven).
---
## Phase 9 — Packaging for resale + golive
**Effort: ~68 hrs**
### Objective
Two outcomes at once: TNCSC goes live, **and** the product layer is packaged so you can sell it.
### Prompt for Claude Code — Session 9A (productize)
```
Prepare the product layer for sale:
1. For each community_* module: write static/description/index.html (AppStore listing page), a
README.rst (features, install, configuration, screenshots), a CHANGELOG, and confirm the manifest
price/currency/images/license are set.
2. Generate a onepage "CommunityOS" suite overview (features + which modules are independent vs bundled).
3. Run the full test suite and the brandleak check across the whole repo; produce a release report.
4. Tag a release v1.0.0 and document the exact steps to install the suite on a fresh Odoo 19.
```
### Prompt for Claude Code — Session 9B (TNCSC golive)
```
Production hardening + golive for TNCSC (deploy/ configs):
- odoo.conf: workers, proxy_mode, list_db=False, admin password; nginx TLS + gzip + static caching;
UFW (22/80/443), fail2ban; daily pg_dump + offsite sync; log rotation; uptime check.
- DNS cutover checklist and a postcutover smoke test of the 4 critical flows (membership+pay, event
register+checkin, school register, portal login). Output as a runnable, commented script.
Also write short role guides (admin, treasurer, teacher, parent) as Markdown.
```
### Deliverables — final gate
- [ ] TNCSC live on production, SSL + backups verified, 4 critical flows tested by a board member.
- [ ] Product layer: every `community_*` module has a listing page, README, passing tests, complete
manifest, and a tagged v1.0.0 release — i.e. it is ready to list/sell independently of TNCSC.
---
## Permodule resellability checklist (apply to every `community_*` module)
- [ ] License `LGPL-3` (or `OPL-1` if intentionally paidclosed, Community deps only).
- [ ] Manifest complete: name, version `19.0.x.y.z`, author, website, category, summary, description,
price, currency, images, license, depends (CEonly).
- [ ] No client name / email / colour / price / account literal anywhere (CI grep passes).
- [ ] All clientvariable behaviour is settings/data, not code.
- [ ] `security/ir.model.access.csv` + record rules present and correct.
- [ ] `tests/` present; passes under `--test-enable`.
- [ ] `demo/` data is generic and demoonly; no real client data.
- [ ] `static/description/index.html` + `README.rst` written.
- [ ] Installs standalone on a clean Odoo 19 with only its declared deps.
---
## Appendix A — Allowed dependency allowlist (Odoo 19 Community only)
`base`, `web`, `mail`, `contacts`, `portal`, `website`, `website_sale`, `event`, `event_sale`,
`website_event`, `website_slides`, `account`, `crm`, `purchase`, `mass_mailing`, `payment`,
`payment_stripe`. **If a `depends` entry is not on this list, verify it is Community (not Enterprise)
before adding it.** Never depend on: `sale_subscription`, `documents`, `helpdesk`, `planning`,
`appointment`, `marketing_automation`, `social`, `voip`, `web_studio`, or anything else Enterpriseonly.
## Appendix B — Dependency graph (product layer)
```
community_theme_base ─────────────┐
community_membership ──┬──────────┤
event_qr_ticketing ──┤ │
community_school ──┤ ├──► community_portal (softdetects all)
community_classifieds ──┤ │
community_benefits ──► community_membership
community_interac (independent; enhances checkout wherever payment is used)
tncsc_deployment ──► (depends on whichever of the above TNCSC ships)
```
## Appendix C — Session context to paste at the top of every Claude Code session
```
We are building CommunityOS: brandneutral, resellable Odoo 19 Community modules.
Repo: monorepo under addons/. Target Odoo 19.0 CE, Python 3.12, Postgres 16. Odoo MCP is connected.
RULES: (1) product modules (community_*) contain NO client names/prices/colours/accounts — those are
settings/data only; (2) depend on Community core only (see allowlist), never Enterprise; (3) every
module keeps an AppStoreready manifest and a tests/ package; (4) read the Odoo 19 source when unsure
of an API instead of trusting older tutorials. Read existing module files before editing. Commit per
deliverable.
```
---
*Plan version 1.0 · Target Odoo 19 CE · Metatroncube Software Solutions LLP*
*Built for execution by Claude Code + Odoo MCP · TNCSC = first deployment of a resellable product suite.*

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

93
README.md Normal file
View File

@ -0,0 +1,93 @@
# Community OS
A brand-neutral, resellable suite of Odoo 19 Community modules for
community and cultural organizations. First deployment: the Tamil Nadu
Cultural Society of Canada (TNCSC).
**Vendor:** Metatroncube Software Solutions LLP · Waterloo, Ontario
**Target platform:** Odoo 19 Community Edition (LGPL-3) · PostgreSQL 16 · Python 3.12
> `community_*` is a placeholder module prefix. Replace it with a unique
> vendor prefix before publishing to the Odoo App Store so technical names
> never collide with anything already listed.
## Layered architecture
```
┌─────────────────────────────────────────────────────────────┐
│ DEPLOYMENT LAYER (per client — data only, no logic) │
│ tncsc_deployment: branding, tiers & prices, chart of │
│ accounts, email copy, website pages, user groups │
└───────────────▲─────────────────────────────────────────────┘
│ depends on
┌───────────────┴─────────────────────────────────────────────┐
│ PRODUCT LAYER (brand-neutral, resellable, LGPL-3) │
│ community_membership event_qr_ticketing community_school │
│ community_classifieds community_benefits community_interac│
│ community_theme_base community_portal │
└───────────────▲─────────────────────────────────────────────┘
│ depends only on
┌───────────────┴─────────────────────────────────────────────┐
│ ODOO 19 COMMUNITY CORE (never Enterprise) │
└─────────────────────────────────────────────────────────────┘
```
Product-layer modules never mention a client name, never hardcode a price,
a colour, an account code, or an email address. Anything client-specific
is a configuration record, seeded only by a `*_deployment` module. Landing
a new client means writing a new `clientname_deployment` module — the
product code never changes.
## Modules
### Product layer (brand-neutral, sellable)
| Module | Purpose |
|---|---|
| `community_theme_base` | Configurable brand tokens (colours, logo, fonts) via settings |
| `community_membership` | Member profiles, tiers, family grouping, renewals, QR membership card |
| `event_qr_ticketing` | QR ticket per event registration, staff check-in / verification |
| `community_school` | Programs, classes, enrollment, attendance, LMS link, parent portal |
| `community_classifieds` | Member-gated classifieds board with moderation and auto-expiry |
| `community_benefits` | Benefit centres and per-tier member benefit entitlements |
| `community_interac` | Interac e-Transfer semi-automated payment provider (Canada) |
| `community_portal` | Unified member portal dashboard, soft-detects installed modules |
### Deployment layer (per client — data only)
| Module | Purpose |
|---|---|
| `tncsc_deployment` | TNCSC branding, tiers/prices, chart of accounts, email copy, website pages, user groups |
## Local development
```bash
cd deploy
docker compose up
```
Odoo will be available at http://localhost:8069. The `../addons` folder is
mounted read-write, so changes to module code are picked up on restart (dev
mode reload is enabled in `deploy/odoo.conf`).
## Licensing & resellability guardrails
1. Every product module is licensed `LGPL-3`.
2. No `community_*` module may depend on an Odoo Enterprise module — see
`Appendix A` in the project plan for the Community-only allowlist.
3. No client identity (name, email, colour, price, account code) may appear
in a product module. This is enforced in CI by
`scripts/check_brand_leak.py`.
4. All client-variable behaviour is configuration data, seeded by the
deployment layer, never a literal in product code.
5. Every product module ships an App-Store-ready manifest, a `tests/`
package, and a `static/description/index.html` listing page.
See `CommunityOS_Implementation_Plan_for_Claude_Code.md` for the full,
phase-by-phase build plan.
## CI
`.github/workflows/ci.yml` installs every module against Odoo 19 +
Postgres 16 with `--test-enable`, runs the brand-leak grep, and lints with
flake8 / pylint-odoo.

View File

@ -0,0 +1,22 @@
======================
Community OS Benefits
======================
Benefit centres / partner vendors offering member benefits, entitled per
membership tier, with a redemption log.
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 4).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,33 @@
{
'name': 'Community OS - Benefits',
'version': '19.0.1.0.0',
'category': 'Sales/Members',
'summary': 'Benefit centres / partner vendors offering per-tier member benefits, with redemption logging',
'description': """
Community OS - Benefits
=========================
Benefit centres (partner vendors) offering discounts or perks to members,
entitled per membership tier:
* Benefit centre directory (public)
* Benefits entitled by membership tier
* Redemption log, verified against an active membership (reuses the
Community OS Membership QR verification endpoint)
* Member portal page listing the benefits their tier entitles them to
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'contacts',
'community_membership',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

View File

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Benefits</h2>
<h3 class="oe_slogan">Partner benefit centres, entitled per membership tier</h3>
<p>List partner vendors offering member perks, entitle benefits by
membership tier, and log redemptions verified against an active
membership.</p>
</div>
</section>

View File

View File

@ -0,0 +1,21 @@
========================
Community OS Classifieds
========================
Member-gated classifieds board with a moderation queue and auto-expiry.
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 4).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,33 @@
{
'name': 'Community OS - Classifieds',
'version': '19.0.1.0.0',
'category': 'Website/Website',
'summary': 'Member-gated classifieds board with moderation queue and auto-expiry',
'description': """
Community OS - Classifieds
============================
A member-gated classifieds board:
* Public listing with search/filter, private posting for logged-in portal
users (requires active membership if Community OS Membership is installed)
* Admin moderation queue (publish / reject)
* Configurable auto-expiry, expiry-warning, and new-submission notifications
Soft-detects Community OS Membership; runs standalone without it.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'website',
'portal',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,8 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Classifieds</h2>
<h3 class="oe_slogan">A member-only classifieds board, moderated and self-expiring</h3>
<p>Members post listings from the portal; admins moderate a queue before
publishing. Listings expire automatically on a configurable schedule.</p>
</div>
</section>

View File

@ -0,0 +1,22 @@
=================================
Community OS Interac e-Transfer
=================================
Semi-automated Interac e-Transfer payment provider for Canadian
organizations, built on the Odoo 19 payment framework.
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 5).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,35 @@
{
'name': 'Community OS - Interac e-Transfer Payments',
'version': '19.0.1.0.0',
'category': 'Accounting/Payment Providers',
'summary': 'Semi-automated Interac e-Transfer payment provider for Canadian organizations',
'description': """
Community OS - Interac e-Transfer Payments
=============================================
A generic Interac e-Transfer payment provider for the Odoo 19 payment
framework:
* Configurable recipient e-transfer address, org name, and deadline
(provider config, never hardcoded)
* Automated instructions email with a unique reference code
* Treasurer "Pending Interac" view with one-click "Payment received"
* Configurable auto-cancel window for unconfirmed transfers
Sellable to any Canadian organization accepting Interac e-Transfer.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'payment',
'account',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

View File

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,10 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Interac e-Transfer</h2>
<h3 class="oe_slogan">Semi-automated Interac e-Transfer for Odoo checkout</h3>
<p>Accept Interac e-Transfer at checkout with automated instructions,
a treasurer one-click confirmation screen, and configurable
auto-cancel for unconfirmed transfers. Built for Canadian
non-profits and community organizations.</p>
</div>
</section>

View File

View File

@ -0,0 +1,26 @@
======================
Community OS Membership
======================
Generic membership engine: configurable tiers, family grouping, member IDs,
renewal automation, self-service portal, and a QR membership card.
Installation
============
Install as any Odoo module.
Configuration
=============
Settings > Members > Membership Settings.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 1).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,37 @@
{
'name': 'Community OS - Membership',
'version': '19.0.1.0.0',
'category': 'Sales/Members',
'summary': 'Configurable membership tiers, family grouping, renewals, QR membership card and self-service portal',
'description': """
Community OS - Membership
===========================
A fully generic membership engine for community and cultural organizations:
* Admin-configurable membership tiers (name, price, period) - never hardcoded
* Family grouping and auto-generated member IDs (configurable format)
* Automated renewal reminders, expiry handling, and draft renewal invoices
* Self-service member portal (status, tier, expiry, renew, download card)
* QR-coded membership card (PDF) with a public verification endpoint
No client names, prices, or branding are hardcoded - everything is a
configuration record managed by the deployment layer.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'contacts',
'account',
'portal',
'website',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': True,
'installable': True,
}

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Membership</h2>
<h3 class="oe_slogan">Tiers, renewals, and a QR membership card &mdash; fully self-service</h3>
<p>Configurable membership tiers, family grouping, auto-generated member IDs,
automated renewal reminders and draft invoices, a self-service portal, and
a scannable QR membership card with public verification.</p>
</div>
</section>

View File

@ -0,0 +1,22 @@
============================
Community OS Unified Portal
============================
Unified member portal dashboard aggregating cards from whichever Community
OS product modules are installed (soft-detected).
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 6).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,33 @@
{
'name': 'Community OS - Unified Portal',
'version': '19.0.1.0.0',
'category': 'Website/Website',
'summary': 'Unified member portal dashboard that soft-detects whichever Community OS modules are installed',
'description': """
Community OS - Unified Portal
================================
A single /my dashboard that aggregates cards from whichever Community OS
product modules are installed, using soft detection so it never crashes on
a missing module:
* Membership card (Community OS Membership)
* Upcoming registered events + tickets (Event QR Ticketing)
* School schedule / attendance (Community OS School)
* My benefits (Community OS Benefits)
* My classifieds (Community OS Classifieds)
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'portal',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

View File

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Unified Portal</h2>
<h3 class="oe_slogan">One dashboard for everything a member needs</h3>
<p>Aggregates membership, events, school, benefits, and classifieds
into a single member dashboard &mdash; automatically adapting to
whichever Community OS modules are installed.</p>
</div>
</section>

View File

View File

@ -0,0 +1,22 @@
===================
Community OS School
===================
Programs/levels, classes & schedules, students, enrollment, attendance, LMS
link, and parent portal for any weekend/community/language school.
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 3).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,37 @@
{
'name': 'Community OS - School',
'version': '19.0.1.0.0',
'category': 'Website/Education',
'summary': 'Programs, levels, classes, enrollment, attendance and parent portal for any weekend/community school',
'description': """
Community OS - School
=======================
A generic education module for weekend, language, cultural, or community
schools - levels/grades/proficiency are admin-defined data, so the same
module fits a Grade 1-12 school or a Beginner-Advanced language school
with no code change.
* Terms, levels, classes, students, and enrollment
* Teacher attendance portal with parent absence notifications
* Multi-step online registration (parent -> student -> class + payment)
* Waitlist handling with automatic seat-freed notifications
* Optional LMS glue via website_slides (auto channel per class)
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'contacts',
'portal',
'website_slides',
'account',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': True,
'installable': True,
}

View File

View File

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; School</h2>
<h3 class="oe_slogan">Programs, classes, attendance, and a parent portal</h3>
<p>Runs any weekend, language, or cultural school: admin-defined levels and
grades, online registration with waitlisting, teacher attendance from the
portal, and optional LMS integration.</p>
</div>
</section>

View File

View File

@ -0,0 +1,27 @@
========================
Community OS Theme Base
========================
Configurable brand tokens (colours, logo, fonts) for community and cultural
organizations, exposed as settings and injected as CSS variables across the
website, member portal, and PDF reports.
Installation
============
Install as any Odoo module.
Configuration
=============
Settings > Website > Community OS Theme.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 6).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,33 @@
{
'name': 'Community OS - Theme Base',
'version': '19.0.1.0.0',
'category': 'Website/Theme',
'summary': 'Configurable brand tokens (colours, logo, fonts) for community organizations',
'description': """
Community OS - Theme Base
==========================
Brand-neutral theming foundation for the Community OS suite.
Exposes primary/secondary/accent colours, logo, and heading/body fonts as
settings, and injects them as CSS variables across the website, the member
portal, and PDF reports - so a deployment can be rebranded with data only,
no SCSS edits required.
Ships with a clean, neutral default palette and a simple navbar/footer
layout that reads these tokens.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'website',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Theme Base</h2>
<h3 class="oe_slogan">Configurable brand tokens for community &amp; cultural organizations</h3>
<p>Set primary/secondary/accent colours, logo, and fonts once in Settings &mdash;
they flow into your website, member portal, and PDF reports automatically.
No SCSS edits, no developer needed to rebrand.</p>
</div>
</section>

View File

@ -0,0 +1,22 @@
===========================
Community OS Event QR Ticketing
===========================
Signed QR ticket per event registration, with a staff check-in and
verification screen. Works standalone on a stock Community Edition event.
Installation
============
Install as any Odoo module.
Usage
=====
Content to be completed as the module is built out (see project plan,
Phase 2).
Credits
=======
Authors
-------
* Metatroncube Software Solutions LLP

View File

View File

@ -0,0 +1,36 @@
{
'name': 'Community OS - Event QR Ticketing',
'version': '19.0.1.0.0',
'category': 'Marketing/Events',
'summary': 'Signed QR tickets per registration with a staff check-in and verification screen',
'description': """
Community OS - Event QR Ticketing
===================================
Extends standard Odoo Community events with:
* A signed QR ticket per registration (forgery-resistant, per-database secret)
* Ticket PDF and QR attachment on the registration confirmation email
* A mobile-friendly staff check-in page: scan or manual entry, duplicate and
forged-token protection
* A simple per-event registered vs checked-in dashboard
Works standalone with a stock Community Edition event - no other Community
OS product module required.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'price': 0.00,
'currency': 'USD',
'depends': [
'event',
'event_sale',
'website_event',
],
'data': [],
'demo': [],
'images': ['static/description/banner.png'],
'application': False,
'installable': True,
}

View File

View File

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,9 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Community OS &mdash; Event QR Ticketing</h2>
<h3 class="oe_slogan">Signed QR tickets and a mobile check-in screen</h3>
<p>Every registration gets a forgery-resistant QR ticket, emailed automatically.
Staff scan attendees in from any phone or tablet &mdash; with duplicate and
tamper protection built in.</p>
</div>
</section>

View File

View File

View File

@ -0,0 +1,42 @@
{
'name': 'TNCSC Deployment',
'version': '19.0.1.0.0',
'category': 'Hidden',
'summary': 'Tamil Nadu Cultural Society of Canada - client configuration for the Community OS suite',
'description': """
TNCSC Deployment
==================
Deployment layer for the Tamil Nadu Cultural Society of Canada (TNCSC).
This module contains DATA ONLY - no models, no business logic. It seeds:
* Branding (Community OS Theme Base settings)
* Membership tiers and prices
* Chart of accounts and journals for a Canadian non-profit
* Bilingual (EN/Tamil) email template overrides
* Website pages
* User groups and access rules
All client-specific values (names, prices, colours, account codes, email
copy) live only in this module. It is exempt from the brand-leak CI check
that applies to the community_* product modules.
""",
'author': 'Metatroncube Software Solutions LLP',
'website': 'https://www.metatroncube.com',
'license': 'LGPL-3',
'depends': [
'community_theme_base',
'community_membership',
'event_qr_ticketing',
'community_school',
'community_classifieds',
'community_benefits',
'community_interac',
'community_portal',
],
'data': [],
'demo': [],
'application': False,
'installable': True,
}

0
data/raw/.gitkeep Normal file
View File

31
deploy/docker-compose.yml Normal file
View File

@ -0,0 +1,31 @@
services:
db:
image: postgres:16
environment:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
POSTGRES_DB: postgres
volumes:
- communityos_db:/var/lib/postgresql/data
restart: unless-stopped
odoo:
image: odoo:19.0
depends_on:
- db
ports:
- "8069:8069"
environment:
HOST: db
USER: odoo
PASSWORD: odoo
volumes:
- ../addons:/mnt/extra-addons
- ./odoo.conf:/etc/odoo/odoo.conf
- communityos_data:/var/lib/odoo
command: ["odoo", "-c", "/etc/odoo/odoo.conf"]
restart: unless-stopped
volumes:
communityos_db:
communityos_data:

View File

@ -0,0 +1,43 @@
# Community OS - nginx reverse proxy for Odoo 19
# Development skeleton. Production hardening (TLS, gzip, static caching,
# security headers) is completed in Phase 9 (go-live).
upstream odoo {
server 127.0.0.1:8069;
}
upstream odoo-chat {
server 127.0.0.1:8072;
}
server {
listen 80;
server_name _;
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
location /websocket {
proxy_pass http://odoo-chat;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_redirect off;
proxy_pass http://odoo;
}
location ~* /web/static/ {
proxy_cache_valid 200 90m;
proxy_buffering on;
expires 864000;
proxy_pass http://odoo;
}
}

13
deploy/odoo.conf Normal file
View File

@ -0,0 +1,13 @@
[options]
admin_passwd = $pbkdf2-sha512$600000$l5KS8v6/977XOgcAIITwvg$F92iQue/QiuEv1Jgf8xefn.ikVIanCsopqLdhpWbFal1DnM7UqCRtZyPwFr4DsVL5hHEOuFcfkrRH5Wv6sDSuQ
db_host = db
db_port = 5432
db_user = odoo
db_password = odoo
addons_path = /mnt/extra-addons
dev_mode = reload,qweb,werkzeug,xml
list_db = True
proxy_mode = False
logfile =
log_level = info

101
scripts/check_brand_leak.py Normal file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Fail if a client-specific term appears inside a product (community_*) module.
Deployment modules (matching *_deployment) are exempt, since client identity
is expected to live there. Run from the repo root:
python scripts/check_brand_leak.py
"""
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
ADDONS_DIR = REPO_ROOT / "addons"
BANNED_WORDS = [
"tncsc",
"tamil nadu",
"tamil nadu cultural society",
]
BANNED_PATTERNS = [
re.compile(r"\b" + re.escape(word).replace(r"\ ", r"\s+") + r"\b", re.IGNORECASE)
for word in BANNED_WORDS
]
# Any email/domain that identifies the specific client deployment.
BANNED_DOMAIN_PATTERN = re.compile(r"\btncsc\.(ca|org|com)\b", re.IGNORECASE)
SCAN_EXTENSIONS = {".py", ".xml", ".csv", ".js", ".rst", ".md", ".html", ".txt"}
# Never scan the plan itself, or this checker's own word list.
EXCLUDE_FILENAMES = {"check_brand_leak.py"}
def is_deployment_module(module_dir: Path) -> bool:
return module_dir.name.endswith("_deployment")
def is_product_module(module_dir: Path) -> bool:
return module_dir.is_dir() and not module_dir.name.startswith(".")
def iter_product_files():
if not ADDONS_DIR.exists():
return
for module_dir in sorted(ADDONS_DIR.iterdir()):
if not is_product_module(module_dir) or is_deployment_module(module_dir):
continue
for path in module_dir.rglob("*"):
if not path.is_file():
continue
if path.name in EXCLUDE_FILENAMES:
continue
if path.suffix.lower() not in SCAN_EXTENSIONS:
continue
yield path
def scan_file(path: Path):
"""Return a list of (line_no, line_text, matched_term) violations."""
violations = []
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except OSError:
return violations
for line_no, line in enumerate(text.splitlines(), start=1):
for pattern in BANNED_PATTERNS:
if pattern.search(line):
violations.append((line_no, line.strip(), pattern.pattern))
if BANNED_DOMAIN_PATTERN.search(line):
violations.append((line_no, line.strip(), BANNED_DOMAIN_PATTERN.pattern))
return violations
def main() -> int:
if not ADDONS_DIR.exists():
print(f"No addons/ directory found at {ADDONS_DIR}; nothing to check.")
return 0
any_violation = False
for path in iter_product_files():
for line_no, line_text, term in scan_file(path):
any_violation = True
rel_path = path.relative_to(REPO_ROOT)
print(f"BRAND LEAK: {rel_path}:{line_no}: matched /{term}/ -> {line_text}")
if any_violation:
print(
"\nOne or more community_* (product) modules reference client-specific "
"identity. Move this content into the appropriate *_deployment module."
)
return 1
print("Brand-leak check passed: no client-specific terms found in product modules.")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Validate that every product module manifest is App-Store-ready.
Checks community_* modules for the required keys (license, version, author,
summary, category, website, depends). *_deployment modules are exempt from
price/currency/images since they are never sold independently.
"""
import ast
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
ADDONS_DIR = REPO_ROOT / "addons"
REQUIRED_KEYS = ["name", "version", "author", "website", "license", "category", "summary", "depends"]
PRODUCT_ONLY_KEYS = ["price", "currency", "images"]
def load_manifest(path: Path) -> dict:
return ast.literal_eval(path.read_text(encoding="utf-8"))
def main() -> int:
if not ADDONS_DIR.exists():
print(f"No addons/ directory found at {ADDONS_DIR}; nothing to check.")
return 0
errors = []
for module_dir in sorted(ADDONS_DIR.iterdir()):
manifest_path = module_dir / "__manifest__.py"
if not manifest_path.exists():
continue
manifest = load_manifest(manifest_path)
is_deployment = module_dir.name.endswith("_deployment")
required = REQUIRED_KEYS + ([] if is_deployment else PRODUCT_ONLY_KEYS)
for key in required:
if key not in manifest or manifest[key] in (None, "", []):
errors.append(f"{module_dir.name}: missing or empty required manifest key '{key}'")
if not is_deployment and manifest.get("license") not in ("LGPL-3", "OPL-1"):
errors.append(
f"{module_dir.name}: license must be 'LGPL-3' (or 'OPL-1' for an "
f"intentionally paid-closed module), got {manifest.get('license')!r}"
)
if manifest.get("version") and not str(manifest["version"]).startswith("19.0."):
errors.append(f"{module_dir.name}: version should start with '19.0.', got {manifest.get('version')!r}")
if errors:
print("Manifest completeness check FAILED:\n")
for error in errors:
print(f" - {error}")
return 1
print("Manifest completeness check passed.")
return 0
if __name__ == "__main__":
sys.exit(main())