JhHardwareWRS/pages/reportForm/reportForm.js
2026-06-24 15:19:14 +08:00

519 lines
17 KiB
JavaScript

const api = require('../../utils/api')
const pad = value => {
const text = String(value)
return text.length > 1 ? text : `0${text}`
}
const parseDateValue = value => {
const text = String(value || '')
if (text.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(text)) {
return new Date(text)
}
return new Date(text.replace(/-/g, '/').replace('T', ' '))
}
const formatDate = value => {
const date = parseDateValue(value)
if (Number.isNaN(date.getTime())) {
return ''
}
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
}
const formatTimeValue = value => {
const date = parseDateValue(value)
if (Number.isNaN(date.getTime())) {
return [0, 0, 0]
}
return [date.getHours(), date.getMinutes(), date.getSeconds()]
}
const formatTimeText = value => {
const parts = formatTimeValue(value)
return `${pad(parts[0])}:${pad(parts[1])}:${pad(parts[2])}`
}
const formatTimePartsText = parts => `${pad(parts[0])}:${pad(parts[1])}:${pad(parts[2])}`
const buildDateTimeValue = (date, timeValue) => (
date ? `${date}T${pad(timeValue[0])}:${pad(timeValue[1])}:${pad(timeValue[2])}` : ''
)
const timeOptions = [
Array.from({ length: 24 }, (_, index) => pad(index)),
Array.from({ length: 60 }, (_, index) => pad(index)),
Array.from({ length: 60 }, (_, index) => pad(index)),
]
const productKey = product => `${String(product.projectNo || '').trim()}||${String(product.productName || '').trim()}||${String(product.process || '').trim()}`
const blockDisplay = block => block.moldDisplayName || block.moldName || block.deviceNo
const productToItem = (product, startAt = '', availableOptions = null, equipmentOptions = [], deviceNo = '') => {
const selectedDeviceNo = deviceNo || ''
const deviceIndex = selectedDeviceNo
? Math.max(0, equipmentOptions.findIndex(item => item.deviceNo === selectedDeviceNo))
: -1
return {
productIndex: product ? 0 : -1,
attendancePointName: product ? product.attendancePointName : '',
deviceIndex,
deviceNo: selectedDeviceNo,
availableOptions: availableOptions || null,
startAt,
startAtText: startAt ? `${formatDate(startAt)} ${formatTimeText(startAt)}` : '',
projectNo: product ? product.projectNo : '',
productName: product ? product.productName : '',
materialCode: product ? product.materialCode : '',
materialName: product ? product.materialName : '',
rawMaterialBatchNo: '',
rawMaterialBatchOptions: [],
rawMaterialBatchIndex: -1,
rawMaterialBatchError: false,
process: product ? product.process : '',
stampingMethod: product ? product.stampingMethod : '',
operatorCount: product ? product.operatorCount : 1,
isMultiPerson: product ? !!product.isMultiPerson : false,
isMisc: product ? !!product.isMisc : false,
standardBeat: product ? product.standardBeat : 0,
standardWorkload: product ? product.standardWorkload : 0,
goodQty: product && product.isMisc ? 0 : '',
defectQty: '',
remark: '',
deviceError: false,
}
}
const REPORTING_LIMIT_MINUTES = 15
Page({
data: {
sessionId: '',
draft: null,
remainingText: '',
remainingSeconds: 0,
reminderShown: false,
confirmVisible: false,
confirmSummary: null,
submitting: false,
continuing: false,
addStartVisible: false,
addStartForm: null,
timeOptions,
},
onLoad(options) {
this.setData({ sessionId: options.sessionId || '' })
this.load()
},
onUnload() {
this.clearCountdown()
},
async load() {
try {
const draft = await api.buildReportDraft(this.data.sessionId)
this.setData({ draft }, () => {
this.showReportReminder()
this.startCountdown(draft)
})
} catch (error) {
if (!this.handleExpiredReport(error)) {
wx.showToast({ title: error.message, icon: 'none' })
}
}
},
handleExpiredReport(error) {
const message = error && error.message ? error.message : ''
if (message.indexOf('超过15分钟') === -1) {
return false
}
wx.showModal({
title: '报工已超时',
content: '请重新扫码点击重新报工,下班时间会更新为重新报工时间。',
showCancel: false,
success: () => {
wx.navigateBack({ delta: 1 })
},
})
return true
},
showReportReminder() {
if (this.data.reminderShown) {
return
}
this.setData({ reminderShown: true })
wx.showModal({
title: '填报提醒',
content: `报工填报时限为${REPORTING_LIMIT_MINUTES}分钟,请尽快完成填报`,
showCancel: false,
})
},
parseTime(value) {
if (!value) {
return 0
}
const text = String(value)
if (text.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(text)) {
return new Date(text).getTime()
}
return new Date(text.replace(/-/g, '/').replace('T', ' ')).getTime()
},
formatRemaining(seconds) {
const safeSeconds = Math.max(0, Number(seconds || 0))
const minutes = Math.floor(safeSeconds / 60)
const second = safeSeconds % 60
const minuteText = minutes < 10 ? `0${minutes}` : `${minutes}`
const secondText = second < 10 ? `0${second}` : `${second}`
return `${minuteText}:${secondText}`
},
clearCountdown() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer)
this.countdownTimer = null
}
},
startCountdown(draft) {
this.clearCountdown()
const endTime = this.parseTime(draft && draft.endAt)
if (!endTime) {
this.setData({ remainingText: '', remainingSeconds: 0 })
return
}
const deadline = endTime + REPORTING_LIMIT_MINUTES * 60 * 1000
const tick = () => {
const remainingSeconds = Math.max(0, Math.ceil((deadline - Date.now()) / 1000))
this.setData({
remainingSeconds,
remainingText: this.formatRemaining(remainingSeconds),
})
if (remainingSeconds <= 0) {
this.clearCountdown()
}
}
tick()
this.countdownTimer = setInterval(tick, 1000)
},
itemOptions(block, item) {
return item.availableOptions || block.options || []
},
onProductChange(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const itemIndex = e.currentTarget.dataset.itemIndex
const productIndex = Number(e.detail.value)
const block = this.data.draft.deviceBlocks[blockIndex]
const currentItem = block.items[itemIndex] || {}
const options = this.itemOptions(block, currentItem)
const product = options[productIndex]
if (!product) {
return
}
const item = productToItem(
product,
currentItem.startAt || block.startAt || this.data.draft.startAt,
currentItem.availableOptions || null,
block.equipmentOptions || [],
currentItem.deviceNo,
)
item.productIndex = productIndex
item.deviceError = !!currentItem.deviceError && !item.deviceNo
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}]`]: item,
})
},
onDeviceChange(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const itemIndex = e.currentTarget.dataset.itemIndex
const deviceIndex = Number(e.detail.value)
const equipment = this.data.draft.deviceBlocks[blockIndex].equipmentOptions[deviceIndex]
if (!equipment) {
return
}
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].deviceIndex`]: deviceIndex,
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].deviceNo`]: equipment.deviceNo,
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].deviceError`]: false,
})
},
onRawMaterialBatchChange(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const itemIndex = e.currentTarget.dataset.itemIndex
const batchIndex = Number(e.detail.value)
const item = this.data.draft.deviceBlocks[blockIndex].items[itemIndex]
const selected = (item.rawMaterialBatchOptions || [])[batchIndex]
if (!selected) {
return
}
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].rawMaterialBatchIndex`]: batchIndex,
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].rawMaterialBatchNo`]: selected,
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].rawMaterialBatchError`]: false,
})
},
onQtyInput(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const itemIndex = e.currentTarget.dataset.itemIndex
const field = e.currentTarget.dataset.field
const updates = {
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].${field}`]: e.detail.value,
}
if (field === 'changeoverCount') {
updates[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].changeoverCountError`] = false
}
this.setData(updates)
},
addItem(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const block = this.data.draft.deviceBlocks[blockIndex]
if (block.options.length < 2) {
return
}
const usedKeys = new Set(
(block.items || [])
.filter(item => item.productName)
.map(item => productKey(item)),
)
const availableOptions = (block.options || []).filter(option => !usedKeys.has(productKey(option)))
if (!availableOptions.length) {
wx.showToast({ title: '该模具没有可新增的产品', icon: 'none' })
return
}
const previousItem = block.items[block.items.length - 1] || {}
const defaultStartAt = previousItem.startAt || block.startAt || this.data.draft.startAt
this.setData({
addStartVisible: true,
addStartForm: {
blockIndex,
availableOptions,
previousStartAt: defaultStartAt,
date: formatDate(defaultStartAt),
timeValue: formatTimeValue(defaultStartAt),
timeText: formatTimeText(defaultStartAt),
},
})
},
onAddStartDateChange(e) {
this.setData({
'addStartForm.date': e.detail.value,
})
},
onAddStartTimeChange(e) {
const timeValue = e.detail.value.map(value => Number(value))
this.setData({
'addStartForm.timeValue': timeValue,
'addStartForm.timeText': formatTimePartsText(timeValue),
})
},
cancelAddStart() {
this.setData({
addStartVisible: false,
addStartForm: null,
})
},
confirmAddStart() {
const form = this.data.addStartForm
if (!form) {
return
}
const block = this.data.draft.deviceBlocks[form.blockIndex]
const startAt = buildDateTimeValue(form.date, form.timeValue)
const startTime = this.parseTime(startAt)
const blockStartTime = this.parseTime(block.startAt || this.data.draft.startAt)
const blockEndTime = this.parseTime(block.endAt || this.data.draft.endAt)
const previousStartTime = this.parseTime(form.previousStartAt)
if (!startTime || startTime < blockStartTime || startTime > blockEndTime) {
wx.showToast({ title: '开始时间需在本设备工作时间内', icon: 'none' })
return
}
if (previousStartTime && startTime < previousStartTime) {
wx.showToast({ title: '开始时间不能早于上一产品', icon: 'none' })
return
}
const item = productToItem(
form.availableOptions && form.availableOptions[0],
startAt,
form.availableOptions || [],
block.equipmentOptions || [],
)
const items = block.items.concat(item)
this.setData({
[`draft.deviceBlocks[${form.blockIndex}].items`]: items,
addStartVisible: false,
addStartForm: null,
})
},
removeItem(e) {
const blockIndex = e.currentTarget.dataset.blockIndex
const itemIndex = e.currentTarget.dataset.itemIndex
const block = this.data.draft.deviceBlocks[blockIndex]
if (block.items.length <= 1) {
return
}
const items = block.items.filter((_, index) => index !== itemIndex)
this.setData({
[`draft.deviceBlocks[${blockIndex}].items`]: items,
})
},
noop() {},
validateDraft(draft) {
const blocks = draft.deviceBlocks || []
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
const block = blocks[blockIndex]
const items = block.items || []
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
const item = items[itemIndex]
if (item.isMisc) {
continue
}
if ((block.options || []).length > 1 && (!item.productName || item.productIndex < 0)) {
wx.showToast({
title: `${blockDisplay(block)}缺少产品信息`,
icon: 'none',
})
return false
}
if (!item.deviceNo) {
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].deviceError`]: true,
})
wx.showToast({
title: `${blockDisplay(block)}请选择设备号`,
icon: 'none',
})
return false
}
if (!item.startAt) {
wx.showToast({
title: `${blockDisplay(block)}请选择开始工作时间`,
icon: 'none',
})
return false
}
if ((item.rawMaterialBatchOptions || []).length && !item.rawMaterialBatchNo) {
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].rawMaterialBatchError`]: true,
})
wx.showToast({
title: `${blockDisplay(block)}请选择材料库存批次号`,
icon: 'none',
})
return false
}
if (item.isContinuousDie) {
const text = String(item.changeoverCount || '').trim()
const value = Number(text)
if (!text || !Number.isInteger(value) || value < 0) {
this.setData({
[`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].changeoverCountError`]: true,
})
wx.showToast({
title: `${blockDisplay(block)}请填写非负整数换料次数`,
icon: 'none',
})
return false
}
}
const goodQty = Number(item.goodQty || 0)
const defectQty = Number(item.defectQty || 0)
if (!Number.isFinite(goodQty) || goodQty < 0 || !Number.isFinite(defectQty) || defectQty < 0) {
wx.showToast({
title: `${blockDisplay(block)}产出数量不能为负数`,
icon: 'none',
})
return false
}
}
}
return true
},
buildConfirmSummary(draft) {
const totalGood = draft.deviceBlocks.reduce((sum, block) => (
sum + block.items.reduce((inner, item) => inner + (item.isMisc ? 0 : Number(item.goodQty || 0)), 0)
), 0)
const totalDefect = draft.deviceBlocks.reduce((sum, block) => (
sum + block.items.reduce((inner, item) => inner + (item.isMisc ? 0 : Number(item.defectQty || 0)), 0)
), 0)
const hasMisc = draft.deviceBlocks.some(block => (block.items || []).some(item => item.isMisc))
return {
deviceText: draft.deviceBlocks.map(block => blockDisplay(block)).join('、'),
totalGood,
totalDefect,
note: hasMisc ? '包含处理杂活,提交后由管理员补充杂活事项' : '提交后进入管理员审核',
}
},
submit() {
if (this.data.continuing) {
return
}
const draft = this.data.draft
if (!this.validateDraft(draft)) {
return
}
this.setData({
confirmVisible: true,
confirmSummary: this.buildConfirmSummary(draft),
})
},
cancelConfirm() {
if (this.data.submitting) {
return
}
this.setData({ confirmVisible: false })
},
async confirmSubmit() {
if (this.data.submitting || this.data.continuing) {
return
}
this.setData({ submitting: true })
wx.showLoading({ title: '提交中' })
try {
const report = await api.submitReport(this.data.draft)
this.setData({ confirmVisible: false })
wx.reLaunch({
url: `/pages/reportResult/reportResult?reportId=${report.id}`,
})
} catch (error) {
if (!this.handleExpiredReport(error)) {
wx.showToast({ title: error.message, icon: 'none' })
}
} finally {
wx.hideLoading()
this.setData({ submitting: false })
}
},
continueWork() {
if (this.data.submitting || this.data.continuing) {
return
}
wx.showModal({
title: '继续上班',
content: '确认取消本次下班报工填报,继续保持上班状态?当前填写内容不会保存。',
confirmText: '继续上班',
cancelText: '返回填写',
success: async res => {
if (!res.confirm) {
return
}
this.setData({ continuing: true })
wx.showLoading({ title: '处理中' })
try {
await api.continueWork(this.data.sessionId)
this.clearCountdown()
wx.showToast({ title: '已继续上班', icon: 'success' })
setTimeout(() => {
wx.reLaunch({
url: '/pages/index/index',
})
}, 500)
} catch (error) {
wx.showToast({ title: error.message, icon: 'none' })
} finally {
wx.hideLoading()
this.setData({ continuing: false })
}
},
})
},
})