Commit 3dd1b455576b0859e6f52923e6aaeb584ab3318d

Authored by 曾国涛
1 parent 768fbcce

feat(invoice): optimize invoice components and enhance user experience

- 重构Client页面表格组件,文件重命名为AllRecordTable.tsx,优化表格项显示
- 修改发票类型显示名称,增加文本复制功能,提升用户交互体验
- 移除部分不必要组件和代码,简化页面结构,提高代码可维护性- 修正InvoiceModal组件中submitter属性,改进页面提交逻辑- 优化InvoiceRecordDetailModal组件,简化订单号显示方式,增加复制功能
- 调整InvoicingModal和ManualInvoicingModal组件,移除冗余的select和list组件
-增加pageSizeOptions配置,提供分页大小选择,改善数据浏览体验
- 修复Invoice组件中开票日期显示格式问题,统一数据展示样式
- 优化Order组件中的按钮权限判断逻辑,提升页面响应性
- 删除不再使用的enumToSelect函数,精简代码依赖
- 修正InvoicingDrawerForm组件中产品名称复制功能的实现
- 优化表单验证逻辑,增强数据校验的准确性和用户体验

通过这些改动,- 实现导出客户信息为Excel文件的功能。
- 使用useMessage钩子来管理Ant Design消息提示。
- 添加单元格操作按钮,包括查看沟通历史、详情和编辑客户信息。
- 组件使用了editable、columnsState和form等高级特性,以提供更好的用户体验和数据管理。

通过这个提交,我们完成了客户记录的表格展示页面,实现了数据的查询、筛选、导出及一些基本操作,提升了项目的业务功能完备性。"增加客户统计信息和调整客户端表格布局"模块结构以支持核销功能本次更新主要涉及发票相关组件及页面的重构,以支持新的核销功能。具体变更包括:

- 重命名并移动了多个与发票核销相关的组件,以便于在新的`InvoiceVerification`目录中集中管理。
- 对`InvoiceVerificationModal`进行了清理,并更新了相关的导入路径。
- 移除了未使用的常量导入和组件,以简化代码并提高可维护性。- 更新了`index.tsx`中的导入路径,以确保正确引入新的组件和常量。
- 修改了`constant.tsx`中的部分导出,以适应新的核销功能。

