Template
1
0

feat: update payment views

This commit is contained in:
2025-12-06 20:42:10 +01:00
parent ce4d5ba013
commit 37164b560f
49 changed files with 1826 additions and 624 deletions

View File

@@ -1,9 +1,20 @@
export const ledger = {
export * from "../schemas/account.ts";
export * from "../schemas/beneficiary.ts";
export * from "../schemas/currency.ts";
export * from "../schemas/ledger.ts";
export * from "../schemas/transaction.ts";
export * from "../schemas/transaction-participant.ts";
export * from "../schemas/wallet.ts";
export const payment = {
dashboard: (await import("../routes/dashboard/spec.ts")).default,
benficiaries: {
create: (await import("../routes/beneficiaries/create/spec.ts")).default,
list: (await import("../routes/beneficiaries/list/spec.ts")).default,
id: (await import("../routes/beneficiaries/:id/spec.ts")).default,
ledgers: (await import("../routes/beneficiaries/ledgers/spec.ts")).default,
},
ledger: {
create: (await import("../routes/ledgers/create/spec.ts")).default,
},
create: (await import("../routes/ledgers/create/spec.ts")).default,
};

View File

@@ -1,5 +1,4 @@
import { db } from "@platform/database";
import { ConflictError } from "@platform/relay";
import {
type Beneficiary,
@@ -16,19 +15,9 @@ import {
* @param values - Beneficiary values to insert.
*/
export async function createBeneficiary({ tenantId, label }: BeneficiaryInsert): Promise<string> {
return db
.begin(async () => {
const _id = crypto.randomUUID();
await db.sql`ASSERT NOT EXISTS (SELECT 1 FROM payment.beneficiary WHERE "tenantId" = ${db.text(tenantId)}), 'duplicate_tenant'`;
await db.sql`INSERT INTO payment.beneficiary RECORDS ${db.transit({ _id, ...BeneficiaryInsertSchema.parse({ tenantId, label }) })}`;
return _id;
})
.catch((error) => {
if (error instanceof Error && error.message === "duplicate_tenant") {
throw new ConflictError(`Tenant '${tenantId}' already has a beneficiary`);
}
throw error;
});
const _id = crypto.randomUUID();
await db.sql`INSERT INTO payment.beneficiary RECORDS ${db.transit({ _id, ...BeneficiaryInsertSchema.parse({ tenantId, label }) })}`;
return _id;
}
/**

View File

@@ -30,7 +30,9 @@ export async function createLedger(values: LedgerInsert): Promise<string> {
})
.catch((error) => {
if (error instanceof Error && error.message === "missing_beneficiary") {
throw new BadRequestError(`Benficiary '${values.beneficiaryId}' does not exist`);
throw new BadRequestError(`Benficiary '${values.beneficiaryId}' does not exist`, {
input: "beneficiary",
});
}
throw error;
});

View File

@@ -1,5 +1,6 @@
import { route } from "@platform/relay";
import z from "zod";
import { BeneficiaryInsertSchema, BeneficiarySchema } from "../../../schemas/beneficiary.ts";
import { BeneficiaryInsertSchema } from "../../../schemas/beneficiary.ts";
export default route.post("/api/v1/payment/beneficiaries").body(BeneficiaryInsertSchema).response(BeneficiarySchema);
export default route.post("/api/v1/payment/beneficiaries").body(BeneficiaryInsertSchema).response(z.uuid());

View File

@@ -1,31 +1,29 @@
import { db } from "@platform/database";
import { NotFoundError } from "@platform/relay";
import route, { DashboardSchema } from "./spec.ts";
export default route.access("public").handle(async ({ params: { id } }) => {
const dashboard = await db.schema(DashboardSchema).one`
export default route.access("public").handle(async ({ params: { id: tenantId } }) => {
return db.schema(DashboardSchema).many`
SELECT
pb.*,
pb._system_from AS "createdAt",
NEST_MANY(
SELECT
pl.*,
pl._system_from AS "createdAt",
FROM
payment.ledger pl
WHERE
pl."beneficiaryId" = pb._id
ORDER BY
pl._id
COALESCE(
NEST_MANY(
SELECT
pl.*,
pl._system_from AS "createdAt"
FROM
payment.ledger pl
WHERE
pl."beneficiaryId" = pb._id
ORDER BY
pl._id
),
[] -- default to empty array
) AS ledgers
FROM
payment.beneficiary pb
WHERE
pb._id = ${id}
pb."tenantId" = ${tenantId}
`;
if (dashboard === undefined) {
return new NotFoundError("Beneficiary not found");
}
return dashboard;
});

View File

@@ -12,5 +12,5 @@ export const DashboardSchema = z.strictObject({
export default route
.get("/api/v1/payment/dashboard/:id")
.params({ id: BeneficiarySchema.shape._id })
.response(DashboardSchema);
.params({ id: BeneficiarySchema.shape.tenantId })
.response(z.array(DashboardSchema));

View File

@@ -9,16 +9,18 @@ import z from "zod";
|
*/
export const currencies = ["USD", "EUR", "GBP", "CHF", "JPY", "CAD", "AUD", "NOK", "SEK"] as const;
export const CurrencySchema = z.union([
z.literal("USD").describe("United States Dollar"),
z.literal("EUR").describe("Euro"),
z.literal("GBP").describe("British Pound Sterling"),
z.literal("CHF").describe("Swiss Franc"),
z.literal("JPY").describe("Japanese Yen"),
z.literal("CAD").describe("Canadian Dollar"),
z.literal("AUD").describe("Australian Dollar"),
z.literal("NOK").describe("Norwegian Krone"),
z.literal("SEK").describe("Swedish Krona"),
z.literal(currencies[0]).describe("United States Dollar"),
z.literal(currencies[1]).describe("Euro"),
z.literal(currencies[2]).describe("British Pound Sterling"),
z.literal(currencies[3]).describe("Swiss Franc"),
z.literal(currencies[4]).describe("Japanese Yen"),
z.literal(currencies[5]).describe("Canadian Dollar"),
z.literal(currencies[6]).describe("Australian Dollar"),
z.literal(currencies[7]).describe("Norwegian Krone"),
z.literal(currencies[8]).describe("Swedish Krona"),
]);
export type Currency = z.output<typeof CurrencySchema>;

View File

@@ -0,0 +1,3 @@
export const tenant = {
create: (await import("../routes/create/spec.ts")).default,
};

View File

@@ -0,0 +1,3 @@
export default {
routes: [(await import("../routes/create/handle.ts")).default],
};

View File

@@ -2,5 +2,15 @@
"name": "@module/tenant",
"version": "0.0.0",
"private": true,
"type": "module"
"type": "module",
"exports": {
"./server": "./entrypoints/server.ts",
"./client": "./entrypoints/client.ts"
},
"dependencies": {
"@platform/database": "workspace:*",
"@platform/parse": "workspace:*",
"@platform/relay": "workspace:*",
"zod": "4.1.13"
}
}

View File

View File

View File

@@ -0,0 +1,26 @@
import z from "zod";
/*
|--------------------------------------------------------------------------------
| Tenant
|--------------------------------------------------------------------------------
*/
export const TenantSchema = z.strictObject({
_id: z.uuid().describe("Primary identifier of the account"),
name: z.string().describe("Human-readable name for the tenant"),
});
export type Tenant = z.output<typeof TenantSchema>;
/*
|--------------------------------------------------------------------------------
| Database
|--------------------------------------------------------------------------------
*/
export const TenantInsertSchema = z.strictObject({
name: TenantSchema.shape.name,
});
export type TenantInsert = z.input<typeof TenantInsertSchema>;