refactor: standardize import statements and formatting across components
- Updated import statements in `textarea.tsx`, `toggle.tsx`, and `tooltip.tsx` for consistency. - Added missing semicolons and adjusted formatting in `use-mobile.ts` and `use-mobile.tsx`. - Cleaned up `package.json` and `root.json` files by removing unnecessary line breaks. - Simplified `turbo.json` by consolidating array formatting for better readability.
This commit is contained in:
parent
7a278cb247
commit
d57b71c200
@ -83,4 +83,4 @@
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { type IOperationContextStore, createOperationContextMiddleware } from "@erp/core/api";
|
||||
import cors, { type CorsOptions } from "cors";
|
||||
import express, { type Application } from "express";
|
||||
import helmet from "helmet";
|
||||
import responseTime from "response-time";
|
||||
|
||||
import { createOperationContextMiddleware, type IOperationContextStore } from "@erp/core/api";
|
||||
|
||||
import type { ConfigType } from "./config";
|
||||
|
||||
// ❗️ No cargamos dotenv aquí. Debe hacerse en el entrypoint o en ./config.
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"acme": {
|
||||
"certificateId": "acme-prod-cert-v1",
|
||||
"certificateSecretName": "acme_cert",
|
||||
"certificatePasswordSecretName": "acme_cert_pwd"
|
||||
},
|
||||
"rodax": {
|
||||
"certificateId": "no se que poner aqui",
|
||||
"certificateSecretName": "certificate_secret_name",
|
||||
"certificatePasswordSecretName": "certificate_password_secret_name"
|
||||
}
|
||||
}
|
||||
"acme": {
|
||||
"certificateId": "acme-prod-cert-v1",
|
||||
"certificateSecretName": "acme_cert",
|
||||
"certificatePasswordSecretName": "acme_cert_pwd"
|
||||
},
|
||||
"rodax": {
|
||||
"certificateId": "no se que poner aqui",
|
||||
"certificateSecretName": "certificate_secret_name",
|
||||
"certificatePasswordSecretName": "certificate_password_secret_name"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,9 @@ import type { ILogger } from "@repo/rdx-logger";
|
||||
import type { ConfigType } from "./config";
|
||||
|
||||
const redact = (value: string): string => {
|
||||
if (value.length <= 4) return "***";
|
||||
if (value.length <= 4) {
|
||||
return "***";
|
||||
}
|
||||
return `${value.slice(0, 2)}***${value.slice(-2)}`;
|
||||
};
|
||||
|
||||
|
||||
@ -62,7 +62,9 @@ const serverStop = (server: http.Server) => {
|
||||
const forceTimeout = 30000;
|
||||
|
||||
// Devolvemos siempre la misma promesa para evitar dobles cierres
|
||||
if (shutdownPromise) return shutdownPromise;
|
||||
if (shutdownPromise) {
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
shutdownPromise = new Promise<void>((resolve, reject) => {
|
||||
logger.warn("⚡️ Shutting down server");
|
||||
@ -165,7 +167,9 @@ const handleSignal = (signal: NodeJS.Signals) => async () => {
|
||||
logger.warn(`Received ${signal}. Starting graceful shutdown...`);
|
||||
try {
|
||||
await serverStop(server);
|
||||
if (database) await closeDatabase(database);
|
||||
if (database) {
|
||||
await closeDatabase(database);
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error("Error during shutdown, forcing exit(1)", { err, label: "handleSignal" });
|
||||
@ -202,7 +206,9 @@ process.on("unhandledRejection", async (reason: any, promise: Promise<any>) => {
|
||||
isReady = false;
|
||||
try {
|
||||
await serverStop(server);
|
||||
if (database) await closeDatabase(database);
|
||||
if (database) {
|
||||
await closeDatabase(database);
|
||||
}
|
||||
} finally {
|
||||
process.exit(1);
|
||||
}
|
||||
@ -219,7 +225,9 @@ process.on("uncaughtException", async (error: Error) => {
|
||||
isReady = false;
|
||||
try {
|
||||
await serverStop(server);
|
||||
if (database) await closeDatabase(database);
|
||||
if (database) {
|
||||
await closeDatabase(database);
|
||||
}
|
||||
} finally {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -183,12 +183,16 @@ export class SequelizeAuditReader implements IAuditReader {
|
||||
};
|
||||
}
|
||||
private accessWhere(access: AuditReadAccess): Record<PropertyKey, unknown> {
|
||||
if (access.mode === "unrestricted") return {};
|
||||
if (access.mode === "unrestricted") {
|
||||
return {};
|
||||
}
|
||||
const alternatives: Record<string, string | null>[] = access.scopes.map((scope) => ({
|
||||
scope_type: scope.type,
|
||||
scope_id: scope.id,
|
||||
}));
|
||||
if (access.includeUnscoped) alternatives.push({ scope_type: null, scope_id: null });
|
||||
if (access.includeUnscoped) {
|
||||
alternatives.push({ scope_type: null, scope_id: null });
|
||||
}
|
||||
return alternatives.length ? { [Op.or]: alternatives } : { id: { [Op.eq]: null } };
|
||||
}
|
||||
private quickSearch(value: string): Record<PropertyKey, unknown>[] {
|
||||
|
||||
@ -118,13 +118,10 @@ test("module-loader falla cuando una dependencia usada no está declarada", asyn
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await initModules(buildModuleParams());
|
||||
await startModules(buildModuleParams());
|
||||
},
|
||||
/without declaring dependency on "catalogs"/i
|
||||
);
|
||||
await assert.rejects(async () => {
|
||||
await initModules(buildModuleParams());
|
||||
await startModules(buildModuleParams());
|
||||
}, /without declaring dependency on "catalogs"/i);
|
||||
});
|
||||
|
||||
test("module-loader sigue detectando ciclos reales entre módulos", async (t) => {
|
||||
@ -165,11 +162,8 @@ test("module-loader rechaza la autoreferencia explícita por nombre de módulo",
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await initModules(buildModuleParams());
|
||||
await startModules(buildModuleParams());
|
||||
},
|
||||
/without declaring dependency on "companies"/i
|
||||
);
|
||||
await assert.rejects(async () => {
|
||||
await initModules(buildModuleParams());
|
||||
await startModules(buildModuleParams());
|
||||
}, /without declaring dependency on "companies"/i);
|
||||
});
|
||||
|
||||
@ -137,6 +137,7 @@ test("Audit HTTP aplica tenant A/B y valida el DTO estricto", async (t) => {
|
||||
"order=invalid",
|
||||
"from=invalid",
|
||||
"to=invalid",
|
||||
])
|
||||
]) {
|
||||
assert.equal((await runtime.request(`/api/v1/audit?${query}`, { headers })).status, 422);
|
||||
}
|
||||
});
|
||||
|
||||
@ -109,7 +109,9 @@ const auditProbeModule: IModuleServer = {
|
||||
before: { name: "Before" },
|
||||
after: { name: "After" },
|
||||
});
|
||||
if (result.isFailure) return result;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
return Result.fail(new Error("test rollback"));
|
||||
});
|
||||
} catch {
|
||||
|
||||
@ -678,15 +678,17 @@ for (const scenario of [
|
||||
await assert.rejects(
|
||||
() =>
|
||||
transactionManager.complete(async (transaction) => {
|
||||
if (scenario.run === "statusChanger")
|
||||
if (scenario.run === "statusChanger") {
|
||||
return statusChanger.changeStatus({
|
||||
companyId,
|
||||
id: proformaId,
|
||||
newStatus: "sent",
|
||||
transaction,
|
||||
});
|
||||
if (scenario.run === "deleter")
|
||||
}
|
||||
if (scenario.run === "deleter") {
|
||||
return deleter.delete({ companyId, id: proformaId, transaction });
|
||||
}
|
||||
return scenario.name === "unarchive"
|
||||
? archiver.unarchive({ companyId, id: proformaId, transaction })
|
||||
: archiver.archive({ companyId, id: proformaId, transaction });
|
||||
@ -696,9 +698,15 @@ for (const scenario of [
|
||||
assert.equal(audit.recordMutationCalls, 1);
|
||||
const persisted = await ProformaModel.findByPk(id);
|
||||
assert.ok(persisted);
|
||||
if (scenario.name === "status") assert.equal(persisted.status, "draft");
|
||||
if (scenario.name === "archive") assert.equal(persisted.archived_at, null);
|
||||
if (scenario.name === "unarchive") assert.notEqual(persisted.archived_at, null);
|
||||
if (scenario.name === "status") {
|
||||
assert.equal(persisted.status, "draft");
|
||||
}
|
||||
if (scenario.name === "archive") {
|
||||
assert.equal(persisted.archived_at, null);
|
||||
}
|
||||
if (scenario.name === "unarchive") {
|
||||
assert.notEqual(persisted.archived_at, null);
|
||||
}
|
||||
assert.equal(
|
||||
await AuditEventModel.count({ where: { action: scenario.action, target_id: id } }),
|
||||
eventsBefore
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
/**
|
||||
@ -55,14 +56,20 @@ export default defineConfig({
|
||||
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
||||
const indexTs = path.join(abs, "index.ts");
|
||||
const indexJs = path.join(abs, "index.js");
|
||||
if (fs.existsSync(indexTs)) return { path: indexTs, external: false };
|
||||
if (fs.existsSync(indexJs)) return { path: indexJs, external: false };
|
||||
if (fs.existsSync(indexTs)) {
|
||||
return { path: indexTs, external: false };
|
||||
}
|
||||
if (fs.existsSync(indexJs)) {
|
||||
return { path: indexJs, external: false };
|
||||
}
|
||||
}
|
||||
|
||||
// Si es un fichero con extensión válida
|
||||
const withExts = [".ts", ".js", ".mjs"];
|
||||
for (const ext of withExts) {
|
||||
if (fs.existsSync(abs + ext)) return { path: abs + ext, external: false };
|
||||
if (fs.existsSync(abs + ext)) {
|
||||
return { path: abs + ext, external: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { path: abs, external: false };
|
||||
|
||||
@ -1,20 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FactuGES 2026</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
</head>
|
||||
|
||||
<title>FactuGES 2026</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="factuges" class="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="factuges" class="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -63,4 +63,4 @@
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vite-plugin-html": "^3.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,61 +2,61 @@ import { Avatar, AvatarFallback, AvatarImage } from "@repo/shadcn-ui/components"
|
||||
|
||||
export function AvatarDemo() {
|
||||
return (
|
||||
<div className='flex flex-row flex-wrap items-center gap-4'>
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/shadcn.png' alt='@shadcn' />
|
||||
<AvatarImage alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className='size-12'>
|
||||
<AvatarImage src='https://github.com/shadcn.png' alt='@shadcn' />
|
||||
<Avatar className="size-12">
|
||||
<AvatarImage alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className='rounded-lg'>
|
||||
<AvatarImage src='https://github.com/evilrabbit.png' alt='@evilrabbit' />
|
||||
<Avatar className="rounded-lg">
|
||||
<AvatarImage alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className='*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale'>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/shadcn.png' alt='@shadcn' />
|
||||
<AvatarImage alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/leerob.png' alt='@leerob' />
|
||||
<AvatarImage alt="@leerob" src="https://github.com/leerob.png" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/evilrabbit.png' alt='@evilrabbit' />
|
||||
<AvatarImage alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className='*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale'>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/shadcn.png' alt='@shadcn' />
|
||||
<AvatarImage alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/leerob.png' alt='@leerob' />
|
||||
<AvatarImage alt="@leerob" src="https://github.com/leerob.png" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/evilrabbit.png' alt='@evilrabbit' />
|
||||
<AvatarImage alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className='*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out'>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/shadcn.png' alt='@shadcn' />
|
||||
<AvatarImage alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/leerob.png' alt='@leerob' />
|
||||
<AvatarImage alt="@leerob" src="https://github.com/leerob.png" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src='https://github.com/evilrabbit.png' alt='@evilrabbit' />
|
||||
<AvatarImage alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
|
||||
@ -6,8 +6,6 @@ import {
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@ -19,16 +17,16 @@ export function BreadcrumbDemo() {
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href='/'>Home</BreadcrumbLink>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='flex items-center gap-1'>
|
||||
<BreadcrumbEllipsis className='h-4 w-4' />
|
||||
<span className='sr-only'>Toggle menu</span>
|
||||
<DropdownMenuTrigger className="flex items-center gap-1">
|
||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='start'>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>Documentation</DropdownMenuItem>
|
||||
<DropdownMenuItem>Themes</DropdownMenuItem>
|
||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
||||
@ -37,7 +35,7 @@ export function BreadcrumbDemo() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href='/docs/components'>Components</BreadcrumbLink>
|
||||
<BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
|
||||
@ -165,8 +165,7 @@ const normalizeRoute = ({
|
||||
const routeLayout = resolveRouteLayout(route, module, inheritedLayout);
|
||||
const routeProtected = resolveRouteProtected(route, module, inheritedProtected);
|
||||
const routeRequireCompany =
|
||||
routeProtected &&
|
||||
(route.handle?.requireCompany ?? inheritedRequireCompany ?? true);
|
||||
routeProtected && (route.handle?.requireCompany ?? inheritedRequireCompany ?? true);
|
||||
|
||||
const normalizedChildren = route.children
|
||||
?.map((child) =>
|
||||
|
||||
@ -6,10 +6,10 @@ type SliderProps = React.ComponentProps<typeof Slider>;
|
||||
export function SliderDemo({ className, ...props }: SliderProps) {
|
||||
return (
|
||||
<Slider
|
||||
className={cn("w-[60%]", className)}
|
||||
defaultValue={[50]}
|
||||
max={100}
|
||||
step={1}
|
||||
className={cn("w-[60%]", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -33,7 +33,7 @@ export const HomeAttentionPanel = ({ items, hasError }: HomeAttentionPanelProps)
|
||||
<p className="text-sm text-muted-foreground">{t("web.home.attention.empty")}</p>
|
||||
) : null}
|
||||
|
||||
{!hasError ? (
|
||||
{hasError ? null : (
|
||||
<ul className="divide-y divide-border/70">
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>
|
||||
@ -53,7 +53,7 @@ export const HomeAttentionPanel = ({ items, hasError }: HomeAttentionPanelProps)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -9,10 +9,7 @@ interface HomeMonthlyBillingSummaryProps {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
export const HomeMonthlyBillingSummary = ({
|
||||
data,
|
||||
hasError,
|
||||
}: HomeMonthlyBillingSummaryProps) => {
|
||||
export const HomeMonthlyBillingSummary = ({ data, hasError }: HomeMonthlyBillingSummaryProps) => {
|
||||
const { t } = useAppTranslation();
|
||||
|
||||
return (
|
||||
@ -22,7 +19,9 @@ export const HomeMonthlyBillingSummary = ({
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
{t("web.home.monthlyBilling.title")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">{t("web.home.monthlyBilling.description")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("web.home.monthlyBilling.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasError ? (
|
||||
@ -32,7 +31,7 @@ export const HomeMonthlyBillingSummary = ({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!hasError ? (
|
||||
{hasError ? null : (
|
||||
<div className="space-y-2">
|
||||
<p className="text-3xl font-semibold tracking-tight tabular-nums text-foreground">
|
||||
{data?.totalAmountLabel ??
|
||||
@ -56,7 +55,7 @@ export const HomeMonthlyBillingSummary = ({
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -10,10 +10,7 @@ interface HomeProformaStatusSummaryProps {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
export const HomeProformaStatusSummary = ({
|
||||
items,
|
||||
hasError,
|
||||
}: HomeProformaStatusSummaryProps) => {
|
||||
export const HomeProformaStatusSummary = ({ items, hasError }: HomeProformaStatusSummaryProps) => {
|
||||
const { t } = useAppTranslation();
|
||||
|
||||
return (
|
||||
|
||||
@ -28,7 +28,9 @@ export const HomeRecentActivity = ({ items, hasPartialError }: HomeRecentActivit
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
{t("web.home.recentActivity.title")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">{t("web.home.recentActivity.description")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("web.home.recentActivity.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasPartialError ? (
|
||||
|
||||
@ -175,7 +175,10 @@ export const AppSidebarNav = ({ sections }: AppSidebarNavProps) => {
|
||||
render={
|
||||
<CollapsibleTrigger
|
||||
render={
|
||||
<button className="flex w-full items-center gap-2" type="button">
|
||||
<button
|
||||
className="flex w-full items-center gap-2"
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
|
||||
@ -16,10 +16,7 @@ function resolveCompanySecondaryLabel(legalName: string, tradeName: string | nul
|
||||
}
|
||||
|
||||
function resolveCompanyInitials(name: string) {
|
||||
const words = name
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
const words = name.trim().split(/\s+/).filter(Boolean);
|
||||
|
||||
if (words.length === 0) {
|
||||
return "--";
|
||||
|
||||
@ -5,7 +5,9 @@ import { ThemeProviderContext } from "./theme-provider";
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeProviderContext);
|
||||
|
||||
if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider");
|
||||
if (context === undefined) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { i18n, registerTranslations } from "@repo/i18next";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||
|
||||
import enResources from "./en.json";
|
||||
|
||||
@ -10,7 +10,9 @@ import {
|
||||
|
||||
const ds = {
|
||||
custom: async (p: any) => {
|
||||
if (p.method === "get") return { status: "pending", company_id: null };
|
||||
if (p.method === "get") {
|
||||
return { status: "pending", company_id: null };
|
||||
}
|
||||
if (p.path.endsWith("/complete")) {
|
||||
assert.equal(p.data.address.street_2, "2B");
|
||||
return { company_id: "c1" };
|
||||
|
||||
@ -98,13 +98,29 @@ export const completeInitialOnboarding = (
|
||||
|
||||
export const validateInitialCompanyInput = (value: InitialCompanyInput): string[] => {
|
||||
const errors: string[] = [];
|
||||
if (!value.legalName.trim()) errors.push("legalName");
|
||||
if (!value.tin.trim()) errors.push("tin");
|
||||
if (!value.address?.street.trim()) errors.push("address.street");
|
||||
if (!value.address?.city.trim()) errors.push("address.city");
|
||||
if (!value.address?.province.trim()) errors.push("address.province");
|
||||
if (!/^\d{5}$/.test(value.address?.postalCode ?? "")) errors.push("address.postalCode");
|
||||
if (!/^[A-Z]{2}$/.test(value.address?.countryCode ?? "")) errors.push("address.countryCode");
|
||||
if (value.email && !/^\S+@\S+\.\S+$/.test(value.email)) errors.push("email");
|
||||
if (!value.legalName.trim()) {
|
||||
errors.push("legalName");
|
||||
}
|
||||
if (!value.tin.trim()) {
|
||||
errors.push("tin");
|
||||
}
|
||||
if (!value.address?.street.trim()) {
|
||||
errors.push("address.street");
|
||||
}
|
||||
if (!value.address?.city.trim()) {
|
||||
errors.push("address.city");
|
||||
}
|
||||
if (!value.address?.province.trim()) {
|
||||
errors.push("address.province");
|
||||
}
|
||||
if (!/^\d{5}$/.test(value.address?.postalCode ?? "")) {
|
||||
errors.push("address.postalCode");
|
||||
}
|
||||
if (!/^[A-Z]{2}$/.test(value.address?.countryCode ?? "")) {
|
||||
errors.push("address.countryCode");
|
||||
}
|
||||
if (value.email && !/^\S+@\S+\.\S+$/.test(value.email)) {
|
||||
errors.push("email");
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
@ -30,9 +30,13 @@ for (const [name, mutate] of Object.entries({
|
||||
postalCode: () => ({ ...valid, address: { ...valid.address, postalCode: "bad" } }),
|
||||
countryCode: () => ({ ...valid, address: { ...valid.address, countryCode: "ESP" } }),
|
||||
email: () => ({ ...valid, email: "bad" }),
|
||||
}))
|
||||
})) {
|
||||
test(name, () => {
|
||||
const errors = validateInitialCompanyInput(mutate());
|
||||
if (name === "valid") assert.deepEqual(errors, []);
|
||||
else assert.ok(errors.length > 0);
|
||||
if (name === "valid") {
|
||||
assert.deepEqual(errors, []);
|
||||
} else {
|
||||
assert.ok(errors.length > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -15,13 +15,19 @@ export async function completeInitialOnboardingAndRestoreSession(
|
||||
): Promise<CompletionTransition> {
|
||||
try {
|
||||
const restored = await actions.restoreSession();
|
||||
if (restored === null) return { kind: "recoverable", companyId, reason: "restore_failed" };
|
||||
if (restored === null) {
|
||||
return { kind: "recoverable", companyId, reason: "restore_failed" };
|
||||
}
|
||||
} catch {
|
||||
return { kind: "recoverable", companyId, reason: "restore_failed" };
|
||||
}
|
||||
const companies = actions.getAvailableCompanies();
|
||||
const created = companies.find((company) => company.id === companyId);
|
||||
if (!created) return { kind: "recoverable", companyId, reason: "company_missing" };
|
||||
if (companies.length > 1) await actions.changeActiveCompany(companyId);
|
||||
if (!created) {
|
||||
return { kind: "recoverable", companyId, reason: "company_missing" };
|
||||
}
|
||||
if (companies.length > 1) {
|
||||
await actions.changeActiveCompany(companyId);
|
||||
}
|
||||
return { kind: "application", companyId };
|
||||
}
|
||||
|
||||
@ -22,18 +22,25 @@ export const InitialOnboardingPage = () => {
|
||||
const m = useCompleteInitialOnboarding();
|
||||
const { restoreSession, availableCompanies, changeActiveCompany } = useIdentityAuthSession();
|
||||
useEffect(() => {
|
||||
if (!m.data) return;
|
||||
const found = availableCompanies.find((c) => c.id === m.data);
|
||||
if (!found) {
|
||||
if (availableCompanies.length > 0) setRecoverableError("company_missing");
|
||||
if (!m.data) {
|
||||
return;
|
||||
}
|
||||
if (availableCompanies.length > 1)
|
||||
const found = availableCompanies.find((c) => c.id === m.data);
|
||||
if (!found) {
|
||||
if (availableCompanies.length > 0) {
|
||||
setRecoverableError("company_missing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (availableCompanies.length > 1) {
|
||||
changeActiveCompany(m.data).catch(() => setRecoverableError("company_selection_failed"));
|
||||
}
|
||||
}, [m.data, availableCompanies, changeActiveCompany]);
|
||||
const set = (k: string, x: string) => setV((s) => ({ ...s, [k]: x }));
|
||||
const setA = (k: string, x: string) => setV((s) => ({ ...s, address: { ...s.address, [k]: x } }));
|
||||
if (m.isSuccess) return <LoadingOverlay />;
|
||||
if (m.isSuccess) {
|
||||
return <LoadingOverlay />;
|
||||
}
|
||||
return (
|
||||
<main className="mx-auto flex min-h-screen max-w-3xl flex-col justify-center gap-6 p-6">
|
||||
<div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { RenderErrorFallback } from "./render-error-fallback";
|
||||
|
||||
@ -7,10 +7,7 @@ export interface RenderErrorFallbackProps extends Pick<FallbackProps, "resetErro
|
||||
onGoHome: () => void;
|
||||
}
|
||||
|
||||
export const RenderErrorFallback = ({
|
||||
onGoHome,
|
||||
resetErrorBoundary,
|
||||
}: RenderErrorFallbackProps) => {
|
||||
export const RenderErrorFallback = ({ onGoHome, resetErrorBoundary }: RenderErrorFallbackProps) => {
|
||||
const { t } = useAppTranslation();
|
||||
|
||||
return (
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { Navigate, createBrowserRouter } from "react-router-dom";
|
||||
|
||||
import { buildModuleRoutes } from "@/components/module-routes";
|
||||
import { UiKitPage } from "@/development/ui-kit";
|
||||
import { HomePage } from "@/home";
|
||||
import {
|
||||
AppFullscreenLayout,
|
||||
AppFullscreenLayoutFrame,
|
||||
@ -11,11 +13,9 @@ import {
|
||||
AuthLayout,
|
||||
AuthLayoutFrame,
|
||||
} from "@/layout";
|
||||
import { UiKitPage } from "@/development/ui-kit";
|
||||
import { HomePage } from "@/home";
|
||||
import ShadcnShowcasePage from "@/pages/shadcn-ui-page";
|
||||
import { buildSettingsRoute } from "@/settings";
|
||||
import TailwindV4ShowcasePage from "@/pages/tailwindcss-page";
|
||||
import { buildSettingsRoute } from "@/settings";
|
||||
|
||||
import { RouteErrorPage, StaticRouteErrorPage } from "../pages";
|
||||
import { modules } from "../register-modules";
|
||||
|
||||
@ -26,8 +26,9 @@ export const RequireCompanyRouteGuard = ({ children }: RequireCompanyRouteGuardP
|
||||
!provision.isPending &&
|
||||
!provision.isSuccess &&
|
||||
!provision.isError
|
||||
)
|
||||
) {
|
||||
provision.mutate();
|
||||
}
|
||||
}, [
|
||||
availableCompanies.length,
|
||||
statusQuery.data?.status,
|
||||
@ -39,8 +40,9 @@ export const RequireCompanyRouteGuard = ({ children }: RequireCompanyRouteGuardP
|
||||
if (
|
||||
isLoading ||
|
||||
(availableCompanies.length === 0 && (statusQuery.isLoading || provision.isPending))
|
||||
)
|
||||
) {
|
||||
return <LoadingOverlay />;
|
||||
}
|
||||
const navigation = statusQuery.data
|
||||
? resolveCompanyGuardDecision({
|
||||
isAuthenticated: true,
|
||||
@ -48,17 +50,22 @@ export const RequireCompanyRouteGuard = ({ children }: RequireCompanyRouteGuardP
|
||||
onboardingStatus: statusQuery.data.status,
|
||||
})
|
||||
: null;
|
||||
if (activeCompany || navigation === "application") return <>{children}</>;
|
||||
if (statusQuery.isError || provision.isError)
|
||||
if (activeCompany || navigation === "application") {
|
||||
return <>{children}</>;
|
||||
}
|
||||
if (statusQuery.isError || provision.isError) {
|
||||
return <div className="p-8">No se pudo cargar el estado de onboarding.</div>;
|
||||
if (statusQuery.data?.status === "pending" || provision.isSuccess)
|
||||
}
|
||||
if (statusQuery.data?.status === "pending" || provision.isSuccess) {
|
||||
return <InitialOnboardingPage />;
|
||||
if (statusQuery.data?.status === "completed")
|
||||
}
|
||||
if (statusQuery.data?.status === "completed") {
|
||||
return (
|
||||
<div className="p-8">
|
||||
La cuenta tiene un onboarding completado, pero no hay una empresa disponible.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const returnTo = `${location.pathname}${location.search}`;
|
||||
return <Navigate replace to={`/company-selection?returnTo=${encodeURIComponent(returnTo)}`} />;
|
||||
};
|
||||
|
||||
@ -15,9 +15,17 @@ export type InitialOnboardingNavigationInput = {
|
||||
export function resolveInitialOnboardingNavigation(
|
||||
input: InitialOnboardingNavigationInput
|
||||
): InitialOnboardingNavigation {
|
||||
if (!input.isAuthenticated) return "login";
|
||||
if (input.availableCompaniesCount > 0) return "application";
|
||||
if (input.onboardingStatus === "pending") return "onboarding";
|
||||
if (input.onboardingStatus === "completed") return "onboarding_inconsistent";
|
||||
if (!input.isAuthenticated) {
|
||||
return "login";
|
||||
}
|
||||
if (input.availableCompaniesCount > 0) {
|
||||
return "application";
|
||||
}
|
||||
if (input.onboardingStatus === "pending") {
|
||||
return "onboarding";
|
||||
}
|
||||
if (input.onboardingStatus === "completed") {
|
||||
return "onboarding_inconsistent";
|
||||
}
|
||||
return "provision_onboarding";
|
||||
}
|
||||
|
||||
@ -8,11 +8,9 @@ const translate = (key: string) =>
|
||||
"routeErrors.notFound.title": "Página no encontrada",
|
||||
"routeErrors.notFound.description": "La dirección no existe o ha cambiado.",
|
||||
"routeErrors.forbidden.title": "No tienes acceso a esta página",
|
||||
"routeErrors.forbidden.description":
|
||||
"No dispones de permisos para acceder a esta sección.",
|
||||
"routeErrors.forbidden.description": "No dispones de permisos para acceder a esta sección.",
|
||||
"routeErrors.server.title": "No se pudo mostrar esta página",
|
||||
"routeErrors.server.description":
|
||||
"Se produjo un error interno mientras se cargaba la ruta.",
|
||||
"routeErrors.server.description": "Se produjo un error interno mientras se cargaba la ruta.",
|
||||
"routeErrors.unavailable.title": "El servicio no está disponible",
|
||||
"routeErrors.unavailable.description":
|
||||
"La sección que intentas abrir no está disponible en este momento.",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { isRouteErrorResponse } from "react-router-dom";
|
||||
|
||||
import type { PageErrorStateVariant } from "@erp/core/components";
|
||||
import { isRouteErrorResponse } from "react-router-dom";
|
||||
|
||||
export type RouteErrorPageVariant = "notFound" | "forbidden" | "server" | "unavailable" | "unknown";
|
||||
|
||||
@ -11,9 +10,7 @@ export interface RouteErrorPageViewModel {
|
||||
canRetry: boolean;
|
||||
}
|
||||
|
||||
interface Translate {
|
||||
(key: string): string;
|
||||
}
|
||||
type Translate = (key: string) => string;
|
||||
|
||||
const normalizeVariantFromStatus = (status: number): RouteErrorPageVariant => {
|
||||
if (status === 403 || status === 401) {
|
||||
|
||||
@ -62,10 +62,7 @@ export const buildSettingsRoute = ({
|
||||
{
|
||||
index: true,
|
||||
element: (
|
||||
<SettingsIndexRoute
|
||||
hasEntries={Boolean(firstEntryPath)}
|
||||
redirectPath={firstEntryPath}
|
||||
/>
|
||||
<SettingsIndexRoute hasEntries={Boolean(firstEntryPath)} redirectPath={firstEntryPath} />
|
||||
),
|
||||
},
|
||||
...settingsChildRoutes,
|
||||
|
||||
@ -38,4 +38,4 @@
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,19 @@ import dotenv from "dotenv";
|
||||
|
||||
import { WorkerEnvSchema } from "./worker-env-schema";
|
||||
|
||||
export type WorkerConfig = { readonly env: "development" | "test" | "production"; readonly timezone: string; readonly database: DatabaseConfig; readonly jobs: { readonly recurringProformas: { readonly enabled: boolean; readonly intervalMs: number; readonly batchSize: number; readonly maxOccurrencesPerRecurringPerRun: number } } };
|
||||
export type WorkerConfig = {
|
||||
readonly env: "development" | "test" | "production";
|
||||
readonly timezone: string;
|
||||
readonly database: DatabaseConfig;
|
||||
readonly jobs: {
|
||||
readonly recurringProformas: {
|
||||
readonly enabled: boolean;
|
||||
readonly intervalMs: number;
|
||||
readonly batchSize: number;
|
||||
readonly maxOccurrencesPerRecurringPerRun: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function loadWorkerConfig(logger: ILogger): WorkerConfig {
|
||||
dotenv.config();
|
||||
@ -14,5 +26,27 @@ export function loadWorkerConfig(logger: ILogger): WorkerConfig {
|
||||
throw new Error("Invalid worker environment configuration");
|
||||
}
|
||||
const env = parsed.data;
|
||||
return { env: env.NODE_ENV, timezone: env.TIMEZONE, database: { dialect: env.DB_DIALECT, host: env.DB_HOST, port: env.DB_PORT, name: env.DB_NAME, user: env.DB_USER, password: env.DB_PASS, logging: env.DB_LOGGING, ssl: env.DB_SSL, timezone: env.TIMEZONE }, jobs: { recurringProformas: { enabled: env.RECURRING_PROFORMAS_JOB_ENABLED, intervalMs: env.RECURRING_PROFORMAS_JOB_INTERVAL_MS, batchSize: env.RECURRING_PROFORMAS_BATCH_SIZE, maxOccurrencesPerRecurringPerRun: env.RECURRING_PROFORMAS_MAX_OCCURRENCES_PER_RUN } } };
|
||||
return {
|
||||
env: env.NODE_ENV,
|
||||
timezone: env.TIMEZONE,
|
||||
database: {
|
||||
dialect: env.DB_DIALECT,
|
||||
host: env.DB_HOST,
|
||||
port: env.DB_PORT,
|
||||
name: env.DB_NAME,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASS,
|
||||
logging: env.DB_LOGGING,
|
||||
ssl: env.DB_SSL,
|
||||
timezone: env.TIMEZONE,
|
||||
},
|
||||
jobs: {
|
||||
recurringProformas: {
|
||||
enabled: env.RECURRING_PROFORMAS_JOB_ENABLED,
|
||||
intervalMs: env.RECURRING_PROFORMAS_JOB_INTERVAL_MS,
|
||||
batchSize: env.RECURRING_PROFORMAS_BATCH_SIZE,
|
||||
maxOccurrencesPerRecurringPerRun: env.RECURRING_PROFORMAS_MAX_OCCURRENCES_PER_RUN,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@ import { configureLogger, setLoggerSingleton } from "@repo/rdx-logger";
|
||||
|
||||
import { loadWorkerConfig } from "./config/worker-config";
|
||||
import { registerWorkerModules } from "./modules/register-modules";
|
||||
import { Scheduler } from "./scheduler/scheduler";
|
||||
import { registerJobs } from "./scheduler/register-jobs";
|
||||
import { Scheduler } from "./scheduler/scheduler";
|
||||
|
||||
const logger = configureLogger("console", { service: "worker" });
|
||||
setLoggerSingleton(logger);
|
||||
@ -29,16 +29,57 @@ let stopping: Promise<void> | null = null;
|
||||
async function bootstrap(): Promise<void> {
|
||||
logger.info("Worker starting");
|
||||
const database = await createSequelize(config.database, logger);
|
||||
const audit = new Audit({ source: { type: "application", id: "factuges-worker" }, contextProvider: new OperationAuditContextProvider(operationContext), store: new SequelizeAuditStore(AuditEventModel, AuditChangeModel, transactionContext) });
|
||||
const audit = new Audit({
|
||||
source: { type: "application", id: "factuges-worker" },
|
||||
contextProvider: new OperationAuditContextProvider(operationContext),
|
||||
store: new SequelizeAuditStore(AuditEventModel, AuditChangeModel, transactionContext),
|
||||
});
|
||||
registerWorkerModules();
|
||||
registerModels(auditModelInitializers, "worker-infrastructure");
|
||||
const modulesRuntime = await initModules({ config, database, logger, operationContext, transactionContext, audit, warmup: { timeoutMs: 10_000, strict: false }, databaseSyncMode: "none", getService: () => { throw new Error("Services are available only in module scope."); } });
|
||||
const scheduler = new Scheduler(registerJobs({ config, logger, runtime: modulesRuntime }), logger);
|
||||
const modulesRuntime = await initModules({
|
||||
config,
|
||||
database,
|
||||
logger,
|
||||
operationContext,
|
||||
transactionContext,
|
||||
audit,
|
||||
warmup: { timeoutMs: 10_000, strict: false },
|
||||
databaseSyncMode: "none",
|
||||
getService: () => {
|
||||
throw new Error("Services are available only in module scope.");
|
||||
},
|
||||
});
|
||||
const scheduler = new Scheduler(
|
||||
registerJobs({ config, logger, runtime: modulesRuntime }),
|
||||
logger
|
||||
);
|
||||
scheduler.start();
|
||||
logger.info("Worker started");
|
||||
const shutdown = async () => { if (stopping) return stopping; stopping = (async () => { logger.info("Worker stopping"); await scheduler.stop(); await closeSequelize(database, logger); logger.info("Worker stopped"); })(); return stopping; };
|
||||
process.on("SIGINT", () => { shutdown().then(() => process.exit(0)).catch(() => process.exit(1)); });
|
||||
process.on("SIGTERM", () => { shutdown().then(() => process.exit(0)).catch(() => process.exit(1)); });
|
||||
const shutdown = async () => {
|
||||
if (stopping) {
|
||||
return stopping;
|
||||
}
|
||||
stopping = (async () => {
|
||||
logger.info("Worker stopping");
|
||||
await scheduler.stop();
|
||||
await closeSequelize(database, logger);
|
||||
logger.info("Worker stopped");
|
||||
})();
|
||||
return stopping;
|
||||
};
|
||||
process.on("SIGINT", () => {
|
||||
shutdown()
|
||||
.then(() => process.exit(0))
|
||||
.catch(() => process.exit(1));
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
shutdown()
|
||||
.then(() => process.exit(0))
|
||||
.catch(() => process.exit(1));
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap().catch((error) => { logger.error("Worker bootstrap failed", { error }); process.exit(1); });
|
||||
bootstrap().catch((error) => {
|
||||
logger.error("Worker bootstrap failed", { error });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@ -10,35 +10,62 @@ import type { ScheduledJob } from "../scheduler/scheduled-job";
|
||||
export class RecurringProformaScheduledJob implements ScheduledJob {
|
||||
public readonly name = "recurring-proformas";
|
||||
|
||||
public constructor(private readonly deps: {
|
||||
intervalMs: number;
|
||||
batchSize: number;
|
||||
maxOccurrencesPerRecurringPerRun: number;
|
||||
repository: RecurringProformaDiscoveryCapability;
|
||||
processor: RecurringProformaOccurrenceProcessorCapability;
|
||||
logger: ILogger;
|
||||
dueThrough: () => Date;
|
||||
}) {}
|
||||
public constructor(
|
||||
private readonly deps: {
|
||||
intervalMs: number;
|
||||
batchSize: number;
|
||||
maxOccurrencesPerRecurringPerRun: number;
|
||||
repository: RecurringProformaDiscoveryCapability;
|
||||
processor: RecurringProformaOccurrenceProcessorCapability;
|
||||
logger: ILogger;
|
||||
dueThrough: () => Date;
|
||||
}
|
||||
) {}
|
||||
|
||||
public get intervalMs(): number { return this.deps.intervalMs; }
|
||||
public get intervalMs(): number {
|
||||
return this.deps.intervalMs;
|
||||
}
|
||||
|
||||
public async execute(): Promise<void> {
|
||||
const dueThrough = this.deps.dueThrough();
|
||||
const discovered = await this.deps.repository.findDue(dueThrough, this.deps.batchSize);
|
||||
if (discovered.isFailure) throw discovered.error;
|
||||
this.deps.logger.info("Recurring proformas job started", { dueThrough: dueThrough.toISOString(), discovered: discovered.data.length });
|
||||
if (discovered.isFailure) {
|
||||
throw discovered.error;
|
||||
}
|
||||
this.deps.logger.info("Recurring proformas job started", {
|
||||
dueThrough: dueThrough.toISOString(),
|
||||
discovered: discovered.data.length,
|
||||
});
|
||||
for (const due of discovered.data) {
|
||||
try {
|
||||
for (let count = 0; count < this.deps.maxOccurrencesPerRecurringPerRun; count += 1) {
|
||||
const processed = await this.deps.processor.process({ companyId: due.companyId, recurringProformaId: due.recurringProformaId });
|
||||
if (!processed.isSuccess) break;
|
||||
const processed = await this.deps.processor.process({
|
||||
companyId: due.companyId,
|
||||
recurringProformaId: due.recurringProformaId,
|
||||
});
|
||||
if (!processed.isSuccess) {
|
||||
break;
|
||||
}
|
||||
const state = processed.data;
|
||||
if (state?.recurringProformaStatus === "completed" || state?.nextOccurrenceDate === null || state?.nextOccurrenceDate === undefined || state.nextOccurrenceDate.getTime() > dueThrough.getTime()) break;
|
||||
if (
|
||||
state?.recurringProformaStatus === "completed" ||
|
||||
state?.nextOccurrenceDate === null ||
|
||||
state?.nextOccurrenceDate === undefined ||
|
||||
state.nextOccurrenceDate.getTime() > dueThrough.getTime()
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.deps.logger.error("Recurring proforma job candidate failed", { recurringProformaId: due.recurringProformaId.toPrimitive(), companyId: due.companyId.toPrimitive(), error });
|
||||
this.deps.logger.error("Recurring proforma job candidate failed", {
|
||||
recurringProformaId: due.recurringProformaId.toPrimitive(),
|
||||
companyId: due.companyId.toPrimitive(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.deps.logger.info("Recurring proformas job completed", { dueThrough: dueThrough.toISOString() });
|
||||
this.deps.logger.info("Recurring proformas job completed", {
|
||||
dueThrough: dueThrough.toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { registerModule } from "@erp/core/api";
|
||||
import { catalogsModule } from "@erp/catalogs/module";
|
||||
import { companiesModule } from "@erp/companies/module";
|
||||
import { registerModule } from "@erp/core/api";
|
||||
import { customerInvoicesModule } from "@erp/customer-invoices/api/module";
|
||||
import { customersModule } from "@erp/customers/module";
|
||||
import { documentSeriesModule } from "@erp/document-series/module";
|
||||
|
||||
@ -3,6 +3,7 @@ import type { ILogger } from "@repo/rdx-logger";
|
||||
|
||||
import type { WorkerConfig } from "../config/worker-config";
|
||||
import { RecurringProformaScheduledJob } from "../jobs/recurring-proforma-scheduled-job";
|
||||
|
||||
import type { ScheduledJob } from "./scheduled-job";
|
||||
|
||||
/** SSOT explícita de jobs activos. */
|
||||
@ -11,18 +12,23 @@ export function registerJobs(params: {
|
||||
logger: ILogger;
|
||||
runtime: ModulesRuntime;
|
||||
}): ScheduledJob[] {
|
||||
if (!params.config.jobs.recurringProformas.enabled) return [];
|
||||
if (!params.config.jobs.recurringProformas.enabled) {
|
||||
return [];
|
||||
}
|
||||
const recurring = params.runtime.customerInvoices.recurringProformas;
|
||||
return [new RecurringProformaScheduledJob({
|
||||
intervalMs: params.config.jobs.recurringProformas.intervalMs,
|
||||
batchSize: params.config.jobs.recurringProformas.batchSize,
|
||||
maxOccurrencesPerRecurringPerRun: params.config.jobs.recurringProformas.maxOccurrencesPerRecurringPerRun,
|
||||
repository: recurring.repository,
|
||||
processor: recurring.occurrenceProcessor,
|
||||
logger: params.logger,
|
||||
dueThrough: () => {
|
||||
const now = new Date();
|
||||
return new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
|
||||
},
|
||||
})];
|
||||
return [
|
||||
new RecurringProformaScheduledJob({
|
||||
intervalMs: params.config.jobs.recurringProformas.intervalMs,
|
||||
batchSize: params.config.jobs.recurringProformas.batchSize,
|
||||
maxOccurrencesPerRecurringPerRun:
|
||||
params.config.jobs.recurringProformas.maxOccurrencesPerRecurringPerRun,
|
||||
repository: recurring.repository,
|
||||
processor: recurring.occurrenceProcessor,
|
||||
logger: params.logger,
|
||||
dueThrough: () => {
|
||||
const now = new Date();
|
||||
return new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@ -1 +1,5 @@
|
||||
export interface ScheduledJob { readonly name: string; readonly intervalMs: number; execute(): Promise<void>; }
|
||||
export interface ScheduledJob {
|
||||
readonly name: string;
|
||||
readonly intervalMs: number;
|
||||
execute(): Promise<void>;
|
||||
}
|
||||
|
||||
@ -7,7 +7,14 @@ import { Scheduler } from "./scheduler";
|
||||
|
||||
test("Scheduler rechaza nombres duplicados", () => {
|
||||
assert.throws(
|
||||
() => new Scheduler([{ name: "same", intervalMs: 1, execute: async () => undefined }, { name: "same", intervalMs: 1, execute: async () => undefined }], configureLogger()),
|
||||
() =>
|
||||
new Scheduler(
|
||||
[
|
||||
{ name: "same", intervalMs: 1, execute: async () => undefined },
|
||||
{ name: "same", intervalMs: 1, execute: async () => undefined },
|
||||
],
|
||||
configureLogger()
|
||||
),
|
||||
/Duplicate scheduled job name/
|
||||
);
|
||||
});
|
||||
@ -15,7 +22,21 @@ test("Scheduler rechaza nombres duplicados", () => {
|
||||
test("Scheduler ejecuta inmediatamente y espera la ejecución activa al parar", async () => {
|
||||
let started = false;
|
||||
let release: (() => void) | undefined;
|
||||
const scheduler = new Scheduler([{ name: "probe", intervalMs: 1, execute: async () => { started = true; await new Promise<void>((resolve) => { release = resolve; }); } }], configureLogger());
|
||||
const scheduler = new Scheduler(
|
||||
[
|
||||
{
|
||||
name: "probe",
|
||||
intervalMs: 1,
|
||||
execute: async () => {
|
||||
started = true;
|
||||
await new Promise<void>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
configureLogger()
|
||||
);
|
||||
scheduler.start();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert.equal(started, true);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ILogger } from "@repo/rdx-logger";
|
||||
|
||||
import type { ScheduledJob } from "./scheduled-job";
|
||||
|
||||
export class Scheduler {
|
||||
@ -7,37 +8,66 @@ export class Scheduler {
|
||||
private readonly delays = new Set<() => void>();
|
||||
private idleHandle: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(private readonly jobs: readonly ScheduledJob[], private readonly logger: ILogger) {
|
||||
constructor(
|
||||
private readonly jobs: readonly ScheduledJob[],
|
||||
private readonly logger: ILogger
|
||||
) {
|
||||
const names = new Set<string>();
|
||||
for (const job of jobs) { if (names.has(job.name)) throw new Error(`Duplicate scheduled job name: ${job.name}`); names.add(job.name); }
|
||||
for (const job of jobs) {
|
||||
if (names.has(job.name)) {
|
||||
throw new Error(`Duplicate scheduled job name: ${job.name}`);
|
||||
}
|
||||
names.add(job.name);
|
||||
}
|
||||
}
|
||||
|
||||
start(): void {
|
||||
for (const job of this.jobs) { this.logger.info("Job registered", { job: job.name }); this.loops.push(this.run(job)); }
|
||||
if (this.jobs.length === 0) this.idleHandle = setInterval(() => undefined, 2_147_483_647);
|
||||
for (const job of this.jobs) {
|
||||
this.logger.info("Job registered", { job: job.name });
|
||||
this.loops.push(this.run(job));
|
||||
}
|
||||
if (this.jobs.length === 0) {
|
||||
this.idleHandle = setInterval(() => undefined, 2_147_483_647);
|
||||
}
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (this.stopping) return;
|
||||
if (this.stopping) {
|
||||
return;
|
||||
}
|
||||
this.stopping = true;
|
||||
if (this.idleHandle) clearInterval(this.idleHandle);
|
||||
for (const resolve of this.delays) resolve();
|
||||
if (this.idleHandle) {
|
||||
clearInterval(this.idleHandle);
|
||||
}
|
||||
for (const resolve of this.delays) {
|
||||
resolve();
|
||||
}
|
||||
await Promise.all(this.loops);
|
||||
}
|
||||
|
||||
private async run(job: ScheduledJob): Promise<void> {
|
||||
while (!this.stopping) {
|
||||
this.logger.debug("Job started", { job: job.name });
|
||||
try { await job.execute(); this.logger.debug("Job completed", { job: job.name }); }
|
||||
catch (error) { this.logger.error("Job failed", { job: job.name, error }); }
|
||||
if (!this.stopping) await this.delay(job.intervalMs);
|
||||
try {
|
||||
await job.execute();
|
||||
this.logger.debug("Job completed", { job: job.name });
|
||||
} catch (error) {
|
||||
this.logger.error("Job failed", { job: job.name, error });
|
||||
}
|
||||
if (!this.stopping) {
|
||||
await this.delay(job.intervalMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async delay(intervalMs: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
const cancel = () => { clearTimeout(timeout); this.delays.delete(cancel); resolve(); };
|
||||
const cancel = () => {
|
||||
clearTimeout(timeout);
|
||||
this.delays.delete(cancel);
|
||||
resolve();
|
||||
};
|
||||
timeout = setTimeout(cancel, intervalMs);
|
||||
this.delays.add(cancel);
|
||||
});
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/express.json",
|
||||
"compilerOptions": { "baseUrl": ".", "noEmit": true, "allowImportingTsExtensions": true, "rootDir": "./src", "outDir": "./dist", "types": ["node"] },
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({ entry: ["src/index.ts"], format: ["esm"], target: "node22", bundle: true, clean: true, outDir: "dist", platform: "node", noExternal: ["@repo", "@erp"] });
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
target: "node22",
|
||||
bundle: true,
|
||||
clean: true,
|
||||
outDir: "dist",
|
||||
platform: "node",
|
||||
noExternal: ["@repo", "@erp"],
|
||||
});
|
||||
|
||||
95
biome.json
95
biome.json
@ -61,9 +61,7 @@
|
||||
"options": {
|
||||
"strictCase": false,
|
||||
"requireAscii": true,
|
||||
"filenameCases": [
|
||||
"kebab-case"
|
||||
]
|
||||
"filenameCases": ["kebab-case"]
|
||||
}
|
||||
},
|
||||
"useForOf": "error",
|
||||
@ -78,28 +76,19 @@
|
||||
"selector": {
|
||||
"kind": "function"
|
||||
},
|
||||
"formats": [
|
||||
"camelCase",
|
||||
"PascalCase"
|
||||
]
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": {
|
||||
"kind": "variable"
|
||||
},
|
||||
"formats": [
|
||||
"camelCase",
|
||||
"PascalCase",
|
||||
"CONSTANT_CASE"
|
||||
]
|
||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||
},
|
||||
{
|
||||
"selector": {
|
||||
"kind": "typeLike"
|
||||
},
|
||||
"formats": [
|
||||
"PascalCase"
|
||||
]
|
||||
"formats": ["PascalCase"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -242,46 +231,21 @@
|
||||
"groups": [
|
||||
":URL:",
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
":BUN:",
|
||||
":NODE:"
|
||||
],
|
||||
[":BUN:", ":NODE:"],
|
||||
":BLANK_LINE:",
|
||||
":PACKAGE_WITH_PROTOCOL:",
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
":PACKAGE:"
|
||||
],
|
||||
[":PACKAGE:"],
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
"!@/**",
|
||||
"!#*",
|
||||
"!~*",
|
||||
"!$*",
|
||||
"!%*"
|
||||
],
|
||||
["!@/**", "!#*", "!~*", "!$*", "!%*"],
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
"@/**",
|
||||
"#*",
|
||||
"~*",
|
||||
"$*",
|
||||
"%*"
|
||||
],
|
||||
["@/**", "#*", "~*", "$*", "%*"],
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
":PATH:",
|
||||
"!./**",
|
||||
"!../**"
|
||||
],
|
||||
[":PATH:", "!./**", "!../**"],
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
"../**"
|
||||
],
|
||||
["../**"],
|
||||
":BLANK_LINE:",
|
||||
[
|
||||
"./**"
|
||||
]
|
||||
["./**"]
|
||||
],
|
||||
"identifierOrder": "lexicographic"
|
||||
}
|
||||
@ -301,12 +265,7 @@
|
||||
"semicolons": "always",
|
||||
"trailingCommas": "es5"
|
||||
},
|
||||
"globals": [
|
||||
"console",
|
||||
"process",
|
||||
"__dirname",
|
||||
"__filename"
|
||||
]
|
||||
"globals": ["console", "process", "__dirname", "__filename"]
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
@ -340,11 +299,7 @@
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": [
|
||||
"**/*.test.{js,ts,tsx}",
|
||||
"**/*.spec.{js,ts,tsx}",
|
||||
"**/__tests__/**"
|
||||
],
|
||||
"includes": ["**/*.test.{js,ts,tsx}", "**/*.spec.{js,ts,tsx}", "**/__tests__/**"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
@ -358,11 +313,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"**/next.config.{js,ts}",
|
||||
"**/tailwind.config.{js,ts}",
|
||||
"**/*.config.{js,ts}"
|
||||
],
|
||||
"includes": ["**/next.config.{js,ts}", "**/tailwind.config.{js,ts}", "**/*.config.{js,ts}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
@ -375,11 +326,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"**/pages/**",
|
||||
"**/app/**/page.{tsx,jsx}",
|
||||
"**/app/**/layout.{tsx,jsx}"
|
||||
],
|
||||
"includes": ["**/pages/**", "**/app/**/page.{tsx,jsx}", "**/app/**/layout.{tsx,jsx}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
@ -389,10 +336,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"**/*.d.ts",
|
||||
"**/lib/env/*.ts"
|
||||
],
|
||||
"includes": ["**/*.d.ts", "**/lib/env/*.ts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
@ -406,10 +350,7 @@
|
||||
"selector": {
|
||||
"kind": "objectLiteralProperty"
|
||||
},
|
||||
"formats": [
|
||||
"CONSTANT_CASE",
|
||||
"camelCase"
|
||||
]
|
||||
"formats": ["CONSTANT_CASE", "camelCase"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -430,4 +371,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,14 @@ export default [
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
ignores: [
|
||||
"**/docs/**",
|
||||
"**/docs/**",
|
||||
"**/dist/**",
|
||||
"**/out/**",
|
||||
"**/.turbo/**",
|
||||
"**/.vscode/**",
|
||||
"**/node_modules/**",
|
||||
"**/scripts/**",
|
||||
"**/tools/**"
|
||||
"**/tools/**",
|
||||
],
|
||||
languageOptions: {
|
||||
parser,
|
||||
@ -61,4 +61,4 @@ export default [
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
@ -40,4 +40,4 @@
|
||||
"react-router-dom": "^7.14.0",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,10 +37,11 @@ export const useAuditPageController = () => {
|
||||
setSearchInput(state.filters.q ?? "");
|
||||
}, [state.filters.q]);
|
||||
useEffect(() => {
|
||||
if (debouncedSearch.trim() !== (state.filters.q ?? ""))
|
||||
if (debouncedSearch.trim() !== (state.filters.q ?? "")) {
|
||||
setSearchParams(
|
||||
serializeAuditPageState(updateAuditFilters(state, { q: debouncedSearch.trim() || null }))
|
||||
);
|
||||
}
|
||||
}, [debouncedSearch, setSearchParams, state]);
|
||||
const params = useMemo(() => toAuditSearchParams(state, new Date()), [state]);
|
||||
const liveState = resolveAuditLiveState({
|
||||
|
||||
@ -117,22 +117,29 @@ export const AuditFilterChips = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const chips: [string, string, Partial<AuditFilters>][] = [];
|
||||
if (filters.period !== "last7Days")
|
||||
if (filters.period !== "last7Days") {
|
||||
chips.push([
|
||||
"period",
|
||||
t(`audit.period.${filters.period}`),
|
||||
{ period: "last7Days", from: null, to: null },
|
||||
]);
|
||||
if (filters.kind) chips.push(["kind", t(`audit.kind.${filters.kind}`), { kind: null }]);
|
||||
if (filters.targetType)
|
||||
}
|
||||
if (filters.kind) {
|
||||
chips.push(["kind", t(`audit.kind.${filters.kind}`), { kind: null }]);
|
||||
}
|
||||
if (filters.targetType) {
|
||||
chips.push([
|
||||
"target",
|
||||
t(`audit.targets.${filters.targetType}`, filters.targetType),
|
||||
{ targetType: null },
|
||||
]);
|
||||
if (filters.action)
|
||||
}
|
||||
if (filters.action) {
|
||||
chips.push(["action", t(`audit.actions.${filters.action}`, filters.action), { action: null }]);
|
||||
if (!(chips.length || filters.q)) return null;
|
||||
}
|
||||
if (!(chips.length || filters.q)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{chips.map(([id, label, value]) => (
|
||||
|
||||
@ -45,16 +45,25 @@ export const presentAuditPath = (path: string, translate: Translate) => {
|
||||
};
|
||||
|
||||
export const presentAuditValue = (value: AuditValue, translate: Translate): string => {
|
||||
if (value === null) return translate("audit.values.null");
|
||||
if (value === true) return translate("audit.values.yes");
|
||||
if (value === false) return translate("audit.values.no");
|
||||
if (typeof value === "string" || typeof value === "number") return String(value);
|
||||
if (value === null) {
|
||||
return translate("audit.values.null");
|
||||
}
|
||||
if (value === true) {
|
||||
return translate("audit.values.yes");
|
||||
}
|
||||
if (value === false) {
|
||||
return translate("audit.values.no");
|
||||
}
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
return JSON.stringify(value, null, 2);
|
||||
};
|
||||
|
||||
export const getAuditChangeSections = (change: AuditChange, translate: Translate) => {
|
||||
if (change.kind === "set")
|
||||
if (change.kind === "set") {
|
||||
return [{ label: translate("audit.changes.newValue"), value: change.after }];
|
||||
}
|
||||
if (change.kind === "unset") {
|
||||
return [{ label: translate("audit.changes.removedValue"), value: change.before }];
|
||||
}
|
||||
|
||||
@ -63,16 +63,32 @@ export const parseAuditPageState = (params: URLSearchParams): AuditPageState =>
|
||||
export const serializeAuditPageState = (state: AuditPageState) => {
|
||||
const p = new URLSearchParams();
|
||||
const f = state.filters;
|
||||
if (f.q) p.set("q", f.q);
|
||||
if (f.period !== "last7Days") p.set("period", f.period);
|
||||
if (f.period === "custom") {
|
||||
if (f.from) p.set("from", f.from);
|
||||
if (f.to) p.set("to", f.to);
|
||||
if (f.q) {
|
||||
p.set("q", f.q);
|
||||
}
|
||||
if (f.period !== "last7Days") {
|
||||
p.set("period", f.period);
|
||||
}
|
||||
if (f.period === "custom") {
|
||||
if (f.from) {
|
||||
p.set("from", f.from);
|
||||
}
|
||||
if (f.to) {
|
||||
p.set("to", f.to);
|
||||
}
|
||||
}
|
||||
if (f.kind) {
|
||||
p.set("kind", f.kind);
|
||||
}
|
||||
if (f.targetType) {
|
||||
p.set("targetType", f.targetType);
|
||||
}
|
||||
if (f.action) {
|
||||
p.set("action", f.action);
|
||||
}
|
||||
if (state.entry) {
|
||||
p.set("entry", state.entry);
|
||||
}
|
||||
if (f.kind) p.set("kind", f.kind);
|
||||
if (f.targetType) p.set("targetType", f.targetType);
|
||||
if (f.action) p.set("action", f.action);
|
||||
if (state.entry) p.set("entry", state.entry);
|
||||
p.set("pageNumber", String(state.pageNumber + 1));
|
||||
p.set("pageSize", String(state.pageSize));
|
||||
p.set("orderBy", state.orderBy);
|
||||
@ -81,7 +97,9 @@ export const serializeAuditPageState = (state: AuditPageState) => {
|
||||
};
|
||||
export const toAuditSearchParams = (state: AuditPageState, now: Date): AuditSearchParams | null => {
|
||||
const range = resolveAuditPeriodRange(state.filters.period, now, state.filters);
|
||||
if (!range) return null;
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
const f = state.filters;
|
||||
return {
|
||||
pageNumber: state.pageNumber,
|
||||
|
||||
@ -5,8 +5,12 @@ export const isAuditPeriodLiveCompatible = (
|
||||
now: Date,
|
||||
custom?: { from: string | null; to: string | null }
|
||||
) => {
|
||||
if (period !== "custom") return true;
|
||||
if (!(custom?.from && custom.to)) return false;
|
||||
if (period !== "custom") {
|
||||
return true;
|
||||
}
|
||||
if (!(custom?.from && custom.to)) {
|
||||
return false;
|
||||
}
|
||||
const from = new Date(custom.from).getTime();
|
||||
const to = new Date(custom.to).getTime();
|
||||
const current = now.getTime();
|
||||
@ -17,10 +21,11 @@ export const resolveAuditPeriodRange = (
|
||||
now: Date,
|
||||
custom?: { from: string | null; to: string | null }
|
||||
) => {
|
||||
if (period === "custom")
|
||||
if (period === "custom") {
|
||||
return custom?.from && custom.to && custom.from < custom.to
|
||||
? { from: custom.from, to: custom.to }
|
||||
: null;
|
||||
}
|
||||
if (period === "today") {
|
||||
const from = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const to = new Date(from);
|
||||
@ -35,8 +40,12 @@ export const localDateTimeToIso = (value: string) => {
|
||||
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
||||
};
|
||||
export const isoToLocalDateTime = (value: string | null) => {
|
||||
if (!value) return "";
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return "";
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return "";
|
||||
}
|
||||
return new Date(date.getTime() - date.getTimezoneOffset() * 60_000).toISOString().slice(0, 16);
|
||||
};
|
||||
|
||||
@ -13,7 +13,9 @@ export const findAuditEntryById = async (
|
||||
dataSource: IDataSource,
|
||||
{ id, signal }: FindAuditEntryByIdParams
|
||||
): Promise<AuditEntryDetail> => {
|
||||
if (!id) throw new Error("auditEntryId is required");
|
||||
if (!id) {
|
||||
throw new Error("auditEntryId is required");
|
||||
}
|
||||
|
||||
const response = await dataSource.getOne<unknown>("audit", id, { signal });
|
||||
return mapAuditEntryDetail(AuditEntryDetailDTOSchema.parse(response));
|
||||
|
||||
@ -15,7 +15,9 @@ export const useAuditEntry = (id: string | null) => {
|
||||
return useQuery<AuditEntryDetail, DefaultError>({
|
||||
queryKey: auditQueryKeys.detail(companyId, id ?? "none"),
|
||||
queryFn: ({ signal }) => {
|
||||
if (!id) throw new Error("auditEntryId is required");
|
||||
if (!id) {
|
||||
throw new Error("auditEntryId is required");
|
||||
}
|
||||
return findAuditEntryById(dataSource, { id, signal });
|
||||
},
|
||||
enabled: activeCompany !== null && id !== null,
|
||||
|
||||
@ -10,7 +10,9 @@ const mapAuditValue = (value: AuditValueDto): AuditValue => {
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
if (Array.isArray(value)) return value.map(mapUnknownAuditValue);
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(mapUnknownAuditValue);
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [key, mapUnknownAuditValue(item)])
|
||||
);
|
||||
@ -25,7 +27,9 @@ const mapUnknownAuditValue = (value: unknown): AuditValue => {
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
if (Array.isArray(value)) return value.map(mapUnknownAuditValue);
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(mapUnknownAuditValue);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [key, mapUnknownAuditValue(item)])
|
||||
|
||||
@ -37,4 +37,4 @@
|
||||
"react-router-dom": "^7.14.0",
|
||||
"react-secure-storage": "^1.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,4 +12,5 @@ const authService = new AuthService(authenticatedUserRepository, tabContextRepos
|
||||
const tabContextService = new TabContextService(tabContextRepository);
|
||||
|
||||
const authProvider = new PassportAuthProvider(authService, tabContextService, transactionManager);
|
||||
|
||||
export { authProvider };
|
||||
|
||||
@ -59,7 +59,7 @@ export class PassportAuthProvider {
|
||||
constructor(
|
||||
private readonly authService: IAuthService,
|
||||
private readonly tabContextService: ITabContextService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
readonly _transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./login.dto";
|
||||
export * from "./get-profile.dto";
|
||||
export * from "./login.dto";
|
||||
|
||||
@ -3,7 +3,7 @@ import type { RouteObject } from "react-router-dom";
|
||||
|
||||
import { LoginPage } from "./pages";
|
||||
|
||||
export const AuthRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
export const AuthRoutes = (_params: ModuleClientParams): RouteObject[] => {
|
||||
return [
|
||||
{
|
||||
path: "login",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { JSX } from "react";
|
||||
import type { JSX } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
import { useIsAuthenticated } from "../hooks";
|
||||
|
||||
/**
|
||||
@ -9,7 +10,7 @@ export const AuthGuard = ({ children }: { children: JSX.Element }) => {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to='/login' replace />;
|
||||
return <Navigate replace to="/login" />;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export const AuthLayout = ({ children }: PropsWithChildren) => {
|
||||
return <>{children}</>;
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
Input,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
|
||||
import { type SubmitErrorHandler, type SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
type LoginFormValues = {
|
||||
email: string;
|
||||
@ -39,20 +39,20 @@ export function LoginForm({ onSubmit, onInvalid, className, ...props }: LoginFor
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className='text-2xl'>Login</CardTitle>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>Enter your email below to login to your account</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit, onInvalid)} className='flex flex-col gap-6'>
|
||||
<form className="flex flex-col gap-6" onSubmit={form.handleSubmit(onSubmit, onInvalid)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type='email' autoComplete='username' {...field} />
|
||||
<Input autoComplete="username" type="email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -61,19 +61,19 @@ export function LoginForm({ onSubmit, onInvalid, className, ...props }: LoginFor
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='password'
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input type='password' autoComplete='current-password' {...field} />
|
||||
<Input autoComplete="current-password" type="password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' className='w-full'>
|
||||
<Button className="w-full" type="submit">
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { PropsWithChildren, createContext, useEffect, useState } from "react";
|
||||
import { IAuthService } from "../services";
|
||||
import { type PropsWithChildren, createContext, useEffect, useState } from "react";
|
||||
|
||||
import type { IAuthService } from "../services";
|
||||
|
||||
export type AuthContextType = {
|
||||
token: string | null;
|
||||
@ -30,7 +31,7 @@ export const AuthProvider = ({
|
||||
|
||||
useEffect(() => {
|
||||
setToken(getAccessToken());
|
||||
}, []);
|
||||
}, [getAccessToken]);
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
const { token } = await authService.login({ email, password });
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
import { AuthContext, AuthContextType } from "../context";
|
||||
|
||||
import { AuthContext, type AuthContextType } from "../context";
|
||||
|
||||
/**
|
||||
* Hook para acceder al contexto de autenticación.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ILoginRequestDTO, ILoginResponseDTO } from "../../common";
|
||||
|
||||
import type { ILoginRequestDTO, ILoginResponseDTO } from "../../common";
|
||||
|
||||
export interface IAuthService {
|
||||
login: (credentials: ILoginRequestDTO) => Promise<ILoginResponseDTO>;
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useAuth } from "../hooks";
|
||||
import { User } from "./types";
|
||||
|
||||
import type { User } from "./types";
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const { token } = useAuth();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { User } from "./types";
|
||||
|
||||
import type { User } from "./types";
|
||||
|
||||
export const useUsers = () => {
|
||||
const client = useDataSource();
|
||||
|
||||
@ -54,4 +54,4 @@
|
||||
"use-debounce": "^10.1.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,9 @@ export class PaymentMethodCreator implements IPaymentMethodCreator {
|
||||
before: {},
|
||||
after: toPaymentMethodAuditState(paymentMethod),
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(paymentMethod);
|
||||
}
|
||||
|
||||
@ -76,7 +76,9 @@ export class PaymentMethodDeleter implements IPaymentMethodDeleter {
|
||||
before,
|
||||
after: {},
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(paymentMethod);
|
||||
}
|
||||
|
||||
@ -58,7 +58,9 @@ export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
||||
before,
|
||||
after: toPaymentMethodAuditState(paymentMethod),
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(paymentMethod);
|
||||
}
|
||||
|
||||
@ -70,7 +70,9 @@ export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
||||
before,
|
||||
after: toPaymentMethodAuditState(paymentMethod),
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(paymentMethod);
|
||||
}
|
||||
|
||||
@ -82,7 +82,9 @@ export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodS
|
||||
before: previousDefaultState,
|
||||
after: toPaymentMethodAuditState(currentDefault),
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +105,9 @@ export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodS
|
||||
before: previousPaymentMethodState,
|
||||
after: toPaymentMethodAuditState(paymentMethod),
|
||||
});
|
||||
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||
if (auditResult.isFailure) {
|
||||
return Result.fail(auditResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(paymentMethod);
|
||||
|
||||
@ -24,7 +24,9 @@ export class DeletePaymentMethodByIdUseCase {
|
||||
const { payment_method_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_method_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentMethodId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class DisablePaymentMethodByIdUseCase {
|
||||
const { payment_method_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_method_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentMethodId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class EnablePaymentMethodByIdUseCase {
|
||||
const { payment_method_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_method_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentMethodId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class DeletePaymentTermByIdUseCase {
|
||||
const { payment_term_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_term_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentTermId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class DisablePaymentTermByIdUseCase {
|
||||
const { payment_term_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_term_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentTermId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class EnablePaymentTermByIdUseCase {
|
||||
const { payment_term_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(payment_term_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const paymentTermId = idOrError.data;
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionCreateProps, TaxDefinitionCode } from "../../../domain";
|
||||
import type { ITaxDefinitionCreateProps } from "../../../domain";
|
||||
|
||||
export interface ICreateTaxDefinitionInputMapper {
|
||||
map(
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from './tax-definition-repository.interface';
|
||||
export * from "./tax-definition-repository.interface";
|
||||
|
||||
@ -17,15 +17,21 @@ export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||
|
||||
public async enable(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||
const existing = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||
if (existing.isFailure) return Result.fail(existing.error);
|
||||
if (existing.isFailure) {
|
||||
return Result.fail(existing.error);
|
||||
}
|
||||
|
||||
const taxDefinition = existing.data;
|
||||
const changeResult = taxDefinition.enable();
|
||||
if (changeResult.isFailure) return Result.fail(changeResult.error);
|
||||
if (changeResult.isFailure) {
|
||||
return Result.fail(changeResult.error);
|
||||
}
|
||||
|
||||
if (changeResult.data) {
|
||||
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||
if (saveResult.isFailure) {
|
||||
return Result.fail(saveResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(changeResult.data);
|
||||
@ -33,15 +39,21 @@ export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||
|
||||
public async disable(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||
const existing = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||
if (existing.isFailure) return Result.fail(existing.error);
|
||||
if (existing.isFailure) {
|
||||
return Result.fail(existing.error);
|
||||
}
|
||||
|
||||
const taxDefinition = existing.data;
|
||||
const changeResult = taxDefinition.disable();
|
||||
if (changeResult.isFailure) return Result.fail(changeResult.error);
|
||||
if (changeResult.isFailure) {
|
||||
return Result.fail(changeResult.error);
|
||||
}
|
||||
|
||||
if (changeResult.data) {
|
||||
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||
if (saveResult.isFailure) {
|
||||
return Result.fail(saveResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(changeResult.data);
|
||||
|
||||
@ -22,16 +22,22 @@ export class TaxDefinitionUpdater implements ITaxDefinitionUpdater {
|
||||
const { companyId, id, patch, transaction } = params;
|
||||
|
||||
const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||
if (existingResult.isFailure) return Result.fail(existingResult.error);
|
||||
if (existingResult.isFailure) {
|
||||
return Result.fail(existingResult.error);
|
||||
}
|
||||
|
||||
const taxDefinition = existingResult.data;
|
||||
|
||||
const updateResult = taxDefinition.update(patch);
|
||||
if (updateResult.isFailure) return Result.fail(updateResult.error);
|
||||
if (updateResult.isFailure) {
|
||||
return Result.fail(updateResult.error);
|
||||
}
|
||||
|
||||
if (updateResult.data) {
|
||||
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||
if (saveResult.isFailure) {
|
||||
return Result.fail(saveResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(taxDefinition);
|
||||
|
||||
@ -12,7 +12,9 @@ export class DeleteTaxDefinitionByIdUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||
const result = await this.deps.deleter.deleteByIdInCompany(companyId, id);
|
||||
if (result.isFailure) return result;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.ok(result.data);
|
||||
}
|
||||
|
||||
@ -12,7 +12,9 @@ export class DisableTaxDefinitionByIdUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||
const result = await this.deps.statusChanger.disable(companyId, id);
|
||||
if (result.isFailure) return result;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.ok(result.data);
|
||||
}
|
||||
|
||||
@ -12,7 +12,9 @@ export class EnableTaxDefinitionByIdUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||
const result = await this.deps.statusChanger.enable(companyId, id);
|
||||
if (result.isFailure) return result;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.ok(result.data);
|
||||
}
|
||||
|
||||
@ -14,7 +14,9 @@ export class GetTaxDefinitionByIdUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||
const found = await this.deps.finder.getByIdInCompany(companyId, id);
|
||||
if (found.isFailure) return found;
|
||||
if (found.isFailure) {
|
||||
return found;
|
||||
}
|
||||
|
||||
const snapshot = this.deps.fullSnapshotBuilder.toOutput(found.data as any);
|
||||
return Result.ok(snapshot);
|
||||
|
||||
@ -15,7 +15,9 @@ export class ListTaxDefinitionsUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, criteria: Criteria) {
|
||||
const found = await this.deps.repository.findByCriteriaInCompany(companyId, criteria);
|
||||
if (found.isFailure) return found;
|
||||
if (found.isFailure) {
|
||||
return found;
|
||||
}
|
||||
|
||||
const collection = found.data.map((item) => this.deps.summarySnapshotBuilder.toOutput(item));
|
||||
return Result.ok(collection);
|
||||
|
||||
@ -21,7 +21,9 @@ export class UpdateTaxDefinitionByIdUseCase {
|
||||
|
||||
public async execute(companyId: UniqueID, params: UpdateTaxDefinitionByIdUseCaseInput) {
|
||||
const mapped = this.deps.dtoMapper.map(params.dto as any);
|
||||
if (mapped.isFailure) return mapped;
|
||||
if (mapped.isFailure) {
|
||||
return mapped;
|
||||
}
|
||||
|
||||
const result = await this.deps.updater.update({
|
||||
companyId,
|
||||
@ -29,7 +31,9 @@ export class UpdateTaxDefinitionByIdUseCase {
|
||||
patch: mapped.data,
|
||||
transaction: undefined,
|
||||
});
|
||||
if (result.isFailure) return result;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const snapshot = this.deps.fullSnapshotBuilder.toOutput(result.data as any);
|
||||
return Result.ok(snapshot);
|
||||
|
||||
@ -24,7 +24,9 @@ export class DeleteTaxRegimeByIdUseCase {
|
||||
const { tax_regime_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(tax_regime_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const taxRegimeId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class DisableTaxRegimeByIdUseCase {
|
||||
const { tax_regime_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(tax_regime_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const taxRegimeId = idOrError.data;
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@ export class EnableTaxRegimeByIdUseCase {
|
||||
const { tax_regime_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(tax_regime_id);
|
||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const taxRegimeId = idOrError.data;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, test } from "node:test";
|
||||
|
||||
import { Name, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
@ -3,8 +3,8 @@ import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
PaymentMethodCannotBeDefaultError,
|
||||
PaymentMethodCannotBeDisabledError,
|
||||
PaymentMethodCannotBeDeletedError,
|
||||
PaymentMethodCannotBeDisabledError,
|
||||
PaymentMethodCannotBeUpdatedError,
|
||||
} from "./errors";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user