Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7507f2370 | ||
|
|
2f9a9afc0d |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/api",
|
||||
"version": "1.0.25",
|
||||
"version": "1.0.26",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
|
||||
@@ -149,18 +149,15 @@ describe("balance floor", () => {
|
||||
});
|
||||
|
||||
it("keeps the source-table exclusion alongside the floor", async () => {
|
||||
// The two guards answer different questions — one reproduces legacy's
|
||||
// DATOS2-only materialization, the other drops superseded history — and
|
||||
// dropping either one changes the customer's balance.
|
||||
// The three guards answer different questions — one windows imported
|
||||
// periods, one drops the cash receipt book the ledger already posts, the
|
||||
// floor drops superseded history — and dropping any one of them changes
|
||||
// the customer's balance.
|
||||
const { service, findMany } = serviceWith(new Date("2026-01-01T00:00:00Z"));
|
||||
|
||||
await service.statement("c1");
|
||||
|
||||
const where = rowsQuery(findMany).where;
|
||||
expect(where.OR).toEqual([
|
||||
{ legacySourceTable: null },
|
||||
{ legacySourceTable: { notIn: expect.arrayContaining(["EFECTIVO"]) } },
|
||||
]);
|
||||
// Imported periods are windowed rather than excluded: history below the
|
||||
// year start (the only carry a floored-by-archive customer has), never
|
||||
// at or above it (those rows sit inside the next BALANCE FORWARD).
|
||||
@@ -172,6 +169,20 @@ describe("balance floor", () => {
|
||||
{ transactionDate: { lt: expect.any(Date) } },
|
||||
],
|
||||
},
|
||||
// The cash journal, qualified by the database it came from — the
|
||||
// insurance line has its own EFECTIVO and that one is a real ledger.
|
||||
{
|
||||
OR: [
|
||||
{ legacySourceDb: null },
|
||||
{ legacySourceDb: { not: "UTILITIES" } },
|
||||
{ legacySourceTable: null },
|
||||
{
|
||||
legacySourceTable: {
|
||||
notIn: expect.arrayContaining(["EFECTIVO"]),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -209,18 +209,6 @@ export const BALANCE_FLOOR_JOIN = Prisma.sql`
|
||||
*/
|
||||
export const NOT_SUPERSEDED = Prisma.sql`(bfloor.floorDate IS NULL OR t.transactionDate >= bfloor.floorDate)`;
|
||||
|
||||
/**
|
||||
* Source tables excluded from the customer-facing statement.
|
||||
*
|
||||
* The legacy portal's `datosfreak` table was materialized from DATOS2 only
|
||||
* (`objects.json:1358`), so the customer's "current balance" never saw
|
||||
* EFECTIVO / EFECTIVO FM3 / CHEQUE FM3 / EFECTIVO_BACKUP cash receipts, nor
|
||||
* the IVA 2015 snapshot. The unified `transactions` table has all of them, so
|
||||
* the statement must drop them to match the legacy number the customer has
|
||||
* been quoted for years. The staff-facing balances worklist and movement
|
||||
* browser keep them — they're real money, just tracked separately
|
||||
* (FM3 = visa fee stream, EFECTIVO = cash receipt stream).
|
||||
*/
|
||||
/**
|
||||
* `legacySourceTable` of an imported prior period.
|
||||
*
|
||||
@@ -235,7 +223,34 @@ export const periodSourceTable = (year: number) => `datos2@${year}`;
|
||||
/** Matches any imported period tag, for discovering which years a customer has. */
|
||||
export const PERIOD_TABLE_PREFIX = "datos2@";
|
||||
|
||||
export const STATEMENT_EXCLUDED_SOURCE_TABLES: readonly string[] = [
|
||||
/**
|
||||
* The legacy cash receipt book — a journal, not a ledger.
|
||||
*
|
||||
* `EFECTIVO` is the office's numbered receipt pad: money is handed over the
|
||||
* counter, a folio is written, and the same receipt is then *posted* to the
|
||||
* utilities ledger (`DATOS2`) as reference `C<folio>`. Legacy summed the ledger
|
||||
* alone — `ws/v2/lib/ledger_repository.php` reads `datosfreak`, which is
|
||||
* materialized from DATOS2 only (`objects.json:1358`). The migration flattened
|
||||
* both tables into one `transactions` table, so anything summing a customer's
|
||||
* rows counts every cash receipt twice.
|
||||
*
|
||||
* Verified against the live legacy database on 2026-08-20: of the 297 receipts
|
||||
* written in 2026, 296 carry a matching DATOS2 posting. Only folio 13536 (CL
|
||||
* 717, $400 USD) has no posting anywhere, and it wants a human's eyes rather
|
||||
* than a code change. Six receipts post converted to pesos under a mistyped
|
||||
* folio, which is why matching on folio and amount found fewer duplicate pairs
|
||||
* than actually exist — a reason to exclude the whole journal rather than to
|
||||
* exclude a list of confirmed pairs.
|
||||
*
|
||||
* THE DATABASE QUALIFIER IS LOAD-BEARING. `SEGUROS 16_be` keeps its own table
|
||||
* also called `EFECTIVO`, and that one is the insurance line's *only* ledger —
|
||||
* nothing posts it anywhere else. Excluding by table name alone erases the
|
||||
* whole insurance balance: 55,444.95 USD and 63,957.78 MXN across 102
|
||||
* customers, 99 of whom have no other rows at all.
|
||||
*/
|
||||
export const CASH_JOURNAL_SOURCE_DB = "UTILITIES";
|
||||
|
||||
export const CASH_JOURNAL_SOURCE_TABLES: readonly string[] = [
|
||||
"EFECTIVO",
|
||||
"EFECTIVO_BACKUP",
|
||||
"EFECTIVO FM3",
|
||||
@@ -243,6 +258,53 @@ export const STATEMENT_EXCLUDED_SOURCE_TABLES: readonly string[] = [
|
||||
"IVA 2015",
|
||||
];
|
||||
|
||||
/**
|
||||
* Prisma form of the cash-journal exclusion.
|
||||
*
|
||||
* Spelled as a positive OR on purpose. `notIn` alone compiles to SQL `NOT IN`,
|
||||
* and `NULL NOT IN (...)` is NULL rather than true, so every app-captured row
|
||||
* (no `legacySourceTable`) would silently vanish. Same for the database test.
|
||||
*/
|
||||
export const notCashJournal = (): Prisma.TransactionWhereInput => ({
|
||||
OR: [
|
||||
{ legacySourceDb: null },
|
||||
{ legacySourceDb: { not: CASH_JOURNAL_SOURCE_DB } },
|
||||
{ legacySourceTable: null },
|
||||
{ legacySourceTable: { notIn: [...CASH_JOURNAL_SOURCE_TABLES] } },
|
||||
],
|
||||
});
|
||||
|
||||
/** Raw-SQL form, for the aggregate queries that cannot use Prisma's builder. */
|
||||
export const NOT_CASH_JOURNAL = Prisma.sql`(
|
||||
t.legacySourceDb IS NULL
|
||||
OR t.legacySourceDb <> ${CASH_JOURNAL_SOURCE_DB}
|
||||
OR t.legacySourceTable IS NULL
|
||||
OR t.legacySourceTable NOT IN (${Prisma.join([
|
||||
...CASH_JOURNAL_SOURCE_TABLES,
|
||||
])}))`;
|
||||
|
||||
/**
|
||||
* Keeps an imported prior period out of the *current* period, NULL-safely.
|
||||
*
|
||||
* The balance floor does not settle the archives on its own, in both
|
||||
* directions. A customer whose newest BALANCE FORWARD lives *inside* an archive
|
||||
* floors at that archive's own January 1st, so every row of it clears the floor
|
||||
* — and that is correct, because below the year start the archive is the only
|
||||
* carry there is. At or above the year start it must go: the archives spill a
|
||||
* couple of rows into the following January and those already sit inside the
|
||||
* next year's BALANCE FORWARD, which is the sum of the whole archive.
|
||||
*
|
||||
* Spelled as a positive OR for the same NULL reason as above.
|
||||
*/
|
||||
export const archiveIsHistorySql = (yearStart: Date) => Prisma.sql`(
|
||||
t.legacySourceTable IS NULL
|
||||
OR t.legacySourceTable NOT LIKE ${`${PERIOD_TABLE_PREFIX}%`}
|
||||
OR t.transactionDate < ${yearStart})`;
|
||||
|
||||
/** January 1st of the running year, UTC — the current period's lower bound. */
|
||||
export const currentYearStart = () =>
|
||||
new Date(Date.UTC(new Date().getUTCFullYear(), 0, 1));
|
||||
|
||||
@Injectable()
|
||||
export class BillingService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
@@ -412,6 +474,16 @@ export class BillingService {
|
||||
async balances(params: BalanceParams) {
|
||||
const { query, page, pageSize, currency, balance, domain, sort } = params;
|
||||
|
||||
// A balance is what the customer owes, so it takes the same rules the
|
||||
// statement takes: the floor, the cash journal, and the archive window.
|
||||
// Without the last two the worklist quoted a different number than the
|
||||
// customer's own statement — NUMid 295 read 14,377.46 against a statement
|
||||
// of 4,377.46, and NUMid 10 read 2,362.20 against -1,137.80, the gap in
|
||||
// each case being a cash receipt already posted to the ledger.
|
||||
const scope = Prisma.sql`AND ${NOT_CASH_JOURNAL} AND ${archiveIsHistorySql(
|
||||
currentYearStart(),
|
||||
)}`;
|
||||
|
||||
const filters: Prisma.Sql[] = [];
|
||||
if (domain) filters.push(Prisma.sql`t.domain = ${domain}`);
|
||||
const txFilter = filters.length
|
||||
@@ -474,7 +546,7 @@ export class BillingService {
|
||||
FROM customers c
|
||||
JOIN transactions t ON t.customerId = c.id
|
||||
${BALANCE_FLOOR_JOIN}
|
||||
WHERE t.voidedAt IS NULL AND t.outstanding = 0 AND ${NOT_SUPERSEDED} ${nameFilter} ${txFilter}
|
||||
WHERE t.voidedAt IS NULL AND t.outstanding = 0 AND ${NOT_SUPERSEDED} ${scope} ${nameFilter} ${txFilter}
|
||||
GROUP BY c.id, c.name, c.nameSource, c.nameMissing, c.city, c.state
|
||||
${having}
|
||||
${orderBy}
|
||||
@@ -490,7 +562,7 @@ export class BillingService {
|
||||
-- Must match the page query's filters exactly, or the total disagrees
|
||||
-- with the rows. (The void exclusion was missing here before the
|
||||
-- outstanding work; a voided-only customer inflated the count.)
|
||||
WHERE t.voidedAt IS NULL AND t.outstanding = 0 AND ${NOT_SUPERSEDED} ${nameFilter} ${txFilter}
|
||||
WHERE t.voidedAt IS NULL AND t.outstanding = 0 AND ${NOT_SUPERSEDED} ${scope} ${nameFilter} ${txFilter}
|
||||
GROUP BY c.id
|
||||
${having}
|
||||
) x
|
||||
@@ -537,11 +609,22 @@ export class BillingService {
|
||||
* Two different questions live here and they use different row sets.
|
||||
* `movements`, `ledgerCustomers`, `crossLineCustomers` and the date range are
|
||||
* INVENTORY — what is stored — and count everything not voided. Everything
|
||||
* under `byCurrency` / `byDomain` is a BALANCE, so it applies NOT_SUPERSEDED
|
||||
* and drops rows an opening balance already accounts for. The four aggregates
|
||||
* moved from Prisma groupBy to raw SQL to express that join; groupBy cannot.
|
||||
* under `byCurrency` / `byDomain` is a BALANCE, so it takes the same scope
|
||||
* `balances()` takes — the opening-balance floor, the cash journal and the
|
||||
* archive window — and the book has to agree with the worklist that sits
|
||||
* under it. The four aggregates moved from Prisma groupBy to raw SQL to
|
||||
* express that join; groupBy cannot.
|
||||
*
|
||||
* KNOWN DIVERGENCE, left deliberately: these three do not drop outstanding
|
||||
* rows, while `balances()` does. Reconciling them moves the book by about
|
||||
* 1.95M MXN and turns on whether an unfunded charge is owed by the customer,
|
||||
* which is the client's call and not settled yet.
|
||||
*/
|
||||
async stats() {
|
||||
const scope = Prisma.sql`AND ${NOT_CASH_JOURNAL} AND ${archiveIsHistorySql(
|
||||
currentYearStart(),
|
||||
)}`;
|
||||
|
||||
const [movements, ledgerCustomers] = await Promise.all([
|
||||
this.prisma.transaction.count({ where: NOT_VOIDED }),
|
||||
this.prisma.transaction
|
||||
@@ -573,7 +656,7 @@ export class BillingService {
|
||||
SUM(t.amount > 0) AS creditCount
|
||||
FROM transactions t
|
||||
${BALANCE_FLOOR_JOIN}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED} ${scope}
|
||||
GROUP BY t.currency
|
||||
`;
|
||||
|
||||
@@ -589,7 +672,7 @@ export class BillingService {
|
||||
SUM(t.amount) AS net, COUNT(*) AS count
|
||||
FROM transactions t
|
||||
${BALANCE_FLOOR_JOIN}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED} ${scope}
|
||||
GROUP BY t.domain, t.currency
|
||||
`;
|
||||
|
||||
@@ -610,7 +693,7 @@ export class BillingService {
|
||||
SELECT t.customerId, t.currency, SUM(t.amount) AS bal
|
||||
FROM transactions t
|
||||
${BALANCE_FLOOR_JOIN}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED}
|
||||
WHERE t.voidedAt IS NULL AND ${NOT_SUPERSEDED} ${scope}
|
||||
GROUP BY t.customerId, t.currency
|
||||
) x
|
||||
GROUP BY currency
|
||||
@@ -832,20 +915,6 @@ export class BillingService {
|
||||
{ legacySourceTable: periodSourceTable(requested) }
|
||||
: {
|
||||
...(floor ? { transactionDate: { gte: floor.transactionDate } } : {}),
|
||||
// NULL-safe exclusion. `notIn` alone compiles to SQL `NOT IN`, and
|
||||
// `NULL NOT IN (...)` is NULL, not true — so every app-captured row
|
||||
// (which has no legacySourceTable) silently vanished from the
|
||||
// statement while still showing in the movement browser. Rows the app
|
||||
// books must appear on the customer's statement, so the null case is
|
||||
// spelled out.
|
||||
OR: [
|
||||
{ legacySourceTable: null },
|
||||
{
|
||||
legacySourceTable: {
|
||||
notIn: STATEMENT_EXCLUDED_SOURCE_TABLES as string[],
|
||||
},
|
||||
},
|
||||
],
|
||||
// An archive row belongs to this period only as history. Below
|
||||
// the year start it is exactly what `opening` is for, and for the
|
||||
// one customer whose newest BALANCE FORWARD lives *inside* an
|
||||
@@ -872,6 +941,10 @@ export class BillingService {
|
||||
{ transactionDate: { lt: yearStart } },
|
||||
],
|
||||
},
|
||||
// The cash receipt book is the ledger's own postings written a
|
||||
// second time, so listing it here would show every counter
|
||||
// payment twice and double the credit side.
|
||||
notCashJournal(),
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Prisma } from "@jorgecuadros/database";
|
||||
import {
|
||||
CASH_JOURNAL_SOURCE_DB,
|
||||
CASH_JOURNAL_SOURCE_TABLES,
|
||||
NOT_CASH_JOURNAL,
|
||||
notCashJournal,
|
||||
} from "./billing.service";
|
||||
|
||||
/**
|
||||
* `EFECTIVO` is the office's paper receipt book, and every receipt in it is
|
||||
* also posted to the utilities ledger as `C<folio>`. Both copies were imported
|
||||
* into one `transactions` table, so a balance that reads the journal counts
|
||||
* each counter payment twice — 1,094,347.78 MXN of phantom credit book-wide,
|
||||
* and 3,500.00 of it on NUMid 10 alone.
|
||||
*
|
||||
* These fail silently in the worst way: the numbers stay plausible, they are
|
||||
* just too generous to the customer. Two shapes of mistake are easy to make
|
||||
* here and both are covered below — dropping the database qualifier (which
|
||||
* erases the insurance line's only ledger) and writing the exclusion as a bare
|
||||
* `NOT IN` (which erases every app-captured row).
|
||||
*/
|
||||
describe("cash journal exclusion", () => {
|
||||
describe("raw SQL form", () => {
|
||||
it("binds the source database rather than interpolating it", () => {
|
||||
expect(NOT_CASH_JOURNAL.values).toContain(CASH_JOURNAL_SOURCE_DB);
|
||||
});
|
||||
|
||||
it("qualifies the table names with the database they came from", () => {
|
||||
// `SEGUROS 16_be` has its own EFECTIVO and it is the insurance line's
|
||||
// ONLY ledger — nothing posts it anywhere else. Matching on the table
|
||||
// name alone erases 55,444.95 USD and 63,957.78 MXN across 102 customers.
|
||||
expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceDb <>");
|
||||
expect(NOT_CASH_JOURNAL.values).toContain(CASH_JOURNAL_SOURCE_DB);
|
||||
});
|
||||
|
||||
it("spells both null cases out instead of relying on NOT IN", () => {
|
||||
// `NULL NOT IN (...)` is NULL, not true. Without these branches every
|
||||
// app-captured row — the ones staff key in by hand — drops out of the
|
||||
// balance while still showing in the movement browser.
|
||||
expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceDb IS NULL");
|
||||
expect(NOT_CASH_JOURNAL.sql).toContain("t.legacySourceTable IS NULL");
|
||||
});
|
||||
|
||||
it("covers the whole cash family, not just EFECTIVO", () => {
|
||||
for (const table of CASH_JOURNAL_SOURCE_TABLES) {
|
||||
expect(NOT_CASH_JOURNAL.values).toContain(table);
|
||||
}
|
||||
});
|
||||
|
||||
it("is a single parenthesised term, safe to AND into a WHERE clause", () => {
|
||||
// It is composed as `... AND ${NOT_CASH_JOURNAL} AND ...`. An unbracketed
|
||||
// OR chain would swallow every condition after it and silently widen the
|
||||
// whole query to the entire table.
|
||||
const sql = NOT_CASH_JOURNAL.sql.trim();
|
||||
expect(sql.startsWith("(")).toBe(true);
|
||||
expect(sql.endsWith(")")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Prisma form", () => {
|
||||
it("matches the raw form's terms so the two cannot drift apart", () => {
|
||||
const branches = notCashJournal().OR as Prisma.TransactionWhereInput[];
|
||||
expect(branches).toEqual([
|
||||
{ legacySourceDb: null },
|
||||
{ legacySourceDb: { not: CASH_JOURNAL_SOURCE_DB } },
|
||||
{ legacySourceTable: null },
|
||||
{ legacySourceTable: { notIn: [...CASH_JOURNAL_SOURCE_TABLES] } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns a fresh object each call", () => {
|
||||
// It is spread into `AND: [...]` arrays that Prisma may mutate; a shared
|
||||
// singleton would leak one query's filters into the next.
|
||||
expect(notCashJournal()).not.toBe(notCashJournal());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,21 +5,10 @@ import { CreateCustomerDto } from "./create-customer.dto";
|
||||
import { UpdateCustomerDto } from "./update-customer.dto";
|
||||
import {
|
||||
BALANCE_FORWARD_TYPE,
|
||||
notCashJournal,
|
||||
PERIOD_TABLE_PREFIX,
|
||||
STATEMENT_EXCLUDED_SOURCE_TABLES,
|
||||
} from "../billing/billing.service";
|
||||
|
||||
/**
|
||||
* Keeps imported prior periods out of a query, NULL-safely.
|
||||
*
|
||||
* A closed year is imported as its own tagged copy of that year's ledger
|
||||
* (`datos2@2025`) and the BALANCE FORWARD rows above it already contain every
|
||||
* peso of it. Anything summing a customer's history has to leave the archives
|
||||
* out or it counts each closed year twice — see the floor comment below.
|
||||
*
|
||||
* `NULL NOT LIKE '...'` is NULL rather than true, so app-captured rows (which
|
||||
* carry no legacySourceTable) need the null branch spelled out or they vanish.
|
||||
*/
|
||||
/**
|
||||
* Keeps an imported prior period out of the *current* period, NULL-safely.
|
||||
*
|
||||
@@ -225,19 +214,7 @@ export class CustomersService {
|
||||
// The floor alone does not settle the archives: a customer floored by
|
||||
// an archive clears it with every row of that archive, and the rows
|
||||
// archives spill into the following January clear any floor.
|
||||
AND: [
|
||||
archiveIsHistory(yearStart),
|
||||
{
|
||||
OR: [
|
||||
{ legacySourceTable: null },
|
||||
{
|
||||
legacySourceTable: {
|
||||
notIn: [...STATEMENT_EXCLUDED_SOURCE_TABLES],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
AND: [archiveIsHistory(yearStart), notCashJournal()],
|
||||
},
|
||||
_sum: { amount: true },
|
||||
_count: { _all: true },
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Prisma } from "@jorgecuadros/database";
|
||||
import {
|
||||
BALANCE_FORWARD_TYPE,
|
||||
notCashJournal,
|
||||
periodSourceTable,
|
||||
} from "../billing/billing.service";
|
||||
import {
|
||||
@@ -835,23 +836,6 @@ const edoCuentaDatos: ReportDef = {
|
||||
{ legacySourceTable: periodSourceTable(requestedYear) }
|
||||
: {
|
||||
...(floor ? { transactionDate: { gte: floor.transactionDate } } : {}),
|
||||
// NULL-safe: `NULL NOT IN (...)` is NULL, not true, so a bare `notIn`
|
||||
// drops every app-captured row (they have no legacySourceTable) — the
|
||||
// same defect this report's on-screen twin was fixed for.
|
||||
OR: [
|
||||
{ legacySourceTable: null },
|
||||
{
|
||||
legacySourceTable: {
|
||||
notIn: [
|
||||
"EFECTIVO",
|
||||
"EFECTIVO_BACKUP",
|
||||
"EFECTIVO FM3",
|
||||
"CHEQUE FM3",
|
||||
"IVA 2015",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
// Archive rows count as history below the year start (that is
|
||||
// what `opening` is for, and for a customer floored by an archive
|
||||
// it is the only carry there is) and are dropped at or above it.
|
||||
@@ -868,6 +852,12 @@ const edoCuentaDatos: ReportDef = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// The cash receipt book, which the ledger already carries as
|
||||
// its own `C<folio>` postings. Taken from the shared helper
|
||||
// rather than restated, so the printed statement and the screen
|
||||
// cannot drift apart — and so this keeps the database
|
||||
// qualifier that spares the insurance line's own EFECTIVO.
|
||||
notCashJournal(),
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/web",
|
||||
"version": "1.0.25",
|
||||
"version": "1.0.26",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 4500",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jorgecuadros-platform",
|
||||
"version": "1.0.25",
|
||||
"version": "1.0.26",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/database",
|
||||
"version": "1.0.25",
|
||||
"version": "1.0.26",
|
||||
"private": true,
|
||||
"main": "generated/client/index.js",
|
||||
"types": "generated/client/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user