feat: shipping warehouse and cost

This commit is contained in:
2024-07-18 04:56:20 +03:00
parent c4dc887305
commit 5c6e7cf5f5
21 changed files with 192 additions and 33 deletions

View File

@@ -80,6 +80,7 @@ export type { DealUpdateServiceResponse } from './models/DealUpdateServiceRespon
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse'; export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse'; export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse'; export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';
export type { GetAllShippingWarehousesResponse } from './models/GetAllShippingWarehousesResponse';
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest'; export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse'; export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest'; export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
@@ -116,6 +117,7 @@ export type { ServicePriceRangeSchema } from './models/ServicePriceRangeSchema';
export type { ServiceSchema } from './models/ServiceSchema'; export type { ServiceSchema } from './models/ServiceSchema';
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest'; export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse'; export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';
export type { ShippingWarehouseSchema } from './models/ShippingWarehouseSchema';
export type { UserSchema } from './models/UserSchema'; export type { UserSchema } from './models/UserSchema';
export type { ValidationError } from './models/ValidationError'; export type { ValidationError } from './models/ValidationError';
@@ -125,3 +127,4 @@ export { ClientService } from './services/ClientService';
export { DealService } from './services/DealService'; export { DealService } from './services/DealService';
export { ProductService } from './services/ProductService'; export { ProductService } from './services/ProductService';
export { ServiceService } from './services/ServiceService'; export { ServiceService } from './services/ServiceService';
export { ShippingWarehouseService } from './services/ShippingWarehouseService';

View File

@@ -7,5 +7,6 @@ export type DealQuickCreateRequest = {
clientName: string; clientName: string;
comment: string; comment: string;
acceptanceDate: string; acceptanceDate: string;
shippingWarehouse: string;
}; };

View File

@@ -6,6 +6,7 @@ import type { ClientSchema } from './ClientSchema';
import type { DealProductSchema } from './DealProductSchema'; import type { DealProductSchema } from './DealProductSchema';
import type { DealServiceSchema } from './DealServiceSchema'; import type { DealServiceSchema } from './DealServiceSchema';
import type { DealStatusHistorySchema } from './DealStatusHistorySchema'; import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
export type DealSchema = { export type DealSchema = {
id: number; id: number;
name: string; name: string;
@@ -19,5 +20,6 @@ export type DealSchema = {
isCompleted: boolean; isCompleted: boolean;
client: ClientSchema; client: ClientSchema;
comment: string; comment: string;
shippingWarehouse?: (ShippingWarehouseSchema | null);
}; };

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
export type GetAllShippingWarehousesResponse = {
shippingWarehouses: Array<ShippingWarehouseSchema>;
};

View File

@@ -11,5 +11,6 @@ export type ServiceSchema = {
price: number; price: number;
serviceType: number; serviceType: number;
priceRanges: Array<ServicePriceRangeSchema>; priceRanges: Array<ServicePriceRangeSchema>;
cost: (number | null);
}; };

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ShippingWarehouseSchema = {
id: number;
name: string;
};

View File

@@ -0,0 +1,21 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GetAllShippingWarehousesResponse } from '../models/GetAllShippingWarehousesResponse';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class ShippingWarehouseService {
/**
* Get All
* @returns GetAllShippingWarehousesResponse Successful Response
* @throws ApiError
*/
public static getAllShippingWarehouses(): CancelablePromise<GetAllShippingWarehousesResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/shipping-warehouse/get-all',
});
}
}

View File

