Compare commits

...

2 Commits

Author SHA1 Message Date
dd6d33366e feat(onboarding): implement initial onboarding process and validation
- Add tests for initial company input validation.
- Create hooks for managing initial onboarding status and provisioning.
- Implement orchestration for completing onboarding and restoring session.
- Develop UI for the initial onboarding page with form handling.
- Enhance routing to include onboarding navigation resolution.
- Introduce error handling for unprovisioned onboarding scenarios.
- Establish API endpoints for onboarding status and completion.
- Validate onboarding request payloads with appropriate schemas.
- Update onboarding module composition to include provisioning services.
2026-09-09 11:19:41 +02:00
b2a9836436 Refactor controllers to remove redundant guards and improve code clarity
- Removed authentication and company context guards from multiple controllers in the customer, proforma, document series, and supplier modules.
- Consolidated guard registration logic in routes to enforce company ID restrictions globally.
- Cleaned up imports and ensured consistent error handling across controllers.
2026-09-09 00:43:34 +02:00
119 changed files with 1096 additions and 889 deletions

View File

@ -1 +1,2 @@
VITE_API_SERVER_URL=http://172.31.142.243:3002/api/v1
VITE_FORCE_INITIAL_ONBOARDING=true

View File

@ -0,0 +1,51 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
completeInitialOnboarding,
getInitialOnboardingStatus,
provisionInitialOnboarding,
toCompleteInitialOnboardingDTO,
} from "./initial-onboarding.api";
const ds = {
custom: async (p: any) => {
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" };
}
return { status: "pending", company_id: null };
},
} as any;
test("maps onboarding status and provision responses", async () => {
assert.deepEqual(await getInitialOnboardingStatus(ds), { status: "pending", companyId: null });
assert.deepEqual(await provisionInitialOnboarding(ds), { status: "pending", companyId: null });
});
test("maps complete payload and response", async () => {
const input: any = {
legalName: "Acme",
tradeName: "",
tin: "B1",
email: "",
phone: "",
website: "",
address: {
street: "Main",
street2: "2B",
city: "Madrid",
postalCode: "28001",
province: "Madrid",
countryCode: "ES",
},
};
assert.equal(await completeInitialOnboarding(ds, input), "c1");
assert.deepEqual(toCompleteInitialOnboardingDTO(input).address, {
street: "Main",
street_2: "2B",
city: "Madrid",
postal_code: "28001",
province: "Madrid",
country_code: "ES",
});
});

View File

@ -0,0 +1,110 @@
import type { IDataSource } from "@erp/core/client";
export type InitialOnboardingStatus = "not_provisioned" | "pending" | "completed";
export interface InitialOnboardingStatusModel {
status: InitialOnboardingStatus;
companyId: string | null;
}
export interface InitialCompanyInput {
legalName: string;
tradeName?: string;
tin: string;
email?: string;
phone?: string;
website?: string;
address: {
street: string;
street2?: string;
city: string;
postalCode: string;
province: string;
countryCode: string;
};
}
interface StatusDTO {
status: InitialOnboardingStatus;
company_id: string | null;
}
interface CompleteResponseDTO {
company_id: string;
}
interface CompanyDTO {
legal_name: string;
trade_name?: string | null;
tin: string;
email?: string | null;
phone?: string | null;
website?: string | null;
address: {
street: string;
street_2?: string | null;
city: string;
postal_code: string;
province: string;
country_code: string;
};
}
const mapStatus = (v: StatusDTO): InitialOnboardingStatusModel => ({
status: v.status,
companyId: v.company_id,
});
export const toCompleteInitialOnboardingDTO = (v: InitialCompanyInput): CompanyDTO => ({
legal_name: v.legalName,
trade_name: v.tradeName || null,
tin: v.tin,
email: v.email || null,
phone: v.phone || null,
website: v.website || null,
address: {
street: v.address.street,
street_2: v.address.street2 || null,
city: v.address.city,
postal_code: v.address.postalCode,
province: v.address.province,
country_code: v.address.countryCode,
},
});
export const getInitialOnboardingStatus = (ds: IDataSource, signal?: AbortSignal) =>
ds
.custom<Record<string, never>, StatusDTO>({
path: "onboarding/initial",
method: "get",
signal,
data: {},
})
.then(mapStatus);
export const provisionInitialOnboarding = (ds: IDataSource, signal?: AbortSignal) =>
ds
.custom<Record<string, never>, StatusDTO>({
path: "onboarding/initial",
method: "post",
signal,
data: {},
})
.then(mapStatus);
export const completeInitialOnboarding = (
ds: IDataSource,
input: InitialCompanyInput,
signal?: AbortSignal
) =>
ds
.custom<CompanyDTO, CompleteResponseDTO>({
path: "onboarding/initial/complete",
method: "post",
signal,
data: toCompleteInitialOnboardingDTO(input),
})
.then((v) => v.company_id);
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");
return errors;
};

View File

@ -0,0 +1,38 @@
import assert from "node:assert/strict";
import test from "node:test";
import { type InitialCompanyInput, validateInitialCompanyInput } from "./initial-onboarding.api";
const valid: InitialCompanyInput = {
legalName: "Acme",
tradeName: "",
tin: "B1",
email: "user@example.com",
phone: "",
website: "",
address: {
street: "Main",
street2: "",
city: "Madrid",
postalCode: "28001",
province: "Madrid",
countryCode: "ES",
},
};
for (const [name, mutate] of Object.entries({
valid: () => valid,
legalName: () => ({ ...valid, legalName: "" }),
tin: () => ({ ...valid, tin: "" }),
address: () => ({ ...valid, address: undefined as never }),
street: () => ({ ...valid, address: { ...valid.address, street: "" } }),
city: () => ({ ...valid, address: { ...valid.address, city: "" } }),
province: () => ({ ...valid, address: { ...valid.address, province: "" } }),
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);
});

