Commit 108b267149286cef2454be8eb97318b685708de7

Authored by 柏杨
1 parent 40715ed1

feat: 手动开票页面

src/pages/Invoice/waitProcessRecord/components/ManualInvoiceModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { PAYEE_OPTIONS } from '@/pages/Order/constant';
  3 +import { postServiceOrderInvoicing } from '@/services';
  4 +import {
  5 + ModalForm,
  6 + ProFormDatePicker,
  7 + ProFormDigit,
  8 + ProFormSelect,
  9 + ProFormText,
  10 +} from '@ant-design/pro-components';
  11 +import { Form, message } from 'antd';
  12 +import { useState } from 'react';
  13 +
  14 +interface ManualInvoiceModalProps {
  15 + record: any;
  16 + onSuccess?: () => void;
  17 +}
  18 +
  19 +const ManualInvoiceModal: React.FC<ManualInvoiceModalProps> = ({
  20 + record,
  21 + onSuccess,
  22 +}) => {
  23 + const [form] = Form.useForm();
  24 + const [visible, setVisible] = useState(false);
  25 +
  26 + // 转换PAYEE_OPTIONS为Select组件需要的格式
  27 + const payeeOptions = Object.entries(PAYEE_OPTIONS).map(([value, label]) => ({
  28 + value,
  29 + label,
  30 + }));
  31 +
  32 + return (
  33 + <ModalForm
  34 + title="手动开票"
  35 + trigger={
  36 + <a
  37 + key="invoice"
  38 + style={{ color: '#1890ff' }}
  39 + onClick={() => setVisible(true)}
  40 + >
  41 + 开票
  42 + </a>
  43 + }
  44 + width={600}
  45 + layout={'horizontal'}
  46 + form={form}
  47 + open={visible}
  48 + onOpenChange={setVisible}
  49 + autoFocusFirstInput
  50 + modalProps={{
  51 + destroyOnClose: true,
  52 + }}
  53 + initialValues={{
  54 + purchaser: record.partyAName,
  55 + payee: record.partyB,
  56 + money: record.price,
  57 + }}
  58 + submitTimeout={2000}
  59 + onFinish={async (values) => {
  60 + try {
  61 + const body = {
  62 + invoiceIdentificationNumber: values.purchaser,
  63 + invoicingTime: values.invoicingTime,
  64 + purchaser: values.purchaser,
  65 + financialReceiptIssuanceTime:
  66 + values.financialReceiptIssuanceTime || values.invoicingTime,
  67 + collectMoneyTime: values.collectMoneyTime || values.invoicingTime,
  68 + invoiceNumber: values.invoiceNumber,
  69 + payee: values.payee,
  70 + money: values.money,
  71 + afterInvoicingStatus: [record.type],
  72 + mainOrderIds: record.mainOrderIds,
  73 + invoiceRecordIds: [record.id],
  74 + };
  75 +
  76 + // 根据现有组件实现,使用data来传递参数
  77 + const res = await postServiceOrderInvoicing({
  78 + data: body,
  79 + });
  80 +
  81 + if (res.result === RESPONSE_CODE.SUCCESS) {
  82 + message.success('开票成功');
  83 + if (onSuccess) {
  84 + onSuccess();
  85 + }
  86 + return true;
  87 + } else {
  88 + message.error('开票失败');
  89 + return false;
  90 + }
  91 + } catch (error) {
  92 + message.error('操作失败');
  93 + return false;
  94 + }
  95 + }}
  96 + >
  97 + <ProFormText
  98 + width="md"
  99 + name="purchaser"
  100 + label="抬头名称"
  101 + rules={[{ required: true, message: '请输入抬头名称' }]}
  102 + disabled
  103 + />
  104 +
  105 + <ProFormText
  106 + width="md"
  107 + name="invoiceType"
  108 + label="发票类型"
  109 + initialValue="普票"
  110 + disabled
  111 + />
  112 +
  113 + <ProFormDigit
  114 + width="md"
  115 + name="money"
  116 + label="发票金额"
  117 + fieldProps={{
  118 + precision: 2,
  119 + prefix: '¥',
  120 + }}
  121 + rules={[{ required: true, message: '请输入发票金额' }]}
  122 + disabled
  123 + />
  124 +
  125 + <ProFormSelect
  126 + width="md"
  127 + name="payee"
  128 + label="收款单位"
  129 + options={payeeOptions}
  130 + rules={[{ required: true, message: '请选择收款单位' }]}
  131 + disabled
  132 + />
  133 +
  134 + <ProFormText
  135 + width="md"
  136 + name="invoiceNumber"
  137 + label="发票号码"
  138 + rules={[{ required: true, message: '请输入发票号码' }]}
  139 + />
  140 +
  141 + <ProFormDatePicker
  142 + width="md"
  143 + name="invoicingTime"
  144 + label="开票时间"
  145 + rules={[{ required: true, message: '请选择开票时间' }]}
  146 + fieldProps={{
  147 + format: 'YYYY-MM-DD',
  148 + }}
  149 + />
  150 + </ModalForm>
  151 + );
  152 +};
  153 +
  154 +export default ManualInvoiceModal;
