Commit 40715ed176fbca01db12421ff0031ed915d7c991
1 parent
425c1722
feat: 添加开票字段
Showing
4 changed files
with
326 additions
and
37 deletions
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 }) => { |
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 }) => { |
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; | ... | ... |