billingHelper.js
9.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import { formatDateYMD } from '@/utils/appointmentHelper'
export function formatKdrq(date) {
const d = date instanceof Date ? date : new Date(date)
if (Number.isNaN(d.getTime())) return formatKdrq(new Date())
const pad = n => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
export function buildBillingKdrqRange(dateRange) {
if (!dateRange || dateRange.length !== 2) return ''
return `${dateRange[0]},${dateRange[1]}`
}
/** 品项总价合计 */
export function calcItemsTotal(form) {
return (form.items || []).reduce((s, it) => s + (parseFloat(it.totalPrice) || 0), 0)
}
/** 储扣明细合计 */
export function calcDeductTotal(form) {
if (form.isDeduct !== '是') return 0
return (form.deductItems || []).reduce((s, d) => {
if (!d || !d.deductId) return s
return s + (parseFloat(d.price) || 0) * (Number(d.quantity) || 0)
}, 0)
}
/** 实付业绩 = 品项总价 - 储扣(与小程序 lx.vue 一致) */
export function calcSfyj(form) {
const val = calcItemsTotal(form) - calcDeductTotal(form)
return Math.max(0, val)
}
/** 欠款 = 整单业绩 - 实付 - 储扣合计(与小程序一致) */
export function calcQk(form) {
const zdyj = Number(form.totalPerformance) || 0
const sfyj = calcSfyj(form)
const deduct = calcDeductTotal(form)
const val = zdyj - sfyj - deduct
return Math.max(0, val)
}
function extractJsjNames(form) {
const names = []
;(form.items || []).forEach(it => {
;(it.workers || []).forEach(w => {
const n = (w.jsjName || '').trim()
if (n && !names.includes(n)) names.push(n)
})
})
return names.length ? names.join(',') : '无'
}
function mapPxmxItem(it, form) {
const qty = Number(it.quantity) || 1
const totalPrice = parseFloat(it.totalPrice) || 0
const pxjg = qty > 0 ? totalPrice / qty : totalPrice
const lqKdJksyjList = (it.workers || [])
.filter(w => w && w.workerId)
.map(w => {
const name = w.workerName || ''
return {
jks: name,
jksxm: name,
jkszh: w.workerId,
jksyj: String(w.amount != null && w.amount !== '' ? w.amount : 0),
jsj_id: w.jsjId || ''
}
})
const lqKdKjbsyjList = (it.kjbWorkers || [])
.filter(k => k && k.workerId)
.map(k => ({
kjbls: k.workerId,
kjblsxm: k.workerName || '',
kjblszh: k.workerId,
kjblsyj: String(k.amount != null && k.amount !== '' ? k.amount : 0)
}))
return {
px: String(it.projectId),
pxmc: it.label || '',
pxjg,
actualPrice: totalPrice,
projectNumber: qty,
sourceType: it.type || '购买',
remark: it.remark || '',
memberId: form.memberId,
qt2: it.qt2 || '',
lqKdJksyjList,
lqKdKjbsyjList
}
}
/**
* 门店 PC 开单表单 → LqKdKdjlbCrInput(对齐小程序 pagesA/lx/lx.vue)
*/
export function buildBillingSubmitBody(form, { storeId, member }) {
const zdyj = Number(form.totalPerformance) || 0
const sfyj = calcSfyj(form)
const qk = calcQk(form)
const deductTotal = calcDeductTotal(form)
const lqKdPxmxList = (form.items || [])
.filter(it => it && it.projectId)
.map(it => mapPxmxItem(it, form))
const lqKdKdjlbDeductList = form.isDeduct === '是'
? (form.deductItems || [])
.filter(d => d && d.deductId)
.map(d => {
const qty = Number(d.quantity) || 1
const unit = parseFloat(d.price) || 0
return {
DeductType: d.deductType || '品项',
DeductId: d.deductId,
Amount: unit * qty,
UnitPrice: unit,
ProjectNumber: qty,
ItemName: d.label || '',
ItemId: d.itemId || ''
}
})
: []
const isCoop = form.paymentMethod === '合作'
const paidSfyj = sfyj
return {
activityId: form.activityId || '',
djmd: storeId,
jsj: extractJsjNames(form),
kdrq: formatKdrq(form.billingDate),
gjlx: (member && member.type) || form.memberType || '',
kdhy: form.memberId,
kdhyc: (member && member.label) || form.memberName || '',
kdhysjh: (member && member.phone) || form.memberPhone || '',
zdyj,
sfyj,
qk,
ckfs: form.isDeduct === '是' ? (form.deductMode || '储扣') : '否',
ckmx: form.isDeduct === '是' ? String(deductTotal) : '',
fkfs: form.paymentMethod || '',
hgjg: isCoop ? (form.cooperateOrg || '') : '',
fkyy: !isCoop ? (form.settleOrg || '') : '',
sfskdd: paidSfyj > 0 ? (form.isFirstOrder || '否') : '否',
jj: form.intro || '',
bz: form.remark || '',
scwj: form.scwj || [],
hyqz: form.hyqz || [],
f_FileUrl: form.f_FileUrl ? JSON.stringify(form.f_FileUrl) : '',
appointmentId: form.appointmentId || '',
lqKdPxmxList,
lqKdKdjlbDeductList
}
}
/**
* 开单校验(对齐小程序 validatePxList + 提交前校验)
*/
export function validateBillingForm(form) {
if (!form.memberId) return '请选择开单会员'
if (!form.billingDate) return '请选择开单日期'
if (form.totalPerformance === '' || form.totalPerformance == null) return '请输入整单业绩'
const items = (form.items || []).filter(x => x && x.projectId)
if (!items.length) return '请至少添加 1 个品项'
let pxSum = 0
for (let i = 0; i < items.length; i++) {
const px = items[i]
const idx = i + 1
if (!px.projectId || !px.label) return `第 ${idx} 个品项请选择品项`
const pxAmount = parseFloat(px.totalPrice) || 0
pxSum += pxAmount
const workers = (px.workers || []).filter(w => w && w.workerId)
if (!workers.length) return `第 ${idx} 个品项请至少添加一个健康师业绩`
for (let j = 0; j < workers.length; j++) {
const w = workers[j]
if (w.amount === '' || w.amount == null) return `第 ${idx} 个品项的第 ${j + 1} 个健康师业绩必须填写`
const yj = parseFloat(w.amount)
if (Number.isNaN(yj) || yj < 0) return `第 ${idx} 个品项的第 ${j + 1} 个健康师业绩必须为有效数字`
}
if (px.qt2 === '科美') {
const kjbs = (px.kjbWorkers || []).filter(k => k && k.workerId)
if (!kjbs.length) return `第 ${idx} 个品项是科美品项,必须至少选择一个科技部老师`
for (let k = 0; k < kjbs.length; k++) {
const kb = kjbs[k]
if (kb.amount === '' || kb.amount == null) return `第 ${idx} 个品项的第 ${k + 1} 个科技部老师业绩必须填写`
const yj = parseFloat(kb.amount)
if (Number.isNaN(yj) || yj < 0) return `第 ${idx} 个品项的第 ${k + 1} 个科技部老师业绩必须为有效数字`
}
}
}
const zdyj = Number(form.totalPerformance) || 0
const pxSumRounded = Math.round(pxSum * 100) / 100
const zdyjRounded = Math.round(zdyj * 100) / 100
if (zdyjRounded < pxSumRounded) {
return `整单业绩(${zdyj})要大于等于品项之和的总价(${pxSumRounded.toFixed(2)})`
}
const ds = form.isDeduct === '是'
? (form.deductItems || []).filter(d => d && d.deductId)
: []
if (form.isDeduct === '是') {
if (!ds.length) return '请添加储扣明细'
for (let i = 0; i < ds.length; i++) {
const d = ds[i]
if (!d.deductId) return `第 ${i + 1} 个储扣明细请选择品项`
const qty = parseInt(d.quantity, 10) || 0
if (qty <= 0) return `第 ${i + 1} 个储扣明细数量必须大于 0`
if (d.maxQty != null && qty > d.maxQty) {
return `第 ${i + 1} 个储扣明细数量不能超过剩余次数(${d.maxQty})`
}
}
const ids = ds.map(d => d.deductId)
if (new Set(ids).size !== ids.length) return '储扣品项不能重复添加'
}
const sfyj = calcSfyj(form)
let jksAmount = 0
let kjbAmount = 0
let hasKjbKeMei = false
items.forEach(px => {
;(px.workers || []).forEach(w => {
jksAmount += parseFloat(w.amount) || 0
})
if (px.qt2 === '科美' && px.type === '购买') {
hasKjbKeMei = true
;(px.kjbWorkers || []).forEach(k => {
kjbAmount += parseFloat(k.amount) || 0
})
}
})
if (form.isDeduct === '是' && ds.length) {
if (Math.abs(jksAmount - sfyj) > 0.01) {
return `总健康师业绩(${jksAmount.toFixed(2)})必须等于实付业绩(${sfyj.toFixed(2)})`
}
if (hasKjbKeMei && Math.abs(kjbAmount - sfyj) > 0.01) {
return `总科技部业绩(${kjbAmount.toFixed(2)})必须等于实付业绩(${sfyj.toFixed(2)})`
}
} else {
for (let i = 0; i < items.length; i++) {
const px = items[i]
const pxAmount = parseFloat(px.totalPrice) || 0
let itemJks = 0
;(px.workers || []).forEach(w => {
itemJks += parseFloat(w.amount) || 0
})
if (Math.abs(pxAmount - itemJks) > 0.01) {
return `第 ${i + 1} 个品项总价(${pxAmount.toFixed(2)})必须等于该品项健康师业绩之和(${itemJks.toFixed(2)})`
}
if (px.qt2 === '科美') {
let itemKjb = 0
;(px.kjbWorkers || []).forEach(k => {
itemKjb += parseFloat(k.amount) || 0
})
if (Math.abs(pxAmount - itemKjb) > 0.01) {
return `第 ${i + 1} 个科美品项总价(${pxAmount.toFixed(2)})必须等于该品项科技部业绩之和(${itemKjb.toFixed(2)})`
}
}
}
}
if (!form.paymentMethod) return '请选择付款方式'
return ''
}
/** 开单日期字符串(yyyy-MM-dd)供金三角接口 */
export function billingDateYmd(form) {
if (!form.billingDate) return formatDateYMD(new Date())
const d = form.billingDate instanceof Date ? form.billingDate : new Date(form.billingDate)
if (Number.isNaN(d.getTime())) return formatDateYMD(new Date())
return formatDateYMD(d)
}