.
This commit is contained in:
parent
05f4b1337b
commit
a464f9b14e
@ -1,8 +1,9 @@
|
|||||||
import { type StartParams } from "@erp/core/api";
|
import type { StartParams } from "@erp/core/api";
|
||||||
import { requireIdentityTenant } from "@erp/identity/api";
|
import { requireIdentityTenant } from "@erp/identity/api";
|
||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import type { InvoiceSeriesInternalDeps } from "../di/invoice-series.di";
|
import type { InvoiceSeriesInternalDeps } from "../di/invoice-series.di";
|
||||||
|
|
||||||
import { ListInvoiceSeriesController } from "./controllers";
|
import { ListInvoiceSeriesController } from "./controllers";
|
||||||
|
|
||||||
export const invoiceSeriesRouter = (params: StartParams) => {
|
export const invoiceSeriesRouter = (params: StartParams) => {
|
||||||
@ -20,5 +21,5 @@ export const invoiceSeriesRouter = (params: StartParams) => {
|
|||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(`${config.server.apiBasePath}/invoice-series`, router);
|
app.use(`${config.server.apiBasePath}/catalogs/invoice-series`, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import {
|
import { SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
||||||
SequelizeRepository,
|
|
||||||
translateSequelizeError,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { Sequelize, Transaction } from "sequelize";
|
import type { Sequelize, Transaction } from "sequelize";
|
||||||
@ -11,7 +8,7 @@ import {
|
|||||||
InvoiceSeriesConcurrencyError,
|
InvoiceSeriesConcurrencyError,
|
||||||
InvoiceSeriesLockRequiredError,
|
InvoiceSeriesLockRequiredError,
|
||||||
InvoiceSeriesTransactionRequiredError,
|
InvoiceSeriesTransactionRequiredError,
|
||||||
} from "../../../../../application/invoice-series";
|
} from "../../../../../application";
|
||||||
import type { InvoiceSeries, InvoiceSeriesCode } from "../../../../../domain";
|
import type { InvoiceSeries, InvoiceSeriesCode } from "../../../../../domain";
|
||||||
import { CustomerInvoiceSeriesModel } from "../../../../common";
|
import { CustomerInvoiceSeriesModel } from "../../../../common";
|
||||||
import type { InvoiceSeriesDomainMapper } from "../mappers";
|
import type { InvoiceSeriesDomainMapper } from "../mappers";
|
||||||
@ -27,10 +24,7 @@ export class SequelizeInvoiceSeriesRepository
|
|||||||
super({ database });
|
super({ database });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findActiveByCompany(params: {
|
public async findActiveByCompany(params: { companyId: UniqueID; transaction?: unknown }) {
|
||||||
companyId: UniqueID;
|
|
||||||
transaction?: unknown;
|
|
||||||
}) {
|
|
||||||
try {
|
try {
|
||||||
const rows = await CustomerInvoiceSeriesModel.findAll({
|
const rows = await CustomerInvoiceSeriesModel.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@ -16,10 +16,14 @@ import { useEffect, useId, useMemo, useState } from "react";
|
|||||||
import type { FieldErrors } from "react-hook-form";
|
import type { FieldErrors } from "react-hook-form";
|
||||||
|
|
||||||
import type { Proforma } from "../../shared";
|
import type { Proforma } from "../../shared";
|
||||||
import { useProformaCreateMutation, useProformasListQuery } from "../../shared/hooks";
|
import {
|
||||||
|
buildInvoiceSeriesSelectItems,
|
||||||
|
useInvoiceSeriesQuery,
|
||||||
|
useProformaCreateMutation,
|
||||||
|
} from "../../shared";
|
||||||
import { buildCreateProformaParams } from "../adapters";
|
import { buildCreateProformaParams } from "../adapters";
|
||||||
import { type ProformaCreateForm, ProformaCreateFormSchema } from "../entities";
|
import { type ProformaCreateForm, ProformaCreateFormSchema } from "../entities";
|
||||||
import { buildProformaCreateDefault, buildProformaSeriesOptions } from "../utils";
|
import { buildProformaCreateDefault } from "../utils";
|
||||||
|
|
||||||
export interface UseCreateProformaControllerOptions {
|
export interface UseCreateProformaControllerOptions {
|
||||||
onCreated?(proforma: Proforma): void;
|
onCreated?(proforma: Proforma): void;
|
||||||
@ -48,12 +52,7 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
|
|||||||
error: createError,
|
error: createError,
|
||||||
} = useProformaCreateMutation();
|
} = useProformaCreateMutation();
|
||||||
|
|
||||||
const seriesQuery = useProformasListQuery({
|
const seriesQuery = useInvoiceSeriesQuery();
|
||||||
criteria: {
|
|
||||||
pageNumber: 1,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialValues = useMemo<ProformaCreateForm>(() => buildProformaCreateDefault(), []);
|
const initialValues = useMemo<ProformaCreateForm>(() => buildProformaCreateDefault(), []);
|
||||||
|
|
||||||
@ -64,20 +63,28 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
|
|||||||
});
|
});
|
||||||
|
|
||||||
const seriesOptions = useMemo(
|
const seriesOptions = useMemo(
|
||||||
() => buildProformaSeriesOptions(seriesQuery.data),
|
() => buildInvoiceSeriesSelectItems(seriesQuery.data ?? []),
|
||||||
[seriesQuery.data]
|
[seriesQuery.data]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const defaultSeries = useMemo(() => {
|
||||||
if (seriesOptions.length !== 1) return;
|
const invoiceSeries = seriesQuery.data ?? [];
|
||||||
if (form.getValues("series")) return;
|
|
||||||
|
|
||||||
form.setValue("series", seriesOptions[0].value, {
|
return (
|
||||||
|
invoiceSeries.find((series) => series.isDefault)?.code ?? invoiceSeries[0]?.code ?? ""
|
||||||
|
);
|
||||||
|
}, [seriesQuery.data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (form.getValues("series")) return;
|
||||||
|
if (!defaultSeries) return;
|
||||||
|
|
||||||
|
form.setValue("series", defaultSeries, {
|
||||||
shouldDirty: false,
|
shouldDirty: false,
|
||||||
shouldTouch: false,
|
shouldTouch: false,
|
||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
});
|
});
|
||||||
}, [form, seriesOptions]);
|
}, [defaultSeries, form]);
|
||||||
|
|
||||||
const setCustomer = (customer: CustomerSelectionOption) => {
|
const setCustomer = (customer: CustomerSelectionOption) => {
|
||||||
setSelectedCustomer(customer);
|
setSelectedCustomer(customer);
|
||||||
@ -163,8 +170,8 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
|
|||||||
setCustomer,
|
setCustomer,
|
||||||
clearCustomer,
|
clearCustomer,
|
||||||
seriesOptions,
|
seriesOptions,
|
||||||
isLoading: seriesQuery.isLoading,
|
isSeriesLoading: seriesQuery.isLoading,
|
||||||
isLoadError: seriesQuery.isError,
|
isSeriesLoadError: seriesQuery.isError,
|
||||||
loadError: seriesQuery.error,
|
seriesLoadError: seriesQuery.error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { ProformaCreateInitialInfoCard } from "./proforma-create-initial-info-ca
|
|||||||
interface ProformaCreateFormProps {
|
interface ProformaCreateFormProps {
|
||||||
formId: string;
|
formId: string;
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
|
isSeriesLoading?: boolean;
|
||||||
onSubmit: React.SubmitEventHandler<HTMLFormElement>;
|
onSubmit: React.SubmitEventHandler<HTMLFormElement>;
|
||||||
selectedCustomer?: CustomerSelectionOption | null;
|
selectedCustomer?: CustomerSelectionOption | null;
|
||||||
onSelectCustomer: () => void;
|
onSelectCustomer: () => void;
|
||||||
@ -20,6 +21,7 @@ interface ProformaCreateFormProps {
|
|||||||
export const ProformaCreateForm = ({
|
export const ProformaCreateForm = ({
|
||||||
formId,
|
formId,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
isSeriesLoading = false,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
selectedCustomer,
|
selectedCustomer,
|
||||||
onSelectCustomer,
|
onSelectCustomer,
|
||||||
@ -33,6 +35,7 @@ export const ProformaCreateForm = ({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<ProformaCreateInitialInfoCard
|
<ProformaCreateInitialInfoCard
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
isSeriesLoading={isSeriesLoading}
|
||||||
onClearCustomer={onClearCustomer}
|
onClearCustomer={onClearCustomer}
|
||||||
onSelectCustomer={onSelectCustomer}
|
onSelectCustomer={onSelectCustomer}
|
||||||
selectedCustomer={selectedCustomer}
|
selectedCustomer={selectedCustomer}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { DatePickerField, FormSectionCard, FormSectionGrid, SelectField } from "@repo/rdx-ui/components";
|
import {
|
||||||
|
DatePickerField,
|
||||||
|
FormSectionCard,
|
||||||
|
FormSectionGrid,
|
||||||
|
SelectField,
|
||||||
|
type SelectFieldItem,
|
||||||
|
} from "@repo/rdx-ui/components";
|
||||||
import { FileTextIcon } from "lucide-react";
|
import { FileTextIcon } from "lucide-react";
|
||||||
|
|
||||||
import type { CustomerSelectionOption } from "@erp/customers";
|
import type { CustomerSelectionOption } from "@erp/customers";
|
||||||
@ -8,14 +14,16 @@ import { ProformaCreateCustomerField } from "./proforma-create-customer-field";
|
|||||||
|
|
||||||
interface ProformaCreateInitialInfoCardProps {
|
interface ProformaCreateInitialInfoCardProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
isSeriesLoading?: boolean;
|
||||||
selectedCustomer?: CustomerSelectionOption | null;
|
selectedCustomer?: CustomerSelectionOption | null;
|
||||||
onSelectCustomer: () => void;
|
onSelectCustomer: () => void;
|
||||||
onClearCustomer: () => void;
|
onClearCustomer: () => void;
|
||||||
seriesOptions: { value: string; label: string }[];
|
seriesOptions: SelectFieldItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProformaCreateInitialInfoCard = ({
|
export const ProformaCreateInitialInfoCard = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
isSeriesLoading = false,
|
||||||
selectedCustomer,
|
selectedCustomer,
|
||||||
onSelectCustomer,
|
onSelectCustomer,
|
||||||
onClearCustomer,
|
onClearCustomer,
|
||||||
@ -44,11 +52,17 @@ export const ProformaCreateInitialInfoCard = ({
|
|||||||
|
|
||||||
<SelectField<ProformaCreateForm>
|
<SelectField<ProformaCreateForm>
|
||||||
className="md:col-span-4"
|
className="md:col-span-4"
|
||||||
disabled={disabled}
|
disabled={disabled || isSeriesLoading}
|
||||||
items={seriesOptions}
|
items={seriesOptions}
|
||||||
label="Serie"
|
label="Serie"
|
||||||
name="series"
|
name="series"
|
||||||
placeholder={seriesOptions.length > 0 ? "Selecciona una serie" : "Sin serie"}
|
placeholder={
|
||||||
|
isSeriesLoading
|
||||||
|
? "Cargando series..."
|
||||||
|
: seriesOptions.length > 0
|
||||||
|
? "Selecciona una serie"
|
||||||
|
: "Sin serie"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormSectionGrid>
|
</FormSectionGrid>
|
||||||
</FormSectionCard>
|
</FormSectionCard>
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { ErrorAlert } from "@erp/core/components";
|
import { ErrorAlert } from "@erp/core/components";
|
||||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||||
import { SelectCustomerDialog } from "@erp/customers";
|
import { SelectCustomerDialog } from "@erp/customers";
|
||||||
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
|
||||||
import { FormProvider } from "react-hook-form";
|
import { FormProvider } from "react-hook-form";
|
||||||
|
|
||||||
import { ProformaInfoAlert } from "../../../update/ui/components";
|
import { ProformaInfoAlert } from "../../../update/ui/components";
|
||||||
import { useCreateProformaPageController } from "../../controllers";
|
import { useCreateProformaPageController } from "../../controllers";
|
||||||
import { ProformaCreateForm, ProformaCreateHeader } from "../blocks";
|
import { ProformaCreateForm, ProformaCreateHeader } from "../blocks";
|
||||||
import { ProformaCreateSkeleton } from "../components";
|
|
||||||
|
|
||||||
export const ProformaCreatePage = () => {
|
export const ProformaCreatePage = () => {
|
||||||
const { createCtrl, selectCustomerCtrl, returnTo } = useCreateProformaPageController();
|
const { createCtrl, selectCustomerCtrl, returnTo } = useCreateProformaPageController();
|
||||||
@ -16,29 +14,6 @@ export const ProformaCreatePage = () => {
|
|||||||
fallbackPath: returnTo,
|
fallbackPath: returnTo,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createCtrl.isLoading) {
|
|
||||||
return <ProformaCreateSkeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createCtrl.isLoadError) {
|
|
||||||
return (
|
|
||||||
<AppContent>
|
|
||||||
<ErrorAlert
|
|
||||||
message={
|
|
||||||
createCtrl.loadError instanceof Error
|
|
||||||
? createCtrl.loadError.message
|
|
||||||
: "Inténtalo de nuevo más tarde."
|
|
||||||
}
|
|
||||||
title="No se pudo preparar la creación de la proforma"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-end">
|
|
||||||
<BackHistoryButton onClick={() => navigateBack()} />
|
|
||||||
</div>
|
|
||||||
</AppContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||||
<FormProvider {...createCtrl.form}>
|
<FormProvider {...createCtrl.form}>
|
||||||
@ -65,9 +40,21 @@ export const ProformaCreatePage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{createCtrl.isSeriesLoadError && (
|
||||||
|
<ErrorAlert
|
||||||
|
message={
|
||||||
|
createCtrl.seriesLoadError instanceof Error
|
||||||
|
? createCtrl.seriesLoadError.message
|
||||||
|
: "Inténtalo de nuevo más tarde."
|
||||||
|
}
|
||||||
|
title="No se pudieron cargar las series de facturación"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<ProformaCreateForm
|
<ProformaCreateForm
|
||||||
formId={createCtrl.formId}
|
formId={createCtrl.formId}
|
||||||
|
isSeriesLoading={createCtrl.isSeriesLoading}
|
||||||
isSubmitting={createCtrl.isCreating}
|
isSubmitting={createCtrl.isCreating}
|
||||||
onClearCustomer={createCtrl.clearCustomer}
|
onClearCustomer={createCtrl.clearCustomer}
|
||||||
onSelectCustomer={selectCustomerCtrl.selectCtrl.openDialog}
|
onSelectCustomer={selectCustomerCtrl.selectCtrl.openDialog}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export const buildProformaCreateDefault = (): ProformaCreateForm => {
|
|||||||
return {
|
return {
|
||||||
customerId: "",
|
customerId: "",
|
||||||
invoiceDate: DateHelper.buildTodayIsoDate(),
|
invoiceDate: DateHelper.buildTodayIsoDate(),
|
||||||
series: "F26",
|
series: "",
|
||||||
languageCode: "es",
|
languageCode: "es",
|
||||||
currencyCode: "EUR",
|
currencyCode: "EUR",
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
|
||||||
|
|
||||||
import type { ProformaList } from "../../shared";
|
|
||||||
|
|
||||||
export const buildProformaSeriesOptions = (proformas?: ProformaList): SelectFieldItem[] => {
|
|
||||||
const uniqueSeries = new Set<string>();
|
|
||||||
|
|
||||||
for (const item of proformas?.items ?? []) {
|
|
||||||
const value = item.series?.trim();
|
|
||||||
if (!value) continue;
|
|
||||||
uniqueSeries.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...uniqueSeries]
|
|
||||||
.sort((left, right) => left.localeCompare(right))
|
|
||||||
.map((value) => ({ value, label: value }));
|
|
||||||
};
|
|
||||||
@ -1,2 +1 @@
|
|||||||
export * from "./build-proforma-create-default";
|
export * from "./build-proforma-create-default";
|
||||||
export * from "./build-proforma-series-options";
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./get-proforma-by-id.adapter";
|
export * from "./get-proforma-by-id.adapter";
|
||||||
|
export * from "./list-invoice-series.adapter";
|
||||||
export * from "./list-proformas.adapter";
|
export * from "./list-proformas.adapter";
|
||||||
export * from "./proforma-to-list-row-patch.adapter";
|
export * from "./proforma-to-list-row-patch.adapter";
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { ListInvoiceSeriesResponseDTO } from "../../../../common";
|
||||||
|
import type { InvoiceSeriesSummary } from "../entities";
|
||||||
|
|
||||||
|
export const mapInvoiceSeriesSummaryDTOToInvoiceSeriesSummary = (
|
||||||
|
dto: ListInvoiceSeriesResponseDTO["items"][number]
|
||||||
|
): InvoiceSeriesSummary => ({
|
||||||
|
id: dto.id,
|
||||||
|
code: dto.code,
|
||||||
|
nextNumber: dto.next_number,
|
||||||
|
paddingLength: dto.padding_length,
|
||||||
|
isDefault: dto.is_default,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListInvoiceSeriesAdapter = {
|
||||||
|
fromDto(dto: ListInvoiceSeriesResponseDTO): InvoiceSeriesSummary[] {
|
||||||
|
return dto.items.map(mapInvoiceSeriesSummaryDTOToInvoiceSeriesSummary);
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -3,5 +3,6 @@ export * from "./create-proforma.api";
|
|||||||
export * from "./delete-proforma-by-id.api";
|
export * from "./delete-proforma-by-id.api";
|
||||||
export * from "./get-proforma-by-id.api";
|
export * from "./get-proforma-by-id.api";
|
||||||
export * from "./issue-proforma-by-id.api";
|
export * from "./issue-proforma-by-id.api";
|
||||||
|
export * from "./list-invoice-series.api";
|
||||||
export * from "./list-proformas-by-criteria.api";
|
export * from "./list-proformas-by-criteria.api";
|
||||||
export * from "./update-proforma-by-id.api";
|
export * from "./update-proforma-by-id.api";
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { ListInvoiceSeriesResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
export const listInvoiceSeries = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
signal?: AbortSignal
|
||||||
|
): Promise<ListInvoiceSeriesResponseDTO> => {
|
||||||
|
return dataSource.getList<ListInvoiceSeriesResponseDTO>("catalogs/invoice-series", {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./forms/proforma-item-form.entity";
|
export * from "./forms/proforma-item-form.entity";
|
||||||
|
export * from "./invoice-series-summary.entity";
|
||||||
export * from "./proforma.entity";
|
export * from "./proforma.entity";
|
||||||
export * from "./proforma-item.entity";
|
export * from "./proforma-item.entity";
|
||||||
export * from "./proforma-list.entity";
|
export * from "./proforma-list.entity";
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
export interface InvoiceSeriesSummary {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
nextNumber: number;
|
||||||
|
paddingLength: number;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./keys";
|
export * from "./keys";
|
||||||
|
export * from "./use-invoice-series-query";
|
||||||
export * from "./use-proforma-change-status-mutation";
|
export * from "./use-proforma-change-status-mutation";
|
||||||
export * from "./use-proforma-create-mutation";
|
export * from "./use-proforma-create-mutation";
|
||||||
export * from "./use-proforma-delete-mutation";
|
export * from "./use-proforma-delete-mutation";
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { ProformasListRequestDTO } from "../../../../common";
|
|||||||
* Prefijo base para listados
|
* Prefijo base para listados
|
||||||
*/
|
*/
|
||||||
export const LIST_PROFORMAS_QUERY_KEY_PREFIX = ["proformas"] as const;
|
export const LIST_PROFORMAS_QUERY_KEY_PREFIX = ["proformas"] as const;
|
||||||
|
export const INVOICE_SERIES_QUERY_KEY = ["customer-invoices", "invoice-series"] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query key para listado de proformas
|
* Query key para listado de proformas
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ListInvoiceSeriesAdapter } from "../adapters";
|
||||||
|
import { listInvoiceSeries } from "../api";
|
||||||
|
import type { InvoiceSeriesSummary } from "../entities";
|
||||||
|
|
||||||
|
import { INVOICE_SERIES_QUERY_KEY } from "./keys";
|
||||||
|
|
||||||
|
export const useInvoiceSeriesQuery = (): UseQueryResult<InvoiceSeriesSummary[], DefaultError> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
|
return useQuery<InvoiceSeriesSummary[], DefaultError>({
|
||||||
|
queryKey: INVOICE_SERIES_QUERY_KEY,
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const dto = await listInvoiceSeries(dataSource, signal);
|
||||||
|
return ListInvoiceSeriesAdapter.fromDto(dto);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||||
|
|
||||||
|
import type { InvoiceSeriesSummary } from "../entities";
|
||||||
|
|
||||||
|
export const buildInvoiceSeriesSelectItems = (
|
||||||
|
invoiceSeries: InvoiceSeriesSummary[]
|
||||||
|
): SelectFieldItem[] => {
|
||||||
|
return invoiceSeries.map((series) => ({
|
||||||
|
value: series.code,
|
||||||
|
label: series.code,
|
||||||
|
}));
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||||
|
|
||||||
|
export const ensureCurrentValueInSelectItems = (
|
||||||
|
items: SelectFieldItem[],
|
||||||
|
currentValue?: string | null
|
||||||
|
): SelectFieldItem[] => {
|
||||||
|
if (!currentValue) return items;
|
||||||
|
|
||||||
|
const exists = items.some((item) => item.value === currentValue);
|
||||||
|
if (exists) return items;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: currentValue,
|
||||||
|
label: currentValue,
|
||||||
|
},
|
||||||
|
...items,
|
||||||
|
];
|
||||||
|
};
|
||||||
@ -1,2 +1,4 @@
|
|||||||
|
export * from "./build-invoice-series-select-items";
|
||||||
export * from "./calculate-proforma-due-dates";
|
export * from "./calculate-proforma-due-dates";
|
||||||
|
export * from "./ensure-current-value-in-select-items";
|
||||||
export * from "./proforma-fiscal-options.utils";
|
export * from "./proforma-fiscal-options.utils";
|
||||||
|
|||||||
@ -15,7 +15,13 @@ import type { FieldErrors } from "react-hook-form";
|
|||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import type { UpdateProformaByIdParams } from "../../shared";
|
import type { UpdateProformaByIdParams } from "../../shared";
|
||||||
import type { Proforma } from "../../shared/entities";
|
import type { Proforma } from "../../shared/entities";
|
||||||
import { useProformaGetQuery, useProformaUpdateMutation } from "../../shared/hooks";
|
import {
|
||||||
|
buildInvoiceSeriesSelectItems,
|
||||||
|
ensureCurrentValueInSelectItems,
|
||||||
|
useInvoiceSeriesQuery,
|
||||||
|
useProformaGetQuery,
|
||||||
|
useProformaUpdateMutation,
|
||||||
|
} from "../../shared";
|
||||||
import { mapProformaToProformaUpdateForm, mapProformaToSelectedCustomer } from "../adapters";
|
import { mapProformaToProformaUpdateForm, mapProformaToSelectedCustomer } from "../adapters";
|
||||||
import { type ProformaUpdateForm, ProformaUpdateFormSchema } from "../entities";
|
import { type ProformaUpdateForm, ProformaUpdateFormSchema } from "../entities";
|
||||||
import {
|
import {
|
||||||
@ -72,6 +78,8 @@ export const useUpdateProformaController = (
|
|||||||
error: updateError,
|
error: updateError,
|
||||||
} = useProformaUpdateMutation();
|
} = useProformaUpdateMutation();
|
||||||
|
|
||||||
|
const invoiceSeriesQuery = useInvoiceSeriesQuery();
|
||||||
|
|
||||||
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
||||||
if (!proformaData) return buildProformaUpdateDefault();
|
if (!proformaData) return buildProformaUpdateDefault();
|
||||||
|
|
||||||
@ -221,7 +229,7 @@ export const useUpdateProformaController = (
|
|||||||
options?.onError?.(normalizedError, params);
|
options?.onError?.(normalizedError, params);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(errors: FieldErrors<ProformaUpdateForm>) => {
|
(_errors: FieldErrors<ProformaUpdateForm>) => {
|
||||||
focusFirstInputFormError(form);
|
focusFirstInputFormError(form);
|
||||||
|
|
||||||
showWarningToast(
|
showWarningToast(
|
||||||
@ -239,6 +247,12 @@ export const useUpdateProformaController = (
|
|||||||
|
|
||||||
const currencyCode = form.watch("currencyCode");
|
const currencyCode = form.watch("currencyCode");
|
||||||
const languageCode = form.watch("languageCode");
|
const languageCode = form.watch("languageCode");
|
||||||
|
const currentSeries = form.watch("series");
|
||||||
|
|
||||||
|
const seriesOptions = useMemo(() => {
|
||||||
|
const activeSeriesItems = buildInvoiceSeriesSelectItems(invoiceSeriesQuery.data ?? []);
|
||||||
|
return ensureCurrentValueInSelectItems(activeSeriesItems, currentSeries);
|
||||||
|
}, [currentSeries, invoiceSeriesQuery.data]);
|
||||||
|
|
||||||
const taxCtrl = useUpdateProformaTaxController({
|
const taxCtrl = useUpdateProformaTaxController({
|
||||||
form,
|
form,
|
||||||
@ -263,6 +277,10 @@ export const useUpdateProformaController = (
|
|||||||
taxCtrl,
|
taxCtrl,
|
||||||
totalsCtrl,
|
totalsCtrl,
|
||||||
paymentCtrl,
|
paymentCtrl,
|
||||||
|
seriesOptions,
|
||||||
|
isSeriesLoading: invoiceSeriesQuery.isLoading,
|
||||||
|
isSeriesLoadError: invoiceSeriesQuery.isError,
|
||||||
|
seriesLoadError: invoiceSeriesQuery.error,
|
||||||
|
|
||||||
//
|
//
|
||||||
currencyCode,
|
currencyCode,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-editor.tsx
|
// modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-editor.tsx
|
||||||
|
|
||||||
import type { CustomerSelectionOption } from "@erp/customers";
|
import type { CustomerSelectionOption } from "@erp/customers";
|
||||||
|
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||||
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ type ProformaUpdateEditorProps = {
|
|||||||
selectedCustomer?: CustomerSelectionOption | null;
|
selectedCustomer?: CustomerSelectionOption | null;
|
||||||
onChangeCustomerClick: () => void;
|
onChangeCustomerClick: () => void;
|
||||||
onCreateCustomerClick: () => void;
|
onCreateCustomerClick: () => void;
|
||||||
|
seriesOptions: SelectFieldItem[];
|
||||||
|
|
||||||
itemsCtrl: UseUpdateProformaItemsControllerResult;
|
itemsCtrl: UseUpdateProformaItemsControllerResult;
|
||||||
taxCtrl: UseUpdateProformaTaxControllerResult;
|
taxCtrl: UseUpdateProformaTaxControllerResult;
|
||||||
@ -44,6 +46,7 @@ export const ProformaUpdateEditorForm = ({
|
|||||||
selectedCustomer,
|
selectedCustomer,
|
||||||
onChangeCustomerClick,
|
onChangeCustomerClick,
|
||||||
onCreateCustomerClick,
|
onCreateCustomerClick,
|
||||||
|
seriesOptions,
|
||||||
itemsCtrl,
|
itemsCtrl,
|
||||||
taxCtrl,
|
taxCtrl,
|
||||||
totalsCtrl,
|
totalsCtrl,
|
||||||
@ -58,7 +61,11 @@ export const ProformaUpdateEditorForm = ({
|
|||||||
<div className="grid grid-cols-1 2xl:grid-cols-4 gap-6 2xl:gap-12">
|
<div className="grid grid-cols-1 2xl:grid-cols-4 gap-6 2xl:gap-12">
|
||||||
<div className="2xl:col-span-3 space-y-6 gap-6 2xl:gap-12 2xl:space-y-12">
|
<div className="2xl:col-span-3 space-y-6 gap-6 2xl:gap-12 2xl:space-y-12">
|
||||||
<div className="grid 2xl:grid-cols-3 gap-6 2xl:gap-6 space-y-12 2xl:space-y-12">
|
<div className="grid 2xl:grid-cols-3 gap-6 2xl:gap-6 space-y-12 2xl:space-y-12">
|
||||||
<ProformaUpdateHeaderEditor className="2xl:col-span-full" disabled={isSubmitting} />
|
<ProformaUpdateHeaderEditor
|
||||||
|
className="2xl:col-span-full"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
seriesOptions={seriesOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProformaUpdateItemsEditor
|
<ProformaUpdateItemsEditor
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
FormSectionCard,
|
FormSectionCard,
|
||||||
FormSectionGrid,
|
FormSectionGrid,
|
||||||
SelectField,
|
SelectField,
|
||||||
|
type SelectFieldItem,
|
||||||
TextAreaField,
|
TextAreaField,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@repo/rdx-ui/components";
|
} from "@repo/rdx-ui/components";
|
||||||
@ -12,6 +13,7 @@ import { FileTextIcon } from "lucide-react";
|
|||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
|
||||||
interface ProformaUpdateHeaderEditorProps {
|
interface ProformaUpdateHeaderEditorProps {
|
||||||
|
seriesOptions: SelectFieldItem[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
|
||||||
@ -19,6 +21,7 @@ interface ProformaUpdateHeaderEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ProformaUpdateHeaderEditor = ({
|
export const ProformaUpdateHeaderEditor = ({
|
||||||
|
seriesOptions,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
className,
|
className,
|
||||||
@ -33,15 +36,6 @@ export const ProformaUpdateHeaderEditor = ({
|
|||||||
title={t("form_groups.proformas.basic_info.title")}
|
title={t("form_groups.proformas.basic_info.title")}
|
||||||
>
|
>
|
||||||
<FormSectionGrid>
|
<FormSectionGrid>
|
||||||
<SelectField
|
|
||||||
className="md:col-span-1"
|
|
||||||
disabled={disabled}
|
|
||||||
label={t("form_fields.proformas.series.label")}
|
|
||||||
name="series"
|
|
||||||
placeholder={t("form_fields.proformas.series.placeholder")}
|
|
||||||
readOnly={readOnly}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
className="md:col-span-2"
|
className="md:col-span-2"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@ -66,6 +60,16 @@ export const ProformaUpdateHeaderEditor = ({
|
|||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectField
|
||||||
|
className="md:col-span-1"
|
||||||
|
disabled={disabled}
|
||||||
|
items={seriesOptions}
|
||||||
|
label={t("form_fields.proformas.series.label")}
|
||||||
|
name="series"
|
||||||
|
placeholder={t("form_fields.proformas.series.placeholder")}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
|
||||||
<DatePickerField
|
<DatePickerField
|
||||||
className="md:col-span-2"
|
className="md:col-span-2"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@ -99,6 +99,23 @@ export const ProformaUpdatePage = () => {
|
|||||||
title={t("pages.proformas.update.error_title", "No se pudo guardar los cambios")}
|
title={t("pages.proformas.update.error_title", "No se pudo guardar los cambios")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{updateCtrl.isSeriesLoadError && (
|
||||||
|
<ErrorAlert
|
||||||
|
message={
|
||||||
|
updateCtrl.seriesLoadError instanceof Error
|
||||||
|
? updateCtrl.seriesLoadError.message
|
||||||
|
: t(
|
||||||
|
"pages.proformas.update.series_load_error.message",
|
||||||
|
"Inténtalo de nuevo más tarde."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
title={t(
|
||||||
|
"pages.proformas.update.series_load_error.title",
|
||||||
|
"No se pudieron cargar las series de facturación"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<ProformaUpdateEditorForm
|
<ProformaUpdateEditorForm
|
||||||
formId={updateCtrl.formId}
|
formId={updateCtrl.formId}
|
||||||
@ -110,6 +127,7 @@ export const ProformaUpdatePage = () => {
|
|||||||
onSubmit={updateCtrl.onSubmit}
|
onSubmit={updateCtrl.onSubmit}
|
||||||
paymentCtrl={updateCtrl.paymentCtrl}
|
paymentCtrl={updateCtrl.paymentCtrl}
|
||||||
selectedCustomer={updateCtrl.selectedCustomer}
|
selectedCustomer={updateCtrl.selectedCustomer}
|
||||||
|
seriesOptions={updateCtrl.seriesOptions}
|
||||||
taxCtrl={updateCtrl.taxCtrl}
|
taxCtrl={updateCtrl.taxCtrl}
|
||||||
totalsCtrl={updateCtrl.totalsCtrl}
|
totalsCtrl={updateCtrl.totalsCtrl}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user