This commit is contained in:
David Arranz 2026-05-04 20:33:24 +02:00
parent 4beb7aa207
commit 92faca9bfa
70 changed files with 394 additions and 157 deletions

View File

@ -5,6 +5,12 @@
// Enable Font Ligatures // Enable Font Ligatures
"editor.fontLigatures": true, "editor.fontLigatures": true,
// Lint
"eslint.workingDirectories": [{ "mode": "auto" }],
"eslint.run": "onType",
"eslint.validate": ["javascript", "typescript", "typescriptreact"],
"eslint.lintTask.enable": true,
// Javascript and TypeScript settings // Javascript and TypeScript settings
"js/ts.suggest.enabled": true, "js/ts.suggest.enabled": true,
"js/ts.suggest.autoImports": true, "js/ts.suggest.autoImports": true,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges-server", "name": "@erp/factuges-server",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsup src/index.ts --config tsup.config.ts", "build": "tsup src/index.ts --config tsup.config.ts",

View File

@ -125,14 +125,14 @@ async function setupModule(name: string, params: ModuleParams, stack: string[])
// 4) models // 4) models
if (pkgApi?.models) { if (pkgApi?.models) {
await withPhase(name, "registerModels", async () => { await withPhase(name, "registerModels", async () => {
await Promise.resolve(registerModels(pkgApi.models, params, { moduleName: name })); await Promise.resolve(registerModels(pkgApi.models!, params, { moduleName: name }));
}); });
} }
// 5) services (namespaced) // 5) services (namespaced)
if (pkgApi?.services) { if (pkgApi?.services) {
await withPhase(name, "registerServices", async () => { await withPhase(name, "registerServices", async () => {
validateModuleServices(name, pkgApi.services); validateModuleServices(name, pkgApi.services!);
for (const [serviceKey, serviceApi] of Object.entries(pkgApi.services!)) { for (const [serviceKey, serviceApi] of Object.entries(pkgApi.services!)) {
const fullName = buildServiceName(name, serviceKey); const fullName = buildServiceName(name, serviceKey);

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/factuges-web", "name": "@erp/factuges-web",
"private": true, "private": true,
"version": "0.6.3", "version": "0.6.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host --clearScreen false", "dev": "vite --host --clearScreen false",

View File

@ -1,18 +1,18 @@
import { Button } from "@repo/shadcn-ui/components"; import { Button } from "@repo/shadcn-ui/components";
import * as React from "react"; import * as React from "react";
import { FallbackProps } from "react-error-boundary"; import type { FallbackProps } from "react-error-boundary";
/** /**
* 1) Fallback simple * 1) Fallback simple
*/ */
export function SimpleFallback({ error, resetErrorBoundary }: FallbackProps) { export function SimpleFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div role='alert' className='p-4 rounded-md border bg-red-50 text-red-700'> <div className="p-4 rounded-md border bg-red-50 text-red-700" role="alert">
<p className='font-medium'> Algo salió mal</p> <p className="font-medium"> Algo salió mal</p>
<pre className='mt-2 text-sm whitespace-pre-wrap'>{error?.message}</pre> <pre className="mt-2 text-sm whitespace-pre-wrap">{error?.message}</pre>
<Button <Button
className="mt-3 px-3 py-1.5 rounded-md bg-red-600 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='mt-3 px-3 py-1.5 rounded-md bg-red-600 text-white'
> >
Reintentar Reintentar
</Button> </Button>
@ -25,22 +25,22 @@ export function SimpleFallback({ error, resetErrorBoundary }: FallbackProps) {
*/ */
export function SectionCardFallback({ error, resetErrorBoundary }: FallbackProps) { export function SectionCardFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div role='alert' className='rounded-2xl border shadow-sm p-5 bg-white'> <div className="rounded-2xl border shadow-sm p-5 bg-white" role="alert">
<div className='flex items-start gap-3'> <div className="flex items-start gap-3">
<div className='shrink-0 rounded-full p-2 bg-red-100'></div> <div className="shrink-0 rounded-full p-2 bg-red-100"></div>
<div className='grow'> <div className="grow">
<h3 className='font-semibold text-gray-900'>No se pudo cargar esta sección</h3> <h3 className="font-semibold text-gray-900">No se pudo cargar esta sección</h3>
<p className='mt-1 text-sm text-gray-600'>{error?.message}</p> <p className="mt-1 text-sm text-gray-600">{error?.message}</p>
<div className='mt-3 flex gap-2'> <div className="mt-3 flex gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-gray-900 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
> >
Reintentar Reintentar
</Button> </Button>
<Button <Button
className="px-3 py-1.5 rounded-md border"
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
className='px-3 py-1.5 rounded-md border'
> >
Refrescar página Refrescar página
</Button> </Button>
@ -56,15 +56,15 @@ export function SectionCardFallback({ error, resetErrorBoundary }: FallbackProps
*/ */
export function FullPageFallback({ error }: FallbackProps) { export function FullPageFallback({ error }: FallbackProps) {
return ( return (
<div className='min-h-screen flex flex-col items-center justify-center px-6 bg-gray-50 text-center'> <div className="min-h-screen flex flex-col items-center justify-center px-6 bg-gray-50 text-center">
<div className='text-5xl'>😵</div> <div className="text-5xl">😵</div>
<h1 className='mt-4 text-2xl font-bold text-gray-900'>Ocurrió un error inesperado</h1> <h1 className="mt-4 text-2xl font-bold text-gray-900">Ocurrió un error inesperado</h1>
<p className='mt-2 text-gray-600'>{error?.message}</p> <p className="mt-2 text-gray-600">{error?.message}</p>
<div className='mt-6 flex flex-wrap items-center justify-center gap-3'> <div className="mt-6 flex flex-wrap items-center justify-center gap-3">
<a href='/' className='px-4 py-2 rounded-md bg-blue-600 text-white'> <a className="px-4 py-2 rounded-md bg-blue-600 text-white" href="/">
Volver al inicio Volver al inicio
</a> </a>
<Button onClick={() => window.location.reload()} className='px-4 py-2 rounded-md border'> <Button className="px-4 py-2 rounded-md border" onClick={() => window.location.reload()}>
Recargar Recargar
</Button> </Button>
</div> </div>
@ -77,22 +77,22 @@ export function FullPageFallback({ error }: FallbackProps) {
*/ */
export function ListFallback({ error, resetErrorBoundary }: FallbackProps) { export function ListFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div role='alert' className='p-4 rounded-md border bg-amber-50'> <div className="p-4 rounded-md border bg-amber-50" role="alert">
<div className='flex items-start gap-3'> <div className="flex items-start gap-3">
<span className='text-xl'>🗂</span> <span className="text-xl">🗂</span>
<div> <div>
<p className='font-medium text-amber-900'>No pudimos cargar la lista.</p> <p className="font-medium text-amber-900">No pudimos cargar la lista.</p>
<p className='text-sm text-amber-800 mt-1'>{error?.message}</p> <p className="text-sm text-amber-800 mt-1">{error?.message}</p>
<div className='mt-3 flex gap-2'> <div className="mt-3 flex gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-amber-700 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-amber-700 text-white'
> >
Reintentar Reintentar
</Button> </Button>
<Button <Button
className="px-3 py-1.5 rounded-md border"
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
className='px-3 py-1.5 rounded-md border'
> >
Recargar Recargar
</Button> </Button>
@ -108,17 +108,17 @@ export function ListFallback({ error, resetErrorBoundary }: FallbackProps) {
*/ */
export function FormFallback({ error, resetErrorBoundary }: FallbackProps) { export function FormFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div role='alert' className='rounded-md border p-4 bg-red-50'> <div className="rounded-md border p-4 bg-red-50" role="alert">
<h4 className='font-semibold text-red-800'>No se pudo mostrar el formulario</h4> <h4 className="font-semibold text-red-800">No se pudo mostrar el formulario</h4>
<p className='mt-1 text-sm text-red-700'>{error?.message}</p> <p className="mt-1 text-sm text-red-700">{error?.message}</p>
<div className='mt-3 flex gap-2'> <div className="mt-3 flex gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-red-600 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-red-600 text-white'
> >
Reintentar Reintentar
</Button> </Button>
<Button onClick={() => history.back()} className='px-3 py-1.5 rounded-md border'> <Button className="px-3 py-1.5 rounded-md border" onClick={() => history.back()}>
Volver atrás Volver atrás
</Button> </Button>
</div> </div>
@ -135,18 +135,18 @@ export function NetworkFallback({ error, resetErrorBoundary }: FallbackProps) {
: "Estás sin conexión. Revisa tu red y reintenta."; : "Estás sin conexión. Revisa tu red y reintenta.";
return ( return (
<div role='alert' className='rounded-md border p-4 bg-blue-50 text-blue-900'> <div className="rounded-md border p-4 bg-blue-50 text-blue-900" role="alert">
<div className='font-medium'>No pudimos obtener los datos</div> <div className="font-medium">No pudimos obtener los datos</div>
<p className='mt-1 text-sm'>{error?.message}</p> <p className="mt-1 text-sm">{error?.message}</p>
<p className='mt-1 text-xs opacity-80'>{note}</p> <p className="mt-1 text-xs opacity-80">{note}</p>
<div className='mt-3 flex gap-2'> <div className="mt-3 flex gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-blue-700 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-blue-700 text-white'
> >
Reintentar Reintentar
</Button> </Button>
<Button onClick={() => window.location.reload()} className='px-3 py-1.5 rounded-md border'> <Button className="px-3 py-1.5 rounded-md border" onClick={() => window.location.reload()}>
Recargar Recargar
</Button> </Button>
</div> </div>
@ -160,23 +160,23 @@ export function NetworkFallback({ error, resetErrorBoundary }: FallbackProps) {
export function DevDetailsFallback({ error, resetErrorBoundary }: FallbackProps) { export function DevDetailsFallback({ error, resetErrorBoundary }: FallbackProps) {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
return ( return (
<div role='alert' className='rounded-md border p-4 bg-gray-50'> <div className="rounded-md border p-4 bg-gray-50" role="alert">
<div className='flex items-center justify-between'> <div className="flex items-center justify-between">
<p className='font-medium text-gray-900'>Algo falló</p> <p className="font-medium text-gray-900">Algo falló</p>
<div className='flex gap-2'> <div className="flex gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-gray-900 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
> >
Reintentar Reintentar
</Button> </Button>
<Button onClick={() => setOpen((v) => !v)} className='px-3 py-1.5 rounded-md border'> <Button className="px-3 py-1.5 rounded-md border" onClick={() => setOpen((v) => !v)}>
{open ? "Ocultar detalles" : "Ver detalles"} {open ? "Ocultar detalles" : "Ver detalles"}
</Button> </Button>
</div> </div>
</div> </div>
{open && ( {open && (
<pre className='mt-3 text-xs whitespace-pre-wrap bg-white p-3 rounded-md border overflow-auto'> <pre className="mt-3 text-xs whitespace-pre-wrap bg-white p-3 rounded-md border overflow-auto">
{error?.stack || error?.message} {error?.stack || error?.message}
</pre> </pre>
)} )}
@ -189,22 +189,22 @@ export function DevDetailsFallback({ error, resetErrorBoundary }: FallbackProps)
*/ */
export function SupportFallback({ error, resetErrorBoundary }: FallbackProps) { export function SupportFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<div role='alert' className='rounded-md border p-4 bg-white'> <div className="rounded-md border p-4 bg-white" role="alert">
<h4 className='font-semibold text-gray-900'>No pudimos completar la acción</h4> <h4 className="font-semibold text-gray-900">No pudimos completar la acción</h4>
<p className='mt-1 text-gray-700'>{error?.message}</p> <p className="mt-1 text-gray-700">{error?.message}</p>
<div className='mt-3 flex flex-wrap gap-2'> <div className="mt-3 flex flex-wrap gap-2">
<Button <Button
className="px-3 py-1.5 rounded-md bg-gray-900 text-white"
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className='px-3 py-1.5 rounded-md bg-gray-900 text-white'
> >
Intentar de nuevo Intentar de nuevo
</Button> </Button>
<a href='/ayuda' className='px-3 py-1.5 rounded-md border'> <a className="px-3 py-1.5 rounded-md border" href="/ayuda">
Ir a Ayuda Ir a Ayuda
</a> </a>
<a <a
href='mailto:soporte@tuapp.com?subject=Error%20en%20la%20aplicaci%C3%B3n' className="px-3 py-1.5 rounded-md border"
className='px-3 py-1.5 rounded-md border' href="mailto:soporte@tuapp.com?subject=Error%20en%20la%20aplicaci%C3%B3n"
> >
Contactar soporte Contactar soporte
</a> </a>

View File

@ -1,6 +1,6 @@
import { IModuleClient, ModuleClientParams } from "@erp/core/client"; import type { IModuleClient, ModuleClientParams } from "@erp/core/client";
import { JSX } from "react"; import type { JSX } from "react";
import { RouteObject, useRoutes } from "react-router-dom"; import { type RouteObject, useRoutes } from "react-router-dom";
interface ModuleRoutesProps { interface ModuleRoutesProps {
modules: IModuleClient[]; modules: IModuleClient[];

View File

@ -1,4 +1,4 @@
import { AxiosInstance } from "axios"; import type { AxiosInstance } from "axios";
/** /**
* Datos requeridos para iniciar sesión. * Datos requeridos para iniciar sesión.

View File

@ -5,9 +5,14 @@ export default [
{ {
files: ["**/*.ts", "**/*.tsx"], files: ["**/*.ts", "**/*.tsx"],
ignores: [ ignores: [
"**/docs/**",
"**/dist/**", "**/dist/**",
"**/out/**",
"**/.turbo/**", "**/.turbo/**",
"**/node_modules/**" "**/.vscode/**",
"**/node_modules/**",
"**/scripts/**",
"**/tools/**"
], ],
languageOptions: { languageOptions: {
parser, parser,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/auth", "name": "@erp/auth",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/core", "name": "@erp/core",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,13 +1,21 @@
import { type JsonTaxCatalogProvider, SpainTaxCatalogProvider } from "../../../common"; import {
FactuGESPaymentCatalogProvider,
type JsonPaymentCatalogProvider,
type JsonTaxCatalogProvider,
SpainTaxCatalogProvider,
} from "../../../common";
export interface ICatalogs { export interface ICatalogs {
taxCatalog: JsonTaxCatalogProvider; taxCatalog: JsonTaxCatalogProvider;
paymentCatalog: JsonPaymentCatalogProvider;
} }
export const buildCatalogs = (): ICatalogs => { export const buildCatalogs = (): ICatalogs => {
const taxCatalog = SpainTaxCatalogProvider(); const taxCatalog = SpainTaxCatalogProvider();
const paymentCatalog = FactuGESPaymentCatalogProvider();
return { return {
taxCatalog, taxCatalog,
paymentCatalog,
}; };
}; };

View File

@ -1 +1,2 @@
export * from "./payments";
export * from "./taxes"; export * from "./taxes";

View File

@ -0,0 +1,23 @@
[
{
"id": "019c2834-a766-7787-a626-fa89cac3a8a1",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "6",
"description": "TRANSFERENCIA",
"group": "General"
},
{
"id": "57ed228f-88bd-431d-b5e6-0ed9cff01684",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "14",
"description": "DOMICILIACION BANCARIA",
"group": "General"
},
{
"id": "336e477f-9260-4cb7-b6fd-76f3b088a395",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "15",
"description": "TRANSFERENCIA BANCARIA",
"group": "General"
}
]

View File

@ -0,0 +1,5 @@
import factugesPaymentCatalog from "./factuges-payment-catalog.json";
import { JsonPaymentCatalogProvider } from "./json-payment-catalog.provider";
export const FactuGESPaymentCatalogProvider = () =>
new JsonPaymentCatalogProvider(factugesPaymentCatalog);

View File

@ -0,0 +1,4 @@
export * from "./factuges-payment-catalog.provider";
export * from "./json-payment-catalog.provider";
export * from "./payment-catalog.provider";
export * from "./payment-catalog-types";

View File

@ -0,0 +1,64 @@
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
import { Maybe } from "@repo/rdx-utils";
import type { PaymentCatalogProvider } from "./payment-catalog.provider";
import type {
PaymentCatalogType,
PaymentItemType,
PaymentLookupItems,
} from "./payment-catalog-types";
export class JsonPaymentCatalogProvider implements PaymentCatalogProvider {
// Índice por código normalizado
private readonly catalog: Map<string, PaymentItemType>;
/**
* @param catalog Catálogo ya parseado (p.ej. import JSON o fetch)
*/
constructor(catalog: PaymentCatalogType) {
this.catalog = new Map<string, PaymentItemType>();
// Normalizamos códigos a minúsculas y sin espacios
for (const item of catalog) {
const key = item.factuges_id;
// En caso de duplicados, el último gana (o lanza error si prefieres)
this.catalog.set(key, item);
}
}
static normalizeCode(code: string): string {
return (code ?? "").trim().toLowerCase();
}
findByFactuGESId(factuges_id: string): Maybe<PaymentItemType> {
const found = this.catalog.get(factuges_id);
return found ? Maybe.some(found) : Maybe.none<PaymentItemType>();
}
findById(id: string): Maybe<PaymentItemType> {
for (const value of this.catalog.values()) {
if (value.id === id) {
return Maybe.some(value);
}
}
return Maybe.none<PaymentItemType>();
}
getAll(): PaymentItemType[] {
return Array.from(this.catalog.values());
}
/** Devuelve un objeto indexado por código, compatible con PaymentMultiSelectField */
toOptionLookup(): PaymentLookupItems {
return this.getAll().map((item) => ({
label: item.description,
value: item.id,
group: item.group,
}));
}
/** Devuelve la lista única de grupos disponibles */
groups(): string[] {
return Array.from(new Set(Array.from(this.catalog.values()).map((i) => i.group)));
}
}

View File

@ -0,0 +1,17 @@
// --- DTOs del catálogo (comparten contrato entre frontend/backend) ---
export type PaymentItemType = {
id: string;
company_id: string;
factuges_id: string;
description: string;
group: string;
};
export type PaymentCatalogType = PaymentItemType[];
export type PaymentLookupItems = {
label: string;
value: string;
group: string;
}[];

View File

@ -0,0 +1,19 @@
import type { Maybe } from "@repo/rdx-utils"; // Usa tu implementación real de Maybe
import type {
PaymentCatalogType,
PaymentItemType,
PaymentLookupItems,
} from "./payment-catalog-types";
export interface PaymentCatalogProvider {
findByFactuGESId(factuges_id: string): Maybe<PaymentItemType>;
findById(id: string): Maybe<PaymentItemType>;
// devuelve el catálogo completo como array
getAll(): PaymentCatalogType;
toOptionLookup(): PaymentLookupItems;
groups(): string[]; //Devuelve una lista con los grupos
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customer-invoices", "name": "@erp/customer-invoices",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -401,6 +401,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
this.props.status = InvoiceStatus.issued(); this.props.status = InvoiceStatus.issued();
return Result.ok(); return Result.ok();
} }
// Cálculos // Cálculos
/** /**

View File

@ -258,6 +258,8 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
// Calcular impuestos individuales a partir de la base imponible // Calcular impuestos individuales a partir de la base imponible
const { ivaAmount, recAmount, retentionAmount } = this.taxes.totals(taxableAmount); const { ivaAmount, recAmount, retentionAmount } = this.taxes.totals(taxableAmount);
// El importe de la retención ya va en negativo (-1) y
// no hace falta indicarlo como resta.
const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount); const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount);
const totalAmount = taxableAmount.add(taxesAmount); const totalAmount = taxableAmount.add(taxesAmount);

View File

@ -18,6 +18,8 @@ type TaxGroupState = {
retentionCode: Maybe<string>; retentionCode: Maybe<string>;
retentionPercentage: Maybe<TaxPercentage>; retentionPercentage: Maybe<TaxPercentage>;
retentionAmount: ItemAmount; retentionAmount: ItemAmount;
taxesAmount: ItemAmount;
}; };
/** /**
@ -56,6 +58,8 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
? Maybe.some(retention.unwrap().percentage) ? Maybe.some(retention.unwrap().percentage)
: Maybe.none(), : Maybe.none(),
retentionAmount: ItemAmount.zero(currency.code), retentionAmount: ItemAmount.zero(currency.code),
taxesAmount: ItemAmount.zero(currency.code),
}); });
} }
@ -67,6 +71,7 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
g.ivaAmount = g.ivaAmount.add(itemTotals.ivaAmount); g.ivaAmount = g.ivaAmount.add(itemTotals.ivaAmount);
g.recAmount = g.recAmount.add(itemTotals.recAmount); g.recAmount = g.recAmount.add(itemTotals.recAmount);
g.retentionAmount = g.retentionAmount.add(itemTotals.retentionAmount); g.retentionAmount = g.retentionAmount.add(itemTotals.retentionAmount);
g.taxesAmount = g.taxesAmount.add(itemTotals.taxesAmount);
} }
return map; return map;
} }

View File

@ -19,8 +19,8 @@ export interface IProformaItemsTotals {
} }
/** /**
* Calcula los totales (scale 4) a partir de las líneas valoradas. * Acumula los totales (scale 4) a partir de los totales de las líneas valoradas.
* La lógica fiscal está en ProformaItem; aquí solo se agregan resultados. * Aquí no se hace ningúna operación de cálculo.
*/ */
export class ProformaItemsTotalsCalculator { export class ProformaItemsTotalsCalculator {
constructor(private readonly items: ProformaItems) {} constructor(private readonly items: ProformaItems) {}

View File

@ -30,16 +30,14 @@ export class ProformaTaxesCalculator {
public calculate(): Collection<IProformaTaxTotals> { public calculate(): Collection<IProformaTaxTotals> {
const groups = proformaComputeTaxGroups(this.items); // <- devuelve en escala 4 const groups = proformaComputeTaxGroups(this.items); // <- devuelve en escala 4
//const currencyCode = this.items.currencyCode;
// Vamos acumulando los importes, redondeando previamente a 2 decimales
const rows = Array.from(groups.values()).map((g) => { const rows = Array.from(groups.values()).map((g) => {
const taxableAmount = this.toInvoiceAmount(g.taxableAmount); const taxableAmount = this.toInvoiceAmount(g.taxableAmount);
const ivaAmount = this.toInvoiceAmount(g.ivaAmount); const ivaAmount = this.toInvoiceAmount(g.ivaAmount);
const recAmount = this.toInvoiceAmount(g.recAmount); const recAmount = this.toInvoiceAmount(g.recAmount);
const retentionAmount = this.toInvoiceAmount(g.retentionAmount); const retentionAmount = this.toInvoiceAmount(g.retentionAmount);
//const taxesAmount = this.toInvoiceAmount(g.taxesAmount); const taxesAmount = this.toInvoiceAmount(g.taxesAmount);
const taxesAmount = ivaAmount.add(recAmount).subtract(retentionAmount);
return { return {
taxableAmount, taxableAmount,

View File

@ -1,3 +1,3 @@
export * from "./common/persistence"; export * from "./common";
export * from "./issued-invoices"; export * from "./issued-invoices";
export * from "./proformas"; export * from "./proformas";

View File

@ -13,16 +13,17 @@ export interface IProformaPersistenceMappers {
export const buildProformaPersistenceMappers = ( export const buildProformaPersistenceMappers = (
catalogs: ICatalogs catalogs: ICatalogs
): IProformaPersistenceMappers => { ): IProformaPersistenceMappers => {
const { taxCatalog } = catalogs; const { taxCatalog, paymentCatalog } = catalogs;
// Mappers para el repositorio // Mappers para el repositorio
const domainMapper = new SequelizeProformaDomainMapper({ const domainMapper = new SequelizeProformaDomainMapper({
taxCatalog, taxCatalog,
paymentCatalog,
}); });
const listMapper = new SequelizeProformaSummaryMapper(); const listMapper = new SequelizeProformaSummaryMapper();
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado // Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
const createMapper = new CreateProformaInputMapper({ taxCatalog }); const createMapper = new CreateProformaInputMapper();
return { return {
domainMapper, domainMapper,

View File

@ -1,3 +1,4 @@
import type { JsonPaymentCatalogProvider } from "@erp/core";
import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import { import {
CurrencyCode, CurrencyCode,
@ -11,11 +12,10 @@ import {
maybeFromNullableResult, maybeFromNullableResult,
maybeToNullable, maybeToNullable,
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { import {
InvoiceNumber, InvoiceNumber,
InvoicePaymentMethod,
InvoiceSerie, InvoiceSerie,
InvoiceStatus, InvoiceStatus,
Proforma, Proforma,
@ -40,9 +40,21 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
private _recipientMapper: SequelizeProformaRecipientDomainMapper; private _recipientMapper: SequelizeProformaRecipientDomainMapper;
private _taxesMapper: SequelizeProformaTaxesDomainMapper; private _taxesMapper: SequelizeProformaTaxesDomainMapper;
private _paymentCatalog: JsonPaymentCatalogProvider;
constructor(params: MapperParamsType) { constructor(params: MapperParamsType) {
super(); super();
const { paymentCatalog } = params as {
paymentCatalog: JsonPaymentCatalogProvider;
};
this._paymentCatalog = paymentCatalog;
if (!this._paymentCatalog) {
throw new Error('paymentCatalog not defined ("SequelizeProformaDomainMapper")');
}
this._itemsMapper = new SequelizeProformaItemDomainMapper(params); this._itemsMapper = new SequelizeProformaItemDomainMapper(params);
this._recipientMapper = new SequelizeProformaRecipientDomainMapper(); this._recipientMapper = new SequelizeProformaRecipientDomainMapper();
this._taxesMapper = new SequelizeProformaTaxesDomainMapper(params); this._taxesMapper = new SequelizeProformaTaxesDomainMapper(params);
@ -118,7 +130,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
); );
// Método de pago (VO opcional con id + descripción) // Método de pago (VO opcional con id + descripción)
let paymentMethod = Maybe.none<InvoicePaymentMethod>(); /*let paymentMethod = Maybe.none<InvoicePaymentMethod>();
if (!isNullishOrEmpty(raw.payment_method_id)) { if (!isNullishOrEmpty(raw.payment_method_id)) {
const paymentId = extractOrPushError( const paymentId = extractOrPushError(
@ -127,19 +139,34 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
errors errors
); );
const paymentVO = extractOrPushError( if (paymentId) {
InvoicePaymentMethod.create( const paymentOrNot = this._paymentCatalog.findById(paymentId.toString());
{ paymentDescription: String(raw.payment_method_description ?? "") },
paymentId ?? undefined
),
"payment_method_description",
errors
);
if (paymentVO) { if (paymentOrNot.isSome()) {
paymentMethod = Maybe.some(paymentVO); const paymentCatalogItem = paymentOrNot.unwrap();
const paymentVO = extractOrPushError(
InvoicePaymentMethod.create(
{ paymentDescription: paymentCatalogItem.description ?? "" },
paymentId
),
"paymentMethod",
errors
);
if (paymentVO) {
paymentMethod = Maybe.some(paymentVO);
}
}
} }
} }*/
// Método de pago (ID)
const paymentMethodId = extractOrPushError(
maybeFromNullableResult(raw.payment_method_id, (value) => UniqueID.create(String(value))),
"payment_method_id",
errors
);
// % descuento global (VO) // % descuento global (VO)
const globalDiscountPercentage = extractOrPushError( const globalDiscountPercentage = extractOrPushError(
@ -170,7 +197,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
notes, notes,
languageCode, languageCode,
currencyCode, currencyCode,
paymentMethod, paymentMethodId,
globalDiscountPercentage, globalDiscountPercentage,
linkedInvoiceId, linkedInvoiceId,
@ -241,7 +268,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
globalDiscountPercentage: attributes.globalDiscountPercentage!, globalDiscountPercentage: attributes.globalDiscountPercentage!,
paymentMethod: attributes.paymentMethod!, paymentMethodId: attributes.paymentMethodId!,
linkedInvoiceId: attributes.linkedInvoiceId!, // El id de la factura emitida (linked_invoice) se asigna al hacer issue() desde la proforma, no viene en el modelo de persistencia. linkedInvoiceId: attributes.linkedInvoiceId!, // El id de la factura emitida (linked_invoice) se asigna al hacer issue() desde la proforma, no viene en el modelo de persistencia.
}; };
@ -296,7 +323,27 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
...params, ...params,
}); });
// 4) Si hubo errores de mapeo, devolvemos colección de validación // 4) Payment
let payment: {
id: string | null;
description: string | null;
} = { id: null, description: null };
if (source.hasPaymentMethod) {
const paymentId = source.paymentMethodId.unwrap();
const paymentOrNot = this._paymentCatalog.findById(paymentId.toString());
if (paymentOrNot.isSome()) {
const paymentItem = paymentOrNot.unwrap();
payment = {
id: paymentItem.id ?? null,
description: paymentItem.description ?? null,
};
}
}
// 5) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) { if (errors.length > 0) {
return Result.fail( return Result.fail(
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors) new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
@ -329,14 +376,8 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
description: maybeToNullable(source.description, (description) => description), description: maybeToNullable(source.description, (description) => description),
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()), notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
payment_method_id: maybeToNullable( payment_method_id: payment.id,
source.paymentMethod, payment_method_description: payment.description,
(payment) => payment.toObjectString().id
),
payment_method_description: maybeToNullable(
source.paymentMethod,
(payment) => payment.toObjectString().payment_description
),
subtotal_amount_value: allAmounts.subtotalAmount.value, subtotal_amount_value: allAmounts.subtotalAmount.value,
subtotal_amount_scale: allAmounts.subtotalAmount.scale, subtotal_amount_scale: allAmounts.subtotalAmount.scale,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/customers", "name": "@erp/customers",
"description": "Customers", "description": "Customers",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges", "name": "@erp/factuges",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,15 +1,22 @@
import type { ICatalogs } from "@erp/core/api"; import type { ICatalogs } from "@erp/core/api";
import { CreateProformaFromFactugesInputMapper, ICreateProformaFromFactugesInputMapper } from '../mappers';
import {
CreateProformaFromFactugesInputMapper,
type ICreateProformaFromFactugesInputMapper,
} from "../mappers";
export interface IFactugesInputMappers { export interface IFactugesInputMappers {
createInputMapper: ICreateProformaFromFactugesInputMapper; createInputMapper: ICreateProformaFromFactugesInputMapper;
} }
export const buildFactugesInputMappers = (catalogs: ICatalogs): IFactugesInputMappers => { export const buildFactugesInputMappers = (catalogs: ICatalogs): IFactugesInputMappers => {
const { taxCatalog } = catalogs; const { taxCatalog, paymentCatalog } = catalogs;
// Mappers el DTO a las props validadas (FactugesProps) y luego construir agregado // Mappers el DTO a las props validadas (FactugesProps) y luego construir agregado
const createInputMapper = new CreateProformaFromFactugesInputMapper({ taxCatalog }); const createInputMapper = new CreateProformaFromFactugesInputMapper({
taxCatalog,
paymentCatalog,
});
return { return {
createInputMapper, createInputMapper,

View File

@ -1,4 +1,4 @@
import type { JsonTaxCatalogProvider } from "@erp/core"; import type { JsonPaymentCatalogProvider, JsonTaxCatalogProvider } from "@erp/core";
import { DiscountPercentage, Tax } from "@erp/core/api"; import { DiscountPercentage, Tax } from "@erp/core/api";
import { import {
InvoiceAmount, InvoiceAmount,
@ -103,6 +103,7 @@ export type ProformaDraft = {
}; };
export type ProformaPaymentDraft = { export type ProformaPaymentDraft = {
payment_id: string;
factuges_id: string; factuges_id: string;
description: string; description: string;
}; };
@ -126,9 +127,14 @@ export class CreateProformaFromFactugesInputMapper
implements ICreateProformaFromFactugesInputMapper implements ICreateProformaFromFactugesInputMapper
{ {
private readonly taxCatalog: JsonTaxCatalogProvider; private readonly taxCatalog: JsonTaxCatalogProvider;
private readonly paymentCatalog: JsonPaymentCatalogProvider;
constructor(params: { taxCatalog: JsonTaxCatalogProvider }) { constructor(params: {
taxCatalog: JsonTaxCatalogProvider;
paymentCatalog: JsonPaymentCatalogProvider;
}) {
this.taxCatalog = params.taxCatalog; this.taxCatalog = params.taxCatalog;
this.paymentCatalog = params.paymentCatalog;
} }
public map( public map(
@ -190,9 +196,20 @@ export class CreateProformaFromFactugesInputMapper
const errors: ValidationErrorDetail[] = []; const errors: ValidationErrorDetail[] = [];
const { companyId } = params; const { companyId } = params;
const factuges_id = String(dto.payment_method_id);
const paymentOrNot = this.paymentCatalog.findByFactuGESId(factuges_id);
if (paymentOrNot.isNone()) {
errors.push({
path: "payment_method_id",
message: "Forma de pago no encontrada",
});
}
return { return {
factuges_id: String(dto.payment_method_id), payment_id: paymentOrNot.unwrap().id,
description: String(dto.payment_method_description), factuges_id: paymentOrNot.unwrap().factuges_id,
description: paymentOrNot.unwrap().description,
}; };
} }

View File

@ -340,6 +340,15 @@ export class CreateProformaFromFactugesUseCase {
InvoicePaymentMethod.create({ paymentDescription: payment.description }, payment.id).data InvoicePaymentMethod.create({ paymentDescription: payment.description }, payment.id).data
); );
console.log({
...proformaDraft,
companyId,
customerId,
status: defaultStatus,
paymentMethod,
recipient,
});
return Result.ok({ return Result.ok({
...proformaDraft, ...proformaDraft,
companyId, companyId,

View File

@ -3,18 +3,21 @@
"id": "019c2834-a766-7787-a626-fa89cac3a8a1", "id": "019c2834-a766-7787-a626-fa89cac3a8a1",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f", "company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "6", "factuges_id": "6",
"description": "TRANSFERENCIA" "description": "TRANSFERENCIA",
"group": "General"
}, },
{ {
"id": "57ed228f-88bd-431d-b5e6-0ed9cff01684", "id": "57ed228f-88bd-431d-b5e6-0ed9cff01684",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f", "company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "14", "factuges_id": "14",
"description": "DOMICILIACION BANCARIA" "description": "DOMICILIACION BANCARIA",
"group": "General"
}, },
{ {
"id": "336e477f-9260-4cb7-b6fd-76f3b088a395", "id": "336e477f-9260-4cb7-b6fd-76f3b088a395",
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f", "company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
"factuges_id": "15", "factuges_id": "15",
"description": "TRANSFERENCIA BANCARIA" "description": "TRANSFERENCIA BANCARIA",
"group": "General"
} }
] ]

View File

@ -1,6 +1,6 @@
import { type SetupParams, buildCatalogs, buildTransactionManager } from "@erp/core/api"; import { type SetupParams, buildCatalogs, buildTransactionManager } from "@erp/core/api";
import type { ProformaPublicServices } from "@erp/customer-invoices/api"; import type { IProformaPublicServices } from "@erp/customer-invoices/api";
import type { CustomerPublicServices } from "@erp/customers/api"; import type { ICustomerPublicServices } from "@erp/customers/api";
import { import {
buildCreateProformaFromFactugesUseCase, buildCreateProformaFromFactugesUseCase,
@ -11,8 +11,8 @@ import type { CreateProformaFromFactugesUseCase } from "../../application/use-ca
export type FactugesInternalDeps = { export type FactugesInternalDeps = {
useCases: { useCases: {
createProforma: (publicServices: { createProforma: (publicServices: {
customerServices: CustomerPublicServices; customerServices: ICustomerPublicServices;
proformaServices: ProformaPublicServices; proformaServices: IProformaPublicServices;
}) => CreateProformaFromFactugesUseCase; }) => CreateProformaFromFactugesUseCase;
}; };
}; };
@ -31,8 +31,8 @@ export function buildFactugesDependencies(params: SetupParams): FactugesInternal
return { return {
useCases: { useCases: {
createProforma: (publicServices: { createProforma: (publicServices: {
customerServices: CustomerPublicServices; customerServices: ICustomerPublicServices;
proformaServices: ProformaPublicServices; proformaServices: IProformaPublicServices;
}) => }) =>
buildCreateProformaFromFactugesUseCase({ buildCreateProformaFromFactugesUseCase({
dtoMapper: inputMappers.createInputMapper, dtoMapper: inputMappers.createInputMapper,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/supplier-invoices", "name": "@erp/supplier-invoices",
"description": "Supplier invoices", "description": "Supplier invoices",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/suppliers", "name": "@erp/suppliers",
"description": "Suppliers", "description": "Suppliers",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,13 +1,14 @@
{ {
"name": "uecko-erp-2025", "name": "uecko-erp-2025",
"private": true, "private": true,
"version": "0.6.3", "version": "0.6.4",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"modules/*", "modules/*",
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"lint": "turbo run lint",
"build": "turbo build", "build": "turbo build",
"build:templates": "bash scripts/build-templates.sh", "build:templates": "bash scripts/build-templates.sh",
"build:api": "bash scripts/build-api.sh rodax --api", "build:api": "bash scripts/build-api.sh rodax --api",

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-criteria", "name": "@repo/rdx-criteria",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ddd", "name": "@repo/rdx-ddd",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-logger", "name": "@repo/rdx-logger",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ui", "name": "@repo/rdx-ui",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-utils", "name": "@repo/rdx-utils",
"version": "0.6.3", "version": "0.6.4",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog" import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar" import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props" import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render" import { useRender } from "@base-ui/react/use-render"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -89,7 +89,7 @@ function Carousel({
) )
React.useEffect(() => { React.useEffect(() => {
if (!api || !setApi) return if (!(api && setApi)) return
setApi(api) setApi(api)
}, [api, setApi]) }, [api, setApi])

View File

@ -180,7 +180,7 @@ function ChartTooltipContent({
labelKey, labelKey,
]) ])
if (!active || !payload?.length) { if (!(active && payload?.length)) {
return null return null
} }
@ -193,7 +193,7 @@ function ChartTooltipContent({
className className
)} )}
> >
{!nestLabel ? tooltipLabel : null} {nestLabel ? null : tooltipLabel}
<div className="grid gap-1.5"> <div className="grid gap-1.5">
{payload {payload
.filter((item) => item.type !== "none") .filter((item) => item.type !== "none")

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { Command as CommandPrimitive } from "cmdk" import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu" import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog" import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu" import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { Input as InputPrimitive } from "@base-ui/react/input" import { Input as InputPrimitive } from "@base-ui/react/input"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props" import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render" import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu" import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar" import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"
import { ChevronDownIcon } from "lucide-react" import { ChevronDownIcon } from "lucide-react"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"
import { Button } from "@repo/shadcn-ui/components/button" import { Button } from "@repo/shadcn-ui/components/button"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { Popover as PopoverPrimitive } from "@base-ui/react/popover" import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select" import { Select as SelectPrimitive } from "@base-ui/react/select"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog" import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -518,7 +518,7 @@ function SidebarMenuButton({
}, },
props props
), ),
render: !tooltip ? render : <TooltipTrigger render={render} />, render: tooltip ? <TooltipTrigger render={render} /> : render,
state: { state: {
slot: "sidebar-menu-button", slot: "sidebar-menu-button",
sidebar: "menu-button", sidebar: "menu-button",

View File

@ -1,6 +1,6 @@
"use client" "use client"
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,4 +1,4 @@
import * as React from "react" import type * as React from "react"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"

View File

@ -1,7 +1,7 @@
import * as React from "react" import * as React from "react"
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle" import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group" import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
import { type VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import { cn } from "@repo/shadcn-ui/lib/utils" import { cn } from "@repo/shadcn-ui/lib/utils"
import { toggleVariants } from "@repo/shadcn-ui/components/toggle" import { toggleVariants } from "@repo/shadcn-ui/components/toggle"