feat: shipping warehouse and cost
This commit is contained in:
@@ -80,6 +80,7 @@ export type { DealUpdateServiceResponse } from './models/DealUpdateServiceRespon
|
||||
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
|
||||
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
|
||||
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';
|
||||
export type { GetAllShippingWarehousesResponse } from './models/GetAllShippingWarehousesResponse';
|
||||
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
|
||||
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
|
||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||
@@ -116,6 +117,7 @@ export type { ServicePriceRangeSchema } from './models/ServicePriceRangeSchema';
|
||||
export type { ServiceSchema } from './models/ServiceSchema';
|
||||
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
|
||||
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';
|
||||
export type { ShippingWarehouseSchema } from './models/ShippingWarehouseSchema';
|
||||
export type { UserSchema } from './models/UserSchema';
|
||||
export type { ValidationError } from './models/ValidationError';
|
||||
|
||||
@@ -125,3 +127,4 @@ export { ClientService } from './services/ClientService';
|
||||
export { DealService } from './services/DealService';
|
||||
export { ProductService } from './services/ProductService';
|
||||
export { ServiceService } from './services/ServiceService';
|
||||
export { ShippingWarehouseService } from './services/ShippingWarehouseService';
|
||||
|
||||
@@ -7,5 +7,6 @@ export type DealQuickCreateRequest = {
|
||||
clientName: string;
|
||||
comment: string;
|
||||
acceptanceDate: string;
|
||||
shippingWarehouse: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ClientSchema } from './ClientSchema';
|
||||
import type { DealProductSchema } from './DealProductSchema';
|
||||
import type { DealServiceSchema } from './DealServiceSchema';
|
||||
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
|
||||
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
|
||||
export type DealSchema = {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -19,5 +20,6 @@ export type DealSchema = {
|
||||
isCompleted: boolean;
|
||||
client: ClientSchema;
|
||||
comment: string;
|
||||
shippingWarehouse?: (ShippingWarehouseSchema | null);
|
||||
};
|
||||
|
||||
|
||||
9
src/client/models/GetAllShippingWarehousesResponse.ts
Normal file
9
src/client/models/GetAllShippingWarehousesResponse.ts
Normal 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>;
|
||||
};
|
||||
|
||||
@@ -11,5 +11,6 @@ export type ServiceSchema = {
|
||||
price: number;
|
||||
serviceType: number;
|
||||
priceRanges: Array<ServicePriceRangeSchema>;
|
||||
cost: (number | null);
|
||||
};
|
||||
|
||||
|
||||
9
src/client/models/ShippingWarehouseSchema.ts
Normal file
9
src/client/models/ShippingWarehouseSchema.ts
Normal 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;
|
||||
};
|
||||
|
||||
21
src/client/services/ShippingWarehouseService.ts
Normal file
21
src/client/services/ShippingWarehouseService.ts
Normal 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',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,22 @@ import {useForm} from "@mantine/form";
|
||||
import styles from './CreateDealForm.module.css';
|
||||
import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx";
|
||||
import {DateTimePicker} from "@mantine/dates";
|
||||
import ShippingWarehouseAutocomplete
|
||||
from "../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||
|
||||
type Props = {
|
||||
onSubmit: (quickDeal: QuickDeal) => void
|
||||
onCancel: () => void;
|
||||
}
|
||||
const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
|
||||
const form = useForm({
|
||||
const form = useForm<QuickDeal>({
|
||||
initialValues: {
|
||||
name: '',
|
||||
clientName: '',
|
||||
clientAddress: '',
|
||||
comment: '',
|
||||
acceptanceDate: new Date()
|
||||
acceptanceDate: new Date(),
|
||||
shippingWarehouse: ''
|
||||
}
|
||||
});
|
||||
return (
|
||||
@@ -25,7 +28,6 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
|
||||
style={{width: '100%'}}
|
||||
onSubmit={form.onSubmit((values) => onSubmit(values))}
|
||||
>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -43,6 +45,10 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
|
||||
<ClientAutocomplete
|
||||
nameRestProps={form.getInputProps('clientName')}
|
||||
/>
|
||||
<ShippingWarehouseAutocomplete
|
||||
{...form.getInputProps('shippingWarehouse')}
|
||||
placeholder={'Склад отгрузки'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles['inputs']}>
|
||||
|
||||
65
src/components/ObjectAutocomplete/ObjectAutocomplete.tsx
Normal file
65
src/components/ObjectAutocomplete/ObjectAutocomplete.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -41,7 +41,6 @@ const queryClient = new QueryClient();
|
||||
OpenAPI.BASE = import.meta.env.VITE_API_URL
|
||||
OpenAPI.TOKEN = JSON.parse(localStorage.getItem('authState') || "{}")['accessToken'];
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
// <React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MantineProvider defaultColorScheme={"dark"}>
|
||||
@@ -54,5 +53,4 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
// </React.StrictMode>,
|
||||
)
|
||||
|
||||
@@ -30,18 +30,15 @@ export const useDealServicesTableColumns = (props: Props) => {
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "service.cost",
|
||||
header: "Себестоимость"
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "quantity",
|
||||
header: "Количество",
|
||||
// Cell: ({row}) => {
|
||||
// return (
|
||||
// <PlusMinusInput
|
||||
// value={row.original.quantity}
|
||||
// onChange={(value) => onChange(row.original, value)}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
|
||||
@@ -287,7 +287,6 @@ const DealEditDrawerProductsTable = () => {
|
||||
|
||||
const useDealStatusChangeState = () => {
|
||||
const {selectedDeal} = useDealPageContext();
|
||||
|
||||
return {
|
||||
statusHistory: selectedDeal?.statusHistory || []
|
||||
}
|
||||
@@ -342,12 +341,6 @@ const DealEditDrawer: FC = () => {
|
||||
<Tabs.Tab value={"servicesAndProducts"} leftSection={<IconBox/>}>
|
||||
Товары и услуги
|
||||
</Tabs.Tab>
|
||||
{/*<Tabs.Tab value={"services"} leftSection={<IconBox/>}>*/}
|
||||
{/* Услуги*/}
|
||||
{/*</Tabs.Tab>*/}
|
||||
{/*<Tabs.Tab value={"products"} leftSection={<IconBarcode/>}>*/}
|
||||
{/* Товары*/}
|
||||
{/*</Tabs.Tab>*/}
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"general"}>
|
||||
<Box h={"100%"} w={"100%"} p={rem(10)}>
|
||||
|
||||
@@ -64,7 +64,6 @@ const Content: FC<Props> = ({deal}) => {
|
||||
return (
|
||||
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
||||
<Flex direction={'column'}>
|
||||
|
||||
<Fieldset legend={"Общие параметры"}>
|
||||
<Flex direction={"column"} gap={rem(10)}>
|
||||
<TextInput
|
||||
@@ -83,12 +82,17 @@ const Content: FC<Props> = ({deal}) => {
|
||||
placeholder={"Текущий статус"}
|
||||
label={"Текущий статус"}
|
||||
value={DealStatusDictionary[deal.currentStatus as DealStatus]}/>
|
||||
|
||||
<Textarea
|
||||
label={'Коментарий к сделке'}
|
||||
placeholder={'Введите коментарий к сделке'}
|
||||
{...form.getInputProps('comment')}
|
||||
/>
|
||||
<TextInput
|
||||
disabled
|
||||
placeholder={"Введите склад отгрузки"}
|
||||
label={"Склад отгрузки"}
|
||||
value={form.values.shippingWarehouse?.name}
|
||||
/>
|
||||
</Flex>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Клиент"}>
|
||||
|
||||
@@ -58,7 +58,7 @@ const ProductAndServiceTab: FC = () => {
|
||||
</div>
|
||||
</Flex>
|
||||
<Flex direction={"column"} className={styles['deal-container-wrapper']}>
|
||||
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice()}</Title>
|
||||
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice().toLocaleString("ru")}₽</Title>
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,15 +9,21 @@ type Props = {
|
||||
const useProductServicesTableColumns = (props: Props) => {
|
||||
const {data, quantity} = props;
|
||||
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>[]>(() => [
|
||||
{
|
||||
accessorKey: "service.name",
|
||||
header: "Услуга",
|
||||
},
|
||||
{
|
||||
accessorKey: "service.cost",
|
||||
header: "Себестоимость",
|
||||
Footer: () => <>Итоговая себестоимость: {totalCost.toLocaleString("ru")}₽</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
Footer: () => <>Итог: {totalPrice}₽</>,
|
||||
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
||||
}
|
||||
], [totalPrice]);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,14 @@ export const useServicesTableColumns = () => {
|
||||
enableGrouping: false,
|
||||
enableSorting: false,
|
||||
Cell: ({row}) => getPriceRow(row.original)
|
||||
|
||||
},
|
||||
{
|
||||
accessorKey: "cost",
|
||||
header: "Себестоимость",
|
||||
enableGrouping: false,
|
||||
enableSorting: false,
|
||||
},
|
||||
|
||||
], []);
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import {ServicePriceRangeSchema, ServiceSchema} from "../../../client";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {ContextModalProps} from "@mantine/modals";
|
||||
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 ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
|
||||
import ServicePriceInput from "../components/ServicePriceInput/ServicePriceInput.tsx";
|
||||
@@ -25,7 +25,8 @@ const CreateServiceModal = ({
|
||||
name: ''
|
||||
},
|
||||
serviceType: -1,
|
||||
priceRanges: [] as ServicePriceRangeSchema[]
|
||||
priceRanges: [] as ServicePriceRangeSchema[],
|
||||
cost: null
|
||||
}
|
||||
|
||||
const form = useForm<ServiceSchema>({
|
||||
@@ -72,6 +73,12 @@ const CreateServiceModal = ({
|
||||
label={"Тип услуги"}
|
||||
{...form.getInputProps('serviceType')}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите себестоимость услуги"}
|
||||
label={"Себестоимость услуги"}
|
||||
hideControls
|
||||
{...form.getInputProps('cost')}
|
||||
/>
|
||||
<ServicePriceInput
|
||||
singlePriceInputProps={{
|
||||
hideControls: true,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Route = createLazyFileRoute('/test')({
|
||||
function TestPage() {
|
||||
return (
|
||||
<>
|
||||
|
||||
{/*<ShippingWarehouseAutocomplete/>*/}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
export type QuickDeal = {
|
||||
name: string
|
||||
clientName: string
|
||||
clientAddress: string
|
||||
comment:string
|
||||
acceptanceDate: Date
|
||||
name: string;
|
||||
clientName: string;
|
||||
clientAddress: string;
|
||||
comment: string;
|
||||
acceptanceDate: Date;
|
||||
shippingWarehouse: string;
|
||||
}
|
||||
Reference in New Issue
Block a user