@cycomdatasystems/mackinac-sdk

中文文档
13.16.0发布于 2026/11/07UNLICENSEDTypeScriptCommonJS近 30 天下载 3201

A shared TypeScript client for the Mackinac API.

原始使用文档
文档目录15 节
  1. Installation
  2. Usage
  3. Configure the SDK once at application startup
  4. Authentication
  5. Example
  6. WRP Matter auto-assignee (since 13.9.0)
  7. Attachment folders (since 13.15.0)
  8. Billing (since 13.5.0)
  9. Contract Management (since 13.6.0)
  10. Code Generation
  11. Regenerate the SDK
  12. Bundled OpenAPI spec
  13. Development
  14. Versioning
  15. License

Mackinac SDK

A shared TypeScript client for the Mackinac API.
Generated from the Mackinac OpenAPI specification, this SDK provides strongly typed models and a unified fetch-based client for web and mobile applications. It ensures consistent API usage, reduces duplication, and keeps all consumers aligned with the latest API contract.


Installation

npm install @cycomdatasystems/mackinac-sdk
# or
yarn add @cycomdatasystems/mackinac-sdk
# or
pnpm add @cycomdatasystems/mackinac-sdk

Usage

Configure the SDK once at application startup

import { configureMackinac } from '@cycomdatasystems/mackinac-sdk';

configureMackinac({
  baseUrl: process.env.MACKINAC_API_URL!,
});

For applications that need to attach authorization headers or other request-specific headers to SDK calls, you can provide a headersProvider function:

import { configureMackinac } from '@cycomdatasystems/mackinac-sdk';

configureMackinac({
  baseUrl: process.env.MACKINAC_API_URL!,
  headersProvider: () => ({
    'Authorization': `Bearer ${getToken()}`,
  }),
});

The headersProvider can also be async to support token retrieval from async sources:

configureMackinac({
  baseUrl: process.env.MACKINAC_API_URL!,
  headersProvider: async () => {
    const token = await getAccessToken();
    return {
      'Authorization': `Bearer ${token}`,
    };
  },
});

Authentication

The SDK provides a headersProvider function for request-scoped authentication. This is the recommended approach for applications that need to attach Authorization headers or other dynamic headers to each SDK call.

Benefits:

  • Centralized header management for all SDK calls
  • Support for async token retrieval
  • Automatically merges with per-call headers
  • Per-request invocation prevents token leakage across requests

Example: Next.js Route Handler with Auth0

// app/api/finance/payments/route.ts
import { getSession } from '@auth0/nextjs-auth0';
import { configureMackinac } from '@cycomdatasystems/mackinac-sdk';
import { getApiV1MattersMatterIdFinancePayments } from '@cycomdatasystems/mackinac-sdk';

export async function GET(request: Request) {
  const session = await getSession();
  
  // Configure SDK with token provider for this request
  configureMackinac({
    baseUrl: process.env.MACKINAC_API_URL!,
    headersProvider: () => ({
      'Authorization': `Bearer ${session.accessToken}`,
    }),
  });
  
  // All SDK calls now include Authorization header
  const payments = await getApiV1MattersMatterIdFinancePayments(123);
  
  return Response.json(payments);
}

Or, if you need async token retrieval:

// src/server/mackinac/config.ts
import { configureMackinac } from '@cycomdatasystems/mackinac-sdk';

export function configureMackinacForRequest(getToken: () => Promise<string>) {
  configureMackinac({
    baseUrl: process.env.MACKINAC_API_URL!,
    headersProvider: async () => {
      const token = await getToken();
      return {
        'Authorization': `Bearer ${token}`,
      };
    },
  });
}
// app/api/finance/payments/route.ts
import { configureMackinacForRequest } from '@/server/mackinac/config';
import { getApiV1MattersMatterIdFinancePayments } from '@cycomdatasystems/mackinac-sdk';

export async function GET(request: Request) {
  // Configure SDK with async token provider for this request
  configureMackinacForRequest(async () => {
    // Simulate async token retrieval
    return await fetchTokenFromAuthService();
  });
  
  // All SDK calls now include Authorization header
  const payments = await getApiV1MattersMatterIdFinancePayments(123);
  
  return Response.json(payments);
}

