@primocaredentgroup/compensi-medici-core 0.1.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 (38) hide show
  1. package/convex/_generated/api.ts +110 -0
  2. package/convex/_generated/component.ts +1356 -0
  3. package/convex/_generated/dataModel.ts +60 -0
  4. package/convex/_generated/server.ts +156 -0
  5. package/convex/audit/logs.ts +69 -0
  6. package/convex/calculation/commissions.ts +497 -0
  7. package/convex/calculation/deductions.ts +119 -0
  8. package/convex/calculation/engine.ts +598 -0
  9. package/convex/calculation/fixedCommissions.ts +217 -0
  10. package/convex/calculation/orchestrator.ts +314 -0
  11. package/convex/calculation/production.ts +495 -0
  12. package/convex/calculation/productionData.ts +155 -0
  13. package/convex/calculation/ruleApplications.ts +121 -0
  14. package/convex/config/categories.ts +114 -0
  15. package/convex/config/commissionPlans.ts +166 -0
  16. package/convex/config/commissionRules.ts +154 -0
  17. package/convex/config/companyMappings.ts +77 -0
  18. package/convex/config/deductionRules.ts +113 -0
  19. package/convex/config/fixedCommissionTypes.ts +79 -0
  20. package/convex/config/globalSettings.ts +79 -0
  21. package/convex/config/invisalignConfig.ts +87 -0
  22. package/convex/config/planTemplates.ts +90 -0
  23. package/convex/config/taxProfiles.ts +122 -0
  24. package/convex/config/userTaxProfiles.ts +87 -0
  25. package/convex/convex.config.ts +3 -0
  26. package/convex/documents/draftInvoices.ts +164 -0
  27. package/convex/documents/invoiceEngine.ts +468 -0
  28. package/convex/documents/invoiceGeneration.ts +611 -0
  29. package/convex/documents/statements.ts +185 -0
  30. package/convex/ledger/entries.ts +314 -0
  31. package/convex/lib/types.ts +126 -0
  32. package/convex/schema.ts +611 -0
  33. package/convex/seedHelpers.ts +41 -0
  34. package/convex/workflow/pendingCompletions.ts +148 -0
  35. package/convex/workflow/pythonWorker.ts +190 -0
  36. package/convex/workflow/runIssues.ts +364 -0
  37. package/convex/workflow/runs.ts +718 -0
  38. package/package.json +43 -0
