Commit 40715ed176fbca01db12421ff0031ed915d7c991

Authored by 柏杨
1 parent 425c1722

feat: 添加开票字段

src/pages/Order/OrderList/ApplyForInvoicingModal.tsx
1 1 import { RESPONSE_CODE } from '@/constants/enum';
2 2 import { postServiceOrderApplyInvoicing } from '@/services';
3   -import { enumToSelect, getAliYunOSSFileNameFromUrl } from '@/utils';
  3 +import { FloatAdd, enumToSelect, getAliYunOSSFileNameFromUrl } from '@/utils';
4 4 import {
5 5 ModalForm,
  6 + ProFormMoney,
6 7 ProFormSelect,
7 8 ProFormText,
8 9 ProFormTextArea,
... ... @@ -11,21 +12,69 @@ import {
11 12 import { Form, message } from 'antd';
12 13 import { useEffect, useState } from 'react';
13 14 import { PAYEE_OPTIONS } from '../constant';
14   -export default ({
15   - setCheckVisible,
16   - isEdit,
17   - subOrders,
18   - isMainOrder,
19   - totalPayment,
20   - onClose,
21   -}) => {
  15 +
  16 +// 定义选项类型
  17 +interface SelectOption {
  18 + label: string;
  19 + value: string;
  20 +}
  21 +
  22 +export default ({ setCheckVisible, isEdit, subOrders, onClose }) => {
22 23 const [isUrgent, setIsUrgent] = useState('');
23   - let sumPrice = totalPayment;
  24 + const [receivingCompanyOptions, setReceivingCompanyOptions] = useState<
  25 + SelectOption[]
  26 + >([]);
24 27  
25 28 let ids = subOrders?.map((item) => {
26 29 return item.id;
27 30 });
28 31  
  32 + // 定义返回类型接口
  33 + interface MainOrderData {
  34 + value: string | number;
  35 + totalPayment: number;
  36 + invoiceIssuedAmount: number;
  37 + availableAmount: number;
  38 + }
  39 +
  40 + // 获取唯一的主订单ID及其相关金额信息
  41 + const getUniqueMainOrderIds = (): MainOrderData[] => {
  42 + const mainOrderIds = subOrders?.map((item: any) => item.mainOrderId);
  43 + const uniqueIds = [...new Set(mainOrderIds)].filter(Boolean);
  44 +
  45 + return uniqueIds.map((id) => {
  46 + // 获取该主订单下所有子订单
  47 + const orderSubOrders = subOrders.filter(
  48 + (item: any) => item.mainOrderId === id,
  49 + );
  50 +
  51 + // 计算该主订单的总金额
  52 + let totalPayment = 0;
  53 + orderSubOrders.forEach((item: any) => {
  54 + totalPayment = FloatAdd(totalPayment, item.totalPayment || 0);
  55 + });
  56 +
  57 + // 计算已开票金额(如果有的话)
  58 + let invoiceIssuedAmount = 0;
  59 + orderSubOrders.forEach((item: any) => {
  60 + invoiceIssuedAmount = FloatAdd(
  61 + invoiceIssuedAmount,
  62 + item.invoiceIssuedAmount || 0,
  63 + );
  64 + });
  65 +
  66 + // 计算可开票金额
  67 + const availableAmount = Math.max(0, totalPayment - invoiceIssuedAmount);
  68 +
  69 + return {
  70 + value: String(id),
  71 + totalPayment,
  72 + invoiceIssuedAmount,
  73 + availableAmount,
  74 + };
  75 + });
  76 + };
  77 +
29 78 let mainIdSet = new Set();
30 79 subOrders?.forEach((item: { mainOrderId: unknown }) => {
31 80 mainIdSet.add(item.mainOrderId);
... ... @@ -63,6 +112,19 @@ export default ({
63 112 useEffect(() => {
64 113 //显示拼接的主订单id
65 114 form.setFieldValue('applyInvoicingNotes', mainIds);
  115 +
  116 + // 设置每个主订单的可开票金额初始值
  117 + const mainOrders = getUniqueMainOrderIds();
  118 + mainOrders.forEach((order) => {
  119 + form.setFieldValue(
  120 + `invoiceAvailableAmount_${order.value}`,
  121 + order.availableAmount,
  122 + );
  123 + });
  124 +
  125 + // 转换收款单位选项并保存
  126 + const options = enumToSelect(PAYEE_OPTIONS);
  127 + setReceivingCompanyOptions(options);
66 128 }, []);
67 129  
68 130 return (
... ... @@ -105,7 +167,38 @@ export default ({
105 167 values.afterInvoicingUpdate = false;
106 168 }
107 169  
108   - const res = await postServiceOrderApplyInvoicing({ data: values });
  170 + // 收集主订单可开票金额
  171 + const invoiceOrderAmounts = getUniqueMainOrderIds().map((item) => ({
  172 + orderId: Number(item.value), // 确保为 number 类型
  173 + availableAmount: values[`invoiceAvailableAmount_${item.value}`],
  174 + }));
  175 + // 校验所有主订单可开票金额之和等于price字段
  176 + const price = values.price;
  177 + let sumAvailable = 0;
  178 + invoiceOrderAmounts.forEach((item) => {
  179 + sumAvailable = FloatAdd(sumAvailable, item.availableAmount || 0);
  180 + });
  181 + if (Math.abs(sumAvailable - price) > 0.01) {
  182 + message.error(
  183 + `所有主订单可开票金额之和(${sumAvailable})必须等于开票金额(${price})`,
  184 + );
  185 + return;
  186 + }
  187 +
  188 + // 获取receivingCompany对应的名称
  189 + const selectedOption = receivingCompanyOptions.find(
  190 + (option) => option.value === values.receivingCompany,
  191 + );
  192 + const receivingCompanyName = selectedOption ? selectedOption.label : '';
  193 +
  194 + // 添加到请求数据中
  195 + const reqData = {
  196 + ...values,
  197 + receivingCompanyName, // 添加收款单位名称
  198 + mainOrderIds: mainIds, // 使用已有的主订单ID字符串
  199 + invoiceOrderAmounts,
  200 + };
  201 + const res = await postServiceOrderApplyInvoicing({ data: reqData });
109 202  
110 203 if (res.result === RESPONSE_CODE.SUCCESS) {
111 204 message.success(res.message);
... ... @@ -114,14 +207,74 @@ export default ({
114 207 }}
115 208 onOpenChange={setCheckVisible}
116 209 >
117   - {isMainOrder ? (
118   - <div className="mb-[24px]">
119   - <span>选中子订单金额之和:</span>
120   - <span className="text-red-500">{sumPrice}¥</span>
  210 + {/* 主订单金额表格,任何情况都显示 */}
  211 + <div style={{ marginBottom: 24 }}>
  212 + <div
  213 + style={{
  214 + display: 'flex',
  215 + fontWeight: 'bold',
  216 + marginBottom: 8,
  217 + padding: '8px 0',
  218 + borderBottom: '1px solid #f0f0f0',
  219 + }}
  220 + >
  221 + <div style={{ flex: 25 }}>订单号</div>
  222 + <div style={{ flex: 18, textAlign: 'right' }}>订单金额</div>
  223 + <div style={{ flex: 18, textAlign: 'right' }}>已开票金额</div>
  224 + <div style={{ flex: 39, textAlign: 'right' }}>可开票金额</div>
121 225 </div>
122   - ) : (
123   - ''
124   - )}
  226 + {getUniqueMainOrderIds().map((item, index) => {
  227 + const maxAvailable = Math.max(
  228 + 0,
  229 + item.totalPayment - item.invoiceIssuedAmount,
  230 + );
  231 + return (
  232 + <div
  233 + key={index}
  234 + style={{
  235 + display: 'flex',
  236 + marginBottom: 8,
  237 + padding: '8px 0',
  238 + borderBottom: '1px solid #f0f0f0',
  239 + }}
  240 + >
  241 + <div style={{ flex: 25 }}>{item.value}</div>
  242 + <div style={{ flex: 18, textAlign: 'right' }}>
  243 + ¥ {item.totalPayment.toFixed(2)}
  244 + </div>
  245 + <div style={{ flex: 18, textAlign: 'right' }}>
  246 + ¥ {item.invoiceIssuedAmount.toFixed(2)}
  247 + </div>
  248 + <div style={{ flex: 39, textAlign: 'right' }}>
  249 + <ProFormMoney
  250 + name={`invoiceAvailableAmount_${item.value}`}
  251 + locale="zh-CN"
  252 + fieldProps={{
  253 + precision: 2,
  254 + style: { width: '70%' },
  255 + }}
  256 + initialValue={item.availableAmount}
  257 + rules={[
  258 + { required: true, message: '请填写可开票金额!' },
  259 + {
  260 + validator: (_, value) => {
  261 + if (value > maxAvailable) {
  262 + return Promise.reject(
  263 + `可开票金额不能超过${maxAvailable.toFixed(2)}`,
  264 + );
  265 + } else if (value === 0) {
  266 + return Promise.reject(`可开票金额不能为0`);
  267 + }
  268 + return Promise.resolve();
  269 + },
  270 + },
  271 + ]}
  272 + />
  273 + </div>
  274 + </div>
  275 + );
  276 + })}
  277 + </div>
125 278  
126 279 <div className="mb-1">
127 280 如果需要合并订单,请将需要合并的订单id写在备注中,id之间用英文逗号隔开。
... ... @@ -161,7 +314,7 @@ export default ({
161 314 </span>
162 315 </div>
163 316 }
164   - options={enumToSelect(PAYEE_OPTIONS)}
  317 + options={receivingCompanyOptions}
165 318 rules={[{ required: true, message: '开票收款单位必填' }]}
166 319 />
167 320 <ProFormSelect
... ...
src/pages/Order/OrderList/InvoicingDrawerForm.tsx
... ... @@ -16,7 +16,6 @@ import {
16 16 FormListActionType,
17 17 ProCard,
18 18 ProFormDigit,
19   - ProFormGroup,
20 19 ProFormInstance,
21 20 ProFormList,
22 21 ProFormMoney,
... ... @@ -82,6 +81,52 @@ export default ({
82 81 });
83 82 types.set('reissue', { title: '重新申请', subOrderIdsName: '重开订单' });
84 83  
  84 + // Get unique main order IDs from the dataList
  85 + // 定义返回类型接口
  86 + interface MainOrderData {
  87 + value: string | number;
  88 + totalPayment: number;
  89 + invoiceIssuedAmount: number;
  90 + availableAmount: number;
  91 + }
  92 +
  93 + const getUniqueMainOrderIds = (): MainOrderData[] => {
  94 + // Extract main order IDs from dataList
  95 + const mainOrderIds = dataListCopy.map(
  96 + (item: any) => item.mainOrderId || item.orderId,
  97 + );
  98 + // Get unique IDs
  99 + const uniqueIds = [...new Set(mainOrderIds)].filter(Boolean);
  100 + return uniqueIds.map((id) => {
  101 + // 确保id是string或number类型
  102 + const orderId = String(id);
  103 + const orderData = dataListCopy.find(
  104 + (d: any) => d.mainOrderId === id || d.orderId === id,
  105 + );
  106 + const totalPayment = orderData?.totalPayment || 0;
  107 + const invoiceIssuedAmount = orderData?.invoiceIssuedAmount || 0;
  108 + const availableAmount = Math.max(0, totalPayment - invoiceIssuedAmount);
  109 +
  110 + return {
  111 + value: orderId,
  112 + totalPayment,
  113 + invoiceIssuedAmount,
  114 + availableAmount,
  115 + };
  116 + });
  117 + };
  118 +
  119 + // Set initial values for invoice available amounts
  120 + useEffect(() => {
  121 + const mainOrders = getUniqueMainOrderIds();
  122 + mainOrders.forEach((order) => {
  123 + form.setFieldValue(
  124 + `invoiceAvailableAmount_${order.value}`,
  125 + order.availableAmount,
  126 + );
  127 + });
  128 + }, []);
  129 +
85 130 function copyToClipboard(text: string) {
86 131 // 创建一个临时的textarea元素
87 132 const textarea = document.createElement('textarea');
... ... @@ -179,9 +224,31 @@ export default ({
179 224 }}
180 225 submitTimeout={2000}
181 226 onFinish={async (values) => {
  227 + // Collect invoice available amounts in the recommended format
  228 + const invoiceOrderAmounts = getUniqueMainOrderIds().map((item) => ({
  229 + orderId: item.value,
  230 + availableAmount: values[`invoiceAvailableAmount_${item.value}`],
  231 + }));
  232 +
  233 + // 获取开票金额(price 字段)
  234 + const price = values.price;
  235 + // 计算所有主订单可开票金额之和
  236 + let sumAvailable = 0;
  237 + invoiceOrderAmounts.forEach((item) => {
  238 + sumAvailable = FloatAdd(sumAvailable, item.availableAmount || 0);
  239 + });
  240 + // 允许0.01误差
  241 + if (Math.abs(sumAvailable - price) > 0.01) {
  242 + message.error(
  243 + `所有主订单可开票金额之和(${sumAvailable})必须等于开票金额(${price})`,
  244 + );
  245 + return;
  246 + }
  247 +
182 248 postServiceInvoiceApplyInvoice({
183 249 data: {
184 250 ...values,
  251 + invoiceOrderAmounts, // 使用数组对象格式
185 252 subOrderIds: dataListCopy.map((item) => {
186 253 return item.id;
187 254 }),
... ... @@ -194,22 +261,73 @@ export default ({
194 261 return !val && setVisible();
195 262 }}
196 263 >
197   - <ProFormList
198   - name="subOrderIdObjs"
199   - readonly={true}
200   - label={types.get(type).subOrderIdsName}
201   - initialValue={dataListCopy.map((item) => {
202   - return {
203   - value: item.id,
204   - };
  264 + <div style={{ marginBottom: 24 }}>
  265 + <div
  266 + style={{
  267 + display: 'flex',
  268 + fontWeight: 'bold',
  269 + marginBottom: 8,
  270 + padding: '8px 0',
  271 + borderBottom: '1px solid #f0f0f0',
  272 + }}
  273 + >
  274 + <div style={{ flex: 25 }}>订单号</div>
  275 + <div style={{ flex: 18, textAlign: 'right' }}>订单金额</div>
  276 + <div style={{ flex: 18, textAlign: 'right' }}>已开票金额</div>
  277 + <div style={{ flex: 39, textAlign: 'right' }}>可开票金额</div>
  278 + </div>
  279 + {getUniqueMainOrderIds().map((item, index) => {
  280 + const maxAvailable = Math.max(
  281 + 0,
  282 + item.totalPayment - item.invoiceIssuedAmount,
  283 + );
  284 + return (
  285 + <div
  286 + key={index}
  287 + style={{
  288 + display: 'flex',
  289 + marginBottom: 8,
  290 + padding: '8px 0',
  291 + borderBottom: '1px solid #f0f0f0',
  292 + }}
  293 + >
  294 + <div style={{ flex: 25 }}>{item.value}</div>
  295 + <div style={{ flex: 18, textAlign: 'right' }}>
  296 + ¥ {item.totalPayment.toFixed(2)}
  297 + </div>
  298 + <div style={{ flex: 18, textAlign: 'right' }}>
  299 + ¥ {item.invoiceIssuedAmount.toFixed(2)}
  300 + </div>
  301 + <div style={{ flex: 39, textAlign: 'right' }}>
  302 + <ProFormMoney
  303 + name={`invoiceAvailableAmount_${item.value}`}
  304 + locale="zh-CN"
  305 + fieldProps={{
  306 + precision: 2,
  307 + style: { width: '70%' },
  308 + }}
  309 + initialValue={item.availableAmount}
  310 + rules={[
  311 + { required: true, message: '请填写可开票金额!' },
  312 + {
  313 + validator: (_, value) => {
  314 + if (value > maxAvailable) {
  315 + return Promise.reject(
  316 + `可开票金额不能超过${maxAvailable.toFixed(2)}`,
  317 + );
  318 + } else if (value === 0) {
  319 + return Promise.reject(`可开票金额不能为0`);
  320 + }
  321 + return Promise.resolve();
  322 + },
  323 + },
  324 + ]}
  325 + />
  326 + </div>
  327 + </div>
  328 + );
205 329 })}
206   - deleteIconProps={false}
207   - copyIconProps={false}
208   - >
209   - <ProFormGroup key="group">
210   - <ProFormText readonly={true} name="value" label="" />
211   - </ProFormGroup>
212   - </ProFormList>
  330 + </div>
213 331 {/*<ProFormSelect
214 332 name="ReissueInvoiceRecordIds"
215 333 label="重开的发票"
... ...
src/pages/Order/OrderList/OrderList.tsx
... ... @@ -2777,7 +2777,22 @@ const OrderList = ({ paramsNew, searchShow, toolbarShow }) =&gt; {
2777 2777 </span>
2778 2778 </div>
2779 2779 <Divider type="vertical" />
2780   -
  2780 + </Flex>
  2781 + <Flex className="pl-6" align="center">
  2782 + <div>
  2783 + <span className="text-[#8C8C8C]">开票中金额:¥</span>
  2784 + <span className="text-slate-700">
  2785 + {record.invoicePendingAmount || '-'}
  2786 + </span>
  2787 + </div>
  2788 + <Divider type="vertical" />
  2789 + <div>
  2790 + <span className="text-[#8C8C8C]">已开票金额:¥</span>
  2791 + <span className="text-slate-700">
  2792 + {record.invoiceIssuedAmount || '-'}
  2793 + </span>
  2794 + </div>
  2795 + <Divider type="vertical" />
2781 2796 {/* 添加发票核销金额 */}
2782 2797 <div>
2783 2798 <span className="text-[#8C8C8C]">发票核销金额:¥</span>
... ... @@ -2786,7 +2801,8 @@ const OrderList = ({ paramsNew, searchShow, toolbarShow }) =&gt; {
2786 2801 </span>
2787 2802 </div>
2788 2803 <Divider type="vertical" />
2789   -
  2804 + </Flex>
  2805 + <Flex className="pl-6" align="center">
2790 2806 <div title={record.notes}>
2791 2807 <div
2792 2808 className="max-w-[850px] whitespace-normal overflow-hidden overflow-ellipsis hover:cursor-pointer"
... ...
src/pages/Order/OrderList/type.d.ts
... ... @@ -48,6 +48,8 @@ export interface OrderListItemType {
48 48 unit: string;
49 49 parameters: any;
50 50 totalPayment: number;
  51 + invoicePendingAmount?: number | string;
  52 + invoiceIssuedAmount?: number | string;
51 53 subOrderPayment: number;
52 54 isCancel: number;
53 55 logisticsStatus: string;
... ...