billingHelper.js 9.5 KB
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)
}