Commit 768fbcceeccc357770a46a82cde7fd9003bdfa27

Authored by 曾国涛
1 parent ea6f0208

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`中的部分导出,以适应新的核销功能。

此次重构旨在为系统引入核销功能,提高发票管理的灵活性和效率。通过整理和更新代码结构,也使得代码更加清晰,便于后续的维护和开发。```
Showing 34 changed files with 3720 additions and 896 deletions

Too many changes to show.

To preserve performance only 34 of 44 files are displayed.

.umirc.ts
@@ -53,24 +53,40 @@ export default defineConfig({ @@ -53,24 +53,40 @@ export default defineConfig({
53 }, 53 },
54 { 54 {
55 name: '发票管理', 55 name: '发票管理',
56 - path: '/invoiceManage',  
57 - component: './Invoice',  
58 - icon: 'BookOutlined',  
59 - access: 'canReadAdminAndFinance',  
60 - },  
61 - /*{  
62 - name: '发票管理',  
63 path: '/Invoice', 56 path: '/Invoice',
64 icon: 'BookOutlined', 57 icon: 'BookOutlined',
65 - access: 'canReadAdminAndFinance',  
66 - routes:[ 58 + access: 'canReadAdminAndFinanceAndSales',
  59 + routes: [
67 { 60 {
68 - name: '权限管理',  
69 - path: 'authrity', 61 + name: '待开票',
  62 + path: 'waitProcessRecord',
70 icon: 'BookOutlined', 63 icon: 'BookOutlined',
71 - component: './Invoice/InvoiceRecord' },  
72 - ]  
73 - },*/ 64 + access: 'canReadAdminAndFinance',
  65 + component: './Invoice/waitProcessRecord',
  66 + },
  67 + {
  68 + name: '开票记录',
  69 + path: 'invoiceRecord',
  70 + icon: 'BookOutlined',
  71 + access: 'canReadAdminAndFinanceAndSales',
  72 + component: './Invoice/InvoiceRecord',
  73 + },
  74 + {
  75 + name: '发票管理',
  76 + path: 'invoice',
  77 + icon: 'BookOutlined',
  78 + access: 'canReadAdminAndFinance',
  79 + component: './Invoice/Invoice',
  80 + },
  81 + {
  82 + name: '发票核销',
  83 + path: 'invoiceVerification',
  84 + icon: 'BookOutlined',
  85 + access: 'canReadAdminAndFinance',
  86 + component: './Invoice/InvoiceVerification',
  87 + },
  88 + ],
  89 + },
