@happyvertical/smrt-expenses 0.60.13 → 0.60.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +15 -2
- package/README.md +8 -0
- package/agents/allocation-test-matrix.md +82 -0
- package/agents/reviewed-allocations.md +138 -0
- package/dist/allocation-internal.d.ts +6 -0
- package/dist/allocation-internal.d.ts.map +1 -0
- package/dist/chunks/{Expense-CimA4lxE.js → Expense-Deq19w2r.js} +28 -3
- package/dist/chunks/Expense-Deq19w2r.js.map +1 -0
- package/dist/chunks/{ExpenseReceiptCollection-CSBrNDzv.js → ExpenseReceiptCollection-D4Na93BF.js} +37 -3
- package/dist/chunks/ExpenseReceiptCollection-D4Na93BF.js.map +1 -0
- package/dist/chunks/{expense-actions-DiIhw0xO.js → expense-actions-DszuHacx.js} +2 -2
- package/dist/chunks/{expense-actions-DiIhw0xO.js.map → expense-actions-DszuHacx.js.map} +1 -1
- package/dist/collections/ExpenseCollection.d.ts +5 -5
- package/dist/collections/ExpenseCollection.d.ts.map +1 -1
- package/dist/expense-allocation.d.ts +133 -0
- package/dist/expense-allocation.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1001 -21
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2359 -627
- package/dist/models/Expense.d.ts +2 -6
- package/dist/models/Expense.d.ts.map +1 -1
- package/dist/models/ExpenseAllocation.d.ts +117 -0
- package/dist/models/ExpenseAllocation.d.ts.map +1 -0
- package/dist/models/ExpenseReceipt.d.ts +7 -0
- package/dist/models/ExpenseReceipt.d.ts.map +1 -1
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/recipes.d.ts +3 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +746 -108
- package/dist/smrt-knowledge.okf.md +17 -3
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -9
- package/dist/chunks/Expense-CimA4lxE.js.map +0 -1
- package/dist/chunks/ExpenseReceiptCollection-CSBrNDzv.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,978 @@
|
|
|
1
|
-
import { n as ExpenseReceipt, r as __exportAll, t as ExpenseReceiptCollection } from "./chunks/ExpenseReceiptCollection-
|
|
2
|
-
import { t as ExpenseActionError } from "./chunks/principal-_1CdcvvW.js";
|
|
1
|
+
import { n as ExpenseReceipt, r as __exportAll, t as ExpenseReceiptCollection } from "./chunks/ExpenseReceiptCollection-D4Na93BF.js";
|
|
2
|
+
import { i as withActionPrincipal, n as authorizeExpenseAction, t as ExpenseActionError } from "./chunks/principal-_1CdcvvW.js";
|
|
3
|
+
import { d as allocationHeads, f as allocationWrites, i as computeContentSha256, p as insertAllocation, r as assertQualifiedClassName, s as instantMs, t as Expense } from "./chunks/Expense-Deq19w2r.js";
|
|
3
4
|
import { DuplicateReceiptError, EXPENSE_PAYERS, EXPENSE_REVIEW_STATUSES, ExpenseError, SUGGESTED_EXPENSE_CATEGORIES } from "./types.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, SmrtRecipe, crossPackageRef, field, foreignKey, isPostgresDatabase, method, smrt } from "@happyvertical/smrt-core";
|
|
6
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
7
|
+
import { registerPermissionDefinitions } from "@happyvertical/smrt-users";
|
|
8
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
9
|
+
import { ContractLineItemCollection, InvoiceCollection, InvoiceLineItemCollection, PurchaseContractCollection, bindInvoiceLineSource, registerInvoiceSourceBinding, unbindInvoiceLineSource } from "@happyvertical/smrt-commerce";
|
|
7
10
|
//#region src/__smrt-register__.ts
|
|
8
|
-
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-expenses","packageVersion":"0.60.13","objects":{"@happyvertical/smrt-expenses:ExpenseCollection":{"name":"expensecollection","className":"ExpenseCollection","qualifiedName":"@happyvertical/smrt-expenses:ExpenseCollection","collection":"expenses","description":"Collection for Expense. Reads go through the normal tenant interceptors, so inside withTenant() every method here sees only that tenant's expenses.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/collections/ExpenseCollection.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{"recordSupplierPayable":{"name":"recordSupplierPayable","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"RecordSupplierPayableInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"recordSupplierPayableEvent":{"name":"recordSupplierPayableEvent","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"RecordSupplierPayableEventInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"completeSupplierPayableBasis":{"name":"completeSupplierPayableBasis","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"CompleteSupplierPayableBasisInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"supplierPayablePosition":{"name":"supplierPayablePosition","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<SupplierPayablePosition | null>","isStatic":false,"isPublic":true},"reimbursementPosition":{"name":"reimbursementPosition","async":true,"parameters":[{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<ExpenseReimbursementPosition>","isStatic":false,"isPublic":true},"refundPosition":{"name":"refundPosition","async":true,"parameters":[{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<ExpenseRefundPosition>","isStatic":false,"isPublic":true},"record":{"name":"record","async":true,"parameters":[{"name":"options","type":"RecordExpenseActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"record","effect":"write","idempotent":false,"openWorld":false,"description":"Record a new expense for review (service-backed)."}},"attachReceipt":{"name":"attachReceipt","async":true,"parameters":[{"name":"options","type":"AttachExpenseReceiptActionInput","optional":false}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"attachReceipt","effect":"write","idempotent":false,"openWorld":false,"description":"Attach a receipt file to an expense (service-backed)."}},"recordReview":{"name":"recordReview","async":true,"parameters":[{"name":"options","type":"ReviewExpenseActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"recordReview","effect":"write","idempotent":false,"openWorld":false,"description":"Approve or reject an expense with a reason (service-backed)."}},"recordReimbursement":{"name":"recordReimbursement","async":true,"parameters":[{"name":"options","type":"MarkExpenseReimbursedActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"recordReimbursement","effect":"write","idempotent":false,"openWorld":false,"description":"Record that an expense was reimbursed (service-backed)."}},"forCostObject":{"name":"forCostObject","async":true,"parameters":[{"name":"costObjectType","type":"string","optional":false},{"name":"costObjectId","type":"string","optional":false}],"returnType":"Promise<Expense[]>","isStatic":false,"isPublic":true},"totalsByCurrency":{"name":"totalsByCurrency","async":true,"parameters":[{"name":"options","type":"ExpenseTotalsOptions","optional":true}],"returnType":"Promise<CurrencyTotals>","isStatic":false,"isPublic":true},"listReviewedTaxFacts":{"name":"listReviewedTaxFacts","async":true,"parameters":[{"name":"options","type":"ReviewedExpenseTaxFactsOptions","optional":false}],"returnType":"Promise<ReviewedExpenseTaxFact[]>","isStatic":false,"isPublic":true},"commitmentPosition":{"name":"commitmentPosition","async":true,"parameters":[{"name":"commitmentId","type":"string","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<CommitmentPosition>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["record","attachReceipt","recordReview","recordReimbursement"]},"mcp":{"include":[]},"cli":false,"tableName":"expenses"},"extends":"SmrtCollection","extendsTypeArg":"Expense","exportName":"ExpenseCollection","collectionExportName":"ExpenseCollectionCollection","collectionOf":"@happyvertical/smrt-expenses:Expense","schema":{"tableName":"expenses","ddl":"CREATE TABLE IF NOT EXISTS \\"expenses\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"category\\" TEXT,\\n \\"description\\" TEXT,\\n \\"amount\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"financial_breakdown\\" TEXT,\\n \\"supplier_payable_json\\" TEXT,\\n \\"paid_by_name\\" TEXT,\\n \\"refund_of_id\\" UUID,\\n \\"refund_recipient\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"recorded_at\\" TIMESTAMP,\\n \\"recorded_by_profile_id\\" UUID,\\n \\"paid_by\\" TEXT,\\n \\"paid_by_profile_id\\" UUID,\\n \\"vendor_id\\" UUID,\\n \\"commitment_id\\" UUID,\\n \\"commitment_line_id\\" UUID,\\n \\"review_status\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID,\\n \\"reviewed_at\\" TIMESTAMP,\\n \\"review_note\\" TEXT,\\n \\"duplicate_of_id\\" UUID,\\n \\"reimbursable\\" BOOLEAN,\\n \\"reimbursed_amount\\" INTEGER,\\n \\"reimbursed_at\\" TIMESTAMP,\\n \\"reimbursement_reference\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"category":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"amount":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"financial_breakdown":{"type":"TEXT","notNull":false,"unique":false},"supplier_payable_json":{"type":"TEXT","notNull":false,"unique":false},"paid_by_name":{"type":"TEXT","notNull":false,"unique":false},"refund_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"refund_recipient":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"recorded_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"recorded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false},"paid_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"vendor_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"review_status":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"reviewed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"review_note":{"type":"TEXT","notNull":false,"unique":false},"duplicate_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"SET NULL","onUpdate":"CASCADE"}},"reimbursable":{"type":"BOOLEAN","notNull":false,"unique":false},"reimbursed_amount":{"type":"INTEGER","notNull":false,"unique":false},"reimbursed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"reimbursement_reference":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expenses_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expenses_cost_object_idx","columns":["tenant_id","cost_object_type","cost_object_id","incurred_on"]},{"name":"expenses_commitment_review_idx","columns":["tenant_id","commitment_id","review_status"]},{"name":"expenses_review_status_idx","columns":["tenant_id","review_status","incurred_on"]},{"name":"expenses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expenses_refund_of_id_idx","columns":["refund_of_id"]},{"name":"expenses_recorded_by_profile_id_idx","columns":["recorded_by_profile_id"]},{"name":"expenses_paid_by_profile_id_idx","columns":["paid_by_profile_id"]},{"name":"expenses_vendor_id_idx","columns":["vendor_id"]},{"name":"expenses_commitment_id_idx","columns":["commitment_id"]},{"name":"expenses_commitment_line_id_idx","columns":["commitment_line_id"]},{"name":"expenses_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expenses_duplicate_of_id_idx","columns":["duplicate_of_id"]}],"version":"81a9a368"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseReceiptCollection":{"name":"expensereceiptcollection","className":"ExpenseReceiptCollection","qualifiedName":"@happyvertical/smrt-expenses:ExpenseReceiptCollection","collection":"expensereceipts","description":"Junction collection over expense_receipts (left: expense, right: asset). Reads go through the normal tenant interceptors.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/collections/ExpenseReceiptCollection.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"_expenseId","type":"string","optional":false},{"name":"_assetId","type":"string","optional":false},{"name":"_opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"_expenseId","type":"string","optional":false},{"name":"_assetIds","type":"string[]","optional":false},{"name":"_opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"attachReceipt":{"name":"attachReceipt","async":true,"parameters":[{"name":"input","type":"AttachReceiptInput","optional":false}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true},"receiptsFor":{"name":"receiptsFor","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false}],"returnType":"Promise<ExpenseReceipt[]>","isStatic":false,"isPublic":true},"findDuplicateReceipts":{"name":"findDuplicateReceipts","async":true,"parameters":[{"name":"options","type":"FindDuplicateReceiptsOptions","optional":true}],"returnType":"Promise<DuplicateReceiptGroup[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":[]},"mcp":{"include":[]},"cli":false,"tableName":"expense_receipts"},"extends":"SmrtJunction","extendsTypeArg":"ExpenseReceipt","exportName":"ExpenseReceiptCollection","collectionExportName":"ExpenseReceiptCollectionCollection","collectionOf":"@happyvertical/smrt-expenses:ExpenseReceipt","schema":{"tableName":"expense_receipts","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_receipts\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"expense_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"uploaded_by_profile_id\\" UUID,\\n \\"filename\\" TEXT,\\n \\"mime_type\\" TEXT,\\n \\"byte_count\\" INTEGER,\\n \\"content_sha256\\" TEXT NOT NULL\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"uploaded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"filename":{"type":"TEXT","notNull":false,"unique":false},"mime_type":{"type":"TEXT","notNull":false,"unique":false},"byte_count":{"type":"INTEGER","notNull":false,"unique":false},"content_sha256":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"expense_receipts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expense_receipts_expense_sha256_key","columns":["expense_id","content_sha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenant_id","content_sha256"]},{"name":"expense_receipts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_receipts_asset_id_idx","columns":["asset_id"]},{"name":"expense_receipts_uploaded_by_profile_id_idx","columns":["uploaded_by_profile_id"]}],"version":"8ed688ee"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:Expense":{"name":"expense","className":"Expense","qualifiedName":"@happyvertical/smrt-expenses:Expense","collection":"expenses","description":"A cost someone incurred.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/Expense.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"costObjectType":{"type":"text","required":false,"_meta":{}},"costObjectId":{"type":"text","required":false,"_meta":{}},"category":{"type":"text","required":false,"_meta":{}},"description":{"type":"text","required":false,"_meta":{}},"amount":{"type":"integer","required":false,"_meta":{}},"currency":{"type":"text","required":false,"_meta":{}},"financialBreakdown":{"type":"text","required":false,"_meta":{}},"supplierPayableJson":{"type":"text","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"paidByName":{"type":"text","required":false,"_meta":{}},"refundOfId":{"type":"foreignKey","required":false,"related":"Expense","_meta":{"onDelete":"RESTRICT"}},"refundRecipient":{"type":"text","required":false,"_meta":{}},"incurredOn":{"type":"text","required":false,"_meta":{}},"recordedAt":{"type":"datetime","required":false,"_meta":{}},"recordedByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"paidBy":{"type":"text","required":false,"_meta":{},"enum":["company","person","third-party"]},"paidByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"vendorId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:Vendor"},"commitmentId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:PurchaseContract","_meta":{"validateSubtype":true}},"commitmentLineId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:ContractLineItem"},"reviewStatus":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["unreviewed","reviewed","rejected"],"readonly":true},"reviewedByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"reviewedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"reviewNote":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"duplicateOfId":{"type":"foreignKey","required":false,"related":"Expense","_meta":{"onDelete":"SET NULL"}},"reimbursable":{"type":"boolean","required":false,"_meta":{}},"reimbursedAmount":{"type":"integer","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"reimbursedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"reimbursementReference":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true}},"methods":{"getFinancialBreakdown":{"name":"getFinancialBreakdown","async":false,"parameters":[],"returnType":"ExpenseFinancialBreakdown | null","isStatic":false,"isPublic":true},"setFinancialBreakdown":{"name":"setFinancialBreakdown","async":false,"parameters":[{"name":"value","type":"ExpenseFinancialBreakdown | null","optional":false,"unionBranches":[{"type":"ExpenseFinancialBreakdown"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true},"getRefundRecipient":{"name":"getRefundRecipient","async":false,"parameters":[],"returnType":"ExpenseRefundRecipient | null","isStatic":false,"isPublic":true},"setRefundRecipient":{"name":"setRefundRecipient","async":false,"parameters":[{"name":"value","type":"ExpenseRefundRecipient | null","optional":false,"unionBranches":[{"type":"ExpenseRefundRecipient"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true},"getCostBasis":{"name":"getCostBasis","async":false,"parameters":[],"returnType":"ExpenseCostBasis | null","isStatic":false,"isPublic":true},"isReimbursed":{"name":"isReimbursed","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isCountable":{"name":"isCountable","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"review":{"name":"review","async":true,"parameters":[{"name":"input","type":"ReviewExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"reject":{"name":"reject","async":true,"parameters":[{"name":"input","type":"RejectExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"markDuplicate":{"name":"markDuplicate","async":true,"parameters":[{"name":"input","type":"MarkDuplicateExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"reopen":{"name":"reopen","async":true,"parameters":[{"name":"input","type":"ReopenExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"markReimbursed":{"name":"markReimbursed","async":true,"parameters":[{"name":"input","type":"MarkReimbursedInput","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"getCostObject":{"name":"getCostObject","async":true,"parameters":[],"returnType":"Promise<T | null>","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"recordSupplierPayable":{"name":"recordSupplierPayable","async":true,"parameters":[{"name":"input","type":"RecordSupplierPayableInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"recordSupplierPayableEvent":{"name":"recordSupplierPayableEvent","async":true,"parameters":[{"name":"input","type":"RecordSupplierPayableEventInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"completeSupplierPayableBasis":{"name":"completeSupplierPayableBasis","async":true,"parameters":[{"name":"input","type":"CompleteSupplierPayableBasisInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"getSupplierPayablePosition":{"name":"getSupplierPayablePosition","async":true,"parameters":[{"name":"input","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<SupplierPayablePosition | null>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"object","optional":true,"memberTypes":["Date | string"]}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"getReimbursementPosition":{"name":"getReimbursementPosition","async":true,"parameters":[],"returnType":"Promise<ExpenseReimbursementPosition>","isStatic":false,"isPublic":true},"getRefundPosition":{"name":"getRefundPosition","async":true,"parameters":[],"returnType":"Promise<ExpenseRefundPosition>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expenses","api":{"include":["record","attachReceipt","recordReview","recordReimbursement"]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expenses_cost_object_idx","columns":["tenantId","costObjectType","costObjectId","incurredOn"]},{"name":"expenses_commitment_review_idx","columns":["tenantId","commitmentId","reviewStatus"]},{"name":"expenses_review_status_idx","columns":["tenantId","reviewStatus","incurredOn"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Expense","collectionExportName":"ExpenseCollection","schema":{"tableName":"expenses","ddl":"CREATE TABLE IF NOT EXISTS \\"expenses\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"category\\" TEXT,\\n \\"description\\" TEXT,\\n \\"amount\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"financial_breakdown\\" TEXT,\\n \\"supplier_payable_json\\" TEXT,\\n \\"paid_by_name\\" TEXT,\\n \\"refund_of_id\\" UUID,\\n \\"refund_recipient\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"recorded_at\\" TIMESTAMP,\\n \\"recorded_by_profile_id\\" UUID,\\n \\"paid_by\\" TEXT,\\n \\"paid_by_profile_id\\" UUID,\\n \\"vendor_id\\" UUID,\\n \\"commitment_id\\" UUID,\\n \\"commitment_line_id\\" UUID,\\n \\"review_status\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID,\\n \\"reviewed_at\\" TIMESTAMP,\\n \\"review_note\\" TEXT,\\n \\"duplicate_of_id\\" UUID,\\n \\"reimbursable\\" BOOLEAN,\\n \\"reimbursed_amount\\" INTEGER,\\n \\"reimbursed_at\\" TIMESTAMP,\\n \\"reimbursement_reference\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"category":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"amount":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"financial_breakdown":{"type":"TEXT","notNull":false,"unique":false},"supplier_payable_json":{"type":"TEXT","notNull":false,"unique":false},"paid_by_name":{"type":"TEXT","notNull":false,"unique":false},"refund_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"refund_recipient":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"recorded_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"recorded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false},"paid_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"vendor_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"review_status":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"reviewed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"review_note":{"type":"TEXT","notNull":false,"unique":false},"duplicate_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"SET NULL","onUpdate":"CASCADE"}},"reimbursable":{"type":"BOOLEAN","notNull":false,"unique":false},"reimbursed_amount":{"type":"INTEGER","notNull":false,"unique":false},"reimbursed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"reimbursement_reference":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expenses_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expenses_cost_object_idx","columns":["tenant_id","cost_object_type","cost_object_id","incurred_on"]},{"name":"expenses_commitment_review_idx","columns":["tenant_id","commitment_id","review_status"]},{"name":"expenses_review_status_idx","columns":["tenant_id","review_status","incurred_on"]},{"name":"expenses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expenses_refund_of_id_idx","columns":["refund_of_id"]},{"name":"expenses_recorded_by_profile_id_idx","columns":["recorded_by_profile_id"]},{"name":"expenses_paid_by_profile_id_idx","columns":["paid_by_profile_id"]},{"name":"expenses_vendor_id_idx","columns":["vendor_id"]},{"name":"expenses_commitment_id_idx","columns":["commitment_id"]},{"name":"expenses_commitment_line_id_idx","columns":["commitment_line_id"]},{"name":"expenses_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expenses_duplicate_of_id_idx","columns":["duplicate_of_id"]}],"version":"81a9a368"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseReceipt":{"name":"expensereceipt","className":"ExpenseReceipt","qualifiedName":"@happyvertical/smrt-expenses:ExpenseReceipt","collection":"expensereceipts","description":"A receipt file attached to an expense.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/ExpenseReceipt.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"expenseId":{"type":"foreignKey","required":true,"related":"Expense","_meta":{"required":true,"onDelete":"CASCADE"}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"uploadedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"filename":{"type":"text","required":false,"_meta":{}},"mimeType":{"type":"text","required":false,"_meta":{}},"byteCount":{"type":"integer","required":false,"_meta":{}},"contentSha256":{"type":"text","required":true,"_meta":{"required":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expense_receipts","api":{"include":[]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expense_receipts_expense_sha256_key","columns":["expenseId","contentSha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenantId","contentSha256"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ExpenseReceipt","collectionExportName":"ExpenseReceiptCollection","validationRules":[{"field":"expenseId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"contentSha256","rule":"required","fieldType":"text"}],"schema":{"tableName":"expense_receipts","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_receipts\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"expense_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"uploaded_by_profile_id\\" UUID,\\n \\"filename\\" TEXT,\\n \\"mime_type\\" TEXT,\\n \\"byte_count\\" INTEGER,\\n \\"content_sha256\\" TEXT NOT NULL\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"uploaded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"filename":{"type":"TEXT","notNull":false,"unique":false},"mime_type":{"type":"TEXT","notNull":false,"unique":false},"byte_count":{"type":"INTEGER","notNull":false,"unique":false},"content_sha256":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"expense_receipts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expense_receipts_expense_sha256_key","columns":["expense_id","content_sha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenant_id","content_sha256"]},{"name":"expense_receipts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_receipts_asset_id_idx","columns":["asset_id"]},{"name":"expense_receipts_uploaded_by_profile_id_idx","columns":["uploaded_by_profile_id"]}],"version":"8ed688ee"},"importPath":"@happyvertical/smrt-expenses"}},"moduleType":"smrt","recipes":[{"id":"expenses.tracking","className":"ExpensesRecipe","label":"Expenses","summary":"Record what the business spends, with receipts, and review it.","synonyms":["costs","receipts","spending","reimbursements"],"models":["@happyvertical/smrt-expenses:Expense","@happyvertical/smrt-expenses:ExpenseReceipt"],"nav":[{"label":"Expenses","model":"@happyvertical/smrt-expenses:Expense","icon":"receipt","description":"Costs you have paid, with the receipt and who paid."}],"requires":[],"section":{"id":"accounting","label":"Accounting","icon":"bank","description":"Your books: accounts, journals and balances."},"options":{"@happyvertical/smrt-expenses:Expense":{"fields":{"description":{"order":1,"required":true},"amount":{"order":2},"currency":{"order":3},"incurredOn":{"label":"Date","order":4,"help":"The date on the receipt, as YYYY-MM-DD."},"category":{"order":5,"help":"One word, such as material, travel, freight or other."},"vendorId":{"label":"Vendor","order":6},"paidBy":{"order":7},"paidByProfileId":{"label":"Person who paid","order":8,"help":"Who paid out of their own pocket, when a person paid."},"paidByName":{"order":9},"reimbursable":{"order":10},"costObjectId":{"label":"Charged to","order":11,"help":"The job, project or client the cost belongs to."},"costObjectType":{"visibility":"hidden"},"financialBreakdown":{"visibility":"hidden"},"supplierPayableJson":{"visibility":"hidden"},"refundRecipient":{"visibility":"hidden"},"reimbursedAmount":{"help":"The recorded amount paid back to the person who paid."},"refundOfId":{"visibility":"hidden"},"recordedByProfileId":{"visibility":"hidden"},"commitmentId":{"visibility":"hidden"},"commitmentLineId":{"visibility":"hidden"},"reviewedByProfileId":{"visibility":"hidden"},"duplicateOfId":{"visibility":"hidden"}}},"@happyvertical/smrt-expenses:ExpenseReceipt":{"fields":{"contentSha256":{"visibility":"hidden"},"byteCount":{"visibility":"hidden"},"uploadedByProfileId":{"visibility":"hidden"}}}},"help":{"markdown":"## Overview\\n\\nExpenses are the costs the business pays: a receipt, who paid it and what it\\nwas for. Each expense is reviewed once, and a reimbursable one is marked when\\nthe person is paid back.\\n\\n## Tasks\\n\\n### Record an expense\\n\\n1. Create a new expense and write a short **{field:description}**.\\n2. Enter the **{field:amount}** and its **{field:currency}**.\\n3. Set the **{field:incurredOn}** date from the receipt and pick a\\n **{field:category}**.\\n4. Choose who **{field:paidBy}**: the company, a person or a third party.\\n5. When a person paid, pick the **{field:paidByProfileId}** and tick\\n **{field:reimbursable}** when they are owed the money back.\\n\\n### Review it\\n\\n1. Open the expense and check the receipt attached to it.\\n2. **{field:reviewStatus}** moves from unreviewed to reviewed, or to rejected,\\n when someone reviews it. A rejected expense can be reopened.\\n","fieldRefs":["amount","category","currency","description","incurredOn","paidBy","paidByProfileId","reimbursable","reviewStatus"]},"demo":{"mode":"server","reasons":["Needs a server because its package does not build for a browser: node:crypto; and its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-commerce, @happyvertical/smrt-jobs, @happyvertical/smrt-ledgers, @happyvertical/smrt-products, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-subscriptions, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3616, #3617, #3624, #3625, #3626, #3627)."]}}],"smrtDependencies":["@happyvertical/smrt-assets","@happyvertical/smrt-commerce","@happyvertical/smrt-core","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3615","#3616","#3617","#3624","#3625","#3626","#3627"],"reason":"node:crypto","via":["@happyvertical/smrt-agents","@happyvertical/smrt-commerce","@happyvertical/smrt-jobs","@happyvertical/smrt-ledgers","@happyvertical/smrt-products","@happyvertical/smrt-profiles","@happyvertical/smrt-reports","@happyvertical/smrt-secrets","@happyvertical/smrt-subscriptions","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
|
|
11
|
+
ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-expenses","packageVersion":"0.60.15","objects":{"@happyvertical/smrt-expenses:ExpenseCollection":{"name":"expensecollection","className":"ExpenseCollection","qualifiedName":"@happyvertical/smrt-expenses:ExpenseCollection","collection":"expenses","description":"Collection for Expense. Reads go through the normal tenant interceptors, so inside withTenant() every method here sees only that tenant's expenses.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/collections/ExpenseCollection.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{"recordSupplierPayable":{"name":"recordSupplierPayable","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"RecordSupplierPayableInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"recordSupplierPayableEvent":{"name":"recordSupplierPayableEvent","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"RecordSupplierPayableEventInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"completeSupplierPayableBasis":{"name":"completeSupplierPayableBasis","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"CompleteSupplierPayableBasisInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"supplierPayablePosition":{"name":"supplierPayablePosition","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false},{"name":"input","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<SupplierPayablePosition | null>","isStatic":false,"isPublic":true},"reimbursementPosition":{"name":"reimbursementPosition","async":true,"parameters":[{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<ExpenseReimbursementPosition>","isStatic":false,"isPublic":true},"refundPosition":{"name":"refundPosition","async":true,"parameters":[{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<ExpenseRefundPosition>","isStatic":false,"isPublic":true},"record":{"name":"record","async":true,"parameters":[{"name":"options","type":"RecordExpenseActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"record","effect":"write","idempotent":false,"openWorld":false,"description":"Record a new expense for review (service-backed)."}},"attachReceipt":{"name":"attachReceipt","async":true,"parameters":[{"name":"options","type":"AttachExpenseReceiptActionInput","optional":false}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"attachReceipt","effect":"write","idempotent":false,"openWorld":false,"description":"Attach a receipt file to an expense (service-backed)."}},"recordReview":{"name":"recordReview","async":true,"parameters":[{"name":"options","type":"ReviewExpenseActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"recordReview","effect":"write","idempotent":false,"openWorld":false,"description":"Approve or reject an expense with a reason (service-backed)."}},"recordReimbursement":{"name":"recordReimbursement","async":true,"parameters":[{"name":"options","type":"MarkExpenseReimbursedActionInput","optional":false}],"returnType":"Promise<Expense>","isStatic":false,"isPublic":true,"decoratorConfig":{"httpMethod":"POST","path":"recordReimbursement","effect":"write","idempotent":false,"openWorld":false,"description":"Record that an expense was reimbursed (service-backed)."}},"forCostObject":{"name":"forCostObject","async":true,"parameters":[{"name":"costObjectType","type":"string","optional":false},{"name":"costObjectId","type":"string","optional":false}],"returnType":"Promise<Expense[]>","isStatic":false,"isPublic":true},"totalsByCurrency":{"name":"totalsByCurrency","async":true,"parameters":[{"name":"options","type":"ExpenseTotalsOptions","optional":true}],"returnType":"Promise<CurrencyTotals>","isStatic":false,"isPublic":true},"reviewAllocation":{"name":"reviewAllocation","async":true,"parameters":[{"name":"input","type":"ReviewExpenseAllocationInput","optional":false},{"name":"authority","type":"ExpenseAllocationAuthority","optional":false}],"returnType":"Promise<ExpenseAllocationResult>","isStatic":false,"isPublic":true},"withdrawAllocation":{"name":"withdrawAllocation","async":true,"parameters":[{"name":"input","type":"WithdrawExpenseAllocationInput","optional":false},{"name":"authority","type":"ExpenseAllocationAuthority","optional":false}],"returnType":"Promise<WithdrawExpenseAllocationResult>","isStatic":false,"isPublic":true},"listReviewedTaxFacts":{"name":"listReviewedTaxFacts","async":true,"parameters":[{"name":"options","type":"ReviewedExpenseTaxFactsOptions","optional":false}],"returnType":"Promise<ReviewedExpenseTaxFact[]>","isStatic":false,"isPublic":true},"commitmentPosition":{"name":"commitmentPosition","async":true,"parameters":[{"name":"commitmentId","type":"string","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<CommitmentPosition>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["record","attachReceipt","recordReview","recordReimbursement"]},"mcp":{"include":[]},"cli":false,"tableName":"expenses"},"extends":"SmrtCollection","extendsTypeArg":"Expense","exportName":"ExpenseCollection","collectionExportName":"ExpenseCollectionCollection","collectionOf":"@happyvertical/smrt-expenses:Expense","schema":{"tableName":"expenses","ddl":"CREATE TABLE IF NOT EXISTS \\"expenses\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"category\\" TEXT,\\n \\"description\\" TEXT,\\n \\"amount\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"financial_breakdown\\" TEXT,\\n \\"supplier_payable_json\\" TEXT,\\n \\"paid_by_name\\" TEXT,\\n \\"refund_of_id\\" UUID,\\n \\"refund_recipient\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"recorded_at\\" TIMESTAMP,\\n \\"recorded_by_profile_id\\" UUID,\\n \\"paid_by\\" TEXT,\\n \\"paid_by_profile_id\\" UUID,\\n \\"vendor_id\\" UUID,\\n \\"commitment_id\\" UUID,\\n \\"commitment_line_id\\" UUID,\\n \\"review_status\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID,\\n \\"reviewed_at\\" TIMESTAMP,\\n \\"review_note\\" TEXT,\\n \\"duplicate_of_id\\" UUID,\\n \\"reimbursable\\" BOOLEAN,\\n \\"reimbursed_amount\\" INTEGER,\\n \\"reimbursed_at\\" TIMESTAMP,\\n \\"reimbursement_reference\\" TEXT,\\n \\"active_allocation_revision_id\\" UUID\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"category":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"amount":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"financial_breakdown":{"type":"TEXT","notNull":false,"unique":false},"supplier_payable_json":{"type":"TEXT","notNull":false,"unique":false},"paid_by_name":{"type":"TEXT","notNull":false,"unique":false},"refund_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"refund_recipient":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"recorded_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"recorded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false},"paid_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"vendor_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"review_status":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"reviewed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"review_note":{"type":"TEXT","notNull":false,"unique":false},"duplicate_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"SET NULL","onUpdate":"CASCADE"}},"reimbursable":{"type":"BOOLEAN","notNull":false,"unique":false},"reimbursed_amount":{"type":"INTEGER","notNull":false,"unique":false},"reimbursed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"reimbursement_reference":{"type":"TEXT","notNull":false,"unique":false},"active_allocation_revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}}},"indexes":[{"name":"expenses_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expenses_cost_object_idx","columns":["tenant_id","cost_object_type","cost_object_id","incurred_on"]},{"name":"expenses_commitment_review_idx","columns":["tenant_id","commitment_id","review_status"]},{"name":"expenses_review_status_idx","columns":["tenant_id","review_status","incurred_on"]},{"name":"expenses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expenses_refund_of_id_idx","columns":["refund_of_id"]},{"name":"expenses_recorded_by_profile_id_idx","columns":["recorded_by_profile_id"]},{"name":"expenses_paid_by_profile_id_idx","columns":["paid_by_profile_id"]},{"name":"expenses_vendor_id_idx","columns":["vendor_id"]},{"name":"expenses_commitment_id_idx","columns":["commitment_id"]},{"name":"expenses_commitment_line_id_idx","columns":["commitment_line_id"]},{"name":"expenses_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expenses_duplicate_of_id_idx","columns":["duplicate_of_id"]},{"name":"expenses_active_allocation_revision_id_idx","columns":["active_allocation_revision_id"]}],"version":"f8413307"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseReceiptCollection":{"name":"expensereceiptcollection","className":"ExpenseReceiptCollection","qualifiedName":"@happyvertical/smrt-expenses:ExpenseReceiptCollection","collection":"expensereceipts","description":"Junction collection over expense_receipts (left: expense, right: asset). Reads go through the normal tenant interceptors.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/collections/ExpenseReceiptCollection.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{"byRight":{"name":"byRight","async":true,"parameters":[{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"byLeft":{"name":"byLeft","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<TItem[]>","isStatic":false,"isPublic":true},"attach":{"name":"attach","async":true,"parameters":[{"name":"_expenseId","type":"string","optional":false},{"name":"_assetId","type":"string","optional":false},{"name":"_opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true},"detach":{"name":"detach","async":true,"parameters":[{"name":"leftId","type":"string","optional":false},{"name":"rightId","type":"string","optional":false},{"name":"opts","type":"JunctionFilterOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"setLinks":{"name":"setLinks","async":true,"parameters":[{"name":"_expenseId","type":"string","optional":false},{"name":"_assetIds","type":"string[]","optional":false},{"name":"_opts","type":"JunctionAttachOptions","optional":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"attachReceipt":{"name":"attachReceipt","async":true,"parameters":[{"name":"input","type":"AttachReceiptInput","optional":false}],"returnType":"Promise<ExpenseReceipt>","isStatic":false,"isPublic":true},"receiptsFor":{"name":"receiptsFor","async":true,"parameters":[{"name":"expenseId","type":"string","optional":false}],"returnType":"Promise<ExpenseReceipt[]>","isStatic":false,"isPublic":true},"findDuplicateReceipts":{"name":"findDuplicateReceipts","async":true,"parameters":[{"name":"options","type":"FindDuplicateReceiptsOptions","optional":true}],"returnType":"Promise<DuplicateReceiptGroup[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":[]},"mcp":{"include":[]},"cli":false,"tableName":"expense_receipts"},"extends":"SmrtJunction","extendsTypeArg":"ExpenseReceipt","exportName":"ExpenseReceiptCollection","collectionExportName":"ExpenseReceiptCollectionCollection","collectionOf":"@happyvertical/smrt-expenses:ExpenseReceipt","schema":{"tableName":"expense_receipts","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_receipts\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"expense_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"uploaded_by_profile_id\\" UUID,\\n \\"filename\\" TEXT,\\n \\"mime_type\\" TEXT,\\n \\"byte_count\\" INTEGER,\\n \\"content_sha256\\" TEXT NOT NULL\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"uploaded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"filename":{"type":"TEXT","notNull":false,"unique":false},"mime_type":{"type":"TEXT","notNull":false,"unique":false},"byte_count":{"type":"INTEGER","notNull":false,"unique":false},"content_sha256":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"expense_receipts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expense_receipts_expense_sha256_key","columns":["expense_id","content_sha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenant_id","content_sha256"]},{"name":"expense_receipts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_receipts_asset_id_idx","columns":["asset_id"]},{"name":"expense_receipts_uploaded_by_profile_id_idx","columns":["uploaded_by_profile_id"]}],"version":"8ed688ee"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:Revisions":{"name":"revisions","className":"Revisions","qualifiedName":"@happyvertical/smrt-expenses:Revisions","collection":"expenseallocationrevisions","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/expense-allocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{},"decoratorConfig":{"api":{"include":[]},"mcp":{"include":[]},"cli":false,"tableName":"expense_allocation_revisions"},"extends":"SmrtCollection","extendsTypeArg":"ExpenseAllocationRevision","exportName":"Revisions","collectionExportName":"RevisionsCollection","collectionOf":"@happyvertical/smrt-expenses:ExpenseAllocationRevision","schema":{"tableName":"expense_allocation_revisions","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_revisions\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"kind\\" TEXT DEFAULT 'allocation',\\n \\"source_cost_object_type\\" TEXT,\\n \\"source_cost_object_id\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"paid_by\\" TEXT DEFAULT 'company',\\n \\"reimbursable\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"has_supplier_tax\\" BOOLEAN,\\n \\"basis_equals_gross\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"previous_revision_id\\" UUID,\\n \\"request_id\\" TEXT,\\n \\"request_fingerprint\\" TEXT,\\n \\"source_fingerprint\\" TEXT,\\n \\"source_fingerprint_nonce\\" TEXT,\\n \\"source_revision\\" TEXT,\\n \\"amount_minor\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID NOT NULL,\\n \\"reviewed_by_user_id\\" UUID NOT NULL,\\n \\"review_evidence\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"allocation"},"source_cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"source_cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false,"default":"company"},"reimbursable":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"has_supplier_tax":{"type":"BOOLEAN","notNull":false,"unique":false},"basis_equals_gross":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"previous_revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"request_id":{"type":"TEXT","notNull":false,"unique":false},"request_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"source_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"source_fingerprint_nonce":{"type":"TEXT","notNull":false,"unique":false},"source_revision":{"type":"TEXT","notNull":false,"unique":false},"amount_minor":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"reviewed_by_user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"review_evidence":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_revisions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_request_key","columns":["source_expense_id","request_id"],"unique":true},{"name":"expense_allocation_revision_successor","columns":["previous_revision_id"],"unique":true},{"name":"expense_allocation_revisions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_revisions_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expense_allocation_revisions_reviewed_by_user_id_idx","columns":["reviewed_by_user_id"]}],"version":"83425a51"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:Portions":{"name":"portions","className":"Portions","qualifiedName":"@happyvertical/smrt-expenses:Portions","collection":"expenseallocationportions","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/expense-allocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{},"decoratorConfig":{"api":{"include":[]},"mcp":{"include":[]},"cli":false,"tableName":"expense_allocation_portions"},"extends":"SmrtCollection","extendsTypeArg":"ExpenseAllocationPortion","exportName":"Portions","collectionExportName":"PortionsCollection","collectionOf":"@happyvertical/smrt-expenses:ExpenseAllocationPortion","schema":{"tableName":"expense_allocation_portions","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_portions\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"revision_id\\" UUID NOT NULL,\\n \\"portion_key\\" TEXT,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"cost_code_ref\\" TEXT,\\n \\"amount_minor\\" INTEGER,\\n \\"currency\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"portion_key":{"type":"TEXT","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"cost_code_ref":{"type":"TEXT","notNull":false,"unique":false},"amount_minor":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_portions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_portion_key","columns":["revision_id","portion_key"],"unique":true},{"name":"expense_allocation_portions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_portions_source_expense_id_idx","columns":["source_expense_id"]}],"version":"6b0600b1"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:Billings":{"name":"billings","className":"Billings","qualifiedName":"@happyvertical/smrt-expenses:Billings","collection":"expenseallocationbillings","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/expense-allocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{},"methods":{},"decoratorConfig":{"api":{"include":[]},"mcp":{"include":[]},"cli":false,"tableName":"expense_allocation_billings"},"extends":"SmrtCollection","extendsTypeArg":"ExpenseAllocationBilling","exportName":"Billings","collectionExportName":"BillingsCollection","collectionOf":"@happyvertical/smrt-expenses:ExpenseAllocationBilling","schema":{"tableName":"expense_allocation_billings","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_billings\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"portion_id\\" UUID NOT NULL,\\n \\"invoice_id\\" UUID,\\n \\"draft_type\\" TEXT,\\n \\"draft_id\\" TEXT,\\n \\"invoice_line_id\\" UUID,\\n \\"request_id\\" TEXT,\\n \\"request_fingerprint\\" TEXT,\\n \\"related_reservation_id\\" UUID,\\n \\"kind\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"portion_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expense_allocation_portions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"invoice_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"draft_type":{"type":"TEXT","notNull":false,"unique":false},"draft_id":{"type":"TEXT","notNull":false,"unique":false},"invoice_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"request_id":{"type":"TEXT","notNull":false,"unique":false},"request_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"related_reservation_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_billings","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"kind":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_billings_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_billing_request","columns":["source_expense_id","request_id"],"unique":true},{"name":"expense_allocation_billing_release","columns":["related_reservation_id","kind"],"unique":true},{"name":"expense_allocation_billings_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_billings_portion_id_idx","columns":["portion_id"]},{"name":"expense_allocation_billings_invoice_id_idx","columns":["invoice_id"]},{"name":"expense_allocation_billings_invoice_line_id_idx","columns":["invoice_line_id"]}],"version":"432aeb1f"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:Expense":{"name":"expense","className":"Expense","qualifiedName":"@happyvertical/smrt-expenses:Expense","collection":"expenses","description":"A cost someone incurred.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/Expense.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"costObjectType":{"type":"text","required":false,"_meta":{}},"costObjectId":{"type":"text","required":false,"_meta":{}},"category":{"type":"text","required":false,"_meta":{}},"description":{"type":"text","required":false,"_meta":{}},"amount":{"type":"integer","required":false,"_meta":{}},"currency":{"type":"text","required":false,"_meta":{}},"financialBreakdown":{"type":"text","required":false,"_meta":{}},"supplierPayableJson":{"type":"text","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"paidByName":{"type":"text","required":false,"_meta":{}},"refundOfId":{"type":"foreignKey","required":false,"related":"Expense","_meta":{"onDelete":"RESTRICT"}},"refundRecipient":{"type":"text","required":false,"_meta":{}},"incurredOn":{"type":"text","required":false,"_meta":{}},"recordedAt":{"type":"datetime","required":false,"_meta":{}},"recordedByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"paidBy":{"type":"text","required":false,"_meta":{},"enum":["company","person","third-party"]},"paidByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"vendorId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:Vendor"},"commitmentId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:PurchaseContract","_meta":{"validateSubtype":true}},"commitmentLineId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:ContractLineItem"},"reviewStatus":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["unreviewed","reviewed","rejected"],"readonly":true},"reviewedByProfileId":{"type":"crossPackageRef","required":false,"related":"PROFILE"},"reviewedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"reviewNote":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"duplicateOfId":{"type":"foreignKey","required":false,"related":"Expense","_meta":{"onDelete":"SET NULL"}},"reimbursable":{"type":"boolean","required":false,"_meta":{}},"reimbursedAmount":{"type":"integer","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"reimbursedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"reimbursementReference":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"activeAllocationRevisionId":{"type":"foreignKey","required":false,"related":"ExpenseAllocationRevision","_meta":{"readonly":true,"nullable":true,"onDelete":"RESTRICT"},"readonly":true}},"methods":{"getFinancialBreakdown":{"name":"getFinancialBreakdown","async":false,"parameters":[],"returnType":"ExpenseFinancialBreakdown | null","isStatic":false,"isPublic":true},"setFinancialBreakdown":{"name":"setFinancialBreakdown","async":false,"parameters":[{"name":"value","type":"ExpenseFinancialBreakdown | null","optional":false,"unionBranches":[{"type":"ExpenseFinancialBreakdown"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true},"getRefundRecipient":{"name":"getRefundRecipient","async":false,"parameters":[],"returnType":"ExpenseRefundRecipient | null","isStatic":false,"isPublic":true},"setRefundRecipient":{"name":"setRefundRecipient","async":false,"parameters":[{"name":"value","type":"ExpenseRefundRecipient | null","optional":false,"unionBranches":[{"type":"ExpenseRefundRecipient"},{"type":"null"}]}],"returnType":"void","isStatic":false,"isPublic":true},"getCostBasis":{"name":"getCostBasis","async":false,"parameters":[],"returnType":"ExpenseCostBasis | null","isStatic":false,"isPublic":true},"isReimbursed":{"name":"isReimbursed","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isCountable":{"name":"isCountable","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"review":{"name":"review","async":true,"parameters":[{"name":"input","type":"ReviewExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"reject":{"name":"reject","async":true,"parameters":[{"name":"input","type":"RejectExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"markDuplicate":{"name":"markDuplicate","async":true,"parameters":[{"name":"input","type":"MarkDuplicateExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"reopen":{"name":"reopen","async":true,"parameters":[{"name":"input","type":"ReopenExpenseInput","optional":false}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"markReimbursed":{"name":"markReimbursed","async":true,"parameters":[{"name":"input","type":"MarkReimbursedInput","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"getCostObject":{"name":"getCostObject","async":true,"parameters":[],"returnType":"Promise<T | null>","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"recordSupplierPayable":{"name":"recordSupplierPayable","async":true,"parameters":[{"name":"input","type":"RecordSupplierPayableInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"recordSupplierPayableEvent":{"name":"recordSupplierPayableEvent","async":true,"parameters":[{"name":"input","type":"RecordSupplierPayableEventInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"completeSupplierPayableBasis":{"name":"completeSupplierPayableBasis","async":true,"parameters":[{"name":"input","type":"CompleteSupplierPayableBasisInput","optional":false}],"returnType":"Promise<SupplierPayablePosition>","isStatic":false,"isPublic":true},"getSupplierPayablePosition":{"name":"getSupplierPayablePosition","async":true,"parameters":[{"name":"input","type":"object","optional":true,"memberTypes":["string"]}],"returnType":"Promise<SupplierPayablePosition | null>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"object","optional":true,"memberTypes":["Date | string"]}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"getReimbursementPosition":{"name":"getReimbursementPosition","async":true,"parameters":[],"returnType":"Promise<ExpenseReimbursementPosition>","isStatic":false,"isPublic":true},"getRefundPosition":{"name":"getRefundPosition","async":true,"parameters":[],"returnType":"Promise<ExpenseRefundPosition>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expenses","api":{"include":["record","attachReceipt","recordReview","recordReimbursement"]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expenses_cost_object_idx","columns":["tenantId","costObjectType","costObjectId","incurredOn"]},{"name":"expenses_commitment_review_idx","columns":["tenantId","commitmentId","reviewStatus"]},{"name":"expenses_review_status_idx","columns":["tenantId","reviewStatus","incurredOn"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Expense","collectionExportName":"ExpenseCollection","schema":{"tableName":"expenses","ddl":"CREATE TABLE IF NOT EXISTS \\"expenses\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"category\\" TEXT,\\n \\"description\\" TEXT,\\n \\"amount\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"financial_breakdown\\" TEXT,\\n \\"supplier_payable_json\\" TEXT,\\n \\"paid_by_name\\" TEXT,\\n \\"refund_of_id\\" UUID,\\n \\"refund_recipient\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"recorded_at\\" TIMESTAMP,\\n \\"recorded_by_profile_id\\" UUID,\\n \\"paid_by\\" TEXT,\\n \\"paid_by_profile_id\\" UUID,\\n \\"vendor_id\\" UUID,\\n \\"commitment_id\\" UUID,\\n \\"commitment_line_id\\" UUID,\\n \\"review_status\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID,\\n \\"reviewed_at\\" TIMESTAMP,\\n \\"review_note\\" TEXT,\\n \\"duplicate_of_id\\" UUID,\\n \\"reimbursable\\" BOOLEAN,\\n \\"reimbursed_amount\\" INTEGER,\\n \\"reimbursed_at\\" TIMESTAMP,\\n \\"reimbursement_reference\\" TEXT,\\n \\"active_allocation_revision_id\\" UUID\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"category":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"amount":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"financial_breakdown":{"type":"TEXT","notNull":false,"unique":false},"supplier_payable_json":{"type":"TEXT","notNull":false,"unique":false},"paid_by_name":{"type":"TEXT","notNull":false,"unique":false},"refund_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"refund_recipient":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"recorded_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"recorded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false},"paid_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"vendor_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"commitment_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"review_status":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"reviewed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"review_note":{"type":"TEXT","notNull":false,"unique":false},"duplicate_of_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"SET NULL","onUpdate":"CASCADE"}},"reimbursable":{"type":"BOOLEAN","notNull":false,"unique":false},"reimbursed_amount":{"type":"INTEGER","notNull":false,"unique":false},"reimbursed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"reimbursement_reference":{"type":"TEXT","notNull":false,"unique":false},"active_allocation_revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}}},"indexes":[{"name":"expenses_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expenses_cost_object_idx","columns":["tenant_id","cost_object_type","cost_object_id","incurred_on"]},{"name":"expenses_commitment_review_idx","columns":["tenant_id","commitment_id","review_status"]},{"name":"expenses_review_status_idx","columns":["tenant_id","review_status","incurred_on"]},{"name":"expenses_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expenses_refund_of_id_idx","columns":["refund_of_id"]},{"name":"expenses_recorded_by_profile_id_idx","columns":["recorded_by_profile_id"]},{"name":"expenses_paid_by_profile_id_idx","columns":["paid_by_profile_id"]},{"name":"expenses_vendor_id_idx","columns":["vendor_id"]},{"name":"expenses_commitment_id_idx","columns":["commitment_id"]},{"name":"expenses_commitment_line_id_idx","columns":["commitment_line_id"]},{"name":"expenses_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expenses_duplicate_of_id_idx","columns":["duplicate_of_id"]},{"name":"expenses_active_allocation_revision_id_idx","columns":["active_allocation_revision_id"]}],"version":"f8413307"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseAllocationRevision":{"name":"expenseallocationrevision","className":"ExpenseAllocationRevision","qualifiedName":"@happyvertical/smrt-expenses:ExpenseAllocationRevision","collection":"expenseallocationrevisions","description":"Persisted command-owned ExpenseAllocationRevision evidence.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/ExpenseAllocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{"kind":{"type":"text","required":false,"default":"allocation","enum":["allocation","withdrawal"]},"sourceCostObjectType":{"type":"text","required":false},"sourceCostObjectId":{"type":"text","required":false},"incurredOn":{"type":"text","required":false},"paidBy":{"type":"text","required":false,"default":"company","enum":["company","person","third-party"]},"reimbursable":{"type":"boolean","required":true,"default":false},"hasSupplierTax":{"type":"boolean","required":false,"_meta":{"nullable":true}},"basisEqualsGross":{"type":"boolean","required":true,"default":false},"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"sourceExpenseId":{"type":"foreignKey","required":true,"related":"Expense","_meta":{"required":true,"onDelete":"RESTRICT"}},"previousRevisionId":{"type":"foreignKey","required":false,"related":"ExpenseAllocationRevision","_meta":{"nullable":true,"onDelete":"RESTRICT"}},"requestId":{"type":"text","required":false},"requestFingerprint":{"type":"text","required":false},"sourceFingerprint":{"type":"text","required":false},"sourceFingerprintNonce":{"type":"text","required":false},"sourceRevision":{"type":"text","required":false},"amountMinor":{"type":"integer","required":false,"_meta":{}},"currency":{"type":"text","required":false},"reviewedByProfileId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-profiles:Profile"},"reviewedByUserId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-users:User"},"reviewEvidence":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expense_allocation_revisions","api":{"include":[]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expense_allocation_request_key","columns":["sourceExpenseId","requestId"],"unique":true},{"name":"expense_allocation_revision_successor","columns":["previousRevisionId"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ExpenseAllocationRevision","collectionExportName":"ExpenseAllocationRevisionCollection","validationRules":[{"field":"reimbursable","rule":"required","fieldType":"boolean"},{"field":"basisEqualsGross","rule":"required","fieldType":"boolean"},{"field":"sourceExpenseId","rule":"required","fieldType":"foreignKey"},{"field":"reviewedByProfileId","rule":"required","fieldType":"crossPackageRef"},{"field":"reviewedByUserId","rule":"required","fieldType":"crossPackageRef"}],"schema":{"tableName":"expense_allocation_revisions","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_revisions\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"kind\\" TEXT DEFAULT 'allocation',\\n \\"source_cost_object_type\\" TEXT,\\n \\"source_cost_object_id\\" TEXT,\\n \\"incurred_on\\" TEXT,\\n \\"paid_by\\" TEXT DEFAULT 'company',\\n \\"reimbursable\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"has_supplier_tax\\" BOOLEAN,\\n \\"basis_equals_gross\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"previous_revision_id\\" UUID,\\n \\"request_id\\" TEXT,\\n \\"request_fingerprint\\" TEXT,\\n \\"source_fingerprint\\" TEXT,\\n \\"source_fingerprint_nonce\\" TEXT,\\n \\"source_revision\\" TEXT,\\n \\"amount_minor\\" INTEGER,\\n \\"currency\\" TEXT,\\n \\"reviewed_by_profile_id\\" UUID NOT NULL,\\n \\"reviewed_by_user_id\\" UUID NOT NULL,\\n \\"review_evidence\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"allocation"},"source_cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"source_cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"incurred_on":{"type":"TEXT","notNull":false,"unique":false},"paid_by":{"type":"TEXT","notNull":false,"unique":false,"default":"company"},"reimbursable":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"has_supplier_tax":{"type":"BOOLEAN","notNull":false,"unique":false},"basis_equals_gross":{"type":"BOOLEAN","notNull":true,"unique":false,"default":false},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"previous_revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"request_id":{"type":"TEXT","notNull":false,"unique":false},"request_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"source_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"source_fingerprint_nonce":{"type":"TEXT","notNull":false,"unique":false},"source_revision":{"type":"TEXT","notNull":false,"unique":false},"amount_minor":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"reviewed_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"reviewed_by_user_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"review_evidence":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_revisions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_request_key","columns":["source_expense_id","request_id"],"unique":true},{"name":"expense_allocation_revision_successor","columns":["previous_revision_id"],"unique":true},{"name":"expense_allocation_revisions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_revisions_reviewed_by_profile_id_idx","columns":["reviewed_by_profile_id"]},{"name":"expense_allocation_revisions_reviewed_by_user_id_idx","columns":["reviewed_by_user_id"]}],"version":"83425a51"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseAllocationPortion":{"name":"expenseallocationportion","className":"ExpenseAllocationPortion","qualifiedName":"@happyvertical/smrt-expenses:ExpenseAllocationPortion","collection":"expenseallocationportions","description":"Persisted command-owned ExpenseAllocationPortion evidence.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/ExpenseAllocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"sourceExpenseId":{"type":"foreignKey","required":true,"related":"Expense","_meta":{"required":true,"onDelete":"RESTRICT"}},"revisionId":{"type":"foreignKey","required":true,"related":"ExpenseAllocationRevision","_meta":{"required":true,"onDelete":"RESTRICT"}},"portionKey":{"type":"text","required":false},"costObjectType":{"type":"text","required":false},"costObjectId":{"type":"text","required":false},"costCodeRef":{"type":"text","required":false},"amountMinor":{"type":"integer","required":false,"_meta":{}},"currency":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expense_allocation_portions","api":{"include":[]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expense_allocation_portion_key","columns":["revisionId","portionKey"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ExpenseAllocationPortion","collectionExportName":"ExpenseAllocationPortionCollection","validationRules":[{"field":"sourceExpenseId","rule":"required","fieldType":"foreignKey"},{"field":"revisionId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"expense_allocation_portions","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_portions\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"revision_id\\" UUID NOT NULL,\\n \\"portion_key\\" TEXT,\\n \\"cost_object_type\\" TEXT,\\n \\"cost_object_id\\" TEXT,\\n \\"cost_code_ref\\" TEXT,\\n \\"amount_minor\\" INTEGER,\\n \\"currency\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"revision_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expense_allocation_revisions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"portion_key":{"type":"TEXT","notNull":false,"unique":false},"cost_object_type":{"type":"TEXT","notNull":false,"unique":false},"cost_object_id":{"type":"TEXT","notNull":false,"unique":false},"cost_code_ref":{"type":"TEXT","notNull":false,"unique":false},"amount_minor":{"type":"INTEGER","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_portions_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_portion_key","columns":["revision_id","portion_key"],"unique":true},{"name":"expense_allocation_portions_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_portions_source_expense_id_idx","columns":["source_expense_id"]}],"version":"6b0600b1"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseAllocationBilling":{"name":"expenseallocationbilling","className":"ExpenseAllocationBilling","qualifiedName":"@happyvertical/smrt-expenses:ExpenseAllocationBilling","collection":"expenseallocationbillings","description":"Persisted command-owned ExpenseAllocationBilling evidence.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/ExpenseAllocation.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"sourceExpenseId":{"type":"foreignKey","required":true,"related":"Expense","_meta":{"required":true,"onDelete":"RESTRICT"}},"portionId":{"type":"foreignKey","required":true,"related":"ExpenseAllocationPortion","_meta":{"required":true,"onDelete":"RESTRICT"}},"invoiceId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:Invoice","_meta":{"nullable":true}},"draftType":{"type":"text","required":false},"draftId":{"type":"text","required":false},"invoiceLineId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-commerce:InvoiceLineItem","_meta":{"nullable":true}},"requestId":{"type":"text","required":false},"requestFingerprint":{"type":"text","required":false},"relatedReservationId":{"type":"foreignKey","required":false,"related":"ExpenseAllocationBilling","_meta":{"nullable":true,"onDelete":"RESTRICT"}},"kind":{"type":"text","required":false}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expense_allocation_billings","api":{"include":[]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expense_allocation_billing_request","columns":["sourceExpenseId","requestId"],"unique":true},{"name":"expense_allocation_billing_release","columns":["relatedReservationId","kind"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ExpenseAllocationBilling","collectionExportName":"ExpenseAllocationBillingCollection","validationRules":[{"field":"sourceExpenseId","rule":"required","fieldType":"foreignKey"},{"field":"portionId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"expense_allocation_billings","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_allocation_billings\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"source_expense_id\\" UUID NOT NULL,\\n \\"portion_id\\" UUID NOT NULL,\\n \\"invoice_id\\" UUID,\\n \\"draft_type\\" TEXT,\\n \\"draft_id\\" TEXT,\\n \\"invoice_line_id\\" UUID,\\n \\"request_id\\" TEXT,\\n \\"request_fingerprint\\" TEXT,\\n \\"related_reservation_id\\" UUID,\\n \\"kind\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"source_expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"portion_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expense_allocation_portions","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"invoice_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"draft_type":{"type":"TEXT","notNull":false,"unique":false},"draft_id":{"type":"TEXT","notNull":false,"unique":false},"invoice_line_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"request_id":{"type":"TEXT","notNull":false,"unique":false},"request_fingerprint":{"type":"TEXT","notNull":false,"unique":false},"related_reservation_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"expense_allocation_billings","column":"id","onDelete":"RESTRICT","onUpdate":"CASCADE"}},"kind":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"expense_allocation_billings_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"expense_allocation_billing_request","columns":["source_expense_id","request_id"],"unique":true},{"name":"expense_allocation_billing_release","columns":["related_reservation_id","kind"],"unique":true},{"name":"expense_allocation_billings_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_allocation_billings_portion_id_idx","columns":["portion_id"]},{"name":"expense_allocation_billings_invoice_id_idx","columns":["invoice_id"]},{"name":"expense_allocation_billings_invoice_line_id_idx","columns":["invoice_line_id"]}],"version":"432aeb1f"},"importPath":"@happyvertical/smrt-expenses"},"@happyvertical/smrt-expenses:ExpenseReceipt":{"name":"expensereceipt","className":"ExpenseReceipt","qualifiedName":"@happyvertical/smrt-expenses:ExpenseReceipt","collection":"expensereceipts","description":"A receipt file attached to an expense.","filePath":"/home/runner/work/smrt/smrt/packages/expenses/src/models/ExpenseReceipt.ts","packageName":"@happyvertical/smrt-expenses","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"expenseId":{"type":"foreignKey","required":true,"related":"Expense","_meta":{"required":true,"onDelete":"CASCADE"}},"assetId":{"type":"crossPackageRef","required":true,"related":"@happyvertical/smrt-assets:Asset"},"uploadedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"filename":{"type":"text","required":false,"_meta":{}},"mimeType":{"type":"text","required":false,"_meta":{}},"byteCount":{"type":"integer","required":false,"_meta":{}},"contentSha256":{"type":"text","required":true,"_meta":{"required":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"object","optional":true,"memberTypes":["Date | string"]}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"expense_receipts","api":{"include":[]},"mcp":{"include":[]},"cli":false,"indexes":[{"name":"expense_receipts_expense_sha256_key","columns":["expenseId","contentSha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenantId","contentSha256"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ExpenseReceipt","collectionExportName":"ExpenseReceiptCollection","validationRules":[{"field":"expenseId","rule":"required","fieldType":"foreignKey"},{"field":"assetId","rule":"required","fieldType":"crossPackageRef"},{"field":"contentSha256","rule":"required","fieldType":"text"}],"schema":{"tableName":"expense_receipts","ddl":"CREATE TABLE IF NOT EXISTS \\"expense_receipts\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"expense_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID NOT NULL,\\n \\"uploaded_by_profile_id\\" UUID,\\n \\"filename\\" TEXT,\\n \\"mime_type\\" TEXT,\\n \\"byte_count\\" INTEGER,\\n \\"content_sha256\\" TEXT NOT NULL\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"expense_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"expenses","column":"id","onDelete":"CASCADE","onUpdate":"CASCADE"}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":true,"unique":false},"uploaded_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"filename":{"type":"TEXT","notNull":false,"unique":false},"mime_type":{"type":"TEXT","notNull":false,"unique":false},"byte_count":{"type":"INTEGER","notNull":false,"unique":false},"content_sha256":{"type":"TEXT","notNull":true,"unique":false}},"indexes":[{"name":"expense_receipts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"expense_receipts_expense_sha256_key","columns":["expense_id","content_sha256"],"unique":true},{"name":"expense_receipts_tenant_sha256_idx","columns":["tenant_id","content_sha256"]},{"name":"expense_receipts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"expense_receipts_asset_id_idx","columns":["asset_id"]},{"name":"expense_receipts_uploaded_by_profile_id_idx","columns":["uploaded_by_profile_id"]}],"version":"8ed688ee"},"importPath":"@happyvertical/smrt-expenses"}},"moduleType":"smrt","recipes":[{"id":"expenses.tracking","className":"ExpensesRecipe","label":"Expenses","summary":"Record what the business spends, with receipts, and review it.","synonyms":["costs","receipts","spending","reimbursements"],"models":["@happyvertical/smrt-expenses:Expense","@happyvertical/smrt-expenses:ExpenseReceipt"],"nav":[{"label":"Expenses","model":"@happyvertical/smrt-expenses:Expense","icon":"receipt","description":"Costs you have paid, with the receipt and who paid."}],"requires":[],"section":{"id":"accounting","label":"Accounting","icon":"bank","description":"Your books: accounts, journals and balances."},"options":{"@happyvertical/smrt-expenses:Expense":{"fields":{"description":{"order":1,"required":true},"amount":{"order":2},"currency":{"order":3},"incurredOn":{"label":"Date","order":4,"help":"The date on the receipt, as YYYY-MM-DD."},"category":{"order":5,"help":"One word, such as material, travel, freight or other."},"vendorId":{"label":"Vendor","order":6},"paidBy":{"order":7},"paidByProfileId":{"label":"Person who paid","order":8,"help":"Who paid out of their own pocket, when a person paid."},"paidByName":{"order":9},"reimbursable":{"order":10},"costObjectId":{"label":"Charged to","order":11,"help":"The job, project or client the cost belongs to."},"costObjectType":{"visibility":"hidden"},"activeAllocationRevisionId":{"visibility":"hidden"},"financialBreakdown":{"visibility":"hidden"},"supplierPayableJson":{"visibility":"hidden"},"refundRecipient":{"visibility":"hidden"},"reimbursedAmount":{"help":"The recorded amount paid back to the person who paid."},"refundOfId":{"visibility":"hidden"},"recordedByProfileId":{"visibility":"hidden"},"commitmentId":{"visibility":"hidden"},"commitmentLineId":{"visibility":"hidden"},"reviewedByProfileId":{"visibility":"hidden"},"duplicateOfId":{"visibility":"hidden"}}},"@happyvertical/smrt-expenses:ExpenseReceipt":{"fields":{"contentSha256":{"visibility":"hidden"},"byteCount":{"visibility":"hidden"},"uploadedByProfileId":{"visibility":"hidden"}}}},"help":{"markdown":"## Overview\\n\\nExpenses are the costs the business pays: a receipt, who paid it and what it\\nwas for. Each expense is reviewed once, and a reimbursable one is marked when\\nthe person is paid back.\\n\\n## Tasks\\n\\n### Record an expense\\n\\n1. Create a new expense and write a short **{field:description}**.\\n2. Enter the **{field:amount}** and its **{field:currency}**.\\n3. Set the **{field:incurredOn}** date from the receipt and pick a\\n **{field:category}**.\\n4. Choose who **{field:paidBy}**: the company, a person or a third party.\\n5. When a person paid, pick the **{field:paidByProfileId}** and tick\\n **{field:reimbursable}** when they are owed the money back.\\n\\n### Review it\\n\\n1. Open the expense and check the receipt attached to it.\\n2. **{field:reviewStatus}** moves from unreviewed to reviewed, or to rejected,\\n when someone reviews it. A rejected expense can be reopened.\\n","fieldRefs":["amount","category","currency","description","incurredOn","paidBy","paidByProfileId","reimbursable","reviewStatus"]},"demo":{"mode":"server","reasons":["Needs a server because its package does not build for a browser: node:crypto; and its package depends on @happyvertical/smrt-agents, @happyvertical/smrt-commerce, @happyvertical/smrt-jobs, @happyvertical/smrt-ledgers, @happyvertical/smrt-products, @happyvertical/smrt-profiles, @happyvertical/smrt-reports, @happyvertical/smrt-secrets, @happyvertical/smrt-subscriptions, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3615, #3616, #3617, #3624, #3625, #3626, #3627)."]}}],"smrtDependencies":["@happyvertical/smrt-assets","@happyvertical/smrt-commerce","@happyvertical/smrt-core","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3615","#3616","#3617","#3624","#3625","#3626","#3627"],"reason":"node:crypto","via":["@happyvertical/smrt-agents","@happyvertical/smrt-commerce","@happyvertical/smrt-jobs","@happyvertical/smrt-ledgers","@happyvertical/smrt-products","@happyvertical/smrt-profiles","@happyvertical/smrt-reports","@happyvertical/smrt-secrets","@happyvertical/smrt-subscriptions","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/permissions.ts
|
|
14
|
+
var EXPENSE_ALLOCATION_PERMISSION_DEFINITIONS = [{
|
|
15
|
+
slug: "expenses.read",
|
|
16
|
+
name: "Read allocated expense portions",
|
|
17
|
+
description: "Read bounded expense allocation portions through current host target authority; does not expose original expense or receipt records.",
|
|
18
|
+
category: "accounting"
|
|
19
|
+
}];
|
|
20
|
+
registerPermissionDefinitions(EXPENSE_ALLOCATION_PERMISSION_DEFINITIONS);
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/models/ExpenseAllocation.ts
|
|
23
|
+
var __defProp$2 = Object.defineProperty;
|
|
24
|
+
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
25
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value
|
|
30
|
+
}) : obj[key] = value;
|
|
31
|
+
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
32
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
33
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
34
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
38
|
+
var ExpenseAllocationRevision = class extends SmrtObject {
|
|
39
|
+
kind = "allocation";
|
|
40
|
+
/** Immutable bounded source facts retained even after unbilled withdrawal/correction. */
|
|
41
|
+
sourceCostObjectType = "";
|
|
42
|
+
sourceCostObjectId = "";
|
|
43
|
+
incurredOn = "";
|
|
44
|
+
paidBy = "company";
|
|
45
|
+
reimbursable = false;
|
|
46
|
+
hasSupplierTax = null;
|
|
47
|
+
basisEqualsGross = false;
|
|
48
|
+
tenantId = "";
|
|
49
|
+
sourceExpenseId = "";
|
|
50
|
+
previousRevisionId = null;
|
|
51
|
+
requestId = "";
|
|
52
|
+
requestFingerprint = "";
|
|
53
|
+
sourceFingerprint = "";
|
|
54
|
+
/** Secret to bounded portion readers; prevents low-entropy source-total guessing from its digest. */
|
|
55
|
+
sourceFingerprintNonce = "";
|
|
56
|
+
sourceRevision = "";
|
|
57
|
+
amountMinor = 0;
|
|
58
|
+
currency = "";
|
|
59
|
+
reviewedByProfileId = "";
|
|
60
|
+
reviewedByUserId = "";
|
|
61
|
+
reviewEvidence = "";
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
super(options);
|
|
64
|
+
for (const key of [
|
|
65
|
+
"kind",
|
|
66
|
+
"sourceCostObjectType",
|
|
67
|
+
"sourceCostObjectId",
|
|
68
|
+
"incurredOn",
|
|
69
|
+
"paidBy",
|
|
70
|
+
"reimbursable",
|
|
71
|
+
"hasSupplierTax",
|
|
72
|
+
"basisEqualsGross",
|
|
73
|
+
"tenantId",
|
|
74
|
+
"sourceExpenseId",
|
|
75
|
+
"previousRevisionId",
|
|
76
|
+
"requestId",
|
|
77
|
+
"requestFingerprint",
|
|
78
|
+
"sourceFingerprint",
|
|
79
|
+
"sourceFingerprintNonce",
|
|
80
|
+
"sourceRevision",
|
|
81
|
+
"amountMinor",
|
|
82
|
+
"currency",
|
|
83
|
+
"reviewedByProfileId",
|
|
84
|
+
"reviewedByUserId",
|
|
85
|
+
"reviewEvidence"
|
|
86
|
+
]) if (options[key] !== void 0) Object.assign(this, { [key]: options[key] });
|
|
87
|
+
}
|
|
88
|
+
/** Refuse all writes except command-owned inserts, including same-id adoption. */
|
|
89
|
+
async save(options) {
|
|
90
|
+
if (!allocationWrites.has(this) || !this.tenantId || this.isPersisted) throw new Error("Expense allocation evidence is command-owned and immutable");
|
|
91
|
+
this.requireInsertOnSave();
|
|
92
|
+
return super.save(options);
|
|
93
|
+
}
|
|
94
|
+
/** Evidence cannot be erased, including superseded revisions. */
|
|
95
|
+
async delete() {
|
|
96
|
+
throw new Error("Expense allocation evidence cannot be deleted");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
__publicField$2(ExpenseAllocationRevision, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
100
|
+
__decorateClass$2([field({
|
|
101
|
+
type: "boolean",
|
|
102
|
+
nullable: true
|
|
103
|
+
})], ExpenseAllocationRevision.prototype, "hasSupplierTax", 2);
|
|
104
|
+
__decorateClass$2([tenantId()], ExpenseAllocationRevision.prototype, "tenantId", 2);
|
|
105
|
+
__decorateClass$2([foreignKey("Expense", {
|
|
106
|
+
required: true,
|
|
107
|
+
onDelete: "RESTRICT"
|
|
108
|
+
})], ExpenseAllocationRevision.prototype, "sourceExpenseId", 2);
|
|
109
|
+
__decorateClass$2([foreignKey("ExpenseAllocationRevision", {
|
|
110
|
+
nullable: true,
|
|
111
|
+
onDelete: "RESTRICT"
|
|
112
|
+
})], ExpenseAllocationRevision.prototype, "previousRevisionId", 2);
|
|
113
|
+
__decorateClass$2([field({ type: "integer" })], ExpenseAllocationRevision.prototype, "amountMinor", 2);
|
|
114
|
+
__decorateClass$2([crossPackageRef("@happyvertical/smrt-profiles:Profile", { required: true })], ExpenseAllocationRevision.prototype, "reviewedByProfileId", 2);
|
|
115
|
+
__decorateClass$2([crossPackageRef("@happyvertical/smrt-users:User", { required: true })], ExpenseAllocationRevision.prototype, "reviewedByUserId", 2);
|
|
116
|
+
ExpenseAllocationRevision = __decorateClass$2([TenantScoped({ mode: "required" }), smrt({
|
|
117
|
+
tableName: "expense_allocation_revisions",
|
|
118
|
+
api: { include: [] },
|
|
119
|
+
mcp: { include: [] },
|
|
120
|
+
cli: false,
|
|
121
|
+
indexes: [{
|
|
122
|
+
name: "expense_allocation_request_key",
|
|
123
|
+
columns: ["sourceExpenseId", "requestId"],
|
|
124
|
+
unique: true
|
|
125
|
+
}, {
|
|
126
|
+
name: "expense_allocation_revision_successor",
|
|
127
|
+
columns: ["previousRevisionId"],
|
|
128
|
+
unique: true
|
|
129
|
+
}]
|
|
130
|
+
})], ExpenseAllocationRevision);
|
|
131
|
+
var ExpenseAllocationPortion = class extends SmrtObject {
|
|
132
|
+
tenantId = "";
|
|
133
|
+
sourceExpenseId = "";
|
|
134
|
+
revisionId = "";
|
|
135
|
+
portionKey = "";
|
|
136
|
+
costObjectType = "";
|
|
137
|
+
costObjectId = "";
|
|
138
|
+
costCodeRef = "";
|
|
139
|
+
amountMinor = 0;
|
|
140
|
+
currency = "";
|
|
141
|
+
constructor(options = {}) {
|
|
142
|
+
super(options);
|
|
143
|
+
for (const key of [
|
|
144
|
+
"tenantId",
|
|
145
|
+
"sourceExpenseId",
|
|
146
|
+
"revisionId",
|
|
147
|
+
"portionKey",
|
|
148
|
+
"costObjectType",
|
|
149
|
+
"costObjectId",
|
|
150
|
+
"costCodeRef",
|
|
151
|
+
"amountMinor",
|
|
152
|
+
"currency"
|
|
153
|
+
]) if (options[key] !== void 0) Object.assign(this, { [key]: options[key] });
|
|
154
|
+
}
|
|
155
|
+
/** Refuse all writes except command-owned inserts, including same-id adoption. */
|
|
156
|
+
async save(options) {
|
|
157
|
+
if (!allocationWrites.has(this) || !this.tenantId || this.isPersisted) throw new Error("Expense allocation evidence is command-owned and immutable");
|
|
158
|
+
this.requireInsertOnSave();
|
|
159
|
+
return super.save(options);
|
|
160
|
+
}
|
|
161
|
+
/** Evidence cannot be erased, including superseded revisions. */
|
|
162
|
+
async delete() {
|
|
163
|
+
throw new Error("Expense allocation evidence cannot be deleted");
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
__publicField$2(ExpenseAllocationPortion, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
167
|
+
__decorateClass$2([tenantId()], ExpenseAllocationPortion.prototype, "tenantId", 2);
|
|
168
|
+
__decorateClass$2([foreignKey("Expense", {
|
|
169
|
+
required: true,
|
|
170
|
+
onDelete: "RESTRICT"
|
|
171
|
+
})], ExpenseAllocationPortion.prototype, "sourceExpenseId", 2);
|
|
172
|
+
__decorateClass$2([foreignKey("ExpenseAllocationRevision", {
|
|
173
|
+
required: true,
|
|
174
|
+
onDelete: "RESTRICT"
|
|
175
|
+
})], ExpenseAllocationPortion.prototype, "revisionId", 2);
|
|
176
|
+
__decorateClass$2([field({ type: "integer" })], ExpenseAllocationPortion.prototype, "amountMinor", 2);
|
|
177
|
+
ExpenseAllocationPortion = __decorateClass$2([TenantScoped({ mode: "required" }), smrt({
|
|
178
|
+
tableName: "expense_allocation_portions",
|
|
179
|
+
api: { include: [] },
|
|
180
|
+
mcp: { include: [] },
|
|
181
|
+
cli: false,
|
|
182
|
+
indexes: [{
|
|
183
|
+
name: "expense_allocation_portion_key",
|
|
184
|
+
columns: ["revisionId", "portionKey"],
|
|
185
|
+
unique: true
|
|
186
|
+
}]
|
|
187
|
+
})], ExpenseAllocationPortion);
|
|
188
|
+
var ExpenseAllocationBilling = class extends SmrtObject {
|
|
189
|
+
tenantId = "";
|
|
190
|
+
sourceExpenseId = "";
|
|
191
|
+
portionId = "";
|
|
192
|
+
invoiceId = null;
|
|
193
|
+
draftType = "";
|
|
194
|
+
draftId = "";
|
|
195
|
+
invoiceLineId = null;
|
|
196
|
+
requestId = "";
|
|
197
|
+
requestFingerprint = "";
|
|
198
|
+
relatedReservationId = null;
|
|
199
|
+
kind = "reserve";
|
|
200
|
+
constructor(options = {}) {
|
|
201
|
+
super(options);
|
|
202
|
+
for (const key of [
|
|
203
|
+
"tenantId",
|
|
204
|
+
"sourceExpenseId",
|
|
205
|
+
"portionId",
|
|
206
|
+
"invoiceId",
|
|
207
|
+
"draftType",
|
|
208
|
+
"draftId",
|
|
209
|
+
"invoiceLineId",
|
|
210
|
+
"requestId",
|
|
211
|
+
"requestFingerprint",
|
|
212
|
+
"relatedReservationId",
|
|
213
|
+
"kind"
|
|
214
|
+
]) if (options[key] !== void 0) Object.assign(this, { [key]: options[key] });
|
|
215
|
+
}
|
|
216
|
+
/** Refuse all writes except command-owned inserts, including same-id adoption. */
|
|
217
|
+
async save(options) {
|
|
218
|
+
if (!allocationWrites.has(this) || !this.tenantId || this.isPersisted) throw new Error("Expense allocation evidence is command-owned and immutable");
|
|
219
|
+
this.requireInsertOnSave();
|
|
220
|
+
return super.save(options);
|
|
221
|
+
}
|
|
222
|
+
/** Evidence cannot be erased, including superseded revisions. */
|
|
223
|
+
async delete() {
|
|
224
|
+
throw new Error("Expense allocation evidence cannot be deleted");
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
__publicField$2(ExpenseAllocationBilling, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
228
|
+
__decorateClass$2([tenantId()], ExpenseAllocationBilling.prototype, "tenantId", 2);
|
|
229
|
+
__decorateClass$2([foreignKey("Expense", {
|
|
230
|
+
required: true,
|
|
231
|
+
onDelete: "RESTRICT"
|
|
232
|
+
})], ExpenseAllocationBilling.prototype, "sourceExpenseId", 2);
|
|
233
|
+
__decorateClass$2([foreignKey("ExpenseAllocationPortion", {
|
|
234
|
+
required: true,
|
|
235
|
+
onDelete: "RESTRICT"
|
|
236
|
+
})], ExpenseAllocationBilling.prototype, "portionId", 2);
|
|
237
|
+
__decorateClass$2([crossPackageRef("@happyvertical/smrt-commerce:Invoice", { nullable: true })], ExpenseAllocationBilling.prototype, "invoiceId", 2);
|
|
238
|
+
__decorateClass$2([crossPackageRef("@happyvertical/smrt-commerce:InvoiceLineItem", { nullable: true })], ExpenseAllocationBilling.prototype, "invoiceLineId", 2);
|
|
239
|
+
__decorateClass$2([foreignKey("ExpenseAllocationBilling", {
|
|
240
|
+
nullable: true,
|
|
241
|
+
onDelete: "RESTRICT"
|
|
242
|
+
})], ExpenseAllocationBilling.prototype, "relatedReservationId", 2);
|
|
243
|
+
ExpenseAllocationBilling = __decorateClass$2([TenantScoped({ mode: "required" }), smrt({
|
|
244
|
+
tableName: "expense_allocation_billings",
|
|
245
|
+
api: { include: [] },
|
|
246
|
+
mcp: { include: [] },
|
|
247
|
+
cli: false,
|
|
248
|
+
indexes: [{
|
|
249
|
+
name: "expense_allocation_billing_request",
|
|
250
|
+
columns: ["sourceExpenseId", "requestId"],
|
|
251
|
+
unique: true
|
|
252
|
+
}, {
|
|
253
|
+
name: "expense_allocation_billing_release",
|
|
254
|
+
columns: ["relatedReservationId", "kind"],
|
|
255
|
+
unique: true
|
|
256
|
+
}]
|
|
257
|
+
})], ExpenseAllocationBilling);
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/expense-allocation.ts
|
|
260
|
+
var __defProp$1 = Object.defineProperty;
|
|
261
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
262
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
|
|
263
|
+
enumerable: true,
|
|
264
|
+
configurable: true,
|
|
265
|
+
writable: true,
|
|
266
|
+
value
|
|
267
|
+
}) : obj[key] = value;
|
|
268
|
+
var __decorateClass$1 = (decorators, target2, key, kind) => {
|
|
269
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target2, key) : target2;
|
|
270
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target2, key, result) : decorator(result)) || result;
|
|
271
|
+
if (kind && result) __defProp$1(target2, key, result);
|
|
272
|
+
return result;
|
|
273
|
+
};
|
|
274
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
275
|
+
var Revisions = class extends SmrtCollection {};
|
|
276
|
+
__publicField$1(Revisions, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
277
|
+
__publicField$1(Revisions, "_itemClass", ExpenseAllocationRevision);
|
|
278
|
+
Revisions = __decorateClass$1([smrt({
|
|
279
|
+
api: { include: [] },
|
|
280
|
+
mcp: { include: [] },
|
|
281
|
+
cli: false
|
|
282
|
+
})], Revisions);
|
|
283
|
+
var Portions = class extends SmrtCollection {};
|
|
284
|
+
__publicField$1(Portions, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
285
|
+
__publicField$1(Portions, "_itemClass", ExpenseAllocationPortion);
|
|
286
|
+
Portions = __decorateClass$1([smrt({
|
|
287
|
+
api: { include: [] },
|
|
288
|
+
mcp: { include: [] },
|
|
289
|
+
cli: false
|
|
290
|
+
})], Portions);
|
|
291
|
+
var Billings = class extends SmrtCollection {};
|
|
292
|
+
__publicField$1(Billings, "__smrtPackage__", "@happyvertical/smrt-expenses");
|
|
293
|
+
__publicField$1(Billings, "_itemClass", ExpenseAllocationBilling);
|
|
294
|
+
Billings = __decorateClass$1([smrt({
|
|
295
|
+
api: { include: [] },
|
|
296
|
+
mcp: { include: [] },
|
|
297
|
+
cli: false
|
|
298
|
+
})], Billings);
|
|
299
|
+
function invalid(message) {
|
|
300
|
+
throw new ExpenseError("EXPENSE_INVALID", `Expense allocation: ${message}`);
|
|
301
|
+
}
|
|
302
|
+
function text(value) {
|
|
303
|
+
if (typeof value !== "string" || !value.trim() || value !== value.trim() || value.length > 1e3) invalid("bounded nonempty identifiers/evidence required.");
|
|
304
|
+
}
|
|
305
|
+
function digest(value) {
|
|
306
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
307
|
+
}
|
|
308
|
+
function target(row) {
|
|
309
|
+
if (!row || Object.keys(row).some((key) => ![
|
|
310
|
+
"portionKey",
|
|
311
|
+
"costObjectType",
|
|
312
|
+
"costObjectId",
|
|
313
|
+
"costCodeRef",
|
|
314
|
+
"amountMinor"
|
|
315
|
+
].includes(key))) invalid("unknown portion field.");
|
|
316
|
+
for (const value of [
|
|
317
|
+
row.portionKey,
|
|
318
|
+
row.costObjectType,
|
|
319
|
+
row.costObjectId,
|
|
320
|
+
row.costCodeRef
|
|
321
|
+
]) text(value);
|
|
322
|
+
assertQualifiedClassName("ExpenseAllocation", row.portionKey, row.costObjectType);
|
|
323
|
+
if (!Number.isSafeInteger(row.amountMinor) || row.amountMinor <= 0) invalid("portions require positive safe integer minor units.");
|
|
324
|
+
return {
|
|
325
|
+
portionKey: row.portionKey,
|
|
326
|
+
costObjectType: row.costObjectType,
|
|
327
|
+
costObjectId: row.costObjectId,
|
|
328
|
+
costCodeRef: row.costCodeRef,
|
|
329
|
+
amountMinor: row.amountMinor
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async function portionsFor(source, revisionId) {
|
|
333
|
+
return (await Portions.create({ db: source.db })).list({
|
|
334
|
+
where: {
|
|
335
|
+
tenantId: source.tenantId,
|
|
336
|
+
revisionId
|
|
337
|
+
},
|
|
338
|
+
limit: 1e3,
|
|
339
|
+
orderBy: ["portion_key ASC"]
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async function sourceFor(db, id, tenant) {
|
|
343
|
+
const { ExpenseCollection } = await Promise.resolve().then(() => ExpenseCollection_exports);
|
|
344
|
+
const source = await (await ExpenseCollection.create({ db })).get({
|
|
345
|
+
id,
|
|
346
|
+
tenantId: tenant
|
|
347
|
+
}, { cache: false });
|
|
348
|
+
if (!source || source.tenantId !== tenant) invalid("owned source required.");
|
|
349
|
+
return source;
|
|
350
|
+
}
|
|
351
|
+
async function lock(source) {
|
|
352
|
+
if (isPostgresDatabase(source.db)) await source.db.query(`SELECT id FROM "${source.tableName}" WHERE id = ? AND tenant_id = ? FOR UPDATE`, source.id, source.tenantId);
|
|
353
|
+
await source.loadFromId();
|
|
354
|
+
}
|
|
355
|
+
async function authorize(source, principal, authority, targets) {
|
|
356
|
+
if (typeof authority?.authorizeTarget !== "function") invalid("host target authorization is required.");
|
|
357
|
+
for (const item of targets) {
|
|
358
|
+
if (!ObjectRegistry.getClassByQualifiedName(item.costObjectType)) invalid("unknown cost-object type.");
|
|
359
|
+
const object = await (await ObjectRegistry.getCollection(item.costObjectType, { db: source.db })).get({ id: item.costObjectId }, { cache: false });
|
|
360
|
+
if (!object || object.tenantId !== principal.tenantId) invalid("target must exist in the active tenant.");
|
|
361
|
+
await authority.authorizeTarget(principal, Object.freeze({ ...item }), source.db);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function identity(source, revision, row) {
|
|
365
|
+
if (!row.id || !source.id || !revision.id) invalid("persisted allocation identities required.");
|
|
366
|
+
const { ExpenseCollection } = await Promise.resolve().then(() => ExpenseCollection_exports);
|
|
367
|
+
const hasRefunds = (await (await ExpenseCollection.create({ db: source.db })).list({
|
|
368
|
+
where: {
|
|
369
|
+
refundOfId: source.id,
|
|
370
|
+
tenantId: source.tenantId
|
|
371
|
+
},
|
|
372
|
+
select: ["id"],
|
|
373
|
+
limit: 1
|
|
374
|
+
})).length > 0;
|
|
375
|
+
return {
|
|
376
|
+
portionId: row.id,
|
|
377
|
+
sourceExpenseId: source.id,
|
|
378
|
+
revisionId: revision.id,
|
|
379
|
+
sourceFingerprint: revision.sourceFingerprint,
|
|
380
|
+
incurredOn: revision.incurredOn,
|
|
381
|
+
paidBy: revision.paidBy,
|
|
382
|
+
reimbursable: revision.reimbursable,
|
|
383
|
+
hasSupplierTax: revision.hasSupplierTax,
|
|
384
|
+
hasRefunds,
|
|
385
|
+
costBasisMinor: !hasRefunds && revision.basisEqualsGross ? row.amountMinor : null,
|
|
386
|
+
currency: revision.currency,
|
|
387
|
+
active: source.activeAllocationRevisionId === revision.id,
|
|
388
|
+
portionKey: row.portionKey,
|
|
389
|
+
costObjectType: row.costObjectType,
|
|
390
|
+
costObjectId: row.costObjectId,
|
|
391
|
+
costCodeRef: row.costCodeRef,
|
|
392
|
+
amountMinor: row.amountMinor
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
async function activeReservations(source) {
|
|
396
|
+
return (await source.db.query(`SELECT r.id, r.portion_id FROM expense_allocation_billings r
|
|
397
|
+
WHERE r.source_expense_id = ? AND r.tenant_id = ? AND r.kind = 'reserve'
|
|
398
|
+
AND NOT EXISTS (SELECT 1 FROM expense_allocation_billings x
|
|
399
|
+
WHERE x.related_reservation_id = r.id AND x.tenant_id = r.tenant_id AND x.kind = 'release')`, source.id, source.tenantId)).rows.map((row) => ({
|
|
400
|
+
id: row.id,
|
|
401
|
+
portionId: row.portion_id
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
async function latestRevision(source) {
|
|
405
|
+
const result = await source.db.query(`SELECT r.id FROM expense_allocation_revisions r
|
|
406
|
+
WHERE r.source_expense_id = ? AND r.tenant_id = ? AND NOT EXISTS
|
|
407
|
+
(SELECT 1 FROM expense_allocation_revisions n WHERE n.previous_revision_id = r.id AND n.source_expense_id = r.source_expense_id AND n.tenant_id = r.tenant_id)
|
|
408
|
+
LIMIT 2`, source.id, source.tenantId);
|
|
409
|
+
if (result.rows.length > 1) invalid("allocation audit chain is ambiguous.");
|
|
410
|
+
const id = result.rows[0]?.id;
|
|
411
|
+
return id ? (await Revisions.create({ db: source.db })).get({
|
|
412
|
+
id,
|
|
413
|
+
sourceExpenseId: source.id,
|
|
414
|
+
tenantId: source.tenantId
|
|
415
|
+
}, { cache: false }) : null;
|
|
416
|
+
}
|
|
417
|
+
async function reviewExpenseAllocation(db, input, authority) {
|
|
418
|
+
if (!input || Object.keys(input).some((key) => ![
|
|
419
|
+
"sourceExpenseId",
|
|
420
|
+
"expectedSourceRevision",
|
|
421
|
+
"requestId",
|
|
422
|
+
"portions",
|
|
423
|
+
"reviewEvidence"
|
|
424
|
+
].includes(key))) invalid("unknown input field.");
|
|
425
|
+
text(input.sourceExpenseId);
|
|
426
|
+
text(input.requestId);
|
|
427
|
+
text(input.reviewEvidence);
|
|
428
|
+
if (instantMs(input.expectedSourceRevision) === null || !Array.isArray(input.portions) || input.portions.length < 1 || input.portions.length > 1e3) invalid("source revision and 1–1000 portions required.");
|
|
429
|
+
const proposed = input.portions.map(target).sort((a, b) => a.portionKey < b.portionKey ? -1 : a.portionKey > b.portionKey ? 1 : 0);
|
|
430
|
+
if (new Set(proposed.map((row) => row.portionKey)).size !== proposed.length) invalid("duplicate portion key.");
|
|
431
|
+
const fingerprint = digest({
|
|
432
|
+
sourceExpenseId: input.sourceExpenseId,
|
|
433
|
+
expectedSourceRevision: new Date(input.expectedSourceRevision).toISOString(),
|
|
434
|
+
portions: proposed,
|
|
435
|
+
reviewEvidence: input.reviewEvidence
|
|
436
|
+
});
|
|
437
|
+
const principal = await authorizeExpenseAction("expenses", "recordReview", { db }, { allowSuperAdminBypass: false });
|
|
438
|
+
return withActionPrincipal(principal, async () => {
|
|
439
|
+
return (await sourceFor(db, input.sourceExpenseId, principal.tenantId)).withTransaction(async (source) => {
|
|
440
|
+
await lock(source);
|
|
441
|
+
if (typeof authority?.authorizeSource !== "function") invalid("host original-source authorization is required.");
|
|
442
|
+
await authority.authorizeSource(principal, source.id, source.db, { action: "reviewAllocation" });
|
|
443
|
+
const prior = await (await Revisions.create({ db: source.db })).get({
|
|
444
|
+
sourceExpenseId: source.id,
|
|
445
|
+
tenantId: principal.tenantId,
|
|
446
|
+
requestId: input.requestId
|
|
447
|
+
}, { cache: false });
|
|
448
|
+
if (prior) {
|
|
449
|
+
if (prior.kind !== "allocation" || prior.requestFingerprint !== fingerprint) invalid("idempotency key payload conflict.");
|
|
450
|
+
const children2 = await portionsFor(source, prior.id);
|
|
451
|
+
await authorize(source, principal, authority, children2);
|
|
452
|
+
return {
|
|
453
|
+
revision: prior,
|
|
454
|
+
portions: await Promise.all(children2.map((row) => identity(source, prior, row)))
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (instantMs(source.updated_at) !== instantMs(input.expectedSourceRevision)) invalid("stale source revision.");
|
|
458
|
+
if (source.reviewStatus !== "reviewed" || source.duplicateOfId || source.amount <= 0 || source.refundOfId) invalid("positive reviewed original required.");
|
|
459
|
+
if (proposed.reduce((sum, row) => sum + BigInt(row.amountMinor), 0n) !== BigInt(source.amount)) invalid("portions must exactly equal source amount; no remainder.");
|
|
460
|
+
const old = source.activeAllocationRevisionId ? await portionsFor(source, source.activeAllocationRevisionId) : [];
|
|
461
|
+
await authorize(source, principal, authority, [...old, ...proposed]);
|
|
462
|
+
if ((await activeReservations(source)).length) invalid("release draft reservations before correction; issued portions require explicit credit/reversal.");
|
|
463
|
+
await source.claimRevision(input.expectedSourceRevision);
|
|
464
|
+
const previous = await latestRevision(source);
|
|
465
|
+
if (source.activeAllocationRevisionId ? previous?.id !== source.activeAllocationRevisionId || previous.kind !== "allocation" : previous && previous.kind !== "withdrawal") invalid("allocation audit chain disagrees with its active head.");
|
|
466
|
+
const sourceNonce = previous?.sourceFingerprintNonce || randomBytes(32).toString("hex");
|
|
467
|
+
const revision = await insertAllocation(new ExpenseAllocationRevision({
|
|
468
|
+
db: source.db,
|
|
469
|
+
tenantId: principal.tenantId,
|
|
470
|
+
sourceExpenseId: source.id,
|
|
471
|
+
previousRevisionId: previous?.id ?? null,
|
|
472
|
+
kind: "allocation",
|
|
473
|
+
sourceCostObjectType: source.costObjectType,
|
|
474
|
+
sourceCostObjectId: source.costObjectId,
|
|
475
|
+
incurredOn: source.incurredOn,
|
|
476
|
+
paidBy: source.paidBy,
|
|
477
|
+
reimbursable: source.reimbursable,
|
|
478
|
+
hasSupplierTax: source.getFinancialBreakdown()?.taxes.some((tax) => tax.amount !== 0) ?? null,
|
|
479
|
+
basisEqualsGross: source.getCostBasis()?.costBasis === source.amount,
|
|
480
|
+
requestId: input.requestId,
|
|
481
|
+
requestFingerprint: fingerprint,
|
|
482
|
+
sourceFingerprintNonce: sourceNonce,
|
|
483
|
+
sourceFingerprint: digest({
|
|
484
|
+
nonce: sourceNonce,
|
|
485
|
+
id: source.id,
|
|
486
|
+
amount: source.amount,
|
|
487
|
+
currency: source.currency,
|
|
488
|
+
incurredOn: source.incurredOn,
|
|
489
|
+
financialBreakdown: source.financialBreakdown,
|
|
490
|
+
vendorId: source.vendorId,
|
|
491
|
+
paidBy: source.paidBy,
|
|
492
|
+
reimbursable: source.reimbursable,
|
|
493
|
+
category: source.category,
|
|
494
|
+
costObjectType: source.costObjectType,
|
|
495
|
+
costObjectId: source.costObjectId,
|
|
496
|
+
commitmentId: source.commitmentId,
|
|
497
|
+
commitmentLineId: source.commitmentLineId,
|
|
498
|
+
paidByProfileId: source.paidByProfileId,
|
|
499
|
+
paidByName: source.paidByName
|
|
500
|
+
}),
|
|
501
|
+
sourceRevision: new Date(input.expectedSourceRevision).toISOString(),
|
|
502
|
+
amountMinor: source.amount,
|
|
503
|
+
currency: source.currency,
|
|
504
|
+
reviewedByProfileId: principal.profileId,
|
|
505
|
+
reviewedByUserId: principal.userId,
|
|
506
|
+
reviewEvidence: input.reviewEvidence
|
|
507
|
+
}));
|
|
508
|
+
const children = [];
|
|
509
|
+
for (const item of proposed) children.push(await insertAllocation(new ExpenseAllocationPortion({
|
|
510
|
+
db: source.db,
|
|
511
|
+
...item,
|
|
512
|
+
tenantId: principal.tenantId,
|
|
513
|
+
sourceExpenseId: source.id,
|
|
514
|
+
revisionId: revision.id,
|
|
515
|
+
currency: source.currency
|
|
516
|
+
})));
|
|
517
|
+
source.activeAllocationRevisionId = revision.id;
|
|
518
|
+
allocationHeads.set(source, revision.id);
|
|
519
|
+
try {
|
|
520
|
+
await source.save();
|
|
521
|
+
} finally {
|
|
522
|
+
allocationHeads.delete(source);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
revision,
|
|
526
|
+
portions: await Promise.all(children.map((row) => identity(source, revision, row)))
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
async function authorizeUnsplit(source, principal, revision, authority) {
|
|
532
|
+
if (typeof authority.authorizeUnsplitTarget !== "function") invalid("host restored-unsplit target authorization is required.");
|
|
533
|
+
const costObjectType = revision.sourceCostObjectType;
|
|
534
|
+
const costObjectId = revision.sourceCostObjectId;
|
|
535
|
+
if (Boolean(costObjectType) !== Boolean(costObjectId)) invalid("restored unsplit destination is malformed.");
|
|
536
|
+
if (costObjectType) {
|
|
537
|
+
if (!ObjectRegistry.getClassByQualifiedName(costObjectType)) invalid("restored unsplit type is unknown.");
|
|
538
|
+
const object = await (await ObjectRegistry.getCollection(costObjectType, { db: source.db })).get({ id: costObjectId }, { cache: false });
|
|
539
|
+
if (!object || object.tenantId !== principal.tenantId) invalid("restored unsplit destination must exist in the active tenant.");
|
|
540
|
+
}
|
|
541
|
+
await authority.authorizeUnsplitTarget(principal, Object.freeze({
|
|
542
|
+
sourceExpenseId: source.id,
|
|
543
|
+
costObjectType,
|
|
544
|
+
costObjectId
|
|
545
|
+
}), source.db);
|
|
546
|
+
}
|
|
547
|
+
async function withdrawExpenseAllocation(db, input, authority) {
|
|
548
|
+
if (!input || Object.keys(input).some((key) => ![
|
|
549
|
+
"sourceExpenseId",
|
|
550
|
+
"expectedActiveRevisionId",
|
|
551
|
+
"expectedSourceRevision",
|
|
552
|
+
"requestId",
|
|
553
|
+
"reviewEvidence"
|
|
554
|
+
].includes(key))) invalid("unknown withdrawal input field.");
|
|
555
|
+
text(input.sourceExpenseId);
|
|
556
|
+
text(input.expectedActiveRevisionId);
|
|
557
|
+
text(input.requestId);
|
|
558
|
+
text(input.reviewEvidence);
|
|
559
|
+
if (instantMs(input.expectedSourceRevision) === null) invalid("withdrawal source revision is required.");
|
|
560
|
+
const fingerprint = digest({
|
|
561
|
+
action: "withdrawAllocation",
|
|
562
|
+
sourceExpenseId: input.sourceExpenseId,
|
|
563
|
+
expectedActiveRevisionId: input.expectedActiveRevisionId,
|
|
564
|
+
expectedSourceRevision: new Date(input.expectedSourceRevision).toISOString(),
|
|
565
|
+
reviewEvidence: input.reviewEvidence
|
|
566
|
+
});
|
|
567
|
+
const principal = await billingPrincipal(db);
|
|
568
|
+
return withActionPrincipal(principal, async () => {
|
|
569
|
+
return (await sourceFor(db, input.sourceExpenseId, principal.tenantId)).withTransaction(async (source) => {
|
|
570
|
+
await lock(source);
|
|
571
|
+
if (typeof authority?.authorizeSource !== "function") invalid("host original-source authorization is required.");
|
|
572
|
+
await authority.authorizeSource(principal, source.id, source.db, { action: "withdrawAllocation" });
|
|
573
|
+
const revisions = await Revisions.create({ db: source.db });
|
|
574
|
+
const replay = await revisions.get({
|
|
575
|
+
sourceExpenseId: source.id,
|
|
576
|
+
tenantId: principal.tenantId,
|
|
577
|
+
requestId: input.requestId
|
|
578
|
+
}, { cache: false });
|
|
579
|
+
if (replay && (replay.kind !== "withdrawal" || replay.requestFingerprint !== fingerprint)) invalid("withdrawal idempotency key payload conflict.");
|
|
580
|
+
const targetRevisionId = replay?.previousRevisionId ?? input.expectedActiveRevisionId;
|
|
581
|
+
const previous = await revisions.get({
|
|
582
|
+
id: targetRevisionId,
|
|
583
|
+
sourceExpenseId: source.id,
|
|
584
|
+
tenantId: principal.tenantId
|
|
585
|
+
}, { cache: false });
|
|
586
|
+
if (!previous || previous.kind !== "allocation") invalid("withdrawal requires the original reviewed allocation revision.");
|
|
587
|
+
const old = await portionsFor(source, previous.id);
|
|
588
|
+
await authorize(source, principal, authority, old);
|
|
589
|
+
await authorizeUnsplit(source, principal, previous, authority);
|
|
590
|
+
if (replay) return {
|
|
591
|
+
revision: replay,
|
|
592
|
+
withdrawnRevisionId: previous.id
|
|
593
|
+
};
|
|
594
|
+
if (source.activeAllocationRevisionId !== input.expectedActiveRevisionId || instantMs(source.updated_at) !== instantMs(input.expectedSourceRevision)) invalid("stale active allocation or source revision.");
|
|
595
|
+
if ((await latestRevision(source))?.id !== previous.id) invalid("withdrawal audit chain is not current.");
|
|
596
|
+
if ((await activeReservations(source)).length) invalid("reserved, bound or issued portions cannot be withdrawn; release authorized draft claims explicitly.");
|
|
597
|
+
await source.claimRevision(input.expectedSourceRevision);
|
|
598
|
+
const revision = await insertAllocation(new ExpenseAllocationRevision({
|
|
599
|
+
db: source.db,
|
|
600
|
+
tenantId: principal.tenantId,
|
|
601
|
+
sourceExpenseId: source.id,
|
|
602
|
+
previousRevisionId: previous.id,
|
|
603
|
+
kind: "withdrawal",
|
|
604
|
+
requestId: input.requestId,
|
|
605
|
+
requestFingerprint: fingerprint,
|
|
606
|
+
sourceFingerprint: previous.sourceFingerprint,
|
|
607
|
+
sourceFingerprintNonce: previous.sourceFingerprintNonce,
|
|
608
|
+
sourceRevision: new Date(input.expectedSourceRevision).toISOString(),
|
|
609
|
+
amountMinor: previous.amountMinor,
|
|
610
|
+
currency: previous.currency,
|
|
611
|
+
sourceCostObjectType: previous.sourceCostObjectType,
|
|
612
|
+
sourceCostObjectId: previous.sourceCostObjectId,
|
|
613
|
+
incurredOn: previous.incurredOn,
|
|
614
|
+
paidBy: previous.paidBy,
|
|
615
|
+
reimbursable: previous.reimbursable,
|
|
616
|
+
hasSupplierTax: previous.hasSupplierTax,
|
|
617
|
+
basisEqualsGross: previous.basisEqualsGross,
|
|
618
|
+
reviewedByProfileId: principal.profileId,
|
|
619
|
+
reviewedByUserId: principal.userId,
|
|
620
|
+
reviewEvidence: input.reviewEvidence
|
|
621
|
+
}));
|
|
622
|
+
source.activeAllocationRevisionId = null;
|
|
623
|
+
allocationHeads.set(source, null);
|
|
624
|
+
try {
|
|
625
|
+
await source.save();
|
|
626
|
+
} finally {
|
|
627
|
+
allocationHeads.delete(source);
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
revision,
|
|
631
|
+
withdrawnRevisionId: previous.id
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async function resolveExpenseAllocationPortion(db, portionId, authority) {
|
|
637
|
+
text(portionId);
|
|
638
|
+
const principal = await authorizeExpenseAction("expenses", "get", { db }, { allowSuperAdminBypass: false });
|
|
639
|
+
return withActionPrincipal(principal, async () => {
|
|
640
|
+
const row = await (await Portions.create({ db })).get({
|
|
641
|
+
id: portionId,
|
|
642
|
+
tenantId: principal.tenantId
|
|
643
|
+
}, { cache: false });
|
|
644
|
+
if (!row) invalid("owned portion required.");
|
|
645
|
+
const source = await sourceFor(db, row.sourceExpenseId, principal.tenantId);
|
|
646
|
+
if (source.activeAllocationRevisionId === row.revisionId && (source.reviewStatus !== "reviewed" || source.duplicateOfId)) invalid("source review is no longer usable.");
|
|
647
|
+
const revision = await (await Revisions.create({ db })).get({
|
|
648
|
+
id: row.revisionId,
|
|
649
|
+
tenantId: principal.tenantId
|
|
650
|
+
}, { cache: false });
|
|
651
|
+
if (!revision || revision.sourceExpenseId !== source.id) invalid("owned revision required.");
|
|
652
|
+
await authorize(source, principal, authority, [row]);
|
|
653
|
+
return identity(source, revision, row);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async function allocationTargetAmount(source, filter) {
|
|
657
|
+
if (!source.activeAllocationRevisionId) {
|
|
658
|
+
if (Object.keys(filter).some((key) => key.startsWith("costCodeRef"))) return null;
|
|
659
|
+
const { ExpenseCollection } = await Promise.resolve().then(() => ExpenseCollection_exports);
|
|
660
|
+
return await (await ExpenseCollection.create({ db: source.db })).get({
|
|
661
|
+
...filter,
|
|
662
|
+
id: source.id,
|
|
663
|
+
tenantId: source.tenantId
|
|
664
|
+
}, { cache: false }) ? source.amount : null;
|
|
665
|
+
}
|
|
666
|
+
const rows = await (await Portions.create({ db: source.db })).list({
|
|
667
|
+
where: {
|
|
668
|
+
...filter,
|
|
669
|
+
revisionId: source.activeAllocationRevisionId,
|
|
670
|
+
tenantId: source.tenantId
|
|
671
|
+
},
|
|
672
|
+
limit: 1e3
|
|
673
|
+
});
|
|
674
|
+
if (!rows.length) return null;
|
|
675
|
+
const total = rows.reduce((sum, row) => sum + BigInt(row.amountMinor), 0n);
|
|
676
|
+
if (total > BigInt(Number.MAX_SAFE_INTEGER)) invalid("target sum exceeds safe range.");
|
|
677
|
+
return Number(total);
|
|
678
|
+
}
|
|
679
|
+
var EXPENSE_ALLOCATION_INVOICE_SOURCE = "billable:@happyvertical/smrt-expenses:ExpenseAllocationPortion";
|
|
680
|
+
async function reservationFor(db, id, tenantId) {
|
|
681
|
+
const reservation = await (await Billings.create({ db })).get({
|
|
682
|
+
id,
|
|
683
|
+
tenantId
|
|
684
|
+
}, { cache: false });
|
|
685
|
+
if (!reservation || reservation.kind !== "reserve" || reservation.tenantId !== tenantId) invalid("owned reservation required.");
|
|
686
|
+
return reservation;
|
|
687
|
+
}
|
|
688
|
+
async function bindingFor(source, reservationId) {
|
|
689
|
+
return (await Billings.create({ db: source.db })).get({
|
|
690
|
+
sourceExpenseId: source.id,
|
|
691
|
+
relatedReservationId: reservationId,
|
|
692
|
+
kind: "bind",
|
|
693
|
+
tenantId: source.tenantId
|
|
694
|
+
}, { cache: false });
|
|
695
|
+
}
|
|
696
|
+
async function billingPrincipal(db) {
|
|
697
|
+
return authorizeExpenseAction("expenses", "recordReview", { db }, { allowSuperAdminBypass: false });
|
|
698
|
+
}
|
|
699
|
+
async function billingPortion(source, id, principal, authority, action) {
|
|
700
|
+
const portion = await (await Portions.create({ db: source.db })).get({
|
|
701
|
+
id,
|
|
702
|
+
sourceExpenseId: source.id,
|
|
703
|
+
tenantId: principal.tenantId
|
|
704
|
+
}, { cache: false });
|
|
705
|
+
if (!portion) invalid("owned source portion required.");
|
|
706
|
+
await authorize(source, principal, authority, [portion]);
|
|
707
|
+
if (typeof authority.authorizeSource !== "function") invalid("host source authority required.");
|
|
708
|
+
await authority.authorizeSource(principal, source.id, source.db, {
|
|
709
|
+
action,
|
|
710
|
+
portion: {
|
|
711
|
+
portionId: portion.id,
|
|
712
|
+
portionKey: portion.portionKey,
|
|
713
|
+
costObjectType: portion.costObjectType,
|
|
714
|
+
costObjectId: portion.costObjectId,
|
|
715
|
+
costCodeRef: portion.costCodeRef,
|
|
716
|
+
amountMinor: portion.amountMinor
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
return portion;
|
|
720
|
+
}
|
|
721
|
+
async function reserveExpenseAllocationPortion(db, input, authority) {
|
|
722
|
+
text(input.portionId);
|
|
723
|
+
text(input.requestId);
|
|
724
|
+
if (Object.keys(input).some((key) => ![
|
|
725
|
+
"portionId",
|
|
726
|
+
"requestId",
|
|
727
|
+
"draftType",
|
|
728
|
+
"draftId",
|
|
729
|
+
"invoiceId",
|
|
730
|
+
"invoiceLineId"
|
|
731
|
+
].includes(key))) invalid("unknown reservation field.");
|
|
732
|
+
const principal = await billingPrincipal(db);
|
|
733
|
+
return withActionPrincipal(principal, async () => {
|
|
734
|
+
const row = await (await Portions.create({ db })).get({
|
|
735
|
+
id: input.portionId,
|
|
736
|
+
tenantId: principal.tenantId
|
|
737
|
+
}, { cache: false });
|
|
738
|
+
if (!row) invalid("owned portion required.");
|
|
739
|
+
return (await sourceFor(db, row.sourceExpenseId, principal.tenantId)).withTransaction(async (source) => {
|
|
740
|
+
await lock(source);
|
|
741
|
+
const portion = await billingPortion(source, row.id, principal, authority, "reserveAllocation");
|
|
742
|
+
const invoiceBound = Boolean(input.invoiceId || input.invoiceLineId);
|
|
743
|
+
if (invoiceBound) {
|
|
744
|
+
text(input.invoiceId);
|
|
745
|
+
text(input.invoiceLineId);
|
|
746
|
+
if (typeof authority.authorizeInvoice !== "function") invalid("host invoice authority required.");
|
|
747
|
+
await authority.authorizeInvoice(principal, portion, input.invoiceId, source.db);
|
|
748
|
+
} else {
|
|
749
|
+
text(input.draftType);
|
|
750
|
+
text(input.draftId);
|
|
751
|
+
if (typeof authority.authorizeDraft !== "function") invalid("host draft authority required.");
|
|
752
|
+
await authority.authorizeDraft(principal, portion, {
|
|
753
|
+
type: input.draftType,
|
|
754
|
+
id: input.draftId
|
|
755
|
+
}, source.db);
|
|
756
|
+
}
|
|
757
|
+
const fingerprint = digest({
|
|
758
|
+
portionId: input.portionId,
|
|
759
|
+
requestId: input.requestId,
|
|
760
|
+
draftType: input.draftType ?? null,
|
|
761
|
+
draftId: input.draftId ?? null,
|
|
762
|
+
invoiceId: input.invoiceId ?? null,
|
|
763
|
+
invoiceLineId: input.invoiceLineId ?? null
|
|
764
|
+
});
|
|
765
|
+
const prior = await (await Billings.create({ db: source.db })).get({
|
|
766
|
+
sourceExpenseId: source.id,
|
|
767
|
+
tenantId: principal.tenantId,
|
|
768
|
+
requestId: input.requestId
|
|
769
|
+
}, { cache: false });
|
|
770
|
+
if (prior) {
|
|
771
|
+
if (prior.requestFingerprint !== fingerprint || prior.kind !== "reserve") invalid("reservation idempotency conflict.");
|
|
772
|
+
return prior;
|
|
773
|
+
}
|
|
774
|
+
if ((await source.getRefundPosition()).refundIds.length) invalid("refunded source needs explicit billing reversal policy.");
|
|
775
|
+
if (source.activeAllocationRevisionId !== portion.revisionId) invalid("superseded portion cannot be reserved.");
|
|
776
|
+
if ((await activeReservations(source)).some((item) => item.portionId === portion.id)) invalid("portion capacity already reserved.");
|
|
777
|
+
if (!source.updated_at) invalid("source revision required.");
|
|
778
|
+
await source.claimRevision(source.updated_at);
|
|
779
|
+
const reservation = await insertAllocation(new ExpenseAllocationBilling({
|
|
780
|
+
db: source.db,
|
|
781
|
+
tenantId: principal.tenantId,
|
|
782
|
+
sourceExpenseId: source.id,
|
|
783
|
+
portionId: portion.id,
|
|
784
|
+
requestId: input.requestId,
|
|
785
|
+
requestFingerprint: fingerprint,
|
|
786
|
+
kind: "reserve",
|
|
787
|
+
draftType: input.draftType ?? "",
|
|
788
|
+
draftId: input.draftId ?? ""
|
|
789
|
+
}));
|
|
790
|
+
if (invoiceBound) await bindReservation(source, reservation, portion, {
|
|
791
|
+
invoiceId: input.invoiceId,
|
|
792
|
+
invoiceLineId: input.invoiceLineId,
|
|
793
|
+
requestId: `${input.requestId}:bind`
|
|
794
|
+
}, principal, authority);
|
|
795
|
+
return reservation;
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
async function bindReservation(source, reservation, portion, input, principal, authority) {
|
|
800
|
+
if (typeof authority.authorizeInvoice !== "function") invalid("host invoice authority required.");
|
|
801
|
+
await authority.authorizeInvoice(principal, portion, input.invoiceId, source.db);
|
|
802
|
+
const billings = await Billings.create({ db: source.db });
|
|
803
|
+
const fingerprint = digest({
|
|
804
|
+
reservationId: reservation.id,
|
|
805
|
+
...input
|
|
806
|
+
});
|
|
807
|
+
const previous = await bindingFor(source, reservation.id);
|
|
808
|
+
if (previous) {
|
|
809
|
+
if (previous.requestId !== input.requestId || previous.requestFingerprint !== fingerprint) invalid("reservation already bound or payload changed.");
|
|
810
|
+
return previous;
|
|
811
|
+
}
|
|
812
|
+
if ((await source.getRefundPosition()).refundIds.length) invalid("refunded source needs explicit billing reversal policy.");
|
|
813
|
+
if (!(await activeReservations(source)).some((item) => item.id === reservation.id)) invalid("released reservation cannot bind.");
|
|
814
|
+
if (await billings.get({
|
|
815
|
+
sourceExpenseId: source.id,
|
|
816
|
+
tenantId: principal.tenantId,
|
|
817
|
+
requestId: input.requestId
|
|
818
|
+
}, { cache: false })) invalid("request identity already used.");
|
|
819
|
+
const invoice = await (await InvoiceCollection.create({ db: source.db })).get({
|
|
820
|
+
id: input.invoiceId,
|
|
821
|
+
tenantId: principal.tenantId
|
|
822
|
+
}, { cache: false });
|
|
823
|
+
const line = await (await InvoiceLineItemCollection.create({ db: source.db })).get({
|
|
824
|
+
id: input.invoiceLineId,
|
|
825
|
+
tenantId: principal.tenantId
|
|
826
|
+
}, { cache: false });
|
|
827
|
+
if (!invoice || invoice.tenantId !== principal.tenantId || invoice.status !== "draft" || invoice.currency !== portion.currency || !line || line.tenantId !== principal.tenantId || line.invoiceId !== invoice.id || line.sourceType !== "billable:@happyvertical/smrt-expenses:ExpenseAllocationPortion" || line.sourceId !== portion.id || !invoice.updated_at || !source.updated_at) invalid("owned same-currency draft and canonical source line required.");
|
|
828
|
+
await invoice.claimRevision(invoice.updated_at);
|
|
829
|
+
await source.claimRevision(source.updated_at);
|
|
830
|
+
const binding = await insertAllocation(new ExpenseAllocationBilling({
|
|
831
|
+
db: source.db,
|
|
832
|
+
tenantId: principal.tenantId,
|
|
833
|
+
sourceExpenseId: source.id,
|
|
834
|
+
portionId: portion.id,
|
|
835
|
+
invoiceId: input.invoiceId,
|
|
836
|
+
invoiceLineId: input.invoiceLineId,
|
|
837
|
+
requestId: input.requestId,
|
|
838
|
+
requestFingerprint: fingerprint,
|
|
839
|
+
kind: "bind",
|
|
840
|
+
relatedReservationId: reservation.id
|
|
841
|
+
}));
|
|
842
|
+
await bindInvoiceLineSource({
|
|
843
|
+
db: source.db,
|
|
844
|
+
tenantId: principal.tenantId,
|
|
845
|
+
lineId: input.invoiceLineId,
|
|
846
|
+
reservationId: reservation.id
|
|
847
|
+
});
|
|
848
|
+
return binding;
|
|
849
|
+
}
|
|
850
|
+
async function bindExpenseAllocationPortion(db, reservationId, input, authority) {
|
|
851
|
+
text(reservationId);
|
|
852
|
+
text(input.invoiceId);
|
|
853
|
+
text(input.invoiceLineId);
|
|
854
|
+
text(input.requestId);
|
|
855
|
+
const principal = await billingPrincipal(db);
|
|
856
|
+
return withActionPrincipal(principal, async () => {
|
|
857
|
+
const reservation = await reservationFor(db, reservationId, principal.tenantId);
|
|
858
|
+
return (await sourceFor(db, reservation.sourceExpenseId, principal.tenantId)).withTransaction(async (source) => {
|
|
859
|
+
await lock(source);
|
|
860
|
+
const portion = await billingPortion(source, reservation.portionId, principal, authority, "bindAllocation");
|
|
861
|
+
return bindReservation(source, reservation, portion, input, principal, authority);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
async function releaseExpenseAllocationPortion(db, reservationId, requestId, authority) {
|
|
866
|
+
text(reservationId);
|
|
867
|
+
text(requestId);
|
|
868
|
+
const principal = await billingPrincipal(db);
|
|
869
|
+
return withActionPrincipal(principal, async () => {
|
|
870
|
+
const reservation = await reservationFor(db, reservationId, principal.tenantId);
|
|
871
|
+
return (await sourceFor(db, reservation.sourceExpenseId, principal.tenantId)).withTransaction(async (source) => {
|
|
872
|
+
await lock(source);
|
|
873
|
+
const portion = await billingPortion(source, reservation.portionId, principal, authority, "releaseAllocation");
|
|
874
|
+
const binding = await bindingFor(source, reservation.id);
|
|
875
|
+
if (binding?.invoiceId) {
|
|
876
|
+
if (typeof authority.authorizeInvoice !== "function") invalid("host invoice authority required.");
|
|
877
|
+
await authority.authorizeInvoice(principal, portion, binding.invoiceId, source.db);
|
|
878
|
+
} else {
|
|
879
|
+
if (typeof authority.authorizeDraft !== "function") invalid("host draft authority required.");
|
|
880
|
+
await authority.authorizeDraft(principal, portion, {
|
|
881
|
+
type: reservation.draftType,
|
|
882
|
+
id: reservation.draftId
|
|
883
|
+
}, source.db);
|
|
884
|
+
}
|
|
885
|
+
const billings = await Billings.create({ db: source.db });
|
|
886
|
+
const fingerprint = digest({
|
|
887
|
+
reservationId,
|
|
888
|
+
requestId
|
|
889
|
+
});
|
|
890
|
+
const prior = await billings.get({
|
|
891
|
+
sourceExpenseId: source.id,
|
|
892
|
+
tenantId: principal.tenantId,
|
|
893
|
+
requestId
|
|
894
|
+
}, { cache: false });
|
|
895
|
+
if (prior) {
|
|
896
|
+
if (prior.requestFingerprint !== fingerprint || prior.kind !== "release") invalid("release idempotency conflict.");
|
|
897
|
+
return prior;
|
|
898
|
+
}
|
|
899
|
+
if (!(await activeReservations(source)).some((item) => item.id === reservationId)) invalid("reservation already released.");
|
|
900
|
+
if (binding?.invoiceId) {
|
|
901
|
+
const invoice = await (await InvoiceCollection.create({ db: source.db })).get({
|
|
902
|
+
id: binding.invoiceId,
|
|
903
|
+
tenantId: principal.tenantId
|
|
904
|
+
}, { cache: false });
|
|
905
|
+
if (!invoice || invoice.status !== "draft" || !invoice.updated_at) invalid("issued reservation cannot be released; use explicit credit/reversal.");
|
|
906
|
+
await invoice.claimRevision(invoice.updated_at);
|
|
907
|
+
}
|
|
908
|
+
if (!source.updated_at) invalid("source revision required.");
|
|
909
|
+
await source.claimRevision(source.updated_at);
|
|
910
|
+
const released = await insertAllocation(new ExpenseAllocationBilling({
|
|
911
|
+
db: source.db,
|
|
912
|
+
tenantId: principal.tenantId,
|
|
913
|
+
sourceExpenseId: source.id,
|
|
914
|
+
portionId: reservation.portionId,
|
|
915
|
+
invoiceId: binding?.invoiceId ?? null,
|
|
916
|
+
invoiceLineId: binding?.invoiceLineId ?? null,
|
|
917
|
+
requestId,
|
|
918
|
+
requestFingerprint: fingerprint,
|
|
919
|
+
kind: "release",
|
|
920
|
+
relatedReservationId: reservationId,
|
|
921
|
+
draftType: reservation.draftType,
|
|
922
|
+
draftId: reservation.draftId
|
|
923
|
+
}));
|
|
924
|
+
if (binding?.invoiceLineId) await unbindInvoiceLineSource({
|
|
925
|
+
db: source.db,
|
|
926
|
+
tenantId: principal.tenantId,
|
|
927
|
+
lineId: binding.invoiceLineId,
|
|
928
|
+
reservationId
|
|
929
|
+
});
|
|
930
|
+
return released;
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
registerInvoiceSourceBinding(EXPENSE_ALLOCATION_INVOICE_SOURCE, async ({ executor, invoice, line, reservationId, phase }) => {
|
|
935
|
+
if (!invoice.tenantId || line.tenantId !== invoice.tenantId) invalid("binding tenant mismatch.");
|
|
936
|
+
const reservation = await reservationFor(executor, reservationId, invoice.tenantId);
|
|
937
|
+
const source = await sourceFor(executor, reservation.sourceExpenseId, invoice.tenantId);
|
|
938
|
+
const binding = await bindingFor(source, reservationId);
|
|
939
|
+
if (!binding || binding.invoiceId !== invoice.id || binding.invoiceLineId !== line.id || line.sourceId !== reservation.portionId || line.sourceType !== "billable:@happyvertical/smrt-expenses:ExpenseAllocationPortion") invalid("binding does not identify this exact invoice line.");
|
|
940
|
+
const active = (await activeReservations(source)).some((item) => item.id === reservationId);
|
|
941
|
+
if (phase === "release" ? active : !active) invalid("binding reservation state is invalid.");
|
|
942
|
+
});
|
|
943
|
+
async function getExpenseAllocationPortionPosition(db, portionId, authority) {
|
|
944
|
+
const identity2 = await resolveExpenseAllocationPortion(db, portionId, authority);
|
|
945
|
+
const principal = await authorizeExpenseAction("expenses", "get", { db }, { allowSuperAdminBypass: false });
|
|
946
|
+
return withActionPrincipal(principal, async () => {
|
|
947
|
+
const source = await sourceFor(db, identity2.sourceExpenseId, principal.tenantId);
|
|
948
|
+
identity2.active = source.activeAllocationRevisionId === identity2.revisionId;
|
|
949
|
+
const active = (await activeReservations(source)).find((row) => row.portionId === portionId);
|
|
950
|
+
if (!active) return {
|
|
951
|
+
identity: identity2,
|
|
952
|
+
available: identity2.active,
|
|
953
|
+
reservation: null
|
|
954
|
+
};
|
|
955
|
+
const reservation = await reservationFor(db, active.id, principal.tenantId);
|
|
956
|
+
const binding = await bindingFor(source, active.id);
|
|
957
|
+
const invoice = binding?.invoiceId ? await (await InvoiceCollection.create({ db })).get({
|
|
958
|
+
id: binding.invoiceId,
|
|
959
|
+
tenantId: principal.tenantId
|
|
960
|
+
}, { cache: false }) : null;
|
|
961
|
+
return {
|
|
962
|
+
identity: identity2,
|
|
963
|
+
available: false,
|
|
964
|
+
reservation: {
|
|
965
|
+
reservationId: reservation.id,
|
|
966
|
+
draftType: reservation.draftType,
|
|
967
|
+
draftId: reservation.draftId,
|
|
968
|
+
invoiceId: binding?.invoiceId ?? null,
|
|
969
|
+
invoiceLineId: binding?.invoiceLineId ?? null,
|
|
970
|
+
invoiceStatus: invoice?.status ?? null,
|
|
971
|
+
issued: Boolean(binding && (!invoice || invoice.status !== "draft"))
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
});
|
|
975
|
+
}
|
|
9
976
|
//#endregion
|
|
10
977
|
//#region src/collections/ExpenseCollection.ts
|
|
11
978
|
var ExpenseCollection_exports = /* @__PURE__ */ __exportAll({ ExpenseCollection: () => ExpenseCollection });
|
|
@@ -88,19 +1055,19 @@ var ExpenseCollection = class extends SmrtCollection {
|
|
|
88
1055
|
return source.getRefundPosition();
|
|
89
1056
|
}
|
|
90
1057
|
async record(options) {
|
|
91
|
-
const { recordExpenseAction } = await import("./chunks/expense-actions-
|
|
1058
|
+
const { recordExpenseAction } = await import("./chunks/expense-actions-DszuHacx.js");
|
|
92
1059
|
return recordExpenseAction(this, options);
|
|
93
1060
|
}
|
|
94
1061
|
async attachReceipt(options) {
|
|
95
|
-
const { attachExpenseReceiptAction } = await import("./chunks/expense-actions-
|
|
1062
|
+
const { attachExpenseReceiptAction } = await import("./chunks/expense-actions-DszuHacx.js");
|
|
96
1063
|
return attachExpenseReceiptAction(this, options);
|
|
97
1064
|
}
|
|
98
1065
|
async recordReview(options) {
|
|
99
|
-
const { reviewExpenseAction } = await import("./chunks/expense-actions-
|
|
1066
|
+
const { reviewExpenseAction } = await import("./chunks/expense-actions-DszuHacx.js");
|
|
100
1067
|
return reviewExpenseAction(this, options);
|
|
101
1068
|
}
|
|
102
1069
|
async recordReimbursement(options) {
|
|
103
|
-
const { markExpenseReimbursedAction } = await import("./chunks/expense-actions-
|
|
1070
|
+
const { markExpenseReimbursedAction } = await import("./chunks/expense-actions-DszuHacx.js");
|
|
104
1071
|
return markExpenseReimbursedAction(this, options);
|
|
105
1072
|
}
|
|
106
1073
|
/**
|
|
@@ -135,23 +1102,35 @@ var ExpenseCollection = class extends SmrtCollection {
|
|
|
135
1102
|
async totalsByCurrency(options = {}) {
|
|
136
1103
|
const statuses = options.reviewStatuses ?? ["reviewed"];
|
|
137
1104
|
if (statuses.length === 0) return {};
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1105
|
+
const where = { ...options.where ?? {} };
|
|
1106
|
+
const targetFilter = {};
|
|
1107
|
+
for (const key of Object.keys(where)) if (/^cost(ObjectType|ObjectId|CodeRef)/.test(key)) {
|
|
1108
|
+
targetFilter[key] = where[key];
|
|
1109
|
+
delete where[key];
|
|
1110
|
+
}
|
|
1111
|
+
const rows = await this.list({ where: {
|
|
1112
|
+
...where,
|
|
1113
|
+
reviewStatus: [...statuses],
|
|
1114
|
+
duplicateOfId: null
|
|
1115
|
+
} });
|
|
146
1116
|
const totals = {};
|
|
147
1117
|
const seen = /* @__PURE__ */ new Set();
|
|
148
1118
|
for (const row of rows) {
|
|
149
|
-
if (seen.has(row.id)) continue;
|
|
1119
|
+
if (!row.id || seen.has(row.id)) continue;
|
|
150
1120
|
seen.add(row.id);
|
|
151
|
-
|
|
1121
|
+
const amount = Object.keys(targetFilter).length ? await allocationTargetAmount(row, targetFilter) : toMinorUnits(row.amount);
|
|
1122
|
+
if (amount !== null) addTo(totals, row.currency, amount);
|
|
152
1123
|
}
|
|
153
1124
|
return totals;
|
|
154
1125
|
}
|
|
1126
|
+
/** Review a complete allocation through ambient reviewer and mandatory host authority. */
|
|
1127
|
+
async reviewAllocation(input, authority) {
|
|
1128
|
+
return reviewExpenseAllocation(this.db, input, authority);
|
|
1129
|
+
}
|
|
1130
|
+
/** Audit withdrawal of entirely unreserved portions before correcting the original source. */
|
|
1131
|
+
async withdrawAllocation(input, authority) {
|
|
1132
|
+
return withdrawExpenseAllocation(this.db, input, authority);
|
|
1133
|
+
}
|
|
155
1134
|
/**
|
|
156
1135
|
* Stored supplier-tax facts for reporting. Only reviewed non-duplicate
|
|
157
1136
|
* expenses are returned; callers must retain a null breakdown as unknown.
|
|
@@ -356,6 +1335,7 @@ var ExpensesRecipe = class extends SmrtRecipe {
|
|
|
356
1335
|
help: "The job, project or client the cost belongs to."
|
|
357
1336
|
},
|
|
358
1337
|
costObjectType: { visibility: "hidden" },
|
|
1338
|
+
activeAllocationRevisionId: { visibility: "hidden" },
|
|
359
1339
|
financialBreakdown: { visibility: "hidden" },
|
|
360
1340
|
supplierPayableJson: { visibility: "hidden" },
|
|
361
1341
|
refundRecipient: { visibility: "hidden" },
|
|
@@ -375,6 +1355,6 @@ var ExpensesRecipe = class extends SmrtRecipe {
|
|
|
375
1355
|
};
|
|
376
1356
|
};
|
|
377
1357
|
//#endregion
|
|
378
|
-
export { DuplicateReceiptError, EXPENSE_PAYERS, EXPENSE_REVIEW_STATUSES, Expense, ExpenseActionError, ExpenseCollection, ExpenseError, ExpenseReceipt, ExpenseReceiptCollection, ExpensesRecipe, SUGGESTED_EXPENSE_CATEGORIES, computeContentSha256, ExpenseCollection_exports as t };
|
|
1358
|
+
export { DuplicateReceiptError, EXPENSE_ALLOCATION_INVOICE_SOURCE, EXPENSE_ALLOCATION_PERMISSION_DEFINITIONS, EXPENSE_PAYERS, EXPENSE_REVIEW_STATUSES, Expense, ExpenseActionError, ExpenseAllocationBilling, ExpenseAllocationPortion, ExpenseAllocationRevision, ExpenseCollection, ExpenseError, ExpenseReceipt, ExpenseReceiptCollection, ExpensesRecipe, SUGGESTED_EXPENSE_CATEGORIES, bindExpenseAllocationPortion, computeContentSha256, getExpenseAllocationPortionPosition, releaseExpenseAllocationPortion, reserveExpenseAllocationPortion, resolveExpenseAllocationPortion, reviewExpenseAllocation, ExpenseCollection_exports as t, withdrawExpenseAllocation };
|
|
379
1359
|
|
|
380
1360
|
//# sourceMappingURL=index.js.map
|