Integrations — zerp's external & integration surface

The whole integrations domain reduces to one idea:

This is where zerp touches the outside world. Two modules are true outbound/inbound integrations with third-party services — MCP (lets an external AI agent drive finance) and Zoom (video meetings for HR). A third pair are client-facing edgesVersion (mobile force-update check) and the Wingold member-code link (a vestigial field tying a zerp user to an external Wingold member). Everything else cross-cutting that an integration leans on — audit, logging, notifications, file upload — is documented as platform "spine" docs; this overview only points at them.

Source: BE src/modules/mcp (incl. mcp/tools/), src/modules/zoom, src/modules/version; plus the Wingold field in src/modules/user/user.schema.ts and src/constant.ts · Admin src/modules/hr/zoom (Zoom only — MCP/Version/Wingold have no admin UI)


1. The integration surface at a glance

Integration Direction What it is Doc / source
MCP Inbound (an external AI agent → zerp) A Model Context Protocol server over SSE exposing four finance tools (list_accounts, create/get/post_journal_entry) so an LLM can draft & post journal entries. mcp.md
Zoom Both (zerp → Zoom REST; Zoom → zerp webhooks) Schedule/join/end/delete Zoom meetings for HR training & interviews; embedded Web SDK join; webhook-driven status & recording sync. zoom.md
Version Outbound-facing (mobile client → zerp) A public GET /api/version returning iOS/Android/web app versions + force-update flags + store URLs, read from the runtime config. See §2 Version. Source version/version.controller.ts.
Wingold Linkage only A single wingoldMemberCode field on the User and a AP_WINGOLD_TIME_FORMAT constant — a vestigial link to an external Wingold membership; no client/sync code in this repo. See §3 Wingold.

MCP and Zoom are the only two with their own module folders, schemas, and full per-module docs in this domain. Version and Wingold are documented inline below — they are too small for a standalone rebuild-grade doc, and Wingold is essentially a placeholder.

Pointers to cross-cutting integration spine (platform docs)

These are integration-adjacent capabilities that many modules use; they are owned by the platform "spine", not by this domain:

  • Audit / trail — every mutation's before/after snapshot via @AuditMeta; the Zoom and MCP-posted entries both flow through it. → platform/audit-trail (BE audit-trail/).
  • Activity log — the AuditLog collection of field-level update records. → platform/audit-trail (BE log/).
  • Notifications — in-app Notification collection + GraphQL notificationCreated subscription (real-time push to clients). → platform/notifications (BE notification/).
  • Files / assets / uploadPOST /api/file-uploads controller + upload collection for binary assets referenced across modules. → platform/files-assets-upload (BE upload/).

2. Version (mobile force-update)

A tiny, unauthenticated REST edge for the mobile apps to check whether they must update.

Source: version/version.controller.ts, version/version.module.ts (imports ApConfigModule).

// GET /api/version  (no auth)
@Get("/")
async version() {
  const config = await this.configSvc.findLast();   // latest runtime config doc
  return {
    ios:     { version: config.iosVersion,     forceUpdate: config.iosForceUpdate,
               downloadUrl: "https://apps.apple.com/us/app/msgold/id6450125286" },
    android: { version: config.androidVersion, forceUpdate: config.androidForceUpdate,
               downloadUrl: "https://play.google.com/store/apps/details?id=com.msgold.app" },
    web:     { v: "1.2", forceUpdate: true },        // hardcoded
  };
}
  • No auth, no tenant scoping — it is a global, public endpoint the apps poll on launch.
  • iOS/Android versions + force-update flags come from the runtime config (ApConfigService.findLast()); the store URLs are hardcoded to the MSGold apps, and the web block is hardcoded (v: "1.2", forceUpdate: true).
  • The matching admin surface is the runtime config screen (where iosVersion / androidVersion / *ForceUpdate are edited) — see subscription-config.

Not a module — a dormant linkage. The only traces of "Wingold" in zerp-be:

Trace Location Meaning
wingoldMemberCode: number (default 0) user/user.schema.ts (UserEntity) A per-user code linking a zerp user to an external Wingold membership.
AP_WINGOLD_TIME_FORMAT = "YYYY-MM-DDThh:mm:ss" src/constant.ts A date format constant intended for Wingold-compatible timestamps.

There is no Wingold client, service, controller, sync job, or config in this repository — the field is populated/consumed elsewhere (an external Wingold system or a separate service), not by code in zerp-be. Document it as a known integration seam / TODO, not an implemented integration. If a Wingold sync is ever built, it belongs in this domain alongside mcp.md / zoom.md.


4. Cross-domain relationships

                         ┌──────────────────────────── outside world ───────────────────────────┐
   external AI agent ───▶│ MCP (SSE /api/mcp)  ─delegates▶ finance: account / journal / transaction│
   Zoom Cloud  ◀──REST──▶│ Zoom (/webhooks/zoom + GraphQL) ─sits under▶ HR menu (training/interview)│
   mobile apps ─poll────▶│ Version (GET /api/version)  ─reads▶ runtime config (subscription-config) │
   Wingold (external) ···│ wingoldMemberCode on User   ······· (no sync code here)                  │
                         └──────────────────────────────────────────────────────────────────────┘
            cross-cutting spine used by the above:  audit-trail · log · notifications · upload (platform/*)

Related domains & platform docs: finance/account · finance/journal · finance/transaction (MCP writes through these) · hr/training · recruitment/recruitment (Zoom meetings are scheduled for these, by convention) · subscription-config (Version reads runtime config) · platform/auth · platform/permissions-access · platform/audit-trail · platform/notifications · platform/files-assets-upload · platform/multi-tenancy.


5. Shared mechanics & gotchas (domain-wide)

  • Two different auth styles. MCP and Zoom-GraphQL use the normal zerp auth chain (@ApiAuthorize() / @ApGqlAuthorize()), but the inbound webhooks/edges bypass it: Zoom's /webhooks/zoom/events authenticates by HMAC signature + replay window, and /api/version is fully public. Don't assume an integration endpoint is session-guarded.
  • Context source differs. Zoom derives tenant/host from ApContextService (session-bound); MCP takes companyId/branchId/createdBy as tool arguments (not session-derived) — its real boundary is the transport-level auth, and create_journal_entry has no company check at all. See mcp.md §9.
  • No outbound-call transactionality. Zoom (and any future sync) makes an external HTTP call plus a Mongo write that are not atomic — partial failures can drift zerp from the external system.
  • External config lives in env, not the DB. Zoom needs ZOOM_* env vars across two Zoom apps; Version reads app versions from the runtime config doc. Misconfiguration is logged at startup (ZoomConfigService.warnMissingConfig) rather than failing hard.
  • Historical naming. The MCP server announces itself as "zyncount" and the Version store URLs / app ids point at MSGold — both are legacy names from the product's lineage, not bugs.