This file provides guidance to Cline (me) when working with code in this repository.
Full Circle is a multi-tenant web-based ERP system built with Elixir/Phoenix, covering accounting, billing, payroll (Malaysia-standard), inventory, and agricultural operations. It uses Phoenix LiveView exclusively for the UI (no REST/SPA pattern).
Runtime versions: Elixir 1.19.5, Erlang/OTP 28.3.1, Phoenix 1.8.3, Phoenix LiveView 1.1.x
Remote origin uses SSH: git@github.com:tankwanghow/full_circle.git
# Setup
mix setup # deps.get, ecto.setup, assets.setup, assets.build
# Development
mix phx.server # Start dev server (HTTP :4000, HTTPS :4001)
iex -S mix phx.server # Start with interactive shell
# Database
mix ecto.migrate # Run migrations
mix ecto.reset # Drop, create, migrate, seed
# Testing
mix test # Run all tests (auto-creates/migrates test DB)
mix test test/full_circle/accounting_test.exs # Single test file
mix test test/full_circle/accounting_test.exs:42 # Single test at line
# Code quality
mix credo # Static analysis
# Assets
mix assets.build # Build CSS (Tailwind) and JS (esbuild)
mix assets.deploy # Minified build + phx.digest for productionEvery entity belongs to a Company. Routes are scoped as /companies/:company_id/*. The set_active_company plug verifies user access via CompanyUser junction table and stores the active company in session. All queries are scoped through Sys.user_company(company, user) subquery joins to enforce data isolation.
FullCircle.Repo— Primary read/write repoFullCircle.QueryRepo— Read-only repo (separate DB userfull_circle_query) for complex reporting queries
FullCircle.Schema (lib/schema.ex) sets binary_id as primary key type for all schemas. Use use FullCircle.Schema instead of use Ecto.Schema.
| Context | Purpose |
|---|---|
Accounting |
GL accounts, transactions, tax codes, fixed assets, contacts |
Billing |
Sales invoices (Invoice) |
Billing |
Purchase invoices (PurInvoice) — same context |
ReceiveFund |
Cash receipts, received cheques |
BillPay |
Payments |
Cheque |
Deposits, returns, post-dated cheques |
DebCre |
Debit/credit notes |
HR |
Employees, salary types, pay slips, time attendance, holidays |
Product |
Goods and packaging |
Layer |
Agricultural: houses, flocks, harvests, weighing, movements |
EInvMetas |
E-invoice metadata (Malaysia LHDN integration) |
Reporting |
Report queries |
Sys |
Companies, users, logging |
UserAccounts |
Authentication (bcrypt, session tokens) |
Authorization |
Role-based access via can?(user, :action, company) |
Reusable CRUD operations used across most contexts: get!, filter, create, save, delete. Accepts schema class, company, and user — automatically handles authorization checks and audit logging. When adding a new entity, implement the query/2 function in the schema's context.
Defined in lib/full_circle/authorization.ex. Roles: admin, manager, supervisor, cashier, clerk, auditor, punch_camera, guest, disable. Authorization uses pattern-matched can?/3 functions.
Each feature follows a consistent folder pattern:
index.ex— List view with search/paginationform.ex— Create/edit formindex_component.ex— Table row component for indexdetail_component.ex— Nested detail line component (e.g., invoice lines)print.ex— Print-optimized view (usesprint_rootlayout, HTML print stylesheets)
Five layouts in lib/full_circle_web/components/layouts/:
root.html.heex— Main app shellapp.html.heex— Authenticated app contentprint_root.html.heex— Print-optimized (no nav, print CSS)punch.html.heex— Time punch kiosk moderecon.html.heex— Face recognition attendance
- CSS: Tailwind CSS 3.4, configured in
assets/tailwind.config.js - JS: esbuild with ESM format and code splitting. Entry points in
assets/js/:app.js— Main app (LiveView hooks, IndexedDB caching viaindexdb.js)tri_autocomplete.js— Custom autocomplete componenttake_photo_human.js/face_id.js— Face recognition (uses Human.js library inassets/vendor/human-main/)qr_attend.js— QR code attendance scanning
Supports English (en) and Chinese (zh) via Gettext. Locale stored in session, set by set_locale plug.
- Document numbers are gapless per company (see
create_gapless_doc_numbermigration) - Transactions use a double-entry pattern with
TransactionandTransactionMatchertables - Database triggers handle transaction posting automatically (see
create_transaction_triggermigration) - Print views support
pre_printparameter (true = data only for pre-printed forms, false = full letterhead) - Company deletion cascades via database triggers (see
create_triggers_when_delete_companymigration) - PostgreSQL
pg_trgmextension used for fuzzy search (seecreate_fuzzy_searchmigration)
- Use
list_code_definition_namesonlib/full_circle/to get all module/function names quickly. - Use
search_fileswith regex likedef can\?\(user, :to find authorization patterns. - Use
read_fileonlib/full_circle/authorization.exfor role checks.
- Adding new LiveView event: Search for existing
def handle_event("save"and insert similar block. - New schema field: Use
replace_in_fileto add toschemablock andchangesetfunction. - Authorization: Add
can?(user, :new_action, company)inauthorization.ex.
- After edits, run
execute_commandwithmix testto verify. - Use
search_filesto find test files for modified modules.
- Use
execute_commandfor./deploy_to_linode/deploy.shafter commits. - Check
priv/repo/seeds.exsfor data setup.