This commit is contained in:
David Arranz 2026-07-02 17:48:39 +02:00
parent 7a161717c2
commit 6187a77dd9
44 changed files with 311 additions and 139 deletions

View File

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

View File

@ -1,7 +1,7 @@
{
"name": "@erp/factuges-web",
"private": true,
"version": "0.7.8",
"version": "0.8.2",
"type": "module",
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit",

View File

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

View File

@ -1,7 +1,7 @@
{
"name": "@erp/catalogs",
"description": "Catalogs module",
"version": "0.7.8",
"version": "0.8.2",
"private": true,
"type": "module",
"sideEffects": false,

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import {
InvoicePaymentMethod,
InvoiceStatus,
IssuedInvoiceItem,
IssuedInvoiceRecipient,
IssuedInvoiceTax,
IssuedInvoiceTaxes,
type Proforma,
@ -29,6 +30,11 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
public toCreateProps(source: ProformaIssueReadModel): Result<IIssuedInvoiceCreateProps, Error> {
const { proforma } = source;
const recipientResult = this.resolveRecipient(proforma);
if (recipientResult.isFailure) {
return Result.fail(recipientResult.error);
}
const itemsResult = this.resolveItems(proforma);
if (itemsResult.isFailure) {
@ -72,7 +78,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
paymentMethod: paymentResult.data,
customerId: proforma.customerId,
recipient: proforma.recipient.getOrUndefined()!,
recipient: recipientResult.data,
items: itemsResult.data,
@ -99,7 +105,31 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
totalAmount: proformaTotals.totalAmount,
verifactu: Maybe.none(),
} satisfies IIssuedInvoiceCreateProps);
}
private resolveRecipient(proforma: Proforma): Result<IssuedInvoiceRecipient, Error> {
const recipient = proforma.recipient;
if (recipient.isNone()) {
return Result.fail(new Error("Recipient is required"));
}
const recipientValue = recipient.unwrap();
const newRecipient = IssuedInvoiceRecipient.create({
name: recipientValue.name,
tin: recipientValue.tin.unwrap(),
street: recipientValue.street.unwrap(),
street2: recipientValue.street2,
city: recipientValue.city.unwrap(),
postalCode: recipientValue.postalCode.unwrap(),
province: recipientValue.province,
country: recipientValue.country,
});
return newRecipient;
}
private resolveItems(proforma: Proforma): Result<IssuedInvoiceItem[], Error> {

View File

@ -1,5 +1,5 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import { DomainValidationError, maybeToEmptyString, maybeToNullable } from "@repo/rdx-ddd";
import { DomainValidationError, maybeToNullable } from "@repo/rdx-ddd";
import type { IssuedInvoice } from "../../../../domain";
@ -22,12 +22,12 @@ export class IssuedInvoiceRecipientFullSnapshotBuilder
return {
id: invoice.customerId.toString(),
name: recipient.name.toString(),
tin: maybeToEmptyString(recipient.tin, (v) => v.toString()),
street: maybeToNullable(recipient.street, (v) => v.toString()),
tin: recipient.tin.toString(),
street: recipient.street.toString(),
street2: maybeToNullable(recipient.street2, (v) => v.toString()),
city: maybeToNullable(recipient.city, (v) => v.toString()),
city: recipient.city.toString(),
province: maybeToNullable(recipient.province, (v) => v.toString()),
postal_code: maybeToNullable(recipient.postalCode, (v) => v.toString()),
postal_code: recipient.postalCode.toString(),
country: maybeToNullable(recipient.country, (v) => v.toString()),
};
}

View File

@ -14,10 +14,10 @@ import { type Maybe, Result } from "@repo/rdx-utils";
export type IssuedInvoiceRecipientProps = {
name: Name;
tin: TINNumber;
street: Maybe<Street>;
street: Street;
street2: Maybe<Street>;
city: Maybe<City>;
postalCode: Maybe<PostalCode>;
city: City;
postalCode: PostalCode;
province: Maybe<Province>;
country: Maybe<Country>;
};
@ -56,7 +56,7 @@ export class IssuedInvoiceRecipient extends ValueObject<IssuedInvoiceRecipientPr
return this.props.tin;
}
get street(): Maybe<Street> {
get street(): Street {
return this.props.street;
}
@ -64,11 +64,11 @@ export class IssuedInvoiceRecipient extends ValueObject<IssuedInvoiceRecipientPr
return this.props.street2;
}
get city(): Maybe<City> {
get city(): City {
return this.props.city;
}
get postalCode(): Maybe<PostalCode> {
get postalCode(): PostalCode {
return this.props.postalCode;
}
@ -92,10 +92,10 @@ export class IssuedInvoiceRecipient extends ValueObject<IssuedInvoiceRecipientPr
return {
tin: this.tin.toString(),
name: this.name.toString(),
street: maybeToNullable(this.street, (value) => value.toString()),
street: this.street.toString(),
street2: maybeToNullable(this.street2, (value) => value.toString()),
city: maybeToNullable(this.city, (value) => value.toString()),
postal_code: maybeToNullable(this.postalCode, (value) => value.toString()),
city: this.city.toString(),
postal_code: this.postalCode.toString(),
province: maybeToNullable(this.province, (value) => value.toString()),
country: maybeToNullable(this.country, (value) => value.toString()),
};

View File

@ -314,7 +314,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
return Result.ok(false);
}
if (!canManuallyTransitionProformaStatus({ currentStatus, nextStatus })) {
if (!canManuallyTransitionProformaStatus({ currentStatus, nextStatus, proforma: this })) {
return Result.fail(
new InvalidProformaTransitionError(
currentStatus.toPrimitive(),
@ -392,6 +392,26 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
);
}
const recipientValue = this.recipient.unwrap();
if (
!recipientValue.name ||
recipientValue.tin.isNone() ||
recipientValue.street.isNone() ||
recipientValue.city.isNone() ||
recipientValue.postalCode.isNone() ||
recipientValue.province.isNone() ||
recipientValue.country.isNone()
) {
return Result.fail(
new DomainValidationError(
"MISSING_RECIPIENT",
"recipient",
"Recipient is required to issue the proforma"
)
);
}
if (this.items.size() === 0) {
return Result.fail(
new DomainValidationError(

View File

@ -1,4 +1,4 @@
import { INVOICE_STATUS, type InvoiceStatus } from "../..";
import { INVOICE_STATUS, type InvoiceStatus, type Proforma } from "../..";
/**
* Transiciones manuales permitidas para una proforma.
@ -17,11 +17,38 @@ const PROFORMA_MANUAL_STATUS_TRANSITIONS: Readonly<
};
export function canManuallyTransitionProformaStatus(params: {
proforma: Proforma;
currentStatus: InvoiceStatus;
nextStatus: InvoiceStatus;
}): boolean {
const current = params.currentStatus.toPrimitive() as INVOICE_STATUS;
const next = params.nextStatus.toPrimitive() as INVOICE_STATUS;
return PROFORMA_MANUAL_STATUS_TRANSITIONS[current].includes(next);
let possible = PROFORMA_MANUAL_STATUS_TRANSITIONS[current].includes(next);
// Validación adicional: si la transición es diferente a 'draft', debe existir
// un destinatario obligatoriamente con todos sus datos rellenos.
if (next !== INVOICE_STATUS.DRAFT) {
const { recipient } = params.proforma;
if (recipient.isNone()) {
possible = false;
} else {
const recipientValue = recipient.unwrap();
if (
!recipientValue.name ||
recipientValue.tin.isNone() ||
recipientValue.street.isNone() ||
recipientValue.city.isNone() ||
recipientValue.postalCode.isNone() ||
recipientValue.province.isNone() ||
recipientValue.country.isNone()
) {
possible = false;
}
}
}
return possible;
}

View File

@ -47,11 +47,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
//const customerTin = extractOrPushError(TINNumber.create(_tin!), "customer_tin", errors);
const customerTin = extractOrPushError(TINNumber.create(_tin), "customer_tin", errors);
const customerStreet = extractOrPushError(
maybeFromNullableResult(_street, (value) => Street.create(value)),
"customer_street",
errors
);
const customerStreet = extractOrPushError(Street.create(_street), "customer_street", errors);
const customerStreet2 = extractOrPushError(
maybeFromNullableResult(_street2, (value) => Street.create(value)),
@ -59,11 +55,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
errors
);
const customerCity = extractOrPushError(
maybeFromNullableResult(_city, (value) => City.create(value)),
"customer_city",
errors
);
const customerCity = extractOrPushError(City.create(_city), "customer_city", errors);
const customerProvince = extractOrPushError(
maybeFromNullableResult(_province, (value) => Province.create(value)),
@ -72,7 +64,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
);
const customerPostalCode = extractOrPushError(
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
PostalCode.create(_postal_code),
"customer_postal_code",
errors
);
@ -125,11 +117,11 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
return {
customer_tin: recipient.tin.toString(),
customer_name: recipient.name.toPrimitive(),
customer_street: maybeToNullable(recipient.street, (v) => v.toPrimitive()),
customer_street: recipient.street.toPrimitive(),
customer_street2: maybeToNullable(recipient.street2, (v) => v.toPrimitive()),
customer_city: maybeToNullable(recipient.city, (v) => v.toPrimitive()),
customer_city: recipient.city.toPrimitive(),
customer_province: maybeToNullable(recipient.province, (v) => v.toPrimitive()),
customer_postal_code: maybeToNullable(recipient.postalCode, (v) => v.toPrimitive()),
customer_postal_code: recipient.postalCode.toPrimitive(),
customer_country: maybeToNullable(recipient.country, (v) => v.toPrimitive()),
};
}

View File

@ -58,11 +58,7 @@ export class SequelizeIssuedInvoiceRecipientListMapper extends SequelizeQueryMap
const customerTin = extractOrPushError(TINNumber.create(_tin!), "customer_tin", errors);
const customerStreet = extractOrPushError(
maybeFromNullableResult(_street, (value) => Street.create(value)),
"customer_street",
errors
);
const customerStreet = extractOrPushError(Street.create(_street), "customer_street", errors);
const customerStreet2 = extractOrPushError(
maybeFromNullableResult(_street2, (value) => Street.create(value)),
@ -70,11 +66,7 @@ export class SequelizeIssuedInvoiceRecipientListMapper extends SequelizeQueryMap
errors
);
const customerCity = extractOrPushError(
maybeFromNullableResult(_city, (value) => City.create(value)),
"customer_city",
errors
);
const customerCity = extractOrPushError(City.create(_city), "customer_city", errors);
const customerProvince = extractOrPushError(
maybeFromNullableResult(_province, (value) => Province.create(value)),
@ -83,7 +75,7 @@ export class SequelizeIssuedInvoiceRecipientListMapper extends SequelizeQueryMap
);
const customerPostalCode = extractOrPushError(
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
PostalCode.create(_postal_code),
"customer_postal_code",
errors
);

View File

@ -259,9 +259,8 @@ export class IssuedInvoiceRepository
column: "description",
},
recipient_name: {
type: "association",
association: "current_customer",
column: "name",
type: "root",
column: "recipient_name",
},
},
sortableFields: [
@ -272,6 +271,7 @@ export class IssuedInvoiceRepository
"created_at",
"serie",
],
fullTextTableAlias: "CustomerInvoiceModel",
enableFullText: true,
database: this.database,
strictMode: true, // fuerza error si ORDER BY no permitido
@ -280,9 +280,9 @@ export class IssuedInvoiceRepository
/**
* Restricciones propias del repositorio.
*
* El dominio `Proforma` no conoce `is_proforma`; este discriminador
* El dominio `IssuedInvoice` no conoce `is_proforma`; este discriminador
* pertenece a la infraestructura porque actualmente se comparte tabla
* con facturas emitidas.
* con proformas.
*
* No se añade `deleted_at: null` porque el modelo usa `paranoid: true`;
* Sequelize aplica automáticamente `deleted_at IS NULL` en las consultas.

View File

@ -455,6 +455,7 @@ export class ProformaRepository
"created_at",
"serie",
],
fullTextTableAlias: "CustomerInvoiceModel",
enableFullText: true,
database: this.database,
strictMode: true, // fuerza error si ORDER BY no permitido

View File

@ -89,7 +89,7 @@ export const ProformaLineEditor = ({
{
id: "quantity",
header: t("form_fields.items.quantity.label", "Cantidad"),
headClassName: "w-[100px] text-right",
headClassName: "w-[75px] text-right",
cell: ({ index }) => (
<QuantityField
inputClassName="border-none"
@ -102,7 +102,7 @@ export const ProformaLineEditor = ({
{
id: "unitAmount",
header: t("form_fields.items.unit_amount.label", "Importe unitario"),
headClassName: "w-[200px] text-right",
headClassName: "w-[100px] text-right",
cell: ({ index }) => (
<AmountField
inputClassName="border-none"

View File

@ -95,7 +95,7 @@ export const ProformaUpdateHeaderEditor = ({
readOnly={readOnly}
/>
<TextAreaField
className="md:col-span-12 hidden"
className="md:col-span-12"
disabled={disabled}
label={t("form_fields.proformas.notes.label")}
maxLength={256}

View File

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

View File

@ -14,6 +14,11 @@ export interface ICustomerPublicServices {
context: ICustomerServicesContext
) => Promise<Result<Customer, Error>>;
getCustomerById: (
id: UniqueID,
context: ICustomerServicesContext
) => Promise<Result<Customer, Error>>;
createCustomer: (
id: UniqueID,
props: ICustomerCreateProps,

View File

@ -42,6 +42,18 @@ export function buildCustomerPublicServices(
return Result.ok(customerResult.data);
},
getCustomerById: async (id: UniqueID, context: ICustomerServicesContext) => {
const { companyId, transaction } = context;
const customerResult = await finder.findCustomerById(companyId, id, transaction);
if (customerResult.isFailure) {
return Result.fail(customerResult.error);
}
return Result.ok(customerResult.data);
},
createCustomer: async (
id: UniqueID,
props: ICustomerCreateProps,

View File

@ -1,5 +1,5 @@
import { useUrlParamId } from "@erp/core/hooks";
import { useSearchParams } from "react-router-dom";
import { createSearchParams, useNavigate, useSearchParams } from "react-router-dom";
import { useCustomerUpdateController } from "./use-customer-update.controller";
@ -11,12 +11,21 @@ import { useCustomerUpdateController } from "./use-customer-update.controller";
export const useCustomerUpdatePageController = () => {
const customerId = useUrlParamId();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const updateCtrl = useCustomerUpdateController(customerId);
const returnTo = searchParams.get("returnTo") ?? "/customers";
const updateCtrl = useCustomerUpdateController(customerId, {
onUpdated: (updated) => {
navigate({
pathname: `/customers/${updated.id}/`,
search: createSearchParams({
returnTo,
}).toString(),
});
},
});
return {
updateCtrl,
returnTo,

View File

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

View File

@ -1,7 +1,7 @@
{
"name": "@erp/identity",
"description": "Identity module",
"version": "0.7.8",
"version": "0.8.2",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -22,7 +22,7 @@ export const identityAPIModule: IModuleServer = {
* - NO conecta infraestructura
*/
async setup(params) {
const { env: ENV, app, database, baseRoutePath: API_BASE_PATH, logger } = params;
const { logger } = params;
// 1) Dominio interno
const internal = buildIdentityDependencies(params);

View File

@ -0,0 +1,107 @@
import {
ExpressController,
type RequestWithAuth,
UnauthorizedApiError,
ValidationApiError,
} from "@erp/core/api";
import { EmailAddress, UniqueID } from "@repo/rdx-ddd";
import type { NextFunction, Request, Response } from "express";
import type { IAccessTokenVerifier, IAccountRepository } from "../../../application";
const COMPANY_HEADER_NAME = "X-Company-Id";
function parseBearerToken(authorization?: string): string | undefined {
if (!authorization) {
return undefined;
}
const [scheme, token] = authorization.split(" ");
if (scheme?.toLowerCase() !== "bearer") {
return undefined;
}
return token?.trim() || undefined;
}
function getCompanyIdFromHeader(req: Request): UniqueID | undefined | Error {
const rawCompanyId = req.get(COMPANY_HEADER_NAME);
if (!rawCompanyId) {
return undefined;
}
const companyIdResult = UniqueID.create(rawCompanyId);
if (companyIdResult.isFailure) {
return new Error(`Header "${COMPANY_HEADER_NAME}" must be a valid UUID.`);
}
return companyIdResult.data;
}
export function authenticateUser(params: {
accessTokenVerifier: IAccessTokenVerifier;
accountRepository: IAccountRepository;
}) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const authorization = req.get("authorization");
const token = parseBearerToken(authorization);
if (!token) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const verifiedTokenResult = await params.accessTokenVerifier.verify(token);
if (verifiedTokenResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const accountIdResult = UniqueID.create(verifiedTokenResult.data.accountId);
if (accountIdResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const accountResult = await params.accountRepository.findById(accountIdResult.data);
if (accountResult.isFailure || accountResult.data.isNone()) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const account = accountResult.data.unwrap();
const canAuthenticateResult = account.canAuthenticate();
if (canAuthenticateResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const emailResult = EmailAddress.create(verifiedTokenResult.data.email);
if (emailResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const companyIdFromHeader = getCompanyIdFromHeader(req);
if (companyIdFromHeader instanceof Error) {
return ExpressController.errorResponse(
new ValidationApiError(companyIdFromHeader.message, [
{
field: COMPANY_HEADER_NAME,
message: companyIdFromHeader.message,
},
]),
req,
res
);
}
(req as RequestWithAuth).user = {
userId: account.id,
email: emailResult.data,
companyId: companyIdFromHeader,
roles: [],
};
return next();
} catch {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
};
}

View File

@ -1,69 +1,9 @@
import { ExpressController, type RequestWithAuth, UnauthorizedApiError } from "@erp/core/api";
import { EmailAddress, UniqueID } from "@repo/rdx-ddd";
import type { NextFunction, Request, Response } from "express";
import type { IAccessTokenVerifier, IAccountRepository } from "../../../application";
function parseBearerToken(authorization?: string): string | undefined {
if (!authorization) {
return undefined;
}
const [scheme, token] = authorization.split(" ");
if (scheme?.toLowerCase() !== "bearer") {
return undefined;
}
return token?.trim() || undefined;
}
import { authenticateUser } from "./authenticate-user.middleware";
export function identityAuthenticateRequest(params: {
accessTokenVerifier: IAccessTokenVerifier;
accountRepository: IAccountRepository;
}) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const authorization = req.get("authorization");
const token = parseBearerToken(authorization);
if (!token) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const verifiedTokenResult = await params.accessTokenVerifier.verify(token);
if (verifiedTokenResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const accountIdResult = UniqueID.create(verifiedTokenResult.data.accountId);
if (accountIdResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const accountResult = await params.accountRepository.findById(accountIdResult.data);
if (accountResult.isFailure || accountResult.data.isNone()) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const account = accountResult.data.unwrap();
const canAuthenticateResult = account.canAuthenticate();
if (canAuthenticateResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
const emailResult = EmailAddress.create(verifiedTokenResult.data.email);
if (emailResult.isFailure) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
(req as RequestWithAuth).user = {
userId: account.id,
email: emailResult.data,
};
return next();
} catch {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
};
return authenticateUser(params);
}

View File

@ -1 +1,4 @@
export * from "./authenticate-user.middleware";
export * from "./require-authenticated.middleware";
export * from "./require-company-context.middleware";
export * from "./identity-authenticate-request";

View File

@ -0,0 +1,13 @@
import { ExpressController, UnauthorizedApiError, type RequestWithAuth } from "@erp/core/api";
import type { NextFunction, Response } from "express";
export function requireAuthenticated() {
return (req: RequestWithAuth, res: Response, next: NextFunction) => {
if (!req.user) {
return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res);
}
next();
};
}

View File

@ -0,0 +1,20 @@
import { ExpressController, ForbiddenApiError, type RequestWithAuth } from "@erp/core/api";
import type { NextFunction, Response } from "express";
export function requireCompanyContext() {
return (req: RequestWithAuth, res: Response, next: NextFunction) => {
/**
* TODO: validar membership real cuando exista persistencia de CompanyMembership.
*/
if (!req.user?.companyId) {
return ExpressController.errorResponse(
new ForbiddenApiError("Company context required"),
req,
res
);
}
next();
};
}

View File

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

View File

@ -326,6 +326,7 @@ export class SupplierInvoiceRepository
},
},
sortableFields: ["invoice_date", "due_date", "id", "created_at"],
fullTextTableAlias: "SupplierInvoiceModel",
enableFullText: true,
database: this.database,
strictMode: true,

View File

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

View File

@ -1,7 +1,7 @@
{
"name": "uecko-erp-2025",
"private": true,
"version": "0.7.8",
"version": "0.8.2",
"workspaces": [
"apps/*",
"modules/*",

View File

@ -1,6 +1,6 @@
{
"name": "@repo/i18next",
"version": "0.7.8",
"version": "0.8.2",
"private": true,
"type": "module",
"scripts": {

View File

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

View File

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

View File

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

View File

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

View File

@ -77,7 +77,7 @@ export const LineDescriptionField = <TFormValues extends FieldValues>({
React.useEffect(() => {
resizeTextarea();
}, [resizeTextarea]);
}, []);
return (
<Field className={cn("gap-1", className)} data-invalid={!!fieldError} orientation={orientation}>

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@repo/shadcn-ui",
"version": "0.7.8",
"version": "0.8.2",
"type": "module",
"private": true,
"exports": {

View File

@ -1,6 +1,6 @@
{
"name": "@repo/typescript-config",
"version": "0.7.8",
"version": "0.8.2",
"private": true,
"publishConfig": {
"access": "public"