Example: Synchronous Headers Provider

import { configureMackinac } from '@cycomdatasystems/mackinac-sdk';

configureMackinac({
  baseUrl: process.env.MACKINAC_API_URL!,
  headersProvider: () => ({
    'Authorization': `Bearer ${process.env.API_TOKEN}`,
  }),
});

Header Merge Priority

Headers are merged in the following order (later overrides earlier):

  1. Default headers (Content-Type: application/json)
  2. Headers from headersProvider()
  3. Per-call headers passed to individual endpoint functions
// Example of per-call override
const payments = await getApiV1MattersMatterIdFinancePayments(
  123,
  {
    headers: {
      'X-Request-Id': 'custom-id', // Merges with provider headers
      'Authorization': 'Bearer override-token', // Overrides provider's Authorization
    }
  }
);

Middleware-Based Authentication (Legacy)

This SDK does not handle authentication internally.

If your application uses Auth0 (or another IdP), and access tokens are added to outbound requests by middleware or framework-level fetch interception, you should not supply tokens to this SDK directly.

The SDK simply calls fetch(). Any middleware or runtime environment that injects the Authorization header will continue to work as expected.


Example

import { getApiV1MattersMyOpen } from '@cycomdatasystems/mackinac-sdk';

// Fetch "My Open Matters" for the current user
const matters = await getApiV1MattersMyOpen({ moduleCode: 'Claims' });

console.log('Open matters:', matters);

All generated functions return response bodies directly (not wrapped objects), following the configured Orval output.

WRP Matter auto-assignee (since 13.9.0)

NG User Admins can configure an optional auto-assignee for Matters created by WRP in the current tenant. These endpoints require users:update and an admitted, enabled NG caller whose UserAccount supplies the linked StaffId. Configure the SDK and authentication headers before calling them; use the application's current tenant authentication context. There is no tenant ID in the URL or request body, and the SDK does not select a tenant independently of the authenticated request. Mackinac resolves NG's authenticated org_id to the tenant and checks the namespaced Entra tid against that tenant's GraphTenantID. Do not send X-Org-Id or another custom tenant header. WRP's existing M2M flow resolves its tenant from azp; these SDK calls do not change that flow. NG caller linkage uses the admitted UserAccount and signed Entra oid, not a token StaffId or Auth0 subject. Eligible users and linked Staff must share the resolved integer TenantID; legacy STAFF.ORG_ID is not the tenant key.

import {
  getWrpMatterAutoAssignee,
  setWrpMatterAutoAssignee,
  getEligibleWrpMatterAutoAssignees,
} from '@cycomdatasystems/mackinac-sdk';

const current = await getWrpMatterAutoAssignee();
const choices = await getEligibleWrpMatterAutoAssignees();
// After the administrator selects a choice, save its userId, not its staffId:
const saved = await setWrpMatterAutoAssignee({ userId: 123 });
const disabled = await setWrpMatterAutoAssignee({ userId: null });
  • getWrpMatterAutoAssignee(options?: RequestInit) and setWrpMatterAutoAssignee(body: SetWrpMatterAutoAssigneeDto, options?: RequestInit) return Promise<WrpMatterAutoAssigneeDto>: { userId: number | null, staffId: string | null }. Both fields are null when disabled. The PUT body is { userId: number | null }; send null explicitly to disable, not an omitted field.
  • getEligibleWrpMatterAutoAssignees(options?: RequestInit) returns Promise<WrpMatterAutoAssigneeUserDto[]>: a bare array of { userId: number, staffId: string, displayName: string }, or [] when no users qualify, ordered by userId and limited to the caller's organization. It requires neither users:read nor Auth0 access. displayName comes from trimmed Staff.Name, falling back to StaffId; there is no cursor or wrapper.
  • Mackinac validates that the target is an Active, unblocked NG user with a bound EntraOid and linked active Staff in the current tenant. The StaffId must be nonblank and at most seven characters; no AS/module flag is required. Invalid selections reject with Mackinac 400: followed by ValidationProblemDetails with errors.userId. Authentication, authorization, missing organization context, and storage failures reject with Mackinac 401:, Mackinac 403:, Mackinac 503:, and Mackinac 500: respectively, using the SDK's existing Error behavior rather than typed error return values. JWT challenges and existing policy denials may have no response body; tenant middleware can also reject a request before these endpoints execute.
  • The selected user's StaffId becomes Primary Staff only for WRP assignment-matter and create-as-matter actions. No WRP submission payload change is needed. The setting is tenant-scoped, not shared across tenants and not per user or per form.

