@fleox-agent-crm/agentcrm 0.0.0-stage → 0.3.0

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 (129) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +223 -2
  3. package/changelog.json +57 -0
  4. package/dist/admin/admin-files.json +26 -0
  5. package/dist/admin/assets/chartsImpl-CE2akJat.js +91 -0
  6. package/dist/admin/assets/firebase-BHSUggRo.js +2 -0
  7. package/dist/admin/assets/gitJson-BulGh2KV.js +3 -0
  8. package/dist/admin/assets/index-DidVVK7L.js +1181 -0
  9. package/dist/admin/assets/index-hMXwHSvN.css +1 -0
  10. package/dist/admin/assets/index.esm-BFc7vwnl.js +400 -0
  11. package/dist/admin/assets/index.esm-CtEQZRb1.js +1651 -0
  12. package/dist/admin/assets/index.esm-Dum0J07z.js +16 -0
  13. package/dist/admin/assets/index.esm2017-EOzKbpUS.js +390 -0
  14. package/dist/admin/assets/lucide-react.min-DEDlHCPj.js +6 -0
  15. package/dist/admin/assets/sdk-CnrLnlRo.js +1717 -0
  16. package/dist/admin/config.js +3 -0
  17. package/dist/admin/favicon.svg +1 -0
  18. package/dist/admin/index.html +139 -0
  19. package/dist/admin/runtime/agentcrm-runtime.js +104 -0
  20. package/dist/admin/runtime/lucide-react.js +791 -0
  21. package/dist/admin/runtime/react-dom-client.js +17 -0
  22. package/dist/admin/runtime/react-dom.js +20 -0
  23. package/dist/admin/runtime/react-i18next.js +19 -0
  24. package/dist/admin/runtime/react-jsx-runtime.js +17 -0
  25. package/dist/admin/runtime/react.js +22 -0
  26. package/dist/agent/agentcrm.mjs +3932 -0
  27. package/dist/agent/chunks/_commonjsHelpers-ClciMIcB.mjs +14 -0
  28. package/dist/agent/chunks/activities-Cj_SIOay.mjs +227 -0
  29. package/dist/agent/chunks/api-B6OqNVWp.mjs +97 -0
  30. package/dist/agent/chunks/appearance-BaShelzt.mjs +174 -0
  31. package/dist/agent/chunks/automations-BWrvLqP6.mjs +698 -0
  32. package/dist/agent/chunks/backup-ClSmxmFZ.mjs +103 -0
  33. package/dist/agent/chunks/batch-CVETiJj5.mjs +142 -0
  34. package/dist/agent/chunks/compact-CsFPaAAq.mjs +69 -0
  35. package/dist/agent/chunks/context-CxFfqS4D.mjs +65 -0
  36. package/dist/agent/chunks/core-Dy7vY9H6.mjs +255 -0
  37. package/dist/agent/chunks/dedupe-BepmsaHv.mjs +123 -0
  38. package/dist/agent/chunks/encoding-C9MzdP95.mjs +36 -0
  39. package/dist/agent/chunks/exportCsv-B5hRvkNm.mjs +95 -0
  40. package/dist/agent/chunks/extCheck--7lmVvl7.mjs +193 -0
  41. package/dist/agent/chunks/extPackage-BGl5Oqs8.mjs +115 -0
  42. package/dist/agent/chunks/extScaffold-DqxI7R1e.mjs +320 -0
  43. package/dist/agent/chunks/extensionActions-D2HtpQz4.mjs +48 -0
  44. package/dist/agent/chunks/extensions-BP1k60md.mjs +151 -0
  45. package/dist/agent/chunks/featureRegistry-C9nbVShp.mjs +149 -0
  46. package/dist/agent/chunks/fields-B0bitgmr.mjs +476 -0
  47. package/dist/agent/chunks/files-CjsPJSR6.mjs +158 -0
  48. package/dist/agent/chunks/files-q8Xu5bft.mjs +367 -0
  49. package/dist/agent/chunks/firebase-DxGVQV9E.mjs +454 -0
  50. package/dist/agent/chunks/firebaseRules-iori-s7U.mjs +221 -0
  51. package/dist/agent/chunks/forms-BKU5F9WF.mjs +847 -0
  52. package/dist/agent/chunks/gitJson-fjr8mcHe.mjs +528 -0
  53. package/dist/agent/chunks/hosting-Bme7LxPc.mjs +144 -0
  54. package/dist/agent/chunks/http-Bk9ICb7I.mjs +127 -0
  55. package/dist/agent/chunks/ids-CvnmrMJX.mjs +59 -0
  56. package/dist/agent/chunks/ids-u-noYcy5.mjs +14 -0
  57. package/dist/agent/chunks/importCsv-DmE_sBch.mjs +334 -0
  58. package/dist/agent/chunks/index-B-4W0-P4.mjs +535 -0
  59. package/dist/agent/chunks/index-BM8qeGOb.mjs +2489 -0
  60. package/dist/agent/chunks/index-Bnmav-Oh.mjs +311 -0
  61. package/dist/agent/chunks/index-CRkw3_iF.mjs +147 -0
  62. package/dist/agent/chunks/index-CWrrF1wl.mjs +111 -0
  63. package/dist/agent/chunks/index-DUSdTBkz.mjs +2988 -0
  64. package/dist/agent/chunks/index-RCmo-uld.mjs +26 -0
  65. package/dist/agent/chunks/index-w03ReRs4.mjs +9780 -0
  66. package/dist/agent/chunks/install-CpHoUFtL.mjs +188 -0
  67. package/dist/agent/chunks/jsx-runtime-Ca2uwGDr.mjs +76 -0
  68. package/dist/agent/chunks/loader-C9nm3iwx.mjs +184 -0
  69. package/dist/agent/chunks/lucide-react.min-Bt3gtepY.mjs +64 -0
  70. package/dist/agent/chunks/memory-WNR3VHUJ.mjs +56 -0
  71. package/dist/agent/chunks/migrate-CsClnnN8.mjs +182 -0
  72. package/dist/agent/chunks/modules-CqdVQVJb.mjs +177 -0
  73. package/dist/agent/chunks/nodeImport-BPCt5uMu.mjs +38 -0
  74. package/dist/agent/chunks/package-DWbn3t2R.mjs +194 -0
  75. package/dist/agent/chunks/papaparse-DjBYnQM2.mjs +1285 -0
  76. package/dist/agent/chunks/pipelines-DCdRBNBV.mjs +234 -0
  77. package/dist/agent/chunks/plan-C0TBrU92.mjs +254 -0
  78. package/dist/agent/chunks/pluginRegistry-DyHtlkcr.mjs +83 -0
  79. package/dist/agent/chunks/query-C5nJB_3C.mjs +192 -0
  80. package/dist/agent/chunks/records-DoqiT9xR.mjs +2191 -0
  81. package/dist/agent/chunks/registry-Ceexawxl.mjs +182 -0
  82. package/dist/agent/chunks/registry-DijAKZ24.mjs +431 -0
  83. package/dist/agent/chunks/relations-B9TynBmA.mjs +191 -0
  84. package/dist/agent/chunks/reports-BB59N6ke.mjs +189 -0
  85. package/dist/agent/chunks/runtime-DHo7ZEEO.mjs +14852 -0
  86. package/dist/agent/chunks/runtimeConfig-CCOFg1RM.mjs +250 -0
  87. package/dist/agent/chunks/s3-Dz50-NuA.mjs +903 -0
  88. package/dist/agent/chunks/schema-7-4-qtSS.mjs +203 -0
  89. package/dist/agent/chunks/schema-DlHt2PxI.mjs +367 -0
  90. package/dist/agent/chunks/sdk-whvfg786.mjs +43644 -0
  91. package/dist/agent/chunks/seed-CugPGYZL.mjs +523 -0
  92. package/dist/agent/chunks/server-ISwlDRCN.mjs +242 -0
  93. package/dist/agent/chunks/settings-BtvR_jdm.mjs +76 -0
  94. package/dist/agent/chunks/standardObjects-CheqfDqa.mjs +243 -0
  95. package/dist/agent/chunks/stats-CfeJssSS.mjs +51 -0
  96. package/dist/agent/chunks/store-5gqRSKXg.mjs +122 -0
  97. package/dist/agent/chunks/themes-DPZFv3yc.mjs +43 -0
  98. package/dist/agent/chunks/tools-Bew8lxKx.mjs +844 -0
  99. package/dist/agent/chunks/types-BOy9PUl7.mjs +44 -0
  100. package/dist/agent/chunks/users-DI1BvS0f.mjs +107 -0
  101. package/dist/agent/chunks/utils-DmI8FtXP.mjs +83 -0
  102. package/dist/agent/chunks/views-fO_BM5U_.mjs +109 -0
  103. package/dist/version.json +7 -0
  104. package/package.json +97 -4
  105. package/plugin/.claude-plugin/plugin.json +17 -0
  106. package/plugin/skills/agentcrm-admin/SKILL.md +191 -0
  107. package/plugin/skills/agentcrm-admin/references/appearance.md +26 -0
  108. package/plugin/skills/agentcrm-admin/references/import.md +16 -0
  109. package/plugin/skills/agentcrm-admin/references/records.md +30 -0
  110. package/plugin/skills/agentcrm-admin/references/schema.md +59 -0
  111. package/plugin/skills/agentcrm-extensions/SKILL.md +93 -0
  112. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +213 -0
  113. package/plugin/skills/agentcrm-extensions/references/runtime-api.md +41 -0
  114. package/plugin/skills/agentcrm-extensions/references/testing.md +76 -0
  115. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +65 -0
  116. package/plugin/skills/agentcrm-install/SKILL.md +116 -0
  117. package/plugin/skills/agentcrm-install/references/firebase.md +18 -0
  118. package/plugin/skills/agentcrm-install/references/github.md +20 -0
  119. package/plugin/skills/agentcrm-install/references/mcp.md +17 -0
  120. package/plugin/skills/agentcrm-install/references/plans.md +45 -0
  121. package/plugin/skills/agentcrm-install/references/s3.md +24 -0
  122. package/plugin/skills/agentcrm-manage/SKILL.md +108 -0
  123. package/plugin/skills/agentcrm-manage/references/cron.md +11 -0
  124. package/plugin/skills/agentcrm-manage/references/doctor.md +18 -0
  125. package/plugin/skills/agentcrm-sales/SKILL.md +92 -0
  126. package/plugin/skills/agentcrm-sales/references/enrichment.md +13 -0
  127. package/plugin/skills/agentcrm-sales/references/playbooks.md +29 -0
  128. package/plugin/skills/agentcrm-sales/references/weekly-report.md +27 -0
  129. package/types/agentcrm-runtime.d.ts +894 -0
