Add cashflows
A basic cashflow usually defines an owner, direction, schedule, and amount. The amount can be a number, a Money value, an input reference, or a synchronous JavaScript function. Shared settings can instead be inherited from a flow group. Account postings are covered below.
import { cashflow, category, currencies, defineModel, entity, inflow, outflow, periods, schedule } from "deccf";
const building = entity.asset();
const rent = cashflow({
owner: building,
direction: inflow,
category: category("property.rental_income"),
schedule: schedule.monthly(),
amount: 10_000,
});
const maintenance = cashflow({
owner: building,
direction: outflow,
category: category("property.maintenance"),
schedule: schedule.monthly(),
dependsOn: [rent],
amount: (ctx) => ctx.get(rent).amount * 0.05,
});
const model = defineModel({
name: "rent-and-maintenance",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { building },
flows: { rent, maintenance },
});
return model;inflow gives a positive sign and outflow a negative sign. Direction and category are separate: direction sets the sign, while category groups results. The core engine has no built-in business categories. Domain packs can supply reusable categories; a model can define its own with category(path).
amount(ctx) runs once in each scheduled period when the cashflow is active. ctx.time.index starts at zero. ctx.get(rent) returns the current period's signed amount as Money; use .amount to continue with a JavaScript number.
Same-period reads between cashflows require the reader to list the source in dependsOn. The compiler checks the dependency order and rejects cycles. ctx.previous(flow) reads the prior period's amount, and ctx.sum(flow, from, to) sums an inclusive period range that does not extend into the future.
Postings to accounts
A cashflow can also post a balanced transaction to accounts. Put reporting categories on the accounts; each posting row inherits the category of its target account.
import { account, cashflow, category, currencies, defineModel, entity, periods, schedule } from "deccf";
const building = entity.asset();
const cash = account({ owner: building, type: "asset", category: category("assets.cash") });
const rentIncome = account({ owner: building, type: "income", category: category("income.rent") });
const rent = cashflow({
cashAccount: cash,
debit: cash,
credit: rentIncome,
schedule: schedule.monthly(),
amount: 10_000,
});
const model = defineModel({
name: "rent-posting",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { building },
accounts: { cash, rentIncome },
flows: { rent },
});
return model;debit and credit are shorthand for two posting rows with the same amount. Use bookings for more rows; each row points to one account and can have its own amount. owner is optional. direction can be omitted when the engine can identify one asset account as cash, or when the postings are not a cash movement. If several asset accounts are involved, set cashAccount so the engine knows which movement represents cash.
import { account, cashflow, category, currencies, defineModel, entity, periods, schedule, trigger } from "deccf";
const building = entity.asset();
const cash = account({ owner: building, type: "asset", category: category("assets.cash") });
const rentIncome = account({ owner: building, type: "income", category: category("income.rent") });
const latePay = trigger("latePay");
const receivables = account({ owner: building, type: "asset", category: category("assets.receivables") });
const billedRent = cashflow({
cashAccount: cash,
schedule: schedule.monthly(),
amount: 10_000,
bookings: [
{ debit: cash, amount: (ctx) => ctx.amount / 2 },
{ debit: receivables, amount: (ctx) => ctx.amount / 2 },
{ credit: rentIncome, amount: (ctx) => ctx.amount },
],
triggers: latePay,
});
const rentSettlement = cashflow({
cashAccount: cash,
schedule: schedule.onceAfterNoOfDays(30),
triggeredBy: latePay,
bookings: [
{ debit: cash, amount: (ctx) => ctx.source.amount / 2 },
{ credit: receivables, amount: (ctx) => ctx.source.amount / 2 },
],
});
const model = defineModel({
name: "rent-with-late-settlement",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { building },
accounts: { cash, receivables, rentIncome },
flows: { billedRent, rentSettlement },
});
return model;Each occurrence must balance: total debits equal total credits. Posting amounts are zero or positive; a debit increases the signed account balance and a credit decreases it. A liability balance is commonly negative, so a debit payment moves it back toward zero. Categories on balance-sheet accounts follow the signed movement. For income and expense accounts, reports use economic signs: income is positive and expense is negative.
ctx.amount is the cashflow's base amount inside posting functions. A trigger(name) can be listed in triggers; a subscriber using schedule.onceAfterNoOfDays(n) runs after that delay. Set triggeredBy when the model emits multiple trigger types. ctx.source contains an immutable snapshot of the triggering occurrence, including its amount, date, and period.
Callbacks execute synchronously in the host process. Keep side effects out of them so calculation behavior depends on the declared model and run options.
Next: Choose a timeline and schedules.
Group flows with shared defaults
Use flowGroup() when several flows share settings such as an owner, direction, or schedule. A child flow can omit those settings and inherit them, or set its own value to override the group. Groups contain an array of flow references.
import { cashflow, currencies, defineModel, entity, flowGroup, flows, outflow, periods, schedule } from "deccf";
const company = entity.asset();
const operatingCosts = flowGroup({
name: "operatingCosts",
owner: company,
direction: outflow,
schedule: schedule.monthly(),
flows: [
cashflow({ name: "rent", amount: 12_000 }),
cashflow({ name: "insurance", schedule: schedule.annual(), amount: 2_400 }),
],
});
const model = defineModel({
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { company },
flows: flows(operatingCosts),
});
return model;Here rent uses the group's monthly schedule. insurance overrides it with an annual schedule. With flows(operatingCosts), output names use the group name and each child name, such as operatingCosts.rent. If you put a group under a key in the model's flows map, that map key is used as the prefix. Name child flows when you want stable, descriptive names; unnamed children use their position in the group.
flows(...items) accepts flow references, flow groups, and arrays of either, then returns a flat array of flow references. Pass that returned array directly to defineModel({ flows }) so the model retains each group's inherited defaults. A standalone cashflow() may omit settings that a containing group will supply; compilation reports an error if a required schedule or cash-movement direction is still missing after inheritance.