@@ -0,0 +1,121 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+
4
+ /**
5
+ * CRUD e query per ruleApplications — spiegabilità del calcolo.
6
+ *
7
+ * Da analisi.md:
8
+ * - "spiegazione delle regole applicate"
9
+ * - "tutto spiegabile"
10
+ * - "differenze spiegabili" (criterio di successo)
11
+ * - Alla chiusura: "salvataggio delle regole applicate"
12
+ *
13
+ * Ogni ruleApplication collega:
14
+ * - sourceLedgerEntryId → riga di produzione/storno che ha innescato il calcolo
15
+ * - generatedLedgerEntryId → riga commission generata
16
+ * - planId, ruleId → configurazione usata
17
+ * - matchedConditions → snapshot delle condizioni che hanno matchato
18
+ * - formulaSnapshot → snapshot della formula applicata
19
+ * - formulaInput/Output → dettaglio del calcolo
20
+ * - explanationLabel → etichetta leggibile per HR/medici
21
+ *
22
+ * Le ruleApplications vengono scritte da commissions.ts (refreshCommissions).
23
+ * Le funzioni qui sotto servono per query e manutenzione.
24
+ */
25
+
26
+ export const create = mutation({
27
+ args: {
28
+ runId: v.id("compensationRuns"),
29
+ sourceLedgerEntryId: v.id("ledgerEntries"),
30
+ generatedLedgerEntryId: v.id("ledgerEntries"),
31
+ planId: v.id("commissionPlans"),
32
+ ruleId: v.id("commissionRules"),
33
+ matchedConditions: v.any(),
34
+ formulaSnapshot: v.any(),
35
+ formulaInput: v.any(),
36
+ formulaOutput: v.any(),
37
+ resultAmount: v.number(),
38
+ explanationLabel: v.string(),
39
+ },
40
+ handler: async (ctx, args) => {
41
+ return await ctx.db.insert("ruleApplications", {
42
+ ...args,
43
+ createdAt: Date.now(),
44
+ });
45
+ },
46
+ });
47
+
48
+ /** Tutte le ruleApplications per una riga sorgente (produzione/storno) */
49
+ export const listBySourceEntry = query({
50
+ args: { sourceLedgerEntryId: v.id("ledgerEntries") },
51
+ handler: async (ctx, args) => {
52
+ return await ctx.db
53
+ .query("ruleApplications")
54
+ .withIndex("by_source_entry", (q) =>
55
+ q.eq("sourceLedgerEntryId", args.sourceLedgerEntryId),
56
+ )
57
+ .collect();
58
+ },
59
+ });
60
+
61
+ /** Tutte le ruleApplications per una riga commission generata */
62
+ export const listByGeneratedEntry = query({
63
+ args: { generatedLedgerEntryId: v.id("ledgerEntries") },
64
+ handler: async (ctx, args) => {
65
+ return await ctx.db
66
+ .query("ruleApplications")
67
+ .withIndex("by_generated_entry", (q) =>
68
+ q.eq("generatedLedgerEntryId", args.generatedLedgerEntryId),
69
+ )
70
+ .collect();
71
+ },
72
+ });
73
+
74
+ /** Tutte le ruleApplications in un run */
75
+ export const listByRun = query({
76
+ args: { runId: v.id("compensationRuns") },
77
+ handler: async (ctx, args) => {
78
+ return await ctx.db
79
+ .query("ruleApplications")
80
+ .withIndex("by_run", (q) => q.eq("runId", args.runId))
81
+ .collect();
82
+ },
83
+ });
84
+
85
+ /** Applicazioni di una specifica regola in un run */
86
+ export const listByRunAndRule = query({
87
+ args: {
88
+ runId: v.id("compensationRuns"),
89
+ ruleId: v.id("commissionRules"),
90
+ },
91
+ handler: async (ctx, args) => {
92
+ return await ctx.db
93
+ .query("ruleApplications")
94
+ .withIndex("by_run_rule", (q) =>
95
+ q.eq("runId", args.runId).eq("ruleId", args.ruleId),
96
+ )
97
+ .collect();
98
+ },
99
+ });
100
+
101
+ /**
102
+ * Cancella tutte le ruleApplications di un run.
103
+ * Usato dal refreshCommissions prima del ricalcolo.
104
+ */
105
+ export const deleteByRun = mutation({
106
+ args: {
107
+ runId: v.id("compensationRuns"),
108
+ },
109
+ handler: async (ctx, args) => {
110
+ const applications = await ctx.db
111
+ .query("ruleApplications")
112
+ .withIndex("by_run", (q) => q.eq("runId", args.runId))
113
+ .collect();
114
+
115
+ for (const app of applications) {
116
+ await ctx.db.delete(app._id);
117
+ }
118
+
119
+ return { deletedCount: applications.length };
120
+ },
121
+ });
@@ -0,0 +1,114 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+
4
+ /**
5
+ * Categorie rimborsi e rettifiche — gestite come entità DB.
6
+ * Da analisi.md: rimborsi e rettifiche hanno "categoria, importo,
7
+ * segno (+/-), autore, motivazione".
8
+ *
9
+ * Le categorie sono configurabili da UI, non hardcoded.
10
+ * Separate in due tabelle (reimbursementCategories, adjustmentCategories)
11
+ * per semantica chiara, ma con CRUD identico.
12
+ */
13
+
14
+ // ── Reimbursement Categories ─────────────────────────────
15
+
16
+ export const createReimbursementCategory = mutation({
17
+ args: {
18
+ code: v.string(),
19
+ label: v.string(),
20
+ description: v.optional(v.string()),
21
+ sortOrder: v.number(),
22
+ },
23
+ handler: async (ctx, args) => {
24
+ const now = Date.now();
25
+ return await ctx.db.insert("reimbursementCategories", {
26
+ ...args,
27
+ isActive: true,
28
+ createdAt: now,
29
+ updatedAt: now,
30
+ });
31
+ },
32
+ });
33
+
34
+ export const listReimbursementCategories = query({
35
+ args: { activeOnly: v.optional(v.boolean()) },
36
+ handler: async (ctx, args) => {
37
+ if (args.activeOnly) {
38
+ return await ctx.db
39
+ .query("reimbursementCategories")
40
+ .withIndex("by_active", (q) => q.eq("isActive", true))
41
+ .collect();
42
+ }
43
+ return await ctx.db.query("reimbursementCategories").collect();
44
+ },
45
+ });
46
+
47
+ export const updateReimbursementCategory = mutation({
48
+ args: {
49
+ categoryId: v.id("reimbursementCategories"),
50
+ label: v.optional(v.string()),
51
+ description: v.optional(v.string()),
52
+ isActive: v.optional(v.boolean()),
53
+ sortOrder: v.optional(v.number()),
54
+ },
55
+ handler: async (ctx, args) => {
56
+ const { categoryId, ...fields } = args;
57
+ const patch: Record<string, unknown> = { updatedAt: Date.now() };
58
+ for (const [k, val] of Object.entries(fields)) {
59
+ if (val !== undefined) patch[k] = val;
60
+ }
61
+ await ctx.db.patch(categoryId, patch);
62
+ },
63
+ });
64
+
65
+ // ── Adjustment Categories ────────────────────────────────
66
+
67
+ export const createAdjustmentCategory = mutation({
68
+ args: {
69
+ code: v.string(),
70
+ label: v.string(),
71
+ description: v.optional(v.string()),
72
+ sortOrder: v.number(),
73
+ },
74
+ handler: async (ctx, args) => {
75
+ const now = Date.now();
76
+ return await ctx.db.insert("adjustmentCategories", {
77
+ ...args,
78
+ isActive: true,
79
+ createdAt: now,
80
+ updatedAt: now,
81
+ });
82
+ },
83
+ });
84
+
85
+ export const listAdjustmentCategories = query({
86
+ args: { activeOnly: v.optional(v.boolean()) },
87
+ handler: async (ctx, args) => {
88
+ if (args.activeOnly) {
89
+ return await ctx.db
90
+ .query("adjustmentCategories")
91
+ .withIndex("by_active", (q) => q.eq("isActive", true))
92
+ .collect();
93
+ }
94
+ return await ctx.db.query("adjustmentCategories").collect();
95
+ },
96
+ });
97
+
98
+ export const updateAdjustmentCategory = mutation({
99
+ args: {
100
+ categoryId: v.id("adjustmentCategories"),
101
+ label: v.optional(v.string()),
102
+ description: v.optional(v.string()),
103
+ isActive: v.optional(v.boolean()),
104
+ sortOrder: v.optional(v.number()),
105
+ },
106
+ handler: async (ctx, args) => {
107
+ const { categoryId, ...fields } = args;
108
+ const patch: Record<string, unknown> = { updatedAt: Date.now() };
109
+ for (const [k, val] of Object.entries(fields)) {
110
+ if (val !== undefined) patch[k] = val;
111
+ }
112
+ await ctx.db.patch(categoryId, patch);
113
+ },
114
+ });
@@ -0,0 +1,166 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+ import { planStatus } from "../schema";
4
+
5
+ /**
6
+ * Gestione piani provvigionali.
7
+ * Da analisi.md: "ogni piano è associato a un medico,
8
+ * può variare per clinica, categoria, prestazione, convenzione".
9
+ *
10
+ * Requisiti: nessun hardcoded, configurabile via UI,
11
+ * importabile da CSV/Excel, basato su template, spiegabile.
12
+ */
13
+
14
+ export const create = mutation({
15
+ args: {
16
+ userId: v.string(),
17
+ code: v.string(),
18
+ name: v.string(),
19
+ description: v.optional(v.string()),
20
+ effectiveFrom: v.number(),
21
+ effectiveTo: v.optional(v.number()),
22
+ templateId: v.optional(v.id("commissionPlanTemplates")),
23
+ notes: v.optional(v.string()),
24
+ createdBy: v.string(),
25
+ },
26
+ handler: async (ctx, args) => {
27
+ const now = Date.now();
28
+ const { createdBy, ...fields } = args;
29
+
30
+ const planId = await ctx.db.insert("commissionPlans", {
31
+ ...fields,
32
+ status: "draft",
33
+ createdBy,
34
+ createdAt: now,
35
+ updatedBy: createdBy,
36
+ updatedAt: now,
37
+ });
38
+
39
+ await ctx.db.insert("auditLogs", {
40
+ entityType: "commissionPlans",
41
+ entityId: planId,
42
+ action: "created",
43
+ userId: createdBy,
44
+ payload: { code: args.code, name: args.name, userId: args.userId },
45
+ createdAt: now,
46
+ });
47
+
48
+ return planId;
49
+ },
50
+ });
51
+
52
+ export const update = mutation({
53
+ args: {
54
+ planId: v.id("commissionPlans"),
55
+ name: v.optional(v.string()),
56
+ description: v.optional(v.string()),
57
+ effectiveFrom: v.optional(v.number()),
58
+ effectiveTo: v.optional(v.number()),
59
+ notes: v.optional(v.string()),
60
+ updatedBy: v.string(),
61
+ },
62
+ handler: async (ctx, args) => {
63
+ const { planId, updatedBy, ...fields } = args;
64
+ const now = Date.now();
65
+
66
+ const patch: Record<string, unknown> = { updatedBy, updatedAt: now };
67
+ for (const [k, val] of Object.entries(fields)) {
68
+ if (val !== undefined) patch[k] = val;
69
+ }
70
+
71
+ await ctx.db.patch(planId, patch);
72
+
73
+ await ctx.db.insert("auditLogs", {
74
+ entityType: "commissionPlans",
75
+ entityId: planId,
76
+ action: "updated",
77
+ userId: updatedBy,
78
+ payload: patch,
79
+ createdAt: now,
80
+ });
81
+ },
82
+ });
83
+
84
+ /** Cambia stato del piano: draft → active → archived */
85
+ export const changeStatus = mutation({
86
+ args: {
87
+ planId: v.id("commissionPlans"),
88
+ newStatus: planStatus,
89
+ updatedBy: v.string(),
90
+ },
91
+ handler: async (ctx, args) => {
92
+ const plan = await ctx.db.get(args.planId);
93
+ if (!plan) throw new Error("Piano non trovato");
94
+
95
+ const VALID: Record<string, string[]> = {
96
+ draft: ["active"],
97
+ active: ["archived", "draft"],
98
+ archived: ["draft"],
99
+ };
100
+
101
+ if (!VALID[plan.status]?.includes(args.newStatus)) {
102
+ throw new Error(
103
+ `Transizione piano non valida: ${plan.status} → ${args.newStatus}`,
104
+ );
105
+ }
106
+
107
+ const now = Date.now();
108
+ await ctx.db.patch(args.planId, {
109
+ status: args.newStatus,
110
+ updatedBy: args.updatedBy,
111
+ updatedAt: now,
112
+ });
113
+
114
+ await ctx.db.insert("auditLogs", {
115
+ entityType: "commissionPlans",
116
+ entityId: args.planId,
117
+ action: "status_changed",
118
+ userId: args.updatedBy,
119
+ payload: { from: plan.status, to: args.newStatus },
120
+ createdAt: now,
121
+ });
122
+ },
123
+ });
124
+
125
+ export const get = query({
126
+ args: { planId: v.id("commissionPlans") },
127
+ handler: async (ctx, args) => {
128
+ return await ctx.db.get(args.planId);
129
+ },
130
+ });
131
+
132
+ export const listByUser = query({
133
+ args: {
134
+ userId: v.string(),
135
+ status: v.optional(planStatus),
136
+ },
137
+ handler: async (ctx, args) => {
138
+ if (args.status) {
139
+ return await ctx.db
140
+ .query("commissionPlans")
141
+ .withIndex("by_user_status", (q) =>
142
+ q.eq("userId", args.userId).eq("status", args.status!),
143
+ )
144
+ .collect();
145
+ }
146
+ return await ctx.db
147
+ .query("commissionPlans")
148
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
149
+ .collect();
150
+ },
151
+ });
152
+
153
+ export const listActive = query({
154
+ handler: async (ctx) => {
155
+ return await ctx.db
156
+ .query("commissionPlans")
157
+ .withIndex("by_status", (q) => q.eq("status", "active"))
158
+ .collect();
159
+ },
160
+ });
161
+
162
+ export const listAll = query({
163
+ handler: async (ctx) => {
164
+ return await ctx.db.query("commissionPlans").collect();
165
+ },
166
+ });
@@ -0,0 +1,154 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+ import { ruleType, ruleOutputType } from "../schema";
4
+
5
+ /**
6
+ * Gestione regole di calcolo all'interno di un piano provvigionale.
7
+ * Da analisi.md ogni regola ha:
8
+ * 1. Matching (conditions) — quando si applica
9
+ * 2. Formula — percentuale / fisso / gettone / combinazioni
10
+ * 3. Trattamenti economici — deduzioni, esclusioni, moltiplicatori
11
+ *
12
+ * conditions e formula restano JSON per flessibilità,
13
+ * ma i campi espliciti (ruleType, outputType, explanationLabel)
14
+ * danno semantica leggibile al dominio.
15
+ */
16
+
17
+ export const create = mutation({
18
+ args: {
19
+ planId: v.id("commissionPlans"),
20
+ name: v.string(),
21
+ ruleType: ruleType,
22
+ priority: v.number(),
23
+ outputType: ruleOutputType,
24
+ explanationLabel: v.string(),
25
+ conditions: v.any(),
26
+ formula: v.any(),
27
+ validFrom: v.optional(v.number()),
28
+ validTo: v.optional(v.number()),
29
+ createdBy: v.string(),
30
+ },
31
+ handler: async (ctx, args) => {
32
+ const now = Date.now();
33
+ const { createdBy, ...fields } = args;
34
+
35
+ const ruleId = await ctx.db.insert("commissionRules", {
36
+ ...fields,
37
+ isActive: true,
38
+ createdBy,
39
+ createdAt: now,
40
+ updatedAt: now,
41
+ });
42
+
43
+ await ctx.db.insert("auditLogs", {
44
+ entityType: "commissionRules",
45
+ entityId: ruleId,
46
+ action: "created",
47
+ userId: createdBy,
48
+ payload: { planId: args.planId, name: args.name, ruleType: args.ruleType },
49
+ createdAt: now,
50
+ });
51
+
52
+ return ruleId;
53
+ },
54
+ });
55
+
56
+ export const update = mutation({
57
+ args: {
58
+ ruleId: v.id("commissionRules"),
59
+ name: v.optional(v.string()),
60
+ ruleType: v.optional(ruleType),
61
+ priority: v.optional(v.number()),
62
+ outputType: v.optional(ruleOutputType),
63
+ explanationLabel: v.optional(v.string()),
64
+ conditions: v.optional(v.any()),
65
+ formula: v.optional(v.any()),
66
+ validFrom: v.optional(v.number()),
67
+ validTo: v.optional(v.number()),
68
+ updatedBy: v.string(),
69
+ },
70
+ handler: async (ctx, args) => {
71
+ const { ruleId, updatedBy, ...fields } = args;
72
+ const now = Date.now();
73
+
74
+ const patch: Record<string, unknown> = { updatedAt: now };
75
+ for (const [k, val] of Object.entries(fields)) {
76
+ if (val !== undefined) patch[k] = val;
77
+ }
78
+
79
+ await ctx.db.patch(ruleId, patch);
80
+
81
+ await ctx.db.insert("auditLogs", {
82
+ entityType: "commissionRules",
83
+ entityId: ruleId,
84
+ action: "updated",
85
+ userId: updatedBy,
86
+ payload: patch,
87
+ createdAt: now,
88
+ });
89
+ },
90
+ });
91
+
92
+ export const setActive = mutation({
93
+ args: {
94
+ ruleId: v.id("commissionRules"),
95
+ isActive: v.boolean(),
96
+ updatedBy: v.string(),
97
+ },
98
+ handler: async (ctx, args) => {
99
+ const now = Date.now();
100
+ await ctx.db.patch(args.ruleId, {
101
+ isActive: args.isActive,
102
+ updatedAt: now,
103
+ });
104
+
105
+ await ctx.db.insert("auditLogs", {
106
+ entityType: "commissionRules",
107
+ entityId: args.ruleId,
108
+ action: args.isActive ? "activated" : "deactivated",
109
+ userId: args.updatedBy,
110
+ createdAt: now,
111
+ });
112
+ },
113
+ });
114
+
115
+ export const remove = mutation({
116
+ args: {
117
+ ruleId: v.id("commissionRules"),
118
+ userId: v.string(),
119
+ },
120
+ handler: async (ctx, args) => {
121
+ const rule = await ctx.db.get(args.ruleId);
122
+ await ctx.db.delete(args.ruleId);
123
+
124
+ await ctx.db.insert("auditLogs", {
125
+ entityType: "commissionRules",
126
+ entityId: args.ruleId,
127
+ action: "deleted",
128
+ userId: args.userId,
129
+ payload: rule ? { planId: rule.planId, name: rule.name } : undefined,
130
+ createdAt: Date.now(),
131
+ });
132
+ },
133
+ });
134
+
135
+ export const listByPlan = query({
136
+ args: {
137
+ planId: v.id("commissionPlans"),
138
+ activeOnly: v.optional(v.boolean()),
139
+ },
140
+ handler: async (ctx, args) => {
141
+ if (args.activeOnly) {
142
+ return await ctx.db
143
+ .query("commissionRules")
144
+ .withIndex("by_plan_active", (q) =>
145
+ q.eq("planId", args.planId).eq("isActive", true),
146
+ )
147
+ .collect();
148
+ }
149
+ return await ctx.db
150
+ .query("commissionRules")
151
+ .withIndex("by_plan", (q) => q.eq("planId", args.planId))
152
+ .collect();
153
+ },
154
+ });
@@ -0,0 +1,77 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "../_generated/server";
3
+
4
+ const categoryValidator = v.union(
5
+ v.literal("DS"),
6
+ v.literal("Est"),
7
+ v.literal("Est_forfettari"),
8
+ );
9
+
10
+ export const create = mutation({
11
+ args: {
12
+ fromCompanyId: v.string(),
13
+ toCompanyId: v.string(),
14
+ category: categoryValidator,
15
+ },
16
+ handler: async (ctx, args) => {
17
+ const now = Date.now();
18
+ return await ctx.db.insert("companyMappings", {
19
+ ...args,
20
+ isActive: true,
21
+ createdAt: now,
22
+ updatedAt: now,
23
+ });
24
+ },
25
+ });
26
+
27
+ export const update = mutation({
28
+ args: {
29
+ mappingId: v.id("companyMappings"),
30
+ toCompanyId: v.optional(v.string()),
31
+ category: v.optional(categoryValidator),
32
+ isActive: v.optional(v.boolean()),
33
+ },
34
+ handler: async (ctx, args) => {
35
+ const { mappingId, ...fields } = args;
36
+ const patch: Record<string, unknown> = { updatedAt: Date.now() };
37
+ for (const [k, val] of Object.entries(fields)) {
38
+ if (val !== undefined) patch[k] = val;
39
+ }
40
+ await ctx.db.patch(mappingId, patch);
41
+ },
42
+ });
43
+
44
+ export const get = query({
45
+ args: { mappingId: v.id("companyMappings") },
46
+ handler: async (ctx, args) => {
47
+ return await ctx.db.get(args.mappingId);
48
+ },
49
+ });
50
+
51
+ export const listByCategory = query({
52
+ args: { category: categoryValidator },
53
+ handler: async (ctx, args) => {
54
+ return await ctx.db
55
+ .query("companyMappings")
56
+ .withIndex("by_category", (q) =>
57
+ q.eq("category", args.category).eq("isActive", true),
58
+ )
59
+ .collect();
60
+ },
61
+ });
62
+
63
+ export const listAll = query({
64
+ handler: async (ctx) => {
65
+ return await ctx.db.query("companyMappings").collect();
66
+ },
67
+ });
68
+
69
+ export const getByFromCompany = query({
70
+ args: { fromCompanyId: v.string() },
71
+ handler: async (ctx, args) => {
72
+ return await ctx.db
73
+ .query("companyMappings")
74
+ .withIndex("by_from", (q) => q.eq("fromCompanyId", args.fromCompanyId))
75
+ .collect();
76
+ },
77
+ });