Process modules and business types
Process modules and business types
For integrators and implementers who install, combine and ship Juxtaboard modules. This page explains what a module is, how the installer works, the three ways to run it, and the engine features modules depend on. The full list of modules is in . Moving your own setup to another project is in .
If you are new to tables, links and rollups, read first. For a business-side view of the same bundles, see . For the click path in the product, see .
What a module is
A module is a ready-made set of data tables, plus the setup around them (calculated fields, views, dashboards, automations), for one part of a business: bookings, invoicing, point of sale, and so on. You assemble a business by picking modules, or by picking a business type that picks them for you.
There are 33 built-in modules in three tiers. The tier names are shown in the module picker as Foundation, Processes and Extensions.
Tier | Count | What it is | On a second install |
|---|---|---|---|
Foundation | 9 | Shared, single-source-of-truth tables: people, contacts, customers, products, services, categories, price lists, locations, ledger. Everything else links to these | Bound to the table you already have. Never duplicated |
Processes ( | 24 | The processes a business runs: CRM, bookings, invoicing, point of sale, field service, and so on | Always created fresh, so you can have two pipelines or three schedules |
Extensions | 0 | Cross-cutting overlays. The tier exists, but no built-in module uses it | Not applicable |
A module is a starting point, not a locked schema. After the install you own the tables and can change any of them.
How modules depend on each other
Each module declares three lists. The entries are table purposes (the role a table plays, such as core_customer or core_invoice), not table ids.
List | Meaning | Effect at install |
|---|---|---|
| The capabilities that exist after this module installs | Other modules find their targets through these |
| Hard dependencies | The installer adds a module that provides each one, and the dependencies it needs in turn |
| Soft dependencies | If the capability is already there, the module links to it. If not, the module works without it. Not added automatically |
Example: field_service enhances core_invoice. It links work orders to invoices when invoicing is installed and runs fine without it.
Careful: only
requirespulls modules in. If you wantpos_salesto draw down stock, selectinventory_stockyourself.pos_salesonly enhances it. Business types list such modules explicitly for this reason.
Two modules provide no table at all: webshop_site and booking_site create a public website instead (see What an install adds beyond tables).
Purpose and behaviour
A table's purpose says what it means (customers, invoices, price list). Modules, links between tables and some features use it to find "the customers table". A table's behaviour is an engine capability switched on for it. There are six: default_person_directory, person_directory, price_list, ledger, fx_rates and exclusive_scheduling. Five follow from the purpose; exclusive_scheduling is set explicitly, and the module templates for work orders, class sessions, trips, reservations and events set it for you.
Recurring billing used to be a behaviour. It is now the Issue recurring invoice automation action (see Recurring billing).
Setting a purpose or behaviour by hand is covered in , section "Table purposes and behaviours".
How an install works
You give the installer a list of module ids. It works in this order.
Expand dependencies. For every requires entry it adds the first module that provides it, and repeats for those modules.
Order the modules so each one comes after the modules it requires. A circular dependency is reported as a problem.
Decide create or bind for each table. A foundation table whose purpose already exists in the project is bound (reused). Every other table is created.
Create a folder for the new tables, if you asked for one. The picker uses the name Project setup. Reused tables stay where they are.
Create each table and link its reference columns by purpose, not by id. A reference column carries the purpose of its target; the installer points it at whichever table provides that purpose, whether that table was created in this run or already existed. This is why modules can link to each other without hard-coded ids.
Link what is still open. A second pass links columns whose target was created later in the same run, for example an invoice and its lines, which point at each other.
Run each module's setup (rollups, views, dashboards, automations, balance columns), dependencies first.
Money columns take the project's base currency at creation, and column labels follow the project language.
Example. Installing pos_sales alone gives:
Step | Module | Why |
|---|---|---|
1 |
|
|
2 |
| The module you picked |
foundation_customers, inventory_stock and foundation_locations are not added, because pos_sales only enhances them. Add them to the list to get walk-in customers, stock tracking and locations.
Running it again
The install is safe to repeat. Foundation tables are reused, so a second run does not duplicate your customers table. Process tables are created again each time, which is what you want when you need a second pipeline, and not what you want if you ran the same install twice by mistake.
When something goes wrong
If a table cannot be created, the installer deletes the folder and the tables it created in this run, and reports the error. No half-installed module is left behind.
Setup steps (views, dashboards, automations) are best effort. A failed step becomes a warning and the rest continues. The tables are already in place.
A reference column with no table providing its target is reported in unresolved. Install the missing module, or choose the target in the column settings.
Unknown module ids and unsatisfiable dependencies are reported in problems.
The result of an install lists created and bound tables (by template id), tablesByPurpose, unresolved, problems and warnings.
Ways to install
The MCP tools
list_process_modules returns the built-in modules with this project's install state. Optional argument tier: foundation, domain or extension. Each entry has id, tier, nameKey, descriptionKey, provides, requires, enhances, tableCount, installed, installedPurposes and missingPurposes. A partly installed module shows installed: false with the missing purposes. A module that creates no table (the two site modules) always shows installed: false; read its provides instead. nameKey and descriptionKey are translation keys, not display text.
preview_module_install takes moduleIds and writes nothing. It returns expandedModules (in install order), tables (each with moduleId, templateId, purpose and action) and problems.
install_modules takes moduleIds and an optional folderName. Without folderName the tables go to the project root. It returns the install result described above.
Careful: the preview does not look at your project. It plans as if the project were empty, so every foundation table shows
action: "create"even when it will be reused. The real install decides create or bind against what exists. Uselist_process_modulesto see what is already there.
The MCP tools use the built-in catalogue only. They do not see packages imported into the project, and they do not add sample data.
The REST route
POST /api/projects/{projectId}/modules/apply
Content-Type: application/json
{
"moduleIds": ["bookings", "billing_ar"],
"folderName": "Project setup",
"parentId": null,
"verticalId": "salon_spa",
"seed": true
}
Field | Meaning |
|---|---|
| Required. A non-empty list of module ids. Built-in ids and ids of packages imported into the project both work |
| Optional. Folder for the new tables. Omit it for the project root |
| Optional. Document id of the folder that should contain the new folder |
| Optional. With |
The response is the install result plus warnings. Errors: 400 no_modules for an empty list, 400 install_failed when nothing could be installed (for example only unknown ids), 403 without the Owner right, 500 install_failed with the database message. The route uses the signed-in session.
Business types
A business type is a curated list of module ids. Choosing one in the dialog pre-selects its modules; you can still add or drop modules on By module before you press Install selected. A business type adds no install logic of its own.
Each business type has:
Core modules: the modules it needs. Foundation modules that nothing else requires (usually foundation_customers) are listed here too, because the installer does not pull in modules that are only enhanced.
Optional modules: suggestions that are pre-selected but easy to drop.
There are 18 business types in 14 industry groups. The full table is in .
Sample data. When you install after choosing a business type, seven of them also add a few realistic English sample rows to the tables that install created: salon_spa, auto_repair, retail_store, restaurant, fitness_studio, health_clinic and agency_consulting. Sample rows go only into tables created by that install, never into a table that was reused. If a sample row fails, it is skipped with a warning and the install continues. Installing modules by hand adds no sample rows.
What an install adds beyond tables
Many modules also run a setup step after their tables exist. Typical results:
Rollups and counts that need the child table (invoice totals, order totals, enrolled counts, average ratings).
A kanban board by status, a calendar on the scheduled window, a grid with a highlight rule, and a small dashboard.
Balance columns on master tables (on_hand on the product catalogue, available_credit on customers).
Automations and groups where a process needs them.
Setup steps never fail the install. Anything that did not apply comes back as a warning.
Four things are deliberately not done for you:
Websites are not published. webshop_site and booking_site create a ready-to-edit site document bound to your tables. Open the site and use Publish to web to go live. booking_site also switches on the public booking API for the bookings table.
Patient and visit data is not shared with the portal. clinical_records never grants portal access to its tables. Share a record with a portal user only by a deliberate access grant.
No stock or money moves at install time. Stock and payments move only when a row is received, completed or applied (see Cross-module actions).
Sample rows only come from a business type, not from picking modules.
Engine features the modules use
These are table features you can also use on your own tables. The user guide explains how to set each one up in the product; this section says where the built-in modules use them.
Auto-numbering. A Sequence column stamps a number such as INV-2026-0001 on each new row. The built-in tables use a yearly reset: invoices INV-{YYYY}-, credit notes CN-{YYYY}-, purchase orders PO-{YYYY}-, sales S-{YYYY}-, work orders WO-{YYYY}-, trips TRIP-{YYYY}-, production orders MO-{YYYY}-, production batches PB-{YYYY}- and memberships MEM-{YYYY}-, each padded to four digits. Settings are described in , section "Auto-numbering, balances and stock costing".
No double-booking (exclusive_scheduling). The table marks one or more resource columns and a start and an end column. A write is refused if its time window overlaps another row and the two share a resource in any one of the resource columns. Resources are compared column by column, so person 5 never clashes with room 5. A row whose status is in a Declined bucket (for example Cancelled or No show) no longer holds its slot. Built-in uses:
Table | Resources |
|---|---|
| assigned technician |
| instructor and room (location) |
| vehicle and driver |
| dining table |
| venue (location) |
The bookings table is not guarded by this behaviour. You can switch it on for your own tables; see the same user-guide chapter.
Ledger and balance columns. A table with the ledger behaviour is an append-only log of signed movements. A Balance column on a master table keeps the running total. Two built-in pairs:
Master | Balance column | Ledger | Negative allowed |
|---|---|---|---|
|
|
| Yes. Overselling and backorders are normal in retail. Turn off Allow negative balances in Configure balance to forbid |
|
|
| No. A draw that would overspend the wallet is refused |
Installing inventory_stock also turns on FIFO cost layers for the stock ledger. Production uses them to value consumed materials.
Automatic status changes. A status can carry rules of the form "when this formula is true, move to that status". After a row is saved and recalculated, the first matching rule for the row's current status is applied as the system, and only if the move is allowed, the target's required fields are filled and its entry condition holds. A rule that is not allowed is skipped silently. Built-in uses:
Invoice: sent moves to paid when outstanding <= 0 and the total is above zero, and to partial when payments have arrived. partial moves to paid when settled.
Membership: active moves to past_due when the last invoice is unpaid and more than 7 days overdue. past_due moves back to active when that invoice is settled.
Careful: a rule that depends on the date must read a formula column that uses
TODAY()orNOW(), not an inlineTODAY(). The daily refresh recalculates only columns with such formulas, and only rows whose cells changed trigger the rules. A date test written inline in the rule never gets re-evaluated, so the rule never fires. Membership dunning does this with itsdays_past_duecolumn.
Recurring billing
Memberships and leases bill themselves through an automation rule that the module installs: Issue membership invoices on core_membership and Issue rent invoices on core_lease. Each one runs daily at 02:00 and, for every due row, issues an invoice and moves the row to its next period. Because it is an ordinary automation, it has run records, a dry run, a manual run and conditions you can edit.
The subscription table tags its columns with a billing role: nextRun (a blank date pauses billing), cadence (weekly, monthly, quarterly or annual), amount, billTo and lastInvoice are required. quantity, periodStart, periodEnd, billedAmount, anchorDay and previousSubscription are optional. You can tag your own table the same way and add an Issue recurring invoice action to it.
What one run does for one row:
Skips the row if nextRun is blank or still in the future, or if its status is not billable.
Finds the invoice already issued to the bill-to party for the same period, or creates one with issue date today and due date 14 days later. A new invoice is moved to sent, so payments and automatic status changes take over. Several subscriptions of one customer that are due together therefore land on one invoice.
Adds one invoice line, tagged with the subscription and the period start. The price is the row's amount times its quantity (1 when the table has no quantity column).
Only then advances nextRun by one cadence step and records the invoice as the last invoice.
A blank or zero amount or an empty bill-to makes the run fail visibly, and an unknown cadence shows as a skipped run with the reason. Both appear in the rule's run records, so a customer is never quietly left unbilled.
A period is never billed twice: a rerun finds the existing line and only completes the missing step. Month-based cadences keep the original day of the month where the month is long enough (for example 31 January, 28 February, 31 March).
Only rows in the active status are billed. trial is not billable yet, and past_due is left out on purpose so a member who is already behind does not accrue more debt.
Memberships also get a Change plan rule: a button that creates the replacement subscription with the unused value of the current period converted into time at the new rate, then closes the old one. It is blocked while the last invoice is unpaid.
Cross-module actions
Four actions move stock or money across modules. They are explicit calls, not side effects of changing a status, and each is available as a signed-in POST route. All of them need Edit access to the tables involved. The stock side finds the ledger through the on_hand balance column on the product catalogue. If inventory_stock is not installed, the stock side is skipped, the document still works, and the response says so.
Route (under | Body | Effect |
|---|---|---|
|
| For every line, writes a positive |
|
| For every line, writes a negative |
|
| Reads the bill of materials. Writes an |
|
| Writes a draw on the customer's credit wallet, then a |
All four are repeat-safe where it matters: receiving, completing or posting a document that is already done returns 409. If a later step fails, the movements written earlier in the same call are deleted, so no half-booked stock is left. Errors use short codes such as po_has_no_lines or amount_must_be_positive.
Known limits
Class and event capacity is advisory. seats_left is calculated and highlighted when it reaches zero, but enrolments and registrations beyond capacity are not refused.
Purchase orders receive in full. There is no partial receipt.
Bills of materials are one level deep. A component that is itself manufactured is not expanded into its own parts.
Sales refunds do not return stock. The refunded status is a record; no return movement is written.
No automatic billing from work orders, trips or visits. field_service, dispatch_fleet and clinical_records link to invoices when billing is installed, but a finished job, a fare or a visit does not create an invoice by itself. Recurring billing (memberships, leases) does.