Attachment folders (since 13.15.0)

A matter's attachment folders live in SQL (MATTERFOLDER), not as SharePoint Embedded paths. listMatterAttachments is the single read for the Attachments tab: it returns the selected attachments plus the matter's whole flat folder list, so the Table and Tree views build from one response. Only SPE-backed rows (with a drive item) are listed or counted.

import {
  listMatterAttachments, createAttachmentFolder, patchAttachmentFolder,
  deleteAttachmentFolder, moveMatterAttachments, patchMatterAttachment,
} from '@cycomdatasystems/mackinac-sdk';

// view: 'all' (default) | 'unfiled' | 'recent'; folderId cannot be combined with a view other than 'all'.
const { items, folders, unfiledCount, totalCount } = await listMatterAttachments(matterId, { folderId: 42, q: 'brief' });

const folder = await createAttachmentFolder(matterId, { name: 'Pleadings', parentId: null });
await patchAttachmentFolder(folder.id, [{ op: 'replace', path: '/name', value: 'Pleadings 2026' }]);
await moveMatterAttachments(matterId, { attachmentIds: [101, 102], folderIds: [], targetFolderId: folder.id });
await patchMatterAttachment(matterId, 101, [{ op: 'replace', path: '/category', value: 'PLD' }], { moveToCategoryFolder: true });
const { deletedFolderCount, movedAttachmentCount } = await deleteAttachmentFolder(folder.id); // contents move to Unfiled
  • listAttachmentFolders(matterId) and getAttachmentFolder(folderId) return AttachmentFolderDtos. The list is flat: build the tree from parentId (null = a root folder). totalAttachmentCount includes subfolders; categoryCode is set on category folders.
  • patchAttachmentFolder and patchMatterAttachment take an RFC 6902 JSON Patch array, which the SDK sends as application/json-patch+json, and resolve to undefined (204). On a folder, replace /name renames and replace /parentId moves (null or remove = the matter root). On an attachment, replace or remove applies to /category (a DOCCAT code) and /description.
  • moveMatterAttachments is all or nothing (at most 1000 ids). targetFolderId: null moves attachments to Unfiled and folders to the matter root.
  • Folder names are trimmed and unique among siblings, case-insensitively; a clash rejects with Mackinac 409: (attachment-folder-name-conflict). Nesting deeper than 6 levels, or moving a folder into itself or a descendant, rejects with Mackinac 400:.
  • Document register (postApiV1MattersMatterIdDocumentsItemsItemIdRegister) accepts folderId, or fileInCategoryFolder: true with a category (that category's root folder, created if missing). Upload to the container root; DocumentRegistrationDto.folderId reports where the document was filed.

Billing (since 13.5.0)

The Billing reference-data endpoints are named *Billing*: listBillingPeriods, getBillingPeriod, createBillingPeriod, patchBillingPeriod, deleteBillingPeriod, the same five operations for ...BillingRevenueCode(s), and getBillingProfile / patchBillingProfile. The PATCH operations take an RFC 6902 JSON Patch array, which the SDK sends as application/json-patch+json:

import { listBillingPeriods, patchBillingProfile } from '@cycomdatasystems/mackinac-sdk';

const periods = await listBillingPeriods({ page: 1, pageSize: 50, sortBy: 'beginDate', sortDirection: 'desc' });

const profile = await patchBillingProfile([
  { op: 'replace', path: '/billTo', value: 'Department' }, // BillingBillTo: 'Account' | 'Department'
  { op: 'replace', path: '/billGeneralExpenses', value: true },
]);

deleteBillingPeriod and deleteBillingRevenueCode reject with an Error whose message starts Mackinac 409: (the ProblemDetails body follows) when invoices or receipts still reference the item. Revenue codes are code-table rows (code, description, isDefault, isInactive), not rate classes.

Invoices, receipts and receivables (since 13.7.0)

The read model is listBillingInvoices, getBillingInvoice, listBillingInvoiceTransactions, listBillingReceipts and listBillingReceivables. Each search takes one params object, and array params are sent as repeated keys:

import {
  listBillingInvoices, getBillingInvoice, listBillingInvoiceTransactions,
  listBillingReceipts, listBillingReceivables,
} from '@cycomdatasystems/mackinac-sdk';

const page = await listBillingInvoices({
  statuses: ['Pending', 'Billed'],                 // BillingInvoiceStatus; any of them
  accountIds: [101], departmentIds: [202],          // unioned
  billedDateFrom: '2026-01-01', billedDateTo: '2026-01-31', // inclusive whole days
  page: 1, pageSize: 50, sortBy: 'invoiceDate', sortDirection: 'desc',
});
const invoice = await getBillingInvoice(page.hits[0].id);
const lines = await listBillingInvoiceTransactions(invoice.id, { pageSize: 100 });
const receipts = await listBillingReceipts({ revenueCodes: ['FEE'], checkNumber: '10' });
const outstanding = await listBillingReceivables({ sortBy: 'balance', sortDirection: 'desc' });
  • status is Pending, Billed, Paid, Cancelled, or Unknown. Unknown appears only in responses: it covers legacy or NULL values, and passing it as a filter is Mackinac 400:.
  • transactionType is a BillingTransactionType.
  • invoiceNumber, fileNumber and checkNumber are literal prefix matches.
  • A date before 1753-01-01, or a ...To that is earlier than its ...From, is Mackinac 400:.
  • getBillingInvoice and listBillingInvoiceTransactions reject with Mackinac 404: for an invoice you may not see. They never return an empty page.
  • InvoiceDto carries the components of the total: amount = attorneyAmount + staffAmount + expenseAmount + creditCardExpenseAmount + settlementAmount + adjustmentAmount - collectionAmount.
  • Receivables are Billed, unpaid invoices with a positive amount. Each one has a balance.

Billing runs (since 13.16.0)

A billing run is a background job. createBillingRun queues the run and resolves with a BillingRunDto whose status is Queued (HTTP 202). Poll getBillingRun until the status is Succeeded, Failed or Cancelled, then list the invoices it produced with listBillingRunInvoices. previewBillingRun is synchronous and changes nothing.

import {
  previewBillingRun, createBillingRun, getBillingRun, listBillingRunInvoices,
  cancelBillingRun, listBillingRuns,
} from '@cycomdatasystems/mackinac-sdk';
import type { BillingRunCriteriaDto, BillingRunDto } from '@cycomdatasystems/mackinac-sdk';

const criteria: BillingRunCriteriaDto = { periodId: 42 }; // fromDate / toDate default to the period's dates

const preview = await previewBillingRun(criteria);
// preview.billTos: the accounts (or departments, per preview.billTo) that would get an invoice
// preview.pendingInvoiceCount: Pending invoices the run would delete and regenerate

let run: BillingRunDto = await createBillingRun(criteria);
while (run.status === 'Queued' || run.status === 'Running') { // BillingRunStatus
  await new Promise((resolve) => setTimeout(resolve, 3000));
  run = await getBillingRun(run.id); // progress: run.billTosProcessed of run.billToCount
}

if (run.status === 'Failed') {
  console.error(run.errorDetail);
}
const invoices = await listBillingRunInvoices(run.id, { pageSize: 100 }); // InvoiceSummaryDto rows
  • Filters choose what gets billed; they don't limit the work billed. departmentIds, accountIds and fileNumber select which accounts (or departments) are billed. Each selected one is then billed for all of its unbilled work in the date range.
  • The run ANDs departmentIds and accountIds. The invoice and receipt searches union them. Label both differences in the UI.
  • One run per tenant at a time. createBillingRun rejects with Mackinac 409: while another run is Queued or Running. The ProblemDetails body that follows carries the active run's id in activeRunId. To find the active run, call listBillingRuns({ statuses: ['Queued', 'Running'] }).
  • Cancelling. cancelBillingRun resolves with the run (HTTP 202).
    • A Queued run is Cancelled at once.
    • A Running run comes back with cancelRequested: true and stops before its next account or department. Invoices it already produced stay Pending and stay listed under the run.
    • Once the run has ended, the call is Mackinac 409:.
  • Reruns are safe. Re-running a period replaces only its Pending invoices. Billed, Paid and Cancelled invoices are never touched.

Invoice lifecycle and receipts (since 13.16.0)

Manual invoices, invoice lines, status changes ("Finalize" is Billed) and receipts. Every mutation needs the billing:write permission and a caller linked to a staff record (Mackinac 403: otherwise).

import {
  createBillingInvoice, createBillingInvoiceTransaction, patchBillingInvoiceTransaction,
  deleteBillingInvoiceTransaction, deleteBillingInvoice, updateBillingInvoiceStatus,
  updateBillingInvoiceStatuses, createBillingReceipt, getBillingReceipt, patchBillingReceipt,
  deleteBillingReceipt,
} from '@cycomdatasystems/mackinac-sdk';

// A manual invoice: Pending, numbered by the server, with one adjustment line for `amount`.
const invoice = await createBillingInvoice({
  invoiceDate: '2026-11-05', periodId: 42, billToId: 7, amount: 150, description: 'Manual fee',
});

// Lines can change only while the invoice is Pending; totals are recomputed by the server.
const line = await createBillingInvoiceTransaction(invoice.id, { transactionDate: '2026-11-05', amount: 25 });
await patchBillingInvoiceTransaction(invoice.id, line.id, [{ op: 'replace', path: '/amount', value: 30 }]);
await deleteBillingInvoiceTransaction(invoice.id, line.id);

// Finalize: marks the invoice's time, time adjustments and payments billed.
const billed = await updateBillingInvoiceStatus(invoice.id, { status: 'Billed' });

// Record a payment. paidAmount becomes the sum of the receipts; the invoice is Paid once they cover it.
const receipt = await createBillingReceipt({ invoiceId: billed.id, receiptDate: '2026-11-20', amount: 180 });
await patchBillingReceipt(receipt.id, [{ op: 'replace', path: '/checkNumber', value: '1001' }]);
await getBillingReceipt(receipt.id);
await deleteBillingReceipt(receipt.id); // the invoice goes back to Billed

// "Change Status (All)": a partial-success summary, never an all-or-nothing error.
const result = await updateBillingInvoiceStatuses({ invoiceIds: [101, 102, 103], status: 'Cancelled' });
for (const failure of result.failures) {
  console.warn(failure.invoiceId, failure.reason, failure.detail); // reason: 'NotFound' | 'Conflict'
}
await deleteBillingInvoice(invoice.id); // only a Pending invoice without receipts
  • Payment state is derived, never set. paidAmount is the sum of the invoice's receipts. A Billed or Paid invoice is Paid (isPaid: true) exactly when its receipts cover amount; an overpayment counts as paid. Editing a receipt down or deleting one moves the invoice back to Billed.
  • Status rules (Mackinac 409: when broken, nothing changes):
    • an invoice with receipts can't move to Pending or Cancelled;
    • Paid needs a Billed or Paid invoice that its receipts cover (record payments with createBillingReceipt);
    • Pending un-bills the invoice's time, adjustments and payments, the exact inverse of Billed. Unknown is part of BillingInvoiceStatus for responses only; sending it is Mackinac 400:.
  • Pending only. Only a Pending invoice can be deleted (and only without receipts) or have lines added, edited or deleted. Manual lines are always the GeneralExpense type (code 6).
  • Receipts go only on a Billed or Paid invoice. The payer is the invoice's bill-to; the invoice and payer can't be patched. amount must not be zero, and revenueCode must be a revenue code (listBillingRevenueCodes). receiptType is a raw number with no known value set.
  • ⚠️ While a billing run is queued or running, invoice create/delete, line changes and both status calls reject with Mackinac 409: and the ProblemDetails carries activeBillingRunId. Receipts are not affected. Show the active run (getBillingRun) and retry after it ends.
  • Bulk status. invoiceIds holds 1 to 500 distinct ids. Invisible ids come back as NotFound and ineligible ones as Conflict; neither is touched, and the rest are updated together. A Mackinac 409: means the batch was refused as a whole (an active run, or an invoice that became ineligible mid-request); nothing was updated, so retry.
  • Patch bodies use the generic Operation[], like patchBillingPeriod. Line paths: /transactionDate, /amount, /hours. Receipt paths: /receiptDate, /amount, /checkNumber, /revenueCode, /receiptType, /notes. Any other path is Mackinac 400:.
  • Another tenant's invoice, line or receipt is Mackinac 404:, never 403.

Contract Management (since 13.6.0)

The Contract Management (module CM) reads are searchContracts / getContractSearchHits (search and paging, the same MatterSearchHitDtoPaginatedResultDto as searchMatters), getContract and listContractOrders:

import { searchContracts, getContractSearchHits, getContract, listContractOrders } from '@cycomdatasystems/mackinac-sdk';

const first = await searchContracts({ searchCriteria: { contType: 'CO', title: 'Paving*' }, pageSize: 25 });
const next = first.hasMoreHits ? await getContractSearchHits(2, { pageSize: 25 }) : undefined;

const contract = await getContract(1234);
const step = contract.currentApprovalStep?.description; // currentApprovalStep and master may be null
const { orders, contractsTotal, purchaseOrdersTotal } = await listContractOrders(1234);
  • The search requires Contract Management access (V16) and rejects with Mackinac 403: without it. title and number are prefix matches (* is a wildcard), limited to ASCII and Latin-1 characters; anything else is Mackinac 400:.
  • getContract / listContractOrders reject with Mackinac 404: for a matter you may not see, that is not module CM, or has no contract record. They reject with Mackinac 409: when the matter has more than one contract record; the body is a ContractProblemDetails with errorCode: 'DuplicateContractRecord'.
  • In orders, change orders (contType: 'CH') carry null for department, account, expiration, authAmount, totalPaid and openBalance. contractsTotal / purchaseOrdersTotal cover every direct child, including ones you may not see.

Certificates and approval steps (since 13.16.0)

A contract's insurance certificates and its approval steps, plus the tenant's approval profiles (read-only):

import {
  listContractCertificates, createContractCertificate, patchContractCertificate, deleteContractCertificate,
  listContractApprovalSteps, createContractApprovalStep, patchContractApprovalStep, deleteContractApprovalStep,
  importContractApprovalProfile, listApprovalProfiles,
} from '@cycomdatasystems/mackinac-sdk';

const certificates = await listContractCertificates(1234); // latest expiration first, none last
const cert = await createContractCertificate(1234, { type: 'GL', number: 'N-1', expirationDate: '2027-01-01' });
await patchContractCertificate(1234, cert.id, [{ op: 'replace', path: '/remarks', value: 'Renewed' }]);

const { items, currentStepId } = await listContractApprovalSteps(1234);
await patchContractApprovalStep(1234, currentStepId!, [{ op: 'replace', path: '/completedDate', value: '2026-11-02' }]);

const profiles = await listApprovalProfiles();
const seeded = await importContractApprovalProfile(1234, { profileId: profiles[0].id }); // only into a contract with no steps
  • PATCH bodies are typed per path (PatchContractCertificateOperation, PatchContractApprovalStepOperation): only the listed paths and add / replace / remove are accepted. /stageOrder may not end up null. A code, department or staff id is checked only when its value changes, so legacy rows stay editable.
  • Completing a step marks the next step (by stageOrder) received on that date, and the contract's approvalStep follows the current step. Invalidate getContract after a step write.
  • Rejections: Mackinac 400: for an invalid body (a ValidationProblemDetails keyed by field); Mackinac 404: for the contract (as getContract), a certificate or step on another contract, or an unknown department, staff id or profile; Mackinac 409: with errorCode DuplicateContractRecord, or ApprovalStepsExist when importing into a contract that already has steps.

Create, edit and delete contracts, POs and change orders (since 13.16.0)

previewContractOrder, createContract, patchContract and deleteContract write contracts (CO), purchase orders (PO) and change orders (CH):

import { previewContractOrder, createContract, patchContract, deleteContract } from '@cycomdatasystems/mackinac-sdk';

// Would this PO under contract 1234 need an automatic change order? Writes and allocates nothing.
const preview = await previewContractOrder({ masterMatterId: 1234, contType: 'PO', authAmount: 700 });

const { contract, autoChangeOrder } = await createContract({
  contType: 'PO', masterMatterId: 1234, title: 'Paving phase 2', authAmount: 700,
  confirmAutoChangeOrder: preview.wouldCreateChangeOrder, // only after the user has confirmed
});

await patchContract(contract.matterId, [
  { op: 'replace', path: '/authAmount', value: 900 },
  { op: 'replace', path: '/description', value: 'Revised scope' },
], { confirmAutoChangeOrder: false });

await deleteContract(contract.matterId, { deleteAttachments: false });
  • createContract: contType is CO, PO or CH. A CO has no master, a PO's master is optional (a CO or PO), and a CH needs one (a CO or PO). A null fileNumber is allocated from the CTYLAW mask. A child order with no number takes the file number when CM/AUTOFILLCHANGE is on. The server sets netAuthAmount = authAmount, totalPaid = 0 and approvalStep = null.
  • Automatic change order (R6). When a PO under a CO would push the CO's open balance below zero, the create or the authAmount patch rejects with Mackinac 409: and errorCode: 'ChangeOrderConfirmationRequired'; the body carries changeOrderAmount and masterOpenBalanceAfter. After the user confirms, resend with confirmAutoChangeOrder: true (in the body for createContract, in the query for patchContract). The created change order is returned as autoChangeOrder (null when none was created). Invalidate getContract / listContractOrders for the master too.
  • patchContract takes typed operations (PatchContractOperation): only the listed paths are accepted, and /contType, /masterMatterId, /netAuthAmount, /totalPaid and /approvalStep are rejected with Mackinac 400:. Changing /authAmount also sets netAuthAmount. Changing /assocMatterId also sets it on the direct child orders and moves the order's payments. An empty /description deletes the description.
  • deleteContract requires deleteAttachments. It rejects with Mackinac 409: and errorCode: 'ContractHasDependents' while child orders, payments, approval steps or certificates exist; the body carries childOrders, payments, approvalSteps and certificates. deleteMatter on a Contract Management matter behaves the same way.
  • Other rejections: Mackinac 400: for an invalid body (a ValidationProblemDetails keyed by field), including dates before 1753 and amounts with more than 4 decimal places; Mackinac 403: from previewContractOrder / createContract without Contract Management access; Mackinac 404: for a contract you may not see, or a masterMatterId, assocMatterId or vendorId that is not in your tenant; Mackinac 409: with errorCode DuplicateFileNumber or DuplicateContractRecord; Mackinac 503: when another request holds the numbering lock (retry).

Contract payments and spending limits (since 13.16.0)

A contract's or purchase order's Payments tab, the spending-limit preview, and "Enter payment":

import {
  listContractPayments, checkContractPaymentLimit, createContractPayment,
  patchContractPayment, deleteContractPayment,
} from '@cycomdatasystems/mackinac-sdk';

const { items, paidTotal } = await listContractPayments(1234); // newest first; no paging

// Before saving: would this amount warn or be blocked? Writes nothing.
const preview = await checkContractPaymentLimit(1234, { amount: 250 });
if (preview.warningLevel > 0) { /* show preview.warningMessage */ }

const { payment, limit } = await createContractPayment(1234, {
  date: '2026-11-04', objectCode: 'PROF', amount: 250, // vendorId defaults to the contract's vendor
});

await patchContractPayment(1234, payment.id, [{ op: 'replace', path: '/amount', value: 300 }]);
await deleteContractPayment(1234, payment.id);
  • Which payments. A PO lists payments with poId = the PO; any other type lists contractId = the matter, so a contract also lists PO payments that carry its id. Every row counts, whatever paid says. paidTotal sums the rows marked paid.
  • Server-set fields. On create the server sets contractId / poId (a PO payment also carries the PO's master contract), matterId (the contract's associated matter, else the request's matterId), the vendor's payee and address (default: the contract's vendor), payType (from the object code) and origin (1). The response never includes the payee's tax id.
  • Spending limits. Warnings (warningLevel 1/2 with warningMessage) never block. A payment that would leave its order's open balance below zero is refused with Mackinac 422: and errorCode: 'ContractLimitExceeded'; the body (ContractPaymentProblemDetails) carries openBalanceBefore, openBalanceAfter, amount and targetMatterId. Limits apply to the increase in amount, against the payment's own order (its PO when it has one). checkContractPaymentLimit returns 200 even when blocked is true; pass paymentId to preview a new amount for an existing payment.
  • TOTAL_PAID (ContractDto.totalPaid, ContractOrderSummaryDto.totalPaid) is now maintained by the server on every payment write, including the matter finance endpoints. Invalidate getContract / listContractOrders (and the master's, for a PO) after a payment write.
  • patchContractPayment takes typed operations (PatchContractPaymentOperation): only the listed paths are accepted, and links, payee and address are not patchable. Sending /vendorId re-copies that vendor; leaving it out keeps the stored vendor. An empty /notes deletes the notes.
  • Rejections: Mackinac 400: for an invalid body (a ContractValidationProblemDetails keyed by field), including an inactive or reserve object code, an amount of 0, with more than 2 decimal places or outside the money range, dates before 1753, and errorCode: 'PayeeRequired' when neither the request nor the contract names a vendor; Mackinac 404: for a contract you may not see, a payment of another order, or a vendorId, requestor or matterId not in your tenant; Mackinac 409: with errorCode: 'DuplicateContractRecord'; Mackinac 422: with errorCode: 'ContractTypeNotPayable' when creating a payment on a change order (CH).

Code Generation

This SDK is generated from the Mackinac OpenAPI spec using Orval.

Regenerate the SDK

npm run orval

Ensure the latest swagger.json from the Mackinac API is located in the /contracts directory or at the configured URL.

Bundled OpenAPI spec

The exact OpenAPI document used to generate this SDK ships with the package as dist/openapi.json, exposed via the ./openapi.json subpath:

import spec from '@cycomdatasystems/mackinac-sdk/openapi.json'; // typed as OpenAPIV3.Document

console.log(spec.info.title, spec.info.version);

For non-import tooling (codegen, docs, Postman, etc.), resolve the file path:

const path = require.resolve('@cycomdatasystems/mackinac-sdk/openapi.json');

Note: the package uses exports, so only the root barrel (@cycomdatasystems/mackinac-sdk) and ./openapi.json are importable. Deep imports into dist/ are not supported.


Development

  • Requires Node.js 18+
  • Install dependencies:
npm install
  • Run codegen + build:
npm run orval && npm run build
  • Run the WRP auto-assignee runtime and public type-contract checks:
npm test

Versioning

This package follows semantic versioning:

  • PATCH: Regenerated SDK with no API contract changes
  • MINOR: New endpoints or non-breaking additions
  • MAJOR: Breaking API contract changes

License

This package is intended for internal use within Cycom.
If distributed externally, add an appropriate open-source license (e.g., MIT).

版本与发布记录

@cycomdatasystems/mackinac-sdk 共发布 38 个版本,首次发布于 2026/04/22;过去 12 个月发布了 38 个版本。

版本发布日期距今
13.16.0latest2026/11/074 天前
13.15.02026/11/064 天前
13.9.02026/11/019 天前
13.8.02026/11/0110 天前
13.6.02026/10/2913 天前
13.5.02026/10/2813 天前

关于 @cycomdatasystems/mackinac-sdk 的常见问题

@cycomdatasystems/mackinac-sdk 是什么?

A shared TypeScript client for the Mackinac API.

如何安装 @cycomdatasystems/mackinac-sdk?

在项目目录运行 npm install @cycomdatasystems/mackinac-sdk,或使用 pnpm add / yarn add / bun add。

@cycomdatasystems/mackinac-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cycomdatasystems/mackinac-sdk 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@cycomdatasystems/mackinac-sdk 可以商用吗?

@cycomdatasystems/mackinac-sdk 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@cycomdatasystems/mackinac-sdk 的下载量有多少?

根据 npm 官方下载统计,@cycomdatasystems/mackinac-sdk 在 2026/10/11 – 2026/11/09 这 30 天内被下载 3,201 次,日均约 107 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cycomdatasystems/mackinac-sdk 还在维护吗?

最新版本 13.16.0 发布于 2026/11/07(4 天前);至今共发布 38 个版本,其中过去 12 个月发布了 38 个;首次发布于 2026/04/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。