diff --git a/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalController.java b/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalController.java index 6904fc9e3..f2d0374f0 100644 --- a/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalController.java +++ b/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalController.java @@ -71,8 +71,18 @@ public class PerfAppraisalController extends BaseController { @Log(title = "月度绩效考核记录", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody PerfAppraisalBo bo) { - return toAjax(iPerfAppraisalService.insertByBo(bo)); + public R add(@Validated(AddGroup.class) @RequestBody PerfAppraisalBo bo) { + PerfAppraisalBo result = iPerfAppraisalService.insertByBo(bo); + return R.ok(result.getId()); + } + + /** + * 批量新增月度绩效考核记录(含维度明细) + */ + @Log(title = "月度绩效考核记录", businessType = BusinessType.INSERT) + @PostMapping("/batch") + public R addBatch(@RequestBody List list) { + return R.ok(iPerfAppraisalService.batchInsertByBo(list)); } /** diff --git a/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalDetailController.java b/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalDetailController.java index d35b90601..a9d9fe4d2 100644 --- a/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalDetailController.java +++ b/klp-perf/src/main/java/com/klp/perf/controller/PerfAppraisalDetailController.java @@ -85,6 +85,15 @@ public class PerfAppraisalDetailController extends BaseController { return toAjax(iPerfAppraisalDetailService.updateByBo(bo)); } + /** + * 批量修改考核维度评分明细 + */ + @Log(title = "考核维度评分明细", businessType = BusinessType.UPDATE) + @PutMapping("/batch") + public R editBatch(@RequestBody List list) { + return R.ok(iPerfAppraisalDetailService.batchUpdateByBo(list)); + } + /** * 删除考核维度评分明细 * diff --git a/klp-perf/src/main/java/com/klp/perf/controller/PerfSalaryController.java b/klp-perf/src/main/java/com/klp/perf/controller/PerfSalaryController.java index 55d783204..0887fc216 100644 --- a/klp-perf/src/main/java/com/klp/perf/controller/PerfSalaryController.java +++ b/klp-perf/src/main/java/com/klp/perf/controller/PerfSalaryController.java @@ -75,6 +75,15 @@ public class PerfSalaryController extends BaseController { return toAjax(iPerfSalaryService.insertByBo(bo)); } + /** + * 批量新增月度薪资计算记录 + */ + @Log(title = "月度薪资计算记录", businessType = BusinessType.INSERT) + @PostMapping("/batch") + public R addBatch(@RequestBody List list) { + return R.ok(iPerfSalaryService.batchInsertByBo(list)); + } + /** * 修改月度薪资计算记录 */ diff --git a/klp-perf/src/main/java/com/klp/perf/domain/PerfDept.java b/klp-perf/src/main/java/com/klp/perf/domain/PerfDept.java index 105cb377d..61417c52e 100644 --- a/klp-perf/src/main/java/com/klp/perf/domain/PerfDept.java +++ b/klp-perf/src/main/java/com/klp/perf/domain/PerfDept.java @@ -40,6 +40,10 @@ public class PerfDept extends BaseEntity { * 状态(0正常 1停用) */ private String status; + /** + * 产线 + */ + private String productionLine; /** * 删除标志(0=正常 1=已删除) */ diff --git a/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfAppraisalBo.java b/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfAppraisalBo.java index efb7ff490..6f55fa5f7 100644 --- a/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfAppraisalBo.java +++ b/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfAppraisalBo.java @@ -7,6 +7,7 @@ import javax.validation.constraints.*; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import com.fasterxml.jackson.annotation.JsonFormat; /** @@ -95,5 +96,9 @@ public class PerfAppraisalBo extends BaseEntity { */ private String remark; + /** + * 考核维度明细(批量新增时使用) + */ + private List details; } diff --git a/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfDeptBo.java b/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfDeptBo.java index aab7b048c..5a96362f4 100644 --- a/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfDeptBo.java +++ b/klp-perf/src/main/java/com/klp/perf/domain/bo/PerfDeptBo.java @@ -42,6 +42,11 @@ public class PerfDeptBo extends BaseEntity { */ private String status; + /** + * 产线 + */ + private String productionLine; + /** * 备注 */ diff --git a/klp-perf/src/main/java/com/klp/perf/domain/vo/PerfDeptVo.java b/klp-perf/src/main/java/com/klp/perf/domain/vo/PerfDeptVo.java index e0f32c0b4..6c558eda7 100644 --- a/klp-perf/src/main/java/com/klp/perf/domain/vo/PerfDeptVo.java +++ b/klp-perf/src/main/java/com/klp/perf/domain/vo/PerfDeptVo.java @@ -50,6 +50,12 @@ public class PerfDeptVo { @ExcelDictFormat(readConverterExp = "0=正常,1=停用") private String status; + /** + * 产线 + */ + @ExcelProperty(value = "产线") + private String productionLine; + /** * 备注 */ diff --git a/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalDetailService.java b/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalDetailService.java index 90b3b779e..ba7472278 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalDetailService.java +++ b/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalDetailService.java @@ -46,4 +46,8 @@ public interface IPerfAppraisalDetailService { * 校验并批量删除考核维度评分明细信息 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 批量修改考核维度评分明细 + */ + int batchUpdateByBo(List list); } diff --git a/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalService.java b/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalService.java index d6a65f218..fceda789d 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalService.java +++ b/klp-perf/src/main/java/com/klp/perf/service/IPerfAppraisalService.java @@ -35,7 +35,7 @@ public interface IPerfAppraisalService { /** * 新增月度绩效考核记录 */ - Boolean insertByBo(PerfAppraisalBo bo); + PerfAppraisalBo insertByBo(PerfAppraisalBo bo); /** * 修改月度绩效考核记录 @@ -46,4 +46,8 @@ public interface IPerfAppraisalService { * 校验并批量删除月度绩效考核记录信息 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 批量新增月度绩效考核记录(含维度明细) + */ + int batchInsertByBo(List list); } diff --git a/klp-perf/src/main/java/com/klp/perf/service/IPerfSalaryService.java b/klp-perf/src/main/java/com/klp/perf/service/IPerfSalaryService.java index 73bdffddc..27733385f 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/IPerfSalaryService.java +++ b/klp-perf/src/main/java/com/klp/perf/service/IPerfSalaryService.java @@ -46,4 +46,8 @@ public interface IPerfSalaryService { * 校验并批量删除月度薪资计算记录信息 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 批量新增月度薪资计算记录 + */ + int batchInsertByBo(List list); } diff --git a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalDetailServiceImpl.java b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalDetailServiceImpl.java index 943a720f9..4f8821aa4 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalDetailServiceImpl.java +++ b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalDetailServiceImpl.java @@ -102,6 +102,20 @@ public class PerfAppraisalDetailServiceImpl implements IPerfAppraisalDetailServi return baseMapper.updateById(update) > 0; } + /** + * 批量修改考核维度评分明细 + */ + @Override + public int batchUpdateByBo(List list) { + if (list == null || list.isEmpty()) return 0; + int count = 0; + for (PerfAppraisalDetailBo bo : list) { + PerfAppraisalDetail update = BeanUtil.toBean(bo, PerfAppraisalDetail.class); + if (baseMapper.updateById(update) > 0) count++; + } + return count; + } + /** * 保存前的数据校验 */ diff --git a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalServiceImpl.java b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalServiceImpl.java index 563cbbabd..ff79b04f4 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalServiceImpl.java +++ b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfAppraisalServiceImpl.java @@ -13,6 +13,9 @@ import com.klp.perf.domain.bo.PerfAppraisalBo; import com.klp.perf.domain.vo.PerfAppraisalVo; import com.klp.perf.domain.PerfAppraisal; import com.klp.perf.mapper.PerfAppraisalMapper; +import com.klp.perf.mapper.PerfAppraisalDetailMapper; +import com.klp.perf.domain.PerfAppraisalDetail; +import com.klp.perf.domain.bo.PerfAppraisalDetailBo; import com.klp.perf.service.IPerfAppraisalService; import java.util.List; @@ -30,6 +33,7 @@ import java.util.Collection; public class PerfAppraisalServiceImpl implements IPerfAppraisalService { private final PerfAppraisalMapper baseMapper; + private final PerfAppraisalDetailMapper detailMapper; /** * 查询月度绩效考核记录 @@ -81,14 +85,14 @@ public class PerfAppraisalServiceImpl implements IPerfAppraisalService { * 新增月度绩效考核记录 */ @Override - public Boolean insertByBo(PerfAppraisalBo bo) { + public PerfAppraisalBo insertByBo(PerfAppraisalBo bo) { PerfAppraisal add = BeanUtil.toBean(bo, PerfAppraisal.class); validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setId(add.getId()); } - return flag; + return bo; } /** @@ -101,6 +105,35 @@ public class PerfAppraisalServiceImpl implements IPerfAppraisalService { return baseMapper.updateById(update) > 0; } + /** + * 批量新增月度绩效考核记录(含维度明细) + */ + @Override + public int batchInsertByBo(List list) { + if (list == null || list.isEmpty()) { + return 0; + } + int count = 0; + for (PerfAppraisalBo bo : list) { + PerfAppraisal entity = BeanUtil.toBean(bo, PerfAppraisal.class); + validEntityBeforeSave(entity); + if (baseMapper.insert(entity) > 0) { + bo.setId(entity.getId()); + count++; + // 插入维度明细 + List details = bo.getDetails(); + if (details != null && !details.isEmpty()) { + for (PerfAppraisalDetailBo detailBo : details) { + detailBo.setAppraisalId(entity.getId()); + PerfAppraisalDetail detail = BeanUtil.toBean(detailBo, PerfAppraisalDetail.class); + detailMapper.insert(detail); + } + } + } + } + return count; + } + /** * 保存前的数据校验 */ diff --git a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfSalaryServiceImpl.java b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfSalaryServiceImpl.java index 01991b73d..6a4f7ed07 100644 --- a/klp-perf/src/main/java/com/klp/perf/service/impl/PerfSalaryServiceImpl.java +++ b/klp-perf/src/main/java/com/klp/perf/service/impl/PerfSalaryServiceImpl.java @@ -152,6 +152,26 @@ public class PerfSalaryServiceImpl implements IPerfSalaryService { return baseMapper.updateById(update) > 0; } + /** + * 批量新增月度薪资计算记录 + */ + @Override + public int batchInsertByBo(List list) { + if (list == null || list.isEmpty()) { + return 0; + } + int count = 0; + for (PerfSalaryBo bo : list) { + PerfSalary entity = BeanUtil.toBean(bo, PerfSalary.class); + validEntityBeforeSave(entity); + if (baseMapper.insert(entity) > 0) { + bo.setId(entity.getId()); + count++; + } + } + return count; + } + /** * 保存前的数据校验 */ diff --git a/klp-perf/src/main/resources/mapper/perf/PerfDeptMapper.xml b/klp-perf/src/main/resources/mapper/perf/PerfDeptMapper.xml index a359fc9f4..610f19b7e 100644 --- a/klp-perf/src/main/resources/mapper/perf/PerfDeptMapper.xml +++ b/klp-perf/src/main/resources/mapper/perf/PerfDeptMapper.xml @@ -10,6 +10,7 @@ + diff --git a/klp-ui/src/api/perf/appraisal.js b/klp-ui/src/api/perf/appraisal.js index 6957e6573..8e0ba894a 100644 --- a/klp-ui/src/api/perf/appraisal.js +++ b/klp-ui/src/api/perf/appraisal.js @@ -42,3 +42,12 @@ export function delPerfAppraisal(ids) { method: 'delete' }) } + +// 批量新增月度绩效考核记录(含维度明细) +export function batchAddPerfAppraisal(data) { + return request({ + url: '/perf/appraisal/batch', + method: 'post', + data: data + }) +} diff --git a/klp-ui/src/api/perf/appraisalDetail.js b/klp-ui/src/api/perf/appraisalDetail.js index 21e23fc07..e4d8cfcee 100644 --- a/klp-ui/src/api/perf/appraisalDetail.js +++ b/klp-ui/src/api/perf/appraisalDetail.js @@ -42,3 +42,12 @@ export function delPerfAppraisalDetail(ids) { method: 'delete' }) } + +// 批量修改考核维度评分明细 +export function batchUpdatePerfAppraisalDetail(data) { + return request({ + url: '/perf/appraisalDetail/batch', + method: 'put', + data: data + }) +} diff --git a/klp-ui/src/api/perf/salary.js b/klp-ui/src/api/perf/salary.js index e4136c846..db6659d4b 100644 --- a/klp-ui/src/api/perf/salary.js +++ b/klp-ui/src/api/perf/salary.js @@ -42,3 +42,12 @@ export function delPerfSalary(ids) { method: 'delete' }) } + +// 批量新增月度薪资计算记录 +export function batchAddPerfSalary(data) { + return request({ + url: '/perf/salary/batch', + method: 'post', + data: data + }) +} diff --git a/klp-ui/src/utils/perfDataService.js b/klp-ui/src/utils/perfDataService.js new file mode 100644 index 000000000..78d007d4c --- /dev/null +++ b/klp-ui/src/utils/perfDataService.js @@ -0,0 +1,110 @@ +/** + * 绩效考核数据获取服务 + * + * 从 WMS 系统拉取生产、质量、成本数据,用于考核计算引擎。 + */ +import { listLightPendingAction } from '@/api/wms/pendingAction' +import { getCoilStatisticsList } from '@/api/wms/coil' +import { PRODUCTION_LINES } from '@/utils/meta' + +/** + * 根据产线 ID 串获取对应的加工工序 actionTypes(逗号分隔) + * 例如: "1,4" → "501,202,521,206,505,525" + */ +function getProcesses(productionLineIds) { + if (!productionLineIds) return '' + const ids = productionLineIds.split(',').map(s => parseInt(s.trim())) + const processes = [] + ids.forEach(id => { + const pl = PRODUCTION_LINES.find(p => p.id === id) + if (pl && pl.processes) { + pl.processes.split(',').forEach(p => { + const trimmed = p.trim() + if (trimmed && !processes.includes(trimmed)) { + processes.push(trimmed) + } + }) + } + }) + return processes.join(',') +} + +/** + * 获取部门在指定周期的生产数据 + * @param {String} productionLineIds - 部门绑定的产线ID串,如 "1,4" + * @param {String} period - 考核周期 "YYYY-MM" + * @returns {{ totalWeight: Number, abCount: Number, totalCount: Number, avgCostPerTon: Number }} + */ +export async function fetchProductionData(productionLineIds, period) { + const processes = getProcesses(productionLineIds) + if (!processes) { + return { totalWeight: 0, abCount: 0, totalCount: 0, avgCostPerTon: 0 } + } + + // Step 1: 查询指定工序已完成的加工记录 + const pendingRes = await listLightPendingAction({ + actionStatus: 2, + actionTypes: processes + }) + const records = pendingRes.rows || pendingRes.data || [] + const coilIds = [] + records.forEach(r => { + if (r.processedCoilIds) { + const ids = typeof r.processedCoilIds === 'string' + ? r.processedCoilIds.split(',').map(s => s.trim()).filter(Boolean) + : r.processedCoilIds + ids.forEach(id => { if (!coilIds.includes(id)) coilIds.push(id) }) + } + }) + + if (coilIds.length === 0) { + return { totalWeight: 0, abCount: 0, totalCount: 0, avgCostPerTon: 0 } + } + + // Step 2: 根据钢卷 ID 查询统计数据 + // 构造 period 的起止时间 + const [year, month] = period.split('-') + const startTime = `${year}-${month}-01 00:00:00` + const lastDay = new Date(parseInt(year), parseInt(month), 0).getDate() + const endTime = `${year}-${month}-${String(lastDay).padStart(2, '0')} 23:59:59` + + const statsRes = await getCoilStatisticsList({ + coilIds: coilIds.join(','), + startTime, + endTime + }) + const coils = statsRes.rows || statsRes.data || [] + + // Step 3: 汇总产量、质量、成本 + let totalWeight = 0 + let abCount = 0 + let totalCount = coils.length + + coils.forEach(c => { + // 产量:累加钢卷重量 + const weight = parseFloat(c.coilWeight) || parseFloat(c.weight) || 0 + totalWeight += weight + + // 质量:统计 A/B 级 + const quality = (c.quality || c.qualityStatus || c.grade || '').toString().trim().toUpperCase() + if (quality === 'A' || quality === 'B') { + abCount++ + } + }) + + // 成本:从统计数据中获取吨钢成本(如果有) + let avgCostPerTon = 0 + let costCount = 0 + coils.forEach(c => { + const cost = parseFloat(c.costPerTon) || parseFloat(c.unitCost) || 0 + if (cost > 0) { + avgCostPerTon += cost + costCount++ + } + }) + if (costCount > 0) { + avgCostPerTon = avgCostPerTon / costCount + } + + return { totalWeight, abCount, totalCount, avgCostPerTon } +} diff --git a/klp-ui/src/utils/scoreEngine.js b/klp-ui/src/utils/scoreEngine.js new file mode 100644 index 000000000..97d07a9ab --- /dev/null +++ b/klp-ui/src/utils/scoreEngine.js @@ -0,0 +1,120 @@ +/** + * 考核系数计算引擎 + * + * DIMENSION_CALCULATORS: 维度计算器注册表,key 为 dimName,value 为计算器对象 + * calculateDimScore(detail): 根据 detail 计算单维度得分 + * calculateTotal(details): 遍历所有 detail,计算加权总分 + */ + +// ========== 计算器注册表 ========== +// 每个计算器: { calc(actual, target, weight) => score, desc } +// actual — 实际值(数值) +// target — 目标值(数值) +// weight — 权重(如 40 表示 40%) +// 返回值: 维度得分(0 ~ 120 之间) + +const DIMENSION_CALCULATORS = { + '生产效率': { + desc: '实际产量 / 目标产量 × 100 × 权重%', + calc(actual, target, weight) { + // 达成率 = 实际产量 / 目标产量,封顶 120% + const rate = Math.min(1.2, actual / target) + return Math.round(rate * 100 * (weight / 100) * 100) / 100 + } + }, + '产品质量': { + desc: 'A+B 级卷占比 ≥ 目标值 → 满分,否则按比例得分', + calc(actual, target, weight) { + // actual: A+B级卷占比(%), target: 目标占比(%) + if (actual >= target) return weight + if (target === 0) return 0 + return Math.round(weight * (actual / target) * 100) / 100 + } + }, + '成本控制': { + desc: '平均吨钢成本 ≤ 目标值 → 满分,否则按比例扣分', + calc(actual, target, weight) { + // actual: 实际吨钢成本(元), target: 目标吨钢成本(元) + if (actual <= target) return weight + if (actual === 0) return weight + return Math.round(weight * (target / actual) * 100) / 100 + } + }, + '安全管理': { + desc: '一票否决制:事故次数 > 目标值 → 0 分', + calc(actual, target, weight) { + // TODO: 待定义具体逻辑 + return 0 + } + }, + '设备维护': { + desc: '故障停机时间越少得分越高', + calc(actual, target, weight) { + // TODO: 待定义具体逻辑 + return 0 + } + }, + '团队管理': { + desc: '主观打分 / 100 × 权重', + calc(actual, target, weight) { + // TODO: 待定义具体逻辑 + return 0 + } + } +} + +// ========== 辅助函数 ========== + +/** 安全解析为数值,解析失败返回 0 */ +function parseNum(val) { + if (val === null || val === undefined || val === '') return 0 + const n = parseFloat(val) + return isNaN(n) ? 0 : n +} + +/** + * 计算单个维度的得分 + * @param {Object} detail - appraisal_detail 对象,需含 dimName, actualValue, targetValue, weight + * @returns {Number} 维度得分 + */ +export function calculateDimScore(detail) { + const calcEntry = DIMENSION_CALCULATORS[detail.dimName] + if (!calcEntry) return 0 + + const actual = parseNum(detail.actualValue) + const target = parseNum(detail.targetValue) + const weight = parseNum(detail.weight) + + if (target === 0) return 0 + + return calcEntry.calc(actual, target, weight) +} + +/** + * 批量计算所有维度得分及加权总分 + * @param {Array} details - appraisal_detail 对象数组 + * @returns {{ finalScore: Number, details: Array }} 总分及带有计算得分的明细 + */ +export function calculateTotal(details) { + if (!details || !details.length) return { finalScore: 0, details: [] } + + let weightedSum = 0 + let totalBonus = 0 + let totalPenalty = 0 + + const computed = details.map(d => { + const dimScore = calculateDimScore(d) + const bonus = parseNum(d.bonus) || 0 + const penalty = parseNum(d.penalty) || 0 + totalBonus += bonus + totalPenalty += penalty + weightedSum += dimScore + return { ...d, _dimScore: dimScore } + }) + + const finalScore = Math.min(120, weightedSum + totalBonus - totalPenalty) + + return { finalScore: Math.max(0, finalScore), details: computed } +} + +export { DIMENSION_CALCULATORS } diff --git a/klp-ui/src/views/perf/dept/index.vue b/klp-ui/src/views/perf/dept/index.vue index d2d0550e7..02a7ebc0f 100644 --- a/klp-ui/src/views/perf/dept/index.vue +++ b/klp-ui/src/views/perf/dept/index.vue @@ -35,22 +35,43 @@ >