... ...
src/pages/Invoice/waitProcessRecord/index.tsx
... ... @@ -2,6 +2,7 @@ import { RESPONSE_CODE } from &#39;@/constants/enum&#39;;
2 2 import InvoiceRecordDetailModal from '@/pages/Invoice/InvoiceRecord/components/InvoiceRecordDetailModal';
3 3 import InvoiceModal from '@/pages/Invoice/waitProcessRecord/components/InvoiceModal';
4 4 import InvoicingModal from '@/pages/Invoice/waitProcessRecord/components/InvoicingModal';
  5 +import ManualInvoiceModal from '@/pages/Invoice/waitProcessRecord/components/ManualInvoiceModal';
5 6 import ManualInvoicingModal from '@/pages/Invoice/waitProcessRecord/components/ManualInvoicingModal';
6 7 import { PAYEE_OPTIONS } from '@/pages/Order/constant';
7 8 import {
... ... @@ -20,6 +21,7 @@ import {
20 21 Popconfirm,
21 22 Space,
22 23 Table,
  24 + Tabs,
23 25 Tooltip,
24 26 message,
25 27 } from 'antd';
... ... @@ -27,12 +29,15 @@ import { useEffect, useRef, useState } from &#39;react&#39;;
27 29  
28 30 const InvoiceRecord = () => {
29 31 const waitDealrecordActionRef = useRef<ActionType>();
  32 + const manualInvoiceActionRef = useRef<ActionType>();
30 33 const [invoiceTypeValueEnum, setInvoiceTypeValueEnum] = useState({});
31 34 const [invoicingTypeValueEnum, setInvoicingTypeValueEnum] = useState({});
32 35 const [salesCodeValueEnum, setSalesCodeValueEnum] = useState({});
33 36 const [invoiceRecordDetailVisible, setInvoiceRecordDetailVisible] =
34 37 useState(false);
35 38 const [invoiceRecord, setInvoiceRecord] = useState({});
  39 + const [activeTab, setActiveTab] = useState('auto');
  40 +
36 41 useEffect(() => {
37 42 async function extracted() {
38 43 let invoiceTypeRet = await postServiceConstInvoiceType();
... ... @@ -65,7 +70,11 @@ const InvoiceRecord = () =&gt; {
65 70 }, []);
66 71  
67 72 const reloadRecordTable = () => {
68   - waitDealrecordActionRef.current?.reload();
  73 + if (activeTab === 'auto') {
  74 + waitDealrecordActionRef.current?.reload();
  75 + } else {
  76 + manualInvoiceActionRef.current?.reload();
  77 + }
69 78 };
70 79  
71 80 const waitDealRecordColumns = [
... ... @@ -287,11 +296,11 @@ const InvoiceRecord = () =&gt; {
287 296 valueEnum: {
288 297 true: {
289 298 text: '是',
290   - status: true,
  299 + status: 'true',
291 300 },
292 301 false: {
293 302 text: '否',
294   - status: false,
  303 + status: 'false',
295 304 },
296 305 },
297 306 },
... ... @@ -319,15 +328,6 @@ const InvoiceRecord = () =&gt; {
319 328 width: 200,
320 329 render: (text, record) => {
321 330 return [
322   - /*<InvoiceRecordDetailModal
323   - key="detail"
324   - id={record.id}
325   - subOrderIds={record.subOrderIds}
326   - onClose={()=>{
327   - waitDealrecordActionRef.current?.reload();
328   - }
329   - }
330   - />*/
331 331 <>
332 332 {record.paths.includes('DETAIL') && (
333 333 <a
... ... @@ -368,7 +368,7 @@ const InvoiceRecord = () =&gt; {
368 368 if (res.result === RESPONSE_CODE.SUCCESS) {
369 369 message.success('取消成功');
370 370 }
371   - waitDealrecordActionRef?.current?.reload();
  371 + reloadRecordTable();
372 372 }}
373 373 okText="确定"
374 374 cancelText="取消"
... ... @@ -383,74 +383,330 @@ const InvoiceRecord = () =&gt; {
383 383 },
384 384 },
385 385 ];
386   - return (
387   - <div className="invoice-index">
388   - <ProTable
389   - columns={waitDealRecordColumns}
390   - actionRef={waitDealrecordActionRef}
391   - cardBordered
392   - pagination={{
393   - showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单
394   - pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项
395   - }}
396   - rowSelection={{
397   - selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
398   - alwaysShowAlert: true,
399   - }}
400   - tableAlertOptionRender={({ selectedRowKeys, selectedRows }) => {
401   - console.log(selectedRows);
402   - console.log(selectedRowKeys);
403   - return (
404   - <Space size={16}>
405   - <InvoicingModal
406   - reloadRecordTable={reloadRecordTable}
407   - key="button"
408   - selectedRowKeys={selectedRowKeys}
409   - />
410   - </Space>
411   - );
412   - }}
413   - request={async (params) => {
414   - let res = await postServiceInvoiceQueryInvoiceRecordList({
415   - data: {
416   - ...params,
417   - statusIn: [
418   - 'WAITING_FOR_INVOICING',
419   - 'AUDITING',
420   - 'AUDITING_NOT_PASSED',
421   - ],
422   - needBuildDetails: true,
423   - needBuildSubOrders: true,
424   - },
  386 +
  387 + // 手动开票表格列配置
  388 + const manualInvoiceColumns = [
  389 + {
  390 + dataIndex: 'index',
  391 + valueType: 'indexBorder',
  392 + hideInSearch: true,
  393 + ellipsis: true,
  394 + width: 48,
  395 + },
  396 + {
  397 + title: '开票编号',
  398 + valueType: 'text',
  399 + dataIndex: 'id',
  400 + copyable: true,
  401 + hideInSearch: true,
  402 + ellipsis: true,
  403 + width: 100,
  404 + },
  405 + {
  406 + title: '订单号',
  407 + valueType: 'text',
  408 + dataIndex: 'mainOrderIds',
  409 + hideInSearch: true,
  410 + ellipsis: true,
  411 + width: 150,
  412 + },
  413 + {
  414 + title: '开票金额',
  415 + valueType: 'money',
  416 + dataIndex: 'price',
  417 + width: 100,
  418 + hideInSearch: true,
  419 + ellipsis: true,
  420 + },
  421 + {
  422 + title: '购方名称',
  423 + valueType: 'text',
  424 + dataIndex: 'partyAName',
  425 + width: 150,
  426 + hideInSearch: true,
  427 + ellipsis: true,
  428 + },
  429 + {
  430 + title: '收款单位',
  431 + valueType: 'text',
  432 + hideInSearch: true,
  433 + width: 150,
  434 + dataIndex: 'partyBName',
  435 + ellipsis: true,
  436 + },
  437 + {
  438 + title: '开票类型',
  439 + valueType: 'Text',
  440 + dataIndex: 'typeText',
  441 + width: 100,
  442 + hideInSearch: true,
  443 + ellipsis: true,
  444 + },
  445 + {
  446 + title: '是否加急',
  447 + valueType: 'Text',
  448 + dataIndex: 'isUrgentText',
  449 + width: 80,
  450 + hideInSearch: true,
  451 + ellipsis: true,
  452 + },
  453 + {
  454 + title: '附件',
  455 + key: 'filePaths',
  456 + ellipsis: true,
  457 + width: 100,
  458 + hideInSearch: true,
  459 + render: (_, record) => {
  460 + if (record.filePaths && record.filePaths.length > 0) {
  461 + return record.filePaths.map((item) => {
  462 + // Extract filename from URL
  463 + const url = item;
  464 + const filenameMatch = url.match(/\/([^/]+)\?/);
  465 + const filename = filenameMatch ? filenameMatch[1] : 'file';
  466 +
  467 + // Decode the filename to handle URL encoding
  468 + const decodedFilename = decodeURIComponent(filename);
  469 +
  470 + // Get the clean filename without timestamp prefix if present
  471 + const cleanFilename = decodedFilename.replace(/^\d+-\d+-/, '');
  472 +
  473 + return (
  474 + <div style={{ marginBottom: '5px' }} key={url}>
  475 + <a
  476 + href={url}
  477 + rel="noopener noreferrer"
  478 + download={cleanFilename}
  479 + >
  480 + {cleanFilename}
  481 + </a>
  482 + </div>
  483 + );
425 484 });
426   - return {
427   - data: res?.data?.data,
428   - total: res?.data?.total || 0,
429   - };
430   - }}
431   - columnsState={{
432   - persistenceKey: 'pro-table-singe-demos',
433   - persistenceType: 'localStorage',
434   - defaultValue: {
435   - option: { fixed: 'right', disable: true },
  485 + }
  486 + return '-';
  487 + },
  488 + },
  489 + {
  490 + title: '申请时间',
  491 + dataIndex: 'createTime',
  492 + valueType: 'dateTime',
  493 + width: 160,
  494 + hideInSearch: true,
  495 + ellipsis: true,
  496 + },
  497 + {
  498 + title: '申请人',
  499 + valueType: 'text',
  500 + hideInSearch: false,
  501 + ellipsis: true,
  502 + width: 100,
  503 + dataIndex: 'createByName',
  504 + },
  505 + {
  506 + title: '操作',
  507 + valueType: 'option',
  508 + key: 'option',
  509 + width: 120,
  510 + render: (_, record) => {
  511 + return [
  512 + <ManualInvoiceModal
  513 + key="invoice"
  514 + record={record}
  515 + onSuccess={() => reloadRecordTable()}
  516 + />,
  517 + <Popconfirm
  518 + key="cancel"
  519 + title="取消开票"
  520 + description="确认取消开票?"
  521 + onConfirm={async () => {
  522 + let res = await postServiceInvoiceCancelInvoiceRecord({
  523 + data: {
  524 + invoiceRecordIds: [record.id],
  525 + },
  526 + });
  527 + if (res.result === RESPONSE_CODE.SUCCESS) {
  528 + message.success('取消成功');
  529 + }
  530 + reloadRecordTable();
  531 + }}
  532 + okText="确定"
  533 + cancelText="取消"
  534 + >
  535 + <a key="cancel" style={{ color: '#ff4d4f' }}>
  536 + 取消
  537 + </a>
  538 + </Popconfirm>,
  539 + ];
  540 + },
  541 + },
  542 + // 搜索项
  543 + {
  544 + title: '订单号',
  545 + valueType: 'Text',
  546 + dataIndex: 'mainOrderIds',
  547 + hideInTable: true,
  548 + },
  549 + {
  550 + title: '购方名称',
  551 + valueType: 'Text',
  552 + dataIndex: 'partyANameLike',
  553 + hideInTable: true,
  554 + },
  555 + {
  556 + title: '收款单位',
  557 + valueType: 'select',
  558 + dataIndex: 'partyB',
  559 + filters: true,
  560 + onFilter: true,
  561 + hideInTable: true,
  562 + valueEnum: enumToProTableEnumValue(PAYEE_OPTIONS),
  563 + },
  564 + {
  565 + title: '发票类型',
  566 + valueType: 'select',
  567 + dataIndex: 'type',
  568 + filters: true,
  569 + onFilter: true,
  570 + hideInTable: true,
  571 + valueEnum: enumToProTableEnumValue(invoiceTypeValueEnum),
  572 + },
  573 + {
  574 + title: '是否加急',
  575 + valueType: 'select',
  576 + dataIndex: 'isUrgent',
  577 + filters: true,
  578 + onFilter: true,
  579 + hideInTable: true,
  580 + valueEnum: {
  581 + true: {
  582 + text: '是',
  583 + status: 'true',
  584 + },
  585 + false: {
  586 + text: '否',
  587 + status: 'false',
  588 + },
  589 + },
  590 + },
  591 + {
  592 + title: '申请时间',
  593 + dataIndex: 'createTime',
  594 + valueType: 'dateTimeRange',
  595 + width: 200,
  596 + hideInTable: true,
  597 + search: {
  598 + transform: (value) => {
  599 + if (value) {
  600 + return {
  601 + createTimeGe: value[0],
  602 + createTimeLe: value[1],
  603 + };
  604 + }
  605 + },
  606 + },
  607 + },
  608 + ];
  609 +
  610 + const renderProTable = (isManual) => {
  611 + const tableProps = {
  612 + columns: isManual ? manualInvoiceColumns : waitDealRecordColumns,
  613 + actionRef: isManual ? manualInvoiceActionRef : waitDealrecordActionRef,
  614 + cardBordered: true,
  615 + pagination: {
  616 + showSizeChanger: true, // 显示可以选择每页显示条数的下拉菜单
  617 + pageSizeOptions: ['10', '20', '50', '100'], // 设置可以选择的每页显示条数选项
  618 + },
  619 + request: async (params) => {
  620 + let res = await postServiceInvoiceQueryInvoiceRecordList({
  621 + data: {
  622 + ...params,
  623 + isManual: isManual,
  624 + statusIn: [
  625 + 'WAITING_FOR_INVOICING',
  626 + 'AUDITING',
  627 + 'AUDITING_NOT_PASSED',
  628 + ],
  629 + needBuildDetails: true,
  630 + needBuildSubOrders: true,
436 631 },
437   - onChange(value) {
438   - console.log('value: ', value);
  632 + });
  633 + return {
  634 + data: res?.data?.data,
  635 + total: res?.data?.total || 0,
  636 + };
  637 + },
  638 + columnsState: {
  639 + persistenceKey: isManual
  640 + ? 'manual-invoice-table'
  641 + : 'auto-invoice-table',
  642 + persistenceType: 'localStorage',
  643 + defaultValue: {
  644 + option: { fixed: 'right', disable: true },
  645 + },
  646 + onChange(value) {
  647 + console.log('value: ', value);
  648 + },
  649 + },
  650 + rowKey: 'id',
  651 + search: {
  652 + labelWidth: 'auto',
  653 + },
  654 + options: {
  655 + setting: {
  656 + listsHeight: 400,
  657 + },
  658 + },
  659 + form: {},
  660 + dateFormatter: 'string',
  661 + headerTitle: isManual ? '手动开票列表' : '自动开票列表',
  662 + scroll: { x: 2000, y: 500 },
  663 + };
  664 +
  665 + // 如果不是手动开票,添加多选功能
  666 + if (!isManual) {
  667 + tableProps.rowSelection = {
  668 + selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
  669 + alwaysShowAlert: true,
  670 + };
  671 +
  672 + tableProps.tableAlertOptionRender = ({
  673 + selectedRowKeys,
  674 + selectedRows,
  675 + }) => {
  676 + console.log(selectedRows);
  677 + console.log(selectedRowKeys);
  678 + return (
  679 + <Space size={16}>
  680 + <InvoicingModal
  681 + reloadRecordTable={reloadRecordTable}
  682 + key="button"
  683 + selectedRowKeys={selectedRowKeys}
  684 + />
  685 + </Space>
  686 + );
  687 + };
  688 + }
  689 +
  690 + return <ProTable {...tableProps} />;
  691 + };
  692 +
  693 + return (
  694 + <div className="invoice-index">
  695 + <Tabs
  696 + activeKey={activeTab}
  697 + onChange={(key) => setActiveTab(key)}
  698 + items={[
  699 + {
  700 + key: 'auto',
  701 + label: '自动开票',
  702 + children: renderProTable(false),
439 703 },
440   - }}
441   - rowKey="id"
442   - search={{
443   - labelWidth: 'auto',
444   - }}
445   - options={{
446   - setting: {
447   - listsHeight: 400,
  704 + {
  705 + key: 'manual',
  706 + label: '手动开票',
  707 + children: renderProTable(true),
448 708 },
449   - }}
450   - form={{}}
451   - dateFormatter="string"
452   - headerTitle="待开票列表"
453   - scroll={{ x: 2000, y: 500 }}
  709 + ]}
454 710 />
455 711 {invoiceRecordDetailVisible ? (
456 712 <InvoiceRecordDetailModal
... ... @@ -458,7 +714,7 @@ const InvoiceRecord = () =&gt; {
458 714 id={invoiceRecord.id}
459 715 setVisible={setInvoiceRecordDetailVisible}
460 716 reloadTable={() => {
461   - waitDealrecordActionRef?.current?.reload();
  717 + reloadRecordTable();
462 718 }}
463 719 />
464 720 ) : (
... ...
src/pages/Order/OrderList/ApplyForInvoicingModal.tsx
... ... @@ -107,6 +107,7 @@ export default ({ setCheckVisible, isEdit, subOrders, onClose }) =&gt; {
107 107 receivingCompany: string;
108 108 isUrgent: boolean;
109 109 deadline: string;
  110 + invoiceType: string; // 新增发票类型字段
110 111 }>();
111 112  
112 113 useEffect(() => {
... ... @@ -317,6 +318,21 @@ export default ({ setCheckVisible, isEdit, subOrders, onClose }) =&gt; {
317 318 options={receivingCompanyOptions}
318 319 rules={[{ required: true, message: '开票收款单位必填' }]}
319 320 />
  321 +
  322 + {/* 新增发票类型选择 */}
  323 + <ProFormSelect
  324 + placeholder="选择发票类型"
  325 + name="invoiceType"
  326 + width="lg"
  327 + key="invoiceType"
  328 + label="发票类型"
  329 + options={[
  330 + { label: '普票', value: 'ORDINARY_TICKET' },
  331 + { label: '专票', value: 'SPECIAL_TICKET' },
  332 + ]}
  333 + rules={[{ required: true, message: '发票类型必填' }]}
  334 + />
  335 +
320 336 <ProFormSelect
321 337 placeholder="选择是否加急"
322 338 name="isUrgent"
... ...