View File

@ -0,0 +1,33 @@
import { useDataSource } from "@erp/core/hooks";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
type InitialCompanyInput,
completeInitialOnboarding,
getInitialOnboardingStatus,
provisionInitialOnboarding,
} from "../api/initial-onboarding.api";
export const initialOnboardingStatusKey = ["onboarding", "initial"] as const;
export const useInitialOnboardingStatus = () => {
const ds = useDataSource();
return useQuery({
queryKey: initialOnboardingStatusKey,
queryFn: ({ signal }) => getInitialOnboardingStatus(ds, signal),
});
};
export const useProvisionInitialOnboarding = () => {
const ds = useDataSource();
const qc = useQueryClient();
return useMutation({
mutationFn: () => provisionInitialOnboarding(ds),
onSuccess: (v) => qc.setQueryData(initialOnboardingStatusKey, v),
});
};
export const useCompleteInitialOnboarding = () => {
const ds = useDataSource();
const qc = useQueryClient();
return useMutation({
mutationFn: (v: InitialCompanyInput) => completeInitialOnboarding(ds, v),
onSuccess: (v) => qc.setQueryData(initialOnboardingStatusKey, v),
});
};

View File

@ -0,0 +1,39 @@
import assert from "node:assert/strict";
import test from "node:test";
import { completeInitialOnboardingAndRestoreSession } from "./complete-initial-onboarding-and-restore-session";
test("selects created company among multiple companies", async () => {
let selected = "";
const r = await completeInitialOnboardingAndRestoreSession("x", {
restoreSession: async () => ({}),
getAvailableCompanies: () => [{ id: "a" }, { id: "x" }],
changeActiveCompany: async (id) => {
selected = id;
return { id };
},
});
assert.deepEqual(r, { kind: "application", companyId: "x" });
assert.equal(selected, "x");
});
test("reports missing company without selecting another", async () => {
let selected = false;
const r = await completeInitialOnboardingAndRestoreSession("x", {
restoreSession: async () => ({}),
getAvailableCompanies: () => [{ id: "a" }, { id: "b" }],
changeActiveCompany: async () => {
selected = true;
return null;
},
});
assert.deepEqual(r, { kind: "recoverable", companyId: "x", reason: "company_missing" });
assert.equal(selected, false);
});
test("reports restore failure without repeating completion", async () => {
const r = await completeInitialOnboardingAndRestoreSession("x", {
restoreSession: async () => null,
getAvailableCompanies: () => [],
changeActiveCompany: async () => null,
});
assert.deepEqual(r, { kind: "recoverable", companyId: "x", reason: "restore_failed" });
});

View File

@ -0,0 +1,27 @@
export interface RestoredCompany {
id: string;
}
export interface OnboardingSessionActions {
restoreSession: () => Promise<unknown>;
getAvailableCompanies: () => RestoredCompany[];
changeActiveCompany: (companyId: string) => Promise<RestoredCompany | null>;
}
export type CompletionTransition =
| { kind: "application"; companyId: string }
| { kind: "recoverable"; companyId: string; reason: "company_missing" | "restore_failed" };
export async function completeInitialOnboardingAndRestoreSession(
companyId: string,
actions: OnboardingSessionActions
): Promise<CompletionTransition> {
try {
const restored = await actions.restoreSession();
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);
return { kind: "application", companyId };
}

View File

@ -0,0 +1,115 @@
import { useIdentityAuthSession } from "@erp/identity/client";
import { LoadingOverlay } from "@repo/rdx-ui/components";
import { useEffect, useState } from "react";
import {
type InitialCompanyInput,
useCompleteInitialOnboarding,
} from "../hooks/use-initial-onboarding";
const empty: InitialCompanyInput = {
legalName: "",
tradeName: "",
tin: "",
email: "",
phone: "",
website: "",
address: { street: "", street2: "", city: "", postalCode: "", province: "", countryCode: "ES" },
};
export const InitialOnboardingPage = () => {
const [v, setV] = useState(empty);
const [recoverableError, setRecoverableError] = useState<string | null>(null);
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");
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 />;
return (
<main className="mx-auto flex min-h-screen max-w-3xl flex-col justify-center gap-6 p-6">
<div>
<h1 className="text-3xl font-semibold">Configura tu empresa</h1>
<p className="text-muted-foreground">Completa los datos fiscales para comenzar.</p>
</div>
<form
className="grid gap-4"
onSubmit={async (e) => {
e.preventDefault();
await m.mutateAsync(v);
await restoreSession();
}}
>
{[
["legalName", "Razón social", true],
["tradeName", "Nombre comercial", false],
["tin", "NIF/CIF", true],
["email", "Email", false],
["phone", "Teléfono", false],
["website", "Web", false],
].map(([k, l, r]) => (
<label className="grid gap-1" key={k as string}>
<span>
{l as string}
{r ? " *" : ""}
</span>
<input
className="rounded border p-2"
onChange={(e) => set(k as string, e.target.value)}
required={Boolean(r)}
value={String(v[k as keyof InitialCompanyInput] ?? "")}
/>
</label>
))}
<h2 className="mt-4 text-xl font-medium">Dirección fiscal</h2>
{[
["street", "Calle", true],
["street2", "Complemento", false],
["city", "Ciudad", true],
["postalCode", "Código postal", true],
["province", "Provincia", true],
["countryCode", "País", true],
].map(([k, l, r]) => (
<label className="grid gap-1" key={k as string}>
<span>
{l as string}
{r ? " *" : ""}
</span>
<input
className="rounded border p-2"
onChange={(e) => setA(k as string, e.target.value)}
required={Boolean(r)}
value={String(v.address[k as keyof typeof v.address] ?? "")}
/>
</label>
))}
{recoverableError && (
<p className="text-red-600">
No se pudo restaurar la empresa ({recoverableError}). Puedes reintentar la restauración
o cerrar sesión.
</p>
)}
{m.isError && (
<p className="text-red-600">
No se pudo completar el alta. Revisa los datos e inténtalo de nuevo.
</p>
)}
<button
className="rounded bg-primary px-4 py-2 text-primary-foreground"
disabled={m.isPending}
type="submit"
>
{m.isPending ? "Guardando…" : "Crear empresa"}
</button>
</form>
</main>
);
};