此次重构旨在为系统引入核销功能,提高发票管理的灵活性和效率。通过整理和更新代码结构,也使得代码更加清晰,便于后续的维护和开发。```
src/pages/Invoice/InvoiceVerification/components/AddInvoiceDrawerForm.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { PAYEE_OPTIONS } from '@/pages/Order/constant';
  3 +import {
  4 + postServiceInvoiceAddInvoice,
  5 + postServiceOrderQuerySalesCode,
  6 +} from '@/services';
  7 +import { enumToSelect } from '@/utils';
  8 +import { PlusOutlined } from '@ant-design/icons';
  9 +import {
  10 + DrawerForm,
  11 + ProFormDateTimePicker,
  12 + ProFormGroup,
  13 + ProFormList,
  14 + ProFormMoney,
  15 + ProFormSelect,
  16 + ProFormText,
  17 + ProFormTextArea,
  18 +} from '@ant-design/pro-components';
  19 +import { Button, Form, message } from 'antd';
  20 +import { useEffect, useState } from 'react';
  21 +
  22 +export default ({ onClose }) => {
  23 + const [form] = Form.useForm<{
  24 + invoiceNumber: '';
  25 + invoiceStatus: '';
  26 + purchaser: '';
  27 + payee: '';
  28 + contacts: '';
  29 + sale: '';
  30 + invoicingTime: '';
  31 + notes: '';
  32 + mainOrderIdObjs: [
  33 + {
  34 + mainOrderId: '';
  35 + },
  36 + ];
  37 + money: '';
  38 + }>();
  39 + const [salesCodeOptions, setSalesCodeOptions] = useState([]);
  40 + const getSalesCodeOptions = async () => {
  41 + const res = await postServiceOrderQuerySalesCode();
  42 + let options = res.data?.map((item) => {
  43 + return {
  44 + label: item.userName,
  45 + value: item.userName,
  46 + number: item.number,
  47 + };
  48 + });
  49 + setSalesCodeOptions(options);
  50 + };
  51 + useEffect(() => {
  52 + getSalesCodeOptions();
  53 + }, []);
  54 + return (
  55 + <DrawerForm<{
  56 + invoiceNumber: string;
  57 + invoiceStatus: string;
  58 + purchaser: string;
  59 + payee: string;
  60 + contacts: string;
  61 + sale: string;
  62 + invoicingTime: Date;
  63 + notes: string;
  64 + mainOrderIdObjs: [
  65 + {
  66 + mainOrderId: string;
  67 + },
  68 + ];
  69 + money: string;
  70 + }>
  71 + title="新增开票"
  72 + resize={{
  73 + onResize() {
  74 + console.log('resize!');
  75 + },
  76 + maxWidth: window.innerWidth * 0.8,
  77 + minWidth: 500,
  78 + }}
  79 + form={form}
  80 + trigger={
  81 + <Button type="primary">
  82 + <PlusOutlined />
  83 + 新增
  84 + </Button>
  85 + }
  86 + autoFocusFirstInput
  87 + drawerProps={{
  88 + destroyOnClose: true,
  89 + }}
  90 + submitTimeout={2000}
  91 + onFinish={async (values) => {
  92 + console.log(values);
  93 + const mainOrderIds = values.mainOrderIdObjs.flatMap(
  94 + (item) => item.mainOrderId,
  95 + );
  96 + let attrs = { ...values, mainOrderIds };
  97 + let res = await postServiceInvoiceAddInvoice({
  98 + data: { ...attrs },
  99 + });
  100 + if (res.result === RESPONSE_CODE.SUCCESS) {
  101 + message.success(res.message);
  102 + } else {
  103 + message.error(res.message);
  104 + return false;
  105 + }
  106 + onClose();
  107 + // 不返回不会关闭弹框
  108 + return true;
  109 + }}
  110 + >
  111 + <ProFormText
  112 + name="invoiceNumber"
  113 + width="md"
  114 + label="发票号码"
  115 + placeholder="请输入名称"
  116 + rules={[{ required: true, message: '请输入名称!' }]}
  117 + />
  118 + <ProFormSelect
  119 + name="invoiceStatus"
  120 + label="发票类型"
  121 + valueEnum={{
  122 + SPECIALLY_INVOICED: '专票',
  123 + COMMON_INVOICED: '普票',
  124 + }}
  125 + rules={[{ required: true, message: '请选择发票类型!' }]}
  126 + />
  127 + <ProFormText
  128 + name="purchaser"
  129 + width="md"
  130 + label="购买方"
  131 + placeholder="请输入购买方"
  132 + rules={[{ required: true, message: '请输入购买方!' }]}
  133 + />
  134 + <ProFormSelect
  135 + placeholder="收款单位"
  136 + name="payee"
  137 + width="lg"
  138 + key="payee"
  139 + showSearch
  140 + label="开票收款单位"
  141 + tooltip="财务开票将依据这个字段,选择对应的公司开票"
  142 + options={enumToSelect(PAYEE_OPTIONS)}
  143 + />
  144 + <ProFormText
  145 + name="contacts"
  146 + width="md"
  147 + label="联系人"
  148 + placeholder="请输入联系人"
  149 + rules={[{ required: true, message: '请输入联系人!' }]}
  150 + />
  151 + <ProFormSelect
  152 + name="sale"
  153 + key="sale"
  154 + width="lg"
  155 + showSearch
  156 + label="销售代表"
  157 + placeholder="请选择销售代表"
  158 + options={salesCodeOptions}
  159 + />
  160 + <ProFormDateTimePicker
  161 + name="invoicingTime"
  162 + label="开票时间"
  163 + fieldProps={{
  164 + format: (value) => value.format('YYYY-MM-DD'),
  165 + }}
  166 + rules={[{ required: true, message: '请输入开票时间!' }]}
  167 + />
  168 + <ProFormTextArea name="notes" label="备注" placeholder="请输入名称" />
  169 + <ProFormList
  170 + name="mainOrderIdObjs"
  171 + label="订单号"
  172 + min={1}
  173 + copyIconProps={false}
  174 + deleteIconProps={{
  175 + tooltipText: '删除',
  176 + }}
  177 + initialValue={[
  178 + {
  179 + mainOrderId: '',
  180 + },
  181 + ]}
  182 + >
  183 + <ProFormGroup key="group">
  184 + <ProFormText
  185 + rules={[{ required: true, message: '请输入关联订单!' }]}
  186 + name="mainOrderId"
  187 + />
  188 + </ProFormGroup>
  189 + </ProFormList>
  190 + <ProFormMoney
  191 + label="金额"
  192 + name="money"
  193 + customSymbol="¥"
  194 + min={0}
  195 + rules={[{ required: true, message: '请输入金额!' }]}
  196 + />
  197 + </DrawerForm>
  198 + );
  199 +};
... ...
src/pages/Invoice/InvoiceVerification/components/BankChooseModal.tsx 0 → 100644
  1 +import EllipsisDiv from '@/components/Div/EllipsisDiv';
  2 +import { RESPONSE_CODE } from '@/constants/enum';
  3 +import { INVOCING_STATUS, PAYEE_OPTIONS } from '@/pages/Order/constant';
  4 +import {
  5 + postServiceBankStatementQueryBankStatement,
  6 + postServiceInvoiceInvoiceWriteOff,
  7 +} from '@/services';
  8 +import { FloatAdd, FloatSub, enumValueToLabel, formatDateTime } from '@/utils';
  9 +import { formatDate } from '@/utils/time';
  10 +
  11 +import { ActionType, ProCard, ProTable } from '@ant-design/pro-components';
  12 +import { Button, Divider, Flex, Modal, Tag, message } from 'antd';
  13 +import { useRef, useState } from 'react';
  14 +import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../../constant';
  15 +import '../index.less';
  16 +
  17 +export default ({ loadInvoiceData, invoiceId, setVisible, onClose }) => {
  18 + const [selectedStatement, setSelectedStatement] = useState([]);
  19 + const [selectedStatementIdSet, setSelectedStatementIdSet] = useState(
  20 + new Set(),
  21 + );
  22 + const [totalAmount, setTotalAmount] = useState(0);
  23 +
  24 + // 添加元素到Set
  25 + const addElement = (element) => {
  26 + setSelectedStatementIdSet((prevSet) => new Set([...prevSet, element]));
  27 + };
  28 +
  29 + // 从Set中删除元素
  30 + const removeElement = (element) => {
  31 + setSelectedStatementIdSet((prevSet) => {
  32 + const newSet = new Set(prevSet);
  33 + newSet.delete(element);
  34 + return newSet;
  35 + });
  36 + };
  37 +
  38 + const [btnLoading, setBtnLoading] = useState(false);
  39 +
  40 + const actionRef = useRef<ActionType>();
  41 + const getTableCellText = (target: any) => {
  42 + if (!target) {
  43 + return '';
  44 + }
  45 +
  46 + if (target.props) {
  47 + return target.props.text;
  48 + }
  49 +
  50 + return target;
  51 + };
  52 +
  53 + /**
  54 + * 加载列表表格的各个列格式
  55 + */
  56 + const bankStatementColumnsInit = () => {
  57 + let columns = BANK_STATEMENT_COLUMNS.map((item) => {
  58 + let newItem = { ...item };
  59 + let dataIndex = item.dataIndex;
  60 + let dataType = item.valueType;
  61 +
  62 + if (item.dataIndex === 'status') {
  63 + newItem.hideInSearch = true;
  64 + }
  65 +
  66 + newItem.render = (text, record) => {
  67 + let textValue = record[dataIndex];
  68 +
  69 + if (dataType === 'date') {
  70 + textValue = formatDate(textValue);
  71 + }
  72 +
  73 + if (dataType === 'dateTime') {
  74 + textValue = formatDateTime(textValue);
  75 + }
  76 +
  77 + if (dataType === 'money') {
  78 + textValue = '¥' + textValue;
  79 + }
  80 +
  81 + switch (dataIndex) {
  82 + case 'invoiceStatus':
  83 + return (
  84 + <EllipsisDiv
  85 + text={enumValueToLabel(
  86 + getTableCellText(textValue),
  87 + INVOCING_STATUS,
  88 + )}
  89 + />
  90 + );
  91 +
  92 + case 'status': {
  93 + //这里状态不显示在筛选条件中,只能筛异常的流水
  94 + return (
  95 + <EllipsisDiv
  96 + text={enumValueToLabel(
  97 + getTableCellText(textValue),
  98 + INVOICE_STATUS,
  99 + )}
  100 + />
  101 + );
  102 + }
  103 + case 'payee':
  104 + return (
  105 + <EllipsisDiv
  106 + text={enumValueToLabel(
  107 + getTableCellText(textValue),
  108 + PAYEE_OPTIONS,
  109 + )}
  110 + />
  111 + );
  112 +
  113 + default:
  114 + return <EllipsisDiv text={getTableCellText(textValue)} />;
  115 + }
  116 + };
  117 +
  118 + return newItem;
  119 + });
  120 +
  121 + columns.push({
  122 + title: '操作',
  123 + valueType: 'option',
  124 + key: 'option',
  125 + fixed: 'right',
  126 + width: 70,
  127 + render: (text, record) => [
  128 + <Button
  129 + className="p-0"
  130 + key="choose"
  131 + type="link"
  132 + onClick={() => {
  133 + let amount = record.loanAmount || record.transactionAmount || 0;
  134 +
  135 + //已经选中,取消选中
  136 + if (selectedStatementIdSet.has(record.id)) {
  137 + setSelectedStatement(
  138 + selectedStatement.filter((item) => {
  139 + return item.id !== record.id;
  140 + }),
  141 + );
  142 + removeElement(record.id);
  143 + setTotalAmount(parseFloat(FloatSub(totalAmount, amount)));
  144 + } else {
  145 + //添加到已选中区域中
  146 + let newSelectedStatement = [...selectedStatement];
  147 + newSelectedStatement.push(record);
  148 + setSelectedStatement(newSelectedStatement);
  149 + addElement(record.id);
  150 + setTotalAmount(FloatAdd(totalAmount, amount));
  151 + }
  152 + }}
  153 + >
  154 + {selectedStatementIdSet.has(record.id) ? '取消选中' : '选中'}
  155 + </Button>,
  156 + ],
  157 + });
  158 +
  159 + return columns;
  160 + };
  161 +
  162 + /**
  163 + * 删除已选中
  164 + * @param record
  165 + */
  166 + const removeSelectedStatement = (record: any) => {
  167 + setSelectedStatement(
  168 + selectedStatement.filter((item) => {
  169 + return item.id !== record.id;
  170 + }),
  171 + );
  172 + removeElement(record.id);
  173 + };
  174 +
  175 + const showSelectedStatement = () => {
  176 + let i = 0;
  177 +
  178 + let tags = selectedStatement.map((item) => {
  179 + let tagText = item.id;
  180 +
  181 + if (item.payeePayerName) {
  182 + tagText += ' ' + item.payeePayerName + ' ';
  183 + }
  184 +
  185 + if (item.loanAmount) {
  186 + tagText += item.loanAmount + ' ';
  187 + }
  188 +
  189 + if (item.transactionAmount) {
  190 + tagText += item.transactionAmount;
  191 + }
  192 +
  193 + return (
  194 + <Tag
  195 + key={i++}
  196 + closable={true}
  197 + style={{ userSelect: 'none' }}
  198 + color="blue"
  199 + onClose={(e) => {
  200 + e.preventDefault(); //需要加上这句代码,不然删除tag时,当前tag的下一个tag会被设置ant-tag-hidden
  201 + removeSelectedStatement(item);
  202 + }}
  203 + >
  204 + <span>{tagText}</span>
  205 + </Tag>
  206 + );
  207 + });
  208 +
  209 + return tags;
  210 + };
  211 +
  212 + return (
  213 + <>
  214 + <Modal
  215 + open
  216 + width="80%"
  217 + title="添加银行流水"
  218 + className="bank-statement-choose"
  219 + onOk={async () => {
  220 + setBtnLoading(true);
  221 + let bankStatementIds = selectedStatement?.map((item) => {
  222 + return item.id;
  223 + });
  224 + let res = await postServiceInvoiceInvoiceWriteOff({
  225 + data: {
  226 + invoiceId: invoiceId,
  227 + bankStatementIds: bankStatementIds,
  228 + },
  229 + });
  230 + setTimeout(() => {
  231 + loadInvoiceData();
  232 + }, 500);
  233 + if (res.result === RESPONSE_CODE.SUCCESS) {
  234 + if (res.data?.length > 0) {
  235 + message.info(res.data);
  236 + } else {
  237 + message.success(res.message);
  238 + }
  239 +
  240 + onClose();
  241 + }
  242 + setBtnLoading(false);
  243 + }}
  244 + okButtonProps={{
  245 + loading: btnLoading,
  246 + }}
  247 + onCancel={() => {
  248 + setVisible(false);
  249 + }}
  250 + >
  251 + <Divider orientation="left" plain>
  252 + 已选中(合计:¥{totalAmount})
  253 + </Divider>
  254 + <ProCard className="mb-[16px]" bordered style={{}}>
  255 + <Flex wrap="wrap" gap="small">
  256 + {showSelectedStatement()}
  257 + </Flex>
  258 + </ProCard>
  259 +
  260 + <ProTable
  261 + columns={bankStatementColumnsInit()}
  262 + actionRef={actionRef}
  263 + cardBordered
  264 + pagination={{
  265 + pageSize: 10,
  266 + }}
  267 + editable={{
  268 + type: 'multiple',
  269 + onSave: async (rowKey, data) => {
  270 + console.log(rowKey, data);
  271 + },
  272 + actionRender: (row, config, defaultDom) => [
  273 + defaultDom.save,
  274 + defaultDom.cancel,
  275 + ],
  276 + }}
  277 + request={async (params) => {
  278 + const res = await postServiceBankStatementQueryBankStatement({
  279 + data: { ...params, status: 'ABNORMAL' },
  280 + });
  281 + if (res) {
  282 + return {
  283 + data: res?.data?.data || [],
  284 + total: res?.data?.total || 0,
  285 + };
  286 + }
  287 + }}
  288 + columnsState={{
  289 + persistenceKey: 'pro-table-singe-demos',
  290 + persistenceType: 'localStorage',
  291 + defaultValue: {
  292 + option: { fixed: 'right', disable: true },
  293 + },
  294 + onChange(value) {
  295 + console.log('value: ', value);
  296 + },
  297 + }}
  298 + rowKey="id"
  299 + search={{
  300 + labelWidth: 'auto',
  301 + }}
  302 + options={{
  303 + setting: {
  304 + listsHeight: 400,
  305 + },
  306 + }}
  307 + form={{}}
  308 + dateFormatter="string"
  309 + headerTitle="银行流水列表"
  310 + scroll={{ x: 1400, y: 360 }}
  311 + toolBarRender={() => []}
  312 + />
  313 + </Modal>
  314 + </>
  315 + );
  316 +};
... ...
src/pages/Invoice/InvoiceVerification/components/BankImportModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { blobToJson } from '@/utils';
  3 +
  4 +import { UploadOutlined } from '@ant-design/icons';
  5 +import { ModalForm } from '@ant-design/pro-components';
  6 +import { Button, Form, Upload, UploadFile, UploadProps, message } from 'antd';
  7 +import { RcFile } from 'antd/es/upload';
  8 +import axios from 'axios';
  9 +import { useState } from 'react';
  10 +
  11 +// import { cloneDeep } from 'lodash';
  12 +export default ({ setVisible, onClose }) => {
  13 + const [form] = Form.useForm<{ name: string; company: string }>();
  14 + const [fileList, setFileList] = useState<UploadFile[]>([]);
  15 + const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
  16 + setFileList(newFileList);
  17 +
  18 + const [messageApi, contextHolder] = message.useMessage();
  19 + const [uploadLoading, setUploading] = useState(false);
  20 + console.log(uploadLoading);
  21 +
  22 + const exportLoading = (content: string) => {
  23 + messageApi.open({
  24 + type: 'loading',
  25 + content: content,
  26 + duration: 0,
  27 + });
  28 + };
  29 +
  30 + const downloadImportTemplate = async () => {
  31 + exportLoading('正在下载......');
  32 + axios({
  33 + url: '/api/service/bankStatement/exportTemplate',
  34 + method: 'post',
  35 + responseType: 'blob',
  36 + headers: { Authorization: localStorage.getItem('token') },
  37 + })
  38 + .then((response) => {
  39 + // 创建一个新的 Blob 对象,它包含了服务器响应的数据(即你的 Excel 文件)
  40 + const blob = new Blob([response.data]); // Excel 的 MIME 类型
  41 + const downloadUrl = window.URL.createObjectURL(blob);
  42 + const a = document.createElement('a');
  43 + a.href = downloadUrl;
  44 + a.download = '导入模板.xlsx'; // 你可以为文件命名
  45 + document.body.appendChild(a);
  46 + a.click(); // 模拟点击操作来下载文件
  47 + URL.revokeObjectURL(downloadUrl); // 释放掉 blob 对象所占用的内存
  48 + document.body.removeChild(a);
  49 + })
  50 + .catch((error) => {
  51 + // 处理错误
  52 + console.error('导出错误', error);
  53 + })
  54 + .finally(() => {
  55 + messageApi.destroy();
  56 + });
  57 + };
  58 + const handleUpload = async () => {
  59 + exportLoading('正在导入......');
  60 +
  61 + const formData = new FormData();
  62 + fileList.forEach((file) => {
  63 + formData.append('file', file.originFileObj as RcFile);
  64 + });
  65 +
  66 + setUploading(true);
  67 +
  68 + axios({
  69 + url: '/api/service/bankStatement/importBankStatementForm',
  70 + method: 'post',
  71 + responseType: 'blob',
  72 + headers: {
  73 + Authorization: localStorage.getItem('token'),
  74 + 'Content-Type':
  75 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  76 + },
  77 + data: formData,
  78 + })
  79 + .then((response) => {
  80 + let data = response.data;
  81 + if (data.type === 'application/json') {
  82 + blobToJson(data).then((dataJson) => {
  83 + if (dataJson?.result === RESPONSE_CODE.SUCCESS) {
  84 + message.success(dataJson?.message);
  85 + onClose();
  86 + } else {
  87 + message.error(dataJson?.message);
  88 + }
  89 + });
  90 + } else {
  91 + message.error('上传失败,已下载错误信息表格');
  92 + // 创建一个新的 Blob 对象,它包含了服务器响应的数据(即你的 Excel 文件)
  93 + const blob = new Blob([response.data]); // Excel 的 MIME 类型
  94 + const downloadUrl = window.URL.createObjectURL(blob);
  95 + const a = document.createElement('a');
  96 + a.href = downloadUrl;
  97 + a.download = '银行流水导入模板.xlsx'; // 你可以为文件命名
  98 + document.body.appendChild(a);
  99 + a.click(); // 模拟点击操作来下载文件
  100 + URL.revokeObjectURL(downloadUrl); // 释放掉 blob 对象所占用的内存
  101 + document.body.removeChild(a);
  102 + }
  103 + })
  104 + .catch((error) => {
  105 + // 处理错误
  106 + message.error('系统出现异常了,请联系管理员', error);
  107 + })
  108 + .finally(() => {
  109 + setUploading(false);
  110 + messageApi.destroy();
  111 + });
  112 + };
  113 + const props: UploadProps = {
  114 + onRemove: (file) => {
  115 + const index = fileList.indexOf(file);
  116 + const newFileList = fileList.slice();
  117 + newFileList.splice(index, 1);
  118 + setFileList(newFileList);
  119 + },
  120 + beforeUpload: (file) => {
  121 + setFileList([...fileList, file]);
  122 +
  123 + return false;
  124 + },
  125 + fileList,
  126 + onChange: handleChange,
  127 + accept: '.xlsx',
  128 + };
  129 +
  130 + return (
  131 + <>
  132 + <ModalForm<{
  133 + name: string;
  134 + company: string;
  135 + }>
  136 + width={500}
  137 + open
  138 + title="银行流水导入"
  139 + form={form}
  140 + autoFocusFirstInput
  141 + modalProps={{
  142 + okText: '确定',
  143 + cancelText: '取消',
  144 + destroyOnClose: true,
  145 + onCancel: () => {
  146 + setVisible(false);
  147 + },
  148 + }}
  149 + onFinish={async () => {
  150 + handleUpload();
  151 + }}
  152 + onOpenChange={setVisible}
  153 + >
  154 + <div className="py-4 font-semibold">
  155 + 导入银行流水
  156 + <Button type="link" onClick={downloadImportTemplate}>
  157 + 下载导入模板
  158 + </Button>
  159 + </div>
  160 + <Upload {...props}>
  161 + <Button icon={<UploadOutlined />} disabled={fileList.length > 0}>
  162 + 点击选择文件
  163 + </Button>
  164 + </Upload>
  165 + </ModalForm>
  166 +
  167 + {contextHolder}
  168 + </>
  169 + );
  170 +};
... ...
src/pages/Invoice/InvoiceVerification/components/Invoice.tsx 0 → 100644
  1 +import styled from 'styled-components';
  2 +const InvoiceTmpDiv = styled.div`
  3 + font-size: 12px;
  4 + width: 1120px;
  5 + .title {
  6 + font-size: 26px;
  7 + color: #b16363;
  8 + text-align: center;
  9 + line-height: 56px;
  10 + padding-top: 0;
  11 + }
  12 + .extra {
  13 + color: #b15b16;
  14 + .content {
  15 + color: #181818;
  16 + }
  17 + }
  18 + .height84 {
  19 + height: 110px;
  20 + }
  21 + .row {
  22 + border: 2px solid #b16363;
  23 + border-bottom: none;
  24 + color: #b15b16;
  25 + .content {
  26 + color: #181818;
  27 + }
  28 + }
  29 + .last-row {
  30 + .content {
  31 + color: #181818;
  32 + }
  33 + color: #b15b16;
  34 + border-top: 2px solid #b16363;
  35 + }
  36 + .label {
  37 + width: 78px;
  38 + display: inline-block;
  39 + text-align-last: justify;
  40 + text-align: justify;
  41 + }
  42 + .longLabel {
  43 + width: 178px;
  44 + display: inline-block;
  45 + text-align-last: justify;
  46 + text-align: justify;
  47 + }
  48 + .title-label {
  49 + width: 52px;
  50 + }
  51 +`;
  52 +const Row = styled.div`
  53 + .col_1 {
  54 + width: 2.65%;
  55 + borderleft: none;
  56 + }
  57 + .col_4 {
  58 + width: 3.75%;
  59 + }
  60 + .col_9 {
  61 + width: 7.4%;
  62 + }
  63 + .col_2 {
  64 + width: 8.33%;
  65 + }
  66 + .col_3 {
  67 + width: 12.5%;
  68 + }
  69 + .col_5 {
  70 + width: 20.83%;
  71 + }
  72 + .col_6 {
  73 + width: 42.5%;
  74 + }
  75 + .col_7 {
  76 + width: 29.16%;
  77 + }
  78 + .col_8 {
  79 + width: 33.33%;
  80 + }
  81 + .col_14 {
  82 + width: 58.33%;
  83 + }
  84 + .col_15 {
  85 + width: 42.5%;
  86 + }
  87 + .col_17 {
  88 + width: 70.83%;
  89 + }
  90 + .col_18 {
  91 + width: 100%;
  92 + }
  93 + .col_24 {
  94 + width: 100%;
  95 + }
  96 +`;
  97 +const Col = styled.span`
  98 + display: inline-block;
  99 + padding: 8px;
  100 + box-sizing: border-box;
  101 + vertical-align: middle;
  102 + border-left: 2px solid #b16363;
  103 + height: 100%;
  104 + &.no-border {
  105 + border-left: none;
  106 + }
  107 + .text-center {
  108 + text-align: center;
  109 + border-left: none;
  110 + border-right: none;
  111 + }
  112 + &.transparent-border {
  113 + border-left: 2px solid rgba(0, 0, 0, 0);
  114 + }
  115 + &.invoice-number {
  116 + border-left: none;
  117 + color: #b16363;
  118 + padding: 0 0 0 800px;
  119 + font-size: 14px;
  120 + }
  121 +`;
  122 +const UnderLine = styled.div`
  123 + border: 2px solid #b16363;
  124 + width: 325px;
  125 + height: 8px;
  126 + margin: -1% 0 2% 35%;
  127 + border-left: none;
  128 + border-right: none;
  129 +`;
  130 +const InvoiceInfo = styled.span`
  131 + color: black;
  132 +`;
  133 +const TitleDescription = styled.div`
  134 + margin-top: 4.2%;
  135 +`;
  136 +const ProjectContainer = styled.div`
  137 + width: 100%;
  138 + height: 160px;
  139 + border-top: 2px solid #b16363;
  140 + border-right: 2px solid #b16363;
  141 + border-left: 2px solid #b16363;
  142 + overflow: auto;
  143 + .single-project {
  144 + width: 100%;
  145 + height: 30px;
  146 + }
  147 +`;
  148 +export default ({ data }) => {
  149 + return (
  150 + <div>
  151 + <InvoiceTmpDiv>
  152 + <Row>
  153 + <Col className="col_18 invoice-number">
  154 + 发票号码:<InvoiceInfo>{data.invoiceNumber}</InvoiceInfo>
  155 + </Col>
  156 + <Col className="title col_18 no-border">
  157 + 电子发票(
  158 + {data.type === 'SPECIAL_TICKET' ? '增值税专用发票' : '普通发票'})
  159 + </Col>
  160 + <UnderLine className="UnderLine">
  161 + <div></div>
  162 + </UnderLine>
  163 + </Row>
  164 + <Row className="row height84">
  165 + <Col className="col_1 no-border">购买方信息</Col>
  166 + <Col className="col_15">
  167 + <TitleDescription>
  168 + <span className="label">名称</span>:
  169 + <span className="content">{data.partyAName}</span>
  170 + </TitleDescription>
  171 + <TitleDescription>
  172 + <span className="longLabel">统一社会信用代码/纳税人识别号</span>:
  173 + <span className="content">{data.partyATaxid}</span>
  174 + </TitleDescription>
  175 + </Col>
  176 + <Col className="col_1">销售方信息</Col>
  177 + <Col className="col_6">
  178 + <TitleDescription>
  179 + <span className="label">名称</span>:
  180 + <span className="content">{data.partyBName}</span>
  181 + </TitleDescription>
  182 + <TitleDescription>
  183 + <span className="longLabel">统一社会信用代码/纳税人识别号</span>:
  184 + <span className="content">{data.partyBTaxid}</span>
  185 + </TitleDescription>
  186 + </Col>
  187 + </Row>
  188 + <Row className="row">
  189 + <Col className="col_7 no-border">
  190 + <div className="text-center">项目名称</div>
  191 + </Col>
  192 + <Col className="col_5">
  193 + <div className="text-center">规格型号</div>
  194 + </Col>
  195 + <Col className="">
  196 + <div className="text-center">单位</div>
  197 + </Col>
  198 + <Col className="col_2">
  199 + <div className="text-center">数量</div>
  200 + </Col>
  201 + <Col className="col_2">
  202 + <div className="text-center">单价</div>
  203 + </Col>
  204 + <Col className="col_2">
  205 + <div className="text-center">金额</div>
  206 + </Col>
  207 + <Col className="">
  208 + <div className="text-center">税率/征收率</div>
  209 + </Col>
  210 + <Col className="col_2">
  211 + <div className="text-center">税额</div>
  212 + </Col>
  213 + </Row>
  214 + <Row>
  215 + <ProjectContainer>
  216 + {data &&
  217 + data.invoiceDetails?.map((item) => {
  218 + const {
  219 + taxPrice,
  220 + totalPrice,
  221 + specification,
  222 + projectName,
  223 + quantity,
  224 + price,
  225 + taxRate,
  226 + unit,
  227 + } = item;
  228 + return (
  229 + <div className="single-project" key={item.id}>
  230 + <Col
  231 + className="col_7 transparent-border"
  232 + key={'projectName'}
  233 + >
  234 + <div className="text-center">{projectName}</div>
  235 + </Col>
  236 + <Col
  237 + className="col_5 transparent-border"
  238 + key={'specification'}
  239 + >
  240 + <div className="text-center">{specification}</div>
  241 + </Col>
  242 + <Col className=" transparent-border" key={'unit'}>
  243 + <div className="text-center">{unit}</div>
  244 + </Col>
  245 + <Col className="col_2 transparent-border" key={'quantity'}>
  246 + <div className="text-center">{quantity}</div>
  247 + </Col>
  248 + <Col className="col_2 transparent-border" key={'price'}>
  249 + <div className="text-center">{price}</div>
  250 + </Col>
  251 + <Col
  252 + className="col_2 transparent-border"
  253 + key={'totalPrice'}
  254 + >
  255 + <div className="text-center">{totalPrice}</div>
  256 + </Col>
  257 + <Col className=" transparent-border" key={'taxRate'}>
  258 + <div className="text-center">{taxRate}</div>
  259 + </Col>
  260 + <Col className="col_2 transparent-border" key={'taxPrice'}>
  261 + <div className="text-center">{taxPrice}</div>
  262 + </Col>
  263 + </div>
  264 + );
  265 + })}
  266 + </ProjectContainer>
  267 + </Row>
  268 + <Row className="row">
  269 + <Col className="col_15 no-border">
  270 + 价税合计(大写)
  271 + <InvoiceInfo>{data.totalPriceText}</InvoiceInfo>
  272 + </Col>
  273 + <Col className="no-border">
  274 + (小写)<InvoiceInfo>¥{data.totalPrice}</InvoiceInfo>
  275 + </Col>
  276 + </Row>
  277 + <Row className="row height84">
  278 + <Col className="col_1 no-border">备注</Col>
  279 + <Col className="col_7">
  280 + <InvoiceInfo>{data.comment}</InvoiceInfo>
  281 + </Col>
  282 + </Row>
  283 + <Row className="last-row">
  284 + <Col className="col_6 no-border">
  285 + 开票人:<InvoiceInfo>{data.invoicingPerson}</InvoiceInfo>
  286 + </Col>
  287 + </Row>
  288 + </InvoiceTmpDiv>
  289 + </div>
  290 + );
  291 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceDetailImportModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceInvoiceImportInvoiceDetails } from '@/services';
  3 +import { ModalForm, ProFormUploadDragger } from '@ant-design/pro-components';
  4 +import { Button, Form, message } from 'antd';
  5 +
  6 +export default ({ recordId }) => {
  7 + const [form] = Form.useForm();
  8 + return (
  9 + <ModalForm
  10 + title="新建表单"
  11 + trigger={<Button type="primary">导入明细</Button>}
  12 + form={form}
  13 + autoFocusFirstInput
  14 + modalProps={{
  15 + destroyOnClose: true,
  16 + onCancel: () => console.log('run'),
  17 + }}
  18 + submitTimeout={2000}
  19 + onFinish={async (values) => {
  20 + const formData = new FormData();
  21 + // console.log(fileList[0] as RcFile)
  22 + // formData.append('file', fileList[0] as RcFile);
  23 + formData.append('invoiceRecordId', recordId);
  24 + formData.append('detailsExcel', values.detailsExcel[0].originFileObj);
  25 + // You can use any AJAX library you like
  26 + const res = await postServiceInvoiceImportInvoiceDetails({
  27 + data: formData,
  28 + headers: {
  29 + 'Content-Type':
  30 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  31 + },
  32 + });
  33 + if (res.result === RESPONSE_CODE.SUCCESS) {
  34 + message.success('导入成功');
  35 + return true;
  36 + }
  37 + }}
  38 + >
  39 + <ProFormUploadDragger name="detailsExcel" label="导入明细表" />
  40 + </ModalForm>
  41 + );
  42 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceDetailTable.tsx 0 → 100644
  1 +import InvoiceDetailImportModal from '@/pages/Invoice/InvoiceVerification/components/InvoiceDetailImportModal';
  2 +import { InvoiceProjectSelect } from '@/pages/Invoice/InvoiceVerification/components/InvoiceProjectSelect';
  3 +import {
  4 + ActionType,
  5 + EditableProTable,
  6 + ProCard,
  7 + ProColumns,
  8 + ProFormField,
  9 +} from '@ant-design/pro-components';
  10 +import { useEffect, useRef, useState } from 'react';
  11 +
  12 +export default ({ recordId, details, updateDetails, readOnly }) => {
  13 + const [editableKeys, setEditableRowKeys] = useState([]);
  14 + const ref = useRef<ActionType>();
  15 + useEffect(() => {
  16 + updateDetails(details);
  17 + }, []);
  18 +
  19 + useEffect(() => {
  20 + setEditableRowKeys(details?.map((item) => item.tid));
  21 + }, [details]);
  22 + const columns: ProColumns[] = [
  23 + {
  24 + title: '项目名称',
  25 + dataIndex: 'projectName',
  26 + width: 200,
  27 + ellipsis: true,
  28 + readonly: readOnly,
  29 + renderFormItem: () => {
  30 + return <InvoiceProjectSelect readOnly={readOnly} />;
  31 + },
  32 + },
  33 + {
  34 + title: '规格型号',
  35 + readonly: readOnly,
  36 + dataIndex: 'specification',
  37 + valueType: 'text',
  38 + ellipsis: true,
  39 + },
  40 + {
  41 + title: '单位',
  42 + readonly: readOnly,
  43 + dataIndex: 'unit',
  44 + valueType: 'text',
  45 + ellipsis: true,
  46 + },
  47 + {
  48 + title: '数量',
  49 + readonly: readOnly,
  50 + dataIndex: 'quantity',
  51 + valueType: 'digit',
  52 + ellipsis: true,
  53 + },
  54 + {
  55 + title: '单价',
  56 + readonly: readOnly,
  57 + dataIndex: 'price',
  58 + valueType: 'digit',
  59 + ellipsis: true,
  60 + },
  61 + {
  62 + title: '金额',
  63 + readonly: readOnly,
  64 + dataIndex: 'totalPrice',
  65 + valueType: 'digit',
  66 + ellipsis: true,
  67 + },
  68 + {
  69 + title: '税率/征收率',
  70 + readonly: true,
  71 + dataIndex: 'taxRate',
  72 + valueType: () => ({
  73 + type: 'percent',
  74 + }),
  75 + ellipsis: true,
  76 + },
  77 + {
  78 + title: '税额',
  79 + readonly: true,
  80 + dataIndex: 'taxPrice',
  81 + valueType: 'digit',
  82 + ellipsis: true,
  83 + },
  84 + {
  85 + title: '操作',
  86 + valueType: 'option',
  87 + width: 100,
  88 + render: () => {
  89 + return null;
  90 + },
  91 + },
  92 + ];
  93 +
  94 + return (
  95 + <>
  96 + <EditableProTable
  97 + columns={columns}
  98 + actionRef={ref}
  99 + rowKey="tid"
  100 + scroll={{
  101 + x: 960,
  102 + }}
  103 + value={details}
  104 + controlled={true}
  105 + recordCreatorProps={
  106 + readOnly
  107 + ? false
  108 + : {
  109 + newRecordType: 'dataSource',
  110 + record: () => ({
  111 + tid: Date.now(),
  112 + }),
  113 + }
  114 + }
  115 + toolBarRender={() => {
  116 + return [
  117 + <InvoiceDetailImportModal key={'import'} recordId={recordId} />,
  118 + ];
  119 + }}
  120 + editable={{
  121 + type: 'multiple',
  122 + editableKeys,
  123 + actionRender: (row, config, defaultDoms) => {
  124 + return [defaultDoms.delete];
  125 + },
  126 +
  127 + onValuesChange: (record, recordList) => {
  128 + //修改recordList中tid为record.tid的元素,将它的specification属性设置为invoiceProject的specification属性
  129 + const records = recordList.map((item) => {
  130 + return item;
  131 + });
  132 + updateDetails(records);
  133 + },
  134 + }}
  135 + />
  136 + {
  137 + <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
  138 + <ProFormField
  139 + ignoreFormItem
  140 + fieldProps={{
  141 + style: {
  142 + width: '100%',
  143 + },
  144 + }}
  145 + mode="read"
  146 + valueType="jsonCode"
  147 + text={JSON.stringify(details)}
  148 + />
  149 + </ProCard>
  150 + }
  151 + </>
  152 + );
  153 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceModal.tsx 0 → 100644
  1 +import Invoice from '@/pages/Invoice/InvoiceVerification/components/Invoice';
  2 +import { postServiceInvoiceGetInvoiceRecord } from '@/services';
  3 +import { ModalForm } from '@ant-design/pro-components';
  4 +import { Form } from 'antd';
  5 +import { useEffect, useState } from 'react';
  6 +
  7 +export default ({ recordId, getRecord, button }) => {
  8 + const [data, setData] = useState<any>({});
  9 + const getData = async () => {
  10 + let ret = await postServiceInvoiceGetInvoiceRecord({
  11 + query: {
  12 + id: recordId,
  13 + },
  14 + });
  15 + setData(ret.data);
  16 + };
  17 + useEffect(() => {
  18 + if (recordId) {
  19 + getData();
  20 + }
  21 + }, []);
  22 + const [form] = Form.useForm();
  23 + return (
  24 + <ModalForm
  25 + title="预览发票"
  26 + trigger={button ? button : <a type="primary">预览</a>}
  27 + onOpenChange={(open) => {
  28 + if (open) {
  29 + if (getRecord) {
  30 + setData(getRecord());
  31 + } else {
  32 + getData();
  33 + }
  34 + }
  35 + }}
  36 + width={1200}
  37 + form={form}
  38 + autoFocusFirstInput
  39 + submitter={false}
  40 + modalProps={{
  41 + destroyOnClose: true,
  42 + }}
  43 + >
  44 + <hr />
  45 + <Invoice data={data} />
  46 + </ModalForm>
  47 + );
  48 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceProjectSelect.tsx 0 → 100644
  1 +import { postServiceConstListInvoiceDetailNames } from '@/services';
  2 +import { Select, Tooltip } from 'antd';
  3 +import { useState } from 'react';
  4 +
  5 +export const InvoiceProjectSelect = ({ readOnly, value, onChange }) => {
  6 + const [options, setOptions] = useState<any[]>([]);
  7 + // 定义防抖函数
  8 + let timeoutId = null;
  9 + const fetchOptions = async (keywords) => {
  10 + clearTimeout(timeoutId);
  11 + timeoutId = setTimeout(async () => {
  12 + const res = await postServiceConstListInvoiceDetailNames({
  13 + data: {
  14 + nameLike: keywords,
  15 + },
  16 + });
  17 + const data = res.data;
  18 +
  19 + setOptions(
  20 + data.map((item) => {
  21 + console.log(item);
  22 + return {
  23 + key: item.id,
  24 + label:
  25 + '*' +
  26 + item.productAndServiceCatagoryAbbreviation +
  27 + '*' +
  28 + item?.name,
  29 + value:
  30 + '*' +
  31 + item.productAndServiceCatagoryAbbreviation +
  32 + '*' +
  33 + item?.name,
  34 + ...item,
  35 + };
  36 + }),
  37 + );
  38 + // 这里可以放置实际的搜索逻辑,比如发起网络请求等
  39 + }, 500); // 设置延迟时间,单位毫秒
  40 + };
  41 +
  42 + return readOnly ? (
  43 + <Tooltip title={value}>{value}</Tooltip>
  44 + ) : (
  45 + <Select
  46 + key="project"
  47 + /*readonly={readonly}*/
  48 + showSearch
  49 + placeholder="请选择开票项目"
  50 + filterOption={(input, option) => (option?.label ?? '').includes(input)}
  51 + onChange={(e) => {
  52 + onChange(e);
  53 + }}
  54 + defaultValue={value}
  55 + options={options}
  56 + onSearch={(e) => {
  57 + fetchOptions(e);
  58 + }}
  59 + />
  60 + );
  61 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceRecordDetailModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import InvoiceDetailTable from '@/pages/Invoice/InvoiceVerification/components/InvoiceDetailTable';
  3 +import {
  4 + postServiceConstGetPayeeEnum,
  5 + postServiceConstInvoiceType,
  6 + postServiceConstInvoicingType,
  7 + postServiceInvoiceGetInvoiceRecord,
  8 + postServiceInvoiceModifyRecord,
  9 +} from '@/services';
  10 +import { enumToSelect } from '@/utils';
  11 +import {
  12 + ModalForm,
  13 + ProCard,
  14 + ProForm,
  15 + ProFormInstance,
  16 + ProFormList,
  17 + ProFormSelect,
  18 + ProFormText,
  19 + ProFormTextArea,
  20 +} from '@ant-design/pro-components';
  21 +import { Button, Divider, Form, Space, message } from 'antd';
  22 +import { useEffect, useRef, useState } from 'react';
  23 +
  24 +export default ({ id, setVisible }) => {
  25 + const [readOnly, setReadOnly] = useState(true);
  26 + const [detailTableData, setDetailTableData] = useState([]);
  27 + const [payees, setPayees] = useState([]);
  28 + const [payeeNameOptions, setPayeeNameOptions] = useState([]);
  29 + const formRef = useRef<ProFormInstance>();
  30 + const [form] = Form.useForm();
  31 +
  32 + useEffect(() => {
  33 + console.log('id' + id);
  34 + const getPayees = async () => {
  35 + let res = await postServiceConstGetPayeeEnum();
  36 + setPayees(res.data);
  37 + let payeeNameOptions = res.data.map((item) => {
  38 + return {
  39 + label: item.payeeName,
  40 + value: item.payeeName,
  41 + };
  42 + });
  43 + setPayeeNameOptions(payeeNameOptions);
  44 + };
  45 + getPayees();
  46 + }, []);
  47 + const getRecord = async (id) => {
  48 + let ret = await postServiceInvoiceGetInvoiceRecord({
  49 + query: {
  50 + id: id,
  51 + },
  52 + });
  53 + console.log(ret.data);
  54 + const updatedInvoiceDetails = ret.data.invoiceDetails?.map(
  55 + (item, index) => ({
  56 + ...item, // 保留原有属性
  57 + tid: index + 1, // 添加tid属性,这里以T开头,后面跟索引+1,仅作示例,实际可根据需求生成tid
  58 + }),
  59 + );
  60 + setDetailTableData(updatedInvoiceDetails);
  61 + };
  62 + useEffect(() => {
  63 + getRecord(id);
  64 + }, []);
  65 +
  66 + const updateDetails = (values) => {
  67 + setDetailTableData(values);
  68 + };
  69 + return (
  70 + <>
  71 + <Space>
  72 + <ModalForm
  73 + open
  74 + title="发票详情"
  75 + formRef={formRef}
  76 + request={async () => {
  77 + let ret = await postServiceInvoiceGetInvoiceRecord({
  78 + query: {
  79 + id: id,
  80 + },
  81 + });
  82 + const data = ret.data;
  83 + const orderIdMap = data.orderIdMap;
  84 + const orderIdList = [];
  85 +
  86 + // 使用Object.entries()遍历属性
  87 + Object.entries(orderIdMap).forEach(([key, value]) => {
  88 + const orderId = {
  89 + mainId: key,
  90 + subIds: value,
  91 + };
  92 + orderIdList.push(orderId);
  93 + });
  94 + return {
  95 + ...data,
  96 + orderIdList: orderIdList,
  97 + };
  98 + }}
  99 + submitter={{
  100 + render: () => {
  101 + return [
  102 + <Button
  103 + type={readOnly ? 'primary' : 'default'}
  104 + key="ok"
  105 + onClick={() => {
  106 + setReadOnly(!readOnly);
  107 + }}
  108 + >
  109 + {readOnly ? '编辑' : '取消编辑'}
  110 + </Button>,
  111 + <>
  112 + {!readOnly && (
  113 + <Button
  114 + type="primary"
  115 + key="submit"
  116 + onClick={async () => {
  117 + const result = await postServiceInvoiceModifyRecord({
  118 + data: {
  119 + ...form.getFieldsValue(),
  120 + invoiceDetails: [...detailTableData],
  121 + },
  122 + });
  123 + if (result.result === RESPONSE_CODE.SUCCESS) {
  124 + message.success('提交成功');
  125 + }
  126 + setVisible(false);
  127 + return true;
  128 + }}
  129 + >
  130 + 提交
  131 + </Button>
  132 + )}
  133 + </>,
  134 + /*<Button
  135 + type={'default'}
  136 + key="ok"
  137 + onClick={() => {
  138 + setVisible(false)
  139 + }}
  140 + >
  141 + 取消
  142 + </Button>,*/
  143 + ];
  144 + },
  145 + }}
  146 + width={1200}
  147 + form={form}
  148 + autoFocusFirstInput
  149 + modalProps={{
  150 + destroyOnClose: true,
  151 + onCancel: () => {
  152 + setVisible(false);
  153 + },
  154 + }}
  155 + grid={true}
  156 + layout="horizontal"
  157 + rowProps={{
  158 + gutter: [0, 0],
  159 + }}
  160 + submitTimeout={2000}
  161 + onFinish={async (values) => {
  162 + const result = await postServiceInvoiceModifyRecord({
  163 + data: {
  164 + ...values,
  165 + invoiceDetails: {
  166 + ...detailTableData,
  167 + },
  168 + },
  169 + });
  170 + if (result.result === RESPONSE_CODE.SUCCESS) {
  171 + message.success('提交成功');
  172 + }
  173 + return true;
  174 + }}
  175 + >
  176 + <ProCard
  177 + title="基础信息"
  178 + bordered
  179 + //
  180 + headStyle={{}}
  181 + headerBordered
  182 + size={'small'}
  183 + >
  184 + <ProForm.Group>
  185 + <ProFormText
  186 + readonly
  187 + name="id"
  188 + label="订单批号"
  189 + colProps={{
  190 + span: 5,
  191 + }}
  192 + tooltip="最长为 24 位"
  193 + placeholder="请输入名称"
  194 + />
  195 +
  196 + <ProFormText
  197 + readonly
  198 + width="md"
  199 + colProps={{
  200 + span: 5,
  201 + }}
  202 + name="createByName"
  203 + label="销售代表"
  204 + placeholder="请输入名称"
  205 + />
  206 + <ProFormText
  207 + readonly
  208 + width="md"
  209 + colProps={{
  210 + span: 5,
  211 + }}
  212 + name="createTime"
  213 + label="申请时间"
  214 + placeholder="请输入名称"
  215 + />
  216 + <ProFormSelect
  217 + name="type"
  218 + label="发票类型"
  219 + colProps={{
  220 + span: 5,
  221 + }}
  222 + readonly={readOnly}
  223 + request={async () => {
  224 + let invoiceTypeRet = await postServiceConstInvoiceType();
  225 + return enumToSelect(invoiceTypeRet.data);
  226 + }}
  227 + placeholder="Please select a country"
  228 + rules={[
  229 + { required: true, message: 'Please select your country!' },
  230 + ]}
  231 + />
  232 + <ProFormSelect
  233 + name="invoicingType"
  234 + readonly={readOnly}
  235 + label="开具类型"
  236 + colProps={{
  237 + span: 4,
  238 + }}
  239 + request={async () => {
  240 + let invoicingTypeRet = await postServiceConstInvoicingType();
  241 + let options = enumToSelect(invoicingTypeRet.data);
  242 + return options;
  243 + }}
  244 + placeholder="Please select a country"
  245 + rules={[
  246 + { required: true, message: 'Please select your country!' },
  247 + ]}
  248 + />
  249 + <ProFormList
  250 + label="订单号"
  251 + name="orderIdList"
  252 + creatorButtonProps={false}
  253 + itemRender={({}, { record }) => {
  254 + console.log('record' + JSON.stringify(record));
  255 + return (
  256 + <Space>
  257 + <Button
  258 + key={record.mainId}
  259 + className="pl-1 pr-0"
  260 + type="link"
  261 + target="_blank"
  262 + href={'/order?id=' + record.mainId}
  263 + >
  264 + {record.mainId}
  265 + </Button>
  266 + (
  267 + {record.subIds.map((item) => {
  268 + return (
  269 + <Button
  270 + key={item}
  271 + className="pl-1 pr-0"
  272 + type="link"
  273 + target="_blank"
  274 + href={'/order?subOrderId=' + item}
  275 + >
  276 + {item}
  277 + </Button>
  278 + );
  279 + })}
  280 + )
  281 + <Divider type="vertical" />
  282 + </Space>
  283 + );
  284 + }}
  285 + >
  286 + <ProFormText allowClear={false} width="xs" name={['name']} />
  287 + </ProFormList>
  288 + </ProForm.Group>
  289 + </ProCard>
  290 + <hr />
  291 + <ProCard title="购方信息" bordered headerBordered size={'small'}>
  292 + <ProForm.Group>
  293 + <ProFormText
  294 + readonly={readOnly}
  295 + width="md"
  296 + colProps={{
  297 + span: 8,
  298 + }}
  299 + name="partyAName"
  300 + label="购方名称"
  301 + placeholder="请输入名称"
  302 + />
  303 + <ProFormText
  304 + readonly={readOnly}
  305 + width="md"
  306 + colProps={{
  307 + span: 8,
  308 + }}
  309 + name="partyATaxid"
  310 + label="购方税号"
  311 + placeholder="请输入名称"
  312 + />
  313 + <ProFormText
  314 + readonly={readOnly}
  315 + width="md"
  316 + colProps={{
  317 + span: 8,
  318 + }}
  319 + label="开户银行"
  320 + name={'partyAOpenBank'}
  321 + placeholder="请输入名称"
  322 + />
  323 + <ProFormText
  324 + readonly={readOnly}
  325 + width="md"
  326 + colProps={{
  327 + span: 8,
  328 + }}
  329 + name="partyABankAccount"
  330 + label="银行账号"
  331 + placeholder="请输入名称"
  332 + />
  333 + <ProFormText
  334 + readonly={readOnly}
  335 + width="md"
  336 + colProps={{
  337 + span: 8,
  338 + }}
  339 + name="partyAAddress"
  340 + label="购方地址"
  341 + placeholder="请输入名称"
  342 + />
  343 + <ProFormText
  344 + readonly={readOnly}
  345 + width="md"
  346 + colProps={{
  347 + span: 8,
  348 + }}
  349 + name="partyAPhoneNumber"
  350 + label="电话"
  351 + placeholder="请输入名称"
  352 + />
  353 + </ProForm.Group>
  354 + </ProCard>
  355 + <hr />
  356 + <ProCard title="销方信息" bordered headerBordered size={'small'}>
  357 + <ProForm.Group>
  358 + <ProFormSelect
  359 + readonly={readOnly}
  360 + width="md"
  361 + name="partyBName"
  362 + options={payeeNameOptions}
  363 + onChange={(value: any) => {
  364 + let payee = payees.find((item: any) => {
  365 + return item.payeeName === value;
  366 + });
  367 + console.log(JSON.stringify(payee));
  368 + form.setFieldsValue({
  369 + partyBTaxid: payee.taxId,
  370 + partyBBankAccount: payee.bankAccount,
  371 + partyBOpenBank: payee.openBank,
  372 + partyBAddress: payee.address,
  373 + partyBPhoneNumber: payee.phoneNumber,
  374 + });
  375 + }}
  376 + label="销方名称"
  377 + colProps={{
  378 + span: 8,
  379 + }}
  380 + placeholder="请输入名称"
  381 + />
  382 +
  383 + <ProFormText
  384 + readonly
  385 + width="md"
  386 + name="partyBTaxid"
  387 + label="销方税号"
  388 + colProps={{
  389 + span: 8,
  390 + }}
  391 + placeholder="请输入名称"
  392 + />
  393 + <ProFormText
  394 + readonly
  395 + width="md"
  396 + name="partyBOpenBank"
  397 + label="开户银行"
  398 + colProps={{
  399 + span: 8,
  400 + }}
  401 + placeholder="请输入名称"
  402 + />
  403 + <ProFormText
  404 + readonly
  405 + width="md"
  406 + name="partyBBankAccount"
  407 + label="银行账号"
  408 + colProps={{
  409 + span: 8,
  410 + }}
  411 + placeholder="请输入名称"
  412 + />
  413 + <ProFormText
  414 + readonly
  415 + width="md"
  416 + colProps={{
  417 + span: 8,
  418 + }}
  419 + name="partyBAddress"
  420 + label="销方地址"
  421 + placeholder="请输入名称"
  422 + />
  423 + <ProFormText
  424 + readonly
  425 + width="md"
  426 + colProps={{
  427 + span: 8,
  428 + }}
  429 + name="partyBPhoneNumber"
  430 + label="电话"
  431 + placeholder="请输入名称"
  432 + />
  433 + </ProForm.Group>
  434 + </ProCard>
  435 + <hr />
  436 + <ProCard title="发票明细" bordered headerBordered size={'small'}>
  437 + <InvoiceDetailTable
  438 + recordId={id}
  439 + details={detailTableData}
  440 + updateDetails={updateDetails}
  441 + readOnly={readOnly}
  442 + />
  443 + </ProCard>
  444 + <hr />
  445 + <ProCard title="备注" bordered headerBordered size={'small'}>
  446 + <ProFormTextArea
  447 + readonly={readOnly}
  448 + name="comment"
  449 + placeholder="请输入备注"
  450 + />
  451 + </ProCard>
  452 + </ModalForm>
  453 + </Space>
  454 + </>
  455 + );
  456 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoiceVerificationModal.tsx 0 → 100644
  1 +import ButtonConfirm from '@/components/ButtomConfirm';
  2 +import EllipsisDiv from '@/components/Div/EllipsisDiv';
  3 +import { RESPONSE_CODE } from '@/constants/enum';
  4 +import { INVOCING_STATUS, PAYEE_OPTIONS } from '@/pages/Order/constant';
  5 +import {
  6 + postServiceInvoiceCancelInvoiceAndBankStatement,
  7 + postServiceInvoiceQueryInvoiceDetail,
  8 +} from '@/services';
  9 +import { enumValueToLabel, formatDateTime } from '@/utils';
  10 +import { formatDate } from '@/utils/time';
  11 +import { PlusOutlined } from '@ant-design/icons';
  12 +import {
  13 + ActionType,
  14 + ModalForm,
  15 + ProCard,
  16 + ProTable,
  17 +} from '@ant-design/pro-components';
  18 +import {
  19 + Button,
  20 + Descriptions,
  21 + DescriptionsProps,
  22 + Divider,
  23 + Flex,
  24 + Form,
  25 + message,
  26 +} from 'antd';
  27 +import { useEffect, useRef, useState } from 'react';
  28 +import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../../constant';
  29 +import '../index.less';
  30 +import BankChooseModal from './BankChooseModal';
  31 +
  32 +export default ({ invoiceId, setVisible, onClose }) => {
  33 + const [form] = Form.useForm<{ id: string }>();
  34 + const [bankChooseModalVisible, setBankChooseModalVisible] = useState(false);
  35 + const [invoiceInfo, setInvoiceInfo] = useState({});
  36 + const [relationOrderIds, setRelationOrderIds] = useState([]);
  37 + const [relationBankStatements, setRelationBankStatements] = useState([]);
  38 + const actionRef = useRef<ActionType>();
  39 +
  40 + const loadInvoiceData = async () => {
  41 + let res = await postServiceInvoiceQueryInvoiceDetail({
  42 + data: { invoiceId: invoiceId },
  43 + });
  44 + if (res && res.data) {
  45 + setInvoiceInfo(res.data);
  46 + setRelationOrderIds(res.data.mainOrderIds);
  47 + console.log('bs:' + res.data.bankStatementDtos);
  48 + setRelationBankStatements(res.data.bankStatementDtos);
  49 + }
  50 + };
  51 +
  52 + const showRelationOrders = () => {
  53 + return relationOrderIds?.map((item) => {
  54 + return (
  55 + <>
  56 + <Button
  57 + className="pl-1 pr-0"
  58 + type="link"
  59 + target="_blank"
  60 + href={'/order?id=' + item}
  61 + >
  62 + {item}
  63 + </Button>
  64 + <Divider type="vertical" />
  65 + </>
  66 + );
  67 + });
  68 + };
  69 +
  70 + const items: DescriptionsProps['items'] = [
  71 + {
  72 + key: '1',
  73 + label: '发票号码',
  74 + children: invoiceInfo?.invoiceNumber,
  75 + span: 6,
  76 + },
  77 + {
  78 + key: '2',
  79 + label: '发票类型',
  80 + children: enumValueToLabel(invoiceInfo?.invoiceStatus, INVOCING_STATUS),
  81 + span: 6,
  82 + },
  83 + {
  84 + key: '3',
  85 + label: '状态',
  86 + children: enumValueToLabel(invoiceInfo?.status, INVOICE_STATUS),
  87 + span: 4,
  88 + },
  89 + {
  90 + key: '4',
  91 + label: '购买方',
  92 + children: invoiceInfo?.purchaser,
  93 + span: 8,
  94 + },
  95 + {
  96 + key: '5',
  97 + label: '收款单位',
  98 + children: enumValueToLabel(invoiceInfo?.payee, PAYEE_OPTIONS),
  99 + span: 12,
  100 + },
  101 + {
  102 + key: '6',
  103 + label: '联系人',
  104 + children: invoiceInfo?.contacts,
  105 + span: 4,
  106 + },
  107 + {
  108 + key: '7',
  109 + label: '销售',
  110 + children: invoiceInfo?.sale,
  111 + span: 8,
  112 + },
  113 +
  114 + {
  115 + key: '9',
  116 + label: '开票日期',
  117 + children: formatDate(invoiceInfo?.invoicingTime),
  118 + span: 12,
  119 + },
  120 + {
  121 + key: '10',
  122 + label: '收款时间',
  123 + children: formatDate(invoiceInfo?.collectionTime),
  124 + span: 4,
  125 + },
  126 + {
  127 + key: '8',
  128 + label: '金额',
  129 + children: invoiceInfo?.money,
  130 + span: 10,
  131 + },
  132 + {
  133 + key: '11',
  134 + label: '备注',
  135 + children: invoiceInfo?.notes,
  136 + span: 24,
  137 + },
  138 + ];
  139 +
  140 + const getTableCellText = (target: any) => {
  141 + if (!target) {
  142 + return '';
  143 + }
  144 +
  145 + if (target.props) {
  146 + return target.props.text;
  147 + }
  148 +
  149 + return target;
  150 + };
  151 +
  152 + /**
  153 + * 加载表格的各个列格式
  154 + */
  155 + const bankStatementColumnsInit = () => {
  156 + let columns = BANK_STATEMENT_COLUMNS.map((item) => {
  157 + let newItem = { ...item };
  158 + let dataIndex = item.dataIndex;
  159 + let dataType = item.valueType;
  160 +
  161 + newItem.render = (text, record) => {
  162 + let textValue = record[dataIndex];
  163 +
  164 + if (dataType === 'date') {
  165 + textValue = formatDate(textValue);
  166 + }
  167 +
  168 + if (dataType === 'dateTime') {
  169 + textValue = formatDateTime(textValue);
  170 + }
  171 +
  172 + if (dataType === 'money') {
  173 + textValue = '¥' + textValue;
  174 + }
  175 +
  176 + switch (dataIndex) {
  177 + case 'invoiceStatus':
  178 + return (
  179 + <EllipsisDiv
  180 + text={enumValueToLabel(
  181 + getTableCellText(textValue),
  182 + INVOCING_STATUS,
  183 + )}
  184 + />
  185 + );
  186 +
  187 + case 'status':
  188 + return (
  189 + <EllipsisDiv
  190 + text={enumValueToLabel(
  191 + getTableCellText(textValue),
  192 + INVOICE_STATUS,
  193 + )}
  194 + />
  195 + );
  196 +
  197 + case 'payee':
  198 + return (
  199 + <EllipsisDiv
  200 + text={enumValueToLabel(
  201 + getTableCellText(textValue),
  202 + PAYEE_OPTIONS,
  203 + )}
  204 + />
  205 + );
  206 +
  207 + default:
  208 + return <EllipsisDiv text={getTableCellText(textValue)} />;
  209 + }
  210 + };
  211 +
  212 + return newItem;
  213 + });
  214 +
  215 + columns.push({
  216 + title: '操作',
  217 + valueType: 'option',
  218 + key: 'option',
  219 + fixed: 'right',
  220 + width: 70,
  221 + render: (text, record) => {
  222 + let optBtns = [];
  223 + if (invoiceInfo?.status === 'VERIFIED') {
  224 + return [];
  225 + }
  226 +
  227 + optBtns.push(
  228 + <ButtonConfirm
  229 + key="delete"
  230 + className="p-0"
  231 + title={'确认删除此项吗?'}
  232 + text="删除"
  233 + onConfirm={async () => {
  234 + let res = await postServiceInvoiceCancelInvoiceAndBankStatement({
  235 + data: {
  236 + invoiceId: invoiceId,
  237 + cancelId: [record.id],
  238 + },
  239 + });
  240 + if (res.result === RESPONSE_CODE.SUCCESS) {
  241 + message.success(res.message);
  242 + loadInvoiceData();
  243 + }
  244 + }}
  245 + />,
  246 + );
  247 + return optBtns;
  248 + },
  249 + });
  250 +
  251 + return columns;
  252 + };
  253 +
  254 + useEffect(() => {
  255 + loadInvoiceData();
  256 + }, []);
  257 +
  258 + return (
  259 + <>
  260 + <ModalForm<{
  261 + id: string;
  262 + }>
  263 + className="invoice-detail"
  264 + open
  265 + width="80%"
  266 + title="发票详情"
  267 + form={form}
  268 + autoFocusFirstInput
  269 + modalProps={{
  270 + okText: '确定',
  271 + cancelText: '返回',
  272 + destroyOnClose: true,
  273 + onCancel: () => {
  274 + setVisible(false);
  275 + onClose();
  276 + },
  277 + }}
  278 + submitter={{
  279 + render: (props, defaultDoms) => {
  280 + return [defaultDoms[0]];
  281 + },
  282 + }}
  283 + onFinish={async () => {
  284 + onClose();
  285 + }}
  286 + onOpenChange={setVisible}
  287 + >
  288 + <Divider orientation="left" plain>
  289 + 发票信息
  290 + </Divider>
  291 +
  292 + <Descriptions
  293 + bordered
  294 + column={24}
  295 + size="small"
  296 + title=""
  297 + items={items}
  298 + />
  299 +
  300 + <Divider orientation="left" plain>
  301 + 订单号
  302 + </Divider>
  303 +
  304 + <ProCard bordered style={{}}>
  305 + <Flex>
  306 + <div>{showRelationOrders()}</div>
  307 + </Flex>
  308 + </ProCard>
  309 +
  310 + <Divider plain></Divider>
  311 +
  312 + <ProTable
  313 + columns={bankStatementColumnsInit()}
  314 + actionRef={actionRef}
  315 + cardBordered
  316 + pagination={{
  317 + pageSize: 10,
  318 + }}
  319 + dataSource={relationBankStatements}
  320 + columnsState={{
  321 + persistenceKey: 'pro-table-singe-demos',
  322 + persistenceType: 'localStorage',
  323 + defaultValue: {
  324 + option: { fixed: 'right', disable: true },
  325 + },
  326 + onChange(value) {
  327 + console.log('value: ', value);
  328 + },
  329 + }}
  330 + rowKey="id"
  331 + search={false}
  332 + options={{
  333 + setting: {
  334 + listsHeight: 400,
  335 + },
  336 + reload: false,
  337 + }}
  338 + form={{
  339 + // 由于配置了 transform,提交的参与与定义的不同这里需要转化一下
  340 + syncToUrl: (values, type) => {
  341 + if (type === 'get') {
  342 + return {
  343 + ...values,
  344 + created_at: [values.startTime, values.endTime],
  345 + };
  346 + }
  347 + return values;
  348 + },
  349 + }}
  350 + dateFormatter="string"
  351 + headerTitle="银行流水"
  352 + scroll={{ x: 1400, y: 360 }}
  353 + toolBarRender={() => [
  354 + <Button
  355 + key="button"
  356 + icon={<PlusOutlined />}
  357 + onClick={() => {
  358 + setBankChooseModalVisible(true);
  359 + }}
  360 + hidden={invoiceInfo?.status === 'VERIFIED'}
  361 + type="primary"
  362 + >
  363 + 添加
  364 + </Button>,
  365 + ]}
  366 + />
  367 + </ModalForm>
  368 +
  369 + {bankChooseModalVisible ? (
  370 + <BankChooseModal
  371 + loadInvoiceData={loadInvoiceData}
  372 + setVisible={setBankChooseModalVisible}
  373 + invoiceId={invoiceId}
  374 + onClose={() => {
  375 + setBankChooseModalVisible(false);
  376 + loadInvoiceData();
  377 + actionRef.current?.reload();
  378 + }}
  379 + ></BankChooseModal>
  380 + ) : (
  381 + ''
  382 + )}
  383 + </>
  384 + );
  385 +};
... ...
src/pages/Invoice/InvoiceVerification/components/InvoicingModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceInvoiceInvoicing } from '@/services';
  3 +import { ModalForm } from '@ant-design/pro-components';
  4 +import { Button, Form, message } from 'antd';
  5 +
  6 +export default ({ selectedRowKeys, reloadRecordTable }) => {
  7 + const [form] = Form.useForm<{ name: string; company: string }>();
  8 + return (
  9 + <ModalForm<{
  10 + name: string;
  11 + company: string;
  12 + }>
  13 + title="开票"
  14 + trigger={
  15 + <Button type="primary" disabled={selectedRowKeys?.length === 0}>
  16 + 开票
  17 + </Button>
  18 + }
  19 + form={form}
  20 + autoFocusFirstInput
  21 + modalProps={{
  22 + destroyOnClose: true,
  23 + onCancel: () => console.log('run'),
  24 + }}
  25 + submitTimeout={2000}
  26 + onFinish={async (values) => {
  27 + let res = await postServiceInvoiceInvoicing({
  28 + data: {
  29 + ...values,
  30 + invoiceRecordIds: selectedRowKeys,
  31 + },
  32 + });
  33 + if (res.result === RESPONSE_CODE.SUCCESS) {
  34 + message.success(res.message);
  35 + }
  36 + reloadRecordTable();
  37 + message.success('提交成功');
  38 + return true;
  39 + }}
  40 + >
  41 + {/*<ProFormSelect
  42 + name="invoicingAccount"
  43 + label="开票账号"
  44 + request={async () => {
  45 + const res = await postServiceInvoiceGetInvoicingAccount();
  46 + return res.data.map((item) => {
  47 + return {
  48 + label: item.accountText,
  49 + value: item.account,
  50 + };
  51 + });
  52 + }}
  53 + placeholder="请选择开票账号"
  54 + rules={[{ required: true, message: '请选择开票账号!' }]}
  55 + />*/}
  56 + </ModalForm>
  57 + );
  58 +};
... ...
src/pages/Invoice/InvoiceVerification/components/ManualInvoicingModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import UploadC from '@/pages/Invoice/InvoiceVerification/components/UploadSingleImg';
  3 +import {
  4 + postOrderErpOrderStagesUpload,
  5 + postServiceInvoiceDealInvoicingResult,
  6 +} from '@/services';
  7 +import {
  8 + ModalForm,
  9 + ProFormDatePicker,
  10 + ProFormText,
  11 +} from '@ant-design/pro-components';
  12 +import { Col, Form, Row, message } from 'antd';
  13 +import { RcFile } from 'antd/es/upload';
  14 +import { useEffect } from 'react';
  15 +
  16 +export default ({ record }) => {
  17 + useEffect(() => {
  18 + console.log('invoicing');
  19 + }, []);
  20 + const [form] = Form.useForm();
  21 + return (
  22 + <ModalForm
  23 + title="手动开票"
  24 + trigger={<a type="primary">手动开票</a>}
  25 + width={600}
  26 + layout={'horizontal'}
  27 + form={form}
  28 + autoFocusFirstInput
  29 + modalProps={{
  30 + destroyOnClose: true,
  31 + onCancel: () => console.log('run'),
  32 + }}
  33 + submitTimeout={2000}
  34 + onFinish={async (values) => {
  35 + const res = await postServiceInvoiceDealInvoicingResult({
  36 + data: {
  37 + ...values,
  38 + isSuccess: true,
  39 + invoiceRecordId: record.id,
  40 + manual: true,
  41 + },
  42 + });
  43 + if (res.result === RESPONSE_CODE.SUCCESS) {
  44 + message.success('开票成功');
  45 + return true;
  46 + } else {
  47 + message.error('开票失败');
  48 + }
  49 + }}
  50 + >
  51 + {/*<ProFormText
  52 + rules={[{ required: true, message: '此项为必填项' }]}
  53 + width={'md'}
  54 + name="invoicingPerson"
  55 + label="开票人"
  56 + />*/}
  57 + <ProFormText
  58 + rules={[{ required: true, message: '此项为必填项' }]}
  59 + width={'md'}
  60 + name="invoiceNumber"
  61 + label="发票号码"
  62 + />
  63 + <ProFormDatePicker
  64 + rules={[{ required: true, message: '此项为必填项' }]}
  65 + fieldProps={{
  66 + format: 'YYYY-MM-DD',
  67 + }}
  68 + name="invoicingDate"
  69 + label="开票日期"
  70 + />
  71 + <ProFormText
  72 + rules={[{ required: true, message: '发票必须上传' }]}
  73 + hidden
  74 + name="url"
  75 + label="發票地址"
  76 + />
  77 + <Row>
  78 + <Col span={4}>上传发票</Col>
  79 + <Col span={20}>
  80 + <UploadC
  81 + onFilesChange={async (newFileList) => {
  82 + if (newFileList.length > 0) {
  83 + const formData = new FormData();
  84 + formData.append('file', newFileList[0].originFileObj as RcFile);
  85 + const res = await postOrderErpOrderStagesUpload({
  86 + data: formData,
  87 + headers: {
  88 + 'Content-Type':
  89 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  90 + },
  91 + });
  92 + const url = res.data;
  93 + form.setFieldValue('url', url);
  94 + } else {
  95 + form.setFieldValue('url', null);
  96 + }
  97 + }}
  98 + ></UploadC>
  99 + </Col>
  100 + </Row>
  101 + {/*<ProFormList
  102 + name="invoiceDetailDtoList"
  103 + label="明细"
  104 + creatorButtonProps={false}
  105 + copyIconProps={false}
  106 + itemRender={({ listDom }, { index }) => (
  107 + <ProCard
  108 + bordered
  109 + style={{ marginBlockEnd: 8 }}
  110 + title={`明细${index + 1}`}
  111 + bodyStyle={{ paddingBlockEnd: 0 }}
  112 + >
  113 + {listDom}
  114 + </ProCard>
  115 + )}
  116 + creatorRecord={{ name: '', items: [{ name: '' }] }}
  117 + initialValue={record.invoiceDetails}
  118 + >
  119 + <ProFormText
  120 + name="projectName"
  121 + label="名称"
  122 + placeholder="请输入名称"
  123 + readonly
  124 + />
  125 + <ProFormDigit label="税率" name="taxRate" min={0} max={100} />
  126 + <ProFormMoney label="税额" name="taxPrice" locale="zh-CN" min={0} />
  127 + </ProFormList>*/}
  128 + </ModalForm>
  129 + );
  130 +};
... ...
src/pages/Invoice/InvoiceVerification/components/UploadSingleImg.tsx 0 → 100644
  1 +import { PlusOutlined } from '@ant-design/icons';
  2 +import type { GetProp, UploadFile, UploadProps } from 'antd';
  3 +import { Image, Upload } from 'antd';
  4 +import { useState } from 'react';
  5 +
  6 +type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
  7 +
  8 +const getBase64 = (file: FileType): Promise<string> =>
  9 + new Promise((resolve, reject) => {
  10 + const reader = new FileReader();
  11 + reader.readAsDataURL(file);
  12 + reader.onload = () => resolve(reader.result as string);
  13 + reader.onerror = (error) => reject(error);
  14 + });
  15 +
  16 +export default ({ onFilesChange }) => {
  17 + const [previewOpen, setPreviewOpen] = useState(false);
  18 + const [previewImage, setPreviewImage] = useState('');
  19 + const [fileList, setFileList] = useState<UploadFile[]>([]);
  20 +
  21 + const handlePreview = async (file: UploadFile) => {
  22 + if (!file.url && !file.preview) {
  23 + file.preview = await getBase64(file.originFileObj as FileType);
  24 + }
  25 +
  26 + setPreviewImage(file.url || (file.preview as string));
  27 + setPreviewOpen(true);
  28 + };
  29 +
  30 + const uploadButton = (
  31 + <button style={{ border: 0, background: 'none' }} type="button">
  32 + <PlusOutlined />
  33 + <div style={{ marginTop: 8 }}>Upload</div>
  34 + </button>
  35 + );
  36 + return (
  37 + <>
  38 + <Upload
  39 + listType="picture-card"
  40 + fileList={fileList}
  41 + onPreview={handlePreview}
  42 + onChange={({ fileList: newFileList }) => {
  43 + setFileList(newFileList);
  44 + console.log('file' + JSON.stringify(newFileList));
  45 + onFilesChange(newFileList);
  46 + }}
  47 + >
  48 + {fileList.length >= 1 ? null : uploadButton}
  49 + </Upload>
  50 + {previewImage && (
  51 + <Image
  52 + wrapperStyle={{ display: 'none' }}
  53 + preview={{
  54 + visible: previewOpen,
  55 + onVisibleChange: (visible) => setPreviewOpen(visible),
  56 + afterOpenChange: (visible) => !visible && setPreviewImage(''),
  57 + }}
  58 + src={previewImage}
  59 + />
  60 + )}
  61 + </>
  62 + );
  63 +};
... ...
src/pages/Invoice/InvoiceVerification/index.less 0 → 100644
  1 +.invoice-index td {
  2 + font-family: 'San Francisco', 'Helvetica Neue', Helvetica, Arial,
  3 + 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC',
  4 + 'WenQuanYi Micro Hei', sans-serif;
  5 + font-size: 14px;
  6 +}
  7 +
  8 +.invoice-detail td {
  9 + font-family: 'San Francisco', 'Helvetica Neue', Helvetica, Arial,
  10 + 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC',
  11 + 'WenQuanYi Micro Hei', sans-serif;
  12 + font-size: 14px;
  13 +}
  14 +
  15 +.bank-statement-choose td {
  16 + font-family: 'San Francisco', 'Helvetica Neue', Helvetica, Arial,
  17 + 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC',
  18 + 'WenQuanYi Micro Hei', sans-serif;
  19 + font-size: 14px;
  20 +}
... ...
src/pages/Invoice/InvoiceVerification/index.tsx 0 → 100644
  1 +import ButtonConfirm from '@/components/ButtomConfirm';
  2 +import EllipsisDiv from '@/components/Div/EllipsisDiv';
  3 +import { RESPONSE_CODE } from '@/constants/enum';
  4 +import BankImportModal from '@/pages/Invoice/InvoiceVerification/components/BankImportModal';
  5 +import InvoiceRecordDetailModal from '@/pages/Invoice/InvoiceVerification/components/InvoiceRecordDetailModal';
  6 +import InvoiceVerificationModal from '@/pages/Invoice/InvoiceVerification/components/InvoiceVerificationModal';
  7 +import {
  8 + BANK_STATEMENT_COLUMNS,
  9 + INVOICE_STATUS,
  10 +} from '@/pages/Invoice/constant';
  11 +import { INVOCING_STATUS, PAYEE_OPTIONS } from '@/pages/Order/constant';
  12 +import {
  13 + postServiceBankStatementDeleteBankStatement,
  14 + postServiceBankStatementEditBankStatement,
  15 + postServiceBankStatementQueryBankStatement,
  16 +} from '@/services';
  17 +import { enumValueToLabel, formatDateTime } from '@/utils';
  18 +import { formatDate } from '@/utils/time';
  19 +import { PlusOutlined } from '@ant-design/icons';
  20 +import { ActionType, ProTable } from '@ant-design/pro-components';
  21 +import { Button, message } from 'antd';
  22 +import { useRef, useState } from 'react';
  23 +
  24 +const InvoiceRecord = () => {
  25 + const invoiceActionRef = useRef<ActionType>();
  26 + const bankActionRef = useRef<ActionType>();
  27 + const [bankImportModalVisible, setBankImportModalVisible] = useState(false);
  28 + const [invoiceVerificationVisible, setInvoiceVerificationVisible] =
  29 + useState(false);
  30 + const [invoiceId] = useState(undefined);
  31 + const [invoiceRecordDetailVisible, setInvoiceRecordDetailVisible] =
  32 + useState(false);
  33 + const [invoiceRecord] = useState({});
  34 + const [contextHolder] = message.useMessage();
  35 +
  36 + const reloadBankStatementTable = () => {
  37 + bankActionRef.current?.reload();
  38 + };
  39 +
  40 + const getTableCellText = (target: any) => {
  41 + if (!target) {
  42 + return '';
  43 + }
  44 +
  45 + if (target.props) {
  46 + return target.props.text;
  47 + }
  48 +
  49 + return target;
  50 + };
  51 +
  52 + const bankStatemetColumnsInit = () => {
  53 + let columns = BANK_STATEMENT_COLUMNS.map((item) => {
  54 + let newItem = { ...item };
  55 + let dataIndex = item.dataIndex;
  56 + let dataType = item.valueType;
  57 +
  58 + newItem.render = (text, record) => {
  59 + let textValue = record[dataIndex];
  60 +
  61 + if (dataType === 'date') {
  62 + textValue = formatDate(textValue);
  63 + }
  64 +
  65 + if (dataType === 'dateTime') {
  66 + textValue = formatDateTime(textValue);
  67 + }
  68 +
  69 + if (dataType === 'money') {
  70 + if (textValue === null || textValue === undefined) {
  71 + textValue = '';
  72 + } else {
  73 + textValue = '¥' + textValue;
  74 + }
  75 + }
  76 +
  77 + switch (dataIndex) {
  78 + case 'invoiceStatus':
  79 + return (
  80 + <EllipsisDiv
  81 + text={enumValueToLabel(
  82 + getTableCellText(textValue),
  83 + INVOCING_STATUS,
  84 + )}
  85 + />
  86 + );
  87 +
  88 + case 'status':
  89 + return (
  90 + <EllipsisDiv
  91 + text={enumValueToLabel(
  92 + getTableCellText(textValue),
  93 + INVOICE_STATUS,
  94 + )}
  95 + />
  96 + );
  97 +
  98 + case 'payee':
  99 + return (
  100 + <EllipsisDiv
  101 + text={enumValueToLabel(
  102 + getTableCellText(textValue),
  103 + PAYEE_OPTIONS,
  104 + )}
  105 + />
  106 + );
  107 +
  108 + default:
  109 + return <EllipsisDiv text={getTableCellText(textValue)} />;
  110 + }
  111 + };
  112 +
  113 + return newItem;
  114 + });
  115 +
  116 + columns.push({
  117 + title: '操作',
  118 + valueType: 'option',
  119 + key: 'option',
  120 + fixed: 'right',
  121 + width: 120,
  122 + render: (text, record, _, action) => {
  123 + let btns = [];
  124 + if (record.path?.includes('editBankStatement')) {
  125 + btns.push(
  126 + <a
  127 + key="editable"
  128 + onClick={() => {
  129 + action?.startEditable?.(record.id);
  130 + }}
  131 + >
  132 + 编辑
  133 + </a>,
  134 + );
  135 + }
  136 +
  137 + if (record.path?.includes('deleteBankStatement')) {
  138 + btns.push(
  139 + <ButtonConfirm
  140 + key="delete"
  141 + className="p-0"
  142 + title={'是否删除该银行流水记录?'}
  143 + text="删除"
  144 + onConfirm={async () => {
  145 + let res = await postServiceBankStatementDeleteBankStatement({
  146 + data: { id: record.id },
  147 + });
  148 + if (res.result === RESPONSE_CODE.SUCCESS) {
  149 + message.success(res.message);
  150 + reloadBankStatementTable();
  151 + }
  152 + }}
  153 + />,
  154 + );
  155 + }
  156 + return btns;
  157 + },
  158 + });
  159 +
  160 + return columns;
  161 + };
  162 +
  163 + return (
  164 + <div className="invoice-index">
  165 + <ProTable
  166 + columns={bankStatemetColumnsInit()}
  167 + actionRef={bankActionRef}
  168 + cardBordered
  169 + pagination={{
  170 + pageSize: 10,
  171 + }}
  172 + editable={{
  173 + type: 'multiple',
  174 + onSave: async (rowKey, data) => {
  175 + await postServiceBankStatementEditBankStatement({ data: data });
  176 + },
  177 + actionRender: (row, config, defaultDom) => [
  178 + defaultDom.save,
  179 + defaultDom.cancel,
  180 + ],
  181 + }}
  182 + request={async (params) => {
  183 + const res = await postServiceBankStatementQueryBankStatement({
  184 + data: { ...params },
  185 + });
  186 + if (res) {
  187 + return {
  188 + data: res?.data?.data || [],
  189 + total: res?.data?.total || 0,
  190 + };
  191 + }
  192 + }}
  193 + columnsState={{
  194 + persistenceKey: 'pro-table-singe-demos',
  195 + persistenceType: 'localStorage',
  196 + defaultValue: {
  197 + option: { fixed: 'right', disable: true },
  198 + },
  199 + onChange(value) {
  200 + console.log('value: ', value);
  201 + },
  202 + }}
  203 + rowKey="id"
  204 + search={{
  205 + labelWidth: 'auto',
  206 + }}
  207 + options={{
  208 + setting: {
  209 + listsHeight: 400,
  210 + },
  211 + }}
  212 + form={{}}
  213 + dateFormatter="string"
  214 + headerTitle="银行流水列表"
  215 + scroll={{ x: 1400, y: 360 }}
  216 + toolBarRender={() => [
  217 + <Button
  218 + key="button"
  219 + icon={<PlusOutlined />}
  220 + onClick={() => {
  221 + setBankImportModalVisible(true);
  222 + }}
  223 + type="primary"
  224 + >
  225 + 导入
  226 + </Button>,
  227 + ]}
  228 + />
  229 +
  230 + {bankImportModalVisible ? (
  231 + <BankImportModal
  232 + setVisible={setBankImportModalVisible}
  233 + onClose={() => {
  234 + setBankImportModalVisible(false);
  235 + invoiceActionRef.current?.reload();
  236 + bankActionRef.current?.reload();
  237 + }}
  238 + ></BankImportModal>
  239 + ) : (
  240 + ''
  241 + )}
  242 +
  243 + {invoiceVerificationVisible ? (
  244 + <InvoiceVerificationModal
  245 + setVisible={setInvoiceVerificationVisible}
  246 + invoiceId={invoiceId}
  247 + onClose={() => {
  248 + invoiceActionRef.current?.reload();
  249 + bankActionRef.current?.reload();
  250 + }}
  251 + ></InvoiceVerificationModal>
  252 + ) : (
  253 + ''
  254 + )}
  255 + {invoiceRecordDetailVisible ? (
  256 + <InvoiceRecordDetailModal
  257 + key="detail"
  258 + id={invoiceRecord.id}
  259 + setVisible={setInvoiceRecordDetailVisible}
  260 + />
  261 + ) : (
  262 + ''
  263 + )}
  264 + {contextHolder}
  265 + </div>
  266 + );
  267 +};
  268 +
  269 +export default InvoiceRecord;
... ...
src/pages/Invoice/index.tsx deleted 100644 → 0
1   -import ButtonConfirm from '@/components/ButtomConfirm';
2   -import EllipsisDiv from '@/components/Div/EllipsisDiv';
3   -import { RESPONSE_CODE } from '@/constants/enum';
4   -import AddInvoiceDrawerForm from '@/pages/Invoice/components/AddInvoiceDrawerForm';
5   -import InvoiceModal from '@/pages/Invoice/components/InvoiceModal';
6   -import InvoiceRecordDetailModal from '@/pages/Invoice/components/InvoiceRecordDetailModal';
7   -import InvoicingModal from '@/pages/Invoice/components/InvoicingModal';
8   -import ManualInvoicingModal from '@/pages/Invoice/components/ManualInvoicingModal';
9   -import {
10   - BANK_STATEMENT_COLUMNS,
11   - INVOICE_COLUMNS,
12   - INVOICE_STATUS,
13   -} from '@/pages/Invoice/constant';
14   -import {
15   - postServiceBankStatementDeleteBankStatement,
16   - postServiceBankStatementEditBankStatement,
17   - postServiceBankStatementQueryBankStatement,
18   - postServiceConstAfterInvoicingInvoiceRecordStatus,
19   - postServiceConstBeforeInvoicingInvoiceRecordStatus,
20   - postServiceConstInvoiceType,
21   - postServiceConstInvoicingType,
22   - postServiceInvoiceDeleteInvoice,
23   - postServiceInvoiceInvoicing,
24   - postServiceInvoiceQueryInvoice,
25   - postServiceInvoiceQueryInvoiceRecordList,
26   - postServiceOrderQuerySalesCode,
27   -} from '@/services';
28   -import { excelExport } from '@/services/exportRequest';
29   -import {
30   - enumToProTableEnumValue,
31   - enumToSelect,
32   - enumValueToLabel,
33   - formatDateTime,
34   -} from '@/utils';
35   -import { formatDate } from '@/utils/time';
36   -import { PlusOutlined } from '@ant-design/icons';
37   -import { ActionType, ModalForm, ProTable } from '@ant-design/pro-components';
38   -import { Button, Space, Table, Tabs, message } from 'antd';
39   -import { useEffect, useRef, useState } from 'react';
40   -import { INVOCING_STATUS, PAYEE_OPTIONS } from '../Order/constant';
41   -import BankImportModal from './components/BankImportModal';
42   -import InvoiceVerificationModal from './components/InvoiceVerificationModal';
43   -import './index.less';
44   -
45   -const InvoicePage = () => {
46   - const invoiceActionRef = useRef<ActionType>();
47   - const bankActionRef = useRef<ActionType>();
48   - const waitDealrecordActionRef = useRef<ActionType>();
49   - const processedRecordRef = useRef<ActionType>();
50   - const [invoiceTypeValueEnum, setInvoiceTypeValueEnum] = useState({});
51   - const [invoicingTypeValueEnum, setInvoicingTypeValueEnum] = useState({});
52   - const [salesCodeValueEnum, setSalesCodeValueEnum] = useState({});
53   - const [bankImportModalVisible, setBankImportModalVisible] = useState(false);
54   - const [invoiceVerificationVisible, setInvoiceVerificationVisible] =
55   - useState(false);
56   - const [invoiceId, setInvoiceId] = useState(undefined);
57   - const [invoiceRecordDetailVisible, setInvoiceRecordDetailVisible] =
58   - useState(false);
59   - const [invoiceRecord, setInvoiceRecord] = useState({});
60   - const [messageApi, contextHolder] = message.useMessage();
61   -
62   - useEffect(() => {
63   - async function extracted() {
64   - let invoiceTypeRet = await postServiceConstInvoiceType();
65   - setInvoiceTypeValueEnum(invoiceTypeRet.data);
66   - }
67   -
68   - extracted().catch(console.error);
69   - }, []);
70   -
71   - useEffect(() => {
72   - async function extracted() {
73   - let invoicingTypeRet = await postServiceConstInvoicingType();
74   - setInvoicingTypeValueEnum(invoicingTypeRet.data);
75   - }
76   -
77   - extracted().catch(console.error);
78   - }, []);
79   -
80   - useEffect(() => {
81   - async function extracted() {
82   - const res = await postServiceOrderQuerySalesCode();
83   - let map = {};
84   - res.data?.forEach((item) => {
85   - map[item.userName] = {
86   - text: item.userName,
87   - status: item.userName,
88   - };
89   - });
90   - setSalesCodeValueEnum(map);
91   - }
92   -
93   - extracted().catch(console.error);
94   - }, []);
95   -
96   - const reloadInvoiceTable = () => {
97   - invoiceActionRef.current?.reload();
98   - };
99   -
100   - const reloadBankStatementTable = () => {
101   - bankActionRef.current?.reload();
102   - };
103   - const reloadRecordTable = () => {
104   - waitDealrecordActionRef.current?.reload();
105   - processedRecordRef.current?.reload();
106   - };
107   -
108   - const getTableCellText = (target: any) => {
109   - if (!target) {
110   - return '';
111   - }
112   -
113   - if (target.props) {
114   - return target.props.text;
115   - }
116   -
117   - return target;
118   - };
119   -
120   - const waitDealRecordColumns = [
121   - {
122   - dataIndex: 'index',
123   - valueType: 'indexBorder',
124   - hideInSearch: true,
125   - ellipsis: true,
126   - width: 48,
127   - },
128   - {
129   - title: '开票编号',
130   - valueType: 'text',
131   - dataIndex: 'id',
132   - copyable: true,
133   - hideInSearch: true,
134   - ellipsis: true,
135   - width: 100,
136   - },
137   - {
138   - title: '发票状态',
139   - valueType: 'Text',
140   - dataIndex: 'statusText',
141   - ellipsis: true,
142   - hideInSearch: true,
143   - },
144   - {
145   - title: '申请开票时间',
146   - dataIndex: 'createTime',
147   - valueType: 'dateTime',
148   - hideInSearch: true,
149   - ellipsis: true,
150   - },
151   - {
152   - title: '销售代表',
153   - valueType: 'text',
154   - hideInSearch: true,
155   - ellipsis: true,
156   - dataIndex: 'createByName',
157   - },
158   - {
159   - title: '购方名称',
160   - valueType: 'text',
161   - dataIndex: 'partyAName',
162   - hideInSearch: true,
163   - ellipsis: true,
164   - },
165   - {
166   - title: '购方税号',
167   - valueType: 'text',
168   - hideInSearch: true,
169   - dataIndex: 'partyATaxid',
170   - ellipsis: true,
171   - },
172   - {
173   - title: '收款单位',
174   - valueType: 'text',
175   - hideInSearch: true,
176   - dataIndex: 'partyBName',
177   - ellipsis: true,
178   - },
179   - {
180   - title: '开票金额',
181   - valueType: 'money',
182   - dataIndex: 'price',
183   - hideInSearch: true,
184   - ellipsis: true,
185   - },
186   - {
187   - title: '开具类型',
188   - valueType: 'Text',
189   - dataIndex: 'invoicingTypeText',
190   - hideInSearch: true,
191   - ellipsis: true,
192   - },
193   - {
194   - title: '发票类型',
195   - valueType: 'Text',
196   - dataIndex: 'typeText',
197   - hideInSearch: true,
198   - ellipsis: true,
199   - },
200   - {
201   - title: '是否加急',
202   - valueType: 'Text',
203   - dataIndex: 'isUrgentText',
204   - hideInSearch: true,
205   - ellipsis: true,
206   - },
207   - {
208   - title: '申请备注',
209   - valueType: 'text',
210   - dataIndex: 'applyInvoicingNotes',
211   - hideInSearch: true,
212   - ellipsis: true,
213   - },
214   - {
215   - title: '购方名称',
216   - valueType: 'Text',
217   - dataIndex: 'partyANameLike',
218   - hideInTable: true,
219   - },
220   - {
221   - title: '收款单位',
222   - valueType: 'select',
223   - dataIndex: 'partyB',
224   - filters: true,
225   - onFilter: true,
226   - hideInTable: true,
227   - valueEnum: enumToProTableEnumValue(PAYEE_OPTIONS),
228   - },
229   - {
230   - title: '主订单号',
231   - valueType: 'Text',
232   - dataIndex: 'mainOrderId',
233   - hideInTable: true,
234   - },
235   - {
236   - title: '子订单号',
237   - valueType: 'Text',
238   - dataIndex: 'subOrderId',
239   - hideInTable: true,
240   - },
241   - {
242   - title: '销售代表',
243   - valueType: 'select',
244   - dataIndex: 'salesCode',
245   - filters: true,
246   - onFilter: true,
247   - hideInTable: true,
248   - valueEnum: salesCodeValueEnum,
249   - },
250   - {
251   - title: '发票类型',
252   - valueType: 'select',
253   - dataIndex: 'type',
254   - filters: true,
255   - onFilter: true,
256   - hideInTable: true,
257   - valueEnum: enumToProTableEnumValue(invoiceTypeValueEnum),
258   - },
259   - {
260   - title: '开具类型',
261   - valueType: 'select',
262   - dataIndex: 'invoicingType',
263   - filters: true,
264   - onFilter: true,
265   - hideInTable: true,
266   - valueEnum: enumToProTableEnumValue(invoicingTypeValueEnum),
267   - },
268   - {
269   - title: '开票状态',
270   - valueType: 'select',
271   - dataIndex: 'status',
272   - filters: true,
273   - onFilter: true,
274   - hideInTable: true,
275   - request: async () => {
276   - const res = await postServiceConstBeforeInvoicingInvoiceRecordStatus();
277   - return enumToSelect(res.data);
278   - },
279   - },
280   - {
281   - title: '是否加急',
282   - valueType: 'select',
283   - dataIndex: 'isUrgent',
284   - filters: true,
285   - onFilter: true,
286   - hideInTable: true,
287   - valueEnum: {
288   - true: {
289   - text: '是',
290   - status: true,
291   - },
292   - false: {
293   - text: '否',
294   - status: false,
295   - },
296   - },
297   - },
298   - {
299   - title: '申请开票时间',
300   - dataIndex: 'createTime',
301   - valueType: 'dateTimeRange',
302   - width: 200,
303   - hideInTable: true,
304   - search: {
305   - transform: (value) => {
306   - if (value) {
307   - return {
308   - createTimeGe: value[0],
309   - createTimeLe: value[1],
310   - };
311   - }
312   - },
313   - },
314   - },
315   - {
316   - title: '操作',
317   - valueType: 'option',
318   - key: 'option',
319   - render: (text, record) => {
320   - return [
321   - /*<InvoiceRecordDetailModal
322   - key="detail"
323   - id={record.id}
324   - subOrderIds={record.subOrderIds}
325   - onClose={()=>{
326   - waitDealrecordActionRef.current?.reload();
327   - }
328   - }
329   - />*/
330   - <>
331   - {record.paths.includes('DETAIL') && (
332   - <a
333   - key="detail"
334   - onClick={() => {
335   - setInvoiceRecordDetailVisible(true);
336   - setInvoiceRecord(record);
337   - }}
338   - >
339   - 详情
340   - </a>
341   - )}
342   - </>,
343   - <>
344   - {record.paths.includes('PREVIEW') && (
345   - <InvoiceModal key="invoiceModal" recordId={record.id} />
346   - )}
347   - </>,
348   - <>
349   - {record.paths.includes('INVOICING') && (
350   - <ManualInvoicingModal
351   - key={'ManualInvoicingModal'}
352   - record={record}
353   - ></ManualInvoicingModal>
354   - )}
355   - </>,
356   - ];
357   - },
358   - },
359   - ];
360   -
361   - const processedRecordColumns = [
362   - {
363   - dataIndex: 'index',
364   - valueType: 'indexBorder',
365   - },
366   - {
367   - title: '开票编号',
368   - valueType: 'text',
369   - dataIndex: 'id',
370   - copyable: true,
371   - ellipsis: true,
372   - },
373   - {
374   - title: '发票号码',
375   - valueType: 'text',
376   - dataIndex: 'invoiceNumber',
377   - copyable: true,
378   - ellipsis: true,
379   - },
380   - {
381   - title: '开票日期',
382   - dataIndex: 'invoicingDate',
383   - valueType: 'date',
384   - hideInSearch: true,
385   - ellipsis: true,
386   - },
387   - {
388   - title: '发票类型',
389   - valueType: 'Text',
390   - dataIndex: 'typeText',
391   - hideInSearch: true,
392   - ellipsis: true,
393   - },
394   - {
395   - title: '发票状态',
396   - valueType: 'Text',
397   - dataIndex: 'statusText',
398   - hideInSearch: true,
399   - ellipsis: true,
400   - },
401   - {
402   - title: '购方名称',
403   - valueType: 'text',
404   - dataIndex: 'partyAName',
405   - hideInSearch: true,
406   - ellipsis: true,
407   - },
408   - {
409   - title: '购方税号',
410   - valueType: 'text',
411   - dataIndex: 'partyATaxid',
412   - ellipsis: true,
413   - },
414   - {
415   - title: '收款单位',
416   - valueType: 'text',
417   - dataIndex: 'partyBName',
418   - hideInSearch: true,
419   - ellipsis: true,
420   - },
421   - {
422   - title: '联系人',
423   - valueType: 'text',
424   - dataIndex: 'contacts',
425   - hideInSearch: true,
426   - ellipsis: true,
427   - },
428   - {
429   - title: '申请人',
430   - valueType: 'text',
431   - dataIndex: 'createByName',
432   - hideInSearch: true,
433   - ellipsis: true,
434   - },
435   - {
436   - title: '开票金额(元)',
437   - valueType: 'money',
438   - dataIndex: 'price',
439   - hideInSearch: true,
440   - ellipsis: true,
441   - },
442   - {
443   - title: '备注',
444   - valueType: 'text',
445   - dataIndex: 'contacts',
446   - hideInSearch: true,
447   - ellipsis: true,
448   - },
449   - {
450   - title: '失败原因',
451   - valueType: 'text',
452   - dataIndex: 'failureReason',
453   - hideInSearch: true,
454   - ellipsis: true,
455   - },
456   -
457   - {
458   - title: '购方名称',
459   - valueType: 'text',
460   - dataIndex: 'partyANameLike',
461   - hideInTable: true,
462   - },
463   - {
464   - title: '发票类型',
465   - valueType: 'select',
466   - dataIndex: 'type',
467   - filters: true,
468   - onFilter: true,
469   - hideInTable: true,
470   - valueEnum: enumToProTableEnumValue(invoiceTypeValueEnum),
471   - },
472   -
473   - {
474   - title: '开票状态',
475   - valueType: 'select',
476   - dataIndex: 'status',
477   - filters: true,
478   - onFilter: true,
479   - hideInTable: true,
480   - request: async () => {
481   - const res = await postServiceConstAfterInvoicingInvoiceRecordStatus();
482   - return enumToSelect(res.data);
483   - },
484   - },
485   - {
486   - title: '销售代表',
487   - valueType: 'select',
488   - dataIndex: 'salesCode',
489   - filters: true,
490   - onFilter: true,
491   - hideInTable: true,
492   - valueEnum: salesCodeValueEnum,
493   - },
494   - {
495   - title: '联系人',
496   - valueType: 'text',
497   - dataIndex: 'contactsLike',
498   - hideInTable: true,
499   - },
500   - {
501   - title: '开票日期',
502   - dataIndex: 'invoicingDate',
503   - valueType: 'dateRange',
504   - hideInTable: true,
505   - search: {
506   - transform: (value) => {
507   - if (value) {
508   - return {
509   - invoicingDateGe: value[0],
510   - invoicingDateLe: value[1],
511   - };
512   - }
513   - },
514   - },
515   - },
516   - {
517   - title: '收款单位',
518   - valueType: 'select',
519   - dataIndex: 'partyB',
520   - filters: true,
521   - onFilter: true,
522   - hideInTable: true,
523   - valueEnum: enumToProTableEnumValue(PAYEE_OPTIONS),
524   - },
525   - {
526   - title: '操作',
527   - valueType: 'option',
528   - key: 'option',
529   - render: (text, record) => [
530   - <>
531   - {record.status === 'SUCCESS' && record.paths.includes('DETAIL') && (
532   - <a
533   - key="detail"
534   - onClick={() => {
535   - setInvoiceRecordDetailVisible(true);
536   - setInvoiceRecord(record);
537   - }}
538   - >
539   - 详情
540   - </a>
541   - )}
542   - </>,
543   - <>
544   - {record.status === 'SUCCESS' &&
545   - record.paths.includes('DOWNLOAD_INVOICE') && (
546   - <a href={record.invoiceAddress} download>
547   - 下载发票
548   - </a>
549   - )}
550   - </>,
551   - <>
552   - {record.status === 'FAIL' && record.paths.includes('RETRY') && (
553   - <ModalForm
554   - title="提示"
555   - trigger={
556   - <Button type="link" danger>
557   - 重试
558   - </Button>
559   - }
560   - autoFocusFirstInput
561   - modalProps={{
562   - destroyOnClose: true,
563   - }}
564   - submitTimeout={2000}
565   - onFinish={async () => {
566   - const res = await postServiceInvoiceInvoicing({
567   - data: {
568   - invoiceRecordIds: [record.id],
569   - },
570   - });
571   - if (res) {
572   - message.success(res.message);
573   - processedRecordRef?.current?.reload();
574   - }
575   - return true;
576   - }}
577   - >
578   - 确定重试订单信息吗?
579   - </ModalForm>
580   - )}
581   - </>,
582   - <>
583   - {record.paths.includes('INVOICING') && (
584   - <ManualInvoicingModal
585   - key={'ManualInvoicingModal'}
586   - record={record}
587   - ></ManualInvoicingModal>
588   - )}
589   - </>,
590   - ],
591   - },
592   - ];
593   - /**
594   - * 加载发票列表表格的各个列格式
595   - */
596   - const invoicecColumnsInit = () => {
597   - let columns = INVOICE_COLUMNS.map((item) => {
598   - let newItem = { ...item };
599   - let dataIndex = item.dataIndex;
600   - let dataType = item.valueType;
601   -
602   - newItem.render = (text, record) => {
603   - let textValue = record[dataIndex];
604   -
605   - if (dataType === 'dateRange' || dataType === 'date') {
606   - textValue = formatDate(textValue);
607   - }
608   -
609   - if (dataType === 'dateTime') {
610   - textValue = formatDateTime(textValue);
611   - }
612   -
613   - if (dataType === 'money') {
614   - textValue = '¥' + textValue;
615   - }
616   -
617   - switch (dataIndex) {
618   - case 'invoiceStatus':
619   - return (
620   - <EllipsisDiv
621   - text={enumValueToLabel(
622   - getTableCellText(textValue),
623   - INVOCING_STATUS,
624   - )}
625   - />
626   - );
627   -
628   - case 'status':
629   - return (
630   - <EllipsisDiv
631   - text={enumValueToLabel(
632   - getTableCellText(textValue),
633   - INVOICE_STATUS,
634   - )}
635   - />
636   - );
637   -
638   - case 'payee':
639   - return (
640   - <EllipsisDiv
641   - text={enumValueToLabel(
642   - getTableCellText(textValue),
643   - PAYEE_OPTIONS,
644   - )}
645   - />
646   - );
647   -
648   - default:
649   - return <EllipsisDiv text={getTableCellText(textValue)} />;
650   - }
651   - };
652   -
653   - return newItem;
654   - });
655   -
656   - columns.push({
657   - title: '操作',
658   - valueType: 'option',
659   - key: 'option',
660   - fixed: 'right',
661   - width: 120,
662   - render: (text, record) => {
663   - let btns = [];
664   - if (record.path?.includes('writeOff')) {
665   - btns.push(
666   - <a
667   - key="editable"
668   - onClick={() => {
669   - setInvoiceVerificationVisible(true);
670   - setInvoiceId(record.invoiceId);
671   - }}
672   - >
673   - 核销
674   - </a>,
675   - );
676   - }
677   -
678   - if (record.path?.includes('queryInvoiceDetails')) {
679   - btns.push(
680   - <Button
681   - className="p-0"
682   - key="view"
683   - type="link"
684   - onClick={() => {
685   - setInvoiceVerificationVisible(true);
686   - setInvoiceId(record.invoiceId);
687   - }}
688   - >
689   - 查看
690   - </Button>,
691   - );
692   - }
693   -
694   - if (record.path?.includes('deleteInvoice')) {
695   - btns.push(
696   - <ButtonConfirm
697   - key="delete"
698   - className="p-0"
699   - title={
700   - '确认删除发票号码为[ ' + record.invoiceNumber + ' ]的发票吗?'
701   - }
702   - text="删除"
703   - onConfirm={async () => {
704   - let res = await postServiceInvoiceDeleteInvoice({
705   - data: { invoiceId: record.invoiceId },
706   - });
707   - if (res) {
708   - message.success(res.message);
709   - reloadInvoiceTable();
710   - }
711   - }}
712   - />,
713   - );
714   - }
715   - return btns;
716   - },
717   - });
718   -
719   - return columns;
720   - };
721   -
722   - const bankStatemetColumnsInit = () => {
723   - let columns = BANK_STATEMENT_COLUMNS.map((item) => {
724   - let newItem = { ...item };
725   - let dataIndex = item.dataIndex;
726   - let dataType = item.valueType;
727   -
728   - newItem.render = (text, record) => {
729   - let textValue = record[dataIndex];
730   -
731   - if (dataType === 'date') {
732   - textValue = formatDate(textValue);
733   - }
734   -
735   - if (dataType === 'dateTime') {
736   - textValue = formatDateTime(textValue);
737   - }
738   -
739   - if (dataType === 'money') {
740   - if (textValue === null || textValue === undefined) {
741   - textValue = '';
742   - } else {
743   - textValue = '¥' + textValue;
744   - }
745   - }
746   -
747   - switch (dataIndex) {
748   - case 'invoiceStatus':
749   - return (
750   - <EllipsisDiv
751   - text={enumValueToLabel(
752   - getTableCellText(textValue),
753   - INVOCING_STATUS,
754   - )}
755   - />
756   - );
757   -
758   - case 'status':
759   - return (
760   - <EllipsisDiv
761   - text={enumValueToLabel(
762   - getTableCellText(textValue),
763   - INVOICE_STATUS,
764   - )}
765   - />
766   - );
767   -
768   - case 'payee':
769   - return (
770   - <EllipsisDiv
771   - text={enumValueToLabel(
772   - getTableCellText(textValue),
773   - PAYEE_OPTIONS,
774   - )}
775   - />
776   - );
777   -
778   - default:
779   - return <EllipsisDiv text={getTableCellText(textValue)} />;
780   - }
781   - };
782   -
783   - return newItem;
784   - });
785   -
786   - columns.push({
787   - title: '操作',
788   - valueType: 'option',
789   - key: 'option',
790   - fixed: 'right',
791   - width: 120,
792   - render: (text, record, _, action) => {
793   - let btns = [];
794   - if (record.path?.includes('editBankStatement')) {
795   - btns.push(
796   - <a
797   - key="editable"
798   - onClick={() => {
799   - action?.startEditable?.(record.id);
800   - }}
801   - >
802   - 编辑
803   - </a>,
804   - );
805   - }
806   -
807   - if (record.path?.includes('deleteBankStatement')) {
808   - btns.push(
809   - <ButtonConfirm
810   - key="delete"
811   - className="p-0"
812   - title={'是否删除该银行流水记录?'}
813   - text="删除"
814   - onConfirm={async () => {
815   - let res = await postServiceBankStatementDeleteBankStatement({
816   - data: { id: record.id },
817   - });
818   - if (res.result === RESPONSE_CODE.SUCCESS) {
819   - message.success(res.message);
820   - reloadBankStatementTable();
821   - }
822   - }}
823   - />,
824   - );
825   - }
826   - return btns;
827   - },
828   - });
829   -
830   - return columns;
831   - };
832   -
833   - const tabsItems = [
834   - {
835   - key: 1,
836   - label: '待处理',
837   - children: (
838   - <ProTable
839   - columns={waitDealRecordColumns}
840   - actionRef={waitDealrecordActionRef}
841   - cardBordered
842   - pagination={{
843   - showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单
844   - pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项
845   - }}
846   - rowSelection={{
847   - selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
848   - alwaysShowAlert: true,
849   - }}
850   - tableAlertOptionRender={({ selectedRowKeys, selectedRows }) => {
851   - console.log(selectedRows);
852   - console.log(selectedRowKeys);
853   - return (
854   - <Space size={16}>
855   - <InvoicingModal
856   - reloadRecordTable={reloadRecordTable}
857   - key="button"
858   - selectedRowKeys={selectedRowKeys}
859   - />
860   - </Space>
861   - );
862   - }}
863   - request={async (params) => {
864   - let res = await postServiceInvoiceQueryInvoiceRecordList({
865   - data: {
866   - ...params,
867   - statusIn: [
868   - 'WAITING_FOR_INVOICING',
869   - 'AUDITING',
870   - 'AUDITING_NOT_PASSED',
871   - 'CANCELED',
872   - ],
873   - needBuildDetails: true,
874   - needBuildSubOrders: true,
875   - },
876   - });
877   - return {
878   - data: res?.data?.data,
879   - total: res?.data?.total || 0,
880   - };
881   - }}
882   - columnsState={{
883   - persistenceKey: 'pro-table-singe-demos',
884   - persistenceType: 'localStorage',
885   - defaultValue: {
886   - option: { fixed: 'right', disable: true },
887   - },
888   - onChange(value) {
889   - console.log('value: ', value);
890   - },
891   - }}
892   - rowKey="id"
893   - search={{
894   - labelWidth: 'auto',
895   - }}
896   - options={{
897   - setting: {
898   - listsHeight: 400,
899   - },
900   - }}
901   - form={{}}
902   - dateFormatter="string"
903   - headerTitle="待开票列表"
904   - scroll={{ x: 1400, y: 360 }}
905   - />
906   - ),
907   - },
908   - {
909   - key: 2,
910   - label: '开票记录',
911   - children: (
912   - <ProTable
913   - columns={processedRecordColumns}
914   - actionRef={processedRecordRef}
915   - cardBordered
916   - pagination={{
917   - showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单
918   - pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项
919   - }}
920   - editable={{
921   - type: 'multiple',
922   - onSave: async (rowKey, data) => {
923   - await postServiceBankStatementEditBankStatement({ data: data });
924   - },
925   - actionRender: (row, config, defaultDom) => [
926   - defaultDom.save,
927   - defaultDom.cancel,
928   - ],
929   - }}
930   - search={{
931   - labelWidth: 'auto',
932   - defaultCollapsed: false,
933   - optionRender: (searchConfig, formProps, dom) => [
934   - ...dom,
935   - <Button
936   - key="out"
937   - onClick={() => {
938   - const values = searchConfig?.form?.getFieldsValue();
939   - console.log(values);
940   - messageApi.open({
941   - type: 'loading',
942   - content: '正在导出文件...',
943   - });
944   - excelExport(
945   - '/api/service/invoice/exportInvoiceRecords',
946   - {
947   - ...values,
948   - statusIn: ['INVOICING', 'SUCCESS', 'FAIL'],
949   - },
950   - () => {
951   - messageApi.destroy();
952   - },
953   - );
954   - }}
955   - >
956   - 导出
957   - </Button>,
958   - ],
959   - }}
960   - request={async (params) => {
961   - let res = await postServiceInvoiceQueryInvoiceRecordList({
962   - data: {
963   - ...params,
964   - statusIn: ['INVOICING', 'SUCCESS', 'FAIL'],
965   - },
966   - });
967   - return {
968   - data: res?.data?.data,
969   - total: res?.data?.total || 0,
970   - };
971   - }}
972   - columnsState={{
973   - persistenceKey: 'pro-table-singe-demos',
974   - persistenceType: 'localStorage',
975   - defaultValue: {
976   - option: { fixed: 'right', disable: true },
977   - },
978   - onChange(value) {
979   - console.log('value: ', value);
980   - },
981   - }}
982   - rowKey="id"
983   - options={{
984   - setting: {
985   - listsHeight: 400,
986   - },
987   - }}
988   - form={{}}
989   - dateFormatter="string"
990   - headerTitle="待开票列表"
991   - scroll={{ x: 1400, y: 360 }}
992   - toolBarRender={() => []}
993   - />
994   - ),
995   - },
996   - {
997   - key: 3,
998   - label: '发票管理',
999   - children: (
1000   - <ProTable
1001   - columns={invoicecColumnsInit()}
1002   - actionRef={invoiceActionRef}
1003   - cardBordered
1004   - pagination={{
1005   - pageSize: 10,
1006   - }}
1007   - request={async (params) => {
1008   - const res = await postServiceInvoiceQueryInvoice({
1009   - data: { ...params },
1010   - });
1011   - if (res) {
1012   - return {
1013   - data: res?.data?.data || [],
1014   - total: res?.data?.total || 0,
1015   - };
1016   - }
1017   - }}
1018   - columnsState={{
1019   - persistenceKey: 'pro-table-singe-demos',
1020   - persistenceType: 'localStorage',
1021   - defaultValue: {
1022   - option: { fixed: 'right', disable: true },
1023   - },
1024   - onChange(value) {
1025   - console.log('value: ', value);
1026   - },
1027   - }}
1028   - rowKey="id"
1029   - search={{
1030   - labelWidth: 'auto',
1031   - }}
1032   - options={{
1033   - setting: {
1034   - listsHeight: 400,
1035   - },
1036   - }}
1037   - form={{}}
1038   - dateFormatter="string"
1039   - headerTitle="发票列表"
1040   - scroll={{ x: 1400, y: 360 }}
1041   - toolBarRender={() => [
1042   - <AddInvoiceDrawerForm
1043   - onClose={() => {
1044   - invoiceActionRef.current?.reload();
1045   - bankActionRef.current?.reload();
1046   - }}
1047   - key="add"
1048   - ></AddInvoiceDrawerForm>,
1049   - ]}
1050   - />
1051   - ),
1052   - },
1053   - {
1054   - key: 4,
1055   - label: '银行流水',
1056   - children: (
1057   - <ProTable
1058   - columns={bankStatemetColumnsInit()}
1059   - actionRef={bankActionRef}
1060   - cardBordered
1061   - pagination={{
1062   - pageSize: 10,
1063   - }}
1064   - editable={{
1065   - type: 'multiple',
1066   - onSave: async (rowKey, data) => {
1067   - await postServiceBankStatementEditBankStatement({ data: data });
1068   - },
1069   - actionRender: (row, config, defaultDom) => [
1070   - defaultDom.save,
1071   - defaultDom.cancel,
1072   - ],
1073   - }}
1074   - request={async (params) => {
1075   - const res = await postServiceBankStatementQueryBankStatement({
1076   - data: { ...params },
1077   - });
1078   - if (res) {
1079   - return {
1080   - data: res?.data?.data || [],
1081   - total: res?.data?.total || 0,
1082   - };
1083   - }
1084   - }}
1085   - columnsState={{
1086   - persistenceKey: 'pro-table-singe-demos',
1087   - persistenceType: 'localStorage',
1088   - defaultValue: {
1089   - option: { fixed: 'right', disable: true },
1090   - },
1091   - onChange(value) {
1092   - console.log('value: ', value);
1093   - },
1094   - }}
1095   - rowKey="id"
1096   - search={{
1097   - labelWidth: 'auto',
1098   - }}
1099   - options={{
1100   - setting: {
1101   - listsHeight: 400,
1102   - },
1103   - }}
1104   - form={{}}
1105   - dateFormatter="string"
1106   - headerTitle="银行流水列表"
1107   - scroll={{ x: 1400, y: 360 }}
1108   - toolBarRender={() => [
1109   - <Button
1110   - key="button"
1111   - icon={<PlusOutlined />}
1112   - onClick={() => {
1113   - setBankImportModalVisible(true);
1114   - }}
1115   - type="primary"
1116   - >
1117   - 导入
1118   - </Button>,
1119   - ]}
1120   - />
1121   - ),
1122   - },
1123   - ];
1124   - return (
1125   - <div className="invoice-index">
1126   - <Tabs
1127   - defaultActiveKey="1"
1128   - items={tabsItems}
1129   - onChange={(value) => {
1130   - if (value === 1) {
1131   - invoiceActionRef.current?.reload();
1132   - } else {
1133   - bankActionRef.current?.reload();
1134   - }
1135   - }}
1136   - />
1137   -
1138   - {bankImportModalVisible ? (
1139   - <BankImportModal
1140   - setVisible={setBankImportModalVisible}
1141   - onClose={() => {
1142   - setBankImportModalVisible(false);
1143   - invoiceActionRef.current?.reload();
1144   - bankActionRef.current?.reload();
1145   - }}
1146   - ></BankImportModal>
1147   - ) : (
1148   - ''
1149   - )}
1150   -
1151   - {invoiceVerificationVisible ? (
1152   - <InvoiceVerificationModal
1153   - setVisible={setInvoiceVerificationVisible}
1154   - invoiceId={invoiceId}
1155   - onClose={() => {
1156   - invoiceActionRef.current?.reload();
1157   - bankActionRef.current?.reload();
1158   - }}
1159   - ></InvoiceVerificationModal>
1160   - ) : (
1161   - ''
1162   - )}
1163   - {invoiceRecordDetailVisible ? (
1164   - <InvoiceRecordDetailModal
1165   - key="detail"
1166   - id={invoiceRecord.id}
1167   - setVisible={setInvoiceRecordDetailVisible}
1168   - />
1169   - ) : (
1170   - ''
1171   - )}
1172   - {contextHolder}
1173   - </div>
1174   - );
1175   -};
1176   -
1177   -export default InvoicePage;