@@ -5,19 +5,22 @@ import {useForm} from "@mantine/form";
import styles from './CreateDealForm.module.css'; import styles from './CreateDealForm.module.css';
import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx"; import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx";
import {DateTimePicker} from "@mantine/dates"; import {DateTimePicker} from "@mantine/dates";
import ShippingWarehouseAutocomplete
from "../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
type Props = { type Props = {
onSubmit: (quickDeal: QuickDeal) => void onSubmit: (quickDeal: QuickDeal) => void
onCancel: () => void; onCancel: () => void;
} }
const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => { const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
const form = useForm({ const form = useForm<QuickDeal>({
initialValues: { initialValues: {
name: '', name: '',
clientName: '', clientName: '',
clientAddress: '', clientAddress: '',
comment: '', comment: '',
acceptanceDate: new Date() acceptanceDate: new Date(),
shippingWarehouse: ''
} }
}); });
return ( return (
@@ -25,7 +28,6 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
style={{width: '100%'}} style={{width: '100%'}}
onSubmit={form.onSubmit((values) => onSubmit(values))} onSubmit={form.onSubmit((values) => onSubmit(values))}
> >
<div style={{ <div style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -43,6 +45,10 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
<ClientAutocomplete <ClientAutocomplete
nameRestProps={form.getInputProps('clientName')} nameRestProps={form.getInputProps('clientName')}
/> />
<ShippingWarehouseAutocomplete
{...form.getInputProps('shippingWarehouse')}
placeholder={'Склад отгрузки'}
/>
</div> </div>
<div className={styles['inputs']}> <div className={styles['inputs']}>

View File

@@ -0,0 +1,65 @@
import {Autocomplete, AutocompleteProps} from "@mantine/core";
import {useEffect, useMemo, useState} from "react";
import {ObjectWithNameAndId} from "../../types/utils.ts";
import {omit} from "lodash";
export type AutocompleteObjectType<T extends ObjectWithNameAndId> = T;
type ControlledValueProps<T extends ObjectWithNameAndId> = {
value: AutocompleteObjectType<T>,
onChange: (value: string) => void;
}
type RestProps<T extends ObjectWithNameAndId> = {
defaultValue?: AutocompleteObjectType<T>
onChange: (value: string) => void;
data: AutocompleteObjectType<T>[];
filterBy?: (item: AutocompleteObjectType<T>) => boolean;
}
export type ObjectAutocompleteProps<T extends ObjectWithNameAndId> =
(RestProps<T> & Partial<ControlledValueProps<T>>)
& Omit<AutocompleteProps, 'value' | 'onChange' | 'data'>;
const ObjectAutocomplete = <T extends ObjectWithNameAndId, >(props: ObjectAutocompleteProps<T>) => {
const isControlled = 'value' in props;
const [internalValue, setInternalValue] = useState<undefined | string>(props.defaultValue);
const value = isControlled ? props.value?.name : internalValue;
const data = useMemo(() => {
const propsData = props.filterBy ? props.data.filter(props.filterBy) : props.data;
return propsData.map(item => ({
label: item.name,
value: item.id.toString()
}));
}, [props.data]);
const handleOnChange = (event: string | null) => {
if (!event) return;
if (isControlled) {
props.onChange(event);
return;
}
setInternalValue(event);
}
useEffect(() => {
if (isControlled || !internalValue) return;
props.onChange(internalValue);
}, [internalValue]);
const restProps = omit(props, ['filterBy', 'groupBy']);
return (
<Autocomplete
{...restProps}
value={value?.toString()}
onChange={handleOnChange}
data={data}
/>
)
}
export default ObjectAutocomplete;

View File

@@ -0,0 +1,17 @@
import ObjectAutocomplete, {ObjectAutocompleteProps} from "../../ObjectAutocomplete/ObjectAutocomplete.tsx";
import useShippingWarehousesList from "./hooks/useShippingWarehousesList.tsx";
import {FC} from "react";
import {ShippingWarehouseSchema} from "../../../client";
type Props = Omit<ObjectAutocompleteProps<ShippingWarehouseSchema>, 'data'>;
const ShippingWarehouseAutocomplete: FC<Props> = (props) => {
const {shippingWarehouses} = useShippingWarehousesList();
return (
<ObjectAutocomplete
{...props}
data={shippingWarehouses}
/>
)
}
export default ShippingWarehouseAutocomplete;

View File

@@ -0,0 +1,13 @@
import {useQuery} from "@tanstack/react-query";
import {ShippingWarehouseService} from "../../../../client";
const useShippingWarehousesList = () => {
const {isPending, error, data, refetch} = useQuery({
queryKey: ['getAllShippingWarehouses'],
queryFn: ShippingWarehouseService.getAllShippingWarehouses
});
const shippingWarehouses = isPending || error || !data ? [] : data.shippingWarehouses;
return {shippingWarehouses, refetch}
}
export default useShippingWarehousesList;

View File

@@ -41,7 +41,6 @@ const queryClient = new QueryClient();
OpenAPI.BASE = import.meta.env.VITE_API_URL OpenAPI.BASE = import.meta.env.VITE_API_URL
OpenAPI.TOKEN = JSON.parse(localStorage.getItem('authState') || "{}")['accessToken']; OpenAPI.TOKEN = JSON.parse(localStorage.getItem('authState') || "{}")['accessToken'];
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
// <React.StrictMode>
<Provider store={store}> <Provider store={store}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<MantineProvider defaultColorScheme={"dark"}> <MantineProvider defaultColorScheme={"dark"}>
@@ -54,5 +53,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
</MantineProvider> </MantineProvider>
</QueryClientProvider> </QueryClientProvider>
</Provider> </Provider>
// </React.StrictMode>,
) )

View File

@@ -30,18 +30,15 @@ export const useDealServicesTableColumns = (props: Props) => {
accessorKey: "price", accessorKey: "price",
header: "Цена", header: "Цена",
}, },
{
enableGrouping: false,
accessorKey: "service.cost",
header: "Себестоимость"
},
{ {
enableGrouping: false, enableGrouping: false,
accessorKey: "quantity", accessorKey: "quantity",
header: "Количество", header: "Количество",
// Cell: ({row}) => {
// return (
// <PlusMinusInput
// value={row.original.quantity}
// onChange={(value) => onChange(row.original, value)}
// />
// )
// }
}, },
{ {
enableGrouping: false, enableGrouping: false,

View File

@@ -287,7 +287,6 @@ const DealEditDrawerProductsTable = () => {
const useDealStatusChangeState = () => { const useDealStatusChangeState = () => {
const {selectedDeal} = useDealPageContext(); const {selectedDeal} = useDealPageContext();
return { return {
statusHistory: selectedDeal?.statusHistory || [] statusHistory: selectedDeal?.statusHistory || []
} }
@@ -342,12 +341,6 @@ const DealEditDrawer: FC = () => {
<Tabs.Tab value={"servicesAndProducts"} leftSection={<IconBox/>}> <Tabs.Tab value={"servicesAndProducts"} leftSection={<IconBox/>}>
Товары и услуги Товары и услуги
</Tabs.Tab> </Tabs.Tab>
{/*<Tabs.Tab value={"services"} leftSection={<IconBox/>}>*/}
{/* Услуги*/}
{/*</Tabs.Tab>*/}
{/*<Tabs.Tab value={"products"} leftSection={<IconBarcode/>}>*/}
{/* Товары*/}
{/*</Tabs.Tab>*/}
</Tabs.List> </Tabs.List>
<Tabs.Panel value={"general"}> <Tabs.Panel value={"general"}>
<Box h={"100%"} w={"100%"} p={rem(10)}> <Box h={"100%"} w={"100%"} p={rem(10)}>

View File

@@ -64,7 +64,6 @@ const Content: FC<Props> = ({deal}) => {
return ( return (
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Flex direction={'column'}> <Flex direction={'column'}>
<Fieldset legend={"Общие параметры"}> <Fieldset legend={"Общие параметры"}>
<Flex direction={"column"} gap={rem(10)}> <Flex direction={"column"} gap={rem(10)}>
<TextInput <TextInput
@@ -83,12 +82,17 @@ const Content: FC<Props> = ({deal}) => {
placeholder={"Текущий статус"} placeholder={"Текущий статус"}
label={"Текущий статус"} label={"Текущий статус"}
value={DealStatusDictionary[deal.currentStatus as DealStatus]}/> value={DealStatusDictionary[deal.currentStatus as DealStatus]}/>
<Textarea <Textarea
label={'Коментарий к сделке'} label={'Коментарий к сделке'}
placeholder={'Введите коментарий к сделке'} placeholder={'Введите коментарий к сделке'}
{...form.getInputProps('comment')} {...form.getInputProps('comment')}
/> />
<TextInput
disabled
placeholder={"Введите склад отгрузки"}
label={"Склад отгрузки"}
value={form.values.shippingWarehouse?.name}
/>
</Flex> </Flex>
</Fieldset> </Fieldset>
<Fieldset legend={"Клиент"}> <Fieldset legend={"Клиент"}>

View File

@@ -58,7 +58,7 @@ const ProductAndServiceTab: FC = () => {
</div> </div>
</Flex> </Flex>
<Flex direction={"column"} className={styles['deal-container-wrapper']}> <Flex direction={"column"} className={styles['deal-container-wrapper']}>
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice()}</Title> <Title order={3}>Общая стоимость всех услуг: {getTotalPrice().toLocaleString("ru")}</Title>
</Flex> </Flex>
</div> </div>

View File

@@ -9,15 +9,21 @@ type Props = {
const useProductServicesTableColumns = (props: Props) => { const useProductServicesTableColumns = (props: Props) => {
const {data, quantity} = props; const {data, quantity} = props;
const totalPrice = useMemo(() => data.reduce((acc, row) => acc + (row.price * quantity), 0), [data, quantity]); const totalPrice = useMemo(() => data.reduce((acc, row) => acc + (row.price * quantity), 0), [data, quantity]);
const totalCost = useMemo(() => data.reduce((acc, row) => acc + ((row.service.cost || 0) * quantity), 0), [data, quantity]);
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(() => [ return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(() => [
{ {
accessorKey: "service.name", accessorKey: "service.name",
header: "Услуга", header: "Услуга",
}, },
{
accessorKey: "service.cost",
header: "Себестоимость",
Footer: () => <>Итоговая себестоимость: {totalCost.toLocaleString("ru")}</>,
},
{ {
accessorKey: "price", accessorKey: "price",
header: "Цена", header: "Цена",
Footer: () => <>Итог: {totalPrice}</>, Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}</>,
} }
], [totalPrice]); ], [totalPrice]);
} }

View File

@@ -41,8 +41,14 @@ export const useServicesTableColumns = () => {
enableGrouping: false, enableGrouping: false,
enableSorting: false, enableSorting: false,
Cell: ({row}) => getPriceRow(row.original) Cell: ({row}) => getPriceRow(row.original)
}, },
{
accessorKey: "cost",
header: "Себестоимость",
enableGrouping: false,
enableSorting: false,
},
], []); ], []);
} }

View File

@@ -2,7 +2,7 @@ import {ServicePriceRangeSchema, ServiceSchema} from "../../../client";
import {useForm} from "@mantine/form"; import {useForm} from "@mantine/form";
import {ContextModalProps} from "@mantine/modals"; import {ContextModalProps} from "@mantine/modals";
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import {TextInput} from "@mantine/core"; import {NumberInput, TextInput} from "@mantine/core";
import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx"; import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx";
import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx"; import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
import ServicePriceInput from "../components/ServicePriceInput/ServicePriceInput.tsx"; import ServicePriceInput from "../components/ServicePriceInput/ServicePriceInput.tsx";
@@ -25,7 +25,8 @@ const CreateServiceModal = ({
name: '' name: ''
}, },
serviceType: -1, serviceType: -1,
priceRanges: [] as ServicePriceRangeSchema[] priceRanges: [] as ServicePriceRangeSchema[],
cost: null
} }
const form = useForm<ServiceSchema>({ const form = useForm<ServiceSchema>({
@@ -72,6 +73,12 @@ const CreateServiceModal = ({
label={"Тип услуги"} label={"Тип услуги"}
{...form.getInputProps('serviceType')} {...form.getInputProps('serviceType')}
/> />
<NumberInput
placeholder={"Введите себестоимость услуги"}
label={"Себестоимость услуги"}
hideControls
{...form.getInputProps('cost')}
/>
<ServicePriceInput <ServicePriceInput
singlePriceInputProps={{ singlePriceInputProps={{
hideControls: true, hideControls: true,

View File

@@ -8,7 +8,7 @@ export const Route = createLazyFileRoute('/test')({
function TestPage() { function TestPage() {
return ( return (
<> <>
{/*<ShippingWarehouseAutocomplete/>*/}
</> </>
); );
} }

View File

@@ -1,7 +1,8 @@
export type QuickDeal = { export type QuickDeal = {
name: string name: string;
clientName: string clientName: string;
clientAddress: string clientAddress: string;
comment:string comment: string;
acceptanceDate: Date acceptanceDate: Date;
shippingWarehouse: string;
} }