This commit is contained in:
Ahmed Bouhuolia
2026-05-27 23:28:17 +02:00
parent 4a84d5996e
commit d7d1783eee
7 changed files with 146 additions and 50 deletions

View File

@@ -21,6 +21,7 @@ import {
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto';
import { CustomerResponseDto } from './dtos/CustomerResponse.dto';
import { CustomersListResponseDto } from './dtos/CustomersListResponse.dto';
import { GetCustomersQueryDto } from './dtos/GetCustomersQuery.dto';
import {
BulkDeleteCustomersDto,
@@ -36,6 +37,7 @@ import { CustomerAction } from './types/Customers.types';
@Controller('customers')
@ApiTags('Customers')
@ApiExtraModels(CustomerResponseDto)
@ApiExtraModels(CustomersListResponseDto)
@ApiExtraModels(ValidateBulkDeleteCustomersResponseDto)
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
@@ -60,10 +62,7 @@ export class CustomersController {
@ApiResponse({
status: 200,
description: 'The customers have been successfully retrieved.',
schema: {
type: 'array',
items: { $ref: getSchemaPath(CustomerResponseDto) },
},
schema: { $ref: getSchemaPath(CustomersListResponseDto) },
})
getCustomers(@Query() filterDTO: GetCustomersQueryDto) {
return this.customersApplication.getCustomers(filterDTO);

View File

@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { CustomerResponseDto } from './CustomerResponse.dto';
class CustomersPaginationDto {
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 12 })
pageSize: number;
@ApiProperty({ example: 42 })
total: number;
}
export class CustomersListResponseDto {
@ApiProperty({ type: [CustomerResponseDto] })
data: CustomerResponseDto[];
@ApiProperty({ type: CustomersPaginationDto })
pagination: CustomersPaginationDto;
}

View File

@@ -1,4 +1,5 @@
import React, { createContext, useContext } from 'react';
import type { Item, Customer } from '@bigcapital/sdk-ts';
import {
useEstimate,
@@ -30,11 +31,11 @@ type EstimateFormSubmitPayload = Record<string, unknown>;
interface EstimateFormContextValue {
estimateId?: number;
estimate: UseEstimateResult['data'];
items: any;
customers: any;
items: Item[];
customers: Customer[];
branches: UseBranchesResult['data'];
warehouses: UseWarehousesResult['data'];
projects: any;
projects: unknown[];
isNewMode: boolean;
isItemsFetching: boolean;
@@ -163,11 +164,11 @@ function EstimateFormProvider({
const provider: EstimateFormContextValue = {
estimateId,
estimate,
items: (itemsData as any)?.items,
customers: (customersData as any)?.customers,
items: itemsData?.data ?? [],
customers: customersData?.data ?? [],
branches,
warehouses,
projects: (projectsData as any)?.projects,
projects: (projectsData as { data?: { projects?: unknown[] } })?.data?.projects ?? [],
isNewMode,
isItemsFetching,

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Group, PageFormBigNumber } from '@/components';

View File

@@ -1,10 +1,21 @@
// @ts-nocheck
import React, { createContext, useState } from 'react';
import type {
SaleInvoice,
CreateSaleInvoiceBody,
EditSaleInvoiceBody,
SaleInvoiceStateResponse,
Item,
Customer,
Warehouse,
Branch,
TaxRate,
PdfTemplateResponse,
GetPaymentServicesResponse,
} from '@bigcapital/sdk-ts';
import { isEmpty, pick } from 'lodash';
import { useLocation } from 'react-router-dom';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import { DashboardInsider } from '@/components/Dashboard';
import { transformToEditForm, ITEMS_FILTER_ROLES_QUERY } from './utils';
import {
useInvoice,
@@ -17,28 +28,77 @@ import {
useSettingsInvoices,
useEstimate,
useGetSaleInvoiceState,
GetSaleInvoiceStateResponse,
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
import { useTaxRates } from '@/hooks/query/tax-rates';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
import { useGetPaymentServices } from '@/hooks/query/payment-services';
interface InvoiceFormContextValue {
saleInvoiceState: GetSaleInvoiceStateResponse | null;
isInvoiceStateLoading: boolean;
}
type InvoiceFormSubmitPayload = {
redirect?: boolean;
};
const InvoiceFormContext = createContext<InvoiceFormContextValue>(
{} as InvoiceFormContextValue,
type InvoiceFormContextValue = {
invoice: SaleInvoice | undefined;
items: Item[];
customers: Customer[];
newInvoice: ReturnType<typeof transformToEditForm> | [];
estimateId: string | undefined;
invoiceId: number | undefined;
submitPayload: InvoiceFormSubmitPayload | undefined;
branches: Branch[];
warehouses: Warehouse[];
projects: unknown[];
taxRates: TaxRate[];
brandingTemplates: PdfTemplateResponse[];
paymentServices: GetPaymentServicesResponse | undefined;
isInvoiceLoading: boolean;
isItemsLoading: boolean;
isCustomersLoading: boolean;
isSettingsLoading: boolean;
isWarehouesLoading: boolean;
isBranchesLoading: boolean;
isFeatureLoading: boolean;
isBranchesSuccess: boolean;
isWarehousesSuccess: boolean;
isTaxRatesLoading: boolean;
isBrandingTemplatesLoading: boolean;
isInvoiceStateLoading: boolean;
isPaymentServicesLoading: boolean;
isBootLoading: boolean;
isNewMode: boolean;
createInvoiceMutate: (values: CreateSaleInvoiceBody) => Promise<void>;
editInvoiceMutate: (args: [number, EditSaleInvoiceBody]) => Promise<void>;
setSubmitPayload: React.Dispatch<
React.SetStateAction<InvoiceFormSubmitPayload | undefined>
>;
saleInvoiceState: SaleInvoiceStateResponse | undefined;
};
const InvoiceFormContext = createContext<InvoiceFormContextValue | undefined>(
undefined,
);
type InvoiceFormProviderProps = {
invoiceId?: number;
baseCurrency?: string;
children?: React.ReactNode;
};
/**
* Accounts chart data provider.
* Invoice form data provider.
*/
function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
function InvoiceFormProvider({
invoiceId,
baseCurrency,
...props
}: InvoiceFormProviderProps) {
const { state } = useLocation();
const estimateId = state?.action;
const estimateId = (state as { action?: string })?.action;
const estimateIdNum = estimateId ? Number(estimateId) : undefined;
// Features guard.
const { featureCan } = useFeatureCan();
@@ -47,9 +107,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
const isProjectsFeatureCan = featureCan(Features.Projects);
// Fetch invoice data.
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
enabled: !!invoiceId,
});
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId);
// Fetch tax rates.
const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates();
@@ -62,8 +120,8 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
// Fetches the estimate by the given id.
const { data: estimate, isLoading: isEstimateLoading } = useEstimate(
estimateId,
{ enabled: !!estimateId },
estimateIdNum,
{ enabled: !!estimateIdNum },
);
// Fetches branding templates of invoice.
@@ -78,7 +136,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
? transformToEditForm({
...pick(estimate, ['customer_id', 'currency_code', 'entries']),
})
: [];
: ([] as []);
// Handle fetching the items table based on the given query.
const {
@@ -120,7 +178,9 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
const { mutateAsync: editInvoiceMutate } = useEditInvoice();
// Form submit payload.
const [submitPayload, setSubmitPayload] = useState();
const [submitPayload, setSubmitPayload] = useState<
InvoiceFormSubmitPayload | undefined
>();
// Detarmines whether the form in new mode.
const isNewMode = !invoiceId;
@@ -140,19 +200,19 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
isSettingsLoading ||
isInvoiceStateLoading;
const provider = {
const provider: InvoiceFormContextValue = {
invoice,
items: itemsData?.items,
customers: customersData?.customers,
items: itemsData?.data ?? [],
customers: customersData?.data ?? [],
newInvoice,
estimateId,
invoiceId,
submitPayload,
branches,
warehouses,
projects: projectsData?.projects,
taxRates,
brandingTemplates,
branches: branches ?? [],
warehouses: warehouses ?? [],
projects: (projectsData as { data?: { projects?: unknown[] } })?.data?.projects ?? [],
taxRates: taxRates?.data ?? [],
brandingTemplates: brandingTemplates?.templates ?? [],
isInvoiceLoading,
isItemsLoading,
@@ -166,16 +226,18 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
isTaxRatesLoading,
isBrandingTemplatesLoading,
createInvoiceMutate,
editInvoiceMutate,
createInvoiceMutate: createInvoiceMutate as (
values: CreateSaleInvoiceBody,
) => Promise<void>,
editInvoiceMutate: editInvoiceMutate as (
args: [number, EditSaleInvoiceBody],
) => Promise<void>,
setSubmitPayload,
isNewMode,
// Payment Services
paymentServices,
isPaymentServicesLoading,
// Invoice state
saleInvoiceState,
isInvoiceStateLoading,
@@ -185,7 +247,14 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
return <InvoiceFormContext.Provider value={provider} {...props} />;
}
const useInvoiceFormContext = () =>
React.useContext<InvoiceFormContextValue>(InvoiceFormContext);
const useInvoiceFormContext = (): InvoiceFormContextValue => {
const ctx = React.useContext(InvoiceFormContext);
if (!ctx) {
throw new Error(
'useInvoiceFormContext must be used within an InvoiceFormProvider',
);
}
return ctx;
};
export { InvoiceFormProvider, useInvoiceFormContext };

View File

@@ -37,11 +37,7 @@ export function useCustomers(
return useQuery({
...props,
queryKey: customersKeys.list(query),
queryFn: () =>
(fetchCustomers as (f: ReturnType<typeof useApiFetcher>, q?: Record<string, unknown>) => Promise<CustomersListResponse>)(
fetcher,
query
),
queryFn: () => fetchCustomers(fetcher, query),
});
}

View File

@@ -8375,6 +8375,17 @@ export interface components {
*/
nonDeletableIds: number[];
};
CustomersListResponseDto: {
data: components["schemas"]["CustomerResponseDto"][];
pagination: {
/** @example 1 */
page: number;
/** @example 12 */
pageSize: number;
/** @example 42 */
total: number;
};
};
CustomerResponseDto: {
/** @example 1500 */
balance: number;
@@ -19109,7 +19120,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["CustomerResponseDto"][];
"application/json": components["schemas"]["CustomersListResponseDto"];
};
};
};