@fayz-ai/plugin-financial 0.2.0 → 0.8.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.
- package/README.md +2 -0
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +11 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +64 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +64 -52
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +16 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/registries.d.ts.map +1 -1
- package/package.json +10 -4
- package/src/README.md +5 -5
- package/src/contract.test.ts +1 -1
- package/src/data/supabase.ts +50 -47
- package/src/data/tables.ts +12 -0
- package/src/migrations/000_plg_rename.sql +30 -0
- package/src/migrations/001_financial_base.sql +30 -30
- package/src/migrations/002_chart_of_accounts.sql +10 -10
- package/src/migrations/003_card_brands.sql +4 -4
- package/src/migrations/004_order_to_cash.sql +28 -28
- package/src/migrations/005_seed_defaults.sql +6 -6
- package/src/migrations/006_rls_policies.sql +5 -5
- package/src/migrations/{006_extract_fee_amount.sql → 006b_extract_fee_amount.sql} +4 -4
- package/src/migrations/007_reconciliation.sql +9 -9
- package/src/migrations/007b_movement_payment_method_type.sql +6 -0
- package/src/migrations/008_split_payment_movements.sql +5 -5
- package/src/migrations/index.ts +544 -0
- package/src/registries.ts +8 -7
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fayz-ai/plugin-financial)
|
|
6
6
|
[](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
+
**Status:** beta — pre-1.0. Core surface is stable; some backend facets (see `PLUGIN_PATTERNS.md`) are still landing. APIs may change before 1.0.
|
|
9
|
+
|
|
8
10
|
Most vertical SaaS bolts on finance as an afterthought: a "total" field, a spreadsheet export, and a prayer. `plugin-financial` makes money a first-class part of the app. It models the full order-to-cash loop — receivables, payables, cash registers, statements, card reconciliation, and commission rules — as composable modules you toggle per business.
|
|
9
11
|
|
|
10
12
|
It's built to be the financial spine other plugins lean on. Agenda creates orders against it, reports read from it, and the dashboard surfaces balance, cash flow, and overdue alerts without bespoke glue. Every module is optional, every label and currency is configurable, and item lines can resolve products and services from other plugins via entity lookups. Compose a real, money-aware SaaS instead of stitching one together.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA0JpD,wBAAgB,+BAA+B,IAAI,qBAAqB,CAytBvE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const T: {
|
|
2
|
+
readonly movements: "plg_financial_movements";
|
|
3
|
+
readonly chartOfAccounts: "plg_financial_chart_of_accounts";
|
|
4
|
+
readonly bankAccounts: "plg_financial_bank_accounts";
|
|
5
|
+
readonly cardBrands: "plg_financial_card_brands";
|
|
6
|
+
readonly costCenters: "plg_financial_cost_centers";
|
|
7
|
+
readonly cashRegisterSessions: "plg_financial_cash_register_sessions";
|
|
8
|
+
readonly paymentMethods: "plg_financial_payment_methods";
|
|
9
|
+
readonly paymentMethodTypes: "plg_financial_payment_method_types";
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;CASJ,CAAA"}
|
package/dist/index.cjs
CHANGED
|
@@ -5037,6 +5037,18 @@ function createMockFinancialProvider(options) {
|
|
|
5037
5037
|
return provider;
|
|
5038
5038
|
}
|
|
5039
5039
|
|
|
5040
|
+
// src/data/tables.ts
|
|
5041
|
+
var T = {
|
|
5042
|
+
movements: "plg_financial_movements",
|
|
5043
|
+
chartOfAccounts: "plg_financial_chart_of_accounts",
|
|
5044
|
+
bankAccounts: "plg_financial_bank_accounts",
|
|
5045
|
+
cardBrands: "plg_financial_card_brands",
|
|
5046
|
+
costCenters: "plg_financial_cost_centers",
|
|
5047
|
+
cashRegisterSessions: "plg_financial_cash_register_sessions",
|
|
5048
|
+
paymentMethods: "plg_financial_payment_methods",
|
|
5049
|
+
paymentMethodTypes: "plg_financial_payment_method_types"
|
|
5050
|
+
};
|
|
5051
|
+
|
|
5040
5052
|
// src/data/supabase.ts
|
|
5041
5053
|
function getTenantId() {
|
|
5042
5054
|
return core.getActiveTenantId();
|
|
@@ -5059,7 +5071,7 @@ function camelToSnake(obj) {
|
|
|
5059
5071
|
function getClients() {
|
|
5060
5072
|
const supabase = core.getSupabaseClientOptional();
|
|
5061
5073
|
if (!supabase) throw new Error("Supabase not initialized");
|
|
5062
|
-
return { core: supabase
|
|
5074
|
+
return { core: supabase, pub: supabase };
|
|
5063
5075
|
}
|
|
5064
5076
|
function mapStageToInvoiceStatus(stage) {
|
|
5065
5077
|
switch (stage) {
|
|
@@ -5086,11 +5098,11 @@ async function sweepOverdue() {
|
|
|
5086
5098
|
try {
|
|
5087
5099
|
const { core, pub } = getClients();
|
|
5088
5100
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5089
|
-
const { data: overdueMovs } = await pub.from(
|
|
5101
|
+
const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
|
|
5090
5102
|
if (overdueMovs && overdueMovs.length > 0) {
|
|
5091
5103
|
const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
|
|
5092
5104
|
for (const invId of invoiceIds) {
|
|
5093
|
-
const { data: allMovs } = await pub.from(
|
|
5105
|
+
const { data: allMovs } = await pub.from(T.movements).select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
|
|
5094
5106
|
const movs = allMovs ?? [];
|
|
5095
5107
|
const hasOverdue = movs.some((m) => m.status === "overdue");
|
|
5096
5108
|
const allPaid = movs.every((m) => m.status === "paid");
|
|
@@ -5128,7 +5140,7 @@ async function findOrderIdsByItemMetadata(core, filters) {
|
|
|
5128
5140
|
}
|
|
5129
5141
|
async function getMethodFeePct(pub, paymentMethodId) {
|
|
5130
5142
|
if (!paymentMethodId) return 0;
|
|
5131
|
-
const { data } = await pub.from(
|
|
5143
|
+
const { data } = await pub.from(T.paymentMethods).select("discount_value").eq("id", paymentMethodId).single();
|
|
5132
5144
|
return Number(data?.discount_value) || 0;
|
|
5133
5145
|
}
|
|
5134
5146
|
function genId() {
|
|
@@ -5200,7 +5212,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5200
5212
|
const o = snakeToCamel(data);
|
|
5201
5213
|
const meta = data.metadata ?? {};
|
|
5202
5214
|
const balance = (await fetchInvoiceBalances(pub, [id])).get(id);
|
|
5203
|
-
const { data: movs } = await pub.from(
|
|
5215
|
+
const { data: movs } = await pub.from(T.movements).select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
|
|
5204
5216
|
return {
|
|
5205
5217
|
id: o.id,
|
|
5206
5218
|
direction: o.direction ?? (o.kind === "invoice_payable" ? "debit" : "credit"),
|
|
@@ -5287,7 +5299,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5287
5299
|
);
|
|
5288
5300
|
}
|
|
5289
5301
|
if (order && input.installments.length > 0) {
|
|
5290
|
-
await pub.from(
|
|
5302
|
+
await pub.from(T.movements).insert(
|
|
5291
5303
|
input.installments.map((inst, i) => ({
|
|
5292
5304
|
tenant_id: tenantId,
|
|
5293
5305
|
invoice_id: order.id,
|
|
@@ -5329,12 +5341,12 @@ function createSupabaseFinancialProvider() {
|
|
|
5329
5341
|
async cancelInvoice(id) {
|
|
5330
5342
|
const { core, pub } = getClients();
|
|
5331
5343
|
await core.from("orders").update({ status: "cancelled" }).eq("id", id);
|
|
5332
|
-
await pub.from(
|
|
5344
|
+
await pub.from(T.movements).update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
|
|
5333
5345
|
},
|
|
5334
5346
|
// --- Movements (public.financial_movements) ---
|
|
5335
5347
|
async getMovements(query) {
|
|
5336
5348
|
const { pub } = getClients();
|
|
5337
|
-
let qb = pub.from(
|
|
5349
|
+
let qb = pub.from(T.movements).select("*", { count: "exact" });
|
|
5338
5350
|
if (query.direction) qb = qb.eq("direction", query.direction);
|
|
5339
5351
|
if (query.status) {
|
|
5340
5352
|
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
@@ -5351,7 +5363,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5351
5363
|
},
|
|
5352
5364
|
async payMovement(input) {
|
|
5353
5365
|
const { core, pub } = getClients();
|
|
5354
|
-
const { data: mov } = await pub.from(
|
|
5366
|
+
const { data: mov } = await pub.from(T.movements).select("*").eq("id", input.movementId).single();
|
|
5355
5367
|
if (!mov) throw new Error("Movement not found");
|
|
5356
5368
|
const tenantId = mov.tenant_id ?? getTenantId();
|
|
5357
5369
|
const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
|
|
@@ -5376,17 +5388,17 @@ function createSupabaseFinancialProvider() {
|
|
|
5376
5388
|
card_installments: input.cardInstallments ?? null,
|
|
5377
5389
|
notes: input.notes ?? null
|
|
5378
5390
|
};
|
|
5379
|
-
let { data, error } = await pub.from(
|
|
5391
|
+
let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
|
|
5380
5392
|
if (error && /fee_amount/i.test(error.message ?? "")) {
|
|
5381
5393
|
const { fee_amount, ...rest } = paymentRow;
|
|
5382
|
-
({ data, error } = await pub.from(
|
|
5394
|
+
({ data, error } = await pub.from(T.movements).insert(rest).select().single());
|
|
5383
5395
|
}
|
|
5384
5396
|
if (error) throw error;
|
|
5385
5397
|
const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
|
|
5386
5398
|
const billStatus = newPaid >= mov.amount ? "paid" : "partial";
|
|
5387
|
-
await pub.from(
|
|
5399
|
+
await pub.from(T.movements).update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
|
|
5388
5400
|
if (mov.invoice_id) {
|
|
5389
|
-
const { data: allMovs } = await pub.from(
|
|
5401
|
+
const { data: allMovs } = await pub.from(T.movements).select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
|
|
5390
5402
|
const movs = allMovs ?? [];
|
|
5391
5403
|
const allPaid = movs.length > 0 && movs.every((m) => m.status === "paid");
|
|
5392
5404
|
const anyPaid = movs.some((m) => m.status === "paid" || m.status === "partial");
|
|
@@ -5403,7 +5415,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5403
5415
|
},
|
|
5404
5416
|
async cancelMovement(id) {
|
|
5405
5417
|
const { pub } = getClients();
|
|
5406
|
-
await pub.from(
|
|
5418
|
+
await pub.from(T.movements).update({ status: "cancelled" }).eq("id", id);
|
|
5407
5419
|
},
|
|
5408
5420
|
// --- Transfers (between accounts) ---
|
|
5409
5421
|
async createTransfer(input) {
|
|
@@ -5439,10 +5451,10 @@ function createSupabaseFinancialProvider() {
|
|
|
5439
5451
|
metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
|
|
5440
5452
|
}
|
|
5441
5453
|
];
|
|
5442
|
-
let { data, error } = await pub.from(
|
|
5454
|
+
let { data, error } = await pub.from(T.movements).insert(rowsToInsert).select("id, direction");
|
|
5443
5455
|
if (error && /fee_amount/i.test(error.message ?? "")) {
|
|
5444
5456
|
const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
|
|
5445
|
-
({ data, error } = await pub.from(
|
|
5457
|
+
({ data, error } = await pub.from(T.movements).insert(stripped).select("id, direction"));
|
|
5446
5458
|
}
|
|
5447
5459
|
if (error) throw error;
|
|
5448
5460
|
const rows = data ?? [];
|
|
@@ -5453,13 +5465,13 @@ function createSupabaseFinancialProvider() {
|
|
|
5453
5465
|
// --- Bank Accounts ---
|
|
5454
5466
|
async getBankAccounts() {
|
|
5455
5467
|
const { pub } = getClients();
|
|
5456
|
-
const { data } = await pub.from(
|
|
5468
|
+
const { data } = await pub.from(T.bankAccounts).select("*").eq("is_active", true).order("name");
|
|
5457
5469
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5458
5470
|
},
|
|
5459
5471
|
async createBankAccount(input) {
|
|
5460
5472
|
const { pub } = getClients();
|
|
5461
5473
|
const tenantId = getTenantId();
|
|
5462
|
-
const { data } = await pub.from(
|
|
5474
|
+
const { data } = await pub.from(T.bankAccounts).insert({
|
|
5463
5475
|
...camelToSnake(input),
|
|
5464
5476
|
tenant_id: tenantId,
|
|
5465
5477
|
current_balance: input.initialBalance ?? 0
|
|
@@ -5471,26 +5483,26 @@ function createSupabaseFinancialProvider() {
|
|
|
5471
5483
|
const row = camelToSnake(partial);
|
|
5472
5484
|
delete row.id;
|
|
5473
5485
|
delete row.tenant_id;
|
|
5474
|
-
const { data } = await pub.from(
|
|
5486
|
+
const { data } = await pub.from(T.bankAccounts).update(row).eq("id", id).select().single();
|
|
5475
5487
|
return snakeToCamel(data);
|
|
5476
5488
|
},
|
|
5477
5489
|
// --- Cash Sessions ---
|
|
5478
5490
|
async getCashSessions(bankAccountId) {
|
|
5479
5491
|
const { pub } = getClients();
|
|
5480
|
-
let qb = pub.from(
|
|
5492
|
+
let qb = pub.from(T.cashRegisterSessions).select("*");
|
|
5481
5493
|
if (bankAccountId) qb = qb.eq("bank_account_id", bankAccountId);
|
|
5482
5494
|
const { data } = await qb.order("opened_at", { ascending: false });
|
|
5483
5495
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5484
5496
|
},
|
|
5485
5497
|
async getOpenSession(bankAccountId) {
|
|
5486
5498
|
const { pub } = getClients();
|
|
5487
|
-
const { data } = await pub.from(
|
|
5499
|
+
const { data } = await pub.from(T.cashRegisterSessions).select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
|
|
5488
5500
|
return data ? snakeToCamel(data) : null;
|
|
5489
5501
|
},
|
|
5490
5502
|
async openCashSession(input) {
|
|
5491
5503
|
const { pub } = getClients();
|
|
5492
5504
|
const tenantId = getTenantId();
|
|
5493
|
-
const { data } = await pub.from(
|
|
5505
|
+
const { data } = await pub.from(T.cashRegisterSessions).insert({
|
|
5494
5506
|
tenant_id: tenantId,
|
|
5495
5507
|
bank_account_id: input.bankAccountId,
|
|
5496
5508
|
status: "open",
|
|
@@ -5503,7 +5515,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5503
5515
|
},
|
|
5504
5516
|
async closeCashSession(input) {
|
|
5505
5517
|
const { pub } = getClients();
|
|
5506
|
-
const { data } = await pub.from(
|
|
5518
|
+
const { data } = await pub.from(T.cashRegisterSessions).update({
|
|
5507
5519
|
status: "closed",
|
|
5508
5520
|
closing_balance: input.closingBalance,
|
|
5509
5521
|
closed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5515,8 +5527,8 @@ function createSupabaseFinancialProvider() {
|
|
|
5515
5527
|
},
|
|
5516
5528
|
async getCashSessionSummary(sessionId) {
|
|
5517
5529
|
const { pub } = getClients();
|
|
5518
|
-
const session = (await pub.from(
|
|
5519
|
-
const { data: movs } = await pub.from(
|
|
5530
|
+
const session = (await pub.from(T.cashRegisterSessions).select("*").eq("id", sessionId).single()).data;
|
|
5531
|
+
const { data: movs } = await pub.from(T.movements).select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
|
|
5520
5532
|
const movements = movs ?? [];
|
|
5521
5533
|
return {
|
|
5522
5534
|
session: snakeToCamel(session),
|
|
@@ -5528,23 +5540,23 @@ function createSupabaseFinancialProvider() {
|
|
|
5528
5540
|
// --- Payment Methods ---
|
|
5529
5541
|
async getPaymentMethods() {
|
|
5530
5542
|
const { pub } = getClients();
|
|
5531
|
-
const { data } = await pub.from(
|
|
5543
|
+
const { data } = await pub.from(T.paymentMethods).select("*").eq("is_active", true).order("name");
|
|
5532
5544
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5533
5545
|
},
|
|
5534
5546
|
async getPaymentMethodTypes() {
|
|
5535
5547
|
const { pub } = getClients();
|
|
5536
|
-
const { data } = await pub.from(
|
|
5548
|
+
const { data } = await pub.from(T.paymentMethodTypes).select("*").eq("is_active", true).order("name");
|
|
5537
5549
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5538
5550
|
},
|
|
5539
5551
|
// --- Chart of Accounts ---
|
|
5540
5552
|
async getChartOfAccounts() {
|
|
5541
5553
|
const { pub } = getClients();
|
|
5542
|
-
const { data } = await pub.from(
|
|
5554
|
+
const { data } = await pub.from(T.chartOfAccounts).select("*").eq("is_active", true).order("code");
|
|
5543
5555
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5544
5556
|
},
|
|
5545
5557
|
async getCostCenters() {
|
|
5546
5558
|
const { pub } = getClients();
|
|
5547
|
-
const { data } = await pub.from(
|
|
5559
|
+
const { data } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
|
|
5548
5560
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
5549
5561
|
},
|
|
5550
5562
|
// --- Card Transactions ---
|
|
@@ -5556,7 +5568,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5556
5568
|
async getStatement(query) {
|
|
5557
5569
|
const { core, pub } = getClients();
|
|
5558
5570
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5559
|
-
const { data: acctRows } = await pub.from(
|
|
5571
|
+
const { data: acctRows } = await pub.from(T.bankAccounts).select("id, name, initial_balance, is_active");
|
|
5560
5572
|
const allAccounts = acctRows ?? [];
|
|
5561
5573
|
const consolidated = !query.bankAccountId;
|
|
5562
5574
|
const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
|
|
@@ -5568,10 +5580,10 @@ function createSupabaseFinancialProvider() {
|
|
|
5568
5580
|
}
|
|
5569
5581
|
const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
|
|
5570
5582
|
const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
|
|
5571
|
-
const { data: priorRows } = await scopeFilter(pub.from(
|
|
5583
|
+
const { data: priorRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
|
|
5572
5584
|
const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
|
|
5573
5585
|
const openingBalance = round22(initialSum + priorNet);
|
|
5574
|
-
const { data: movs } = await scopeFilter(pub.from(
|
|
5586
|
+
const { data: movs } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
|
|
5575
5587
|
const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
|
|
5576
5588
|
const invoiceById = /* @__PURE__ */ new Map();
|
|
5577
5589
|
if (invoiceIds.length) {
|
|
@@ -5613,7 +5625,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5613
5625
|
});
|
|
5614
5626
|
const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
|
|
5615
5627
|
if (matchedIds.length) {
|
|
5616
|
-
const { data: matched } = await pub.from(
|
|
5628
|
+
const { data: matched } = await pub.from(T.movements).select("id, invoice_id, notes").in("id", matchedIds);
|
|
5617
5629
|
const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
|
|
5618
5630
|
const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
|
|
5619
5631
|
const invById = /* @__PURE__ */ new Map();
|
|
@@ -5635,7 +5647,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5635
5647
|
};
|
|
5636
5648
|
}
|
|
5637
5649
|
}
|
|
5638
|
-
const { data: todayRows } = await scopeFilter(pub.from(
|
|
5650
|
+
const { data: todayRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lte("payment_date", today2));
|
|
5639
5651
|
const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
|
|
5640
5652
|
return {
|
|
5641
5653
|
entries,
|
|
@@ -5653,9 +5665,9 @@ function createSupabaseFinancialProvider() {
|
|
|
5653
5665
|
async getSummary(dateRange) {
|
|
5654
5666
|
sweepOverdue();
|
|
5655
5667
|
const { pub } = getClients();
|
|
5656
|
-
const { data: accounts } = await pub.from(
|
|
5668
|
+
const { data: accounts } = await pub.from(T.bankAccounts).select("current_balance").eq("is_active", true);
|
|
5657
5669
|
const totalBalance = (accounts ?? []).reduce((s, a) => s + (a.current_balance ?? 0), 0);
|
|
5658
|
-
const { data: movs } = await pub.from(
|
|
5670
|
+
const { data: movs } = await pub.from(T.movements).select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
|
|
5659
5671
|
const allMovs = movs ?? [];
|
|
5660
5672
|
const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
|
|
5661
5673
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -5688,7 +5700,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5688
5700
|
const tenantId = getTenantId();
|
|
5689
5701
|
let imported = 0, duplicates = 0;
|
|
5690
5702
|
for (const line of input.lines) {
|
|
5691
|
-
const { error } = await pub.from(
|
|
5703
|
+
const { error } = await pub.from(T.movements).insert({
|
|
5692
5704
|
tenant_id: tenantId,
|
|
5693
5705
|
direction: line.direction,
|
|
5694
5706
|
movement_kind: "payment",
|
|
@@ -5711,7 +5723,7 @@ function createSupabaseFinancialProvider() {
|
|
|
5711
5723
|
},
|
|
5712
5724
|
async getUnreconciled(query) {
|
|
5713
5725
|
const { pub } = getClients();
|
|
5714
|
-
let qb = pub.from(
|
|
5726
|
+
let qb = pub.from(T.movements).select("*").not("external_source", "is", null).is("reconciled_at", null);
|
|
5715
5727
|
if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
|
|
5716
5728
|
if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
|
|
5717
5729
|
const { data } = await qb.order("payment_date", { ascending: false });
|
|
@@ -5719,12 +5731,12 @@ function createSupabaseFinancialProvider() {
|
|
|
5719
5731
|
},
|
|
5720
5732
|
async suggestReconciliation(bankMovementId) {
|
|
5721
5733
|
const { core, pub } = getClients();
|
|
5722
|
-
const { data: line } = await pub.from(
|
|
5734
|
+
const { data: line } = await pub.from(T.movements).select("*").eq("id", bankMovementId).single();
|
|
5723
5735
|
if (!line) return [];
|
|
5724
5736
|
const amount = Number(line.amount) || 0;
|
|
5725
5737
|
const lineDate = line.payment_date ?? line.due_date;
|
|
5726
5738
|
const tol = Math.max(0.01, round22(amount * 0.02));
|
|
5727
|
-
const { data } = await pub.from(
|
|
5739
|
+
const { data } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round22(amount - tol)).lte("amount", round22(amount + tol)).neq("id", bankMovementId).limit(25);
|
|
5728
5740
|
const candidates = (data ?? []).map((r) => {
|
|
5729
5741
|
const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
|
|
5730
5742
|
const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
|
|
@@ -5750,20 +5762,20 @@ function createSupabaseFinancialProvider() {
|
|
|
5750
5762
|
async reconcileMovement(input) {
|
|
5751
5763
|
const { pub } = getClients();
|
|
5752
5764
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
5753
|
-
await pub.from(
|
|
5765
|
+
await pub.from(T.movements).update({
|
|
5754
5766
|
reconciled_at: now2,
|
|
5755
5767
|
matched_movement_id: input.matchedMovementId ?? null
|
|
5756
5768
|
}).eq("id", input.bankMovementId);
|
|
5757
5769
|
if (input.matchedMovementId) {
|
|
5758
|
-
await pub.from(
|
|
5770
|
+
await pub.from(T.movements).update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
|
|
5759
5771
|
}
|
|
5760
5772
|
},
|
|
5761
5773
|
async unreconcileMovement(bankMovementId) {
|
|
5762
5774
|
const { pub } = getClients();
|
|
5763
|
-
const { data: line } = await pub.from(
|
|
5764
|
-
await pub.from(
|
|
5775
|
+
const { data: line } = await pub.from(T.movements).select("matched_movement_id").eq("id", bankMovementId).single();
|
|
5776
|
+
await pub.from(T.movements).update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
|
|
5765
5777
|
if (line?.matched_movement_id) {
|
|
5766
|
-
await pub.from(
|
|
5778
|
+
await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
|
|
5767
5779
|
}
|
|
5768
5780
|
}
|
|
5769
5781
|
};
|
|
@@ -6011,7 +6023,7 @@ var paymentMethodTypeEntity = {
|
|
|
6011
6023
|
{ key: "transactionType", label: "Transaction Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check", "voucher"], showInTable: true, required: true },
|
|
6012
6024
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6013
6025
|
],
|
|
6014
|
-
data: { table:
|
|
6026
|
+
data: { table: T.paymentMethodTypes, tenantScoped: true }
|
|
6015
6027
|
};
|
|
6016
6028
|
var paymentMethodEntity = {
|
|
6017
6029
|
name: "Payment Method",
|
|
@@ -6023,14 +6035,14 @@ var paymentMethodEntity = {
|
|
|
6023
6035
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
6024
6036
|
// FK to the seeded payment_method_types catalog — load real ids at runtime
|
|
6025
6037
|
// (a static enum select would write the string into the uuid column).
|
|
6026
|
-
{ key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table:
|
|
6038
|
+
{ key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: T.paymentMethodTypes, labelField: "name" }, showInTable: true, required: true },
|
|
6027
6039
|
{ key: "discountValue", label: "Discount %", type: "number", showInTable: true },
|
|
6028
6040
|
{ key: "interestValue", label: "Interest %", type: "number", showInTable: true },
|
|
6029
6041
|
{ key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
|
|
6030
6042
|
{ key: "maxInstallments", label: "Max Installments", type: "number", defaultValue: 1, showInTable: true },
|
|
6031
6043
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6032
6044
|
],
|
|
6033
|
-
data: { table:
|
|
6045
|
+
data: { table: T.paymentMethods, tenantScoped: true }
|
|
6034
6046
|
};
|
|
6035
6047
|
var chartOfAccountsEntity = {
|
|
6036
6048
|
name: "Ledger Account",
|
|
@@ -6045,7 +6057,7 @@ var chartOfAccountsEntity = {
|
|
|
6045
6057
|
{ key: "parentId", label: "Parent", type: "text", showInTable: false },
|
|
6046
6058
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6047
6059
|
],
|
|
6048
|
-
data: { table:
|
|
6060
|
+
data: { table: T.chartOfAccounts, tenantScoped: true }
|
|
6049
6061
|
};
|
|
6050
6062
|
var costCenterEntity = {
|
|
6051
6063
|
name: "Cost Center",
|
|
@@ -6058,7 +6070,7 @@ var costCenterEntity = {
|
|
|
6058
6070
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
6059
6071
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6060
6072
|
],
|
|
6061
|
-
data: { table:
|
|
6073
|
+
data: { table: T.costCenters, tenantScoped: true }
|
|
6062
6074
|
};
|
|
6063
6075
|
var bankAccountEntity = {
|
|
6064
6076
|
name: "Bank Account",
|
|
@@ -6083,7 +6095,7 @@ var bankAccountEntity = {
|
|
|
6083
6095
|
{ key: "dueDay", label: "Due Day", type: "number" },
|
|
6084
6096
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6085
6097
|
],
|
|
6086
|
-
data: { table:
|
|
6098
|
+
data: { table: T.bankAccounts, tenantScoped: true }
|
|
6087
6099
|
};
|
|
6088
6100
|
var cardBrandEntity = {
|
|
6089
6101
|
name: "Card Brand",
|
|
@@ -6095,7 +6107,7 @@ var cardBrandEntity = {
|
|
|
6095
6107
|
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
6096
6108
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
6097
6109
|
],
|
|
6098
|
-
data: { table:
|
|
6110
|
+
data: { table: T.cardBrands, tenantScoped: true }
|
|
6099
6111
|
};
|
|
6100
6112
|
var commissionRuleEntity = {
|
|
6101
6113
|
name: "Commission Rule",
|