74 90
75 { 91 {
76 name: '预存管理', 92 name: '预存管理',
src/pages/Invoice/components/AddInvoiceDrawerForm.tsx renamed to src/pages/Invoice/Invoice/components/AddInvoiceDrawerForm.tsx
src/pages/Invoice/components/BankChooseModal.tsx renamed to src/pages/Invoice/Invoice/components/BankChooseModal.tsx
@@ -11,7 +11,7 @@ import { formatDate } from '@/utils/time'; @@ -11,7 +11,7 @@ import { formatDate } from '@/utils/time';
11 import { ActionType, ProCard, ProTable } from '@ant-design/pro-components'; 11 import { ActionType, ProCard, ProTable } from '@ant-design/pro-components';
12 import { Button, Divider, Flex, Modal, Tag, message } from 'antd'; 12 import { Button, Divider, Flex, Modal, Tag, message } from 'antd';
13 import { useRef, useState } from 'react'; 13 import { useRef, useState } from 'react';
14 -import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../constant'; 14 +import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../../constant';
15 import '../index.less'; 15 import '../index.less';
16 16
17 export default ({ loadInvoiceData, invoiceId, setVisible, onClose }) => { 17 export default ({ loadInvoiceData, invoiceId, setVisible, onClose }) => {
src/pages/Invoice/components/BankImportModal.tsx renamed to src/pages/Invoice/Invoice/components/BankImportModal.tsx
src/pages/Invoice/components/Invoice.tsx renamed to src/pages/Invoice/Invoice/components/Invoice.tsx
src/pages/Invoice/components/InvoiceDetailImportModal.tsx renamed to src/pages/Invoice/Invoice/components/InvoiceDetailImportModal.tsx
src/pages/Invoice/Invoice/components/InvoiceDetailTable.tsx 0 → 100644
  1 +import InvoiceDetailImportModal from '@/pages/Invoice/Invoice/components/InvoiceDetailImportModal';
  2 +import { InvoiceProjectSelect } from '@/pages/Invoice/Invoice/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/components/InvoiceProjectSelect.tsx renamed to src/pages/Invoice/Invoice/components/InvoiceProjectSelect.tsx
src/pages/Invoice/components/InvoiceVerificationModal.tsx renamed to src/pages/Invoice/Invoice/components/InvoiceVerificationModal.tsx
@@ -25,7 +25,7 @@ import { @@ -25,7 +25,7 @@ import {
25 message, 25 message,
26 } from 'antd'; 26 } from 'antd';
27 import { useEffect, useRef, useState } from 'react'; 27 import { useEffect, useRef, useState } from 'react';
28 -import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../constant'; 28 +import { BANK_STATEMENT_COLUMNS, INVOICE_STATUS } from '../../constant';
29 import '../index.less'; 29 import '../index.less';
30 import BankChooseModal from './BankChooseModal'; 30 import BankChooseModal from './BankChooseModal';
31 31
src/pages/Invoice/index.less renamed to src/pages/Invoice/Invoice/index.less
src/pages/Invoice/Invoice/index.tsx 0 → 100644
  1 +import ButtonConfirm from '@/components/ButtomConfirm';
  2 +import EllipsisDiv from '@/components/Div/EllipsisDiv';
  3 +import AddInvoiceDrawerForm from '@/pages/Invoice/Invoice/components/AddInvoiceDrawerForm';
  4 +import BankImportModal from '@/pages/Invoice/Invoice/components/BankImportModal';
  5 +import InvoiceVerificationModal from '@/pages/Invoice/Invoice/components/InvoiceVerificationModal';
  6 +import { INVOICE_COLUMNS, INVOICE_STATUS } from '@/pages/Invoice/constant';
  7 +import { INVOCING_STATUS, PAYEE_OPTIONS } from '@/pages/Order/constant';
  8 +import {
  9 + postServiceInvoiceDeleteInvoice,
  10 + postServiceInvoiceQueryInvoice,
  11 +} from '@/services';
  12 +import { enumValueToLabel, formatDateTime } from '@/utils';
  13 +import { formatDate } from '@/utils/time';
  14 +import { ActionType, ProTable } from '@ant-design/pro-components';
  15 +import { Button, message } from 'antd';
  16 +import { useRef, useState } from 'react';
  17 +
  18 +const InvoiceRecord = () => {
  19 + const invoiceActionRef = useRef<ActionType>();
  20 + const [bankImportModalVisible, setBankImportModalVisible] = useState(false);
  21 + const [invoiceVerificationVisible, setInvoiceVerificationVisible] =
  22 + useState(false);
  23 + const [invoiceId, setInvoiceId] = useState(undefined);
  24 + const reloadInvoiceTable = () => {
  25 + invoiceActionRef.current?.reload();
  26 + };
  27 +
  28 + const getTableCellText = (target: any) => {
  29 + if (!target) {
  30 + return '';
  31 + }
  32 +
  33 + if (target.props) {
  34 + return target.props.text;
  35 + }
  36 +
  37 + return target;
  38 + };
  39 + /**
  40 + * 加载发票列表表格的各个列格式
  41 + */
  42 + const invoicecColumnsInit = () => {
  43 + let columns = INVOICE_COLUMNS.map((item) => {
  44 + let newItem = { ...item };
  45 + let dataIndex = item.dataIndex;
  46 + let dataType = item.valueType;
  47 +
  48 + newItem.render = (text, record) => {
  49 + let textValue = record[dataIndex];
  50 +
  51 + if (dataType === 'dateRange' || dataType === 'date') {
  52 + textValue = formatDate(textValue);
  53 + }
  54 +
  55 + if (dataType === 'dateTime') {
  56 + textValue = formatDateTime(textValue);
  57 + }
  58 +
  59 + if (dataType === 'money') {
  60 + textValue = '¥' + textValue;
  61 + }
  62 +
  63 + switch (dataIndex) {
  64 + case 'invoiceStatus':
  65 + return (
  66 + <EllipsisDiv
  67 + text={enumValueToLabel(
  68 + getTableCellText(textValue),
  69 + INVOCING_STATUS,
  70 + )}
  71 + />
  72 + );
  73 +
  74 + case 'status':
  75 + return (
  76 + <EllipsisDiv
  77 + text={enumValueToLabel(
  78 + getTableCellText(textValue),
  79 + INVOICE_STATUS,
  80 + )}
  81 + />
  82 + );
  83 +
  84 + case 'payee':
  85 + return (
  86 + <EllipsisDiv
  87 + text={enumValueToLabel(
  88 + getTableCellText(textValue),
  89 + PAYEE_OPTIONS,
  90 + )}
  91 + />
  92 + );
  93 +
  94 + default:
  95 + return <EllipsisDiv text={getTableCellText(textValue)} />;
  96 + }
  97 + };
  98 +
  99 + return newItem;
  100 + });
  101 +
  102 + columns.push({
  103 + title: '操作',
  104 + valueType: 'option',
  105 + key: 'option',
  106 + fixed: 'right',
  107 + width: 120,
  108 + render: (text, record) => {
  109 + let btns = [];
  110 + if (record.path?.includes('writeOff')) {
  111 + btns.push(
  112 + <a
  113 + key="editable"
  114 + onClick={() => {
  115 + setInvoiceVerificationVisible(true);
  116 + setInvoiceId(record.invoiceId);
  117 + }}
  118 + >
  119 + 核销
  120 + </a>,
  121 + );
  122 + }
  123 +
  124 + if (record.path?.includes('queryInvoiceDetails')) {
  125 + btns.push(
  126 + <Button
  127 + className="p-0"
  128 + key="view"
  129 + type="link"
  130 + onClick={() => {
  131 + setInvoiceVerificationVisible(true);
  132 + setInvoiceId(record.invoiceId);
  133 + }}
  134 + >
  135 + 查看
  136 + </Button>,
  137 + );
  138 + }
  139 +
  140 + if (record.path?.includes('deleteInvoice')) {
  141 + btns.push(
  142 + <ButtonConfirm
  143 + key="delete"
  144 + className="p-0"
  145 + title={
  146 + '确认删除发票号码为[ ' + record.invoiceNumber + ' ]的发票吗?'
  147 + }
  148 + text="删除"
  149 + onConfirm={async () => {
  150 + let res = await postServiceInvoiceDeleteInvoice({
  151 + data: { invoiceId: record.invoiceId },
  152 + });
  153 + if (res) {
  154 + message.success(res.message);
  155 + reloadInvoiceTable();
  156 + }
  157 + }}
  158 + />,
  159 + );
  160 + }
  161 + return btns;
  162 + },
  163 + });
  164 +
  165 + return columns;
  166 + };
  167 +
  168 + return (
  169 + <div className="invoice-index">
  170 + <ProTable
  171 + columns={invoicecColumnsInit()}
  172 + actionRef={invoiceActionRef}
  173 + cardBordered
  174 + pagination={{
  175 + pageSize: 10,
  176 + }}
  177 + request={async (params) => {
  178 + const res = await postServiceInvoiceQueryInvoice({
  179 + data: { ...params },
  180 + });
  181 + if (res) {
  182 + return {
  183 + data: res?.data?.data || [],
  184 + total: res?.data?.total || 0,
  185 + };
  186 + }
  187 + }}
  188 + columnsState={{
  189 + persistenceKey: 'pro-table-singe-demos',
  190 + persistenceType: 'localStorage',
  191 + defaultValue: {
  192 + option: { fixed: 'right', disable: true },
  193 + },
  194 + onChange(value) {
  195 + console.log('value: ', value);
  196 + },
  197 + }}
  198 + rowKey="id"
  199 + search={{
  200 + labelWidth: 'auto',
  201 + }}
  202 + options={{
  203 + setting: {
  204 + listsHeight: 400,
  205 + },
  206 + }}
  207 + form={{}}
  208 + dateFormatter="string"
  209 + headerTitle="发票列表"
  210 + scroll={{ x: 1400, y: 360 }}
  211 + toolBarRender={() => [
  212 + <AddInvoiceDrawerForm
  213 + onClose={() => {
  214 + invoiceActionRef.current?.reload();
  215 + }}
  216 + key="add"
  217 + ></AddInvoiceDrawerForm>,
  218 + ]}
  219 + />
  220 +
  221 + {bankImportModalVisible ? (
  222 + <BankImportModal
  223 + setVisible={setBankImportModalVisible}
  224 + onClose={() => {
  225 + setBankImportModalVisible(false);
  226 + invoiceActionRef.current?.reload();
  227 + }}
  228 + ></BankImportModal>
  229 + ) : (
  230 + ''
  231 + )}
  232 +
  233 + {invoiceVerificationVisible ? (
  234 + <InvoiceVerificationModal
  235 + setVisible={setInvoiceVerificationVisible}
  236 + invoiceId={invoiceId}
  237 + onClose={() => {
  238 + invoiceActionRef.current?.reload();
  239 + }}
  240 + ></InvoiceVerificationModal>
  241 + ) : (
  242 + ''
  243 + )}
  244 + </div>
  245 + );
  246 +};
  247 +
  248 +export default InvoiceRecord;
src/pages/Invoice/InvoiceRecord/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/InvoiceRecord/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/InvoiceRecord/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/InvoiceRecord/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/InvoiceRecord/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/components/InvoiceDetailTable.tsx renamed to src/pages/Invoice/InvoiceRecord/components/InvoiceDetailTable.tsx
1 -import InvoiceDetailImportModal from '@/pages/Invoice/components/InvoiceDetailImportModal';  
2 -import { InvoiceProjectSelect } from '@/pages/Invoice/components/InvoiceProjectSelect'; 1 +import InvoiceDetailImportModal from '@/pages/Invoice/InvoiceRecord/components/InvoiceDetailImportModal';
  2 +import { InvoiceProjectSelect } from '@/pages/Invoice/InvoiceRecord/components/InvoiceProjectSelect';
3 import { 3 import {
4 ActionType, 4 ActionType,
5 EditableProTable, 5 EditableProTable,
src/pages/Invoice/components/InvoiceModal.tsx renamed to src/pages/Invoice/InvoiceRecord/components/InvoiceModal.tsx
1 -import Invoice from '@/pages/Invoice/components/Invoice'; 1 +import Invoice from '@/pages/Invoice/InvoiceRecord/components/Invoice';
2 import { postServiceInvoiceGetInvoiceRecord } from '@/services'; 2 import { postServiceInvoiceGetInvoiceRecord } from '@/services';
3 import { ModalForm } from '@ant-design/pro-components'; 3 import { ModalForm } from '@ant-design/pro-components';
4 import { Form } from 'antd'; 4 import { Form } from 'antd';
src/pages/Invoice/InvoiceRecord/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/components/InvoiceRecordDetailModal.tsx renamed to src/pages/Invoice/InvoiceRecord/components/InvoiceRecordDetailModal.tsx
1 import { RESPONSE_CODE } from '@/constants/enum'; 1 import { RESPONSE_CODE } from '@/constants/enum';
2 -import InvoiceDetailTable from '@/pages/Invoice/components/InvoiceDetailTable'; 2 +import InvoiceDetailTable from '@/pages/Invoice/InvoiceRecord/components/InvoiceDetailTable';
3 import { 3 import {
4 postServiceConstGetPayeeEnum, 4 postServiceConstGetPayeeEnum,
5 postServiceConstInvoiceType, 5 postServiceConstInvoiceType,
src/pages/Invoice/InvoiceRecord/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/components/InvoicingModal.tsx renamed to src/pages/Invoice/InvoiceRecord/components/InvoicingModal.tsx
src/pages/Invoice/components/ManualInvoicingModal.tsx renamed to src/pages/Invoice/InvoiceRecord/components/ManualInvoicingModal.tsx
1 import { RESPONSE_CODE } from '@/constants/enum'; 1 import { RESPONSE_CODE } from '@/constants/enum';
2 -import UploadC from '@/pages/Invoice/components/UploadSingleImg'; 2 +import UploadC from '@/pages/Invoice/InvoiceRecord/components/UploadSingleImg';
3 import { 3 import {
4 postOrderErpOrderStagesUpload, 4 postOrderErpOrderStagesUpload,
5 postServiceInvoiceDealInvoicingResult, 5 postServiceInvoiceDealInvoicingResult,
src/pages/Invoice/components/UploadSingleImg.tsx renamed to src/pages/Invoice/InvoiceRecord/components/UploadSingleImg.tsx
src/pages/Invoice/InvoiceRecord/index.tsx
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 BankImportModal from '@/pages/Invoice/components/BankImportModal';  
6 -import InvoiceModal from '@/pages/Invoice/components/InvoiceModal';  
7 -import InvoiceRecordDetailModal from '@/pages/Invoice/components/InvoiceRecordDetailModal';  
8 -import InvoiceVerificationModal from '@/pages/Invoice/components/InvoiceVerificationModal';  
9 -import InvoicingModal from '@/pages/Invoice/components/InvoicingModal';  
10 -import ManualInvoicingModal from '@/pages/Invoice/components/ManualInvoicingModal'; 1 +import InvoiceRecordDetailModal from '@/pages/Invoice/InvoiceRecord/components/InvoiceRecordDetailModal';
  2 +import ManualInvoicingModal from '@/pages/Invoice/InvoiceRecord/components/ManualInvoicingModal';
  3 +import { PAYEE_OPTIONS } from '@/pages/Order/constant';
11 import { 4 import {
12 - BANK_STATEMENT_COLUMNS,  
13 - INVOICE_COLUMNS,  
14 - INVOICE_STATUS,  
15 -} from '@/pages/Invoice/constant';  
16 -import { INVOCING_STATUS, PAYEE_OPTIONS } from '@/pages/Order/constant';  
17 -import {  
18 - postServiceBankStatementDeleteBankStatement,  
19 postServiceBankStatementEditBankStatement, 5 postServiceBankStatementEditBankStatement,
20 - postServiceBankStatementQueryBankStatement,  
21 postServiceConstAfterInvoicingInvoiceRecordStatus, 6 postServiceConstAfterInvoicingInvoiceRecordStatus,
22 - postServiceConstBeforeInvoicingInvoiceRecordStatus,  
23 postServiceConstInvoiceType, 7 postServiceConstInvoiceType,
24 - postServiceConstInvoicingType,  
25 - postServiceInvoiceDeleteInvoice,  
26 postServiceInvoiceInvoicing, 8 postServiceInvoiceInvoicing,
27 - postServiceInvoiceQueryInvoice,  
28 postServiceInvoiceQueryInvoiceRecordList, 9 postServiceInvoiceQueryInvoiceRecordList,
29 postServiceOrderQuerySalesCode, 10 postServiceOrderQuerySalesCode,
30 } from '@/services'; 11 } from '@/services';
31 import { excelExport } from '@/services/exportRequest'; 12 import { excelExport } from '@/services/exportRequest';
32 -import {  
33 - enumToProTableEnumValue,  
34 - enumToSelect,  
35 - enumValueToLabel,  
36 - formatDateTime,  
37 -} from '@/utils';  
38 -import { formatDate } from '@/utils/time';  
39 -import { PlusOutlined } from '@ant-design/icons'; 13 +import { enumToProTableEnumValue, enumToSelect } from '@/utils';
40 import { ActionType, ModalForm, ProTable } from '@ant-design/pro-components'; 14 import { ActionType, ModalForm, ProTable } from '@ant-design/pro-components';
41 -import { Button, Space, Table, Tabs, message } from 'antd'; 15 +import { Button, message } from 'antd';
42 import { useEffect, useRef, useState } from 'react'; 16 import { useEffect, useRef, useState } from 'react';
43 17
44 const InvoiceRecord = () => { 18 const InvoiceRecord = () => {
45 - const invoiceActionRef = useRef<ActionType>();  
46 - const bankActionRef = useRef<ActionType>();  
47 - const waitDealrecordActionRef = useRef<ActionType>();  
48 const processedRecordRef = useRef<ActionType>(); 19 const processedRecordRef = useRef<ActionType>();
49 const [invoiceTypeValueEnum, setInvoiceTypeValueEnum] = useState({}); 20 const [invoiceTypeValueEnum, setInvoiceTypeValueEnum] = useState({});
50 - const [invoicingTypeValueEnum, setInvoicingTypeValueEnum] = useState({});  
51 const [salesCodeValueEnum, setSalesCodeValueEnum] = useState({}); 21 const [salesCodeValueEnum, setSalesCodeValueEnum] = useState({});
52 - const [bankImportModalVisible, setBankImportModalVisible] = useState(false);  
53 - const [invoiceVerificationVisible, setInvoiceVerificationVisible] =  
54 - useState(false);  
55 - const [invoiceId, setInvoiceId] = useState(undefined);  
56 const [invoiceRecordDetailVisible, setInvoiceRecordDetailVisible] = 22 const [invoiceRecordDetailVisible, setInvoiceRecordDetailVisible] =
57 useState(false); 23 useState(false);
58 const [invoiceRecord, setInvoiceRecord] = useState({}); 24 const [invoiceRecord, setInvoiceRecord] = useState({});
@@ -69,15 +35,6 @@ const InvoiceRecord = () =&gt; { @@ -69,15 +35,6 @@ const InvoiceRecord = () =&gt; {
69 35
70 useEffect(() => { 36 useEffect(() => {
71 async function extracted() { 37 async function extracted() {
72 - let invoicingTypeRet = await postServiceConstInvoicingType();  
73 - setInvoicingTypeValueEnum(invoicingTypeRet.data);  
74 - }  
75 -  
76 - extracted().catch(console.error);  
77 - }, []);  
78 -  
79 - useEffect(() => {  
80 - async function extracted() {  
81 const res = await postServiceOrderQuerySalesCode(); 38 const res = await postServiceOrderQuerySalesCode();
82 let map = {}; 39 let map = {};
83 res.data?.forEach((item) => { 40 res.data?.forEach((item) => {
@@ -92,271 +49,6 @@ const InvoiceRecord = () =&gt; { @@ -92,271 +49,6 @@ const InvoiceRecord = () =&gt; {
92 extracted().catch(console.error); 49 extracted().catch(console.error);
93 }, []); 50 }, []);
94 51
95 - const reloadInvoiceTable = () => {  
96 - invoiceActionRef.current?.reload();  
97 - };  
98 -  
99 - const reloadBankStatementTable = () => {  
100 - bankActionRef.current?.reload();  
101 - };  
102 - const reloadRecordTable = () => {  
103 - waitDealrecordActionRef.current?.reload();  
104 - processedRecordRef.current?.reload();  
105 - };  
106 -  
107 - const getTableCellText = (target: any) => {  
108 - if (!target) {  
109 - return '';  
110 - }  
111 -  
112 - if (target.props) {  
113 - return target.props.text;  
114 - }  
115 -  
116 - return target;  
117 - };  
118 -  
119 - const waitDealRecordColumns = [  
120 - {  
121 - dataIndex: 'index',  
122 - valueType: 'indexBorder',  
123 - hideInSearch: true,  
124 - ellipsis: true,  
125 - width: 48,  
126 - },  
127 - {  
128 - title: '开票编号',  
129 - valueType: 'text',  
130 - dataIndex: 'id',  
131 - copyable: true,  
132 - hideInSearch: true,  
133 - ellipsis: true,  
134 - width: 100,  
135 - },  
136 - {  
137 - title: '发票状态',  
138 - valueType: 'Text',  
139 - dataIndex: 'statusText',  
140 - ellipsis: true,  
141 - hideInSearch: true,  
142 - },  
143 - {  
144 - title: '申请开票时间',  
145 - dataIndex: 'createTime',  
146 - valueType: 'dateTime',  
147 - hideInSearch: true,  
148 - ellipsis: true,  
149 - },  
150 - {  
151 - title: '销售代表',  
152 - valueType: 'text',  
153 - hideInSearch: true,  
154 - ellipsis: true,  
155 - dataIndex: 'createByName',  
156 - },  
157 - {  
158 - title: '购方名称',  
159 - valueType: 'text',  
160 - dataIndex: 'partyAName',  
161 - hideInSearch: true,  
162 - ellipsis: true,  
163 - },  
164 - {  
165 - title: '购方税号',  
166 - valueType: 'text',  
167 - hideInSearch: true,  
168 - dataIndex: 'partyATaxid',  
169 - ellipsis: true,  
170 - },  
171 - {  
172 - title: '收款单位',  
173 - valueType: 'text',  
174 - hideInSearch: true,  
175 - dataIndex: 'partyBName',  
176 - ellipsis: true,  
177 - },  
178 - {  
179 - title: '开票金额',  
180 - valueType: 'money',  
181 - dataIndex: 'price',  
182 - hideInSearch: true,  
183 - ellipsis: true,  
184 - },  
185 - {  
186 - title: '开具类型',  
187 - valueType: 'Text',  
188 - dataIndex: 'invoicingTypeText',  
189 - hideInSearch: true,  
190 - ellipsis: true,  
191 - },  
192 - {  
193 - title: '发票类型',  
194 - valueType: 'Text',  
195 - dataIndex: 'typeText',  
196 - hideInSearch: true,  
197 - ellipsis: true,  
198 - },  
199 - {  
200 - title: '是否加急',  
201 - valueType: 'Text',  
202 - dataIndex: 'isUrgentText',  
203 - hideInSearch: true,  
204 - ellipsis: true,  
205 - },  
206 - {  
207 - title: '申请备注',  
208 - valueType: 'text',  
209 - dataIndex: 'applyInvoicingNotes',  
210 - hideInSearch: true,  
211 - ellipsis: true,  
212 - },  
213 - {  
214 - title: '购方名称',  
215 - valueType: 'Text',  
216 - dataIndex: 'partyANameLike',  
217 - hideInTable: true,  
218 - },  
219 - {  
220 - title: '收款单位',  
221 - valueType: 'select',  
222 - dataIndex: 'partyB',  
223 - filters: true,  
224 - onFilter: true,  
225 - hideInTable: true,  
226 - valueEnum: enumToProTableEnumValue(PAYEE_OPTIONS),  
227 - },  
228 - {  
229 - title: '主订单号',  
230 - valueType: 'Text',  
231 - dataIndex: 'mainOrderId',  
232 - hideInTable: true,  
233 - },  
234 - {  
235 - title: '子订单号',  
236 - valueType: 'Text',  
237 - dataIndex: 'subOrderId',  
238 - hideInTable: true,  
239 - },  
240 - {  
241 - title: '销售代表',  
242 - valueType: 'select',  
243 - dataIndex: 'salesCode',  
244 - filters: true,  
245 - onFilter: true,  
246 - hideInTable: true,  
247 - valueEnum: salesCodeValueEnum,  
248 - },  
249 - {  
250 - title: '发票类型',  
251 - valueType: 'select',  
252 - dataIndex: 'type',  
253 - filters: true,  
254 - onFilter: true,  
255 - hideInTable: true,  
256 - valueEnum: enumToProTableEnumValue(invoiceTypeValueEnum),  
257 - },  
258 - {  
259 - title: '开具类型',  
260 - valueType: 'select',  
261 - dataIndex: 'invoicingType',  
262 - filters: true,  
263 - onFilter: true,  
264 - hideInTable: true,  
265 - valueEnum: enumToProTableEnumValue(invoicingTypeValueEnum),  
266 - },  
267 - {  
268 - title: '开票状态',  
269 - valueType: 'select',  
270 - dataIndex: 'status',  
271 - filters: true,  
272 - onFilter: true,  
273 - hideInTable: true,  
274 - request: async () => {  
275 - const res = await postServiceConstBeforeInvoicingInvoiceRecordStatus();  
276 - return enumToSelect(res.data);  
277 - },  
278 - },  
279 - {  
280 - title: '是否加急',  
281 - valueType: 'select',  
282 - dataIndex: 'isUrgent',  
283 - filters: true,  
284 - onFilter: true,  
285 - hideInTable: true,  
286 - valueEnum: {  
287 - true: {  
288 - text: '是',  
289 - status: true,  
290 - },  
291 - false: {  
292 - text: '否',  
293 - status: false,  
294 - },  
295 - },  
296 - },  
297 - {  
298 - title: '申请开票时间',  
299 - dataIndex: 'createTime',  
300 - valueType: 'dateTimeRange',  
301 - width: 200,  
302 - hideInTable: true,  
303 - search: {  
304 - transform: (value) => {  
305 - if (value) {  
306 - return {  
307 - createTimeGe: value[0],  
308 - createTimeLe: value[1],  
309 - };  
310 - }  
311 - },  
312 - },  
313 - },  
314 - {  
315 - title: '操作',  
316 - valueType: 'option',  
317 - key: 'option',  
318 - render: (text, record) => {  
319 - return [  
320 - /*<InvoiceRecordDetailModal  
321 - key="detail"  
322 - id={record.id}  
323 - subOrderIds={record.subOrderIds}  
324 - onClose={()=>{  
325 - waitDealrecordActionRef.current?.reload();  
326 - }  
327 - }  
328 - />*/  
329 - <>  
330 - {record.paths.includes('DETAIL') && (  
331 - <a  
332 - key="detail"  
333 - onClick={() => {  
334 - setInvoiceRecordDetailVisible(true);  
335 - setInvoiceRecord(record);  
336 - }}  
337 - >  
338 - 详情  
339 - </a>  
340 - )}  
341 - </>,  
342 - <>  
343 - {record.paths.includes('PREVIEW') && (  
344 - <InvoiceModal key="invoiceModal" recordId={record.id} />  
345 - )}  
346 - </>,  
347 - <>  
348 - {record.paths.includes('INVOICING') && (  
349 - <ManualInvoicingModal  
350 - key={'ManualInvoicingModal'}  
351 - record={record}  
352 - ></ManualInvoicingModal>  
353 - )}  
354 - </>,  
355 - ];  
356 - },  
357 - },  
358 - ];  
359 -  
360 const processedRecordColumns = [ 52 const processedRecordColumns = [
361 { 53 {
362 dataIndex: 'index', 54 dataIndex: 'index',
@@ -527,7 +219,7 @@ const InvoiceRecord = () =&gt; { @@ -527,7 +219,7 @@ const InvoiceRecord = () =&gt; {
527 key: 'option', 219 key: 'option',
528 render: (text, record) => [ 220 render: (text, record) => [
529 <> 221 <>
530 - {record.status === 'SUCCESS' && record.paths.includes('DETAIL') && ( 222 + {record.paths.includes('DETAIL') && (
531 <a 223 <a
532 key="detail" 224 key="detail"
533 onClick={() => { 225 onClick={() => {
@@ -589,576 +281,91 @@ const InvoiceRecord = () =&gt; { @@ -589,576 +281,91 @@ const InvoiceRecord = () =&gt; {
589 ], 281 ],
590 }, 282 },
591 ]; 283 ];
592 - /**  
593 - * 加载发票列表表格的各个列格式  
594 - */  
595 - const invoicecColumnsInit = () => {  
596 - let columns = INVOICE_COLUMNS.map((item) => {  
597 - let newItem = { ...item };  
598 - let dataIndex = item.dataIndex;  
599 - let dataType = item.valueType;  
600 -  
601 - newItem.render = (text, record) => {  
602 - let textValue = record[dataIndex];  
603 -  
604 - if (dataType === 'dateRange' || dataType === 'date') {  
605 - textValue = formatDate(textValue);  
606 - }  
607 -  
608 - if (dataType === 'dateTime') {  
609 - textValue = formatDateTime(textValue);  
610 - }  
611 -  
612 - if (dataType === 'money') {  
613 - textValue = '¥' + textValue;  
614 - }  
615 -  
616 - switch (dataIndex) {  
617 - case 'invoiceStatus':  
618 - return (  
619 - <EllipsisDiv  
620 - text={enumValueToLabel(  
621 - getTableCellText(textValue),  
622 - INVOCING_STATUS,  
623 - )}  
624 - />  
625 - );  
626 -  
627 - case 'status':  
628 - return (  
629 - <EllipsisDiv  
630 - text={enumValueToLabel(  
631 - getTableCellText(textValue),  
632 - INVOICE_STATUS,  
633 - )}  
634 - />  
635 - );  
636 -  
637 - case 'payee':  
638 - return (  
639 - <EllipsisDiv  
640 - text={enumValueToLabel(  
641 - getTableCellText(textValue),  
642 - PAYEE_OPTIONS,  
643 - )}  
644 - />  
645 - );  
646 -  
647 - default:  
648 - return <EllipsisDiv text={getTableCellText(textValue)} />;  
649 - }  
650 - };  
651 -  
652 - return newItem;  
653 - });  
654 -  
655 - columns.push({  
656 - title: '操作',  
657 - valueType: 'option',  
658 - key: 'option',  
659 - fixed: 'right',  
660 - width: 120,  
661 - render: (text, record) => {  
662 - let btns = [];  
663 - if (record.path?.includes('writeOff')) {  
664 - btns.push(  
665 - <a  
666 - key="editable"  
667 - onClick={() => {  
668 - setInvoiceVerificationVisible(true);  
669 - setInvoiceId(record.invoiceId);  
670 - }}  
671 - >  
672 - 核销  
673 - </a>,  
674 - );  
675 - }  
676 -  
677 - if (record.path?.includes('queryInvoiceDetails')) {  
678 - btns.push( 284 + return (
  285 + <div className="invoice-index">
  286 + <ProTable
  287 + columns={processedRecordColumns}
  288 + actionRef={processedRecordRef}
  289 + cardBordered
  290 + pagination={{
  291 + showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单
  292 + pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项
  293 + }}
  294 + editable={{
  295 + type: 'multiple',
  296 + onSave: async (rowKey, data) => {
  297 + await postServiceBankStatementEditBankStatement({ data: data });
  298 + },
  299 + actionRender: (row, config, defaultDom) => [
  300 + defaultDom.save,
  301 + defaultDom.cancel,
  302 + ],
  303 + }}
  304 + search={{
  305 + labelWidth: 'auto',
  306 + defaultCollapsed: false,
  307 + optionRender: (searchConfig, formProps, dom) => [
  308 + ...dom,
679 <Button 309 <Button
680 - className="p-0"  
681 - key="view"  
682 - type="link" 310 + key="out"
683 onClick={() => { 311 onClick={() => {
684 - setInvoiceVerificationVisible(true);  
685 - setInvoiceId(record.invoiceId);  
686 - }}  
687 - >  
688 - 查看  
689 - </Button>,  
690 - );  
691 - }  
692 -  
693 - if (record.path?.includes('deleteInvoice')) {  
694 - btns.push(  
695 - <ButtonConfirm  
696 - key="delete"  
697 - className="p-0"  
698 - title={  
699 - '确认删除发票号码为[ ' + record.invoiceNumber + ' ]的发票吗?'  
700 - }  
701 - text="删除"  
702 - onConfirm={async () => {  
703 - let res = await postServiceInvoiceDeleteInvoice({  
704 - data: { invoiceId: record.invoiceId }, 312 + const values = searchConfig?.form?.getFieldsValue();
  313 + console.log(values);
  314 + messageApi.open({
  315 + type: 'loading',
  316 + content: '正在导出文件...',
705 }); 317 });
706 - if (res) {  
707 - message.success(res.message);  
708 - reloadInvoiceTable();  
709 - }  
710 - }}  
711 - />,  
712 - );  
713 - }  
714 - return btns;  
715 - },  
716 - });  
717 -  
718 - return columns;  
719 - };  
720 -  
721 - const bankStatemetColumnsInit = () => {  
722 - let columns = BANK_STATEMENT_COLUMNS.map((item) => {  
723 - let newItem = { ...item };  
724 - let dataIndex = item.dataIndex;  
725 - let dataType = item.valueType;  
726 -  
727 - newItem.render = (text, record) => {  
728 - let textValue = record[dataIndex];  
729 -  
730 - if (dataType === 'date') {  
731 - textValue = formatDate(textValue);  
732 - }  
733 -  
734 - if (dataType === 'dateTime') {  
735 - textValue = formatDateTime(textValue);  
736 - }  
737 -  
738 - if (dataType === 'money') {  
739 - if (textValue === null || textValue === undefined) {  
740 - textValue = '';  
741 - } else {  
742 - textValue = '¥' + textValue;  
743 - }  
744 - }  
745 -  
746 - switch (dataIndex) {  
747 - case 'invoiceStatus':  
748 - return (  
749 - <EllipsisDiv  
750 - text={enumValueToLabel(  
751 - getTableCellText(textValue),  
752 - INVOCING_STATUS,  
753 - )}  
754 - />  
755 - );  
756 -  
757 - case 'status':  
758 - return (  
759 - <EllipsisDiv  
760 - text={enumValueToLabel(  
761 - getTableCellText(textValue),  
762 - INVOICE_STATUS,  
763 - )}  
764 - />  
765 - );  
766 -  
767 - case 'payee':  
768 - return (  
769 - <EllipsisDiv  
770 - text={enumValueToLabel(  
771 - getTableCellText(textValue),  
772 - PAYEE_OPTIONS,  
773 - )}  
774 - />  
775 - );  
776 -  
777 - default:  
778 - return <EllipsisDiv text={getTableCellText(textValue)} />;  
779 - }  
780 - };  
781 -  
782 - return newItem;  
783 - });  
784 -  
785 - columns.push({  
786 - title: '操作',  
787 - valueType: 'option',  
788 - key: 'option',  
789 - fixed: 'right',  
790 - width: 120,  
791 - render: (text, record, _, action) => {  
792 - let btns = [];  
793 - if (record.path?.includes('editBankStatement')) {  
794 - btns.push(  
795 - <a  
796 - key="editable"  
797 - onClick={() => {  
798 - action?.startEditable?.(record.id);  
799 - }}  
800 - >  
801 - 编辑  
802 - </a>,  
803 - );  
804 - }  
805 -  
806 - if (record.path?.includes('deleteBankStatement')) {  
807 - btns.push(  
808 - <ButtonConfirm  
809 - key="delete"  
810 - className="p-0"  
811 - title={'是否删除该银行流水记录?'}  
812 - text="删除"  
813 - onConfirm={async () => {  
814 - let res = await postServiceBankStatementDeleteBankStatement({  
815 - data: { id: record.id },  
816 - });  
817 - if (res.result === RESPONSE_CODE.SUCCESS) {  
818 - message.success(res.message);  
819 - reloadBankStatementTable();  
820 - }  
821 - }}  
822 - />,  
823 - );  
824 - }  
825 - return btns;  
826 - },  
827 - });  
828 -  
829 - return columns;  
830 - };  
831 -  
832 - const tabsItems = [  
833 - {  
834 - key: 1,  
835 - label: '待处理',  
836 - children: (  
837 - <ProTable  
838 - columns={waitDealRecordColumns}  
839 - actionRef={waitDealrecordActionRef}  
840 - cardBordered  
841 - pagination={{  
842 - showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单  
843 - pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项  
844 - }}  
845 - rowSelection={{  
846 - selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],  
847 - alwaysShowAlert: true,  
848 - }}  
849 - tableAlertOptionRender={({ selectedRowKeys, selectedRows }) => {  
850 - console.log(selectedRows);  
851 - console.log(selectedRowKeys);  
852 - return (  
853 - <Space size={16}>  
854 - <InvoicingModal  
855 - reloadRecordTable={reloadRecordTable}  
856 - key="button"  
857 - selectedRowKeys={selectedRowKeys}  
858 - />  
859 - </Space>  
860 - );  
861 - }}  
862 - request={async (params) => {  
863 - let res = await postServiceInvoiceQueryInvoiceRecordList({  
864 - data: {  
865 - ...params,  
866 - statusIn: [  
867 - 'WAITING_FOR_INVOICING',  
868 - 'AUDITING',  
869 - 'AUDITING_NOT_PASSED',  
870 - 'CANCELED',  
871 - ],  
872 - needBuildDetails: true,  
873 - needBuildSubOrders: true,  
874 - },  
875 - });  
876 - return {  
877 - data: res?.data?.data,  
878 - total: res?.data?.total || 0,  
879 - };  
880 - }}  
881 - columnsState={{  
882 - persistenceKey: 'pro-table-singe-demos',  
883 - persistenceType: 'localStorage',  
884 - defaultValue: {  
885 - option: { fixed: 'right', disable: true },  
886 - },  
887 - onChange(value) {  
888 - console.log('value: ', value);  
889 - },  
890 - }}  
891 - rowKey="id"  
892 - search={{  
893 - labelWidth: 'auto',  
894 - }}  
895 - options={{  
896 - setting: {  
897 - listsHeight: 400,  
898 - },  
899 - }}  
900 - form={{}}  
901 - dateFormatter="string"  
902 - headerTitle="待开票列表"  
903 - scroll={{ x: 1400, y: 360 }}  
904 - />  
905 - ),  
906 - },  
907 - {  
908 - key: 2,  
909 - label: '开票记录',  
910 - children: (  
911 - <ProTable  
912 - columns={processedRecordColumns}  
913 - actionRef={processedRecordRef}  
914 - cardBordered  
915 - pagination={{  
916 - showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单  
917 - pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项  
918 - }}  
919 - editable={{  
920 - type: 'multiple',  
921 - onSave: async (rowKey, data) => {  
922 - await postServiceBankStatementEditBankStatement({ data: data });  
923 - },  
924 - actionRender: (row, config, defaultDom) => [  
925 - defaultDom.save,  
926 - defaultDom.cancel,  
927 - ],  
928 - }}  
929 - search={{  
930 - labelWidth: 'auto',  
931 - defaultCollapsed: false,  
932 - optionRender: (searchConfig, formProps, dom) => [  
933 - ...dom,  
934 - <Button  
935 - key="out"  
936 - onClick={() => {  
937 - const values = searchConfig?.form?.getFieldsValue();  
938 - console.log(values);  
939 - messageApi.open({  
940 - type: 'loading',  
941 - content: '正在导出文件...',  
942 - });  
943 - excelExport(  
944 - '/api/service/invoice/exportInvoiceRecords',  
945 - {  
946 - ...values,  
947 - statusIn: ['INVOICING', 'SUCCESS', 'FAIL'],  
948 - },  
949 - () => {  
950 - messageApi.destroy();  
951 - },  
952 - );  
953 - }}  
954 - >  
955 - 导出  
956 - </Button>,  
957 - ],  
958 - }}  
959 - request={async (params) => {  
960 - let res = await postServiceInvoiceQueryInvoiceRecordList({  
961 - data: {  
962 - ...params,  
963 - statusIn: ['INVOICING', 'SUCCESS', 'FAIL'],  
964 - },  
965 - });  
966 - return {  
967 - data: res?.data?.data,  
968 - total: res?.data?.total || 0,  
969 - };  
970 - }}  
971 - columnsState={{  
972 - persistenceKey: 'pro-table-singe-demos',  
973 - persistenceType: 'localStorage',  
974 - defaultValue: {  
975 - option: { fixed: 'right', disable: true },  
976 - },  
977 - onChange(value) {  
978 - console.log('value: ', value);  
979 - },  
980 - }}  
981 - rowKey="id"  
982 - options={{  
983 - setting: {  
984 - listsHeight: 400,  
985 - },  
986 - }}  
987 - form={{}}  
988 - dateFormatter="string"  
989 - headerTitle="待开票列表"  
990 - scroll={{ x: 1400, y: 360 }}  
991 - toolBarRender={() => []}  
992 - />  
993 - ),  
994 - },  
995 - {  
996 - key: 3,  
997 - label: '发票管理',  
998 - children: (  
999 - <ProTable  
1000 - columns={invoicecColumnsInit()}  
1001 - actionRef={invoiceActionRef}  
1002 - cardBordered  
1003 - pagination={{  
1004 - pageSize: 10,  
1005 - }}  
1006 - request={async (params) => {  
1007 - const res = await postServiceInvoiceQueryInvoice({  
1008 - data: { ...params },  
1009 - });  
1010 - if (res) {  
1011 - return {  
1012 - data: res?.data?.data || [],  
1013 - total: res?.data?.total || 0,  
1014 - };  
1015 - }  
1016 - }}  
1017 - columnsState={{  
1018 - persistenceKey: 'pro-table-singe-demos',  
1019 - persistenceType: 'localStorage',  
1020 - defaultValue: {  
1021 - option: { fixed: 'right', disable: true },  
1022 - },  
1023 - onChange(value) {  
1024 - console.log('value: ', value);  
1025 - },  
1026 - }}  
1027 - rowKey="id"  
1028 - search={{  
1029 - labelWidth: 'auto',  
1030 - }}  
1031 - options={{  
1032 - setting: {  
1033 - listsHeight: 400,  
1034 - },  
1035 - }}  
1036 - form={{}}  
1037 - dateFormatter="string"  
1038 - headerTitle="发票列表"  
1039 - scroll={{ x: 1400, y: 360 }}  
1040 - toolBarRender={() => [  
1041 - <AddInvoiceDrawerForm  
1042 - onClose={() => {  
1043 - invoiceActionRef.current?.reload();  
1044 - bankActionRef.current?.reload();  
1045 - }}  
1046 - key="add"  
1047 - ></AddInvoiceDrawerForm>,  
1048 - ]}  
1049 - />  
1050 - ),  
1051 - },  
1052 - {  
1053 - key: 4,  
1054 - label: '银行流水',  
1055 - children: (  
1056 - <ProTable  
1057 - columns={bankStatemetColumnsInit()}  
1058 - actionRef={bankActionRef}  
1059 - cardBordered  
1060 - pagination={{  
1061 - pageSize: 10,  
1062 - }}  
1063 - editable={{  
1064 - type: 'multiple',  
1065 - onSave: async (rowKey, data) => {  
1066 - await postServiceBankStatementEditBankStatement({ data: data });  
1067 - },  
1068 - actionRender: (row, config, defaultDom) => [  
1069 - defaultDom.save,  
1070 - defaultDom.cancel,  
1071 - ],  
1072 - }}  
1073 - request={async (params) => {  
1074 - const res = await postServiceBankStatementQueryBankStatement({  
1075 - data: { ...params },  
1076 - });  
1077 - if (res) {  
1078 - return {  
1079 - data: res?.data?.data || [],  
1080 - total: res?.data?.total || 0,  
1081 - };  
1082 - }  
1083 - }}  
1084 - columnsState={{  
1085 - persistenceKey: 'pro-table-singe-demos',  
1086 - persistenceType: 'localStorage',  
1087 - defaultValue: {  
1088 - option: { fixed: 'right', disable: true },  
1089 - },  
1090 - onChange(value) {  
1091 - console.log('value: ', value);  
1092 - },  
1093 - }}  
1094 - rowKey="id"  
1095 - search={{  
1096 - labelWidth: 'auto',  
1097 - }}  
1098 - options={{  
1099 - setting: {  
1100 - listsHeight: 400,  
1101 - },  
1102 - }}  
1103 - form={{}}  
1104 - dateFormatter="string"  
1105 - headerTitle="银行流水列表"  
1106 - scroll={{ x: 1400, y: 360 }}  
1107 - toolBarRender={() => [  
1108 - <Button  
1109 - key="button"  
1110 - icon={<PlusOutlined />}  
1111 - onClick={() => {  
1112 - setBankImportModalVisible(true); 318 + excelExport(
  319 + '/api/service/invoice/exportInvoiceRecords',
  320 + {
  321 + ...values,
  322 + statusIn: ['INVOICING', 'SUCCESS', 'FAIL', 'REISSUED'],
  323 + },
  324 + () => {
  325 + messageApi.destroy();
  326 + },
  327 + );
1113 }} 328 }}
1114 - type="primary"  
1115 > 329 >
1116 - 导 330 + 导
1117 </Button>, 331 </Button>,
1118 - ]}  
1119 - />  
1120 - ),  
1121 - },  
1122 - ];  
1123 - return (  
1124 - <div className="invoice-index">  
1125 - <Tabs  
1126 - defaultActiveKey="1"  
1127 - items={tabsItems}  
1128 - onChange={(value) => {  
1129 - if (value === 1) {  
1130 - invoiceActionRef.current?.reload();  
1131 - } else {  
1132 - bankActionRef.current?.reload();  
1133 - } 332 + ],
  333 + }}
  334 + request={async (params) => {
  335 + let res = await postServiceInvoiceQueryInvoiceRecordList({
  336 + data: {
  337 + ...params,
  338 + statusIn: ['INVOICING', 'SUCCESS', 'FAIL', 'REISSUED'],
  339 + },
  340 + });
  341 + return {
  342 + data: res?.data?.data,
  343 + total: res?.data?.total || 0,
  344 + };
1134 }} 345 }}
  346 + columnsState={{
  347 + persistenceKey: 'pro-table-singe-demos',
  348 + persistenceType: 'localStorage',
  349 + defaultValue: {
  350 + option: { fixed: 'right', disable: true },
  351 + },
  352 + onChange(value) {
  353 + console.log('value: ', value);
  354 + },
  355 + }}
  356 + rowKey="id"
  357 + options={{
  358 + setting: {
  359 + listsHeight: 400,
  360 + },
  361 + }}
  362 + form={{}}
  363 + dateFormatter="string"
  364 + headerTitle="待开票列表"
  365 + scroll={{ x: 1400, y: 360 }}
  366 + toolBarRender={() => []}
1135 /> 367 />
1136 368
1137 - {bankImportModalVisible ? (  
1138 - <BankImportModal  
1139 - setVisible={setBankImportModalVisible}  
1140 - onClose={() => {  
1141 - setBankImportModalVisible(false);  
1142 - invoiceActionRef.current?.reload();  
1143 - bankActionRef.current?.reload();  
1144 - }}  
1145 - ></BankImportModal>  
1146 - ) : (  
1147 - ''  
1148 - )}  
1149 -  
1150 - {invoiceVerificationVisible ? (  
1151 - <InvoiceVerificationModal  
1152 - setVisible={setInvoiceVerificationVisible}  
1153 - invoiceId={invoiceId}  
1154 - onClose={() => {  
1155 - invoiceActionRef.current?.reload();  
1156 - bankActionRef.current?.reload();  
1157 - }}  
1158 - ></InvoiceVerificationModal>  
1159 - ) : (  
1160 - ''  
1161 - )}  
1162 {invoiceRecordDetailVisible ? ( 369 {invoiceRecordDetailVisible ? (
1163 <InvoiceRecordDetailModal 370 <InvoiceRecordDetailModal
1164 key="detail" 371 key="detail"
src/pages/Invoice/waitProcessRecord/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/waitProcessRecord/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/waitProcessRecord/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/waitProcessRecord/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/waitProcessRecord/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/waitProcessRecord/components/InvoiceDetailTable.tsx 0 → 100644
  1 +import InvoiceDetailImportModal from '@/pages/Invoice/waitProcessRecord/components/InvoiceDetailImportModal';
  2 +import { InvoiceProjectSelect } from '@/pages/Invoice/waitProcessRecord/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/waitProcessRecord/components/InvoiceModal.tsx 0 → 100644
  1 +import Invoice from '@/pages/Invoice/waitProcessRecord/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/waitProcessRecord/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/waitProcessRecord/components/InvoiceRecordDetailModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import InvoiceDetailTable from '@/pages/Invoice/waitProcessRecord/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 +};