@happyvertical/smrt-timesheets 0.0.0-stage → 0.53.1

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.
Files changed (65) hide show
  1. package/AGENTS.md +105 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +242 -2
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/index.d.ts +20 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +495 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/manifest.json +1412 -0
  12. package/dist/models/index.d.ts +3 -0
  13. package/dist/models/index.d.ts.map +1 -0
  14. package/dist/models/json.d.ts +6 -0
  15. package/dist/models/json.d.ts.map +1 -0
  16. package/dist/models/service-snapshots.d.ts +66 -0
  17. package/dist/models/service-snapshots.d.ts.map +1 -0
  18. package/dist/models/service-time-entry.d.ts +66 -0
  19. package/dist/models/service-time-entry.d.ts.map +1 -0
  20. package/dist/services/index.d.ts +2 -0
  21. package/dist/services/index.d.ts.map +1 -0
  22. package/dist/services/service-evidence-service.d.ts +67 -0
  23. package/dist/services/service-evidence-service.d.ts.map +1 -0
  24. package/dist/smrt-knowledge.json +1016 -0
  25. package/dist/svelte/components/DurationDisplay.svelte +81 -0
  26. package/dist/svelte/components/DurationDisplay.svelte.d.ts +20 -0
  27. package/dist/svelte/components/DurationDisplay.svelte.d.ts.map +1 -0
  28. package/dist/svelte/components/TimeEntryApprovalQueue.svelte +253 -0
  29. package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts +15 -0
  30. package/dist/svelte/components/TimeEntryApprovalQueue.svelte.d.ts.map +1 -0
  31. package/dist/svelte/components/TimeEntryCard.svelte +304 -0
  32. package/dist/svelte/components/TimeEntryCard.svelte.d.ts +25 -0
  33. package/dist/svelte/components/TimeEntryCard.svelte.d.ts.map +1 -0
  34. package/dist/svelte/components/TimeEntryList.svelte +360 -0
  35. package/dist/svelte/components/TimeEntryList.svelte.d.ts +24 -0
  36. package/dist/svelte/components/TimeEntryList.svelte.d.ts.map +1 -0
  37. package/dist/svelte/components/TimeSummary.svelte +176 -0
  38. package/dist/svelte/components/TimeSummary.svelte.d.ts +30 -0
  39. package/dist/svelte/components/TimeSummary.svelte.d.ts.map +1 -0
  40. package/dist/svelte/components/__tests__/DurationDisplay.test.js +57 -0
  41. package/dist/svelte/components/__tests__/TimeEntryApprovalQueue.test.js +114 -0
  42. package/dist/svelte/components/__tests__/TimeEntryCard.test.js +45 -0
  43. package/dist/svelte/components/__tests__/TimeEntryList.test.js +50 -0
  44. package/dist/svelte/components/__tests__/TimeSummary.test.js +39 -0
  45. package/dist/svelte/components/utils.d.ts +51 -0
  46. package/dist/svelte/components/utils.d.ts.map +1 -0
  47. package/dist/svelte/components/utils.js +58 -0
  48. package/dist/svelte/i18n.d.ts +31 -0
  49. package/dist/svelte/i18n.d.ts.map +1 -0
  50. package/dist/svelte/i18n.js +31 -0
  51. package/dist/svelte/index.d.ts +25 -0
  52. package/dist/svelte/index.d.ts.map +1 -0
  53. package/dist/svelte/index.js +27 -0
  54. package/dist/svelte/types.d.ts +41 -0
  55. package/dist/svelte/types.d.ts.map +1 -0
  56. package/dist/svelte/types.js +28 -0
  57. package/dist/types.d.ts +29 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +16 -0
  60. package/dist/types.js.map +1 -0
  61. package/dist/ui.d.ts +17 -0
  62. package/dist/ui.d.ts.map +1 -0
  63. package/dist/ui.js +68 -0
  64. package/dist/ui.js.map +1 -0
  65. package/package.json +85 -3
