872 lines
32 KiB
JavaScript
872 lines
32 KiB
JavaScript
const api = require('../../utils/api')
|
||
|
||
const pad = value => {
|
||
const text = String(value)
|
||
return text.length > 1 ? text : `0${text}`
|
||
}
|
||
|
||
const datePart = value => {
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) {
|
||
return ''
|
||
}
|
||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||
}
|
||
|
||
const timePart = value => {
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) {
|
||
return ''
|
||
}
|
||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||
}
|
||
|
||
const dateTimeValue = (date, time) => (date && time ? `${date}T${time}:00` : '')
|
||
|
||
const productChoiceKey = item => `${item.attendancePointName || ''}||${item.projectNo}||${item.productName}`
|
||
|
||
const optionKey = item => `${item.attendancePointName || ''}||${item.projectNo}||${item.productName}||${item.process || ''}||${api.normalizeDeviceNo(item.deviceNo)}`
|
||
|
||
const segmentKey = item => `${api.normalizeDeviceNo(item.moldName || item.productName || item.deviceNo)}||${String(item.process || '').trim()}`
|
||
|
||
const round2 = value => Math.round(Number(value || 0) * 100) / 100
|
||
|
||
const withCompareClasses = item => {
|
||
const standardWorkload = item.isCleaning
|
||
? 0
|
||
: (item.isMisc ? 0 : (api.calculateStandardWorkload(item.allocatedMinutes, item.standardBeat) || Number(item.standardWorkload || 0)))
|
||
const beatComparison = (item.isCleaning || item.isMisc) ? api.classifyBeat(0, 0) : api.classifyBeat(item.actualBeat, item.standardBeat)
|
||
const workloadComparison = (item.isCleaning || item.isMisc) ? api.classifyWorkload(0, 0) : api.classifyWorkload(item.goodQty, standardWorkload)
|
||
return {
|
||
...item,
|
||
standardWorkload,
|
||
beatCompareClass: beatComparison.className,
|
||
beatReason: beatComparison.reason,
|
||
workloadCompareClass: workloadComparison.className,
|
||
workloadReason: workloadComparison.reason,
|
||
}
|
||
}
|
||
|
||
const correctionOrCurrent = (corrections, key, currentValue) => (
|
||
corrections && corrections[key] !== undefined ? corrections[key] : currentValue
|
||
)
|
||
|
||
const itemActualBeat = item => {
|
||
const outputQty = Number(item.goodQty || 0) + Number(item.defectQty || 0)
|
||
return outputQty > 0 ? round2(Number(item.allocatedMinutes || 0) * 60 / outputQty) : 0
|
||
}
|
||
|
||
Page({
|
||
data: {
|
||
user: null,
|
||
rows: [],
|
||
page: 1,
|
||
pageInput: '1',
|
||
totalPages: 1,
|
||
focusReportId: '',
|
||
refreshing: false,
|
||
scrollTop: 0,
|
||
expandedReportId: '',
|
||
detailExpandedAll: false,
|
||
filterVoided: true,
|
||
productModalVisible: false,
|
||
productModalRows: [],
|
||
productModalKeyword: '',
|
||
productModalPage: 1,
|
||
productModalPageInput: '1',
|
||
productModalTotalPages: 1,
|
||
productModalContext: null,
|
||
productModalLoading: false,
|
||
},
|
||
onLoad(options) {
|
||
this.setData({ focusReportId: options.reportId || '' })
|
||
},
|
||
onShow() {
|
||
this.load()
|
||
},
|
||
noop() {},
|
||
onScroll(e) {
|
||
this.currentScrollTop = e.detail.scrollTop || 0
|
||
},
|
||
toggleDetails() {
|
||
this.setData({
|
||
detailExpandedAll: !this.data.detailExpandedAll,
|
||
expandedReportId: '',
|
||
})
|
||
},
|
||
queryReportTop(id) {
|
||
return new Promise(resolve => {
|
||
this.createSelectorQuery()
|
||
.select(`#review-report-${id}`)
|
||
.boundingClientRect(rect => {
|
||
resolve(rect ? rect.top : null)
|
||
})
|
||
.exec()
|
||
})
|
||
},
|
||
restoreReportPosition(id, beforeTop) {
|
||
if (beforeTop === null || beforeTop === undefined) {
|
||
return
|
||
}
|
||
wx.nextTick(() => {
|
||
this.queryReportTop(id).then(afterTop => {
|
||
if (afterTop === null || afterTop === undefined) {
|
||
return
|
||
}
|
||
const nextScrollTop = Math.max(
|
||
0,
|
||
(this.currentScrollTop || 0) + afterTop - beforeTop,
|
||
)
|
||
this.currentScrollTop = nextScrollTop
|
||
this.setData({ scrollTop: nextScrollTop })
|
||
})
|
||
})
|
||
},
|
||
toggleReportDetail(e) {
|
||
const id = String(e.currentTarget.dataset.id || '')
|
||
const switchingReport = this.data.detailExpandedAll
|
||
|| (!!this.data.expandedReportId && this.data.expandedReportId !== id)
|
||
this.queryReportTop(id).then(beforeTop => {
|
||
this.setData({
|
||
detailExpandedAll: false,
|
||
expandedReportId: this.data.expandedReportId === id ? '' : id,
|
||
}, () => {
|
||
if (switchingReport) {
|
||
this.restoreReportPosition(id, beforeTop)
|
||
}
|
||
})
|
||
})
|
||
},
|
||
async load(page = this.data.page) {
|
||
const user = api.getCurrentUser()
|
||
try {
|
||
let result
|
||
if (this.data.focusReportId) {
|
||
const report = await api.getReport(this.data.focusReportId)
|
||
result = {
|
||
rows: (report.status === 'pending' || report.canEdit || report.isVoided) ? [report] : [],
|
||
page: 1,
|
||
totalPages: 1,
|
||
}
|
||
} else {
|
||
result = await api.listReports({
|
||
status: 'pending',
|
||
page,
|
||
pageSize: 6,
|
||
includeVoided: !this.data.filterVoided,
|
||
})
|
||
}
|
||
const rows = await this.buildReviewRows(result.rows || [])
|
||
const defaultExpandedId = this.data.focusReportId && rows.length ? String(rows[0].id) : ''
|
||
this.setData({
|
||
user,
|
||
rows,
|
||
page: result.page,
|
||
pageInput: String(result.page || 1),
|
||
totalPages: result.totalPages,
|
||
expandedReportId: defaultExpandedId,
|
||
detailExpandedAll: false,
|
||
})
|
||
} catch (error) {
|
||
this.setData({ user, rows: [] })
|
||
wx.showToast({ title: error.message, icon: 'none' })
|
||
}
|
||
},
|
||
async onPullDownRefresh() {
|
||
this.setData({ refreshing: true })
|
||
try {
|
||
await this.load(this.data.page)
|
||
} finally {
|
||
this.setData({ refreshing: false })
|
||
if (wx.stopPullDownRefresh) {
|
||
wx.stopPullDownRefresh()
|
||
}
|
||
}
|
||
},
|
||
async buildReviewRows(rows) {
|
||
const hydrated = []
|
||
for (const row of rows) {
|
||
const editItems = []
|
||
const editDeviceSegments = (row.deviceSegments || []).map((segment, index) => this.makeEditDeviceSegment(segment, index))
|
||
for (const detail of row.items || []) {
|
||
const editItem = await this.buildReviewItem(detail)
|
||
const itemKey = segmentKey(editItem)
|
||
editItem.deviceSegments = editDeviceSegments.filter(segment => segmentKey(segment) === itemKey)
|
||
editItems.push(editItem)
|
||
}
|
||
hydrated.push({
|
||
...row,
|
||
timeRangeText: row.isCleaning ? '清洗报工' : `${row.startAtText} 至 ${row.endAtText}`,
|
||
editStartDate: datePart(row.startAt),
|
||
editStartTime: timePart(row.startAt),
|
||
editEndDate: datePart(row.endAt),
|
||
editEndTime: timePart(row.endAt),
|
||
editReviewRemark: row.reviewRemark || '',
|
||
editItems,
|
||
})
|
||
}
|
||
return hydrated
|
||
},
|
||
async buildReviewItem(detail) {
|
||
const [products, equipmentOptions] = await Promise.all([
|
||
this.loadProductOptions(detail.productName, detail.attendancePointName),
|
||
detail.isMisc ? Promise.resolve([]) : this.loadEquipmentOptions(detail.attendancePointName, !!detail.isCleaning),
|
||
])
|
||
return this.makeEditItem(detail, products, equipmentOptions)
|
||
},
|
||
async loadProductOptions(productName, attendancePointName = '', keyword = '') {
|
||
try {
|
||
const result = await api.listProducts({
|
||
productName: keyword ? '' : productName,
|
||
keyword,
|
||
attendancePointName,
|
||
page: 1,
|
||
pageSize: 100,
|
||
})
|
||
return result.rows || []
|
||
} catch (error) {
|
||
return []
|
||
}
|
||
},
|
||
async loadEquipmentOptions(attendancePointName = '', isCleaning = false) {
|
||
try {
|
||
const result = await api.listEquipment({
|
||
attendancePointName,
|
||
deviceType: isCleaning ? '清洗设备' : '冲压设备',
|
||
page: 1,
|
||
pageSize: 100,
|
||
})
|
||
return result.rows || []
|
||
} catch (error) {
|
||
return []
|
||
}
|
||
},
|
||
makeEditItem(detail, products, equipmentOptions = []) {
|
||
const corrections = detail.corrections || {}
|
||
const current = {
|
||
attendancePointName: detail.attendancePointName || '',
|
||
projectNo: detail.projectNo,
|
||
productName: detail.productName,
|
||
process: detail.process || '',
|
||
stampingMethod: detail.stampingMethod || '',
|
||
deviceNo: detail.deviceNo,
|
||
materialCode: detail.materialCode || '',
|
||
materialName: detail.materialName || '',
|
||
rawMaterialBatchNo: detail.rawMaterialBatchNo || '',
|
||
standardBeat: detail.standardBeat || 0,
|
||
standardWorkload: detail.standardWorkload || 0,
|
||
isCleaning: !!detail.isCleaning,
|
||
isContinuousDie: !!detail.isContinuousDie,
|
||
isMisc: !!detail.isMisc,
|
||
isMultiPerson: !!detail.isMultiPerson,
|
||
operatorCount: detail.operatorCount || 1,
|
||
changeoverCount: detail.changeoverCount || 0,
|
||
remark: detail.remark || '',
|
||
}
|
||
const originalProduct = {
|
||
attendancePointName: current.attendancePointName,
|
||
projectNo: correctionOrCurrent(corrections, 'project_no', current.projectNo),
|
||
productName: correctionOrCurrent(corrections, 'product_name', current.productName),
|
||
process: correctionOrCurrent(corrections, 'process_name', current.process) || '',
|
||
materialCode: current.materialCode,
|
||
materialName: current.materialName,
|
||
stampingMethod: correctionOrCurrent(corrections, 'stamping_method', current.stampingMethod) || '',
|
||
operatorCount: current.operatorCount,
|
||
standardBeat: current.standardBeat,
|
||
isCleaning: current.isCleaning,
|
||
isContinuousDie: current.isContinuousDie,
|
||
isMisc: current.isMisc,
|
||
isMultiPerson: current.isMultiPerson,
|
||
}
|
||
const optionMap = {}
|
||
;(products || []).forEach(product => {
|
||
optionMap[optionKey(product)] = product
|
||
})
|
||
optionMap[optionKey(current)] = current
|
||
const options = Object.keys(optionMap).map(key => optionMap[key])
|
||
const productMap = {}
|
||
options.forEach(product => {
|
||
productMap[productChoiceKey(product)] = {
|
||
attendancePointName: product.attendancePointName || current.attendancePointName,
|
||
projectNo: product.projectNo,
|
||
productName: product.productName,
|
||
}
|
||
})
|
||
const productChoices = Object.keys(productMap).map(key => productMap[key])
|
||
const productLabels = productChoices.map(item => `${item.projectNo} · ${item.productName}`)
|
||
let productIndex = productChoices.findIndex(item => item.projectNo === current.projectNo && item.productName === current.productName)
|
||
if (productIndex < 0) {
|
||
productIndex = 0
|
||
}
|
||
const selectedProduct = productChoices[productIndex] || current
|
||
const processOptions = options.filter(item => (
|
||
item.attendancePointName === selectedProduct.attendancePointName
|
||
&& item.projectNo === selectedProduct.projectNo
|
||
&& item.productName === selectedProduct.productName
|
||
))
|
||
const processLabels = processOptions.map(item => item.process || '未填写工序')
|
||
let processIndex = processOptions.findIndex(item => (item.process || '') === current.process)
|
||
if (processIndex < 0) {
|
||
processIndex = 0
|
||
}
|
||
const selectedOption = processOptions[processIndex] || current
|
||
const selectedDeviceNo = api.normalizeDeviceNo(detail.deviceNo)
|
||
const deviceIndex = selectedDeviceNo
|
||
? equipmentOptions.findIndex(item => api.normalizeDeviceNo(item.deviceNo) === selectedDeviceNo)
|
||
: -1
|
||
return withCompareClasses({
|
||
id: detail.id,
|
||
attendancePointName: current.attendancePointName,
|
||
startAt: detail.startAt || '',
|
||
isCleaning: !!detail.isCleaning,
|
||
isContinuousDie: !!selectedOption.isContinuousDie,
|
||
isMisc: !!detail.isMisc,
|
||
isMultiPerson: !!selectedOption.isMultiPerson,
|
||
operatorCount: selectedOption.operatorCount || detail.operatorCount || 1,
|
||
changeoverCount: detail.changeoverCount || 0,
|
||
changeoverCountError: false,
|
||
remark: detail.remark || '',
|
||
deviceError: false,
|
||
deviceNo: detail.deviceNo,
|
||
deviceIndex,
|
||
equipmentOptions,
|
||
projectNo: selectedOption.projectNo,
|
||
productName: selectedOption.productName,
|
||
process: selectedOption.process || '',
|
||
stampingMethod: selectedOption.stampingMethod || current.stampingMethod || '',
|
||
rawMaterialBatchNo: detail.rawMaterialBatchNo || '',
|
||
productSearchKeyword: '',
|
||
productOptions: options,
|
||
productChoices,
|
||
productLabels,
|
||
productIndex,
|
||
processOptions,
|
||
processLabels,
|
||
processIndex,
|
||
originalProduct,
|
||
goodQty: detail.goodQty,
|
||
defectQty: detail.defectQty,
|
||
allocatedMinutes: detail.allocatedMinutes || 0,
|
||
workTimeText: detail.workTimeText || '0秒',
|
||
actualBeat: detail.actualBeat || 0,
|
||
standardBeat: selectedOption.standardBeat || detail.standardBeat || 0,
|
||
standardWorkload: selectedOption.standardWorkload || detail.standardWorkload || 0,
|
||
deviceSegments: detail.deviceSegments || [],
|
||
})
|
||
},
|
||
productToEditItem(item, product) {
|
||
const selectedDeviceNo = api.normalizeDeviceNo(item.deviceNo)
|
||
const deviceIndex = selectedDeviceNo
|
||
? (item.equipmentOptions || []).findIndex(equipment => api.normalizeDeviceNo(equipment.deviceNo) === selectedDeviceNo)
|
||
: -1
|
||
const nextItem = {
|
||
...item,
|
||
attendancePointName: item.attendancePointName,
|
||
projectNo: product.projectNo || item.projectNo,
|
||
productName: product.productName || item.productName,
|
||
materialCode: product.materialCode || '',
|
||
materialName: product.materialName || '',
|
||
process: product.process || '',
|
||
stampingMethod: product.stampingMethod || '',
|
||
standardBeat: Number(product.standardBeat || 0),
|
||
standardWorkload: api.calculateStandardWorkload(item.allocatedMinutes, product.standardBeat),
|
||
operatorCount: product.operatorCount || 1,
|
||
isCleaning: !!product.isCleaning,
|
||
isContinuousDie: !!product.isContinuousDie,
|
||
isMisc: !!product.isMisc,
|
||
isMultiPerson: !!product.isMultiPerson,
|
||
changeoverCount: product.isContinuousDie ? (item.changeoverCount || 0) : 0,
|
||
changeoverCountError: false,
|
||
actualBeat: itemActualBeat(item),
|
||
deviceIndex,
|
||
productOptions: [product],
|
||
productChoices: [product],
|
||
productLabels: [`${product.projectNo || ''} · ${product.productName || ''}`],
|
||
productIndex: 0,
|
||
processOptions: [product],
|
||
processLabels: [product.process || '未填写工序'],
|
||
processIndex: 0,
|
||
}
|
||
return withCompareClasses(nextItem)
|
||
},
|
||
replaceEditProduct(reportIndex, itemIndex, product) {
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
if (!item || !product) {
|
||
return
|
||
}
|
||
this.replaceEditItem(reportIndex, itemIndex, this.productToEditItem(item, product))
|
||
},
|
||
async openProductModal(e) {
|
||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||
const row = this.data.rows[reportIndex]
|
||
const item = row && row.editItems ? row.editItems[itemIndex] : null
|
||
if (!item) {
|
||
wx.showToast({ title: '报工明细不存在', icon: 'none' })
|
||
return
|
||
}
|
||
this.setData({
|
||
productModalVisible: true,
|
||
productModalContext: { reportIndex, itemIndex },
|
||
productModalRows: [],
|
||
productModalKeyword: '',
|
||
productModalPage: 1,
|
||
productModalPageInput: '1',
|
||
productModalTotalPages: 1,
|
||
})
|
||
await this.loadProductModal(1)
|
||
},
|
||
closeProductModal() {
|
||
this.setData({
|
||
productModalVisible: false,
|
||
productModalRows: [],
|
||
productModalKeyword: '',
|
||
productModalContext: null,
|
||
})
|
||
},
|
||
onDeviceChange(e) {
|
||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||
const deviceIndex = Number(e.detail.value)
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
const equipment = (item.equipmentOptions || [])[deviceIndex]
|
||
if (!equipment) {
|
||
return
|
||
}
|
||
this.setData({
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceIndex`]: deviceIndex,
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceNo`]: equipment.deviceNo,
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceError`]: false,
|
||
})
|
||
},
|
||
async loadProductModal(page = this.data.productModalPage) {
|
||
const context = this.data.productModalContext
|
||
if (!context) {
|
||
return
|
||
}
|
||
const item = this.data.rows[context.reportIndex].editItems[context.itemIndex]
|
||
this.setData({ productModalLoading: true })
|
||
try {
|
||
const result = await api.listProducts({
|
||
keyword: this.data.productModalKeyword,
|
||
attendancePointName: item.attendancePointName,
|
||
page,
|
||
pageSize: 8,
|
||
})
|
||
this.setData({
|
||
productModalRows: result.rows || [],
|
||
productModalPage: result.page,
|
||
productModalPageInput: String(result.page || 1),
|
||
productModalTotalPages: result.totalPages,
|
||
})
|
||
} catch (error) {
|
||
wx.showToast({ title: error.message || '加载产品失败', icon: 'none' })
|
||
} finally {
|
||
this.setData({ productModalLoading: false })
|
||
}
|
||
},
|
||
onProductModalKeywordInput(e) {
|
||
this.setData({ productModalKeyword: e.detail.value })
|
||
this.loadProductModal(1)
|
||
},
|
||
replaceProductFromModal(e) {
|
||
const index = Number(e.currentTarget.dataset.index)
|
||
const context = this.data.productModalContext
|
||
const product = this.data.productModalRows[index]
|
||
if (!context || !product) {
|
||
return
|
||
}
|
||
const item = this.data.rows[context.reportIndex].editItems[context.itemIndex]
|
||
if (product.isMisc) {
|
||
wx.showToast({ title: '处理杂活不能替换到普通报工', icon: 'none' })
|
||
return
|
||
}
|
||
if (!!item.isCleaning !== !!product.isCleaning) {
|
||
wx.showToast({ title: item.isCleaning ? '清洗报工只能替换清洗工序' : '普通报工不能替换为清洗工序', icon: 'none' })
|
||
return
|
||
}
|
||
this.replaceEditProduct(context.reportIndex, context.itemIndex, product)
|
||
this.closeProductModal()
|
||
},
|
||
productModalPrevPage() {
|
||
if (this.data.productModalPage > 1) {
|
||
this.loadProductModal(this.data.productModalPage - 1)
|
||
}
|
||
},
|
||
productModalNextPage() {
|
||
if (this.data.productModalPage < this.data.productModalTotalPages) {
|
||
this.loadProductModal(this.data.productModalPage + 1)
|
||
}
|
||
},
|
||
onProductModalPageInput(e) {
|
||
this.setData({ productModalPageInput: e.detail.value })
|
||
},
|
||
jumpProductModalPage(e) {
|
||
wx.hideKeyboard()
|
||
const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.productModalPageInput
|
||
const target = Math.max(1, Math.min(Number(inputValue) || this.data.productModalPage, this.data.productModalTotalPages))
|
||
if (target === this.data.productModalPage) {
|
||
this.setData({ productModalPageInput: String(this.data.productModalPage || 1) })
|
||
return
|
||
}
|
||
this.loadProductModal(target)
|
||
},
|
||
async restoreProduct(e) {
|
||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
if (!item || !item.originalProduct) {
|
||
return
|
||
}
|
||
const original = item.originalProduct
|
||
wx.showLoading({ title: '恢复中' })
|
||
try {
|
||
const products = await this.loadProductOptions(original.productName, item.attendancePointName)
|
||
const product = (products || []).find(option => (
|
||
option.projectNo === original.projectNo
|
||
&& option.productName === original.productName
|
||
&& (option.process || '') === (original.process || '')
|
||
)) || original
|
||
this.replaceEditProduct(reportIndex, itemIndex, {
|
||
...product,
|
||
projectNo: original.projectNo,
|
||
productName: original.productName,
|
||
process: original.process || '',
|
||
stampingMethod: product.stampingMethod || original.stampingMethod || '',
|
||
materialCode: product.materialCode || original.materialCode || '',
|
||
materialName: product.materialName || original.materialName || '',
|
||
operatorCount: product.operatorCount || original.operatorCount || 1,
|
||
standardBeat: product.standardBeat === undefined ? original.standardBeat : product.standardBeat,
|
||
isCleaning: product.isCleaning === undefined ? original.isCleaning : product.isCleaning,
|
||
isContinuousDie: product.isContinuousDie === undefined ? original.isContinuousDie : product.isContinuousDie,
|
||
isMisc: product.isMisc === undefined ? original.isMisc : product.isMisc,
|
||
isMultiPerson: product.isMultiPerson === undefined ? original.isMultiPerson : product.isMultiPerson,
|
||
changeoverCount: (product.isContinuousDie === undefined ? original.isContinuousDie : product.isContinuousDie) ? (item.changeoverCount || 0) : 0,
|
||
})
|
||
} catch (error) {
|
||
wx.showToast({ title: error.message || '恢复失败', icon: 'none' })
|
||
} finally {
|
||
wx.hideLoading()
|
||
}
|
||
},
|
||
makeEditDeviceSegment(segment, index) {
|
||
const editScanDate = datePart(segment.scannedAt)
|
||
const editScanTime = timePart(segment.scannedAt)
|
||
return {
|
||
...segment,
|
||
label: index === 0 ? '开始模具' : '换模具',
|
||
editScanDate,
|
||
editScanTime,
|
||
originalScanDate: editScanDate,
|
||
originalScanTime: editScanTime,
|
||
}
|
||
},
|
||
replaceEditItem(reportIndex, itemIndex, item) {
|
||
this.setData({
|
||
[`rows[${reportIndex}].editItems[${itemIndex}]`]: item,
|
||
})
|
||
},
|
||
onDeviceSegmentFieldChange(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
const segmentIndex = e.currentTarget.dataset.segmentIndex
|
||
const field = e.currentTarget.dataset.field
|
||
this.setData({
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceSegments[${segmentIndex}].${field}`]: e.detail.value,
|
||
})
|
||
},
|
||
onReportFieldChange(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const field = e.currentTarget.dataset.field
|
||
this.setData({
|
||
[`rows[${reportIndex}].${field}`]: e.detail.value,
|
||
})
|
||
},
|
||
onItemInput(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
const field = e.currentTarget.dataset.field
|
||
const value = e.detail.value
|
||
const updates = {
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].${field}`]: value,
|
||
}
|
||
if (field === 'goodQty' || field === 'defectQty') {
|
||
const item = {
|
||
...this.data.rows[reportIndex].editItems[itemIndex],
|
||
[field]: value,
|
||
}
|
||
const outputQty = Number(item.goodQty || 0) + Number(item.defectQty || 0)
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].actualBeat`] = outputQty > 0
|
||
? round2(Number(item.allocatedMinutes || 0) * 60 / outputQty)
|
||
: 0
|
||
const nextItem = {
|
||
...item,
|
||
actualBeat: updates[`rows[${reportIndex}].editItems[${itemIndex}].actualBeat`],
|
||
}
|
||
const compared = withCompareClasses(nextItem)
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].standardWorkload`] = compared.standardWorkload
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].beatCompareClass`] = compared.beatCompareClass
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].beatReason`] = compared.beatReason
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].workloadCompareClass`] = compared.workloadCompareClass
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].workloadReason`] = compared.workloadReason
|
||
}
|
||
if (field === 'changeoverCount') {
|
||
updates[`rows[${reportIndex}].editItems[${itemIndex}].changeoverCountError`] = false
|
||
}
|
||
this.setData(updates)
|
||
},
|
||
async reloadItemOptions(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
wx.showLoading({ title: '匹配中' })
|
||
try {
|
||
const keyword = String(item.productSearchKeyword || '').trim()
|
||
const products = await this.loadProductOptions(item.productName, item.attendancePointName, keyword)
|
||
this.replaceEditItem(reportIndex, itemIndex, this.makeEditItem(item, products))
|
||
} finally {
|
||
wx.hideLoading()
|
||
}
|
||
},
|
||
onProductSearchInput(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
this.setData({
|
||
[`rows[${reportIndex}].editItems[${itemIndex}].productSearchKeyword`]: e.detail.value,
|
||
})
|
||
},
|
||
onProductChange(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
const productIndex = Number(e.detail.value)
|
||
const selectedProduct = item.productChoices[productIndex]
|
||
const processOptions = item.productOptions.filter(option => (
|
||
option.attendancePointName === selectedProduct.attendancePointName
|
||
&& option.projectNo === selectedProduct.projectNo
|
||
&& option.productName === selectedProduct.productName
|
||
))
|
||
const nextOption = processOptions[0] || selectedProduct
|
||
this.replaceEditItem(reportIndex, itemIndex, withCompareClasses({
|
||
...item,
|
||
productIndex,
|
||
attendancePointName: item.attendancePointName,
|
||
projectNo: selectedProduct.projectNo,
|
||
productName: selectedProduct.productName,
|
||
processOptions,
|
||
processLabels: processOptions.map(option => option.process || '未填写工序'),
|
||
processIndex: 0,
|
||
process: nextOption.process || '',
|
||
stampingMethod: nextOption.stampingMethod || '',
|
||
deviceNo: item.deviceNo,
|
||
standardBeat: nextOption.standardBeat || 0,
|
||
standardWorkload: nextOption.standardWorkload || 0,
|
||
operatorCount: nextOption.operatorCount || 1,
|
||
isMultiPerson: !!nextOption.isMultiPerson,
|
||
}))
|
||
},
|
||
onProcessChange(e) {
|
||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||
const processIndex = Number(e.detail.value)
|
||
const selected = item.processOptions[processIndex]
|
||
if (!selected) {
|
||
return
|
||
}
|
||
this.replaceEditItem(reportIndex, itemIndex, withCompareClasses({
|
||
...item,
|
||
processIndex,
|
||
attendancePointName: item.attendancePointName,
|
||
deviceNo: item.deviceNo,
|
||
projectNo: selected.projectNo,
|
||
productName: selected.productName,
|
||
process: selected.process || '',
|
||
stampingMethod: selected.stampingMethod || '',
|
||
standardBeat: selected.standardBeat || 0,
|
||
standardWorkload: selected.standardWorkload || 0,
|
||
operatorCount: selected.operatorCount || 1,
|
||
isMultiPerson: !!selected.isMultiPerson,
|
||
}))
|
||
},
|
||
async approve(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
const user = this.data.user
|
||
if (!user || user.role !== 'admin') {
|
||
wx.showToast({ title: '无审核权限', icon: 'none' })
|
||
return
|
||
}
|
||
const row = this.data.rows.find(item => String(item.id) === String(id))
|
||
if (!row) {
|
||
wx.showToast({ title: '报工记录不存在', icon: 'none' })
|
||
return
|
||
}
|
||
if (row.isVoided) {
|
||
wx.showToast({ title: '作废记录不能编辑', icon: 'none' })
|
||
return
|
||
}
|
||
if (!row.canEdit) {
|
||
wx.showToast({ title: '已超过可编辑期限', icon: 'none' })
|
||
return
|
||
}
|
||
const invalidItem = (row.editItems || []).find(item => (
|
||
Number(item.goodQty || 0) < 0
|
||
|| Number(item.defectQty || 0) < 0
|
||
|| !Number.isFinite(Number(item.goodQty || 0))
|
||
|| !Number.isFinite(Number(item.defectQty || 0))
|
||
))
|
||
if (invalidItem) {
|
||
wx.showToast({ title: '产出数量不能为负数', icon: 'none' })
|
||
return
|
||
}
|
||
const invalidChangeoverItemIndex = (row.editItems || []).findIndex(item => {
|
||
if (!item.isContinuousDie) {
|
||
return false
|
||
}
|
||
const value = Number(item.changeoverCount)
|
||
return !Number.isInteger(value) || value < 0
|
||
})
|
||
if (invalidChangeoverItemIndex >= 0) {
|
||
this.setData({
|
||
[`rows[${this.data.rows.findIndex(item => String(item.id) === String(id))}].editItems[${invalidChangeoverItemIndex}].changeoverCountError`]: true,
|
||
})
|
||
wx.showToast({ title: '请填写非负整数换料次数', icon: 'none' })
|
||
return
|
||
}
|
||
const missingDeviceItemIndex = (row.editItems || []).findIndex(item => !item.isMisc && !api.normalizeDeviceNo(item.deviceNo))
|
||
if (missingDeviceItemIndex >= 0) {
|
||
this.setData({
|
||
[`rows[${this.data.rows.findIndex(item => String(item.id) === String(id))}].editItems[${missingDeviceItemIndex}].deviceError`]: true,
|
||
})
|
||
wx.showToast({ title: '请选择设备号', icon: 'none' })
|
||
return
|
||
}
|
||
await this.submitApprove(id, row, user)
|
||
},
|
||
async submitApprove(id, row, user) {
|
||
const deviceCorrections = (row.editItems || []).flatMap(item => item.deviceSegments || [])
|
||
.filter(segment => (
|
||
segment.editScanDate !== segment.originalScanDate
|
||
|| segment.editScanTime !== segment.originalScanTime
|
||
))
|
||
.map(segment => ({
|
||
id: segment.id,
|
||
scannedAt: dateTimeValue(segment.editScanDate, segment.editScanTime),
|
||
}))
|
||
const invalidDeviceCorrection = deviceCorrections.find(item => !item.scannedAt)
|
||
if (invalidDeviceCorrection) {
|
||
wx.showToast({ title: '换模具时间不能为空', icon: 'none' })
|
||
return
|
||
}
|
||
const patch = {
|
||
startAt: row.isCleaning ? null : dateTimeValue(row.editStartDate, row.editStartTime),
|
||
endAt: row.isCleaning ? null : dateTimeValue(row.editEndDate, row.editEndTime),
|
||
deviceCorrections: row.isCleaning ? [] : deviceCorrections,
|
||
reviewRemark: row.editReviewRemark || '',
|
||
itemCorrections: (row.editItems || []).map(item => ({
|
||
id: item.id,
|
||
device_no: api.normalizeDeviceNo(item.deviceNo),
|
||
project_no: item.projectNo,
|
||
product_name: item.productName,
|
||
raw_material_batch_no: item.rawMaterialBatchNo || null,
|
||
process_name: item.process || '',
|
||
changeover_count: item.isContinuousDie ? Number(item.changeoverCount || 0) : 0,
|
||
good_qty: Number(item.goodQty || 0),
|
||
defect_qty: Number(item.defectQty || 0),
|
||
scrap_qty: 0,
|
||
remark: item.remark || null,
|
||
})),
|
||
}
|
||
|
||
try {
|
||
await api.approveReport(id, user, patch)
|
||
wx.showToast({ title: row.status === 'approved' ? '已保存更改' : '已审核通过', icon: 'success' })
|
||
if (this.data.focusReportId) {
|
||
setTimeout(() => {
|
||
wx.navigateBack({
|
||
delta: 1,
|
||
fail: () => {
|
||
wx.redirectTo({
|
||
url: '/pages/records/records?mode=pending&status=pending',
|
||
})
|
||
},
|
||
})
|
||
}, 500)
|
||
return
|
||
}
|
||
await this.load(this.data.page)
|
||
} catch (error) {
|
||
wx.showToast({ title: error.message, icon: 'none' })
|
||
}
|
||
},
|
||
prevPage() {
|
||
if (this.data.page > 1) {
|
||
this.load(this.data.page - 1)
|
||
}
|
||
},
|
||
nextPage() {
|
||
if (this.data.page < this.data.totalPages) {
|
||
this.load(this.data.page + 1)
|
||
}
|
||
},
|
||
onPageInput(e) {
|
||
this.setData({ pageInput: e.detail.value })
|
||
},
|
||
jumpToPage(e) {
|
||
wx.hideKeyboard()
|
||
const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.pageInput
|
||
const target = Math.max(1, Math.min(Number(inputValue) || this.data.page, this.data.totalPages))
|
||
if (target === this.data.page) {
|
||
this.setData({ pageInput: String(this.data.page || 1) })
|
||
return
|
||
}
|
||
this.load(target)
|
||
},
|
||
toggleFilterVoided() {
|
||
this.setData({ filterVoided: !this.data.filterVoided, page: 1 })
|
||
this.load(1)
|
||
},
|
||
onFilterVoidedChange(e) {
|
||
this.setData({ filterVoided: !!e.detail.value, page: 1 })
|
||
this.load(1)
|
||
},
|
||
async voidReport(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
wx.showModal({
|
||
title: '确认作废',
|
||
content: '作废的记录将在30天后被系统彻底删除,彻底删除前可随时撤销作废,是否确定删除',
|
||
cancelText: '否',
|
||
confirmText: '是',
|
||
confirmColor: '#c5221f',
|
||
success: async res => {
|
||
if (!res.confirm) {
|
||
return
|
||
}
|
||
wx.showLoading({ title: '处理中' })
|
||
try {
|
||
await api.voidReport(id)
|
||
wx.showToast({ title: '已作废', icon: 'success' })
|
||
await this.load(this.data.page)
|
||
} catch (error) {
|
||
wx.showToast({ title: error.message || '作废失败', icon: 'none' })
|
||
} finally {
|
||
wx.hideLoading()
|
||
}
|
||
},
|
||
})
|
||
},
|
||
async unvoidReport(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
wx.showLoading({ title: '处理中' })
|
||
try {
|
||
await api.unvoidReport(id)
|
||
wx.showToast({ title: '已撤销作废', icon: 'success' })
|
||
await this.load(this.data.page)
|
||
} catch (error) {
|
||
wx.showToast({ title: error.message || '撤销失败', icon: 'none' })
|
||
} finally {
|
||
wx.hideLoading()
|
||
}
|
||
},
|
||
})
|