உள்ளடக்கத்திற்குச் செல்லவும்
KAMOUniverse

மாற்று பதிவேடு

என்ன புதிது.

அது கப்பல்கள் போல், தளத்தில் ஒவ்வொரு மேம்பாடு.

  1. · kamo-shared-library

    Resolve app entitlement by ServiceType; rename LOS to MLOS

    Entitlement resolved ServiceType.name() against **************** The catalog's codes are marketing codes (USERS, VIDEO_CONF, HR_SYSTEM, ...) and never matched, so planFeatureIncluded's missing-row default granted every app on every plan — the billing-to-app link was inert. Catalog rows now carry a typed nullable service_type and resolution goes through it, so an app can be renamed without moving entitlement. - **************** typed service_type column (persisted as the id). - **************** resolve by ServiceType; honour add-ons via the previously unread **************** jsonb; missing-row behaviour behind kamo.entitlement.fail-open (default true until the catalog matrix is seeded). - SubscriptionService: carry serviceType through create/update/DTOs, and derive feature_code from the name when omitted — the settings dialog never sent one, so every create from the UI hit the NOT NULL constraint. - CommerceType: bind each vertical to its required app (MORTGAGE->MLOS, PERSONAL_LOANS->LOS). - AppliedModelEnforcementService: getAllowedCommerceTypes intersects the security-model allow-list with entitlement; assertCommerceTypeAllowed already covers create and edit. - ServiceType: LOS -> MLOS keeping id 14 (ORG_FEATURES persists the id, not the name, so no data migration); new LOS(25) for personal loans — id 25 because reusing 14 would reassign every existing mortgage row. - RoleRightType: MLOS_* rebound, ids 139-144 unchanged; personal-loan LOS_* added at 190-195. - PhiModule.PLOS added: the rename left the personal-loan app mapped to the mortgage PHI module and mortgage unmapped, which compiled clean.

  2. · kamo-internal

    Email a member from their row

    Adds a mail action beside chat, call and profile on a member row, opening the Messages tool with the recipient already filled in. It is hidden unless the org has email enabled, the viewer holds ACCESS_MAIL, and the member actually has an address on file — an action that opens an empty composer is worse than no action. The permission is resolved once in NavTopPanel rather than per row: the deeper useEmailAvailability() probe costs an OTK plus provider and mailbox round-trips, and this panel mounts app-wide. Recipients go through as { initialData: { to: [email] } }. DraftMessage.to is a string[], so a flat { to: '...' } is silently dropped and the composer opens with no recipient — the same trap HexHeadPopover documents.

  3. · MediaService

    Accept 3 GiB attachments by streaming them to storage

    file.getBytes() cannot represent an upload this large — a Java array stops just above 2 GiB — so every attachment past that died on an OutOfMemoryError regardless of heap, and a 1 GB one needed a 1 GB allocation to store a file Spring had already spooled to disk. The MultipartFile now goes to imaging as a re-openable source and is streamed straight through. Limits raised to match: multipart to 3GB per file (3100MB per request, so the largest allowed file is not rejected for its own boundary and headers) and media.attachments.max-bytes to 3 GiB. Multipart spools to an explicit 24Gi emptyDir rather than the container's writable layer. Tomcat max-swallow-size is now -1, so a rejected upload gets its 4xx delivered instead of a reset connection.

  4. · kamo-shared-library

    Stream content-addressed storage so an object can exceed heap

    Chat attachments are moving to a 3 GiB ceiling. storeContentAddressed took a byte[], which cannot express that at all — a Java array stops at Integer.MAX_VALUE — and MinIOStorageService.upload then buffered the whole object a second time so it could retry the put against the next node. Adds a streaming path alongside both: HashUtils.computeDigests derives Blake3 and SHA3-256 from one pass, uploadStreaming re-opens an InputStreamSource per node attempt instead of holding the bytes, and the new storeContentAddressed overload hashes, dedups and stores without ever accumulating the content. Peak memory is the MinIO part buffer rather than the file size. It is deliberately not @Transactional: storing gigabytes takes minutes, and the row is only published (isMissing=false) once the object is durably stored, so a crash leaves the same state the buffered path leaves.

  5. · kamo-internal

    Title the mortgage form panes Borrower(s)

    The Loan/Borrower/Subject Property form serves every borrower on the file, so the tab and pane titles now come from translations and read Borrower(s). Loan and Subject Property were hardcoded English and now resolve through next-intl too.

  6. · kamo-internal

    Drive the email template off the From address, behind an on/off switch

    The template picker in the composer footer is gone. Which template a message wears is not a separate decision from who it is sent as: the member's own primary address signs it as them, a department mailbox signs it as that department with the department's contact details, the organization's contact mailbox signs it as the organization, and another member's mailbox signs it as that member. All that is left to choose is whether to use one at all, which is now a switch in the same corner. Switching it off sends the body exactly as typed. The editor showed a signature in that state while the sent message had none, because a null template fell back to DEFAULT_CONFIG for the preview only; it now renders bare, so what is on screen is what goes out. Also fixes the job title and department never appearing in a composed signature — they hang off TeamMember and arrive as jobTitleOfficial/jobTitleTitle and departmentTitle, not jobTitle and department — and populates the mobile line the userCellPhone toggle already gated.

  7. · kamo-internal

    Offer waiting tickets to every available agent at once

  8. · kamo-internal

    Teal accept-request popup, visually distinct from the lead alert

  9. · kamo-internal

    Switch mailboxes from the same searchable picker the composer uses

    The switcher was the weaker half of the pair: a select holding every mailbox the member can open, which for org-wide access is every mailbox in the org. It also called /accessible, which builds all of them on every page load — a query per member and another per mailbox. Now a button showing the open mailbox, opening MailboxPickerDialog against /sendable. Nothing loads the whole list, and the switcher and the From field offer the same mailboxes under the same headings because they read the same endpoint. Aliases are excluded via aliases=false. Mail sent to one lands in the mailbox behind it, so there is nothing to open; toOpenableMailbox refuses them as a second line of defence. Selection restore is unchanged in behaviour: the query string already carries the id and address, so the button labels itself without waiting on a lookup and only resolves the display name if it is cheap to.

  10. · EmailService

    Let /sendable drop the alias group for callers that open mailboxes

    The /messages mailbox switcher offers the same set as the composer's From field, minus one thing: an alias cannot be opened. Mail addressed to it lands in the mailbox behind it, so it has no folders and nothing to show. Excluded server-side rather than filtered by the caller so the totals stay honest — a client dropping rows from a page would report 'showing 24 of 41234' while rendering 21.

  11. · kamo-internal

    Pure accept-popup decision machine and per-member snooze

  12. · kamo-internal

    Proxy the queue-summary and claim endpoints

  13. · kamo-internal

    Download the executed document, and choose a real signing order

    The sender had no route to the finished document at all. EnvelopeSummary advertised a signedDocumentUrl pointing inside the kamo-signer app's own BFF, authenticated by a signer session — so the only person who could ever fetch what came back was the last signer, for four hours. Adds the OTK proxy route and a download action on every completed envelope. The dialog also sent routingOrder 1, 2, 3…, which LOOKED ordered and was never enforced. ESigService enforces it now, so the choice is explicit: Any order (everyone invited together, today's behaviour and the default) or One at a time. Refusals now show their reason instead of "Action failed" — ESigService states them in words and they were being thrown away — and resend/void are hidden where the service would only ever 409.

  14. · EmailService

    Preview variables for the e-signature templates

    The admin template editor renders every placeholder blank for a key with no sampleVariables arm, so the seven esign templates previewed as a page of empty gaps — including the five new lifecycle notices. One shared arm for the lifecycle five, plus arms for the invite and the step-up code.

  15. · kamo-shared-library

    The five envelope-lifecycle email templates that were never written

    An e-signature envelope had exactly two templates: the invite and the step-up code. Nothing existed for anything that happens AFTER a signer signs — which is why the signer app promises "a signed copy will be emailed to all parties" and no such email has ever been sent. Adds esign.envelope_completed (each party's executed copy), **************** esign.signer_decoderd, esign.envelope_voided and esign.envelope_expired, with the CANONICAL_KEYS entries without which every send is a permanent 404. Also escapes free text in the two existing esign bodies: TemplateRenderer substitutes with no escaping, and a recipient name, document title and staff-typed message are all free text. The plain variants stay supplied because per-org rows are insert-only. EsignLifecycleTemplateTest pins the constant -> dotted-key -> sent-key triple and that every placeholder is one the sender actually fills.

  16. · MediaService

    Decide the ticket claim race with a conditional update

  17. · kamo-internal

    Rebuild the mortgage market settings into seven coherent tabs

    The MORTGAGE market page carried fifteen tabs, twelve of them lender config, and several answered the same question in two places: hedging was switched on under Capital Markets while its tolerance lived under Policies, the servicing model and the servicing terms were separate tabs, product enablement carried a channel restriction while channel rules allowed and disallowed channels, and delegation was set in three places with nothing to say which one won. Grouped by the question an operator is actually answering: institution business model, underwriting authority, licensing offerings product enablement, channel rules, risk overlays capital-markets execution posture, pricing policy, capital policy investors investors and their delegation, overlays, delivery, commitment servicing servicing posture, servicing policy automation AI autonomy, per-stage confidence walls partners provider connections, vendor directory Where a setting is genuinely resolved from several places, the page now says so instead of leaving the operator to guess: the authority section states the delegation precedence, channel-rule hints say an investor delivery rule beats them, and the servicing remittance target is labelled as the org default. Every section is built from a new configKit, replacing about a hundred lines of duplicated load/save/error/snackbar plumbing per tab with one section shell, one table, one save bar and one toast per tab. Licences, risk overlays and provider connections gain inline editing — the API always supported it, so fixing a typo used to mean deleting the row. The four investor sub-editors became one component behind a segmented control, so opening an investor costs one request instead of four. Overview gains a lender-profile readout wired to lenderProfile, lenderProfileValidation and changeLog: three endpoints that had existed since the surface was built and that nothing had ever called, so there was no way to see whether a profile validated or which modules it switched on. Links minted against the old slugs are remapped rather than silently landing on Overview, since SettingsView resolves an unknown ?tab= to index 0.

  18. · kamo-internal

    Pick the From address from a searchable dialog, not a dropdown

    A select cannot hold this list. Everything the /messages switcher offers is sendable, and for a member with ACCESS_ALL_MEMBER_MAILBOXES that is every mailbox in the org — tens of thousands in a large one. The composer now shows the current sender as a button and opens a picker that searches and pages on the server, so nothing loads the whole set. The picker shell is extracted from MemberPickerDialog, which already solved this for the org directory: debouncing, paging, out-of-order responses, infinite scroll, keyboard navigation, group headings, and the empty, error and loading screens. SearchablePickerDialog now owns all of it and takes a fetcher plus a row renderer. MemberPickerDialog is a wrapper over it with its props unchanged, so the manager field, chat member add and e-sign search are untouched; MailboxPickerDialog is the second wrapper. Group headings reuse the switcher's own translation keys, so the same mailbox cannot end up named two different things in two places. Composing as another member still offers that member's aliases: they are addresses on an identity rather than mailboxes, so the endpoint — which answers for the logged-in member — cannot know them. The composer merges them into the first page through the picker's fetcher hook.

  19. · EmailService

    Searchable, paged endpoint for the addresses a member can send from

    GET /api/email/mailboxes/sendable answers the same question as /accessible — every address the member may send as — but as one merged stream that is searched and paged in the database. /accessible cannot serve a picker. It materializes every group in full, and for a member holding ACCESS_ALL_MEMBER_MAILBOXES that means walking every member in the org and then issuing a query per member and another per mailbox. At five figures of staff it is tens of thousands of queries and a payload to match, on every call. Only the privileged tail is paged in SQL; the member's own mailboxes, aliases and shared mailboxes are counted in Java and always sort first, so searching for your own address is never answered on page 40. A mailbox reachable through several grants is listed once, under the narrowest — the same rule the composer applies, so no identity appears twice. Rows carry the group they came from and, for another member's mailbox, its owner, so the client can render the same headings the mailbox switcher uses. Name resolution degrades exactly as MemberDisplayName does; the two have to agree or the same person reads differently in the two places.

  20. · MediaService

    Report the caller's claimable ticket queue

  21. · MediaService

    GET /types/{typeId}/slots so the settings editor can list slots

    The settings UI has always fetched this path, but only POST/PUT/DELETE were mapped, so the Availability Slots tab got a 405 and rendered empty. Unlike /slots/available this returns every active slot on the type without filtering by advance-booking notice or child-org restrictions — an admin needs to see the slots they configured, including ones nobody can currently book. Restrictions ride along in each slot's childOrgRestrictions, so this also replaces the /slots/restrictions read the UI was attempting.

  22. · MediaService

    Own the claimable-ticket predicate in one service

  23. · kamo-internal

    Assign a mailbox to a department and to the organization

    The department dialog's contact email was free text, so it could name an address the org does not own. It becomes a picker over the org's real mailboxes -- the same list **************** offers, so "the assignable mailboxes" means the same set wherever it is offered. Optional, and "None" is both the default and how you unassign; a clear button in the field does it in one click. Behavior-labeling Contact Information keeps its email text field exactly as it was and gains the same picker beneath it. The two are independent: the text field is what the org publishes, the mailbox names one of the org's own. One MailboxSelect serves both screens so they cannot drift, over a shared cached fetch so Contact Information's read row and its picker do not each request the list. The states are kept distinct on purpose: an org with no mailboxes, a mail backend that is down, and a mailbox removed provider-side would otherwise all render as an empty field. The last one shows as "no longer available" rather than looking unassigned, because a stale pointer has to be visible to whoever fixes it. Department saves send the resolved address alongside the pointer. A SecurityService built before mailboxId existed ignores it and reads email, so without that this change would clear every department's address the first time one was saved against the currently deployed build. The rebuilt service ignores the field; remove the bridge once every environment is past that build.

  24. · InitializerService

    Mailbox pointers for departments and orgs, and backfill the departments

    DEPTS.MAILBOX_ID and ORGS.CONTACT_MAILBOX_ID, both plain UUID with no FK to email_provider.mailboxes: mailboxes sync from external providers and can be removed provider-side, and the mailbox delete endpoint knows nothing about departments, so a real FK would turn both of those into failures. DEPTS.EMAIL was free text and holds a mix. Addresses that really are one of the org's mailboxes are linked and rewritten to the mailbox's own casing -- Support@KamoCRM.com was never how that mailbox is addressed. Addresses with no matching mailbox cannot be expressed by the picker that replaces the field, so they are cleared; that is the destructive statement and it is deliberate, covering Sales@ and Corporate@ today. ORGS.CONTACT_EMAIL is left alone. It keeps its own text input and the mailbox sits beside it rather than replacing it.

  25. · kamo-shared-library

    Let a department and an org point at one of their mailboxes

    A department's contact email was free text, so it could name an address the org does not own -- two of the three departments with one set today do exactly that. It now holds a pointer to one of the org's mailboxes, and Organization gains the same pointer beside its existing contactEmail, which is untouched. Assignment is a pointer and nothing more: it records which mailbox the department or org is reached at, and grants nobody access to it. Both columns are plain UUID with no FK to email_provider.mailboxes, deliberately. Mailboxes sync from external providers and can be removed provider-side, and the mailbox delete endpoint knows nothing about departments -- a real FK would turn both of those into failures. A stale pointer is the accepted cost and the UI surfaces it. Department.email stays, holding the address the assigned mailbox resolves to. Consumers want an address, not an id, and mailbox addresses are immutable (updateMailbox only accepts a display name and password), so the copy cannot drift. Assigning writes both; clearing nulls both. A mailbox from another organization is refused -- the picker only offers this org's, but the request can be made without it, and a cross-tenant pointer would put one org's address on another's outbound mail.

  26. · kamo-internal

    A Manager field on Position, and one people picker for the whole app

    The Position card gains Manager, optional. It is a search, not a dropdown: an org can hold thousands of names, and the only thing a select with all of them guarantees is a scroll. A rejected manager — a reporting loop, say — surfaces the server's reason rather than a generic failure the member cannot act on. MemberPickerDialog is the picker. Search runs on the server, results arrive a page at a time, and scrolling asks for the next one, so nothing here holds a directory. It reads as a command palette because that is the fastest route for someone who knows the name, while browsing still works for someone who does not: arrows move, Enter picks, Escape closes, and the footer says so. Rows carry title, department, email and report count — enough to tell two people with the same name apart. Someone who cannot be picked stays visible with the reason attached; a name that silently is not in the list reads as a bug. Two pickers already existed and neither could survive a large org. The e-sign signer dialog and chat's add-members popover both fetched every member and filtered in the browser. Both now render this dialog, keeping their own sources — a signer can be any member, chat's directory is not just staff — and gaining the same search, keyboard handling and look. The popover becomes AddChatMembersDialog, since it is no longer a popover.

  27. · SecurityService

    Set a manager, and search the directory a page at a time

    GET **************** answers one page of the session org's directory. Search, ordering and paging happen in the database; a hostile ?size= is capped. Words are ANDed and LIKE metacharacters escaped, so a pasted email with an underscore narrows the search instead of widening it. The platform System User never appears. member-security now reads and writes the manager. Three rejections, in the order a caller hits them: not a team member of this org, themselves, or someone who already reports up through this member — the last would close the reporting line into a loop no query walking it could terminate, which is why it cannot live in the schema. The manager is resolved BEFORE anything is written. The save is one transaction across roles, rights, timeout and position; returning a 400 partway through would still commit everything written up to that point, so the response would report a rejected manager while the roles quietly changed. MemberAvatarUrlService is the home for avatar URL construction in new code. Several controllers carry private copies that have drifted — AccountController's still picks the photo extension with instanceof, which is false for a Hibernate proxy and serves a .png URL for a JPEG.

  28. · kamo-shared-library

    A manager, and a directory search that survives a big org

    TeamMember gains an optional self-reference for who it reports to. columnDefinition is pinned to INT8 so Hibernate cannot copy Member.ID's DEFAULT unique_rowid() onto the join column and backfill every row with a manager who does not exist. TeamMemberRepo gains searchDirectory: one page of the org's directory, filtered, ordered and windowed in the database. It is written for orgs with five figures of staff, so the caller never holds more than a page — the picker it backs runs on every keystroke. Terms are ANDed against one concatenated haystack, which is what lets "ana lending" find the Ana in Lending; a chain of ORed LIKEs cannot, because LIKE only matches contiguous text. Every association is joined with an explicit LEFT: an implicit path join is INNER in JPQL and would silently drop everyone without a department. Cycles cannot be expressed in the schema, so findManagerIdOf lets the writer walk a reporting chain one link at a time before accepting a manager.

  29. · InitializerService

    Give a team member a manager

    Adds MANAGER_ID to member_team_member, a nullable self-reference for the reporting line. Nothing is backfilled: no manager is a complete answer, both at the top of the chart and for anyone simply unassigned. The DROP DEFAULT is the load-bearing statement, not the ADD COLUMN — ddl-auto: update creates the column first. A join column to Member, whose id is INT8 DEFAULT unique_rowid(), inherits that default unless the entity pins columnDefinition, which would stamp every existing row with a random manager id. The orphan and self-reference sweeps clear ids that a defaulted column would already have written.

  30. · InitializerService

    Add DEPTS.PARENT_DEPT_ID for nested departments

    Nullable with no backfill -- null means top level, which is what every existing department was and most will stay. Declared plain UUID with an explicit DROP DEFAULT. Departments key off UUID DEFAULT gen_random_uuid(), and a FK column that inherits that default gets stamped with a random parent on every existing row, orphaning all of them and failing the constraint that follows.

உங்கள் உலகம் காத்திருக்கிறது.

நிமிடங்களில் இலவசமாகத் தொடங்கவும். நீங்கள் தயாராக இருக்கும் போது பொருட்களை சேர்க்கவும் - எந்த மாற்றமும் இல்லை, புதிய உள்நுழைவுகள் இல்லை.

எப்பொழுதும் இலவசம் — கடன் அட்டை தேவையில்லை