package/dist/index.js ADDED
@@ -0,0 +1,495 @@
1
+ import { SERVICE_TIME_ENTRY_STATUS_TRANSITIONS } from "./types.js";
2
+ import { TIMESHEETS_MODULE_META, TIMESHEETS_UI_SLOTS } from "./ui.js";
3
+ import { ObjectRegistry, SmrtCollection, SmrtObject, ValidationError, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
4
+ import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
5
+ //#region src/__smrt-register__.ts
6
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-timesheets\",\"packageVersion\":\"0.53.1\",\"objects\":{\"@happyvertical/smrt-timesheets:ServiceChargeSnapshot\":{\"name\":\"servicechargesnapshot\",\"className\":\"ServiceChargeSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceChargeSnapshot\",\"collection\":\"servicechargesnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-snapshots.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"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}}},\"timeEntryId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ServiceTimeEntry\",\"_meta\":{\"required\":true}},\"amount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"currency\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"pricingVersion\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"strategy\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"rateSnapshot\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"sourceChargeRef\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"createdAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"service_charge_snapshots\",\"previousQualifiedNames\":[\"@happyvertical/smrt-projects:ServiceChargeSnapshot\"],\"conflictColumns\":[\"time_entry_id\"],\"api\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ServiceChargeSnapshot\",\"collectionExportName\":\"ServiceChargeSnapshotCollection\",\"validationRules\":[{\"field\":\"timeEntryId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"}],\"schema\":{\"tableName\":\"service_charge_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_charge_snapshots\\\" (\\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 \\\"time_entry_id\\\" UUID NOT NULL,\\n \\\"amount\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT,\\n \\\"pricing_version\\\" TEXT,\\n \\\"strategy\\\" TEXT,\\n \\\"rate_snapshot\\\" TEXT,\\n \\\"source_charge_ref\\\" TEXT\\n);\",\"columns\":{\"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},\"time_entry_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"amount\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"pricing_version\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"strategy\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rate_snapshot\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"source_charge_ref\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_charge_snapshots_time_entry_id_idx\",\"columns\":[\"time_entry_id\"],\"unique\":true},{\"name\":\"service_charge_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"service_charge_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"1393a1e2\"},\"importPath\":\"@happyvertical/smrt-timesheets\"},\"@happyvertical/smrt-timesheets:ServiceCompensationSnapshot\":{\"name\":\"servicecompensationsnapshot\",\"className\":\"ServiceCompensationSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceCompensationSnapshot\",\"collection\":\"servicecompensationsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-snapshots.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"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}}},\"timeEntryId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ServiceTimeEntry\",\"_meta\":{\"required\":true}},\"amount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"currency\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"termsVersion\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"rateSnapshot\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"createdAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"service_compensation_snapshots\",\"previousQualifiedNames\":[\"@happyvertical/smrt-projects:ServiceCompensationSnapshot\"],\"conflictColumns\":[\"time_entry_id\"],\"api\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"ServiceCompensationSnapshot\",\"collectionExportName\":\"ServiceCompensationSnapshotCollection\",\"validationRules\":[{\"field\":\"timeEntryId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"}],\"schema\":{\"tableName\":\"service_compensation_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_compensation_snapshots\\\" (\\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 \\\"time_entry_id\\\" UUID NOT NULL,\\n \\\"amount\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT,\\n \\\"terms_version\\\" TEXT,\\n \\\"rate_snapshot\\\" TEXT\\n);\",\"columns\":{\"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},\"time_entry_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"amount\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"terms_version\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rate_snapshot\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_compensation_snapshots_time_entry_id_idx\",\"columns\":[\"time_entry_id\"],\"unique\":true},{\"name\":\"service_compensation_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"service_compensation_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cd8670e5\"},\"importPath\":\"@happyvertical/smrt-timesheets\"},\"@happyvertical/smrt-timesheets:ServiceChargeSnapshotCollection\":{\"name\":\"servicechargesnapshotcollection\",\"className\":\"ServiceChargeSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceChargeSnapshotCollection\",\"collection\":\"servicechargesnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-snapshots.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"fields\":{},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"service_charge_snapshots\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ServiceChargeSnapshot\",\"exportName\":\"ServiceChargeSnapshotCollection\",\"collectionExportName\":\"ServiceChargeSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"service_charge_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_charge_snapshots\\\" (\\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 \\\"time_entry_id\\\" UUID NOT NULL,\\n \\\"amount\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT,\\n \\\"pricing_version\\\" TEXT,\\n \\\"strategy\\\" TEXT,\\n \\\"rate_snapshot\\\" TEXT,\\n \\\"source_charge_ref\\\" TEXT\\n);\",\"columns\":{\"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},\"time_entry_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"amount\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"pricing_version\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"strategy\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rate_snapshot\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"source_charge_ref\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_charge_snapshots_time_entry_id_idx\",\"columns\":[\"time_entry_id\"],\"unique\":true},{\"name\":\"service_charge_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"service_charge_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"1393a1e2\"},\"importPath\":\"@happyvertical/smrt-timesheets\"},\"@happyvertical/smrt-timesheets:ServiceCompensationSnapshotCollection\":{\"name\":\"servicecompensationsnapshotcollection\",\"className\":\"ServiceCompensationSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceCompensationSnapshotCollection\",\"collection\":\"servicecompensationsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-snapshots.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"fields\":{},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"service_compensation_snapshots\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ServiceCompensationSnapshot\",\"exportName\":\"ServiceCompensationSnapshotCollection\",\"collectionExportName\":\"ServiceCompensationSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"service_compensation_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_compensation_snapshots\\\" (\\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 \\\"time_entry_id\\\" UUID NOT NULL,\\n \\\"amount\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT,\\n \\\"terms_version\\\" TEXT,\\n \\\"rate_snapshot\\\" TEXT\\n);\",\"columns\":{\"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},\"time_entry_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"amount\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"terms_version\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rate_snapshot\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_compensation_snapshots_time_entry_id_idx\",\"columns\":[\"time_entry_id\"],\"unique\":true},{\"name\":\"service_compensation_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"service_compensation_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cd8670e5\"},\"importPath\":\"@happyvertical/smrt-timesheets\"},\"@happyvertical/smrt-timesheets:ServiceTimeEntry\":{\"name\":\"servicetimeentry\",\"className\":\"ServiceTimeEntry\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceTimeEntry\",\"collection\":\"servicetimeentries\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-time-entry.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"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}}},\"workRefType\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"workRefId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"participantKind\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"participantProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\",\"_meta\":{\"nullable\":true}},\"agentRef\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"source\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"description\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"startedAt\":{\"type\":\"datetime\",\"required\":false},\"endedAt\":{\"type\":\"datetime\",\"required\":false},\"durationSeconds\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"evidence\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"status\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"submittedAt\":{\"type\":\"datetime\",\"required\":false},\"submittedByProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\",\"_meta\":{\"nullable\":true}},\"approvedAt\":{\"type\":\"datetime\",\"required\":false},\"approvedByProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\",\"_meta\":{\"nullable\":true}},\"approvalPath\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"rejectedAt\":{\"type\":\"datetime\",\"required\":false},\"rejectedByProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\",\"_meta\":{\"nullable\":true}},\"rejectionReason\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"correctionOfId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"ServiceTimeEntry\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"_meta\":{}}},\"methods\":{\"getEvidence\":{\"name\":\"getEvidence\",\"async\":false,\"parameters\":[],\"returnType\":\"ServiceEvidence[]\",\"isStatic\":false,\"isPublic\":true},\"setEvidence\":{\"name\":\"setEvidence\",\"async\":false,\"parameters\":[{\"name\":\"value\",\"type\":\"ServiceEvidence[]\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string, unknown>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"value\",\"type\":\"Record<string, unknown>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"durationHours\":{\"name\":\"durationHours\",\"async\":false,\"parameters\":[],\"returnType\":\"number\",\"isStatic\":false,\"isPublic\":true},\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"service_time_entries\",\"previousQualifiedNames\":[\"@happyvertical/smrt-projects:ServiceTimeEntry\"],\"api\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\"]},\"extends\":\"SmrtObject\",\"exportName\":\"ServiceTimeEntry\",\"collectionExportName\":\"ServiceTimeEntryCollection\",\"schema\":{\"tableName\":\"service_time_entries\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_time_entries\\\" (\\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 \\\"work_ref_type\\\" TEXT,\\n \\\"work_ref_id\\\" TEXT,\\n \\\"participant_kind\\\" TEXT,\\n \\\"participant_profile_id\\\" UUID,\\n \\\"agent_ref\\\" TEXT,\\n \\\"source\\\" TEXT,\\n \\\"description\\\" TEXT,\\n \\\"started_at\\\" TIMESTAMP,\\n \\\"ended_at\\\" TIMESTAMP,\\n \\\"duration_seconds\\\" INTEGER DEFAULT 0,\\n \\\"evidence\\\" TEXT,\\n \\\"status\\\" TEXT,\\n \\\"submitted_at\\\" TIMESTAMP,\\n \\\"submitted_by_profile_id\\\" UUID,\\n \\\"approved_at\\\" TIMESTAMP,\\n \\\"approved_by_profile_id\\\" UUID,\\n \\\"approval_path\\\" TEXT,\\n \\\"rejected_at\\\" TIMESTAMP,\\n \\\"rejected_by_profile_id\\\" UUID,\\n \\\"rejection_reason\\\" TEXT,\\n \\\"correction_of_id\\\" UUID,\\n \\\"metadata\\\" TEXT\\n);\",\"columns\":{\"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},\"work_ref_type\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"work_ref_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"participant_kind\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"participant_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"agent_ref\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"source\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"started_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"ended_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"duration_seconds\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"evidence\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"submitted_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"submitted_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"approved_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"approved_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"approval_path\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rejected_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"rejected_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"rejection_reason\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"correction_of_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_time_entries_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"service_time_entries_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"service_time_entries_participant_profile_id_idx\",\"columns\":[\"participant_profile_id\"]},{\"name\":\"service_time_entries_submitted_by_profile_id_idx\",\"columns\":[\"submitted_by_profile_id\"]},{\"name\":\"service_time_entries_approved_by_profile_id_idx\",\"columns\":[\"approved_by_profile_id\"]},{\"name\":\"service_time_entries_rejected_by_profile_id_idx\",\"columns\":[\"rejected_by_profile_id\"]},{\"name\":\"service_time_entries_correction_of_id_idx\",\"columns\":[\"correction_of_id\"]}],\"version\":\"16c0d6c8\"},\"importPath\":\"@happyvertical/smrt-timesheets\"},\"@happyvertical/smrt-timesheets:ServiceTimeEntryCollection\":{\"name\":\"servicetimeentrycollection\",\"className\":\"ServiceTimeEntryCollection\",\"qualifiedName\":\"@happyvertical/smrt-timesheets:ServiceTimeEntryCollection\",\"collection\":\"servicetimeentries\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/timesheets/src/models/service-time-entry.ts\",\"packageName\":\"@happyvertical/smrt-timesheets\",\"fields\":{},\"methods\":{\"forWork\":{\"name\":\"forWork\",\"async\":true,\"parameters\":[{\"name\":\"workRefType\",\"type\":\"string\",\"optional\":false},{\"name\":\"workRefId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ServiceTimeEntry[]>\",\"isStatic\":false,\"isPublic\":true},\"pendingApproval\":{\"name\":\"pendingApproval\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<ServiceTimeEntry[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"service_time_entries\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ServiceTimeEntry\",\"exportName\":\"ServiceTimeEntryCollection\",\"collectionExportName\":\"ServiceTimeEntryCollectionCollection\",\"schema\":{\"tableName\":\"service_time_entries\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"service_time_entries\\\" (\\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 \\\"work_ref_type\\\" TEXT,\\n \\\"work_ref_id\\\" TEXT,\\n \\\"participant_kind\\\" TEXT,\\n \\\"participant_profile_id\\\" UUID,\\n \\\"agent_ref\\\" TEXT,\\n \\\"source\\\" TEXT,\\n \\\"description\\\" TEXT,\\n \\\"started_at\\\" TIMESTAMP,\\n \\\"ended_at\\\" TIMESTAMP,\\n \\\"duration_seconds\\\" INTEGER DEFAULT 0,\\n \\\"evidence\\\" TEXT,\\n \\\"status\\\" TEXT,\\n \\\"submitted_at\\\" TIMESTAMP,\\n \\\"submitted_by_profile_id\\\" UUID,\\n \\\"approved_at\\\" TIMESTAMP,\\n \\\"approved_by_profile_id\\\" UUID,\\n \\\"approval_path\\\" TEXT,\\n \\\"rejected_at\\\" TIMESTAMP,\\n \\\"rejected_by_profile_id\\\" UUID,\\n \\\"rejection_reason\\\" TEXT,\\n \\\"correction_of_id\\\" UUID,\\n \\\"metadata\\\" TEXT\\n);\",\"columns\":{\"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},\"work_ref_type\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"work_ref_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"participant_kind\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"participant_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"agent_ref\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"source\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"started_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"ended_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"duration_seconds\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"evidence\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"submitted_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"submitted_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"approved_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"approved_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"approval_path\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"rejected_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"rejected_by_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"rejection_reason\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"correction_of_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false,\"foreignKey\":{\"table\":\"service_time_entries\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"service_time_entries_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"service_time_entries_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"service_time_entries_participant_profile_id_idx\",\"columns\":[\"participant_profile_id\"]},{\"name\":\"service_time_entries_submitted_by_profile_id_idx\",\"columns\":[\"submitted_by_profile_id\"]},{\"name\":\"service_time_entries_approved_by_profile_id_idx\",\"columns\":[\"approved_by_profile_id\"]},{\"name\":\"service_time_entries_rejected_by_profile_id_idx\",\"columns\":[\"rejected_by_profile_id\"]},{\"name\":\"service_time_entries_correction_of_id_idx\",\"columns\":[\"correction_of_id\"]}],\"version\":\"16c0d6c8\"},\"importPath\":\"@happyvertical/smrt-timesheets\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
7
+ //#endregion
8
+ //#region src/models/service-snapshots.ts
9
+ var __defProp$1 = Object.defineProperty;
10
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
11
+ var __decorateClass$1 = (decorators, target, key, kind) => {
12
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
13
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
14
+ if (kind && result) __defProp$1(target, key, result);
15
+ return result;
16
+ };
17
+ function assertSnapshotMinorUnits(model, amount) {
18
+ if (typeof amount !== "number" || !Number.isSafeInteger(amount)) throw new ValidationError(`${model}.amount must be a safe integer number of minor units (cents) \u2014 got ${String(amount)}. $19.99 is 1999.`, "VALIDATION_INVALID_VALUE", {
19
+ fieldName: "amount",
20
+ value: amount,
21
+ expectedType: "safe integer minor units"
22
+ });
23
+ }
24
+ var chargeSnapshot = /* @__PURE__ */ new WeakMap();
25
+ var compensationSnapshot = /* @__PURE__ */ new WeakMap();
26
+ async function immutableCommercialSnapshot(object, fields) {
27
+ if (!object.id) return null;
28
+ try {
29
+ const row = await object.db.get(object.tableName, { id: object.id });
30
+ if (!row) return null;
31
+ return JSON.stringify(Object.fromEntries(fields.map((key) => [key, row[key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)] ?? null])));
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function commercialSnapshot(object, fields) {
37
+ return JSON.stringify(Object.fromEntries(fields.map((key) => [key, object[key] ?? null])));
38
+ }
39
+ var CHARGE_FIELDS = [
40
+ "timeEntryId",
41
+ "amount",
42
+ "currency",
43
+ "pricingVersion",
44
+ "strategy",
45
+ "rateSnapshot",
46
+ "sourceChargeRef"
47
+ ];
48
+ var COMPENSATION_FIELDS = [
49
+ "timeEntryId",
50
+ "amount",
51
+ "currency",
52
+ "termsVersion",
53
+ "rateSnapshot"
54
+ ];
55
+ var ServiceChargeSnapshot = class extends SmrtObject {
56
+ tenantId = null;
57
+ timeEntryId = "";
58
+ /**
59
+ * Client charge in **integer minor units** of {@link currency} — `$19.99` is
60
+ * `1999`. The `= 0` initializer is load-bearing: SMRT maps an integer literal
61
+ * to INTEGER and a decimal literal to DECIMAL (#2401).
62
+ *
63
+ * Copied verbatim from the upstream `ClientCharge.amount`, so the two carry
64
+ * the same unit by construction; a split would land cents in a major-units
65
+ * column with no error anywhere.
66
+ */
67
+ amount = 0;
68
+ currency = "USD";
69
+ pricingVersion = "";
70
+ strategy = "";
71
+ rateSnapshot = "{}";
72
+ sourceChargeRef = "";
73
+ createdAt = /* @__PURE__ */ new Date();
74
+ async initialize() {
75
+ await super.initialize();
76
+ if (await this.isSaved()) chargeSnapshot.set(this, commercialSnapshot(this, CHARGE_FIELDS));
77
+ return this;
78
+ }
79
+ async save() {
80
+ assertSnapshotMinorUnits("ServiceChargeSnapshot", this.amount);
81
+ const current = commercialSnapshot(this, CHARGE_FIELDS);
82
+ const prior = await immutableCommercialSnapshot(this, CHARGE_FIELDS) ?? chargeSnapshot.get(this) ?? null;
83
+ if (prior && prior !== current) throw new Error("ServiceChargeSnapshot is immutable; append an upstream adjustment instead.");
84
+ const result = await super.save();
85
+ chargeSnapshot.set(this, current);
86
+ return result;
87
+ }
88
+ };
89
+ __decorateClass$1([tenantId({ nullable: true })], ServiceChargeSnapshot.prototype, "tenantId", 2);
90
+ __decorateClass$1([foreignKey("ServiceTimeEntry", { required: true })], ServiceChargeSnapshot.prototype, "timeEntryId", 2);
91
+ __decorateClass$1([field({ type: "text" })], ServiceChargeSnapshot.prototype, "currency", 2);
92
+ __decorateClass$1([field({ type: "text" })], ServiceChargeSnapshot.prototype, "pricingVersion", 2);
93
+ __decorateClass$1([field({ type: "text" })], ServiceChargeSnapshot.prototype, "strategy", 2);
94
+ __decorateClass$1([field({ type: "text" })], ServiceChargeSnapshot.prototype, "rateSnapshot", 2);
95
+ __decorateClass$1([field({ type: "text" })], ServiceChargeSnapshot.prototype, "sourceChargeRef", 2);
96
+ ServiceChargeSnapshot = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
97
+ tableName: "service_charge_snapshots",
98
+ previousQualifiedNames: ["@happyvertical/smrt-projects:ServiceChargeSnapshot"],
99
+ conflictColumns: ["time_entry_id"],
100
+ api: { include: ["list", "get"] },
101
+ cli: { include: ["list", "get"] },
102
+ mcp: { include: ["list", "get"] }
103
+ })], ServiceChargeSnapshot);
104
+ var ServiceCompensationSnapshot = class extends SmrtObject {
105
+ tenantId = null;
106
+ timeEntryId = "";
107
+ /**
108
+ * Provider earning in **integer minor units** of {@link currency} (#2401).
109
+ *
110
+ * Converts with {@link ServiceChargeSnapshot.amount}: the delivery margin is
111
+ * `charge - compensation`, so a mixed pair would make that subtraction
112
+ * meaningless.
113
+ */
114
+ amount = 0;
115
+ currency = "USD";
116
+ termsVersion = "";
117
+ rateSnapshot = "{}";
118
+ createdAt = /* @__PURE__ */ new Date();
119
+ async initialize() {
120
+ await super.initialize();
121
+ if (await this.isSaved()) compensationSnapshot.set(this, commercialSnapshot(this, COMPENSATION_FIELDS));
122
+ return this;
123
+ }
124
+ async save() {
125
+ assertSnapshotMinorUnits("ServiceCompensationSnapshot", this.amount);
126
+ const current = commercialSnapshot(this, COMPENSATION_FIELDS);
127
+ const prior = await immutableCommercialSnapshot(this, COMPENSATION_FIELDS) ?? compensationSnapshot.get(this) ?? null;
128
+ if (prior && prior !== current) throw new Error("ServiceCompensationSnapshot is immutable; create explicit settlement evidence instead.");
129
+ const result = await super.save();
130
+ compensationSnapshot.set(this, current);
131
+ return result;
132
+ }
133
+ };
134
+ __decorateClass$1([tenantId({ nullable: true })], ServiceCompensationSnapshot.prototype, "tenantId", 2);
135
+ __decorateClass$1([foreignKey("ServiceTimeEntry", { required: true })], ServiceCompensationSnapshot.prototype, "timeEntryId", 2);
136
+ __decorateClass$1([field({ type: "text" })], ServiceCompensationSnapshot.prototype, "currency", 2);
137
+ __decorateClass$1([field({ type: "text" })], ServiceCompensationSnapshot.prototype, "termsVersion", 2);
138
+ __decorateClass$1([field({ type: "text" })], ServiceCompensationSnapshot.prototype, "rateSnapshot", 2);
139
+ ServiceCompensationSnapshot = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
140
+ tableName: "service_compensation_snapshots",
141
+ previousQualifiedNames: ["@happyvertical/smrt-projects:ServiceCompensationSnapshot"],
142
+ conflictColumns: ["time_entry_id"],
143
+ api: { include: ["list", "get"] },
144
+ cli: { include: ["list", "get"] },
145
+ mcp: { include: ["list", "get"] }
146
+ })], ServiceCompensationSnapshot);
147
+ var ServiceChargeSnapshotCollection = class extends SmrtCollection {
148
+ static _itemClass = ServiceChargeSnapshot;
149
+ };
150
+ var ServiceCompensationSnapshotCollection = class extends SmrtCollection {
151
+ static _itemClass = ServiceCompensationSnapshot;
152
+ };
153
+ //#endregion
154
+ //#region src/models/json.ts
155
+ function parseTimesheetJson(value, fallback) {
156
+ try {
157
+ return JSON.parse(value);
158
+ } catch {
159
+ return fallback;
160
+ }
161
+ }
162
+ //#endregion
163
+ //#region src/models/service-time-entry.ts
164
+ var __defProp = Object.defineProperty;
165
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
166
+ var __decorateClass = (decorators, target, key, kind) => {
167
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
168
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
169
+ if (kind && result) __defProp(target, key, result);
170
+ return result;
171
+ };
172
+ var SERVICE_TIME_ENTRY_FROZEN_FIELDS = [
173
+ "tenantId",
174
+ "workRefType",
175
+ "workRefId",
176
+ "participantKind",
177
+ "participantProfileId",
178
+ "agentRef",
179
+ "source",
180
+ "description",
181
+ "startedAt",
182
+ "endedAt",
183
+ "durationSeconds",
184
+ "evidence",
185
+ "correctionOfId"
186
+ ];
187
+ function toColumn(key) {
188
+ return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
189
+ }
190
+ function frozenView(source, fields) {
191
+ const result = {};
192
+ for (const key of fields) {
193
+ let value = source[key] ?? null;
194
+ if ((key === "startedAt" || key === "endedAt") && value) {
195
+ const date = value instanceof Date ? value : new Date(String(value));
196
+ value = Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
197
+ }
198
+ if (key === "durationSeconds") value = Number(value ?? 0);
199
+ result[key] = value === "" ? null : value;
200
+ }
201
+ return JSON.stringify(result);
202
+ }
203
+ var loadedStatus = /* @__PURE__ */ new WeakMap();
204
+ var ServiceTimeEntry = class extends SmrtObject {
205
+ tenantId = null;
206
+ workRefType = null;
207
+ workRefId = null;
208
+ participantKind = "human";
209
+ participantProfileId = null;
210
+ agentRef = "";
211
+ source = "manual";
212
+ description = "";
213
+ startedAt = null;
214
+ endedAt = null;
215
+ durationSeconds = 0;
216
+ evidence = "[]";
217
+ status = "draft";
218
+ submittedAt = null;
219
+ submittedByProfileId = null;
220
+ approvedAt = null;
221
+ approvedByProfileId = null;
222
+ approvalPath = "";
223
+ rejectedAt = null;
224
+ rejectedByProfileId = null;
225
+ rejectionReason = "";
226
+ correctionOfId = null;
227
+ metadata = "{}";
228
+ getEvidence() {
229
+ return parseTimesheetJson(this.evidence, []);
230
+ }
231
+ setEvidence(value) {
232
+ this.evidence = JSON.stringify(value ?? []);
233
+ }
234
+ getMetadata() {
235
+ return parseTimesheetJson(this.metadata, {});
236
+ }
237
+ setMetadata(value) {
238
+ this.metadata = JSON.stringify(value ?? {});
239
+ }
240
+ durationHours() {
241
+ return this.durationSeconds / 3600;
242
+ }
243
+ /**
244
+ * Fields frozen once the entry is approved. Subtypes that add work context
245
+ * (e.g. a support case) return `[...super.frozenFieldNames(), ...]`.
246
+ */
247
+ frozenFieldNames() {
248
+ return SERVICE_TIME_ENTRY_FROZEN_FIELDS;
249
+ }
250
+ async initialize() {
251
+ await super.initialize();
252
+ if (await this.isSaved()) loadedStatus.set(this, this.status);
253
+ return this;
254
+ }
255
+ async save() {
256
+ const prior = await this.readPrior();
257
+ if (prior && prior.status !== this.status && !SERVICE_TIME_ENTRY_STATUS_TRANSITIONS[prior.status].includes(this.status)) throw new Error(`ServiceTimeEntry ${this.id}: illegal status transition '${prior.status}' \u2192 '${this.status}'.`);
258
+ if (prior && (prior.status === "approved" || prior.status === "corrected")) {
259
+ if (frozenView(this, this.frozenFieldNames()) !== prior.frozen) throw new Error("Approved ServiceTimeEntry evidence is immutable; create a correction instead.");
260
+ }
261
+ const result = await super.save();
262
+ loadedStatus.set(this, this.status);
263
+ return result;
264
+ }
265
+ async readPrior() {
266
+ if (!this.id) return null;
267
+ const fields = this.frozenFieldNames();
268
+ try {
269
+ const row = await this.db.get(this.tableName, { id: this.id });
270
+ if (!row?.status) return null;
271
+ const values = {};
272
+ for (const key of fields) values[key] = row[toColumn(key)];
273
+ return {
274
+ status: row.status,
275
+ frozen: frozenView(values, fields)
276
+ };
277
+ } catch {
278
+ const status = loadedStatus.get(this);
279
+ return status ? {
280
+ status,
281
+ frozen: frozenView(this, fields)
282
+ } : null;
283
+ }
284
+ }
285
+ };
286
+ __decorateClass([tenantId({ nullable: true })], ServiceTimeEntry.prototype, "tenantId", 2);
287
+ __decorateClass([field({
288
+ type: "text",
289
+ nullable: true
290
+ })], ServiceTimeEntry.prototype, "workRefType", 2);
291
+ __decorateClass([field({
292
+ type: "text",
293
+ nullable: true
294
+ })], ServiceTimeEntry.prototype, "workRefId", 2);
295
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "participantKind", 2);
296
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], ServiceTimeEntry.prototype, "participantProfileId", 2);
297
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "agentRef", 2);
298
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "source", 2);
299
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "description", 2);
300
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "evidence", 2);
301
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "status", 2);
302
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], ServiceTimeEntry.prototype, "submittedByProfileId", 2);
303
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], ServiceTimeEntry.prototype, "approvedByProfileId", 2);
304
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "approvalPath", 2);
305
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], ServiceTimeEntry.prototype, "rejectedByProfileId", 2);
306
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "rejectionReason", 2);
307
+ __decorateClass([foreignKey("ServiceTimeEntry")], ServiceTimeEntry.prototype, "correctionOfId", 2);
308
+ __decorateClass([field({ type: "text" })], ServiceTimeEntry.prototype, "metadata", 2);
309
+ ServiceTimeEntry = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
310
+ tableName: "service_time_entries",
311
+ previousQualifiedNames: ["@happyvertical/smrt-projects:ServiceTimeEntry"],
312
+ api: { include: ["list", "get"] },
313
+ cli: { include: ["list", "get"] },
314
+ mcp: { include: ["list", "get"] }
315
+ })], ServiceTimeEntry);
316
+ var ServiceTimeEntryCollection = class extends SmrtCollection {
317
+ static _itemClass = ServiceTimeEntry;
318
+ async forWork(workRefType, workRefId) {
319
+ return this.list({
320
+ where: {
321
+ workRefType,
322
+ workRefId
323
+ },
324
+ orderBy: "created_at ASC"
325
+ });
326
+ }
327
+ async pendingApproval() {
328
+ return this.list({
329
+ where: { status: "submitted" },
330
+ orderBy: "submitted_at ASC"
331
+ });
332
+ }
333
+ };
334
+ //#endregion
335
+ //#region src/services/service-evidence-service.ts
336
+ var ServiceEvidenceService = class ServiceEvidenceService {
337
+ constructor(entries, charges, compensation, commercial) {
338
+ this.entries = entries;
339
+ this.charges = charges;
340
+ this.compensation = compensation;
341
+ this.commercial = commercial;
342
+ }
343
+ entries;
344
+ charges;
345
+ compensation;
346
+ commercial;
347
+ static async create(options = {}, commercial) {
348
+ const [entries, charges, compensation] = await Promise.all([
349
+ ServiceTimeEntryCollection.create(options),
350
+ ServiceChargeSnapshotCollection.create(options),
351
+ ServiceCompensationSnapshotCollection.create(options)
352
+ ]);
353
+ return new ServiceEvidenceService(entries, charges, compensation, commercial);
354
+ }
355
+ async record(input) {
356
+ if (Boolean(input.workRefType) !== Boolean(input.workRefId)) throw new Error("workRefType and workRefId must be provided together.");
357
+ if (!(input.workRefType && input.workRefId)) throw new Error("Service time requires a work reference.");
358
+ if (input.participantKind === "human" && !input.participantProfileId) throw new Error("Human service time requires participantProfileId.");
359
+ if (input.participantKind === "agent" && !input.agentRef) throw new Error("Agent service time requires agentRef.");
360
+ if (input.source === "timer" && (!input.startedAt || !input.endedAt)) throw new Error("Timer service time requires startedAt and endedAt.");
361
+ if (input.startedAt && input.endedAt && input.startedAt >= input.endedAt) throw new Error("Service time startedAt must be before endedAt.");
362
+ const duration = input.durationSeconds ?? (input.startedAt && input.endedAt ? Math.round((input.endedAt.getTime() - input.startedAt.getTime()) / 1e3) : 0);
363
+ if (!Number.isInteger(duration) || duration <= 0) throw new Error("Service time durationSeconds must be a positive integer.");
364
+ const entry = await this.entries.create({
365
+ ...input,
366
+ durationSeconds: duration,
367
+ evidence: JSON.stringify(input.evidence ?? []),
368
+ metadata: JSON.stringify(input.metadata ?? {}),
369
+ status: "draft",
370
+ submittedAt: null,
371
+ submittedByProfileId: null,
372
+ approvedAt: null,
373
+ approvedByProfileId: null,
374
+ approvalPath: "",
375
+ rejectedAt: null,
376
+ rejectedByProfileId: null,
377
+ rejectionReason: "",
378
+ correctionOfId: null
379
+ });
380
+ await entry.save();
381
+ return entry;
382
+ }
383
+ async submit(entry, profileId) {
384
+ if (entry.status !== "draft" && entry.status !== "rejected") throw new Error("Only draft or rejected service time may be submitted.");
385
+ entry.status = "submitted";
386
+ entry.submittedAt = /* @__PURE__ */ new Date();
387
+ entry.submittedByProfileId = profileId ?? null;
388
+ return entry.save();
389
+ }
390
+ async approve(entry, options) {
391
+ if (entry.status !== "submitted" && entry.status !== "approved") throw new Error("Only submitted service time or an incomplete approval may be approved.");
392
+ if (!this.commercial) throw new Error("A ServiceCommercialResolver is required to approve service time.");
393
+ const timeEntryId = entry.id;
394
+ if (!timeEntryId) throw new Error("ServiceTimeEntry must be saved before approval.");
395
+ const existingCharge = (await this.charges.list({
396
+ where: { timeEntryId },
397
+ limit: 1
398
+ }))[0];
399
+ const existingCompensation = (await this.compensation.list({
400
+ where: { timeEntryId },
401
+ limit: 1
402
+ }))[0];
403
+ if (entry.status === "approved" && existingCharge && existingCompensation) return entry;
404
+ if (entry.status === "submitted") {
405
+ entry.status = "approved";
406
+ entry.approvedAt = /* @__PURE__ */ new Date();
407
+ entry.approvedByProfileId = options.actorProfileId ?? null;
408
+ entry.approvalPath = options.approvalPath;
409
+ try {
410
+ await entry.save();
411
+ } catch (error) {
412
+ if (!(error instanceof Error && "code" in error && error.code === "RUNTIME_REVISION_CONFLICT")) throw error;
413
+ const concurrent = await this.entries.get(timeEntryId);
414
+ if (concurrent?.status !== "approved") throw error;
415
+ entry = concurrent;
416
+ }
417
+ }
418
+ const provider = existingCompensation ? null : await this.commercial.compensateProvider(entry);
419
+ if (provider) {
420
+ const snapshotId = await deterministicSnapshotId("service-compensation-snapshot", timeEntryId);
421
+ try {
422
+ await this.compensation.create({
423
+ id: snapshotId,
424
+ tenantId: entry.tenantId,
425
+ timeEntryId,
426
+ amount: provider.amount,
427
+ currency: provider.currency ?? "USD",
428
+ termsVersion: provider.version,
429
+ rateSnapshot: JSON.stringify(provider.terms),
430
+ _insertOnly: true
431
+ });
432
+ } catch (error) {
433
+ if (!(await this.compensation.get(snapshotId) ?? (await this.compensation.list({
434
+ where: { timeEntryId },
435
+ limit: 1
436
+ }))[0])) throw error;
437
+ }
438
+ }
439
+ const client = existingCharge ? null : await this.commercial.priceClient(entry);
440
+ if (client) {
441
+ const snapshotId = await deterministicSnapshotId("service-charge-snapshot", timeEntryId);
442
+ try {
443
+ await this.charges.create({
444
+ id: snapshotId,
445
+ tenantId: entry.tenantId,
446
+ timeEntryId,
447
+ amount: client.amount,
448
+ currency: client.currency ?? "USD",
449
+ pricingVersion: client.version,
450
+ strategy: client.strategy ?? "",
451
+ rateSnapshot: JSON.stringify(client.terms),
452
+ sourceChargeRef: client.sourceRef ?? "",
453
+ _insertOnly: true
454
+ });
455
+ } catch (error) {
456
+ if (!(await this.charges.get(snapshotId) ?? (await this.charges.list({
457
+ where: { timeEntryId },
458
+ limit: 1
459
+ }))[0])) throw error;
460
+ }
461
+ }
462
+ return entry;
463
+ }
464
+ async correct(entry, input) {
465
+ if (entry.status !== "approved") throw new Error("Only approved service time may be corrected.");
466
+ if (input.tenantId !== void 0 && input.tenantId !== entry.tenantId) throw new Error("Service time corrections must remain in the original tenant.");
467
+ const correction = await this.record({
468
+ ...input,
469
+ tenantId: entry.tenantId
470
+ });
471
+ correction.correctionOfId = entry.id ?? null;
472
+ await correction.save();
473
+ entry.status = "corrected";
474
+ await entry.save();
475
+ return correction;
476
+ }
477
+ };
478
+ async function deterministicSnapshotId(kind, timeEntryId) {
479
+ const bytes = new TextEncoder().encode(JSON.stringify([kind, timeEntryId]));
480
+ const uuid = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)).slice(0, 16);
481
+ uuid[6] = uuid[6] & 15 | 80;
482
+ uuid[8] = uuid[8] & 63 | 128;
483
+ const hex = Array.from(uuid, (byte) => byte.toString(16).padStart(2, "0")).join("");
484
+ return [
485
+ hex.slice(0, 8),
486
+ hex.slice(8, 12),
487
+ hex.slice(12, 16),
488
+ hex.slice(16, 20),
489
+ hex.slice(20)
490
+ ].join("-");
491
+ }
492
+ //#endregion
493
+ export { SERVICE_TIME_ENTRY_FROZEN_FIELDS, SERVICE_TIME_ENTRY_STATUS_TRANSITIONS, ServiceChargeSnapshot, ServiceChargeSnapshotCollection, ServiceCompensationSnapshot, ServiceCompensationSnapshotCollection, ServiceEvidenceService, ServiceTimeEntry, ServiceTimeEntryCollection, TIMESHEETS_MODULE_META, TIMESHEETS_UI_SLOTS };
494
+
495
+ //# sourceMappingURL=index.js.map