From 05bd620811c491a885c8e16d340eaf597c76a825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Wed, 13 May 2026 14:01:13 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor(wms/coil):=20=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E8=AF=B7=E6=B1=82=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=90=8E=E8=B0=83=E7=94=A8=E6=9B=B4=E6=96=B0=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在三个钢卷相关的页面中,提取请求载荷时排除status、exclusiveStatus、dataType字段,仅传递有效参数给更新接口 --- klp-ui/src/views/wms/coil/do/correct.vue | 5 ++++- klp-ui/src/views/wms/coil/do/correntAll.vue | 5 ++++- klp-ui/src/views/wms/coil/panels/base.vue | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/klp-ui/src/views/wms/coil/do/correct.vue b/klp-ui/src/views/wms/coil/do/correct.vue index 9e58a044..abbd78c9 100644 --- a/klp-ui/src/views/wms/coil/do/correct.vue +++ b/klp-ui/src/views/wms/coil/do/correct.vue @@ -832,7 +832,10 @@ export default { this.buttonLoading = true; if (this.form.coilId) { // 更新 - updateMaterialCoilSimple(this.form).then(_ => { + const { status, exclusiveStatus, dataType, ...payload} = { + ...this.form, + } + updateMaterialCoilSimple(payload).then(_ => { this.$modal.msgSuccess("修正成功"); this.correctVisible = false; this.getMaterialCoil(); diff --git a/klp-ui/src/views/wms/coil/do/correntAll.vue b/klp-ui/src/views/wms/coil/do/correntAll.vue index fbcb386e..ea84f195 100644 --- a/klp-ui/src/views/wms/coil/do/correntAll.vue +++ b/klp-ui/src/views/wms/coil/do/correntAll.vue @@ -812,7 +812,10 @@ export default { } this.buttonLoading = true; // 更新钢卷信息 - updateMaterialCoilSimple(this.form).then(_ => { + const { status, exclusiveStatus, dataType, ...payload} = { + ...this.form, + } + updateMaterialCoilSimple(payload).then(_ => { this.$modal.msgSuccess("修正成功"); this.correctVisible = false; this.getMaterialCoil(); diff --git a/klp-ui/src/views/wms/coil/panels/base.vue b/klp-ui/src/views/wms/coil/panels/base.vue index 7bbd1e3b..b52ece0f 100644 --- a/klp-ui/src/views/wms/coil/panels/base.vue +++ b/klp-ui/src/views/wms/coil/panels/base.vue @@ -2061,7 +2061,10 @@ export default { if (valid) { this.buttonLoading = true; if (this.form.coilId != null) { - updateMaterialCoilSimple(this.form).then(_ => { + const { status, exclusiveStatus, dataType, ...payload} = { + ...this.form, + } + updateMaterialCoilSimple(payload).then(_ => { this.$modal.msgSuccess("修改成功"); this.open = false; this.getList(); From c737f7f66115ed875291a24a0e5889d3caf07c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Wed, 13 May 2026 15:21:02 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(CoilSelector):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=88=97=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=A1=A8=E6=A0=BC=E5=88=97=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拆分默认列和全屏列配置,新增可选列列表 - 新增自定义列配置弹窗,支持调整列顺序、宽度、对齐方式和字段映射 - 优化搜索区域布局,新增列配置按钮 - 移除硬编码的表格列,改用动态渲染配置列 - 修复选中表格高度适配问题,固定操作列位置 --- klp-ui/src/components/CoilSelector/data.js | 154 ++++++++- klp-ui/src/components/CoilSelector/index.vue | 334 +++++++++++++------ 2 files changed, 386 insertions(+), 102 deletions(-) diff --git a/klp-ui/src/components/CoilSelector/data.js b/klp-ui/src/components/CoilSelector/data.js index 1234331d..9ff3f2f3 100644 --- a/klp-ui/src/components/CoilSelector/data.js +++ b/klp-ui/src/components/CoilSelector/data.js @@ -1,4 +1,5 @@ -export const defaultColumns = [{ +export const defaultColumns = [ + { label: '入场卷号', align: 'center', prop: 'enterCoilNo', @@ -61,10 +62,149 @@ export const defaultColumns = [{ width: '100', showOverflowTooltip: true }, - // { - // label: '备注', - // align: 'center', - // prop: 'remark', - // showOverflowTooltip: true - // } + { + label: '备注', + align: 'center', + prop: 'remark', + showOverflowTooltip: true + }, +] + +export const fullPageDefaultColumns = [ + { + label: '入场卷号', + align: 'center', + prop: 'enterCoilNo', + showOverflowTooltip: true + }, + { + label: '当前卷号', + align: 'center', + prop: 'currentCoilNo', + showOverflowTooltip: true + }, + { + label: '存储位置', + align: 'center', + prop: 'actualWarehouseName', + width: '120', + showOverflowTooltip: true + }, + { + label: '物料', + align: 'center', + prop: 'itemName', + width: '100' + }, + { + label: '规格', + align: 'center', + prop: 'specification', + width: '100' + }, + { + label: '重量(t)', + align: 'center', + prop: 'netWeight', + width: '100' + }, + { + label: '材质', + align: 'center', + prop: 'material', + width: '100' + }, + { + label: '厂家', + align: 'center', + prop: 'manufacturer', + width: '100' + }, + { + label: '库区', + align: 'center', + prop: 'warehouseName', + width: '120', + showOverflowTooltip: true + }, + { + label: '创建时间', + align: 'center', + prop: 'createTime', + width: '100', + showOverflowTooltip: true + }, + { + label: '品质', + align: 'center', + prop: 'qualityStatus', + }, + { + label: '表面处理', + align: 'center', + prop: 'surfaceTreatmentDesc', + }, + { + label: '备注', + align: 'center', + prop: 'remark', + width: '100', + showOverflowTooltip: true + }, + { + label: '切边', + align: 'center', + prop: 'trimmingRequirement', + }, + { + label: '包装', + align: 'center', + prop: 'packagingRequirement', + }, + { + label: '镀层质量', + align: 'center', + prop: 'zincLayer', + }, +] + +export const optionalColumns = [ + { label: '入场卷号', value: 'enterCoilNo' }, + { label: '当前卷号', value: 'currentCoilNo' }, + { label: '厂家钢卷号', value: 'supplierCoilNo' }, + { label: '逻辑库区', value: 'warehouseName' }, + { label: '实际库区', value: 'actualWarehouseName' }, + { label: '质量状态', value: 'qualityStatus' }, + { label: '切边要求', value: 'trimmingRequirement' }, + { label: '原料材质', value: 'packingStatus' }, + { label: '包装要求', value: 'packagingRequirement' }, + { label: '产品类型', value: 'itemId' }, + { label: '品名', value: 'itemName' }, + { label: '宽度', value: 'computedWidth' }, + { label: '厚度', value: 'computedThickness' }, + { label: '规格', value: 'specification' }, + { label: '材质', value: 'material' }, + { label: '厂家', value: 'manufacturer' }, + { label: '表面处理', value: 'surfaceTreatmentDesc' }, + { label: '镀层质量', value: 'zincLayer' }, + { label: '长度', value: 'length' }, + { label: '毛重', value: 'grossWeight' }, + { label: '净重', value: 'netWeight' }, + { label: '创建时间', value: 'createTime' }, + { label: '创建人', value: 'createBy' }, + { label: '更新时间', value: 'updateTime' }, + { label: '更新人', value: 'updateByName' }, + { label: '备注', value: 'remark' }, + { label: '生产开始时间', value: 'productionStartTime' }, + { label: '生产结束时间', value: 'productionEndTime' }, + { label: '生产耗时', value: 'productionDuration' }, + { label: '出库状态', value: 'status' }, + { label: '操作完成时间', value: 'actionCompleteTime' }, + { label: '生产线速度', value: 'productionSpeed' }, + { label: '发货绑定车牌号', value: 'bindLicensePlate' }, + { label: '发货绑定目标客户', value: 'bindConsigneeUnit' }, + { label: '发货绑定单位', value: 'bindSenderUnit' }, + { label: '发货绑定负责人', value: 'bindPrincipal' }, + { label: '发货配卷时间', value: 'bindDeliveryTime' }, + { label: '发货时间', value: 'exportTime' }, ] diff --git a/klp-ui/src/components/CoilSelector/index.vue b/klp-ui/src/components/CoilSelector/index.vue index 26701f9c..8bf21d68 100644 --- a/klp-ui/src/components/CoilSelector/index.vue +++ b/klp-ui/src/components/CoilSelector/index.vue @@ -19,75 +19,85 @@ -
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + 全选当前卷 + 显示钢卷地图 + 显示订单详情 + + + + +
搜索 重置 - 全选当前卷 - 显示钢卷地图 - 显示订单详情 - - + 列配置 +
+
@@ -99,12 +109,6 @@ - - - - - - @@ -138,17 +142,11 @@
- + - - - - - - - + @@ -182,12 +180,6 @@ - - - - - - @@ -226,6 +218,52 @@
+ + + + + + + + + + + + + + + + + + + + +
@@ -236,7 +274,7 @@ import { listActualWarehouse } from "@/api/wms/actualWarehouse"; import { treeActualWarehouseTwoLevel } from "@/api/wms/actualWarehouse"; import MemoInput from '@/components/MemoInput/index.vue'; import MutiSelect from '@/components/MutiSelect/index.vue'; -import { defaultColumns } from './data'; +import { defaultColumns, fullPageDefaultColumns, optionalColumns } from './data'; import ActualWarehouseSelect from '@/components/KLPService/ActualWarehouseSelect/index.vue'; import WarehouseBirdMini from '@/views/wms/warehouse/components/WarehouseBirdMini.vue'; import DragResizeBox from '@/components/DragResizeBox/index.vue'; @@ -323,7 +361,7 @@ export default { defaultType: { type: String, default: 'product' - } + }, }, data() { return { @@ -346,10 +384,10 @@ export default { actualWarehouseId: null, itemType: this.defaultType, selectType: this.defaultType, - status: 0, // 不包含已发货的钢卷 - dataType: 1 // 只查询当前数据,不查询历史数据 + status: 0, + dataType: 1 }, - columns: defaultColumns, + optionalColumns, selectedCoils: [], warehouseList: [], selectedNodeId: null, @@ -359,6 +397,9 @@ export default { treeLoading: false, showOrderInfo: false, coilTrimStatistics: {}, + showColumnSetting: false, + columnsConfig: [], + columnsConfigVersion: 0, }; }, computed: { @@ -383,8 +424,37 @@ export default { } }, renderColumns() { - // 如果有自定义列配置,使用它;否则使用默认列 - return this.coilColumn.length > 0 ? this.coilColumn : this.columns; + const _ = this.columnsConfigVersion; + if (this.coilColumn.length > 0) { + return this.coilColumn; + } + const storageKey = this.columnStorageKey; + const savedConfig = localStorage.getItem(storageKey); + if (savedConfig) { + try { + const config = JSON.parse(savedConfig); + if (config.length > 0) { + return config; + } + } catch (e) { + console.error('解析列配置失败', e); + } + } + return this.orderBy ? fullPageDefaultColumns : defaultColumns; + }, + columnStorageKey() { + return `coil-selector-columns-${this.orderBy ? 'full' : 'default'}`; + }, + displayColumns() { + const displayData = [...this.columnsConfig]; + displayData.push({ + label: "", + prop: "", + width: "100", + align: "center", + _isEmpty: true + }); + return displayData; }, // 已选钢卷ID集合(用于快速判断) selectedCoilIds() { @@ -411,7 +481,7 @@ export default { dialogVisible(val) { if (val) { this.resetQuery(); - this.getList(); + // this.getList(); } }, // 非触发器模式下,监听外部visible属性变化 @@ -438,9 +508,13 @@ export default { return; } if (val && !this.selectedCoil?.coilId) { - // 如果传入了coilId但还没有选中数据,尝试通过id匹配(可选功能) this.matchCoilById(val); } + }, + showColumnSetting(val) { + if (val) { + this.loadColumnConfig(); + } } }, created() { @@ -694,8 +768,78 @@ export default { // 关闭对话框 handleClose() { this.dialogVisible = false; - // 触发关闭事件,通知父组件 this.$emit('close'); + }, + confirmColumnConfig() { + this.saveColumnConfig(); + this.loadColumnConfig(); + this.showColumnSetting = false; + }, + loadColumnConfig() { + const storageKey = this.columnStorageKey; + const savedConfig = localStorage.getItem(storageKey); + if (savedConfig) { + try { + this.columnsConfig = JSON.parse(savedConfig); + } catch (e) { + console.error('解析列配置失败', e); + this.columnsConfig = this.orderBy ? [...fullPageDefaultColumns] : [...defaultColumns]; + } + } else { + this.columnsConfig = this.orderBy ? [...fullPageDefaultColumns] : [...defaultColumns]; + } + }, + saveColumnConfig() { + const nonEmptyColumns = this.columnsConfig.filter(col => col.prop && col.label); + localStorage.setItem(this.columnStorageKey, JSON.stringify(nonEmptyColumns)); + this.columnsConfigVersion++; + }, + moveColumnUp(index) { + if (index > 0 && !this.displayColumns[index]._isEmpty) { + const temp = this.columnsConfig[index]; + this.columnsConfig.splice(index, 1); + this.columnsConfig.splice(index - 1, 0, temp); + this.saveColumnConfig(); + } + }, + moveColumnDown(index) { + if (index < this.columnsConfig.length - 1 && !this.displayColumns[index]._isEmpty) { + const temp = this.columnsConfig[index]; + this.columnsConfig.splice(index, 1); + this.columnsConfig.splice(index + 1, 0, temp); + this.saveColumnConfig(); + } + }, + removeColumn(index) { + if (!this.displayColumns[index]._isEmpty) { + this.columnsConfig.splice(index, 1); + this.saveColumnConfig(); + } + }, + columnPropChange(propValue, row) { + const item = optionalColumns.find(item => item.value === propValue); + if (item) { + row.label = item.label; + } else { + row.label = propValue; + } + if (row._isEmpty && propValue) { + const newColumn = { + label: row.label, + prop: row.prop, + width: row.width, + align: row.align, + showOverflowTooltip: true + }; + this.columnsConfig.push(newColumn); + this.saveColumnConfig(); + } else { + this.saveColumnConfig(); + } + }, + resetColumnConfig() { + this.columnsConfig = this.orderBy ? [...fullPageDefaultColumns] : [...defaultColumns]; + this.saveColumnConfig(); } } }; From a171d3962ff81c1241337a14406e9840f2140074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Wed, 13 May 2026 15:21:16 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(wms/bonus):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=A5=96=E9=87=91=E6=B1=A0=E7=8B=AC=E7=AB=8B=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=8E=9F=E6=9C=89=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增了完整的奖金池管理页面pool.vue,补充了名称搜索、完整表格列以及增改查详情功能,同时在原有index.vue中添加了名称搜索项和表格列 --- klp-ui/src/views/wms/bonus/index.vue | 10 + klp-ui/src/views/wms/bonus/pool.vue | 422 +++++++++++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 klp-ui/src/views/wms/bonus/pool.vue diff --git a/klp-ui/src/views/wms/bonus/index.vue b/klp-ui/src/views/wms/bonus/index.vue index e880201f..d3666aa6 100644 --- a/klp-ui/src/views/wms/bonus/index.vue +++ b/klp-ui/src/views/wms/bonus/index.vue @@ -2,6 +2,12 @@
+ + + + @@ -32,6 +38,7 @@ + + + + diff --git a/klp-ui/src/views/wms/bonus/pool.vue b/klp-ui/src/views/wms/bonus/pool.vue new file mode 100644 index 00000000..f592ac87 --- /dev/null +++ b/klp-ui/src/views/wms/bonus/pool.vue @@ -0,0 +1,422 @@ + + + \ No newline at end of file From bbc7e63b2e6e803f2d6daa9d313fa7c3795085b9 Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Wed, 13 May 2026 16:18:05 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor(attendance):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=80=83=E5=8B=A4=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=B7=A8=E5=A4=A9=E7=8F=AD=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加跨天班次判断方法 isCrossDayShift - 修改 getRecords 方法支持跨天查询参数 - 重构考勤记录过滤逻辑,添加 filterWindow 方法 - 调整时间段检查逻辑,区分单时段和多时段班次 - 修复早退状态码命名错误(early_1/2 统一为 early_one/two) - 优化打卡时间匹配逻辑,直接使用记录对象而非时间比较 - 更新严重程度比较列表,修正状态码对应关系 --- .../impl/WmsAttendanceCheckServiceImpl.java | 132 +++++++++++------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java index d15e3144..5b0e29e9 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java @@ -103,22 +103,33 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService continue; } - // 获取该员工该日期的所有打卡记录 - List records = getRecords(schedule.getEmployeeName(), schedule.getWorkDate()); + boolean crossDay = isCrossDayShift(schedule); + List records = getRecords(schedule.getEmployeeName(), schedule.getWorkDate(), crossDay); WmsAttendanceCheck check = buildCheck(schedule, rule, records); - // 删除已有的比对结果 baseMapper.delete(Wrappers.lambdaQuery() .eq(WmsAttendanceCheck::getScheduleId, schedule.getScheduleId())); baseMapper.insert(check); } - // 更新连续旷工天数 updateContinuousAbsent(startLocal, endLocal); } + private boolean isCrossDayShift(WmsAttendanceScheduleVo schedule) { + if (schedule.getShiftStartTime() == null || schedule.getShiftEndTime() == null) { + return false; + } + boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null; + if (hasPeriod2) { + return false; + } + LocalTime start = toLocalTime(schedule.getShiftStartTime()); + LocalTime end = toLocalTime(schedule.getShiftEndTime()); + return end.isBefore(start); + } + private WmsAttendanceRule getActiveRule() { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(WmsAttendanceRule::getDelFlag, 0); @@ -137,12 +148,16 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService return rule; } - private List getRecords(String employeeName, Date workDate) { + private List getRecords(String employeeName, Date workDate, boolean crossDay) { AttendanceRecordsBo recordsBo = new AttendanceRecordsBo(); recordsBo.setEname(employeeName); LocalDate ld = toLocalDate(workDate); recordsBo.setChecktimeStart(toDate(ld.atStartOfDay())); - recordsBo.setChecktimeEnd(toDate(ld.atTime(LocalTime.of(23, 59, 59)))); + if (crossDay) { + recordsBo.setChecktimeEnd(toDate(ld.plusDays(1).atTime(LocalTime.of(23, 59, 59)))); + } else { + recordsBo.setChecktimeEnd(toDate(ld.atTime(LocalTime.of(23, 59, 59)))); + } List voList = attendanceRecordsService.queryList(recordsBo); return voList.stream() .map(v -> { @@ -156,7 +171,8 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService .collect(Collectors.toList()); } - private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule, List records) { + private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule, + List records) { WmsAttendanceCheck check = new WmsAttendanceCheck(); check.setScheduleId(schedule.getScheduleId()); check.setUserId(schedule.getUserId()); @@ -167,6 +183,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService check.setShiftType(schedule.getShiftType()); boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null; + boolean crossDay = isCrossDayShift(schedule); if (records.isEmpty()) { check.setOverallStatus("absent_full"); @@ -174,51 +191,71 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService return check; } - List checkTimes = records.stream() - .map(r -> toLocalDateTime(r.getChecktime()).toLocalTime()) - .collect(Collectors.toList()); - - if (hasPeriod2) { - // 正常班次:按时间中点划分上下午 + if (!hasPeriod2) { + List filtered = filterWindow(records, schedule.getWorkDate(), + schedule.getShiftStartTime(), schedule.getShiftEndTime(), crossDay); + check.setP1StartTime(schedule.getShiftStartTime()); + check.setP1EndTime(schedule.getShiftEndTime()); + checkPeriod(check, rule, 1, filtered, schedule.getShiftStartTime(), schedule.getShiftEndTime()); + } else { LocalTime p1End = toLocalTime(schedule.getShiftEndTime()); LocalTime p2Start = toLocalTime(schedule.getShiftStartTime2()); LocalTime split = LocalTime.of( (p1End.getHour() + p2Start.getHour()) / 2, (p1End.getMinute() + p2Start.getMinute()) / 2); - List period1Times = new ArrayList<>(); - List period2Times = new ArrayList<>(); - for (LocalTime t : checkTimes) { + List p1Records = new ArrayList<>(); + List p2Records = new ArrayList<>(); + for (AttendanceRecords r : records) { + LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime(); if (t.isBefore(split)) { - period1Times.add(t); + p1Records.add(r); } else { - period2Times.add(t); + p2Records.add(r); } } check.setP1StartTime(schedule.getShiftStartTime()); check.setP1EndTime(schedule.getShiftEndTime()); - checkPeriod(check, rule, 1, period1Times, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records); + checkPeriod(check, rule, 1, p1Records, schedule.getShiftStartTime(), schedule.getShiftEndTime()); check.setP2StartTime(schedule.getShiftStartTime2()); check.setP2EndTime(schedule.getShiftEndTime2()); - checkPeriod(check, rule, 2, period2Times, schedule.getShiftStartTime2(), schedule.getShiftEndTime2(), records); - } else { - // 倒班:全天一个时段 - check.setP1StartTime(schedule.getShiftStartTime()); - check.setP1EndTime(schedule.getShiftEndTime()); - checkPeriod(check, rule, 1, checkTimes, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records); + checkPeriod(check, rule, 2, p2Records, schedule.getShiftStartTime2(), schedule.getShiftEndTime2()); } - // 总体判定 calculateOverall(check, rule); return check; } + private List filterWindow(List records, Date workDate, + Date expectedStart, Date expectedEnd, boolean crossDay) { + LocalDate ld = toLocalDate(workDate); + LocalTime st = toLocalTime(expectedStart); + LocalTime et = toLocalTime(expectedEnd); + + LocalDateTime windowStart; + LocalDateTime windowEnd; + + if (crossDay) { + windowStart = LocalDateTime.of(ld, st).minusHours(2); + windowEnd = LocalDateTime.of(ld.plusDays(1), et).plusHours(2); + } else { + windowStart = LocalDateTime.of(ld, st).minusHours(2); + windowEnd = LocalDateTime.of(ld, et).plusHours(2); + } + + return records.stream() + .filter(r -> { + LocalDateTime ct = toLocalDateTime(r.getChecktime()); + return !ct.isBefore(windowStart) && !ct.isAfter(windowEnd); + }) + .collect(Collectors.toList()); + } + private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period, - List periodTimes, Date expectedStart, Date expectedEnd, - List allRecords) { - if (periodTimes.isEmpty()) { + List periodRecords, Date expectedStart, Date expectedEnd) { + if (periodRecords.isEmpty()) { if (period == 1) { check.setP1Status("missed"); } else { @@ -229,21 +266,20 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService LocalTime expStart = toLocalTime(expectedStart); LocalTime expEnd = toLocalTime(expectedEnd); - LocalTime firstCheck = periodTimes.get(0); - LocalTime lastCheck = periodTimes.get(periodTimes.size() - 1); + AttendanceRecords firstRec = periodRecords.get(0); + AttendanceRecords lastRec = periodRecords.get(periodRecords.size() - 1); + LocalTime firstCheck = toLocalDateTime(firstRec.getChecktime()).toLocalTime(); + LocalTime lastCheck = toLocalDateTime(lastRec.getChecktime()).toLocalTime(); int lateMinutes = 0; int earlyMinutes = 0; BigDecimal deduct = BigDecimal.ZERO; String status = "normal"; - // 迟到判定:最早打卡 vs 理论上班时间 if (firstCheck.isAfter(expStart)) { lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes(); if (lateMinutes > rule.getAbsentHalfDay()) { status = "absent_half"; - } else if (lateMinutes > rule.getLateTwo()) { - status = "absent_half"; } else if (lateMinutes > rule.getLateOne()) { status = "late_two"; deduct = deduct.add(rule.getDeductTwo()); @@ -255,21 +291,17 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } } - // 早退判定:最晚打卡 vs 理论下班时间 if (lastCheck.isBefore(expEnd)) { int min = (int) Duration.between(lastCheck, expEnd).toMinutes(); if (min > rule.getAbsentHalfDay()) { status = maxSeverity(status, "absent_half"); earlyMinutes = min; - } else if (min > rule.getLateTwo()) { - status = maxSeverity(status, "absent_half"); - earlyMinutes = min; } else if (min > rule.getLateOne()) { - status = maxSeverity(status, "early_2"); + status = maxSeverity(status, "early_two"); deduct = deduct.add(rule.getDeductTwo()); earlyMinutes = min; } else if (min > rule.getLateWarn()) { - status = maxSeverity(status, "early_1"); + status = maxSeverity(status, "early_one"); deduct = deduct.add(rule.getDeductOne()); earlyMinutes = min; } else { @@ -280,25 +312,16 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } } - // 找到对应的打卡时间 - for (AttendanceRecords r : allRecords) { - LocalTime ct = toLocalDateTime(r.getChecktime()).toLocalTime(); - if (ct.equals(firstCheck)) { - if (period == 1) check.setP1FirstCheck(r.getChecktime()); - else check.setP2FirstCheck(r.getChecktime()); - } - if (ct.equals(lastCheck)) { - if (period == 1) check.setP1LastCheck(r.getChecktime()); - else check.setP2LastCheck(r.getChecktime()); - } - } - if (period == 1) { + check.setP1FirstCheck(firstRec.getChecktime()); + check.setP1LastCheck(lastRec.getChecktime()); check.setP1LateMinutes(lateMinutes); check.setP1EarlyMinutes(earlyMinutes); check.setP1Status(status); check.setP1Deduct(deduct); } else { + check.setP2FirstCheck(firstRec.getChecktime()); + check.setP2LastCheck(lastRec.getChecktime()); check.setP2LateMinutes(lateMinutes); check.setP2EarlyMinutes(earlyMinutes); check.setP2Status(status); @@ -307,7 +330,8 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } private String maxSeverity(String a, String b) { - List severity = java.util.Arrays.asList("normal", "late_warn", "early_warn", "late_one", "early_1", "late_two", "early_2", "absent_half"); + List severity = java.util.Arrays.asList("normal", "late_warn", "early_warn", + "late_one", "early_one", "late_two", "early_two", "absent_half"); int ai = severity.indexOf(a); int bi = severity.indexOf(b); return severity.get(Math.max(ai, bi));