diff options
Diffstat (limited to 'src/services')
-rw-r--r-- | src/services/account/index.ts | 17 | ||||
-rw-r--r-- | src/services/constants.ts | 11 | ||||
-rw-r--r-- | src/services/contractors/ContractorForm.tsx | 14 | ||||
-rw-r--r-- | src/services/contractors/ContractorPanel.tsx | 34 | ||||
-rw-r--r-- | src/services/contractors/index.ts | 22 | ||||
-rw-r--r-- | src/services/index.ts | 20 | ||||
-rw-r--r-- | src/services/products/ProductForm.tsx | 15 | ||||
-rw-r--r-- | src/services/products/index.ts | 20 | ||||
-rw-r--r-- | src/services/transfers/TransferForm.tsx | 44 | ||||
-rw-r--r-- | src/services/transfers/TransfersUpload.tsx | 44 | ||||
-rw-r--r-- | src/services/transfers/index.ts | 35 | ||||
-rw-r--r-- | src/services/waybills/WaybillForm.tsx | 86 | ||||
-rw-r--r-- | src/services/waybills/WaybillPanel.tsx | 42 | ||||
-rw-r--r-- | src/services/waybills/index.ts | 27 |
14 files changed, 431 insertions, 0 deletions
diff --git a/src/services/account/index.ts b/src/services/account/index.ts new file mode 100644 index 0000000..5b5eb10 --- /dev/null +++ b/src/services/account/index.ts @@ -0,0 +1,17 @@ +const service = { + route: 'account', + name: 'Рассчётный счёт', + tableFields: [ + { key: 'date', label: 'Дата', transform: (date: string) => new Date(date).toLocaleDateString() }, + { key: 'amount', label: 'Сумма' }, + ], + actions: [ + { + name: 'Загрузить выписку', + route: '/transfers/upload', + }, + ], + rowLink: (item: any) => `/transfers?date=${item.date}`, +}; + +export default service; diff --git a/src/services/constants.ts b/src/services/constants.ts new file mode 100644 index 0000000..1461d9c --- /dev/null +++ b/src/services/constants.ts @@ -0,0 +1,11 @@ +export const operationNames = { + in: 'Приход', + out: 'Расход', +}; + +export const waybillStatusNames = { + waiting: 'Ожидание', + executed: 'Проведена', + cancelled: 'Отменена', +}; + diff --git a/src/services/contractors/ContractorForm.tsx b/src/services/contractors/ContractorForm.tsx new file mode 100644 index 0000000..d300efc --- /dev/null +++ b/src/services/contractors/ContractorForm.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Form } from 'formik'; +import Input from '../../components/Input'; + +const ContractorForm: React.FC = () => { + return ( + <Form id="form"> + <Input name="name" label="Название" /> + <Input name="vatId" label="УНП" /> + </Form> + ); +}; + +export default ContractorForm; diff --git a/src/services/contractors/ContractorPanel.tsx b/src/services/contractors/ContractorPanel.tsx new file mode 100644 index 0000000..ce94c48 --- /dev/null +++ b/src/services/contractors/ContractorPanel.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import Button from '../../components/Button'; +import { patch, baseURL } from '../../requests'; +import { PanelProps } from '../../containers/Service/ServiceContext'; + + +const ContractorPanel: React.FC<PanelProps> = ({ item, mutate }) => { + const history = useHistory(); + + return ( + <div className="lg:m-4 p-4 flex flex-col lg:pl-16 lg:border-l"> + <span className="text-lg mb-10"> + Долг контрагента: <span className="font-bold">{item.debt}</span> + </span> + <div className="grid lg:grid-cols-2"> + <Button route={`/waybills?contractorId=${item._id}`} variant="outlined"> + Показать накладные + </Button> + <Button route={`/waybills/add?contractorId=${item._id}`}> + Новая накладная + </Button> + <Button route={`/transfers?contractorId=${item._id}`} variant="outlined"> + Показать переводы + </Button> + <Button route={`/transfers/add?contractorId=${item._id}`}> + Новый перевод + </Button> + </div> + </div> + ); +}; + +export default ContractorPanel; diff --git a/src/services/contractors/index.ts b/src/services/contractors/index.ts new file mode 100644 index 0000000..124e338 --- /dev/null +++ b/src/services/contractors/index.ts @@ -0,0 +1,22 @@ +import Form from './ContractorForm'; +import Panel from './ContractorPanel'; + +const service = { + route: 'contractors', + name: 'Контрагенты', + nameSingular: 'Контрагент', + tableFields: [ + { key: 'vatId', label: 'УНП' }, + { key: 'name', label: 'Название' }, + { key: 'debt', label: 'Долг' }, + ], + default: { + name: '', + vatId: '', + debt: 0, + }, + Form, + Panel, +}; + +export default service; diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..7f559f2 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,20 @@ +import contractors from './contractors'; +import waybills from './waybills'; +import transfers from './transfers'; +import products from './products'; +import account from './account'; +import { registerServiceHooks } from '../hooks/useAPIClient'; +import { ServiceParams } from '../containers/Service/ServiceContext'; + +const services = [ + contractors, + products, + waybills, + transfers, + account, +] as ServiceParams[]; + +services.forEach((service: any) => registerServiceHooks(service.route)); + +export default services; + diff --git a/src/services/products/ProductForm.tsx b/src/services/products/ProductForm.tsx new file mode 100644 index 0000000..62f0e70 --- /dev/null +++ b/src/services/products/ProductForm.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Form } from 'formik'; +import Input from '../../components/Input'; + + +const ProductForm: React.FC = () => { + return ( + <Form id="form"> + <Input name="name" label="Название" /> + <Input name="price" type="number" label="Цена" /> + </Form> + ); +}; + +export default ProductForm; diff --git a/src/services/products/index.ts b/src/services/products/index.ts new file mode 100644 index 0000000..7e1509a --- /dev/null +++ b/src/services/products/index.ts @@ -0,0 +1,20 @@ +import Form from './ProductForm'; + +const service = { + route: 'products', + name: 'Товары', + nameSingular: 'Товар', + tableFields: [ + { key: 'name', label: 'Название' }, + { key: 'price', label: 'Цена' }, + { key: 'quantity', label: 'На складе' }, + ], + default: { + name: '', + price: '', + quantity: 0, + }, + Form, +}; + +export default service; diff --git a/src/services/transfers/TransferForm.tsx b/src/services/transfers/TransferForm.tsx new file mode 100644 index 0000000..3502609 --- /dev/null +++ b/src/services/transfers/TransferForm.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Form, FormikProps } from 'formik'; +import moment from 'moment'; +import Input from '../../components/Input'; +import Select from '../../components/Select'; +import hooks from '../../hooks/useAPIClient'; + + +const mapper = (item: any) => ({ key: item._id, label: item.name }); + + +const TransferForm: React.FC<FormikProps<any>> = ({ setFieldValue, values }) => { + const { data: contractors } = hooks.contractors.useList(); + + if (!values.date) setFieldValue('date', moment().format('YYYY-MM-DD')); + if (!values.contractorId && contractors?.length) setFieldValue('contractorId', contractors[0]._id); + + return ( + <Form id="form"> + <div className="grid grid-cols-2"> + <Select + name="contractorId" + label="Контрагент" + options={contractors?.map(mapper)} + required + /> + <Input name="date" type="date" label="Дата" required /> + </div> + <div className="grid grid-cols-2"> + <Select + name="operation" + label="Операция" + options={[ + { key: 'in', label: 'Приход' }, + { key: 'out', label: 'Расход' }, + ]} + /> + <Input name="amount" type="number" label="Сумма" required /> + </div> + </Form> + ); +}; + +export default TransferForm; diff --git a/src/services/transfers/TransfersUpload.tsx b/src/services/transfers/TransfersUpload.tsx new file mode 100644 index 0000000..1c81b08 --- /dev/null +++ b/src/services/transfers/TransfersUpload.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { Form, Formik } from 'formik'; +import Button from '../../components/Button'; +import Input from '../../components/Input'; +import Page, { Action } from '../../containers/Page'; +import { post } from '../../requests'; + +const TransfersUpload: React.FC = () => { + const history = useHistory(); + + const handleSubmitFile = () => { + const reader = new FileReader(); + const element = document.getElementById('file') as HTMLInputElement; + const file = element?.files?.[0]; + if (file) { + reader.readAsDataURL(file); + reader.onload = (e: any) => { + const uri = e.target.result; + post('/uploads', { uri }).then(history.goBack); + }; + } + }; + + const actions: Action[] = [ + { name: 'Назад', variant: 'outlined', onClick: history.goBack }, + { name: 'Загрузить', type: 'submit', form: 'form' }, + ]; + + return ( + <Page + title="Загрузить выписку" + actions={actions} + > + <Formik onSubmit={handleSubmitFile} initialValues={{}}> + <Form id="form"> + <Input name="file" type="file" accept=".pdf" label="Прикрепите файл" id="file" /> + </Form> + </Formik> + </Page> + ); +}; + +export default TransfersUpload; diff --git a/src/services/transfers/index.ts b/src/services/transfers/index.ts new file mode 100644 index 0000000..09f6e04 --- /dev/null +++ b/src/services/transfers/index.ts @@ -0,0 +1,35 @@ +import Form from './TransferForm'; +import UploadPage from './TransfersUpload'; + +import { operationNames } from '../constants'; + +const service = { + route: 'transfers', + name: 'Переводы', + nameSingular: 'Перевод', + tableFields: [ + { key: 'date', label: 'Дата', transform: (date: string) => new Date(date).toLocaleDateString() }, + { key: 'contractor.name', label: 'Контрагент' }, + { key: 'operation', label: 'Операция', transform: (op: 'in' | 'out') => operationNames[op] }, + { key: 'amount', label: 'Сумма' }, + ], + actions: [ + { + name: 'Загрузить выписку', + route: '/transfers/upload', + variant: 'outlined', + }, + { + name: 'Добавить', + route: '/transfers/add', + }, + ], + default: { + operation: 'in', + records: [], + }, + routes: { upload: UploadPage }, + Form, +}; + +export default service; diff --git a/src/services/waybills/WaybillForm.tsx b/src/services/waybills/WaybillForm.tsx new file mode 100644 index 0000000..c11ab9e --- /dev/null +++ b/src/services/waybills/WaybillForm.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Form, FormikProps } from 'formik'; +import _ from 'lodash'; +import moment from 'moment'; +import Input from '../../components/Input'; +import Button from '../../components/Button'; +import Select from '../../components/Select'; +import Paper from '../../components/Paper'; +import hooks from '../../hooks/useAPIClient'; + + +const mapper = (item: any) => ({ key: item._id, label: item.name }); + + +const WaybillForm: React.FC<FormikProps<any>> = ({ setFieldValue, values }) => { + const { data: products } = hooks.products.useList(); + const { data: contractors } = hooks.contractors.useList(); + + if (!values.date) setFieldValue('date', moment().format('YYYY-MM-DD')); + if (!values.contractorId && contractors?.length) setFieldValue('contractorId', contractors[0]._id); + + const handleAddRecord = () => setFieldValue('records', [...values.records, { + productId: _.map(products, '_id').reduce((acc, id) => { + return acc || (!_.map(values.records, 'productId').includes(id) && id); + }, false), + price: '', + quantity: 1, + }]); + + const handleRemoveRecord = (index: number) => () => { + const records = [...values.records]; + records.splice(index, 1); + setFieldValue('records', records); + }; + + return ( + <Form id="form"> + <Select + name="contractorId" + label="Контрагент" + options={contractors?.map(mapper)} + /> + <div className="grid grid-cols-2"> + <Select + name="operation" + label="Операция" + options={[ + { key: 'in', label: 'Приход' }, + { key: 'out', label: 'Расход' }, + ]} + /> + <Input name="date" type="date" label="Дата" /> + </div> + {values.records.map((record: any, index: number) => ( + <Paper variant="outlined" className="my-4 md:mx-4" key={`${index}-${record.productId}`}> + <Select + name={`records.${index}.productId`} + label="Товар" + options={products?.map(mapper)} + required + /> + <div className="grid grid-cols-3"> + <Input + name={`records.${index}.price`} + type="number" + label="Цена" + required + /> + <Input + name={`records.${index}.quantity`} + type="number" + label="Количество" + required + /> + <div className="flex justify-end items-end"> + <Button onClick={handleRemoveRecord(index)} size="sm" variant="outlined">Удалить</Button> + </div> + </div> + </Paper> + ))} + <Button onClick={handleAddRecord} variant="outlined" size="sm">Добавить товар</Button> + </Form> + ); +}; + +export default WaybillForm; diff --git a/src/services/waybills/WaybillPanel.tsx b/src/services/waybills/WaybillPanel.tsx new file mode 100644 index 0000000..101a871 --- /dev/null +++ b/src/services/waybills/WaybillPanel.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import Button from '../../components/Button'; +import { patch, baseURL } from '../../requests'; +import { PanelProps } from '../../containers/Service/ServiceContext'; + + +const WaybillPanel: React.FC<PanelProps> = ({ item, mutate }) => { + const history = useHistory(); + + const handleChangeStatus = (status: any) => patch(`/waybills/${item._id}`, { status }) + .then(() => { + history.push('/waybills'); + mutate({ ...item, status }); + }); + + const handlePrint = () => window.open(`${baseURL}/spreadsheets/${item._id}`, '_blank'); + + const handleExecute = () => handleChangeStatus('executed'); + const handleCancel = () => handleChangeStatus('cancelled'); + + const executed = item.status === 'executed'; + + return ( + <div className="lg:m-4 p-4 flex flex-col lg:pl-16 lg:border-l"> + <div className="grid lg:grid-cols-2"> + <Button route={`/contractors/${item.contractorId}`} variant="outlined"> + Перейти к контрагенту + </Button> + <Button onClick={handlePrint} variant="outlined"> + Печать + </Button> + <span className="text-lg text-center mt-4">Итоговая сумма: ${item.total}</span> + <Button onClick={executed ? handleCancel : handleExecute} size="lg"> + {executed ? 'Откатить' : 'Провести'} + </Button> + </div> + </div> + ); +}; + +export default WaybillPanel; diff --git a/src/services/waybills/index.ts b/src/services/waybills/index.ts new file mode 100644 index 0000000..f8a4be6 --- /dev/null +++ b/src/services/waybills/index.ts @@ -0,0 +1,27 @@ +import Form from './WaybillForm'; +import Panel from './WaybillPanel'; +import { waybillStatusNames, operationNames } from '../constants'; + +const service = { + route: 'waybills', + name: 'Накладные', + nameSingular: 'Накладная', + tableFields: [ + { + key: 'status', + label: 'Статус', + transform: (status: 'waiting' | 'executed' | 'cancelled') => waybillStatusNames[status], + }, + { key: 'operation', label: 'Операция', transform: (op: 'in' | 'out') => operationNames[op] }, + { key: 'total', label: 'Сумма' }, + { key: 'contractor.name', label: 'Контрагент' }, + ], + default: { + operation: 'in', + records: [], + }, + Form, + Panel, +}; + +export default service; |