Swap the inbound greeting to KamoCRM-Main-Intro-2
Uploaded as a new file rather than overwriting the old one, so rolling back is a one-line change to KAMO_ATTENDANT_INTRO plus a dialplan reload — no re-upload n...
Rebuild against kamo-shared-library for the SW5 task book
An empty commit. ServiceTaskBookService and the four task-book entities live in the shared library; ServiceTaskBookController cannot resolve them until Security...
Author a flat-rate task book, priced by the minute
Replaces the SW5 placeholder pane with the real authoring surface: three sub-views on one tab — books, categories, entries — following the SubView switcher Mark...
BaseLosController's unauthorized()/forbidden() do exist
A task brief told an implementer those helpers were invented and did not exist. requireOrgId genuinely does not; those two do, declared protected static on Base...
Right-gated task book API
SW5 Task 3, controller half. CRUD for books, categories, tasks and options, plus GET /{bookUid}/tasks/by-code/{code} - the lookup a quote or work order performs...
Task book service with org-scoped CRUD and code resolution
SW5 Task 3, library half. ServiceTaskBookService exposes books, categories, tasks and options behind an org-scoped API, plus resolveTaskByCode - the lookup a qu...
Un-gate pricing, tax and tiers for service markets
A SERVICE market got exactly three tabs — Overview, Vendors, Agreements. Customers, Tax & Shipping and Discounts & Pricing were all gated on commerceType === 'R...
Apply a remittance to a claim
"Unapplied money" is money the payer has sent that nobody attached to a claim, and the queue could only be looked at. POST_INSURANCE_PAYMENTS gated an operation...
Find a claim by the number a payer quotes
Posting a remittance needs the claim it settles, and a remittance carries no internal id — only the number the practice sent. Without this lookup, applying mone...
An inbound call reads properly and respects the status pill
Three things wrong with the softphone tool window's incoming-call overlay. The number was painted exactly as the INVITE carried it, so a caller arrived as "+15...
Four things that could be looked at and not done
Each of these had a working endpoint, a client function, and no way for a person to reach it. Closing an order loop. The open-loops queue exists because the or...
Nothing returned an account id, so nothing could be withdrawn
Withdrawing portal access and granting a proxy both take an account id, and no endpoint ever returned one. A practice could invite a patient to the portal and t...
Record the enum-ordering trap the PBX rollout hit
Seeding a catalog row whose pricing_model is a new enum value makes that row unreadable to any service still on an older shared-lib, and getCatalog loads every ...
Rebuild against shared-lib PricingModel.PER_EXTENSION
The running image predates the enum value, so reading the two seeded PBX catalog rows (pricing_model = 'PER_EXTENSION') throws on enum parse - and because Subsc...
Add the flat-rate task book
A priced task catalogue whose entries carry labourMinutes — the number that both prices a line and sizes a visit. ServiceLineItem.taskCode, landed in SW3, joins...
Restore SW4's close-out, lost to a stale working-tree edit
The previous commit edited the on-disk copy of this plan, which predated the Shipped section — that section was appended to a scratchpad copy and landed from th...
Raise memory limit to 1Gi and relax liveness probe timeout
Traefik sat at ~485Mi against a 512Mi limit (95%). The cgroup recorded 47836 memory.max hits with oom_kill 0 — never OOM-killed, because enough of the footprint...
Applying DDL opens a 40001 window — watch it, do not restart
Bumping a catalog version leaves pooled JDBC connections holding the old one and the next query through each fails SQLState 40001, which Hibernate does not retr...
Price-list entry read and update spoke to the wrong endpoint
Two silent client/server mismatches made the retail price-list entry screen's write path dead: - the entries list was unwrapped with the key "entries", but C...
Per-extension PBX billing schema, backfill and catalog
PbxBillingSchemaMigration is @Order(0) because it adds a column. The shared-library entity maps phone_cost_mode from the moment the build starts, so any runner ...
Bill phone service by the extension
PricingModel.PER_EXTENSION, its own reserved quantity key, an allocator, a nightly reconciler, a removal gate and the summary API - the phone twin of the per-ma...
Give the phone add-ons a phone icon
PER_EXTENSION already renders as "extension/month" - addOnUnitLabel handles it and needs no change. The icon map does: it is keyed on the lowercased catalog cod...
Show what extensions cost, where extensions are managed
ExtensionUsagePanel on the stats tab, beside the mailbox and storage panels: count by class, the catalog rate, the monthly total, and the rule in plain language...
Queue timeout and "press 0" both fall through to voicemail
Replaces the hand-rolled Dial()+Goto queue loop with app_queue. The deciding factor is DTMF: digits are NOT matched against the dialplan while Dial() is running...
Count the extensions a third-party server does not charge for
Queues, IVRs, ring groups and spares are the extensions nobody is assigned to, and under the third-party rule they are free - twelve of one production org's sev...
The extension sweep no longer erases every member assignment
MemberVoipConfigController.put writes an assignment to two places - MEMBER_VOIP_CONFIG and **************** - and **************** then cleared the second on it...
Count billable phone extensions
Adds the phone twin of the per-mailbox aggregator: PbxBillingClass with an exhaustive VoipProviderType switch and no default arm, OrgPbxBillingAggregator (no Sp...
Hold-music queue ringing 8000 and 8080 simultaneously
Eight hold tracks uploaded as the `kamocrm-hold` MOH class, shuffled. The random ordering comes from `random=1` on the row in FreePBX's `music` table, which is ...
A reaction toggled twice at once is a no-op, not a 500
setReaction read, then saved, then caught the constraint violation the read was supposed to prevent — which does not work: the violation surfaces at flush, afte...
Let the database decide whether a reaction is already there
The unique constraint on (message, member, emoji) was doing nothing useful. A read-then-save loses the race with the member's own second click, and catching the...
A failed reaction or edit does not stick to the message
Takes @kamo/chat-core 0.1.9, which rolls an optimistic edit or reaction back when the connection drops rather than only when the server refuses. The bubble's sa...
An emoji on a single message, and a message you can correct
A member asked for both on the individual chat. They land on the same per-message menu the window already has, beside Copy and Unsend. Reactions render as a ch...
React to a message with an emoji, and correct one you sent
POST **************** sets or clears the caller's emoji and answers with the message's whole reaction set, so a client that raced itself converges on the server...
Per-message emoji reactions, and a message its sender can correct
Two things a member asked for on the individual chat: an emoji anyone in the conversation can put on a single message, and the ability to fix a message you have...
Recorded greeting, dial-by-extension, 1/2 to operator
Replaces the music-on-hold hold pattern with the recorded intro: - **************** plays the greeting AND listens at the same time, so a caller can dial ove...
The workspace you chose is the one you land in
Choose "ABC Mortgage" at sign-in and you arrived in a workspace wearing KamoCRM's logo and colours, with a left-hand menu offering White Labeling, Plans & Billi...
Stop the unread card offering mail that was read and deleted
The home-page unread-mail widget reads email_metadata.search_index directly — it must not spend an IMAP connection, which is the whole reason it exists — and th...
Keep support tickets out of the per-member unread badge
perSender is what puts a number on a person — their member-directory row, their hex head — so it has to mean "unread messages from them". It was summing every s...
An unread count names a sender only when a single member is the answer
senderId is what pins an unread badge to a person — their member-directory row, their hex head — so it has to mean "these messages are from them". It was set fo...
Inbound auto-attendant — music on hold + dial-by-extension
Inbound calls had nowhere to go: the `incoming` table was completely empty, so even a delivered call would have dead-ended. Adds the attendant context plus the ...
A per-mailbox add-on is not priced per user
PER_MAILBOX fell through to the default arm, so the public pricing page advertised the two e-mail add-ons as "/user/month" against a per-mailbox price. PER_EXTE...
One shared calendar per org, one primary per member
Provisioning had nothing underneath it, so two simultaneous first loads both inserted. Collapses what that already made — twenty duplicate rows across seven org...
Stop two first loads provisioning two of each calendar
The launchpad paints two calendar widgets and each fetches its own feed, so two requests reached listCalendars at once. Provisioning is find-then-create, both p...
Saving a settings page no longer repaints the tab as the platform
Signed into Wienerschnitzel, opened Settings → Branding, saved Company Info, and the entire workspace reverted to Kamo's logo and colours. Closing the tab and o...
Allowlist Telnyx in both fail2ban jails
Adds the Telnyx US signaling addresses (192.76.120.10, 64.16.250.10 — sip.telnyx.com) to ignoreip now that the carrier trunk is provisioned. Also overrides ign...
Predial hooks must Return(), not MacroExit() — Asterisk 21
Asterisk 21 removed the Macro() application family outright, and FreePBX reaches these hooks with Gosub() (extensions_additional.conf: **************** The stoc...
Make fail2ban actually run — enable Asterisk's security log
fail2ban has never started on this PBX. Its asterisk-security jail watches /var/log/asterisk/security, a file Asterisk never created: FreePBX generates logger_l...
Scan the shared-lib email.billing repository package
@EnableJpaRepositories lists packages explicitly, so MailboxBillingDayRepository was invisible and the service refused to start outright — caught by booting it,...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.