@@ -0,0 +1,191 @@
1
+ import { createRequire as __acrmRequire } from "node:module";
2
+ import { fileURLToPath as __acrmPath } from "node:url";
3
+ import { dirname as __acrmDirname } from "node:path";
4
+ const require2 = __acrmRequire(import.meta.url);
5
+ const __filename = __acrmPath(import.meta.url);
6
+ const __dirname = __acrmDirname(__filename);
7
+ import { filesOf, objectsOf, recordsOf, activitiesOf, objectOf, recordOf, requireRecord, requireObject } from "./store-5gqRSKXg.mjs";
8
+ import { recordLabel } from "./fields-B0bitgmr.mjs";
9
+ import { actorId, assertCan, OpsError } from "./context-CxFfqS4D.mjs";
10
+ const sameTarget = (a, b) => a.kind === b.kind && a.id === b.id;
11
+ function fileFieldIds(ctx, objectId) {
12
+ const o = objectsOf(ctx.store).find((x) => x.id === objectId);
13
+ return o ? o.fields.filter((f) => f.type === "image" || f.type === "files").map((f) => f.id) : [];
14
+ }
15
+ function fileIdsIn(value) {
16
+ if (typeof value === "string" && value) return [value];
17
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string" && !!v);
18
+ return [];
19
+ }
20
+ function referencedFileIds(ctx) {
21
+ const out = /* @__PURE__ */ new Set();
22
+ const byObject = /* @__PURE__ */ new Map();
23
+ for (const r of recordsOf(ctx.store, void 0, { includeDeleted: true })) {
24
+ let ids = byObject.get(r.object);
25
+ if (!ids) byObject.set(r.object, ids = fileFieldIds(ctx, r.object));
26
+ for (const f of ids) for (const id of fileIdsIn(r.fields[f])) out.add(id);
27
+ }
28
+ for (const a of activitiesOf(ctx.store, { includeDeleted: true })) for (const id of a.attachments ?? []) out.add(id);
29
+ return out;
30
+ }
31
+ function fileCleanupMutations(ctx, doomed) {
32
+ const records = new Set(doomed.records ?? []);
33
+ const activities = new Set(doomed.activities ?? []);
34
+ if (!records.size && !activities.size) return [];
35
+ const now = ctx.now();
36
+ const out = [];
37
+ for (const f of filesOf(ctx.store, { includeDeleted: true })) {
38
+ const gone = (t) => t.kind === "record" ? records.has(t.id) : activities.has(t.id);
39
+ if (!f.targets.some(gone)) continue;
40
+ const rest = f.targets.filter((t) => !gone(t));
41
+ const fields = { targets: rest, updatedAt: now };
42
+ if (!rest.length && !f.deletedAt) fields.deletedAt = now;
43
+ out.push({ op: "patch", collection: "files", id: f.id, fields });
44
+ }
45
+ return out;
46
+ }
47
+ function fileMergeMutations(ctx, duplicates, primary) {
48
+ const dup = new Set(duplicates);
49
+ const now = ctx.now();
50
+ const out = [];
51
+ for (const f of filesOf(ctx.store, { includeDeleted: true })) {
52
+ if (!f.targets.some((t) => t.kind === "record" && dup.has(t.id))) continue;
53
+ const targets = [];
54
+ for (const t of f.targets) {
55
+ const next = t.kind === "record" && dup.has(t.id) ? { kind: "record", object: primary.object, id: primary.id } : t;
56
+ if (!targets.some((x) => sameTarget(x, next))) targets.push(next);
57
+ }
58
+ out.push({ op: "patch", collection: "files", id: f.id, fields: { targets, updatedAt: now } });
59
+ }
60
+ return out;
61
+ }
62
+ function fileTargetPatch(ctx, file, add, remove = []) {
63
+ let targets = file.targets.filter((t) => !remove.some((r) => sameTarget(r, t)));
64
+ for (const t of add) if (!targets.some((x) => sameTarget(x, t))) targets = [...targets, t];
65
+ if (targets.length === file.targets.length && targets.every((t, i) => sameTarget(t, file.targets[i]))) return null;
66
+ return { op: "patch", collection: "files", id: file.id, fields: { targets, updatedAt: ctx.now() } };
67
+ }
68
+ function relationFields(object) {
69
+ return object.fields.filter((f) => f.type === "relation" && f.relation);
70
+ }
71
+ function relationIds(value) {
72
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
73
+ return typeof value === "string" && value ? [value] : [];
74
+ }
75
+ function toRef(ctx, record) {
76
+ return { id: record.id, object: record.object, label: recordLabel(objectOf(ctx.store, record.object), record) };
77
+ }
78
+ function labelOf(ctx, id) {
79
+ const r = recordOf(ctx.store, id);
80
+ return r ? recordLabel(objectOf(ctx.store, r.object), r) : void 0;
81
+ }
82
+ function relatedRecords(ctx, record, fieldId) {
83
+ return relationIds(record.fields[fieldId]).map((id) => recordOf(ctx.store, id)).filter((r) => !!r && !r.deletedAt);
84
+ }
85
+ function inverseRelations(ctx, record) {
86
+ const out = [];
87
+ for (const object of objectsOf(ctx.store)) {
88
+ for (const field of relationFields(object)) {
89
+ if (field.relation.object !== record.object) continue;
90
+ const records = recordsOf(ctx.store, object.id).filter((r) => relationIds(r.fields[field.id]).includes(record.id));
91
+ out.push({
92
+ object: object.id,
93
+ field: field.id,
94
+ fieldLabel: field.label,
95
+ label: object.labelPlural,
96
+ records: records.map((r) => toRef(ctx, r))
97
+ });
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+ function expandRelations(ctx, record) {
103
+ const object = objectOf(ctx.store, record.object);
104
+ const out = {};
105
+ if (!object) return out;
106
+ for (const field of relationFields(object)) out[field.id] = relatedRecords(ctx, record, field.id).map((r) => toRef(ctx, r));
107
+ return out;
108
+ }
109
+ function validateRelationValue(ctx, field, value) {
110
+ const target = field.relation?.object;
111
+ for (const id of relationIds(value)) {
112
+ const r = recordOf(ctx.store, id);
113
+ if (!r || r.deletedAt) return `${field.label}: record "${id}" not found.`;
114
+ if (r.object !== target) return `${field.label}: "${id}" is a ${r.object} record, expected ${target}.`;
115
+ }
116
+ return null;
117
+ }
118
+ function patchFieldMutation(ctx, record, fieldId, value) {
119
+ return {
120
+ op: "patch",
121
+ collection: "records",
122
+ id: record.id,
123
+ fields: { [`fields.${fieldId}`]: value, updatedAt: ctx.now(), updatedBy: actorId(ctx) }
124
+ };
125
+ }
126
+ function rewriteReferences(ctx, fromIds, toId) {
127
+ const from = new Set(fromIds);
128
+ const out = [];
129
+ for (const object of objectsOf(ctx.store)) {
130
+ const fields = relationFields(object);
131
+ if (!fields.length) continue;
132
+ for (const r of recordsOf(ctx.store, object.id, { includeDeleted: true })) {
133
+ if (from.has(r.id)) continue;
134
+ for (const field of fields) {
135
+ const ids = relationIds(r.fields[field.id]);
136
+ if (!ids.some((id) => from.has(id))) continue;
137
+ if (field.relation.cardinality === "many") {
138
+ const next = [...new Set(ids.map((id) => from.has(id) ? toId : id).filter((id) => !!id))];
139
+ out.push(patchFieldMutation(ctx, r, field.id, next.length ? next : void 0));
140
+ } else {
141
+ out.push(patchFieldMutation(ctx, r, field.id, toId ?? void 0));
142
+ }
143
+ }
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ async function linkRecords(ctx, recordId, fieldId, targetId) {
149
+ assertCan(ctx, "records.write");
150
+ const record = requireRecord(ctx.store, recordId);
151
+ const object = requireObject(ctx.store, record.object);
152
+ const field = object.fields.find((f) => f.id === fieldId);
153
+ if (!field || field.type !== "relation") throw new OpsError("invalid", `"${fieldId}" is not a relation field of ${object.labelPlural}.`);
154
+ const err = validateRelationValue(ctx, field, targetId);
155
+ if (err) throw new OpsError("validation", err);
156
+ const ids = relationIds(record.fields[fieldId]);
157
+ const value = field.relation.cardinality === "many" ? [.../* @__PURE__ */ new Set([...ids, targetId])] : targetId;
158
+ await ctx.store.write([patchFieldMutation(ctx, record, fieldId, value)], { message: `Link ${object.labelSingular}` });
159
+ return requireRecord(ctx.store, recordId);
160
+ }
161
+ async function unlinkRecords(ctx, recordId, fieldId, targetId) {
162
+ assertCan(ctx, "records.write");
163
+ const record = requireRecord(ctx.store, recordId);
164
+ const ids = relationIds(record.fields[fieldId]).filter((id) => id !== targetId);
165
+ const object = requireObject(ctx.store, record.object);
166
+ const field = object.fields.find((f) => f.id === fieldId);
167
+ if (!field || field.type !== "relation") throw new OpsError("invalid", `"${fieldId}" is not a relation field of ${object.labelPlural}.`);
168
+ const value = field.relation.cardinality === "many" ? ids.length ? ids : void 0 : ids[0];
169
+ await ctx.store.write([patchFieldMutation(ctx, record, fieldId, value)], { message: `Unlink ${object.labelSingular}` });
170
+ return requireRecord(ctx.store, recordId);
171
+ }
172
+ export {
173
+ relationIds as a,
174
+ fileCleanupMutations as b,
175
+ rewriteReferences as c,
176
+ fileMergeMutations as d,
177
+ fileFieldIds as e,
178
+ fileTargetPatch as f,
179
+ fileIdsIn as g,
180
+ referencedFileIds as h,
181
+ inverseRelations as i,
182
+ expandRelations as j,
183
+ linkRecords as k,
184
+ labelOf as l,
185
+ relatedRecords as m,
186
+ relationFields as r,
187
+ sameTarget as s,
188
+ toRef as t,
189
+ unlinkRecords as u,
190
+ validateRelationValue as v
191
+ };
@@ -0,0 +1,189 @@
1
+ import { createRequire as __acrmRequire } from "node:module";
2
+ import { fileURLToPath as __acrmPath } from "node:url";
3
+ import { dirname as __acrmDirname } from "node:path";
4
+ const require2 = __acrmRequire(import.meta.url);
5
+ const __filename = __acrmPath(import.meta.url);
6
+ const __dirname = __acrmDirname(__filename);
7
+ import { recordLabel, dateValueToMs } from "./fields-B0bitgmr.mjs";
8
+ import { L as LIFECYCLE_OPTIONS } from "./standardObjects-CheqfDqa.mjs";
9
+ import { A as ACTIVITY_TYPES } from "../agentcrm.mjs";
10
+ import { lastActivityAt } from "./activities-Cj_SIOay.mjs";
11
+ import { OpsError, assertCan } from "./context-CxFfqS4D.mjs";
12
+ import { forecast, pipelineOfRecord } from "./pipelines-DCdRBNBV.mjs";
13
+ import { getSettings } from "./settings-BtvR_jdm.mjs";
14
+ import { objectOf, recordsOf, pipelinesOf, usersOf, activitiesOf, requirePipeline } from "./store-5gqRSKXg.mjs";
15
+ import "node:util";
16
+ import "node:fs";
17
+ import "node:path";
18
+ import "node:url";
19
+ import "node:crypto";
20
+ import "node:os";
21
+ import "node:child_process";
22
+ import "node:http";
23
+ import "./ids-CvnmrMJX.mjs";
24
+ import "./query-C5nJB_3C.mjs";
25
+ import "./relations-B9TynBmA.mjs";
26
+ import "./pluginRegistry-DyHtlkcr.mjs";
27
+ import "./records-DoqiT9xR.mjs";
28
+ import "./runtimeConfig-CCOFg1RM.mjs";
29
+ const DAY = 864e5;
30
+ function defaultPipelineId(ctx, pipelineId) {
31
+ if (pipelineId) return requirePipeline(ctx.store, pipelineId).id;
32
+ const p = pipelinesOf(ctx.store, "deals")[0] ?? pipelinesOf(ctx.store)[0];
33
+ if (!p) throw new OpsError("notFound", "No pipeline.");
34
+ return p.id;
35
+ }
36
+ function pipelineByStage(ctx, opts = {}) {
37
+ assertCan(ctx, "reports.read");
38
+ const pipelineId = defaultPipelineId(ctx, opts.pipelineId);
39
+ const pipeline = requirePipeline(ctx.store, pipelineId);
40
+ const f = forecast(ctx, { pipelineId, ownerId: opts.ownerId });
41
+ return {
42
+ pipelineId,
43
+ stages: f.byStage.map((s) => ({ stageId: s.stageId, label: s.label, color: pipeline.stages.find((x) => x.id === s.stageId)?.color ?? "gray", kind: s.kind, count: s.count, amount: s.amount, weighted: s.weighted }))
44
+ };
45
+ }
46
+ function monthKey(ms) {
47
+ const d = new Date(ms);
48
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
49
+ }
50
+ function lastMonths(now, months) {
51
+ const d = new Date(now);
52
+ const out = [];
53
+ for (let i = months - 1; i >= 0; i--) out.push(monthKey(new Date(d.getFullYear(), d.getMonth() - i, 1).getTime()));
54
+ return out;
55
+ }
56
+ function closedAt(r) {
57
+ return dateValueToMs(r.fields.closeDate) ?? r.updatedAt;
58
+ }
59
+ function wonLost(ctx, opts = {}) {
60
+ assertCan(ctx, "reports.read");
61
+ const pipelineId = defaultPipelineId(ctx, opts.pipelineId);
62
+ const pipeline = requirePipeline(ctx.store, pipelineId);
63
+ const months = lastMonths(ctx.now(), Math.max(1, Math.min(36, opts.months ?? 6)));
64
+ const rows = new Map(months.map((m) => [m, { month: m, wonCount: 0, wonAmount: 0, lostCount: 0, lostAmount: 0, winRate: null }]));
65
+ for (const r of recordsOf(ctx.store, pipeline.object)) {
66
+ if (opts.ownerId && r.ownerId !== opts.ownerId) continue;
67
+ if (pipelineOfRecord(ctx, r)?.id !== pipelineId) continue;
68
+ const stage = pipeline.stages.find((s) => s.id === r.fields[pipeline.stageField]);
69
+ if (!stage || stage.kind === "open") continue;
70
+ const row = rows.get(monthKey(closedAt(r)));
71
+ if (!row) continue;
72
+ const amount = typeof r.fields.amount === "number" ? r.fields.amount : 0;
73
+ if (stage.kind === "won") {
74
+ row.wonCount++;
75
+ row.wonAmount += amount;
76
+ } else {
77
+ row.lostCount++;
78
+ row.lostAmount += amount;
79
+ }
80
+ }
81
+ const list = [...rows.values()].map((r) => ({ ...r, winRate: r.wonCount + r.lostCount ? Math.round(r.wonCount / (r.wonCount + r.lostCount) * 1e3) / 10 : null }));
82
+ const t = list.reduce((a, r) => ({ wonCount: a.wonCount + r.wonCount, wonAmount: a.wonAmount + r.wonAmount, lostCount: a.lostCount + r.lostCount, lostAmount: a.lostAmount + r.lostAmount }), { wonCount: 0, wonAmount: 0, lostCount: 0, lostAmount: 0 });
83
+ return { pipelineId, months: list, totals: { ...t, winRate: t.wonCount + t.lostCount ? Math.round(t.wonCount / (t.wonCount + t.lostCount) * 1e3) / 10 : null } };
84
+ }
85
+ function activityByUser(ctx, opts = {}) {
86
+ assertCan(ctx, "reports.read");
87
+ const days = opts.days ?? 30;
88
+ const since = ctx.now() - days * DAY;
89
+ const users = new Map(usersOf(ctx.store).map((u) => [u.id, u.name]));
90
+ const rows = /* @__PURE__ */ new Map();
91
+ const row = (id) => {
92
+ let r = rows.get(id);
93
+ if (!r) {
94
+ r = { userId: id, name: id ? users.get(id) ?? id : "Unassigned", total: 0, byType: Object.fromEntries(ACTIVITY_TYPES.map((t) => [t, 0])), tasksDone: 0, tasksOpen: 0 };
95
+ rows.set(id, r);
96
+ }
97
+ return r;
98
+ };
99
+ for (const a of activitiesOf(ctx.store)) {
100
+ const who = (a.type === "task" ? a.assigneeId ?? a.createdBy : a.createdBy) ?? null;
101
+ if (a.type === "task" && !a.doneAt) row(who).tasksOpen++;
102
+ if (a.createdAt < since && !(a.doneAt && a.doneAt >= since)) continue;
103
+ const r = row(who);
104
+ r.total++;
105
+ r.byType[a.type]++;
106
+ if (a.type === "task" && a.doneAt && a.doneAt >= since) r.tasksDone++;
107
+ }
108
+ return { days, users: [...rows.values()].sort((a, b) => b.total - a.total) };
109
+ }
110
+ function leadSources(ctx, opts = {}) {
111
+ assertCan(ctx, "reports.read");
112
+ const objectId = opts.object ?? "people";
113
+ const object = objectOf(ctx.store, objectId);
114
+ const field = object?.fields.find((f) => f.id === "leadSource");
115
+ if (!object || !field) return { field: "leadSource", sources: [] };
116
+ const counts = /* @__PURE__ */ new Map();
117
+ for (const r of recordsOf(ctx.store, objectId)) {
118
+ if (opts.since && r.createdAt < opts.since) continue;
119
+ const id = typeof r.fields.leadSource === "string" ? r.fields.leadSource : null;
120
+ const entry = counts.get(id) ?? { id, label: id ? field.options?.find((o) => o.id === id)?.label ?? id : "Unknown", count: 0, customers: 0 };
121
+ entry.count++;
122
+ if (r.fields.lifecycleStage === "customer" || r.fields.lifecycleStage === "evangelist") entry.customers++;
123
+ counts.set(id, entry);
124
+ }
125
+ return { field: "leadSource", sources: [...counts.values()].sort((a, b) => b.count - a.count) };
126
+ }
127
+ function staleDeals(ctx, opts = {}) {
128
+ assertCan(ctx, "reports.read");
129
+ const days = opts.days ?? getSettings(ctx).staleDealDays;
130
+ const now = ctx.now();
131
+ const out = [];
132
+ for (const pipeline of pipelinesOf(ctx.store)) {
133
+ if (opts.pipelineId && pipeline.id !== opts.pipelineId) continue;
134
+ const object = objectOf(ctx.store, pipeline.object);
135
+ for (const r of recordsOf(ctx.store, pipeline.object)) {
136
+ if (pipelineOfRecord(ctx, r)?.id !== pipeline.id) continue;
137
+ const stage = pipeline.stages.find((s) => s.id === r.fields[pipeline.stageField]);
138
+ if (!stage || stage.kind !== "open") continue;
139
+ const last = Math.max(lastActivityAt(ctx, r.id) ?? 0, r.updatedAt);
140
+ const idle = Math.floor((now - last) / DAY);
141
+ if (idle < days) continue;
142
+ out.push({ id: r.id, label: recordLabel(object, r), stage: stage.label, amount: typeof r.fields.amount === "number" ? r.fields.amount : 0, ownerId: r.ownerId, lastActivityAt: lastActivityAt(ctx, r.id) ?? null, idleDays: idle });
143
+ }
144
+ }
145
+ out.sort((a, b) => b.idleDays - a.idleDays || b.amount - a.amount);
146
+ return { days, deals: opts.limit ? out.slice(0, opts.limit) : out };
147
+ }
148
+ function conversion(ctx, opts = {}) {
149
+ assertCan(ctx, "reports.read");
150
+ const object = objectOf(ctx.store, "people");
151
+ const field = object?.fields.find((f) => f.id === "lifecycleStage");
152
+ const options = field?.options ?? LIFECYCLE_OPTIONS;
153
+ const counts = new Map(options.map((o) => [o.id, 0]));
154
+ for (const r of recordsOf(ctx.store, "people")) {
155
+ if (opts.since && r.createdAt < opts.since) continue;
156
+ const v = r.fields.lifecycleStage;
157
+ if (typeof v === "string" && counts.has(v)) counts.set(v, counts.get(v) + 1);
158
+ }
159
+ const stages = options.map((o, i) => ({ id: o.id, label: o.label, count: counts.get(o.id) ?? 0, reached: options.slice(i).reduce((a, x) => a + (counts.get(x.id) ?? 0), 0), conversionFromPrevious: null }));
160
+ for (let i = 1; i < stages.length; i++) {
161
+ const prev = stages[i - 1].reached;
162
+ stages[i].conversionFromPrevious = prev ? Math.round(stages[i].reached / prev * 1e3) / 10 : null;
163
+ }
164
+ return { stages };
165
+ }
166
+ const REPORTS = {
167
+ pipelineByStage: { description: "Open pipeline by stage: count, amount, weighted amount.", run: pipelineByStage },
168
+ forecast: { description: "Weighted forecast (amount × probability) by stage and close month.", run: forecast },
169
+ wonLost: { description: "Won vs lost deals per month with win rate (params: months).", run: wonLost },
170
+ activityByUser: { description: "Activities logged per user over the last N days (params: days).", run: activityByUser },
171
+ leadSources: { description: "People per lead source, with how many became customers.", run: leadSources },
172
+ staleDeals: { description: "Open deals without activity for N days (params: days).", run: staleDeals },
173
+ conversion: { description: "Lifecycle stage funnel with step conversion rates.", run: conversion }
174
+ };
175
+ function runReport(ctx, name, params = {}) {
176
+ const report = REPORTS[name];
177
+ if (!report) throw new OpsError("notFound", `Unknown report "${name}". Reports: ${Object.keys(REPORTS).join(", ")}.`);
178
+ return report.run(ctx, params);
179
+ }
180
+ export {
181
+ REPORTS,
182
+ activityByUser,
183
+ conversion,
184
+ leadSources,
185
+ pipelineByStage,
186
+ runReport,
187
+ staleDeals,
188
+ wonLost
189
+ };