Parte cliente de Suppliers
This commit is contained in:
parent
91166766a3
commit
6e97e91173
@ -12,22 +12,45 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/common/index.ts",
|
".": "./src/common/index.ts",
|
||||||
"./common": "./src/common/index.ts",
|
"./common": "./src/common/index.ts",
|
||||||
"./api": "./src/api/index.ts"
|
"./api": "./src/api/index.ts",
|
||||||
|
"./api/domain": "./src/api/domain/index.ts",
|
||||||
|
"./client": "./src/web/manifest.ts",
|
||||||
|
"./globals.css": "./src/web/globals.css",
|
||||||
|
"./components": "./src/web/components/index.ts"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/react": "^19.1.2",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@erp/auth": "workspace:*",
|
"@erp/auth": "workspace:*",
|
||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@repo/i18next": "workspace:*",
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
|
"@repo/rdx-ui": "workspace:*",
|
||||||
"@repo/rdx-utils": "workspace:*",
|
"@repo/rdx-utils": "workspace:*",
|
||||||
|
"@repo/shadcn-ui": "workspace:*",
|
||||||
|
"@tanstack/react-query": "^5.90.6",
|
||||||
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"lucide-react": "^0.577.0",
|
||||||
|
"react-data-table-component": "^7.7.0",
|
||||||
|
"react-hook-form": "^7.72.1",
|
||||||
|
"react-i18next": "^16.2.4",
|
||||||
|
"react-router-dom": "^6.26.0",
|
||||||
"sequelize": "^6.37.8",
|
"sequelize": "^6.37.8",
|
||||||
|
"use-debounce": "^10.0.5",
|
||||||
"zod": "^4.1.11"
|
"zod": "^4.1.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import type { GetSupplierByIdResponseDTO } from "../../../common";
|
||||||
|
import type { Supplier } from "../entities";
|
||||||
|
|
||||||
|
export const GetSupplierByIdAdapter = {
|
||||||
|
fromDTO(dto: GetSupplierByIdResponseDTO): Supplier {
|
||||||
|
return {
|
||||||
|
id: dto.id,
|
||||||
|
companyId: dto.company_id,
|
||||||
|
status: dto.status,
|
||||||
|
reference: dto.reference,
|
||||||
|
|
||||||
|
isCompany: dto.is_company === "1",
|
||||||
|
name: dto.name,
|
||||||
|
tradeName: dto.trade_name,
|
||||||
|
tin: dto.tin,
|
||||||
|
|
||||||
|
street: dto.street,
|
||||||
|
street2: dto.street2,
|
||||||
|
city: dto.city,
|
||||||
|
province: dto.province,
|
||||||
|
postalCode: dto.postal_code,
|
||||||
|
country: dto.country,
|
||||||
|
|
||||||
|
primaryEmail: dto.email_primary,
|
||||||
|
secondaryEmail: dto.email_secondary,
|
||||||
|
primaryPhone: dto.phone_primary,
|
||||||
|
secondaryPhone: dto.phone_secondary,
|
||||||
|
primaryMobile: dto.mobile_primary,
|
||||||
|
secondaryMobile: dto.mobile_secondary,
|
||||||
|
|
||||||
|
fax: dto.fax,
|
||||||
|
website: dto.website,
|
||||||
|
|
||||||
|
languageCode: dto.language_code,
|
||||||
|
currencyCode: dto.currency_code,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
3
modules/supplier/src/web/shared/adapters/index.ts
Normal file
3
modules/supplier/src/web/shared/adapters/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./get-supplier-by-id.adapter";
|
||||||
|
export * from "./list-suppliers.adapter";
|
||||||
|
export * from "./supplier-to-list-row-patch.adapter";
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import type { ListSuppliersResponseDTO } from "../../../common";
|
||||||
|
import type { SupplierList, SupplierListRow } from "../entities";
|
||||||
|
|
||||||
|
type ListSuppliersItemDTO = ListSuppliersResponseDTO["items"][number];
|
||||||
|
|
||||||
|
export const ListSuppliersAdapter = {
|
||||||
|
fromDTO(pageDto: ListSuppliersResponseDTO): SupplierList {
|
||||||
|
return {
|
||||||
|
page: pageDto.page,
|
||||||
|
per_page: pageDto.per_page,
|
||||||
|
total_pages: pageDto.total_pages,
|
||||||
|
total_items: pageDto.total_items,
|
||||||
|
items: pageDto.items.map(ListSuppliersRowAdapter.fromDTO),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListSuppliersRowAdapter = {
|
||||||
|
fromDTO(rowDto: ListSuppliersItemDTO): SupplierListRow {
|
||||||
|
return {
|
||||||
|
id: rowDto.id,
|
||||||
|
companyId: rowDto.company_id,
|
||||||
|
status: rowDto.status,
|
||||||
|
reference: rowDto.reference,
|
||||||
|
|
||||||
|
isCompany: rowDto.is_company === "1",
|
||||||
|
name: rowDto.name,
|
||||||
|
tradeName: rowDto.trade_name,
|
||||||
|
tin: rowDto.tin,
|
||||||
|
|
||||||
|
street: rowDto.street,
|
||||||
|
street2: rowDto.street2,
|
||||||
|
city: rowDto.city,
|
||||||
|
province: rowDto.province,
|
||||||
|
postalCode: rowDto.postal_code,
|
||||||
|
country: rowDto.country,
|
||||||
|
|
||||||
|
primaryEmail: rowDto.email_primary,
|
||||||
|
secondaryEmail: rowDto.email_secondary,
|
||||||
|
primaryPhone: rowDto.phone_primary,
|
||||||
|
secondaryPhone: rowDto.phone_secondary,
|
||||||
|
primaryMobile: rowDto.mobile_primary,
|
||||||
|
secondaryMobile: rowDto.mobile_secondary,
|
||||||
|
|
||||||
|
fax: rowDto.fax,
|
||||||
|
website: rowDto.website,
|
||||||
|
|
||||||
|
languageCode: rowDto.language_code,
|
||||||
|
currencyCode: rowDto.currency_code,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import type { Supplier, SupplierListRow } from "../entities";
|
||||||
|
|
||||||
|
export type SupplierListRowPatch = Pick<SupplierListRow, "id"> & Partial<SupplierListRow>;
|
||||||
|
|
||||||
|
export const SupplierToListRowPatchAdapter = {
|
||||||
|
fromSupplier(supplier: Supplier): SupplierListRowPatch {
|
||||||
|
return {
|
||||||
|
id: supplier.id,
|
||||||
|
status: supplier.status,
|
||||||
|
reference: supplier.reference,
|
||||||
|
|
||||||
|
isCompany: supplier.isCompany,
|
||||||
|
name: supplier.name,
|
||||||
|
tradeName: supplier.tradeName,
|
||||||
|
tin: supplier.tin,
|
||||||
|
|
||||||
|
city: supplier.city,
|
||||||
|
province: supplier.province,
|
||||||
|
country: supplier.country,
|
||||||
|
|
||||||
|
primaryEmail: supplier.primaryEmail,
|
||||||
|
primaryPhone: supplier.primaryPhone,
|
||||||
|
primaryMobile: supplier.primaryMobile,
|
||||||
|
|
||||||
|
languageCode: supplier.languageCode,
|
||||||
|
currencyCode: supplier.currencyCode,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
24
modules/supplier/src/web/shared/api/create-supplier.api.ts
Normal file
24
modules/supplier/src/web/shared/api/create-supplier.api.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { CreateSupplierRequestDTO, SupplierCreationResponseDTO } from "../../../common";
|
||||||
|
|
||||||
|
export type SupplierCreateInput = CreateSupplierRequestDTO;
|
||||||
|
export type SupplierCreateOutput = SupplierCreationResponseDTO;
|
||||||
|
|
||||||
|
export async function createSupplier(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
id: string,
|
||||||
|
data: SupplierCreateInput
|
||||||
|
) {
|
||||||
|
if (!id) throw new Error("supplierId is required");
|
||||||
|
|
||||||
|
const response = await dataSource.createOne<SupplierCreateInput, SupplierCreateOutput>(
|
||||||
|
"suppliers",
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { DeleteSupplierByIdRequestDTO } from "../../../common";
|
||||||
|
|
||||||
|
export type SupplierDeleteInput = DeleteSupplierByIdRequestDTO;
|
||||||
|
|
||||||
|
export async function deleteSupplierById(dataSource: IDataSource, id: string, signal: AbortSignal) {
|
||||||
|
const response = await dataSource.deleteOne<SupplierDeleteInput>("suppliers", id, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { GetSupplierByIdResponseDTO } from "../../../common";
|
||||||
|
|
||||||
|
export type SupplierGetOutput = GetSupplierByIdResponseDTO;
|
||||||
|
|
||||||
|
export async function getSupplierById(dataSource: IDataSource, id: string, signal: AbortSignal) {
|
||||||
|
const response = dataSource.getOne<SupplierGetOutput>("suppliers", id, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
5
modules/supplier/src/web/shared/api/index.ts
Normal file
5
modules/supplier/src/web/shared/api/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./create-supplier.api";
|
||||||
|
export * from "./delete-supplier-by-id.api";
|
||||||
|
export * from "./get-supplier-by-id.api";
|
||||||
|
export * from "./list-suppliers.api";
|
||||||
|
export * from "./update-supplier-by-id.api";
|
||||||
19
modules/supplier/src/web/shared/api/list-suppliers.api.ts
Normal file
19
modules/supplier/src/web/shared/api/list-suppliers.api.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { ListSuppliersResponseDTO } from "../../../common";
|
||||||
|
|
||||||
|
export type SupplierListOutput = ListSuppliersResponseDTO;
|
||||||
|
|
||||||
|
export async function getListSuppliers(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
criteria: CriteriaDTO,
|
||||||
|
signal: AbortSignal
|
||||||
|
) {
|
||||||
|
const response = dataSource.getList<SupplierListOutput>("suppliers", {
|
||||||
|
signal,
|
||||||
|
...criteria,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { UpdateSupplierByIdRequestDTO, UpdateSupplierByIdResponseDTO } from "../../../common";
|
||||||
|
|
||||||
|
export type SupplierUpdateInput = UpdateSupplierByIdRequestDTO;
|
||||||
|
export type SupplierUpdateOutput = UpdateSupplierByIdResponseDTO;
|
||||||
|
|
||||||
|
export async function updateSupplierById(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
id: string,
|
||||||
|
data: SupplierUpdateInput
|
||||||
|
) {
|
||||||
|
if (!id) throw new Error("supplierId is required");
|
||||||
|
|
||||||
|
const response = dataSource.updateOne<SupplierUpdateInput, SupplierUpdateOutput>(
|
||||||
|
"suppliers",
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
0
modules/supplier/src/web/shared/constants/index.ts
Normal file
0
modules/supplier/src/web/shared/constants/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export const COUNTRY_OPTIONS = [
|
||||||
|
{ value: "es", label: "España" },
|
||||||
|
{ value: "fr", label: "Francia" },
|
||||||
|
{ value: "de", label: "Alemania" },
|
||||||
|
{ value: "it", label: "Italia" },
|
||||||
|
{ value: "pt", label: "Portugal" },
|
||||||
|
{ value: "us", label: "Estados Unidos" },
|
||||||
|
{ value: "mx", label: "México" },
|
||||||
|
{ value: "ar", label: "Argentina" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const LANGUAGE_OPTIONS = [
|
||||||
|
{ value: "es", label: "Español" },
|
||||||
|
{ value: "en", label: "Inglés" },
|
||||||
|
{ value: "fr", label: "Francés" },
|
||||||
|
{ value: "de", label: "Alemán" },
|
||||||
|
{ value: "it", label: "Italiano" },
|
||||||
|
{ value: "pt", label: "Portugués" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const CURRENCY_OPTIONS = [
|
||||||
|
{ value: "EUR", label: "Euro" },
|
||||||
|
{ value: "USD", label: "Dólar estadounidense" },
|
||||||
|
{ value: "GBP", label: "Libra esterlina" },
|
||||||
|
{ value: "ARS", label: "Peso argentino" },
|
||||||
|
{ value: "MXN", label: "Peso mexicano" },
|
||||||
|
{ value: "JPY", label: "Yen japonés" },
|
||||||
|
] as const;
|
||||||
3
modules/supplier/src/web/shared/entities/index.ts
Normal file
3
modules/supplier/src/web/shared/entities/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./supplier.entity";
|
||||||
|
export * from "./supplier-list.entity";
|
||||||
|
export * from "./supplier-list-row.entity";
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
export interface SupplierListRow {
|
||||||
|
id: string;
|
||||||
|
companyId: string;
|
||||||
|
status: string;
|
||||||
|
reference: string;
|
||||||
|
|
||||||
|
isCompany: boolean;
|
||||||
|
name: string;
|
||||||
|
tradeName: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
street2: string;
|
||||||
|
city: string;
|
||||||
|
province: string;
|
||||||
|
postalCode: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
primaryEmail: string;
|
||||||
|
secondaryEmail: string;
|
||||||
|
primaryPhone: string;
|
||||||
|
secondaryPhone: string;
|
||||||
|
primaryMobile: string;
|
||||||
|
secondaryMobile: string;
|
||||||
|
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import type { SupplierListRow } from "./supplier-list-row.entity";
|
||||||
|
|
||||||
|
export interface SupplierList {
|
||||||
|
items: SupplierListRow[];
|
||||||
|
total_pages: number;
|
||||||
|
total_items: number;
|
||||||
|
page: number;
|
||||||
|
per_page: number;
|
||||||
|
}
|
||||||
31
modules/supplier/src/web/shared/entities/supplier.entity.ts
Normal file
31
modules/supplier/src/web/shared/entities/supplier.entity.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export interface Supplier {
|
||||||
|
id: string;
|
||||||
|
companyId: string;
|
||||||
|
status: string;
|
||||||
|
reference: string;
|
||||||
|
|
||||||
|
isCompany: boolean;
|
||||||
|
name: string;
|
||||||
|
tradeName: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
street2: string;
|
||||||
|
city: string;
|
||||||
|
province: string;
|
||||||
|
postalCode: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
primaryEmail: string;
|
||||||
|
secondaryEmail: string;
|
||||||
|
primaryPhone: string;
|
||||||
|
secondaryPhone: string;
|
||||||
|
primaryMobile: string;
|
||||||
|
secondaryMobile: string;
|
||||||
|
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
8
modules/supplier/src/web/shared/hooks/index.ts
Normal file
8
modules/supplier/src/web/shared/hooks/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * from "./keys";
|
||||||
|
export * from "./supplier-cache-strategy";
|
||||||
|
export * from "./to-validation-errors";
|
||||||
|
export * from "./use-list-suppliers-query";
|
||||||
|
export * from "./use-supplier-create-mutation";
|
||||||
|
export * from "./use-supplier-delete-mutation";
|
||||||
|
export * from "./use-supplier-get-query";
|
||||||
|
export * from "./use-supplier-update-mutation";
|
||||||
29
modules/supplier/src/web/shared/hooks/keys.ts
Normal file
29
modules/supplier/src/web/shared/hooks/keys.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
|
||||||
|
import type { QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export const LIST_SUPPLIERS_QUERY_KEY_PREFIX = ["suppliers"] as const;
|
||||||
|
|
||||||
|
export const LIST_SUPPLIERS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey =>
|
||||||
|
[
|
||||||
|
...LIST_SUPPLIERS_QUERY_KEY_PREFIX,
|
||||||
|
{
|
||||||
|
pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX,
|
||||||
|
pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE,
|
||||||
|
q: criteria?.q ?? "",
|
||||||
|
filters: criteria?.filters ?? [],
|
||||||
|
orderBy: criteria?.orderBy ?? "",
|
||||||
|
order: criteria?.order ?? "",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const SUPPLIER_QUERY_KEY = (supplierId?: string): QueryKey => [
|
||||||
|
"suppliers:detail",
|
||||||
|
{
|
||||||
|
supplierId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SUPPLIER_CREATE_KEY = ["suppliers", "create"] as const;
|
||||||
|
export const SUPPLIER_UPDATE_KEY = ["suppliers", "update"] as const;
|
||||||
|
export const SUPPLIER_DELETE_KEY = ["suppliers", "delete"] as const;
|
||||||
149
modules/supplier/src/web/shared/hooks/supplier-cache-strategy.ts
Normal file
149
modules/supplier/src/web/shared/hooks/supplier-cache-strategy.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import type { QueryClient, QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { SupplierToListRowPatchAdapter } from "../adapters";
|
||||||
|
import type { Supplier, SupplierList, SupplierListRow } from "../entities";
|
||||||
|
|
||||||
|
import { LIST_SUPPLIERS_QUERY_KEY_PREFIX, SUPPLIER_QUERY_KEY } from "./keys";
|
||||||
|
|
||||||
|
export interface SupplierListCacheSnapshot {
|
||||||
|
key: QueryKey;
|
||||||
|
page?: SupplierList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteSupplierCacheContext {
|
||||||
|
snapshots: SupplierListCacheSnapshot[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelSupplierListQueries(queryClient: QueryClient) {
|
||||||
|
return queryClient.cancelQueries({
|
||||||
|
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateSupplierListQueries(queryClient: QueryClient) {
|
||||||
|
return queryClient.invalidateQueries({
|
||||||
|
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateSupplierDetailQuery(queryClient: QueryClient, supplierId: string) {
|
||||||
|
return queryClient.invalidateQueries({
|
||||||
|
queryKey: SUPPLIER_QUERY_KEY(supplierId),
|
||||||
|
exact: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSupplierDetailCache(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
supplierId: string,
|
||||||
|
supplier: Supplier
|
||||||
|
) {
|
||||||
|
queryClient.setQueryData<Supplier>(SUPPLIER_QUERY_KEY(supplierId), supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeSupplierDetailCache(queryClient: QueryClient, supplierId: string) {
|
||||||
|
queryClient.removeQueries({
|
||||||
|
queryKey: SUPPLIER_QUERY_KEY(supplierId),
|
||||||
|
exact: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllSupplierListQueryKeys(queryClient: QueryClient): QueryKey[] {
|
||||||
|
const entries = queryClient.getQueriesData<SupplierList>({
|
||||||
|
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
|
||||||
|
});
|
||||||
|
|
||||||
|
return entries.map(([key]) => key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function upsertSupplierInListCaches(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
supplier: Pick<SupplierListRow, "id"> & Partial<SupplierListRow>
|
||||||
|
) {
|
||||||
|
const keys = getAllSupplierListQueryKeys(queryClient);
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
const page = queryClient.getQueryData<SupplierList>(key);
|
||||||
|
if (!page) continue;
|
||||||
|
|
||||||
|
const index = page.items.findIndex((row) => row.id === supplier.id);
|
||||||
|
if (index === -1) continue;
|
||||||
|
|
||||||
|
const nextItems = page.items.slice();
|
||||||
|
nextItems[index] = {
|
||||||
|
...page.items[index],
|
||||||
|
...supplier,
|
||||||
|
};
|
||||||
|
|
||||||
|
queryClient.setQueryData<SupplierList>(key, {
|
||||||
|
...page,
|
||||||
|
items: nextItems,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeSupplierFromListCaches(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
supplierId: string
|
||||||
|
): SupplierListCacheSnapshot[] {
|
||||||
|
const snapshots = getAllSupplierListQueryKeys(queryClient).map((key) => ({
|
||||||
|
key,
|
||||||
|
page: queryClient.getQueryData<SupplierList>(key),
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const { key, page } of snapshots) {
|
||||||
|
if (!page) continue;
|
||||||
|
|
||||||
|
queryClient.setQueryData<SupplierList>(key, {
|
||||||
|
...page,
|
||||||
|
items: page.items.filter((row) => row.id !== supplierId),
|
||||||
|
total_items: Math.max(0, page.total_items - 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restoreSupplierListCaches(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
snapshots: SupplierListCacheSnapshot[]
|
||||||
|
) {
|
||||||
|
for (const snapshot of snapshots) {
|
||||||
|
queryClient.setQueryData(snapshot.key, snapshot.page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncCreatedSupplierCaches(queryClient: QueryClient, supplier: Supplier) {
|
||||||
|
setSupplierDetailCache(queryClient, supplier.id, supplier);
|
||||||
|
return invalidateSupplierListQueries(queryClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncUpdatedSupplierCaches(queryClient: QueryClient, supplier: Supplier) {
|
||||||
|
setSupplierDetailCache(queryClient, supplier.id, supplier);
|
||||||
|
upsertSupplierInListCaches(queryClient, SupplierToListRowPatchAdapter.fromSupplier(supplier));
|
||||||
|
|
||||||
|
return invalidateSupplierListQueries(queryClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function prepareDeleteSupplierOptimisticUpdate(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
supplierId: string
|
||||||
|
): Promise<DeleteSupplierCacheContext> {
|
||||||
|
await cancelSupplierListQueries(queryClient);
|
||||||
|
const snapshots = removeSupplierFromListCaches(queryClient, supplierId);
|
||||||
|
|
||||||
|
return { snapshots };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rollbackDeleteSupplierOptimisticUpdate(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
context?: DeleteSupplierCacheContext
|
||||||
|
) {
|
||||||
|
if (!context) return;
|
||||||
|
restoreSupplierListCaches(queryClient, context.snapshots);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function finalizeDeletedSupplierCaches(queryClient: QueryClient, supplierId: string) {
|
||||||
|
removeSupplierDetailCache(queryClient, supplierId);
|
||||||
|
return invalidateSupplierListQueries(queryClient);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { ZodError } from "zod";
|
||||||
|
|
||||||
|
// Helpers de validación a errores de dominio
|
||||||
|
|
||||||
|
export function toValidationErrors(error: ZodError<unknown>) {
|
||||||
|
return error.issues.map((err) => ({
|
||||||
|
field: err.path.join("."),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ListSuppliersAdapter } from "../adapters";
|
||||||
|
import { getListSuppliers } from "../api";
|
||||||
|
import type { SupplierList } from "../entities";
|
||||||
|
|
||||||
|
import { LIST_SUPPLIERS_QUERY_KEY } from "./keys";
|
||||||
|
|
||||||
|
type ListSuppliersQueryOptions = {
|
||||||
|
enabled?: boolean;
|
||||||
|
criteria?: Partial<CriteriaDTO>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useListSuppliersQuery = (
|
||||||
|
options?: ListSuppliersQueryOptions
|
||||||
|
): UseQueryResult<SupplierList, DefaultError> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const enabled = options?.enabled ?? true;
|
||||||
|
const criteria = options?.criteria ?? {};
|
||||||
|
|
||||||
|
return useQuery<SupplierList, DefaultError>({
|
||||||
|
queryKey: LIST_SUPPLIERS_QUERY_KEY(criteria as CriteriaDTO),
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const dto = await getListSuppliers(dataSource, criteria as CriteriaDTO, signal);
|
||||||
|
return ListSuppliersAdapter.fromDTO(dto);
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { CreateSupplierRequestSchema } from "../../../common";
|
||||||
|
import { GetSupplierByIdAdapter } from "../adapters";
|
||||||
|
import { type SupplierCreateInput, createSupplier } from "../api";
|
||||||
|
import type { Supplier } from "../entities";
|
||||||
|
|
||||||
|
import { SUPPLIER_CREATE_KEY } from "./keys";
|
||||||
|
import {
|
||||||
|
invalidateSupplierListQueries,
|
||||||
|
syncCreatedSupplierCaches,
|
||||||
|
} from "./supplier-cache-strategy";
|
||||||
|
import { toValidationErrors } from "./to-validation-errors";
|
||||||
|
|
||||||
|
type CreateSupplierContext = {};
|
||||||
|
|
||||||
|
type CreateSupplierPayload = {
|
||||||
|
data: SupplierCreateInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSupplierCreateMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const schema = CreateSupplierRequestSchema;
|
||||||
|
|
||||||
|
return useMutation<Supplier, DefaultError, CreateSupplierPayload, CreateSupplierContext>({
|
||||||
|
mutationKey: SUPPLIER_CREATE_KEY,
|
||||||
|
|
||||||
|
mutationFn: async ({ data }) => {
|
||||||
|
const id = UniqueID.generateNewID().toString();
|
||||||
|
|
||||||
|
const result = schema.safeParse(data);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = await createSupplier(dataSource, id, data);
|
||||||
|
return GetSupplierByIdAdapter.fromDTO(dto);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: (createdSupplier) => {
|
||||||
|
syncCreatedSupplierCaches(queryClient, createdSupplier);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettled: async () => {
|
||||||
|
await invalidateSupplierListQueries(queryClient);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { deleteSupplierById } from "../api";
|
||||||
|
|
||||||
|
import { SUPPLIER_DELETE_KEY } from "./keys";
|
||||||
|
import {
|
||||||
|
type DeleteSupplierCacheContext,
|
||||||
|
finalizeDeletedSupplierCaches,
|
||||||
|
invalidateSupplierListQueries,
|
||||||
|
prepareDeleteSupplierOptimisticUpdate,
|
||||||
|
rollbackDeleteSupplierOptimisticUpdate,
|
||||||
|
} from "./supplier-cache-strategy";
|
||||||
|
|
||||||
|
export interface DeleteSupplierPayload {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSupplierContext extends DeleteSupplierCacheContext {}
|
||||||
|
|
||||||
|
export const useSupplierDeleteMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
|
return useMutation<{ id: string }, DefaultError, DeleteSupplierPayload, DeleteSupplierContext>({
|
||||||
|
mutationKey: SUPPLIER_DELETE_KEY,
|
||||||
|
|
||||||
|
mutationFn: async ({ id }) => {
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("supplierId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteSupplierById(dataSource, id, new AbortController().signal);
|
||||||
|
return { id };
|
||||||
|
},
|
||||||
|
|
||||||
|
onMutate: async ({ id }) => {
|
||||||
|
return prepareDeleteSupplierOptimisticUpdate(queryClient, id);
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: (_error, _variables, context) => {
|
||||||
|
rollbackDeleteSupplierOptimisticUpdate(queryClient, context);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: ({ id }) => {
|
||||||
|
finalizeDeletedSupplierCaches(queryClient, id);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettled: async () => {
|
||||||
|
await invalidateSupplierListQueries(queryClient);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { GetSupplierByIdAdapter } from "../adapters";
|
||||||
|
import { getSupplierById } from "../api";
|
||||||
|
import type { Supplier } from "../entities";
|
||||||
|
|
||||||
|
import { SUPPLIER_QUERY_KEY } from "./keys";
|
||||||
|
|
||||||
|
type SupplierGetQueryOptions = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSupplierGetQuery = (
|
||||||
|
supplierId?: string,
|
||||||
|
options?: SupplierGetQueryOptions
|
||||||
|
): UseQueryResult<Supplier, DefaultError> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const enabled = options?.enabled ?? Boolean(supplierId);
|
||||||
|
|
||||||
|
return useQuery<Supplier, DefaultError>({
|
||||||
|
queryKey: SUPPLIER_QUERY_KEY(supplierId),
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const dto = await getSupplierById(dataSource, String(supplierId), signal);
|
||||||
|
return GetSupplierByIdAdapter.fromDTO(dto);
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { UpdateSupplierByIdRequestSchema } from "../../../common";
|
||||||
|
import { GetSupplierByIdAdapter } from "../adapters";
|
||||||
|
import { type SupplierUpdateInput, updateSupplierById } from "../api";
|
||||||
|
import type { Supplier } from "../entities";
|
||||||
|
|
||||||
|
import { SUPPLIER_UPDATE_KEY } from "./keys";
|
||||||
|
import {
|
||||||
|
invalidateSupplierListQueries,
|
||||||
|
syncUpdatedSupplierCaches,
|
||||||
|
} from "./supplier-cache-strategy";
|
||||||
|
import { toValidationErrors } from "./to-validation-errors";
|
||||||
|
|
||||||
|
type UpdateSupplierContext = {};
|
||||||
|
|
||||||
|
type UpdateSupplierPayload = {
|
||||||
|
id: string;
|
||||||
|
data: SupplierUpdateInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSupplierUpdateMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const schema = UpdateSupplierByIdRequestSchema;
|
||||||
|
|
||||||
|
return useMutation<Supplier, DefaultError, UpdateSupplierPayload, UpdateSupplierContext>({
|
||||||
|
mutationKey: SUPPLIER_UPDATE_KEY,
|
||||||
|
|
||||||
|
mutationFn: async ({ id, data }) => {
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("supplierId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = schema.safeParse(data);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = await updateSupplierById(dataSource, id, data);
|
||||||
|
return GetSupplierByIdAdapter.fromDTO(dto);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: (updatedSupplier) => {
|
||||||
|
syncUpdatedSupplierCaches(queryClient, updatedSupplier);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettled: async () => {
|
||||||
|
await invalidateSupplierListQueries(queryClient);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
4
modules/supplier/src/web/shared/index.ts
Normal file
4
modules/supplier/src/web/shared/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./constants";
|
||||||
|
export * from "./entities";
|
||||||
|
export * from "./hooks";
|
||||||
@ -702,6 +702,9 @@ importers:
|
|||||||
'@erp/core':
|
'@erp/core':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
'@hookform/resolvers':
|
||||||
|
specifier: ^5.0.1
|
||||||
|
version: 5.2.2(react-hook-form@7.72.1(react@19.2.0))
|
||||||
'@repo/i18next':
|
'@repo/i18next':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/i18n
|
version: link:../../packages/i18n
|
||||||
@ -714,15 +717,45 @@ importers:
|
|||||||
'@repo/rdx-logger':
|
'@repo/rdx-logger':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-logger
|
version: link:../../packages/rdx-logger
|
||||||
|
'@repo/rdx-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-ui
|
||||||
'@repo/rdx-utils':
|
'@repo/rdx-utils':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-utils
|
version: link:../../packages/rdx-utils
|
||||||
|
'@repo/shadcn-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/shadcn-ui
|
||||||
|
'@tanstack/react-query':
|
||||||
|
specifier: ^5.90.6
|
||||||
|
version: 5.90.6(react@19.2.0)
|
||||||
|
'@tanstack/react-table':
|
||||||
|
specifier: ^8.21.3
|
||||||
|
version: 8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
express:
|
express:
|
||||||
specifier: ^4.18.2
|
specifier: ^4.18.2
|
||||||
version: 4.21.2
|
version: 4.21.2
|
||||||
|
lucide-react:
|
||||||
|
specifier: ^0.577.0
|
||||||
|
version: 0.577.0(react@19.2.0)
|
||||||
|
react-data-table-component:
|
||||||
|
specifier: ^7.7.0
|
||||||
|
version: 7.7.0(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0))
|
||||||
|
react-hook-form:
|
||||||
|
specifier: ^7.72.1
|
||||||
|
version: 7.72.1(react@19.2.0)
|
||||||
|
react-i18next:
|
||||||
|
specifier: ^16.2.4
|
||||||
|
version: 16.2.4(i18next@25.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||||
|
react-router-dom:
|
||||||
|
specifier: ^6.26.0
|
||||||
|
version: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
sequelize:
|
sequelize:
|
||||||
specifier: ^6.37.8
|
specifier: ^6.37.8
|
||||||
version: 6.37.8(mysql2@3.15.3)(pg-hstore@2.3.4)
|
version: 6.37.8(mysql2@3.15.3)(pg-hstore@2.3.4)
|
||||||
|
use-debounce:
|
||||||
|
specifier: ^10.0.5
|
||||||
|
version: 10.0.6(react@19.2.0)
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.1.11
|
specifier: ^4.1.11
|
||||||
version: 4.1.12
|
version: 4.1.12
|
||||||
@ -730,6 +763,18 @@ importers:
|
|||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.25
|
version: 4.17.25
|
||||||
|
'@types/react':
|
||||||
|
specifier: ^19.1.2
|
||||||
|
version: 19.2.2
|
||||||
|
'@types/react-router-dom':
|
||||||
|
specifier: ^5.3.3
|
||||||
|
version: 5.3.3
|
||||||
|
react:
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.0
|
||||||
|
react-dom:
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.0(react@19.2.0)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user