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 { 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';

View File

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

View File

@@ -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);
};

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;
serviceType: number;
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 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']}>

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.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>,
)

View File

@@ -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,

View File

@@ -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)}>

View File

@@ -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={"Клиент"}>

View File

@@ -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>

View File

@@ -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]);
}

View File

@@ -41,8 +41,14 @@ export const useServicesTableColumns = () => {
enableGrouping: false,
enableSorting: false,
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 {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,

View File

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

View File

@@ -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;
}