View File

@ -1 +1,2 @@
export * from "./app-routes";
export * from "./resolve-initial-onboarding-navigation";

View File

@ -1,29 +1,64 @@
import { useIdentityAuthSession } from "@erp/identity/client";
import { LoadingOverlay } from "@repo/rdx-ui/components";
import type { ReactNode } from "react";
import { type ReactNode, useEffect } from "react";
import { Navigate, useLocation } from "react-router-dom";
import {
useInitialOnboardingStatus,
useProvisionInitialOnboarding,
} from "@/onboarding/hooks/use-initial-onboarding";
import { InitialOnboardingPage } from "@/onboarding/ui/initial-onboarding-page";
import { resolveCompanyGuardDecision } from "./resolve-company-guard-decision";
interface RequireCompanyRouteGuardProps {
children: ReactNode;
}
export const RequireCompanyRouteGuard = ({ children }: RequireCompanyRouteGuardProps) => {
const location = useLocation();
const { activeCompany, availableCompanies, isLoading } = useIdentityAuthSession();
if (isLoading) {
const statusQuery = useInitialOnboardingStatus();
const provision = useProvisionInitialOnboarding();
useEffect(() => {
if (
availableCompanies.length === 0 &&
statusQuery.data?.status === "not_provisioned" &&
!provision.isPending &&
!provision.isSuccess &&
!provision.isError
)
provision.mutate();
}, [
availableCompanies.length,
statusQuery.data?.status,
provision.isPending,
provision.isSuccess,
provision.mutate,
provision.isError,
]);
if (
isLoading ||
(availableCompanies.length === 0 && (statusQuery.isLoading || provision.isPending))
)
return <LoadingOverlay />;
}
if (activeCompany) {
return <>{children}</>;
}
const navigation = statusQuery.data
? resolveCompanyGuardDecision({
isAuthenticated: true,
availableCompaniesCount: availableCompanies.length,
onboardingStatus: statusQuery.data.status,
})
: null;
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)
return <InitialOnboardingPage />;
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}`;
if (availableCompanies.length === 0) {
return <Navigate replace to="/no-companies" />;
}
return <Navigate replace to={`/company-selection?returnTo=${encodeURIComponent(returnTo)}`} />;
};

View File

@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import test from "node:test";
import { resolveCompanyGuardDecision } from "./resolve-company-guard-decision";
const d = (n: number, s: any, a = false) =>
resolveCompanyGuardDecision({
isAuthenticated: true,
availableCompaniesCount: n,
hasActiveCompany: a,
onboardingStatus: s,
});
test("companies have priority", () => {
assert.equal(d(2, "pending"), "application");
assert.equal(d(2, "pending"), "application");
});
test("zero companies route by onboarding state", () => {
assert.equal(d(0, "pending"), "onboarding");
assert.equal(d(0, "not_provisioned"), "provision_onboarding");
assert.equal(d(0, "completed"), "onboarding_inconsistent");
});
test("unauthenticated goes to login", () =>
assert.equal(
resolveCompanyGuardDecision({
isAuthenticated: false,
availableCompaniesCount: 0,
hasActiveCompany: false,
onboardingStatus: "pending",
}),
"login"
));

View File

@ -0,0 +1,15 @@
import {
type InitialOnboardingNavigation,
resolveInitialOnboardingNavigation,
} from "./resolve-initial-onboarding-navigation";
export const resolveCompanyGuardDecision = (input: {
isAuthenticated: boolean;
availableCompaniesCount: number;
hasActiveCompany: boolean;
onboardingStatus: "not_provisioned" | "pending" | "completed";
}): InitialOnboardingNavigation =>
resolveInitialOnboardingNavigation({
isAuthenticated: input.isAuthenticated,
availableCompaniesCount: input.availableCompaniesCount,
onboardingStatus: input.onboardingStatus,
});

View File

@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import test from "node:test";
import { resolveInitialOnboardingNavigation } from "./resolve-initial-onboarding-navigation";
const cases = [
[false, 0, "pending", "login"],
[true, 1, "pending", "application"],
[true, 1, "completed", "application"],
[true, 1, "not_provisioned", "application"],
[true, 0, "pending", "onboarding"],
[true, 0, "not_provisioned", "provision_onboarding"],
[true, 0, "completed", "onboarding_inconsistent"],
[true, 2, "pending", "application"],
] as const;
for (const [isAuthenticated, companies, status, expected] of cases) {
test(`${String(isAuthenticated)} + ${companies} companies + ${status}`, () => {
assert.equal(
resolveInitialOnboardingNavigation({
isAuthenticated,
availableCompaniesCount: companies,
onboardingStatus: status,
}),
expected
);
});
}
test("pending with available companies has no side effects", () => {
const input = {
isAuthenticated: true,
availableCompaniesCount: 1,
onboardingStatus: "pending" as const,
};
assert.equal(resolveInitialOnboardingNavigation(input), "application");
assert.deepEqual(input, {
isAuthenticated: true,
availableCompaniesCount: 1,
onboardingStatus: "pending",
});
});

View File

@ -0,0 +1,23 @@
export type InitialOnboardingNavigation =
| "login"
| "application"
| "onboarding"
| "provision_onboarding"
| "onboarding_inconsistent";
export type InitialOnboardingNavigationInput = {
isAuthenticated: boolean;
availableCompaniesCount: number;
onboardingStatus: "not_provisioned" | "pending" | "completed";
};
/** Decide la navegación sin leer ni modificar persistencia. */
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";
return "provision_onboarding";
}

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreatePaymentMethodRequestDTO } from "../../../../../common";
import type { CreatePaymentMethodUseCase } from "../../../../application";
@ -16,11 +11,6 @@ export class CreatePaymentMethodController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeletePaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -15,11 +10,6 @@ export class DeletePaymentMethodByIdController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisablePaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -15,11 +10,6 @@ export class DisablePaymentMethodByIdController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { EnablePaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -15,11 +10,6 @@ export class EnablePaymentMethodByIdController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetPaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -15,11 +10,6 @@ export class GetPaymentMethodByIdController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListPaymentMethodsUseCase } from "../../../../application";
@ -15,11 +10,6 @@ export class ListPaymentMethodsController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { SetDefaultPaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -13,12 +8,6 @@ export class SetDefaultPaymentMethodByIdController extends ExpressController {
super();
this.errorMapper = paymentMethodsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,10 +1,5 @@
import type { UpdatePaymentMethodByIdRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdatePaymentMethodByIdUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
@ -16,11 +11,6 @@ export class UpdatePaymentMethodByIdController extends ExpressController {
this.errorMapper = paymentMethodsApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenantMembership } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
@ -33,6 +33,7 @@ export const paymentMethodsRouter = (params: StartParams) => {
// ----------------------------------------------
router.use(...requireIdentityTenantMembership(params));
router.use(forbidQueryFieldGuard("companyId"));
// ----------------------------------------------

View File

@ -1,10 +1,5 @@
import type { CreatePaymentTermRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreatePaymentTermUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -14,12 +9,6 @@ export class CreatePaymentTermController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeletePaymentTermByIdUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -13,12 +8,6 @@ export class DeletePaymentTermByIdController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisablePaymentTermByIdUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -13,12 +8,6 @@ export class DisablePaymentTermByIdController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { EnablePaymentTermByIdUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -13,12 +8,6 @@ export class EnablePaymentTermByIdController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetPaymentTermByIdUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -13,12 +8,6 @@ export class GetPaymentTermByIdController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListPaymentTermsUseCase } from "../../../../application/payment-terms";
@ -14,12 +9,6 @@ export class ListPaymentTermsController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,10 +1,5 @@
import type { UpdatePaymentTermByIdRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdatePaymentTermByIdUseCase } from "../../../../application/payment-terms";
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
@ -14,12 +9,6 @@ export class UpdatePaymentTermByIdController extends ExpressController {
super();
this.errorMapper = paymentTermsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenantMembership } from "@erp/identity/api";
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
@ -30,6 +30,7 @@ export const paymentTermsRouter = (params: StartParams) => {
const router = Router({ mergeParams: true });
router.use(...requireIdentityTenantMembership(params));
router.use(forbidQueryFieldGuard("companyId"));
router.get(
"/",

View File

@ -1,10 +1,5 @@
import type { CreateTaxDefinitionRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateTaxDefinitionUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -14,12 +9,6 @@ export class CreateTaxDefinitionController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeleteTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -13,12 +8,6 @@ export class DeleteTaxDefinitionByIdController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisableTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -13,12 +8,6 @@ export class DisableTaxDefinitionByIdController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { EnableTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -13,12 +8,6 @@ export class EnableTaxDefinitionByIdController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -13,12 +8,6 @@ export class GetTaxDefinitionByIdController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListTaxDefinitionsUseCase } from "../../../../application/tax-definitions";
@ -14,12 +9,6 @@ export class ListTaxDefinitionsController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,10 +1,5 @@
import type { UpdateTaxDefinitionByIdRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
@ -14,12 +9,6 @@ export class UpdateTaxDefinitionByIdController extends ExpressController {
super();
this.errorMapper = taxDefinitionsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenantMembership } from "@erp/identity/api";
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
@ -30,6 +30,7 @@ export const taxDefinitionsRouter = (params: StartParams) => {
const router = Router({ mergeParams: true });
router.use(...requireIdentityTenantMembership(params));
router.use(forbidQueryFieldGuard("companyId"));
router.get(
"/",

View File

@ -1,10 +1,5 @@
import type { CreateTaxRegimeRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateTaxRegimeUseCase } from "../../../../application/tax-regimes";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -14,12 +9,6 @@ export class CreateTaxRegimeController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeleteTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -13,12 +8,6 @@ export class DeleteTaxRegimeByIdController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisableTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -13,12 +8,6 @@ export class DisableTaxRegimeByIdController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { EnableTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -13,12 +8,6 @@ export class EnableTaxRegimeByIdController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -13,12 +8,6 @@ export class GetTaxRegimeByIdController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListTaxRegimesUseCase } from "../../../../application";
@ -14,12 +9,6 @@ export class ListTaxRegimesController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,10 +1,5 @@
import type { UpdateTaxRegimeByIdRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
@ -14,12 +9,6 @@ export class UpdateTaxRegimeByIdController extends ExpressController {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenantMembership } from "@erp/identity/api";
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
@ -30,6 +30,7 @@ export const taxRegimesRouter = (params: StartParams) => {
const router = Router({ mergeParams: true });
router.use(...requireIdentityTenantMembership(params));
router.use(forbidQueryFieldGuard("companyId"));
router.get(
"/",

View File

@ -1,4 +1,4 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateCompanyRequestDTO } from "../../../../common";
import type { CreateCompanyUseCase } from "../../../application";
@ -8,7 +8,6 @@ export class CreateCompanyController extends ExpressController {
public constructor(private readonly useCase: CreateCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisableCompanyUseCase } from "../../../application";
import { companiesApiErrorMapper } from "../companies-api-error-mapper";
@ -11,13 +7,12 @@ export class DisableCompanyController extends ExpressController {
public constructor(private readonly useCase: DisableCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {
const tenantId = this.getTenantId();
const { company_id } = this.req.params;
if (!company_id || !tenantId) {
if (!(company_id && tenantId)) {
return this.invalidInputError("Company ID missing");
}

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { EnableCompanyUseCase } from "../../../application";
import { companiesApiErrorMapper } from "../companies-api-error-mapper";
@ -11,13 +7,12 @@ export class EnableCompanyController extends ExpressController {
public constructor(private readonly useCase: EnableCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {
const tenantId = this.getTenantId();
const { company_id } = this.req.params;
if (!company_id || !tenantId) {
if (!(company_id && tenantId)) {
return this.invalidInputError("Company ID missing");
}

View File

@ -1,4 +1,4 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { FindAvailableCompaniesForAccountUseCase } from "../../../application";
import { companiesApiErrorMapper } from "../companies-api-error-mapper";
@ -7,7 +7,6 @@ export class FindAvailableCompaniesController extends ExpressController {
public constructor(private readonly useCase: FindAvailableCompaniesForAccountUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetCompanyByIdUseCase } from "../../../application";
import { companiesApiErrorMapper } from "../companies-api-error-mapper";
@ -11,13 +7,12 @@ export class GetCompanyByIdController extends ExpressController {
public constructor(private readonly useCase: GetCompanyByIdUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {
const tenantId = this.getTenantId();
const { company_id } = this.req.params;
if (!company_id || !tenantId) {
if (!(company_id && tenantId)) {
return this.invalidInputError("Company ID missing");
}

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetCurrentCompanyUseCase } from "../../../application";
import { companiesApiErrorMapper } from "../companies-api-error-mapper";
@ -11,7 +7,6 @@ export class GetCurrentCompanyController extends ExpressController {
public constructor(private readonly useCase: GetCurrentCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateCompanyRequestDTO } from "../../../../common";
import type { UpdateCompanyUseCase } from "../../../application";
@ -12,13 +8,12 @@ export class UpdateCompanyController extends ExpressController {
public constructor(private readonly useCase: UpdateCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {
const tenantId = this.getTenantId();
const { company_id } = this.req.params;
if (!company_id || !tenantId) {
if (!(company_id && tenantId)) {
return this.invalidInputError("Company ID missing");
}

View File

@ -1,8 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateCurrentCompanyRequestDTO } from "../../../../common";
import type { UpdateCurrentCompanyUseCase } from "../../../application";
@ -12,7 +8,6 @@ export class UpdateCurrentCompanyController extends ExpressController {
public constructor(private readonly useCase: UpdateCurrentCompanyUseCase) {
super();
this.errorMapper = companiesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {

View File

@ -1,5 +1,5 @@
import type { Criteria } from "@repo/rdx-criteria/server";
import type { NextFunction, Request, Response } from "express";
import type { NextFunction, Request, RequestHandler, Response } from "express";
import {
type ApiError,
@ -7,7 +7,7 @@ import {
UnauthorizedApiError,
ValidationApiError,
} from "./errors";
import type { ExpressController } from "./express-controller";
import { ExpressController } from "./express-controller";
export type GuardResultLike = { isFailure: boolean; error?: ApiError };
@ -53,12 +53,16 @@ export function requireCompanyContextGuard(): GuardFn {
Impide que el cliente intente fijar el 'companyId' vía query (?companyId=...).
La política de scoping real debe hacerse en repos/servicios; esto es defensa adicional.
*/
export function forbidQueryFieldGuard(field = "companyId"): GuardFn {
return ({ req }: GuardContext) => {
if (Object.hasOwn(req.query, field)) {
return guardFail(new ValidationApiError(`Query param "${field}" is not allowed`));
export function forbidQueryFieldGuard(field = "companyId"): RequestHandler {
return (req, res, next) => {
if (Object.hasOwn(req.query ?? {}, field)) {
return ExpressController.errorResponse(
new ValidationApiError(`Query param "${field}" is not allowed`),
req,
res
);
}
return guardOk();
return next();
};
}

View File

@ -1,9 +1,10 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenant } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
import { UpdateCompanyVerifactuProfileRequestSchema } from "../../../../common";
import type { CompanyVerifactuProfileInternalDeps } from "../di";
import {
GetCompanyVerifactuProfileController,
UpdateCompanyVerifactuProfileController,
@ -19,6 +20,8 @@ export const companyVerifactuProfileRouter = (params: StartParams) => {
const router = Router({ mergeParams: true });
router.use(...requireIdentityTenant(params));
router.use(forbidQueryFieldGuard("companyId"));
router.use(forbidQueryFieldGuard("company_id"));
router.get("/", (req: Request, res: Response, next: NextFunction) => {
const controller = new GetCompanyVerifactuProfileController(

View File

@ -1,21 +1,10 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetCompanyVerifactuProfileUseCase } from "../../../../application";
export class GetCompanyVerifactuProfileController extends ExpressController {
public constructor(private readonly useCase: GetCompanyVerifactuProfileUseCase) {
super();
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateCompanyVerifactuProfileRequestDTO } from "../../../../../common";
import type { UpdateCompanyVerifactuProfileUseCase } from "../../../../application";
@ -11,12 +6,6 @@ import type { UpdateCompanyVerifactuProfileUseCase } from "../../../../applicati
export class UpdateCompanyVerifactuProfileController extends ExpressController {
public constructor(private readonly useCase: UpdateCompanyVerifactuProfileUseCase) {
super();
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetIssuedInvoiceByIdUseCase } from "../../../../application/issued-invoices/index.ts";
import { issuedInvoicesApiErrorMapper } from "../issued-invoices-api-error-mapper.ts";
@ -14,11 +9,6 @@ export class GetIssuedInvoiceByIdController extends ExpressController {
this.errorMapper = issuedInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import { ListIssuedInvoicesResponseSchema } from "../../../../../common/index.ts";
@ -16,11 +11,6 @@ export class ListIssuedInvoicesController extends ExpressController {
this.errorMapper = issuedInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,10 +1,4 @@
import {
ExpressController,
type RendererFormat,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { ReportIssueInvoiceByIdQueryRequestDTO } from "../../../../../common";
import type { ReportIssuedInvoiceUseCase } from "../../../../application/index.ts";
@ -16,11 +10,6 @@ export class ReportIssuedInvoiceController extends ExpressController {
this.errorMapper = issuedInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenant } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
@ -17,7 +17,7 @@ import {
} from "./controllers";
export const issuedInvoicesRouter = (params: StartParams) => {
const { app, config, getService, getInternal } = params;
const { app, config, getInternal } = params;
const deps = getInternal<IssuedInvoicesInternalDeps>("customer-invoices", "issuedInvoices");
@ -26,6 +26,7 @@ export const issuedInvoicesRouter = (params: StartParams) => {
// ----------------------------------------------
router.use(...requireIdentityTenant(params));
router.use(forbidQueryFieldGuard("companyId"));
// ----------------------------------------------

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { ArchiveProformaByIdUseCase } from "../../../../application";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper";
@ -12,11 +7,6 @@ export class ArchiveProformaByIdController extends ExpressController {
public constructor(private readonly useCase: ArchiveProformaByIdUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { ChangeStatusProformaByIdRequestDTO } from "../../../../../common/dto";
import type { ChangeStatusProformaUseCase } from "../../../../application";
@ -15,11 +10,6 @@ export class ChangeStatusProformaController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateProformaRequestDTO } from "../../../../../common/dto/index.ts";
import type { CreateProformaUseCase } from "../../../../application/index.ts";
@ -15,11 +10,6 @@ export class CreateProformaController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeleteProformaByIdUseCase } from "../../../../application/index.ts";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
@ -14,11 +9,6 @@ export class DeleteProformaController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,14 +1,9 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import {
type DuplicateProformaByIdParamsRequestDTO,
DuplicateProformaByIdParamsRequestSchema,
DuplicateProformaByIdResponseSchema,
type DuplicateProformaByIdParamsRequestDTO,
} from "../../../../../common";
import type { DuplicateProformaByIdUseCase } from "../../../../application";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper";
@ -17,12 +12,6 @@ export class DuplicateProformaByIdController extends ExpressController {
public constructor(private readonly useCase: DuplicateProformaByIdUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetProformaByIdUseCase } from "../../../../application/index.ts";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
@ -14,11 +9,6 @@ export class GetProformaByIdController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { GetProformaIssuanceReadinessResponseSchema } from "../../../../../common";
import type { GetProformaIssuanceReadinessUseCase } from "../../../../application";
@ -13,12 +8,6 @@ export class GetProformaIssuanceReadinessController extends ExpressController {
public constructor(private readonly useCase: GetProformaIssuanceReadinessUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { IssueProformaByIdResponseSchema } from "../../../../../common";
import type { IssueProformaUseCase } from "../../../../application/proformas";
@ -19,11 +14,6 @@ export class IssueProformaController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListProformasUseCase, ProformaListScope } from "../../../../application/index.ts";
@ -31,11 +26,6 @@ export class ListProformasController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { PreviewProformaReportUseCase } from "../../../../application";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
@ -12,11 +7,6 @@ export class PreviewProformaReportController extends ExpressController {
public constructor(private readonly useCase: PreviewProformaReportUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { ReportProformaPdfUseCase } from "../../../../application";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
@ -12,11 +7,6 @@ export class ReportProformaPdfController extends ExpressController {
public constructor(private readonly useCase: ReportProformaPdfUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UnarchiveProformaByIdUseCase } from "../../../../application";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper";
@ -12,11 +7,6 @@ export class UnarchiveProformaByIdController extends ExpressController {
public constructor(private readonly useCase: UnarchiveProformaByIdUseCase) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
import type { UpdateProformaByIdUseCase } from "../../../../application";
@ -15,11 +10,6 @@ export class UpdateProformaController extends ExpressController {
this.errorMapper = proformasApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenant } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
@ -20,6 +20,7 @@ import {
ChangeStatusProformaByIdParamsRequestSchema,
ChangeStatusProformaByIdRequestSchema,
CreateProformaRequestSchema,
CreateRecurringProformaFromProformaRequestSchema,
DeleteProformaByIdParamsRequestSchema,
DuplicateProformaByIdParamsRequestSchema,
GetProformaByIdRequestSchema,
@ -32,7 +33,6 @@ import {
UnarchiveProformaByIdParamsRequestSchema,
UpdateProformaByIdParamsRequestSchema,
UpdateProformaByIdRequestSchema,
CreateRecurringProformaFromProformaRequestSchema,
} from "../../../../common";
import type { IIssuedInvoicePublicServices } from "../../../application";
import { CreateRecurringProformaFromProformaController } from "../../recurring-proformas/express/controllers";
@ -59,6 +59,7 @@ export const proformasRouter = (params: StartParams) => {
// ----------------------------------------------
router.use(...requireIdentityTenant(params));
router.use(forbidQueryFieldGuard("companyId"));
// ----------------------------------------------
@ -181,6 +182,7 @@ export const proformasRouter = (params: StartParams) => {
router.post(
"/:proforma_id/recurring-proforma",
validateRequest(GetProformaByIdRequestSchema, "params"),
forbidQueryFieldGuard("company_id"),
validateRequest(CreateRecurringProformaFromProformaRequestSchema, "body"),
(req: Request, res: Response, next: NextFunction) => {
const controller = new CreateRecurringProformaFromProformaController(

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import type { CreateRecurringProformaFromProformaRequestDTO } from "../../../../../common";
@ -12,17 +7,9 @@ import { proformasApiErrorMapper } from "../../../proformas/express/proformas-ap
import { parseDateOnly } from "../mappers/parse-date-only";
export class CreateRecurringProformaFromProformaController extends ExpressController {
public constructor(
private readonly fromProforma: RecurringProformaCommands["fromProforma"]
) {
public constructor(private readonly fromProforma: RecurringProformaCommands["fromProforma"]) {
super();
this.errorMapper = proformasApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {
@ -37,8 +24,7 @@ export class CreateRecurringProformaFromProformaController extends ExpressContro
if (startDate.isFailure) return this.invalidInputError(startDate.error.message);
const endDate = body.schedule.endDate;
const parsedEndDate =
typeof endDate === "string" ? parseDateOnly(endDate) : undefined;
const parsedEndDate = typeof endDate === "string" ? parseDateOnly(endDate) : undefined;
if (parsedEndDate?.isFailure) {
return this.invalidInputError(parsedEndDate.error.message);
}

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateCustomerRequestDTO } from "../../../../common/dto";
import type { CreateCustomerUseCase } from "../../../application";
@ -13,13 +8,6 @@ export class CreateCustomerController extends ExpressController {
public constructor(private readonly useCase: CreateCustomerUseCase) {
super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DeleteCustomerUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
@ -12,13 +7,6 @@ export class DeleteCustomerController extends ExpressController {
public constructor(private readonly useCase: DeleteCustomerUseCase) {
super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
async executeImpl(): Promise<any> {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetCustomerByIdUseCase } from "@erp/customers/api/application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
@ -12,13 +7,6 @@ export class GetCustomerController extends ExpressController {
public constructor(private readonly useCase: GetCustomerByIdUseCase) {
super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListCustomersUseCase } from "../../../application";
@ -13,13 +8,6 @@ export class ListCustomersController extends ExpressController {
public constructor(private readonly useCase: ListCustomersUseCase) {
super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
private getCriteriaWithDefaultOrder() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
import type { UpdateCustomerUseCase } from "../../../application";
@ -13,13 +8,6 @@ export class UpdateCustomerController extends ExpressController {
public constructor(private readonly useCase: UpdateCustomerUseCase) {
super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { type StartParams, validateRequest } from "@erp/core/api";
import { type StartParams, forbidQueryFieldGuard, validateRequest } from "@erp/core/api";
import { requireIdentityTenant } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
@ -30,6 +30,7 @@ export const customersRouter = (params: StartParams) => {
// 🔐 Autenticación + Tenancy para TODO el router
router.use(...requireIdentityTenant(params));
router.use(forbidQueryFieldGuard("companyId"));
// ----------------------------------------------

View File

@ -1,9 +1,4 @@
import {
ExpressController,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { buildTransactionManager } from "@erp/core/api";
import { ExpressController, type buildTransactionManager } from "@erp/core/api";
import type { AssignNextDocumentNumberRequestDTO } from "../../../../common";
import type { AssignNextDocumentNumberUseCase } from "../../../application";
@ -16,15 +11,11 @@ export class AssignNextDocumentNumberController extends ExpressController {
) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard(), requireCompanyContextGuard());
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
if (!companyId) return this.forbiddenError("Tenant ID not found");
const result = await this.transactionManager.complete((transaction: unknown) =>
this.useCase.execute({
companyId: companyId.toString(),
@ -32,7 +23,6 @@ export class AssignNextDocumentNumberController extends ExpressController {
transaction,
})
);
return result.match(
(data) => this.ok(data),
(error) => this.handleError(error)

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CreateDocumentSeriesRequestDTO } from "../../../../common";
import type { CreateDocumentSeriesUseCase } from "../../../application";
@ -13,12 +8,6 @@ export class CreateDocumentSeriesController extends ExpressController {
public constructor(private readonly useCase: CreateDocumentSeriesUseCase) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { DisableDocumentSeriesUseCase } from "../../../application";
import { documentSeriesApiErrorMapper } from "../document-series-api-error-mapper";
@ -12,12 +7,6 @@ export class DisableDocumentSeriesController extends ExpressController {
public constructor(private readonly useCase: DisableDocumentSeriesUseCase) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetDocumentSeriesByIdUseCase } from "../../../application";
import { documentSeriesApiErrorMapper } from "../document-series-api-error-mapper";
@ -12,12 +7,6 @@ export class GetDocumentSeriesByIdController extends ExpressController {
public constructor(private readonly useCase: GetDocumentSeriesByIdUseCase) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListDocumentSeriesUseCase } from "../../../application";
@ -13,12 +8,6 @@ export class ListDocumentSeriesController extends ExpressController {
public constructor(private readonly useCase: ListDocumentSeriesUseCase) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {
@ -28,7 +17,14 @@ export class ListDocumentSeriesController extends ExpressController {
}
const { filters, pageNumber, pageSize, quickSearch } = this.criteria;
const criteria = Criteria.fromPrimitives(filters.toPrimitives(), "code", "ASC", pageSize, pageNumber, quickSearch);
const criteria = Criteria.fromPrimitives(
filters.toPrimitives(),
"code",
"ASC",
pageSize,
pageNumber,
quickSearch
);
const result = await this.useCase.execute({
companyId: companyId.toString(),

View File

@ -1,9 +1,4 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UpdateDocumentSeriesRequestDTO } from "../../../../common";
import type { UpdateDocumentSeriesUseCase } from "../../../application";
@ -13,12 +8,6 @@ export class UpdateDocumentSeriesController extends ExpressController {
public constructor(private readonly useCase: UpdateDocumentSeriesUseCase) {
super();
this.errorMapper = documentSeriesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id")
);
}
protected async executeImpl() {

View File

@ -1,4 +1,9 @@
import { type StartParams, buildTransactionManager, validateRequest } from "@erp/core/api";
import {
type StartParams,
buildTransactionManager,
forbidQueryFieldGuard,
validateRequest,
} from "@erp/core/api";
import { requireIdentityTenant } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
@ -28,13 +33,20 @@ export const documentSeriesRouter = (params: StartParams) => {
router.use(...requireIdentityTenant(params));
router.get("/", (req: Request, res: Response, next: NextFunction) => {
const controller = new ListDocumentSeriesController(deps.useCases.listDocumentSeries());
return controller.execute(req, res, next);
});
router.get(
"/",
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id"),
(req: Request, res: Response, next: NextFunction) => {
const controller = new ListDocumentSeriesController(deps.useCases.listDocumentSeries());
return controller.execute(req, res, next);
}
);
router.get(
"/:id",
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id"),
validateRequest(GetDocumentSeriesByIdRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => {
const controller = new GetDocumentSeriesByIdController(deps.useCases.getDocumentSeriesById());
@ -44,6 +56,8 @@ export const documentSeriesRouter = (params: StartParams) => {
router.post(
"/",
forbidQueryFieldGuard("companyId"),
forbidQueryFieldGuard("company_id"),
validateRequest(CreateDocumentSeriesRequestSchema, "body"),
(req: Request, res: Response, next: NextFunction) => {
const controller = new CreateDocumentSeriesController(deps.useCases.createDocumentSeries());

View File

@ -9,13 +9,6 @@ export class CreateProformaFromFactugesController extends ExpressController {
public constructor(private readonly useCase: CreateProformaFromFactugesUseCase) {
super();
this.errorMapper = factugesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
/*this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);*/
}
protected async executeImpl() {

View File

@ -1,7 +1,10 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UtcDateTime } from "@repo/rdx-ddd";
import type { ChangeAccountPasswordRequestDTO, ChangeAccountPasswordResponseDTO } from "../../../../common";
import type {
ChangeAccountPasswordRequestDTO,
ChangeAccountPasswordResponseDTO,
} from "../../../../common";
import type { ChangeCurrentAccountPasswordUseCase } from "../../../application/accounts";
import { identityAccountApiErrorMapper } from "../identity-account-api-error-mapper";
@ -12,7 +15,6 @@ export class ChangeCurrentAccountPasswordController extends ExpressController {
) {
super();
this.errorMapper = identityAccountApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { GetAccountProfileResponseDTO } from "../../../../common";
import type { GetCurrentAccountUseCase } from "../../../application/accounts";
@ -8,7 +8,6 @@ export class GetCurrentAccountController extends ExpressController {
public constructor(private readonly useCase: GetCurrentAccountUseCase) {
super();
this.errorMapper = identityAccountApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { CurrentSessionResponseDTO } from "../../../../common";
import type { GetCurrentSessionUseCase } from "../../../application";
@ -8,7 +8,6 @@ export class GetCurrentSessionController extends ExpressController {
public constructor(private readonly useCase: GetCurrentSessionUseCase) {
super();
this.errorMapper = identityAuthApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,4 +1,4 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UtcDateTime } from "@repo/rdx-ddd";
import type { LogoutRequestDTO, LogoutResponseDTO } from "../../../../common";
@ -12,7 +12,6 @@ export class LogoutController extends ExpressController {
) {
super();
this.errorMapper = identityAuthApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -1,7 +1,10 @@
import { ExpressController, requireAuthenticatedGuard } from "@erp/core/api";
import { ExpressController } from "@erp/core/api";
import type { UtcDateTime } from "@repo/rdx-ddd";
import type { UpdateAccountProfileRequestDTO, UpdateAccountProfileResponseDTO } from "../../../../common";
import type {
UpdateAccountProfileRequestDTO,
UpdateAccountProfileResponseDTO,
} from "../../../../common";
import type { UpdateCurrentAccountUseCase } from "../../../application/accounts";
import { identityAccountApiErrorMapper } from "../identity-account-api-error-mapper";
@ -12,7 +15,6 @@ export class UpdateCurrentAccountController extends ExpressController {
) {
super();
this.errorMapper = identityAccountApiErrorMapper;
this.registerGuards(requireAuthenticatedGuard());
}
protected async executeImpl() {

View File

@ -0,0 +1,9 @@
import { ApplicationError } from "@repo/rdx-ddd";
/** La transición requiere un onboarding provisionado previamente para la cuenta. */
export class InitialOnboardingNotProvisionedError extends ApplicationError {
constructor() {
super("Initial onboarding has not been provisioned.", "initial_onboarding_not_provisioned");
this.name = "InitialOnboardingNotProvisionedError";
}
}

View File

@ -1 +1,5 @@
export type IOnboardingPublicServices = Record<never, never>;
import type { InitialOnboardingProvisioner } from "./initial-onboarding-provisioner";
export interface IOnboardingPublicServices {
provisioner: InitialOnboardingProvisioner;
}

View File

@ -6,6 +6,7 @@ import { UniqueID, UtcDateTime } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { InitialOnboarding } from "../../domain";
import { InitialOnboardingNotProvisionedError } from "../errors/initial-onboarding-not-provisioned-error";
import { CompleteInitialOnboardingInputMapper } from "../mappers/complete-initial-onboarding-input.mapper";
import { CompleteInitialOnboardingUseCase } from "./complete-initial-onboarding.use-case";
@ -137,7 +138,10 @@ test("completed existente ignora payload nuevo", async () => {
});
test("ausente falla sin provisionar", async () => {
const s = setup(undefined, true);
assert.equal((await s.useCase.execute(input)).isFailure, true);
const result = await s.useCase.execute(input);
assert.equal(result.isFailure, true);
assert.ok(result.error instanceof InitialOnboardingNotProvisionedError);
assert.equal(result.error.code, "initial_onboarding_not_provisioned");
assert.deepEqual(s.events, ["begin", "lock", "rollback"]);
});
for (const failure of ["read", "company", "admin", "save", "throw", "commit", "begin"]) {

Some files were not shown because too many files have changed in this diff Show More