diff --git a/klp-aps/pom.xml b/klp-aps/pom.xml index 250a890f..b7e58540 100644 --- a/klp-aps/pom.xml +++ b/klp-aps/pom.xml @@ -17,5 +17,9 @@ com.klp klp-common + + com.klp + klp-wms + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/1.txt b/klp-aps/src/main/java/com/klp/aps/controller/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarController.java new file mode 100644 index 00000000..b8eefc36 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarController.java @@ -0,0 +1,93 @@ +package com.klp.aps.controller; + +import com.klp.common.annotation.Log; +import com.klp.common.annotation.RepeatSubmit; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.domain.R; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import com.klp.common.enums.BusinessType; +import com.klp.common.utils.poi.ExcelUtil; +import com.klp.aps.domain.bo.ApsCalendarBo; +import com.klp.aps.domain.vo.ApsCalendarVo; +import com.klp.aps.service.ApsCalendarService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +/** + * 工厂日历Controller + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/calendar") +public class ApsCalendarController extends BaseController { + + private final ApsCalendarService apsCalendarService; + + /** + * 查询工厂日历列表 + */ + @GetMapping("/list") + public TableDataInfo list(ApsCalendarBo bo, PageQuery pageQuery) { + return apsCalendarService.queryPageList(bo, pageQuery); + } + + /** + * 导出工厂日历列表 + */ + @Log(title = "工厂日历", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ApsCalendarBo bo, HttpServletResponse response) { + List list = apsCalendarService.queryList(bo); + ExcelUtil.exportExcel(list, "工厂日历", ApsCalendarVo.class, response); + } + + /** + * 获取工厂日历详细信息 + */ + @GetMapping("/{calendarId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long calendarId) { + return R.ok(apsCalendarService.queryById(calendarId)); + } + + /** + * 新增工厂日历 + */ + @Log(title = "工厂日历", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ApsCalendarBo bo) { + return toAjax(apsCalendarService.insertByBo(bo)); + } + + /** + * 修改工厂日历 + */ + @Log(title = "工厂日历", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ApsCalendarBo bo) { + return toAjax(apsCalendarService.updateByBo(bo)); + } + + /** + * 删除工厂日历 + */ + @Log(title = "工厂日历", businessType = BusinessType.DELETE) + @DeleteMapping("/{calendarIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] calendarIds) { + return toAjax(apsCalendarService.deleteWithValidByIds(Arrays.asList(calendarIds), true)); + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarShiftController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarShiftController.java new file mode 100644 index 00000000..03cc8e1d --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsCalendarShiftController.java @@ -0,0 +1,103 @@ +package com.klp.aps.controller; + +import com.klp.common.annotation.Log; +import com.klp.common.annotation.RepeatSubmit; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.domain.R; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import com.klp.common.enums.BusinessType; +import com.klp.common.utils.poi.ExcelUtil; +import com.klp.aps.domain.bo.ApsCalendarShiftBo; +import com.klp.aps.domain.vo.ApsCalendarShiftVo; +import com.klp.aps.service.ApsCalendarShiftService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +/** + * 日历班次配置Controller + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/calendar-shift") +public class ApsCalendarShiftController extends BaseController { + + private final ApsCalendarShiftService apsCalendarShiftService; + + /** + * 查询日历班次配置列表 + */ + @GetMapping("/list") + public TableDataInfo list(ApsCalendarShiftBo bo, PageQuery pageQuery) { + return apsCalendarShiftService.queryPageList(bo, pageQuery); + } + + /** + * 导出日历班次配置列表 + */ + @Log(title = "日历班次配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ApsCalendarShiftBo bo, HttpServletResponse response) { + List list = apsCalendarShiftService.queryList(bo); + ExcelUtil.exportExcel(list, "日历班次配置", ApsCalendarShiftVo.class, response); + } + + /** + * 获取日历班次配置详细信息 + */ + @GetMapping("/{configId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long configId) { + return R.ok(apsCalendarShiftService.queryById(configId)); + } + + /** + * 新增日历班次配置 + */ + @Log(title = "日历班次配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ApsCalendarShiftBo bo) { + return toAjax(apsCalendarShiftService.insertByBo(bo)); + } + + /** + * 修改日历班次配置 + */ + @Log(title = "日历班次配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ApsCalendarShiftBo bo) { + return toAjax(apsCalendarShiftService.updateByBo(bo)); + } + + /** + * 删除日历班次配置 + */ + @Log(title = "日历班次配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] configIds) { + return toAjax(apsCalendarShiftService.deleteWithValidByIds(Arrays.asList(configIds), true)); + } + + /** + * 一键生成全年工作日日历 + */ + @Log(title = "日历班次配置", businessType = BusinessType.INSERT) + @PostMapping("/generate-workday-year/{year}") + public R generateWorkdayYear(@PathVariable Integer year, + @RequestParam(value = "overwriteExisting", required = false, defaultValue = "false") Boolean overwriteExisting) { + return R.ok(apsCalendarShiftService.generateWorkdayForYear(year, overwriteExisting)); + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsGanttController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsGanttController.java new file mode 100644 index 00000000..3db93f99 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsGanttController.java @@ -0,0 +1,45 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsGanttQueryReq; +import com.klp.aps.domain.vo.ApsFactoryCalendarRespVo; +import com.klp.aps.domain.vo.ApsGanttItemVo; +import com.klp.aps.service.ApsGanttService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * APS 甘特图查询(MVP) + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps") +public class ApsGanttController extends BaseController { + + private final ApsGanttService apsGanttService; + + /** + * GET /aps/gantt + * 参数:lineId/queryStart/queryEnd/planId/orderId + */ + @GetMapping("/gantt") + public R> gantt(@Validated ApsGanttQueryReq req) { + return R.ok(apsGanttService.selectGanttItems(req)); + } + + /** + * 工厂日历聚合接口(减轻前端计算压力) + */ + @GetMapping("/factory-calendar") + public R factoryCalendar(@Validated ApsGanttQueryReq req) { + return R.ok(apsGanttService.queryFactoryCalendar(req)); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsLineCapabilityController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsLineCapabilityController.java new file mode 100644 index 00000000..d75e107f --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsLineCapabilityController.java @@ -0,0 +1,93 @@ +package com.klp.aps.controller; + +import com.klp.common.annotation.Log; +import com.klp.common.annotation.RepeatSubmit; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.domain.R; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import com.klp.common.enums.BusinessType; +import com.klp.common.utils.poi.ExcelUtil; +import com.klp.aps.domain.bo.ApsLineCapabilityBo; +import com.klp.aps.domain.vo.ApsLineCapabilityVo; +import com.klp.aps.service.ApsLineCapabilityService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +/** + * 产线能力Controller + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/line-capability") +public class ApsLineCapabilityController extends BaseController { + + private final ApsLineCapabilityService apsLineCapabilityService; + + /** + * 查询产线能力列表 + */ + @GetMapping("/list") + public TableDataInfo list(ApsLineCapabilityBo bo, PageQuery pageQuery) { + return apsLineCapabilityService.queryPageList(bo, pageQuery); + } + + /** + * 导出产线能力列表 + */ + @Log(title = "产线能力", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ApsLineCapabilityBo bo, HttpServletResponse response) { + List list = apsLineCapabilityService.queryList(bo); + ExcelUtil.exportExcel(list, "产线能力", ApsLineCapabilityVo.class, response); + } + + /** + * 获取产线能力详细信息 + */ + @GetMapping("/{capabilityId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long capabilityId) { + return R.ok(apsLineCapabilityService.queryById(capabilityId)); + } + + /** + * 新增产线能力 + */ + @Log(title = "产线能力", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ApsLineCapabilityBo bo) { + return toAjax(apsLineCapabilityService.insertByBo(bo)); + } + + /** + * 修改产线能力 + */ + @Log(title = "产线能力", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ApsLineCapabilityBo bo) { + return toAjax(apsLineCapabilityService.updateByBo(bo)); + } + + /** + * 删除产线能力 + */ + @Log(title = "产线能力", businessType = BusinessType.DELETE) + @DeleteMapping("/{capabilityIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] capabilityIds) { + return toAjax(apsLineCapabilityService.deleteWithValidByIds(Arrays.asList(capabilityIds), true)); + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsLockController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsLockController.java new file mode 100644 index 00000000..3d30e7e0 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsLockController.java @@ -0,0 +1,44 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsLockReq; +import com.klp.aps.service.ApsLockService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * APS 锁定/解锁 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps") +public class ApsLockController extends BaseController { + + private final ApsLockService apsLockService; + + /** + * POST /aps/lock + */ + @PostMapping("/lock") + public R lock(@Validated @RequestBody ApsLockReq req) { + Long lockId = apsLockService.createLock(req, getUsername()); + return R.ok(lockId); + } + + /** + * POST /aps/lock/release/{lockId} + */ + @PostMapping("/lock/release/{lockId}") + public R release(@PathVariable Long lockId) { + apsLockService.releaseLock(lockId, getUsername()); + return R.ok(); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsOperationController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsOperationController.java new file mode 100644 index 00000000..af183f7b --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsOperationController.java @@ -0,0 +1,34 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsRescheduleReq; +import com.klp.aps.service.ApsOperationService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * APS 工序级操作(重排等) + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/operation") +public class ApsOperationController extends BaseController { + + private final ApsOperationService apsOperationService; + + /** + * POST /aps/operation/reschedule + */ + @PostMapping("/reschedule") + public R reschedule(@Validated @RequestBody ApsRescheduleReq req) { + apsOperationService.reschedule(req, getUsername()); + return R.ok(); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsOrderController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsOrderController.java new file mode 100644 index 00000000..fa755a9c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsOrderController.java @@ -0,0 +1,48 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsConvertFromCrmReq; +import com.klp.aps.domain.dto.ApsConvertFromCrmResp; +import com.klp.aps.domain.dto.ApsConvertFromProductReq; +import com.klp.aps.domain.dto.ApsConvertFromProductsReq; +import com.klp.aps.service.ApsPlanService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/order") +public class ApsOrderController extends BaseController { + + private final ApsPlanService apsPlanService; + + @PostMapping("/convert-from-crm") + public R convertFromCrm(@Validated @RequestBody ApsConvertFromCrmReq req) { + Long wmsOrderId = apsPlanService.convertFromCrmOrderWithItems(req.getCrmOrderId(), req.getCrmItemIds(), getUsername()); + return R.ok(new ApsConvertFromCrmResp(wmsOrderId, null)); + } + + @PostMapping("/convert-from-product") + public R convertFromProduct(@Validated @RequestBody ApsConvertFromProductReq req) { + Long wmsOrderId = apsPlanService.convertFromProduct( + req.getProductId(), + req.getQuantity(), + req.getProductName(), + req.getRemark(), + getUsername() + ); + return R.ok(new ApsConvertFromCrmResp(wmsOrderId, null)); + } + + @PostMapping("/convert-from-products") + public R convertFromProducts(@Validated @RequestBody ApsConvertFromProductsReq req) { + Long wmsOrderId = apsPlanService.convertFromProducts(req.getItems(), req.getRemark(), getUsername()); + return R.ok(new ApsConvertFromCrmResp(wmsOrderId, null)); + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsPlanController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsPlanController.java new file mode 100644 index 00000000..8ab12f73 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsPlanController.java @@ -0,0 +1,55 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsAutoScheduleReq; +import com.klp.aps.domain.dto.ApsPlanCreateReq; +import com.klp.aps.service.ApsAutoScheduleService; +import com.klp.aps.service.ApsPlanService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * APS 计划(MVP) + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/plan") +public class ApsPlanController extends BaseController { + + private final ApsPlanService apsPlanService; + private final ApsAutoScheduleService apsAutoScheduleService; + + /** + * POST /aps/plan/create + * 创建计划头与明细,返回 planId + */ + @PostMapping("/create") + public R create(@Validated @RequestBody ApsPlanCreateReq req) { + Long planId = apsPlanService.createPlan(req, getUsername()); + return R.ok(planId); + } + + /** + * POST /aps/plan/auto-schedule + * 自动排程生成 operation + change_log,并更新 plan 状态 + */ + @PostMapping("/auto-schedule") + public R autoSchedule(@Validated @RequestBody ApsAutoScheduleReq req) { + apsAutoScheduleService.autoSchedule(req, getUsername()); + return R.ok(); + } + + /** + * POST /aps/plan/publish/{planId} + * 仅允许已排产(plan.status=1) 的计划发布,状态流转为 2(已发布/生产中) + */ + @PostMapping("/publish/{planId}") + public R publish(@PathVariable Long planId) { + apsPlanService.publishPlan(planId, getUsername()); + return R.ok(); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsScheduleSheetController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsScheduleSheetController.java new file mode 100644 index 00000000..2491ac7a --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsScheduleSheetController.java @@ -0,0 +1,55 @@ +package com.klp.aps.controller; + +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.aps.domain.dto.ApsScheduleSheetQueryReq; +import com.klp.aps.domain.dto.ApsScheduleSheetSupplementSaveReq; +import com.klp.aps.domain.vo.ApsScheduleSheetResp; +import com.klp.aps.service.ApsScheduleSheetService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; + +/** + * 统一排产表(查询 + 导出) + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps") +public class ApsScheduleSheetController extends BaseController { + + private final ApsScheduleSheetService apsScheduleSheetService; + + /** + * GET /aps/schedule-sheet + */ + @GetMapping("/schedule-sheet") + public R query(@Validated ApsScheduleSheetQueryReq req) { + return R.ok(apsScheduleSheetService.query(req)); + } + + /** + * GET /aps/schedule-sheet/export + */ + @GetMapping("/schedule-sheet/export") + public void export(@Validated ApsScheduleSheetQueryReq req, HttpServletResponse response) { + apsScheduleSheetService.exportExcel(req, response); + } + + /** + * POST /aps/schedule-sheet/supplement/save + */ + @PostMapping("/schedule-sheet/supplement/save") + public R saveSupplement(@Validated @RequestBody ApsScheduleSheetSupplementSaveReq req) { + apsScheduleSheetService.saveSupplement(req, getUsername()); + return R.ok(); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/controller/ApsShiftTemplateController.java b/klp-aps/src/main/java/com/klp/aps/controller/ApsShiftTemplateController.java new file mode 100644 index 00000000..07e3a660 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/controller/ApsShiftTemplateController.java @@ -0,0 +1,93 @@ +package com.klp.aps.controller; + +import com.klp.common.annotation.Log; +import com.klp.common.annotation.RepeatSubmit; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.domain.R; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import com.klp.common.enums.BusinessType; +import com.klp.common.utils.poi.ExcelUtil; +import com.klp.aps.domain.bo.ApsShiftTemplateBo; +import com.klp.aps.domain.vo.ApsShiftTemplateVo; +import com.klp.aps.service.ApsShiftTemplateService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +/** + * 班次模板Controller + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aps/shift-template") +public class ApsShiftTemplateController extends BaseController { + + private final ApsShiftTemplateService apsShiftTemplateService; + + /** + * 查询班次模板列表 + */ + @GetMapping("/list") + public TableDataInfo list(ApsShiftTemplateBo bo, PageQuery pageQuery) { + return apsShiftTemplateService.queryPageList(bo, pageQuery); + } + + /** + * 导出班次模板列表 + */ + @Log(title = "班次模板", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ApsShiftTemplateBo bo, HttpServletResponse response) { + List list = apsShiftTemplateService.queryList(bo); + ExcelUtil.exportExcel(list, "班次模板", ApsShiftTemplateVo.class, response); + } + + /** + * 获取班次模板详细信息 + */ + @GetMapping("/{shiftId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long shiftId) { + return R.ok(apsShiftTemplateService.queryById(shiftId)); + } + + /** + * 新增班次模板 + */ + @Log(title = "班次模板", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ApsShiftTemplateBo bo) { + return toAjax(apsShiftTemplateService.insertByBo(bo)); + } + + /** + * 修改班次模板 + */ + @Log(title = "班次模板", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ApsShiftTemplateBo bo) { + return toAjax(apsShiftTemplateService.updateByBo(bo)); + } + + /** + * 删除班次模板 + */ + @Log(title = "班次模板", businessType = BusinessType.DELETE) + @DeleteMapping("/{shiftIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] shiftIds) { + return toAjax(apsShiftTemplateService.deleteWithValidByIds(Arrays.asList(shiftIds), true)); + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/1.txt b/klp-aps/src/main/java/com/klp/aps/domain/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/domain/bo/1.txt b/klp-aps/src/main/java/com/klp/aps/domain/bo/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarBo.java b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarBo.java new file mode 100644 index 00000000..255197e1 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarBo.java @@ -0,0 +1,30 @@ +package com.klp.aps.domain.bo; + +import com.klp.common.core.domain.BaseEntity; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; +import java.time.LocalDate; + +/** + * 工厂日历业务对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ApsCalendarBo extends BaseEntity { + + private Long calendarId; + + @NotNull(message = "日期不能为空", groups = {AddGroup.class, EditGroup.class}) + private LocalDate calendarDate; + + @NotNull(message = "日历类型不能为空", groups = {AddGroup.class, EditGroup.class}) + private Integer calendarType; + + private String factoryCode; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarShiftBo.java b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarShiftBo.java new file mode 100644 index 00000000..755ba16c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsCalendarShiftBo.java @@ -0,0 +1,38 @@ +package com.klp.aps.domain.bo; + +import com.klp.common.core.domain.BaseEntity; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * 日历班次配置业务对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ApsCalendarShiftBo extends BaseEntity { + + private Long configId; + + @NotNull(message = "日期不能为空", groups = {AddGroup.class, EditGroup.class}) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate calendarDate; + + @NotNull(message = "产线ID不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long lineId; + + @NotNull(message = "班次ID不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long shiftId; + + private BigDecimal plannedHours; + + private Integer status; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsLineCapabilityBo.java b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsLineCapabilityBo.java new file mode 100644 index 00000000..fdbb3be3 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsLineCapabilityBo.java @@ -0,0 +1,38 @@ +package com.klp.aps.domain.bo; + +import com.klp.common.core.domain.BaseEntity; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 产线能力业务对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ApsLineCapabilityBo extends BaseEntity { + + private Long capabilityId; + + @NotNull(message = "产线ID不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long lineId; + + private Long productId; + + private Long processId; + + @NotNull(message = "每小时产能不能为空", groups = {AddGroup.class, EditGroup.class}) + private BigDecimal capacityPerHour; + + private Integer setupMinutes; + + private Integer priority; + + private Integer isEnabled; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsShiftTemplateBo.java b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsShiftTemplateBo.java new file mode 100644 index 00000000..7e3ffab4 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/bo/ApsShiftTemplateBo.java @@ -0,0 +1,40 @@ +package com.klp.aps.domain.bo; + +import com.klp.common.core.domain.BaseEntity; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalTime; + +/** + * 班次模板业务对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ApsShiftTemplateBo extends BaseEntity { + + private Long shiftId; + + @NotBlank(message = "班次编码不能为空", groups = {AddGroup.class, EditGroup.class}) + private String shiftCode; + + @NotBlank(message = "班次名称不能为空", groups = {AddGroup.class, EditGroup.class}) + private String shiftName; + + @NotNull(message = "开始时间不能为空", groups = {AddGroup.class, EditGroup.class}) + private LocalTime startTime; + + @NotNull(message = "结束时间不能为空", groups = {AddGroup.class, EditGroup.class}) + private LocalTime endTime; + + private Integer crossDay; + + private BigDecimal efficiencyRate; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsAutoScheduleReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsAutoScheduleReq.java new file mode 100644 index 00000000..89bf39da --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsAutoScheduleReq.java @@ -0,0 +1,22 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 自动排程请求 + */ +@Data +public class ApsAutoScheduleReq { + /** 必填:计划ID */ + @NotNull(message = "planId 不能为空") + private Long planId; + + /** 是否清理旧排程(默认 true,仅删除未锁定记录) */ + private Boolean clearOld; + + /** 备注/预留 */ + private String remark; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmReq.java new file mode 100644 index 00000000..c81a28df --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmReq.java @@ -0,0 +1,18 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +@Data +public class ApsConvertFromCrmReq { + + @NotBlank(message = "crmOrderId 不能为空") + private String crmOrderId; + + /** + * 可选:指定转换的 CRM 明细 itemId 列表;为空则转换该订单全部明细 + */ + private List crmItemIds; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmResp.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmResp.java new file mode 100644 index 00000000..2481c94e --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromCrmResp.java @@ -0,0 +1,12 @@ +package com.klp.aps.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ApsConvertFromCrmResp { + + private Long wmsOrderId; + private String wmsOrderCode; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductReq.java new file mode 100644 index 00000000..965d0508 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductReq.java @@ -0,0 +1,22 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Data +public class ApsConvertFromProductReq { + + @NotNull(message = "productId 不能为空") + private Long productId; + + @NotNull(message = "quantity 不能为空") + @DecimalMin(value = "0.0001", message = "quantity 必须大于 0") + private BigDecimal quantity; + + private String productName; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductsReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductsReq.java new file mode 100644 index 00000000..78fb0b3f --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsConvertFromProductsReq.java @@ -0,0 +1,17 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class ApsConvertFromProductsReq { + + @Valid + @NotEmpty(message = "items 不能为空") + private List items; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsGanttQueryReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsGanttQueryReq.java new file mode 100644 index 00000000..bb6c63ee --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsGanttQueryReq.java @@ -0,0 +1,33 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 甘特查询参数 + */ +@Data +public class ApsGanttQueryReq { + /** 可空:不传则查全部产线 */ + private Long lineId; + + /** 必填:查询开始时间 */ + @NotNull(message = "queryStart 不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime queryStart; + + /** 必填:查询结束时间 */ + @NotNull(message = "queryEnd 不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime queryEnd; + + /** 可空:按计划过滤 */ + private Long planId; + + /** 可空:按订单过滤 */ + private Long orderId; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsLockReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsLockReq.java new file mode 100644 index 00000000..ccbbeb56 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsLockReq.java @@ -0,0 +1,30 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 排产锁定请求 + */ +@Data +public class ApsLockReq { + + /** + * 锁定类型:1计划 2产线时间窗 3工序 + */ + private Integer lockType; + + private Long planId; + + private Long lineId; + + private Long operationId; + + private LocalDateTime lockStartTime; + + private LocalDateTime lockEndTime; + + private String lockReason; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsPlanCreateReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsPlanCreateReq.java new file mode 100644 index 00000000..85819ece --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsPlanCreateReq.java @@ -0,0 +1,38 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 创建计划请求 + */ +@Data +public class ApsPlanCreateReq { + /** 必填:订单ID */ + @NotNull(message = "orderId 不能为空") + private Long orderId; + + /** 可空:排产计划编号,不传则后端生成 */ + private String planCode; + + /** 可空:版本,不传默认 V1 */ + private String version; + + /** 可空:优先级(0低 1中 2高 3VIP) */ + private Integer priority; + + /** 可空:目标产线ID(不传则按默认产线) */ + private Long lineId; + + /** 可空:计划开始时间 */ + private LocalDateTime startDate; + + /** 可空:计划结束时间 */ + private LocalDateTime endDate; + + /** 可空:备注 */ + private String remark; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsRescheduleReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsRescheduleReq.java new file mode 100644 index 00000000..860f6fba --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsRescheduleReq.java @@ -0,0 +1,34 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 单工序重排请求 + */ +@Data +public class ApsRescheduleReq { + + /** 必填:工序排程ID */ + @NotNull(message = "operationId 不能为空") + private Long operationId; + + /** 必填:目标产线ID */ + @NotNull(message = "targetLineId 不能为空") + private Long targetLineId; + + /** 必填:目标开始时间 */ + @NotNull(message = "targetStartTime 不能为空") + private LocalDateTime targetStartTime; + + /** 必填:目标结束时间 */ + @NotNull(message = "targetEndTime 不能为空") + private LocalDateTime targetEndTime; + + /** 必填:重排原因 */ + @NotNull(message = "reason 不能为空") + private String reason; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetQueryReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetQueryReq.java new file mode 100644 index 00000000..87e09e02 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetQueryReq.java @@ -0,0 +1,33 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 统一排产表查询参数(MVP) + */ +@Data +public class ApsScheduleSheetQueryReq { + /** 可空:计划查询关键字(支持 planId 或 planCode) */ + private String planId; + + /** 可空:订单查询关键字(支持 orderId 或 orderCode) */ + private String orderId; + + /** 可空:产线ID(不传查全部产线) */ + private Long lineId; + + /** 必填:查询开始时间 */ + @NotNull(message = "queryStart 不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime queryStart; + + /** 必填:查询结束时间 */ + @NotNull(message = "queryEnd 不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime queryEnd; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetSupplementSaveReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetSupplementSaveReq.java new file mode 100644 index 00000000..bbcfdbbf --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsScheduleSheetSupplementSaveReq.java @@ -0,0 +1,31 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.List; + +@Data +public class ApsScheduleSheetSupplementSaveReq { + + @Valid + @NotEmpty(message = "rows 不能为空") + private List rows; + + @Data + public static class Row { + @NotNull(message = "operationId 不能为空") + private Long operationId; + + private Long rawMaterialId; + private String rawCoilNos; + private BigDecimal rawNetWeight; + private String rawPackaging; + private String rawEdgeReq; + private String rawCoatingType; + private String rawLocation; + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderCreateReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderCreateReq.java new file mode 100644 index 00000000..ce868272 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderCreateReq.java @@ -0,0 +1,20 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ApsWmsOrderCreateReq { + + private String orderCode; + private Long customerId; + private String customerName; + private String salesManager; + private Integer orderStatus; + private String remark; + private BigDecimal taxAmount; + private BigDecimal noTaxAmount; + private String createBy; + private String updateBy; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderDetailCreateReq.java b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderDetailCreateReq.java new file mode 100644 index 00000000..52d6081f --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/dto/ApsWmsOrderDetailCreateReq.java @@ -0,0 +1,21 @@ +package com.klp.aps.domain.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ApsWmsOrderDetailCreateReq { + + private Long orderId; + private Long productId; + private BigDecimal quantity; + private String unit; + private String remark; + private BigDecimal taxPrice; + private BigDecimal noTaxPrice; + private Long groupId; + private Integer delFlag; + private String createBy; + private String updateBy; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarEntity.java new file mode 100644 index 00000000..fb25982a --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarEntity.java @@ -0,0 +1,30 @@ +package com.klp.aps.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.klp.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; + +/** + * 工厂日历实体 wms_calendar + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wms_calendar") +public class ApsCalendarEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "calendar_id", type = IdType.AUTO) + private Long calendarId; + + private LocalDate calendarDate; + + private Integer calendarType; + + private String factoryCode; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarShiftEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarShiftEntity.java new file mode 100644 index 00000000..91607b30 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsCalendarShiftEntity.java @@ -0,0 +1,35 @@ +package com.klp.aps.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.klp.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * 日历班次配置实体 wms_calendar_shift + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wms_calendar_shift") +public class ApsCalendarShiftEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "config_id", type = IdType.AUTO) + private Long configId; + + private LocalDate calendarDate; + + private Long lineId; + + private Long shiftId; + + private BigDecimal plannedHours; + + private Integer status; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsLineCapabilityEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsLineCapabilityEntity.java new file mode 100644 index 00000000..34677ef9 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsLineCapabilityEntity.java @@ -0,0 +1,38 @@ +package com.klp.aps.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.klp.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 产线能力实体 wms_line_capability + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wms_line_capability") +public class ApsLineCapabilityEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "capability_id", type = IdType.AUTO) + private Long capabilityId; + + private Long lineId; + + private Long productId; + + private Long processId; + + private BigDecimal capacityPerHour; + + private Integer setupMinutes; + + private Integer priority; + + private Integer isEnabled; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleChangeLogEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleChangeLogEntity.java new file mode 100644 index 00000000..4d3f13a4 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleChangeLogEntity.java @@ -0,0 +1,18 @@ +package com.klp.aps.domain.entity; + +import lombok.Data; + +/** + * wms_schedule_change_log(APS 自动排程审计) + */ +@Data +public class ApsScheduleChangeLogEntity { + private Long logId; + private Long operationId; + private String changeType; + private String beforeValue; + private String afterValue; + private String changeReason; + private String changeBy; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleLockEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleLockEntity.java new file mode 100644 index 00000000..f51c8797 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleLockEntity.java @@ -0,0 +1,24 @@ +package com.klp.aps.domain.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * wms_schedule_lock + */ +@Data +public class ApsScheduleLockEntity { + private Long lockId; + private Integer lockType; + private Long planId; + private Long lineId; + private Long operationId; + private LocalDateTime lockStartTime; + private LocalDateTime lockEndTime; + private String lockReason; + private Integer status; + private String createBy; + private String updateBy; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleOperationEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleOperationEntity.java new file mode 100644 index 00000000..15aef75d --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsScheduleOperationEntity.java @@ -0,0 +1,32 @@ +package com.klp.aps.domain.entity; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * wms_schedule_operation(APS 自动排程落库) + */ +@Data +public class ApsScheduleOperationEntity { + private Long operationId; + private Long planId; + private Long detailId; + private Long orderId; + private Long orderDetailId; + private Long productId; + private Long processId; + private Long lineId; + private Integer sequenceNo; + private BigDecimal planQty; + private LocalDateTime startTime; + private LocalDateTime endTime; + private Integer setupMinutes; + private Integer status; + private Integer lockedFlag; + private String remark; + private String createBy; + private String updateBy; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanDetailEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanDetailEntity.java new file mode 100644 index 00000000..feed95b5 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanDetailEntity.java @@ -0,0 +1,26 @@ +package com.klp.aps.domain.entity; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * wms_schedule_plan_detail(仅用于 APS MVP 插入) + */ +@Data +public class ApsSchedulePlanDetailEntity { + private Long detailId; + private Long planId; + private Long lineId; + private Long taskId; + private Long productId; + private BigDecimal quantity; + private LocalDate startDate; + private LocalDate endDate; + private String remark; + private Integer delFlag; + private String createBy; + private String updateBy; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanEntity.java new file mode 100644 index 00000000..cd5f738c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsSchedulePlanEntity.java @@ -0,0 +1,25 @@ +package com.klp.aps.domain.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * wms_schedule_plan(仅用于 APS MVP 插入/回填主键) + */ +@Data +public class ApsSchedulePlanEntity { + private Long planId; + private String planCode; + private String version; + private Long orderId; + private Integer status; + private String remark; + private Integer delFlag; + private String createBy; + private String updateBy; + private Integer priority; + private LocalDateTime startDate; + private LocalDateTime endDate; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsShiftTemplateEntity.java b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsShiftTemplateEntity.java new file mode 100644 index 00000000..69ff462c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/entity/ApsShiftTemplateEntity.java @@ -0,0 +1,37 @@ +package com.klp.aps.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.klp.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalTime; + +/** + * 班次模板实体 wms_shift_template + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wms_shift_template") +public class ApsShiftTemplateEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "shift_id", type = IdType.AUTO) + private Long shiftId; + + private String shiftCode; + + private String shiftName; + + private LocalTime startTime; + + private LocalTime endTime; + + private Integer crossDay; + + private BigDecimal efficiencyRate; + + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderItemRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderItemRow.java new file mode 100644 index 00000000..137bf7d6 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderItemRow.java @@ -0,0 +1,16 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ApsCrmOrderItemRow { + + private String itemId; + private String orderId; + private String productType; + private Long productNum; + private BigDecimal contractPrice; + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderRow.java new file mode 100644 index 00000000..81e670b5 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsCrmOrderRow.java @@ -0,0 +1,16 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ApsCrmOrderRow { + + private String orderId; + private String orderCode; + private String customerId; + private String salesman; + private BigDecimal orderAmount; + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineCapabilityRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineCapabilityRow.java new file mode 100644 index 00000000..d3f42b57 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineCapabilityRow.java @@ -0,0 +1,17 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 产线能力(wms_line_capability)最小行 + */ +@Data +public class ApsLineCapabilityRow { + private Long lineId; + private BigDecimal capacityPerHour; + private Integer setupMinutes; + private Integer priority; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineMetaRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineMetaRow.java new file mode 100644 index 00000000..8c7bbc96 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsLineMetaRow.java @@ -0,0 +1,9 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +@Data +public class ApsLineMetaRow { + private Long lineId; + private String lineName; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsOrderDetailRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsOrderDetailRow.java new file mode 100644 index 00000000..849435ff --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsOrderDetailRow.java @@ -0,0 +1,16 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * wms_order_detail 最小取数行 + */ +@Data +public class ApsOrderDetailRow { + private Long detailId; + private Long productId; + private BigDecimal quantity; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsPlanDetailRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsPlanDetailRow.java new file mode 100644 index 00000000..049aec18 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsPlanDetailRow.java @@ -0,0 +1,17 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 计划明细最小行(用于自动排程) + */ +@Data +public class ApsPlanDetailRow { + private Long detailId; + private Long taskId; + private Long lineId; + private Long productId; + private BigDecimal quantity; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductMetaRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductMetaRow.java new file mode 100644 index 00000000..a2c09191 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductMetaRow.java @@ -0,0 +1,11 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +@Data +public class ApsProductMetaRow { + private Long productId; + private String productName; + private String specification; + private String material; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductProcessRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductProcessRow.java new file mode 100644 index 00000000..1165574b --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsProductProcessRow.java @@ -0,0 +1,13 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +/** + * 产品工艺路线(wms_product_processe)最小行 + */ +@Data +public class ApsProductProcessRow { + private Long processId; + private Integer processSequence; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsSchedulePlanRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsSchedulePlanRow.java new file mode 100644 index 00000000..8892f88a --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsSchedulePlanRow.java @@ -0,0 +1,20 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * wms_schedule_plan 最小行(用于自动排程) + */ +@Data +public class ApsSchedulePlanRow { + private Long planId; + private String planCode; + private String version; + private Long orderId; + private Integer status; + private LocalDateTime startDate; + private LocalDateTime endDate; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsShiftSlotRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsShiftSlotRow.java new file mode 100644 index 00000000..1d451408 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsShiftSlotRow.java @@ -0,0 +1,23 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; + +/** + * 日历班次时段(wms_calendar_shift + wms_shift_template) + */ +@Data +public class ApsShiftSlotRow { + private LocalDate calendarDate; + private Long lineId; + private Long shiftId; + private BigDecimal plannedHours; + private LocalTime startTime; + private LocalTime endTime; + private Integer crossDay; + private BigDecimal efficiencyRate; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/row/ApsWmsOrderRow.java b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsWmsOrderRow.java new file mode 100644 index 00000000..28aeb2ef --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/row/ApsWmsOrderRow.java @@ -0,0 +1,10 @@ +package com.klp.aps.domain.row; + +import lombok.Data; + +@Data +public class ApsWmsOrderRow { + + private Long orderId; + private String orderCode; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/1.txt b/klp-aps/src/main/java/com/klp/aps/domain/vo/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarShiftVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarShiftVo.java new file mode 100644 index 00000000..d93b7068 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarShiftVo.java @@ -0,0 +1,46 @@ +package com.klp.aps.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * 日历班次配置视图对象 + */ +@Data +@ExcelIgnoreUnannotated +public class ApsCalendarShiftVo { + + @ExcelProperty(value = "主键") + private Long configId; + + @ExcelProperty(value = "日期") + private LocalDate calendarDate; + + @ExcelProperty(value = "产线ID") + private Long lineId; + + @ExcelProperty(value = "产线名称") + private String lineName; + + @ExcelProperty(value = "班次ID") + private Long shiftId; + + @ExcelProperty(value = "班次名称") + private String shiftName; + + @ExcelProperty(value = "计划工时") + private BigDecimal plannedHours; + + @ExcelProperty(value = "状态") + private Integer status; + + @ExcelProperty(value = "状态名称") + private String statusName; + + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarVo.java new file mode 100644 index 00000000..750d4755 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsCalendarVo.java @@ -0,0 +1,33 @@ +package com.klp.aps.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.time.LocalDate; + +/** + * 工厂日历视图对象 + */ +@Data +@ExcelIgnoreUnannotated +public class ApsCalendarVo { + + @ExcelProperty(value = "主键") + private Long calendarId; + + @ExcelProperty(value = "日期") + private LocalDate calendarDate; + + @ExcelProperty(value = "日历类型") + private Integer calendarType; + + @ExcelProperty(value = "日历类型名称") + private String calendarTypeName; + + @ExcelProperty(value = "工厂编码") + private String factoryCode; + + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarDayVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarDayVo.java new file mode 100644 index 00000000..f79d407c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarDayVo.java @@ -0,0 +1,16 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ApsFactoryCalendarDayVo { + private Integer taskCount = 0; + private Integer shiftCount = 0; + private Integer lineCount = 0; + /** 1=工作日,0=休息日,null=未知 */ + private Integer dayStatus; + private List tasks = new ArrayList<>(); +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarOverviewVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarOverviewVo.java new file mode 100644 index 00000000..64102c29 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarOverviewVo.java @@ -0,0 +1,15 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +@Data +public class ApsFactoryCalendarOverviewVo { + private Integer taskCount; + private Integer activeLineCount; + private Integer totalLineCount; + private Integer shiftCount; + private String totalHours; + private String peakDayText; + private String busiestLineText; + private String busiestLineShiftText; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarRespVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarRespVo.java new file mode 100644 index 00000000..ca3bbec2 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarRespVo.java @@ -0,0 +1,16 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ApsFactoryCalendarRespVo { + private ApsFactoryCalendarOverviewVo overview; + private List dateList; + private Map lineNameMap; + private Map shiftNameMap; + private Map lineDayMap; + private Map lineShiftDayMap; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarTaskLiteVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarTaskLiteVo.java new file mode 100644 index 00000000..1d777947 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsFactoryCalendarTaskLiteVo.java @@ -0,0 +1,9 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +@Data +public class ApsFactoryCalendarTaskLiteVo { + private String label; + private String title; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsGanttItemVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsGanttItemVo.java new file mode 100644 index 00000000..280223e4 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsGanttItemVo.java @@ -0,0 +1,33 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 甘特图条目返回 + */ +@Data +public class ApsGanttItemVo { + private Long operationId; + private Long planId; + private String planCode; + private Long orderId; + private Long orderDetailId; + private Long productId; + private String productName; + private String material; + private String specification; + private Long processId; + private String processName; + private Long lineId; + private String lineName; + private Integer sequenceNo; + private BigDecimal planQty; + private LocalDateTime startTime; + private LocalDateTime endTime; + private Integer status; + private Integer lockedFlag; +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsLineCapabilityVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsLineCapabilityVo.java new file mode 100644 index 00000000..aa67267d --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsLineCapabilityVo.java @@ -0,0 +1,54 @@ +package com.klp.aps.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 产线能力视图对象 + */ +@Data +@ExcelIgnoreUnannotated +public class ApsLineCapabilityVo { + + @ExcelProperty(value = "主键") + private Long capabilityId; + + @ExcelProperty(value = "产线ID") + private Long lineId; + + @ExcelProperty(value = "产线名称") + private String lineName; + + @ExcelProperty(value = "产品ID") + private Long productId; + + @ExcelProperty(value = "产品名称") + private String productName; + + @ExcelProperty(value = "工序ID") + private Long processId; + + @ExcelProperty(value = "工序名称") + private String processName; + + @ExcelProperty(value = "每小时产能") + private BigDecimal capacityPerHour; + + @ExcelProperty(value = "换产时间(分钟)") + private Integer setupMinutes; + + @ExcelProperty(value = "优先级") + private Integer priority; + + @ExcelProperty(value = "是否启用") + private Integer isEnabled; + + @ExcelProperty(value = "是否启用名称") + private String isEnabledName; + + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetResp.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetResp.java new file mode 100644 index 00000000..e46647ee --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetResp.java @@ -0,0 +1,30 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 统一排产表返回 + */ +@Data +public class ApsScheduleSheetResp { + /** 表头元信息(可扩展:计划号/日期/机组等) */ + private Map header; + + /** 明细行 */ + private List rows; + + /** 合计 */ + private Summary summary; + + @Data + public static class Summary { + private Integer totalCount; + private BigDecimal totalPlanQty; + private BigDecimal totalRawNetWeight; + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetRowVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetRowVo.java new file mode 100644 index 00000000..e19aa65b --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsScheduleSheetRowVo.java @@ -0,0 +1,53 @@ +package com.klp.aps.domain.vo; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 统一排产表行(前端统一展示用) + * + * 说明:字段是“统一超集”,不同机组可只展示子集,但表头保持一致。 + */ +@Data +public class ApsScheduleSheetRowVo { + /** 排程主键 */ + private Long operationId; + + /** 产线 */ + private Long lineId; + private String lineName; + + /** 计划/订单 */ + private Long planId; + private String planCode; + private Long orderId; + private String orderCode; + + /** 销售信息 */ + private String salesman; + private String customerName; + + /** 产品/工序 */ + private Long productId; + private String productName; + private String material; + private String specification; + private Long processId; + private String processName; + + /** 排程数量/时间 */ + private BigDecimal planQty; + private String startTime; + private String endTime; + + /** 原料钢卷(来自 wms_material_coil 的展示字段,MVP:按 productId 兜底关联) */ + private Long rawMaterialId; // 回填钢卷ID + private String rawCoilNos; // current_coil_no 逗号分隔 + private String rawLocation; // 实际库位名称 + private BigDecimal rawNetWeight; // 合计净重(吨/或 kg,按库表单位展示) + private String rawPackaging; // packaging_requirement + private String rawEdgeReq; // trimming_requirement + private String rawCoatingType; // coating_type +} + diff --git a/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsShiftTemplateVo.java b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsShiftTemplateVo.java new file mode 100644 index 00000000..e279fdd5 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/domain/vo/ApsShiftTemplateVo.java @@ -0,0 +1,43 @@ +package com.klp.aps.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalTime; + +/** + * 班次模板视图对象 + */ +@Data +@ExcelIgnoreUnannotated +public class ApsShiftTemplateVo { + + @ExcelProperty(value = "主键") + private Long shiftId; + + @ExcelProperty(value = "班次编码") + private String shiftCode; + + @ExcelProperty(value = "班次名称") + private String shiftName; + + @ExcelProperty(value = "开始时间") + private LocalTime startTime; + + @ExcelProperty(value = "结束时间") + private LocalTime endTime; + + @ExcelProperty(value = "跨天标识") + private Integer crossDay; + + @ExcelProperty(value = "跨天标识名称") + private String crossDayName; + + @ExcelProperty(value = "效率系数") + private BigDecimal efficiencyRate; + + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/1.txt b/klp-aps/src/main/java/com/klp/aps/mapper/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsAutoScheduleMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsAutoScheduleMapper.java new file mode 100644 index 00000000..daf0ed8c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsAutoScheduleMapper.java @@ -0,0 +1,64 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.entity.ApsScheduleChangeLogEntity; +import com.klp.aps.domain.entity.ApsScheduleOperationEntity; +import com.klp.aps.domain.row.ApsLineCapabilityRow; +import com.klp.aps.domain.row.ApsLineMetaRow; +import com.klp.aps.domain.row.ApsOrderDetailRow; +import com.klp.aps.domain.row.ApsPlanDetailRow; +import com.klp.aps.domain.row.ApsProductMetaRow; +import com.klp.aps.domain.row.ApsSchedulePlanRow; +import com.klp.aps.domain.row.ApsShiftSlotRow; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.time.LocalDate; +import java.util.List; + +public interface ApsAutoScheduleMapper { + + ApsSchedulePlanRow selectPlanByIdForUpdate(@Param("planId") Long planId); + + List selectOrderDetailsByOrderId(@Param("orderId") Long orderId); + + List selectPlanDetailsByPlanId(@Param("planId") Long planId); + + List selectLineCandidatesByProductAndLine(@Param("productId") Long productId, + @Param("lineId") Long lineId); + + ApsLineMetaRow selectLineMetaById(@Param("lineId") Long lineId); + + ApsProductMetaRow selectProductMetaById(@Param("productId") Long productId); + + List selectAvailableShifts(@Param("lineId") Long lineId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + Long selectPlanDetailId(@Param("planId") Long planId, @Param("taskId") Long taskId); + + int deleteUnlockedOperationsByPlanId(@Param("planId") Long planId); + + /** + * 返回冲突记录的 end_time(若无冲突返回 null) + */ + LocalDateTime selectFirstConflictEndTime(@Param("lineId") Long lineId, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("excludeOperationId") Long excludeOperationId); + + /** + * 返回命中锁定的 lock_end_time(若未命中返回 null) + */ + LocalDateTime selectFirstHitLockEndTime(@Param("lineId") Long lineId, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("planId") Long planId, + @Param("excludeOperationId") Long excludeOperationId); + + int insertOperation(ApsScheduleOperationEntity op); + + int insertChangeLog(ApsScheduleChangeLogEntity log); + + int updatePlanStatus(@Param("planId") Long planId, @Param("status") Integer status, @Param("updateBy") String updateBy); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarMapper.java new file mode 100644 index 00000000..c396b259 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarMapper.java @@ -0,0 +1,27 @@ +package com.klp.aps.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.klp.aps.domain.entity.ApsCalendarEntity; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.util.List; + +/** + * 工厂日历Mapper接口 + */ +public interface ApsCalendarMapper extends BaseMapper { + + /** + * 查询工厂日历列表 + */ + List selectCalendarList(@Param("calendarDate") LocalDate calendarDate, + @Param("calendarType") Integer calendarType, + @Param("factoryCode") String factoryCode); + + /** + * 根据日期和工厂编码查询 + */ + ApsCalendarEntity selectByDateAndFactory(@Param("calendarDate") LocalDate calendarDate, + @Param("factoryCode") String factoryCode); +} diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarShiftMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarShiftMapper.java new file mode 100644 index 00000000..6b5faea8 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsCalendarShiftMapper.java @@ -0,0 +1,23 @@ +package com.klp.aps.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.klp.aps.domain.entity.ApsCalendarShiftEntity; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.util.List; + +/** + * 日历班次配置Mapper接口 + */ +public interface ApsCalendarShiftMapper extends BaseMapper { + + /** + * 根据日期、产线和班次查询 + */ + ApsCalendarShiftEntity selectByDateLineAndShift(@Param("calendarDate") LocalDate calendarDate, + @Param("lineId") Long lineId, + @Param("shiftId") Long shiftId); + + List selectDistinctLineIds(); +} diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsGanttMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsGanttMapper.java new file mode 100644 index 00000000..1866aa79 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsGanttMapper.java @@ -0,0 +1,11 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.dto.ApsGanttQueryReq; +import com.klp.aps.domain.vo.ApsGanttItemVo; + +import java.util.List; + +public interface ApsGanttMapper { + List selectGanttItems(ApsGanttQueryReq req); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsLineCapabilityMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsLineCapabilityMapper.java new file mode 100644 index 00000000..bcfce60e --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsLineCapabilityMapper.java @@ -0,0 +1,18 @@ +package com.klp.aps.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.klp.aps.domain.entity.ApsLineCapabilityEntity; +import org.apache.ibatis.annotations.Param; + +/** + * 产线能力Mapper接口 + */ +public interface ApsLineCapabilityMapper extends BaseMapper { + + /** + * 根据产线、产品、工序查询 + */ + ApsLineCapabilityEntity selectByLineProductAndProcess(@Param("lineId") Long lineId, + @Param("productId") Long productId, + @Param("processId") Long processId); +} diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsLockMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsLockMapper.java new file mode 100644 index 00000000..07b1f836 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsLockMapper.java @@ -0,0 +1,14 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.entity.ApsScheduleLockEntity; +import org.apache.ibatis.annotations.Param; + +public interface ApsLockMapper { + + int insertLock(ApsScheduleLockEntity lock); + + int updateLockStatus(@Param("lockId") Long lockId, + @Param("status") Integer status, + @Param("updateBy") String updateBy); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsOperationMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsOperationMapper.java new file mode 100644 index 00000000..1d16b2c4 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsOperationMapper.java @@ -0,0 +1,15 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.entity.ApsScheduleChangeLogEntity; +import com.klp.aps.domain.entity.ApsScheduleOperationEntity; +import org.apache.ibatis.annotations.Param; + +public interface ApsOperationMapper { + + ApsScheduleOperationEntity selectByIdForUpdate(@Param("operationId") Long operationId); + + int updateSlot(ApsScheduleOperationEntity op); + + int insertChangeLog(ApsScheduleChangeLogEntity log); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsPlanMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsPlanMapper.java new file mode 100644 index 00000000..ed6a3e04 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsPlanMapper.java @@ -0,0 +1,35 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.dto.ApsWmsOrderCreateReq; +import com.klp.aps.domain.dto.ApsWmsOrderDetailCreateReq; +import com.klp.aps.domain.entity.ApsSchedulePlanDetailEntity; +import com.klp.aps.domain.entity.ApsSchedulePlanEntity; +import com.klp.aps.domain.row.ApsCrmOrderItemRow; +import com.klp.aps.domain.row.ApsCrmOrderRow; +import com.klp.aps.domain.row.ApsOrderDetailRow; +import com.klp.aps.domain.row.ApsWmsOrderRow; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApsPlanMapper { + + List selectOrderDetailsByOrderId(@Param("orderId") Long orderId); + + ApsCrmOrderRow selectCrmOrderById(@Param("crmOrderId") String crmOrderId); + + List selectCrmOrderItemsByOrderId(@Param("crmOrderId") String crmOrderId); + + ApsWmsOrderRow selectWmsOrderByCode(@Param("orderCode") String orderCode); + + int insertWmsOrder(ApsWmsOrderCreateReq req); + + int insertWmsOrderDetail(ApsWmsOrderDetailCreateReq req); + + Long selectAnyProductionLineId(); + + int insertSchedulePlan(ApsSchedulePlanEntity plan); + + int insertSchedulePlanDetail(ApsSchedulePlanDetailEntity detail); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsScheduleSheetMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsScheduleSheetMapper.java new file mode 100644 index 00000000..1c647a2b --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsScheduleSheetMapper.java @@ -0,0 +1,18 @@ +package com.klp.aps.mapper; + +import com.klp.aps.domain.dto.ApsScheduleSheetQueryReq; +import com.klp.aps.domain.vo.ApsScheduleSheetRowVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApsScheduleSheetMapper { + List selectSheetRows(ApsScheduleSheetQueryReq req); + + int deleteOperationCoils(@Param("operationId") Long operationId, @Param("updateBy") String updateBy); + + int insertOperationCoil(@Param("operationId") Long operationId, @Param("coilId") Long coilId, @Param("createBy") String createBy); + + int updateOperationSupplement(@Param("operationId") Long operationId, @Param("remarkJson") String remarkJson, @Param("updateBy") String updateBy); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/mapper/ApsShiftTemplateMapper.java b/klp-aps/src/main/java/com/klp/aps/mapper/ApsShiftTemplateMapper.java new file mode 100644 index 00000000..a7f88719 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/mapper/ApsShiftTemplateMapper.java @@ -0,0 +1,16 @@ +package com.klp.aps.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.klp.aps.domain.entity.ApsShiftTemplateEntity; +import org.apache.ibatis.annotations.Param; + +/** + * 班次模板Mapper接口 + */ +public interface ApsShiftTemplateMapper extends BaseMapper { + + /** + * 根据班次编码查询 + */ + ApsShiftTemplateEntity selectByShiftCode(@Param("shiftCode") String shiftCode); +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/1.txt b/klp-aps/src/main/java/com/klp/aps/service/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsAutoScheduleService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsAutoScheduleService.java new file mode 100644 index 00000000..4fc1afcd --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsAutoScheduleService.java @@ -0,0 +1,8 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsAutoScheduleReq; + +public interface ApsAutoScheduleService { + void autoSchedule(ApsAutoScheduleReq req, String operator); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarService.java new file mode 100644 index 00000000..1194428c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarService.java @@ -0,0 +1,45 @@ +package com.klp.aps.service; + +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.page.TableDataInfo; +import com.klp.aps.domain.bo.ApsCalendarBo; +import com.klp.aps.domain.vo.ApsCalendarVo; + +import java.util.Collection; +import java.util.List; + +/** + * 工厂日历Service接口 + */ +public interface ApsCalendarService { + + /** + * 查询工厂日历 + */ + ApsCalendarVo queryById(Long calendarId); + + /** + * 查询工厂日历列表 + */ + TableDataInfo queryPageList(ApsCalendarBo bo, PageQuery pageQuery); + + /** + * 查询工厂日历列表 + */ + List queryList(ApsCalendarBo bo); + + /** + * 新增工厂日历 + */ + Boolean insertByBo(ApsCalendarBo bo); + + /** + * 修改工厂日历 + */ + Boolean updateByBo(ApsCalendarBo bo); + + /** + * 校验并批量删除工厂日历 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarShiftService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarShiftService.java new file mode 100644 index 00000000..71bc7b5b --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsCalendarShiftService.java @@ -0,0 +1,52 @@ +package com.klp.aps.service; + +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.page.TableDataInfo; +import com.klp.aps.domain.bo.ApsCalendarShiftBo; +import com.klp.aps.domain.vo.ApsCalendarShiftVo; + +import java.util.Collection; +import java.util.List; + + +/** + * 日历班次配置Service接口 + */ +public interface ApsCalendarShiftService { + + /** + * 查询日历班次配置 + */ + ApsCalendarShiftVo queryById(Long configId); + + /** + * 查询日历班次配置列表 + */ + TableDataInfo queryPageList(ApsCalendarShiftBo bo, PageQuery pageQuery); + + /** + * 查询日历班次配置列表 + */ + List queryList(ApsCalendarShiftBo bo); + + /** + * 新增日历班次配置 + */ + Boolean insertByBo(ApsCalendarShiftBo bo); + + /** + * 修改日历班次配置 + */ + Boolean updateByBo(ApsCalendarShiftBo bo); + + /** + * 校验并批量删除日历班次配置 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 一键生成指定年份的工作日日历(状态=启用) + * @param overwriteExisting true=覆盖已有记录为工作日;false=仅补齐缺失记录 + */ + Integer generateWorkdayForYear(Integer year, Boolean overwriteExisting); +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsGanttService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsGanttService.java new file mode 100644 index 00000000..2d590913 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsGanttService.java @@ -0,0 +1,14 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsGanttQueryReq; +import com.klp.aps.domain.vo.ApsFactoryCalendarRespVo; +import com.klp.aps.domain.vo.ApsGanttItemVo; + +import java.util.List; + +public interface ApsGanttService { + List selectGanttItems(ApsGanttQueryReq req); + + ApsFactoryCalendarRespVo queryFactoryCalendar(ApsGanttQueryReq req); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsLineCapabilityService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsLineCapabilityService.java new file mode 100644 index 00000000..d80676c3 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsLineCapabilityService.java @@ -0,0 +1,45 @@ +package com.klp.aps.service; + +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.page.TableDataInfo; +import com.klp.aps.domain.bo.ApsLineCapabilityBo; +import com.klp.aps.domain.vo.ApsLineCapabilityVo; + +import java.util.Collection; +import java.util.List; + +/** + * 产线能力Service接口 + */ +public interface ApsLineCapabilityService { + + /** + * 查询产线能力 + */ + ApsLineCapabilityVo queryById(Long capabilityId); + + /** + * 查询产线能力列表 + */ + TableDataInfo queryPageList(ApsLineCapabilityBo bo, PageQuery pageQuery); + + /** + * 查询产线能力列表 + */ + List queryList(ApsLineCapabilityBo bo); + + /** + * 新增产线能力 + */ + Boolean insertByBo(ApsLineCapabilityBo bo); + + /** + * 修改产线能力 + */ + Boolean updateByBo(ApsLineCapabilityBo bo); + + /** + * 校验并批量删除产线能力 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsLockService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsLockService.java new file mode 100644 index 00000000..822714a6 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsLockService.java @@ -0,0 +1,11 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsLockReq; + +public interface ApsLockService { + + Long createLock(ApsLockReq req, String operator); + + void releaseLock(Long lockId, String operator); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsOperationService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsOperationService.java new file mode 100644 index 00000000..c6a8ab10 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsOperationService.java @@ -0,0 +1,8 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsRescheduleReq; + +public interface ApsOperationService { + void reschedule(ApsRescheduleReq req, String operator); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsPlanService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsPlanService.java new file mode 100644 index 00000000..9c36a7c2 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsPlanService.java @@ -0,0 +1,19 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsConvertFromProductReq; +import com.klp.aps.domain.dto.ApsPlanCreateReq; + +public interface ApsPlanService { + Long createPlan(ApsPlanCreateReq req, String operator); + + Long convertFromCrmOrder(String crmOrderId, String operator); + + Long convertFromCrmOrderWithItems(String crmOrderId, java.util.List crmItemIds, String operator); + + Long convertFromProduct(Long productId, java.math.BigDecimal quantity, String productName, String remark, String operator); + + Long convertFromProducts(java.util.List items, String remark, String operator); + + void publishPlan(Long planId, String operator); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsScheduleSheetService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsScheduleSheetService.java new file mode 100644 index 00000000..3ea2bd4c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsScheduleSheetService.java @@ -0,0 +1,16 @@ +package com.klp.aps.service; + +import com.klp.aps.domain.dto.ApsScheduleSheetQueryReq; +import com.klp.aps.domain.dto.ApsScheduleSheetSupplementSaveReq; +import com.klp.aps.domain.vo.ApsScheduleSheetResp; + +import javax.servlet.http.HttpServletResponse; + +public interface ApsScheduleSheetService { + ApsScheduleSheetResp query(ApsScheduleSheetQueryReq req); + + void exportExcel(ApsScheduleSheetQueryReq req, HttpServletResponse response); + + void saveSupplement(ApsScheduleSheetSupplementSaveReq req, String operator); +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/ApsShiftTemplateService.java b/klp-aps/src/main/java/com/klp/aps/service/ApsShiftTemplateService.java new file mode 100644 index 00000000..a4352286 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/ApsShiftTemplateService.java @@ -0,0 +1,45 @@ +package com.klp.aps.service; + +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.page.TableDataInfo; +import com.klp.aps.domain.bo.ApsShiftTemplateBo; +import com.klp.aps.domain.vo.ApsShiftTemplateVo; + +import java.util.Collection; +import java.util.List; + +/** + * 班次模板Service接口 + */ +public interface ApsShiftTemplateService { + + /** + * 查询班次模板 + */ + ApsShiftTemplateVo queryById(Long shiftId); + + /** + * 查询班次模板列表 + */ + TableDataInfo queryPageList(ApsShiftTemplateBo bo, PageQuery pageQuery); + + /** + * 查询班次模板列表 + */ + List queryList(ApsShiftTemplateBo bo); + + /** + * 新增班次模板 + */ + Boolean insertByBo(ApsShiftTemplateBo bo); + + /** + * 修改班次模板 + */ + Boolean updateByBo(ApsShiftTemplateBo bo); + + /** + * 校验并批量删除班次模板 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/1.txt b/klp-aps/src/main/java/com/klp/aps/service/impl/1.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsAutoScheduleServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsAutoScheduleServiceImpl.java new file mode 100644 index 00000000..47ad024c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsAutoScheduleServiceImpl.java @@ -0,0 +1,234 @@ +package com.klp.aps.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.klp.aps.domain.row.*; +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.dto.ApsAutoScheduleReq; +import com.klp.aps.domain.entity.ApsScheduleChangeLogEntity; +import com.klp.aps.domain.entity.ApsScheduleOperationEntity; +import com.klp.aps.domain.row.ApsLineCapabilityRow; +import com.klp.aps.domain.row.ApsSchedulePlanRow; +import com.klp.aps.mapper.ApsAutoScheduleMapper; +import com.klp.aps.service.ApsAutoScheduleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@RequiredArgsConstructor +@Service +public class ApsAutoScheduleServiceImpl implements ApsAutoScheduleService { + + private final ApsAutoScheduleMapper apsAutoScheduleMapper; + + @Transactional(rollbackFor = Exception.class) + @Override + public void autoSchedule(ApsAutoScheduleReq req, String operator) { + boolean clearOld = req.getClearOld() == null || Boolean.TRUE.equals(req.getClearOld()); + + // 1) 锁定计划行,避免并发排程 + ApsSchedulePlanRow plan = apsAutoScheduleMapper.selectPlanByIdForUpdate(req.getPlanId()); + if (plan == null) { + throw new ServiceException("排产计划不存在"); + } + if (plan.getStatus() == null || plan.getStatus() != 0) { + throw new ServiceException("仅新建状态计划可自动排程"); + } + + // 2) 清理旧排程(仅未锁定) + if (clearOld) { + apsAutoScheduleMapper.deleteUnlockedOperationsByPlanId(plan.getPlanId()); + } + + // 3) 读取订单明细(基础校验) + List details = apsAutoScheduleMapper.selectOrderDetailsByOrderId(plan.getOrderId()); + if (details == null || details.isEmpty()) { + throw new ServiceException("订单明细为空,无法排程"); + } + + // 4) 正排:按计划明细(仅到产线粒度,不依赖工艺路线) + List planDetails = apsAutoScheduleMapper.selectPlanDetailsByPlanId(plan.getPlanId()); + if (planDetails == null || planDetails.isEmpty()) { + throw new ServiceException("计划明细为空,无法排程"); + } + + int seqNo = 1; + LocalDateTime cursor = plan.getStartDate() != null ? plan.getStartDate() : LocalDateTime.now(); + for (ApsPlanDetailRow pd : planDetails) { + Long productId = pd.getProductId(); + Long lineId = pd.getLineId(); + BigDecimal qty = safeQty(pd.getQuantity()); + if (lineId == null) { + throw new ServiceException("计划明细未指定产线: detailId=" + pd.getDetailId()); + } + + List caps = apsAutoScheduleMapper.selectLineCandidatesByProductAndLine(productId, lineId); + if (caps == null || caps.isEmpty()) { + ApsLineMetaRow lineMeta = apsAutoScheduleMapper.selectLineMetaById(lineId); + ApsProductMetaRow productMeta = apsAutoScheduleMapper.selectProductMetaById(productId); + String lineName = lineMeta != null && lineMeta.getLineName() != null ? lineMeta.getLineName() : ("产线" + lineId); + String productText = productMeta == null + ? ("产品" + productId) + : (safe(productMeta.getProductName()) + "-" + safe(productMeta.getSpecification()) + "-" + safe(productMeta.getMaterial())); + throw new ServiceException("产线【" + lineName + "】未配置产品产能:" + productText); + } + ApsLineCapabilityRow cap = caps.get(0); + + long runMinutes = calcRunMinutes(qty, cap.getCapacityPerHour()); + int setupMinutes = cap.getSetupMinutes() == null ? 0 : Math.max(0, cap.getSetupMinutes()); + long totalMinutes = runMinutes + setupMinutes; + + ScheduledSlot slot = findEarliestAvailableSlotWithShift(lineId, cursor, totalMinutes, plan.getPlanId()); + + ApsScheduleOperationEntity op = new ApsScheduleOperationEntity(); + op.setPlanId(plan.getPlanId()); + op.setDetailId(pd.getDetailId()); + op.setOrderId(plan.getOrderId()); + op.setOrderDetailId(pd.getTaskId()); + op.setProductId(productId); + op.setProcessId(null); + op.setLineId(lineId); + op.setSequenceNo(seqNo++); + op.setPlanQty(qty); + op.setStartTime(slot.start); + op.setEndTime(slot.end); + op.setSetupMinutes(setupMinutes); + op.setStatus(0); + op.setLockedFlag(0); + op.setCreateBy(operator); + op.setUpdateBy(operator); + apsAutoScheduleMapper.insertOperation(op); + + if (op.getOperationId() == null) { + throw new ServiceException("写入排程结果失败:未回填 operationId"); + } + + ApsScheduleChangeLogEntity log = new ApsScheduleChangeLogEntity(); + log.setOperationId(op.getOperationId()); + log.setChangeType("CREATE"); + log.setBeforeValue(null); + log.setAfterValue(JSON.toJSONString(op)); + log.setChangeReason("AUTO_SCHEDULE"); + log.setChangeBy(operator); + apsAutoScheduleMapper.insertChangeLog(log); + + cursor = slot.end; + } + + // 5) 更新计划状态 -> 已排产 + apsAutoScheduleMapper.updatePlanStatus(plan.getPlanId(), 1, operator); + } + + /** + * 在日历班次 + 冲突/锁定约束下寻找最早可用时间窗(当前实现:仅允许落在单个班次内,跨班次暂不支持) + */ + private ScheduledSlot findEarliestAvailableSlotWithShift(Long lineId, LocalDateTime earliest, long totalMinutes, Long planId) { + if (lineId == null) { + throw new ServiceException("排程失败:lineId 为空"); + } + if (earliest == null) { + earliest = LocalDateTime.now(); + } + if (totalMinutes <= 0) { + totalMinutes = 1; + } + + // 只在 earliest 所在日期起,向后若干天内查找可用班次 + LocalDate startDate = earliest.toLocalDate(); + LocalDate endDate = startDate.plusDays(30); + + java.util.List shifts = + apsAutoScheduleMapper.selectAvailableShifts(lineId, startDate, endDate); + if (shifts == null || shifts.isEmpty()) { + throw new ServiceException("未配置可用班次或当前日期范围无班次:lineId=" + lineId); + } + + for (ApsShiftSlotRow shift : shifts) { + LocalDate d = shift.getCalendarDate(); + LocalTime st = shift.getStartTime(); + LocalTime et = shift.getEndTime(); + if (d == null || st == null || et == null) { + continue; + } + LocalDateTime slotStart = LocalDateTime.of(d, st); + LocalDateTime slotEnd = shift.getCrossDay() != null && shift.getCrossDay() == 1 + ? LocalDateTime.of(d.plusDays(1), et) + : LocalDateTime.of(d, et); + + // 跳过整班结束早于 earliest 的班次 + if (!slotEnd.isAfter(earliest)) { + continue; + } + + // 在班次内,从 max(earliest, 班次开始) 起尝试 + LocalDateTime start = earliest.isAfter(slotStart) ? earliest : slotStart; + // 防止在单个班次内死循环,最多推进 100 次 + for (int i = 0; i < 100; i++) { + LocalDateTime end = start.plusMinutes(totalMinutes); + if (!end.isBefore(slotEnd) && !end.equals(slotEnd)) { + // 超出当前班次结束,换下一个班次 + break; + } + + LocalDateTime conflictEnd = apsAutoScheduleMapper.selectFirstConflictEndTime(lineId, start, end, null); + if (conflictEnd != null && conflictEnd.isAfter(start) && conflictEnd.isBefore(slotEnd)) { + start = conflictEnd; + continue; + } + + LocalDateTime lockEnd = apsAutoScheduleMapper.selectFirstHitLockEndTime(lineId, start, end, planId, null); + if (lockEnd != null && lockEnd.isAfter(start) && lockEnd.isBefore(slotEnd)) { + start = lockEnd; + continue; + } + + return new ScheduledSlot(start, end, lineId, 0); + } + } + + throw new ServiceException("排程失败:在可用班次内无法找到可用时间窗(lineId=" + lineId + ")"); + } + + private static long calcRunMinutes(BigDecimal qty, BigDecimal capacityPerHour) { + BigDecimal q = safeQty(qty); + if (capacityPerHour == null || capacityPerHour.compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("产线能力 capacityPerHour 不合法"); + } + // minutes = ceil(qty / capPerHour * 60) + BigDecimal minutes = q.multiply(BigDecimal.valueOf(60)) + .divide(capacityPerHour, 0, RoundingMode.CEILING); + long m = minutes.longValue(); + return Math.max(1L, m); + } + + private static BigDecimal safeQty(BigDecimal qty) { + BigDecimal q = qty == null ? BigDecimal.ZERO : qty; + // wms_order_detail.quantity 是 decimal(18,0),这里统一按 4 位小数存 op.plan_qty + return q.setScale(4, RoundingMode.HALF_UP); + } + + private static String safe(String s) { + return s == null || s.trim().isEmpty() ? "-" : s.trim(); + } + + private static class ScheduledSlot { + private final LocalDateTime start; + private final LocalDateTime end; + private final Long lineId; + private final int setupMinutes; + + private ScheduledSlot(LocalDateTime start, LocalDateTime end, Long lineId, int setupMinutes) { + this.start = start; + this.end = end; + this.lineId = lineId; + this.setupMinutes = setupMinutes; + } + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarServiceImpl.java new file mode 100644 index 00000000..30ccc2b6 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarServiceImpl.java @@ -0,0 +1,124 @@ +package com.klp.aps.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.exception.ServiceException; +import com.klp.common.utils.StringUtils; +import com.klp.aps.domain.bo.ApsCalendarBo; +import com.klp.aps.domain.entity.ApsCalendarEntity; +import com.klp.aps.domain.vo.ApsCalendarVo; +import com.klp.aps.mapper.ApsCalendarMapper; +import com.klp.aps.service.ApsCalendarService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 工厂日历Service业务层处理 + */ +@RequiredArgsConstructor +@Service +public class ApsCalendarServiceImpl implements ApsCalendarService { + + private final ApsCalendarMapper baseMapper; + + private static final Map CALENDAR_TYPE_MAP = new HashMap() {{ + put(1, "工作日"); + put(2, "周末"); + put(3, "法定假日"); + put(4, "停机日"); + }}; + + @Override + public ApsCalendarVo queryById(Long calendarId) { + ApsCalendarEntity entity = baseMapper.selectById(calendarId); + ApsCalendarVo vo = BeanUtil.toBean(entity, ApsCalendarVo.class); + if (vo != null && vo.getCalendarType() != null) { + vo.setCalendarTypeName(CALENDAR_TYPE_MAP.getOrDefault(vo.getCalendarType(), "未知")); + } + return vo; + } + + @Override + public TableDataInfo queryPageList(ApsCalendarBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectPage(pageQuery.build(), lqw); + List voList = result.getRecords().stream() + .map(entity -> { + ApsCalendarVo vo = BeanUtil.toBean(entity, ApsCalendarVo.class); + if (vo.getCalendarType() != null) { + vo.setCalendarTypeName(CALENDAR_TYPE_MAP.getOrDefault(vo.getCalendarType(), "未知")); + } + return vo; + }) + .collect(Collectors.toList()); + TableDataInfo build = TableDataInfo.build(voList); + build.setTotal(result.getTotal()); + return build; + } + + @Override + public List queryList(ApsCalendarBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List list = baseMapper.selectList(lqw); + return list.stream() + .map(entity -> { + ApsCalendarVo vo = BeanUtil.toBean(entity, ApsCalendarVo.class); + if (vo.getCalendarType() != null) { + vo.setCalendarTypeName(CALENDAR_TYPE_MAP.getOrDefault(vo.getCalendarType(), "未知")); + } + return vo; + }) + .collect(Collectors.toList()); + } + + private LambdaQueryWrapper buildQueryWrapper(ApsCalendarBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getCalendarDate() != null, ApsCalendarEntity::getCalendarDate, bo.getCalendarDate()); + lqw.eq(bo.getCalendarType() != null, ApsCalendarEntity::getCalendarType, bo.getCalendarType()); + lqw.eq(StringUtils.isNotBlank(bo.getFactoryCode()), ApsCalendarEntity::getFactoryCode, bo.getFactoryCode()); + return lqw; + } + + @Override + public Boolean insertByBo(ApsCalendarBo bo) { + ApsCalendarEntity entity = BeanUtil.toBean(bo, ApsCalendarEntity.class); + validEntityBeforeSave(entity); + return baseMapper.insert(entity) > 0; + } + + @Override + public Boolean updateByBo(ApsCalendarBo bo) { + ApsCalendarEntity entity = BeanUtil.toBean(bo, ApsCalendarEntity.class); + validEntityBeforeSave(entity); + return baseMapper.updateById(entity) > 0; + } + + private void validEntityBeforeSave(ApsCalendarEntity entity) { + if (entity.getFactoryCode() == null || entity.getFactoryCode().isEmpty()) { + entity.setFactoryCode("DEFAULT"); + } + ApsCalendarEntity exist = baseMapper.selectByDateAndFactory(entity.getCalendarDate(), entity.getFactoryCode()); + if (exist != null && !exist.getCalendarId().equals(entity.getCalendarId())) { + throw new ServiceException("该日期和工厂编码的组合已存在"); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO: 可以添加业务校验,例如检查是否被排产计划使用 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarShiftServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarShiftServiceImpl.java new file mode 100644 index 00000000..c7d4a197 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsCalendarShiftServiceImpl.java @@ -0,0 +1,229 @@ +package com.klp.aps.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.bo.ApsCalendarShiftBo; +import com.klp.aps.domain.bo.ApsShiftTemplateBo; +import com.klp.aps.domain.entity.ApsCalendarEntity; +import com.klp.aps.domain.entity.ApsCalendarShiftEntity; +import com.klp.aps.domain.vo.ApsCalendarShiftVo; +import com.klp.aps.mapper.ApsCalendarMapper; +import com.klp.aps.mapper.ApsCalendarShiftMapper; +import com.klp.aps.service.ApsCalendarShiftService; +import com.klp.service.IWmsProductionLineService; +import com.klp.domain.vo.WmsProductionLineVo; +import com.klp.aps.service.ApsShiftTemplateService; +import com.klp.aps.domain.vo.ApsShiftTemplateVo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 日历班次配置Service业务层处理 + */ +@RequiredArgsConstructor +@Service +public class ApsCalendarShiftServiceImpl implements ApsCalendarShiftService { + + private final ApsCalendarShiftMapper baseMapper; + private final ApsCalendarMapper calendarMapper; + private final IWmsProductionLineService productionLineService; + private final ApsShiftTemplateService shiftTemplateService; + + private static final Map STATUS_MAP = new HashMap() {{ + put(0, "停用"); + put(1, "启用"); + }}; + + @Override + public ApsCalendarShiftVo queryById(Long configId) { + ApsCalendarShiftEntity entity = baseMapper.selectById(configId); + ApsCalendarShiftVo vo = BeanUtil.toBean(entity, ApsCalendarShiftVo.class); + enrichVo(vo); + return vo; + } + + @Override + public TableDataInfo queryPageList(ApsCalendarShiftBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectPage(pageQuery.build(), lqw); + List voList = result.getRecords().stream() + .map(entity -> { + ApsCalendarShiftVo vo = BeanUtil.toBean(entity, ApsCalendarShiftVo.class); + enrichVo(vo); + return vo; + }) + .collect(Collectors.toList()); + TableDataInfo tableDataInfo = TableDataInfo.build(voList); + tableDataInfo.setTotal(result.getTotal()); + return tableDataInfo; + } + + @Override + public List queryList(ApsCalendarShiftBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List list = baseMapper.selectList(lqw); + return list.stream() + .map(entity -> { + ApsCalendarShiftVo vo = BeanUtil.toBean(entity, ApsCalendarShiftVo.class); + enrichVo(vo); + return vo; + }) + .collect(Collectors.toList()); + } + + private void enrichVo(ApsCalendarShiftVo vo) { + if (vo == null) return; + if (vo.getStatus() != null) { + vo.setStatusName(STATUS_MAP.getOrDefault(vo.getStatus(), "未知")); + } + if (vo.getLineId() != null) { + try { + WmsProductionLineVo line = productionLineService.queryById(vo.getLineId()); + if (line != null) { + vo.setLineName(line.getLineName()); + } + } catch (Exception e) { + // 忽略异常,仅记录 + } + } + if (vo.getShiftId() != null) { + try { + ApsShiftTemplateVo shift = shiftTemplateService.queryById(vo.getShiftId()); + if (shift != null) { + vo.setShiftName(shift.getShiftName()); + } + } catch (Exception e) { + // 忽略异常,仅记录 + } + } + } + + private LambdaQueryWrapper buildQueryWrapper(ApsCalendarShiftBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getCalendarDate() != null, ApsCalendarShiftEntity::getCalendarDate, bo.getCalendarDate()); + lqw.eq(bo.getLineId() != null, ApsCalendarShiftEntity::getLineId, bo.getLineId()); + lqw.eq(bo.getShiftId() != null, ApsCalendarShiftEntity::getShiftId, bo.getShiftId()); + lqw.eq(bo.getStatus() != null, ApsCalendarShiftEntity::getStatus, bo.getStatus()); + return lqw; + } + + @Override + public Boolean insertByBo(ApsCalendarShiftBo bo) { + ApsCalendarShiftEntity entity = BeanUtil.toBean(bo, ApsCalendarShiftEntity.class); + validEntityBeforeSave(entity); + return baseMapper.insert(entity) > 0; + } + + @Override + public Boolean updateByBo(ApsCalendarShiftBo bo) { + ApsCalendarShiftEntity entity = BeanUtil.toBean(bo, ApsCalendarShiftEntity.class); + validEntityBeforeSave(entity); + return baseMapper.updateById(entity) > 0; + } + + private void validEntityBeforeSave(ApsCalendarShiftEntity entity) { + ApsCalendarShiftEntity exist = baseMapper.selectByDateLineAndShift( + entity.getCalendarDate(), entity.getLineId(), entity.getShiftId()); + if (exist != null && !exist.getConfigId().equals(entity.getConfigId())) { + throw new ServiceException("该日期、产线和班次的组合已存在"); + } + if (entity.getStatus() == null) { + entity.setStatus(1); + } + if (entity.getPlannedHours() == null) { + entity.setPlannedHours(java.math.BigDecimal.ZERO); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 预留业务校验扩展点 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + @Override + public Integer generateWorkdayForYear(Integer year, Boolean overwriteExisting) { + if (year == null || year < 2000 || year > 2100) { + throw new ServiceException("年份范围不合法,请输入 2000-2100"); + } + + List lineIds = baseMapper.selectDistinctLineIds(); + if (lineIds == null || lineIds.isEmpty()) { + throw new ServiceException("暂无可用产线,请先维护至少一条日历班次或产线数据"); + } + + List shifts = shiftTemplateService.queryList(new ApsShiftTemplateBo()); + if (shifts == null || shifts.isEmpty()) { + throw new ServiceException("暂无班次模板,请先配置班次模板"); + } + + LocalDate start = LocalDate.of(year, 1, 1); + LocalDate end = LocalDate.of(year, 12, 31); + + int affected = 0; + for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) { + ApsCalendarEntity cal = calendarMapper.selectByDateAndFactory(d, "DEFAULT"); + if (cal == null) { + ApsCalendarEntity c = new ApsCalendarEntity(); + c.setCalendarDate(d); + c.setCalendarType(1); + c.setFactoryCode("DEFAULT"); + c.setRemark("系统批量生成工作日"); + affected += calendarMapper.insert(c); + } else if (Boolean.TRUE.equals(overwriteExisting) && (cal.getCalendarType() == null || cal.getCalendarType() != 1)) { + cal.setCalendarType(1); + affected += calendarMapper.updateById(cal); + } + } + + for (Long lineId : lineIds) { + for (ApsShiftTemplateVo shift : shifts) { + if (shift == null || shift.getShiftId() == null) continue; + for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) { + ApsCalendarShiftEntity exist = baseMapper.selectByDateLineAndShift(d, lineId, shift.getShiftId()); + if (exist == null) { + ApsCalendarShiftEntity entity = new ApsCalendarShiftEntity(); + entity.setCalendarDate(d); + entity.setLineId(lineId); + entity.setShiftId(shift.getShiftId()); + entity.setStatus(1); + entity.setPlannedHours(BigDecimal.valueOf(8)); + entity.setRemark("系统批量生成"); + affected += baseMapper.insert(entity); + } else { + boolean changed = false; + if (Boolean.TRUE.equals(overwriteExisting)) { + if (exist.getStatus() == null || exist.getStatus() != 1) { + exist.setStatus(1); + changed = true; + } + if (exist.getPlannedHours() == null || exist.getPlannedHours().compareTo(BigDecimal.ZERO) <= 0) { + exist.setPlannedHours(BigDecimal.valueOf(8)); + changed = true; + } + } + if (changed) { + affected += baseMapper.updateById(exist); + } + } + } + } + } + return affected; + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsGanttServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsGanttServiceImpl.java new file mode 100644 index 00000000..4826a44c --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsGanttServiceImpl.java @@ -0,0 +1,265 @@ +package com.klp.aps.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.klp.aps.domain.bo.ApsShiftTemplateBo; +import com.klp.aps.domain.dto.ApsGanttQueryReq; +import com.klp.aps.domain.entity.ApsCalendarShiftEntity; +import com.klp.aps.domain.vo.ApsFactoryCalendarDayVo; +import com.klp.aps.domain.vo.ApsFactoryCalendarOverviewVo; +import com.klp.aps.domain.vo.ApsFactoryCalendarRespVo; +import com.klp.aps.domain.vo.ApsFactoryCalendarTaskLiteVo; +import com.klp.aps.domain.vo.ApsGanttItemVo; +import com.klp.aps.domain.vo.ApsShiftTemplateVo; +import com.klp.aps.mapper.ApsCalendarShiftMapper; +import com.klp.aps.mapper.ApsGanttMapper; +import com.klp.aps.service.ApsGanttService; +import com.klp.aps.service.ApsShiftTemplateService; +import com.klp.domain.bo.WmsProductionLineBo; +import com.klp.domain.vo.WmsProductionLineVo; +import com.klp.service.IWmsProductionLineService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@RequiredArgsConstructor +@Service +public class ApsGanttServiceImpl implements ApsGanttService { + + private final ApsGanttMapper apsGanttMapper; + private final ApsCalendarShiftMapper calendarShiftMapper; + private final IWmsProductionLineService productionLineService; + private final ApsShiftTemplateService shiftTemplateService; + + @Override + public List selectGanttItems(ApsGanttQueryReq req) { + return apsGanttMapper.selectGanttItems(req); + } + + @Override + public ApsFactoryCalendarRespVo queryFactoryCalendar(ApsGanttQueryReq req) { + if (req.getQueryStart() == null || req.getQueryEnd() == null) { + ApsFactoryCalendarRespVo empty = new ApsFactoryCalendarRespVo(); + empty.setOverview(new ApsFactoryCalendarOverviewVo()); + empty.setDateList(new ArrayList<>()); + empty.setLineNameMap(new HashMap<>()); + empty.setShiftNameMap(new HashMap<>()); + empty.setLineDayMap(new HashMap<>()); + empty.setLineShiftDayMap(new HashMap<>()); + return empty; + } + + List tasks = apsGanttMapper.selectGanttItems(req); + List lineVos = productionLineService.queryList(new WmsProductionLineBo()); + List shiftVos = shiftTemplateService.queryList(new ApsShiftTemplateBo()); + + Map lineNameMap = new HashMap<>(); + for (WmsProductionLineVo l : lineVos) { + if (l != null && l.getLineId() != null) { + lineNameMap.put(l.getLineId(), l.getLineName() != null ? l.getLineName() : l.getLineCode()); + } + } + + Map shiftNameMap = new HashMap<>(); + for (ApsShiftTemplateVo s : shiftVos) { + if (s != null && s.getShiftId() != null) { + shiftNameMap.put(s.getShiftId(), s.getShiftName()); + } + } + + Map lineDayMap = new HashMap<>(); + Map lineShiftDayMap = new HashMap<>(); + Map> lineDayShiftSetMap = new HashMap<>(); + Map> lineShiftLineSetMap = new HashMap<>(); + + List calendarShiftList = calendarShiftMapper.selectList( + Wrappers.lambdaQuery() + .between(ApsCalendarShiftEntity::getCalendarDate, req.getQueryStart().toLocalDate(), req.getQueryEnd().toLocalDate()) + ); + Map lineStatusAgg = new HashMap<>(); + for (ApsCalendarShiftEntity cs : calendarShiftList) { + if (cs == null || cs.getLineId() == null || cs.getShiftId() == null || cs.getCalendarDate() == null || cs.getStatus() == null) continue; + String d = cs.getCalendarDate().format(DateTimeFormatter.ISO_DATE); + String lineShiftKey = cs.getLineId() + "|" + cs.getShiftId() + "|" + d; + lineShiftDayMap.computeIfAbsent(lineShiftKey, k -> new ApsFactoryCalendarDayVo()).setDayStatus(cs.getStatus()); + + String lineKey = cs.getLineId() + "|" + d; + Integer old = lineStatusAgg.get(lineKey); + int cur = cs.getStatus(); + if (old == null) { + lineStatusAgg.put(lineKey, cur); + } else if (old == 1 || cur == 1) { + lineStatusAgg.put(lineKey, 1); + } else { + lineStatusAgg.put(lineKey, 0); + } + } + + Map dayCountMap = new HashMap<>(); + Map lineCountMap = new HashMap<>(); + Map lineShiftCountMap = new HashMap<>(); + + Set activeLineSet = new HashSet<>(); + BigDecimal totalHours = BigDecimal.ZERO; + int validTaskCount = 0; + + for (ApsGanttItemVo t : tasks) { + if (t == null || t.getStartTime() == null || t.getLineId() == null) continue; + validTaskCount++; + LocalDateTime st = t.getStartTime(); + String dateText = st.toLocalDate().format(DateTimeFormatter.ISO_DATE); + Long lineId = t.getLineId(); + + activeLineSet.add(lineId); + dayCountMap.put(dateText, dayCountMap.getOrDefault(dateText, 0) + 1); + lineCountMap.put(lineId, lineCountMap.getOrDefault(lineId, 0) + 1); + + if (t.getEndTime() != null && t.getEndTime().isAfter(st)) { + totalHours = totalHours.add(BigDecimal.valueOf(Duration.between(st, t.getEndTime()).toMinutes()) + .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP)); + } + + Long shiftId = matchShiftId(shiftVos, st.toLocalTime()); + + String lineDayKey = lineId + "|" + dateText; + ApsFactoryCalendarDayVo lineDayVo = lineDayMap.computeIfAbsent(lineDayKey, k -> new ApsFactoryCalendarDayVo()); + lineDayVo.setTaskCount(lineDayVo.getTaskCount() + 1); + if (shiftId != null) { + Set shiftSet = lineDayShiftSetMap.computeIfAbsent(lineDayKey, k -> new HashSet<>()); + shiftSet.add(shiftId); + lineDayVo.setShiftCount(shiftSet.size()); + } + pushTaskLite(lineDayVo, t, shiftId, shiftNameMap, true); + + if (shiftId != null) { + String lsDayKey = lineId + "|" + shiftId + "|" + dateText; + ApsFactoryCalendarDayVo lsDayVo = lineShiftDayMap.computeIfAbsent(lsDayKey, k -> new ApsFactoryCalendarDayVo()); + lsDayVo.setTaskCount(lsDayVo.getTaskCount() + 1); + Set lineSet = lineShiftLineSetMap.computeIfAbsent(lsDayKey, k -> new HashSet<>()); + lineSet.add(lineId); + lsDayVo.setLineCount(lineSet.size()); + pushTaskLite(lsDayVo, t, shiftId, shiftNameMap, false); + + String aggKey = lineId + "|" + shiftId; + lineShiftCountMap.put(aggKey, lineShiftCountMap.getOrDefault(aggKey, 0) + 1); + } + } + + for (Map.Entry e : lineStatusAgg.entrySet()) { + lineDayMap.computeIfAbsent(e.getKey(), k -> new ApsFactoryCalendarDayVo()).setDayStatus(e.getValue()); + } + + ApsFactoryCalendarOverviewVo overview = new ApsFactoryCalendarOverviewVo(); + overview.setTaskCount(validTaskCount); + overview.setActiveLineCount(activeLineSet.size()); + overview.setTotalLineCount(lineNameMap.size()); + overview.setShiftCount(shiftNameMap.size()); + overview.setTotalHours(totalHours.setScale(1, RoundingMode.HALF_UP).toPlainString()); + + String peakDay = maxKey(dayCountMap); + overview.setPeakDayText(peakDay == null ? "-" : peakDay + " (" + dayCountMap.get(peakDay) + "单)"); + + Long busiestLineId = maxKeyLong(lineCountMap); + overview.setBusiestLineText(busiestLineId == null ? "-" : lineNameMap.getOrDefault(busiestLineId, "产线" + busiestLineId) + " (" + lineCountMap.get(busiestLineId) + "单)"); + + String busiestLs = maxKey(lineShiftCountMap); + if (busiestLs == null) { + overview.setBusiestLineShiftText("-"); + } else { + String[] arr = busiestLs.split("\\|"); + Long lId = Long.valueOf(arr[0]); + Long sId = Long.valueOf(arr[1]); + overview.setBusiestLineShiftText(lineNameMap.getOrDefault(lId, "产线" + lId) + + " · " + shiftNameMap.getOrDefault(sId, "班次" + sId) + + " (" + lineShiftCountMap.get(busiestLs) + "单)"); + } + + ApsFactoryCalendarRespVo resp = new ApsFactoryCalendarRespVo(); + resp.setOverview(overview); + resp.setDateList(buildDateList(req.getQueryStart(), req.getQueryEnd())); + resp.setLineNameMap(lineNameMap); + resp.setShiftNameMap(shiftNameMap); + resp.setLineDayMap(lineDayMap); + resp.setLineShiftDayMap(lineShiftDayMap); + return resp; + } + + private List buildDateList(LocalDateTime startTime, LocalDateTime endTime) { + List arr = new ArrayList<>(); + if (startTime == null || endTime == null) return arr; + LocalDate start = startTime.toLocalDate(); + LocalDate end = endTime.toLocalDate(); + for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) { + arr.add(d.format(DateTimeFormatter.ISO_DATE)); + } + return arr; + } + + private Long matchShiftId(List shifts, LocalTime time) { + if (shifts == null || time == null) return null; + for (ApsShiftTemplateVo s : shifts) { + if (s == null || s.getShiftId() == null || s.getStartTime() == null || s.getEndTime() == null) continue; + boolean cross = s.getCrossDay() != null && s.getCrossDay() == 1; + if (!cross) { + if (!time.isBefore(s.getStartTime()) && !time.isAfter(s.getEndTime())) return s.getShiftId(); + } else { + if (!time.isBefore(s.getStartTime()) || !time.isAfter(s.getEndTime())) return s.getShiftId(); + } + } + return null; + } + + private void pushTaskLite(ApsFactoryCalendarDayVo vo, + ApsGanttItemVo task, + Long shiftId, + Map shiftNameMap, + boolean withShift) { + if (vo.getTasks().size() >= 3) return; + ApsFactoryCalendarTaskLiteVo lite = new ApsFactoryCalendarTaskLiteVo(); + String st = task.getStartTime() == null ? "--:--" : task.getStartTime().toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm")); + String et = task.getEndTime() == null ? "--:--" : task.getEndTime().toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm")); + String p = task.getProductName() == null ? "产品" : task.getProductName(); + String sp = task.getSpecification() == null ? "-" : task.getSpecification(); + String m = task.getMaterial() == null ? "-" : task.getMaterial(); + String shiftText = (withShift && shiftId != null) ? " [" + shiftNameMap.getOrDefault(shiftId, "班次" + shiftId) + "]" : ""; + lite.setLabel(st + " " + p + "-" + sp + "-" + m + shiftText); + lite.setTitle(st + "-" + et + " " + p + "-" + sp + "-" + m); + vo.getTasks().add(lite); + } + + private String maxKey(Map map) { + String key = null; + int max = Integer.MIN_VALUE; + for (Map.Entry e : map.entrySet()) { + if (e.getValue() != null && e.getValue() > max) { + key = e.getKey(); + max = e.getValue(); + } + } + return key; + } + + private Long maxKeyLong(Map map) { + Long key = null; + int max = Integer.MIN_VALUE; + for (Map.Entry e : map.entrySet()) { + if (e.getValue() != null && e.getValue() > max) { + key = e.getKey(); + max = e.getValue(); + } + } + return key; + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLineCapabilityServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLineCapabilityServiceImpl.java new file mode 100644 index 00000000..a911530a --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLineCapabilityServiceImpl.java @@ -0,0 +1,149 @@ +package com.klp.aps.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.bo.ApsLineCapabilityBo; +import com.klp.aps.domain.entity.ApsLineCapabilityEntity; +import com.klp.aps.domain.vo.ApsLineCapabilityVo; +import com.klp.aps.mapper.ApsLineCapabilityMapper; +import com.klp.aps.service.ApsLineCapabilityService; +import com.klp.domain.vo.WmsProductionLineVo; +import com.klp.service.IWmsProductionLineService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 产线能力Service业务层处理 + */ +@RequiredArgsConstructor +@Service +public class ApsLineCapabilityServiceImpl implements ApsLineCapabilityService { + + private final ApsLineCapabilityMapper baseMapper; + private final IWmsProductionLineService productionLineService; + + private static final Map ENABLED_MAP =new HashMap(){{ + put(0, "停用"); + put(1, "启用"); + + + }}; + + @Override + public ApsLineCapabilityVo queryById(Long capabilityId) { + ApsLineCapabilityEntity entity = baseMapper.selectById(capabilityId); + ApsLineCapabilityVo vo = BeanUtil.toBean(entity, ApsLineCapabilityVo.class); + enrichVo(vo); + return vo; + } + + @Override + public TableDataInfo queryPageList(ApsLineCapabilityBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectPage(pageQuery.build(), lqw); + List voList = result.getRecords().stream() + .map(entity -> { + ApsLineCapabilityVo vo = BeanUtil.toBean(entity, ApsLineCapabilityVo.class); + enrichVo(vo); + return vo; + }) + .collect(Collectors.toList()); + TableDataInfo build = TableDataInfo.build(voList); + build.setTotal(result.getTotal()); + return build; + } + + @Override + public List queryList(ApsLineCapabilityBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List list = baseMapper.selectList(lqw); + return list.stream() + .map(entity -> { + ApsLineCapabilityVo vo = BeanUtil.toBean(entity, ApsLineCapabilityVo.class); + enrichVo(vo); + return vo; + }) + .collect(Collectors.toList()); + } + + private void enrichVo(ApsLineCapabilityVo vo) { + if (vo == null) return; + if (vo.getIsEnabled() != null) { + vo.setIsEnabledName(ENABLED_MAP.getOrDefault(vo.getIsEnabled(), "未知")); + } + if (vo.getLineId() != null) { + try { + WmsProductionLineVo line = productionLineService.queryById(vo.getLineId()); + if (line != null) { + vo.setLineName(line.getLineName()); + } + } catch (Exception e) { + // 忽略异常,仅记录 + } + } + // TODO: 如果产品ID和工序ID有对应的服务,可以在这里补充产品名称和工序名称 + } + + private LambdaQueryWrapper buildQueryWrapper(ApsLineCapabilityBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getLineId() != null, ApsLineCapabilityEntity::getLineId, bo.getLineId()); + lqw.eq(bo.getProductId() != null, ApsLineCapabilityEntity::getProductId, bo.getProductId()); + lqw.eq(bo.getProcessId() != null, ApsLineCapabilityEntity::getProcessId, bo.getProcessId()); + lqw.eq(bo.getIsEnabled() != null, ApsLineCapabilityEntity::getIsEnabled, bo.getIsEnabled()); + return lqw; + } + + @Override + public Boolean insertByBo(ApsLineCapabilityBo bo) { + ApsLineCapabilityEntity entity = BeanUtil.toBean(bo, ApsLineCapabilityEntity.class); + validEntityBeforeSave(entity); + return baseMapper.insert(entity) > 0; + } + + @Override + public Boolean updateByBo(ApsLineCapabilityBo bo) { + ApsLineCapabilityEntity entity = BeanUtil.toBean(bo, ApsLineCapabilityEntity.class); + validEntityBeforeSave(entity); + return baseMapper.updateById(entity) > 0; + } + + private void validEntityBeforeSave(ApsLineCapabilityEntity entity) { + // 如果产品ID和工序ID都为空,表示通用能力,允许重复 + // 如果产品ID或工序ID不为空,检查是否已存在 + if (entity.getProductId() != null || entity.getProcessId() != null) { + ApsLineCapabilityEntity exist = baseMapper.selectByLineProductAndProcess( + entity.getLineId(), entity.getProductId(), entity.getProcessId()); + if (exist != null && !exist.getCapabilityId().equals(entity.getCapabilityId())) { + throw new ServiceException("该产线、产品和工序的组合已存在"); + } + } + if (entity.getIsEnabled() == null) { + entity.setIsEnabled(1); + } + if (entity.getPriority() == null) { + entity.setPriority(999); + } + if (entity.getSetupMinutes() == null) { + entity.setSetupMinutes(0); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO: 可以添加业务校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLockServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLockServiceImpl.java new file mode 100644 index 00000000..c18a8dcf --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsLockServiceImpl.java @@ -0,0 +1,63 @@ +package com.klp.aps.service.impl; + +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.dto.ApsLockReq; +import com.klp.aps.domain.entity.ApsScheduleLockEntity; +import com.klp.aps.mapper.ApsLockMapper; +import com.klp.aps.service.ApsLockService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class ApsLockServiceImpl implements ApsLockService { + + private final ApsLockMapper apsLockMapper; + + @Transactional(rollbackFor = Exception.class) + @Override + public Long createLock(ApsLockReq req, String operator) { + if (req.getLockType() == null) { + throw new ServiceException("lockType 不能为空"); + } + if (req.getLockType() == 2 && (req.getLineId() == null || req.getLockStartTime() == null || req.getLockEndTime() == null)) { + throw new ServiceException("产线时间窗锁定必须指定 lineId / lockStartTime / lockEndTime"); + } + if (req.getLockType() == 3 && req.getOperationId() == null) { + throw new ServiceException("工序锁定必须指定 operationId"); + } + if (req.getLockStartTime() != null && req.getLockEndTime() != null + && !req.getLockStartTime().isBefore(req.getLockEndTime())) { + throw new ServiceException("lockStartTime 必须早于 lockEndTime"); + } + + ApsScheduleLockEntity lock = new ApsScheduleLockEntity(); + lock.setLockType(req.getLockType()); + lock.setPlanId(req.getPlanId()); + lock.setLineId(req.getLineId()); + lock.setOperationId(req.getOperationId()); + lock.setLockStartTime(req.getLockStartTime()); + lock.setLockEndTime(req.getLockEndTime()); + lock.setLockReason(req.getLockReason()); + lock.setStatus(1); + lock.setCreateBy(operator); + lock.setUpdateBy(operator); + + apsLockMapper.insertLock(lock); + if (lock.getLockId() == null) { + throw new ServiceException("创建锁定记录失败:未回填 lockId"); + } + return lock.getLockId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void releaseLock(Long lockId, String operator) { + int rows = apsLockMapper.updateLockStatus(lockId, 0, operator); + if (rows == 0) { + throw new ServiceException("锁定记录不存在或已失效"); + } + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsOperationServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsOperationServiceImpl.java new file mode 100644 index 00000000..552f57ef --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsOperationServiceImpl.java @@ -0,0 +1,75 @@ +package com.klp.aps.service.impl; + +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.dto.ApsRescheduleReq; +import com.klp.aps.domain.entity.ApsScheduleChangeLogEntity; +import com.klp.aps.domain.entity.ApsScheduleOperationEntity; +import com.klp.aps.mapper.ApsAutoScheduleMapper; +import com.klp.aps.mapper.ApsOperationMapper; +import com.klp.aps.service.ApsOperationService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@RequiredArgsConstructor +@Service +public class ApsOperationServiceImpl implements ApsOperationService { + + private final ApsOperationMapper apsOperationMapper; + private final ApsAutoScheduleMapper apsAutoScheduleMapper; + + @Transactional(rollbackFor = Exception.class) + @Override + public void reschedule(ApsRescheduleReq req, String operator) { + ApsScheduleOperationEntity origin = apsOperationMapper.selectByIdForUpdate(req.getOperationId()); + if (origin == null) { + throw new ServiceException("工序排程不存在"); + } + if (origin.getLockedFlag() != null && origin.getLockedFlag() == 1) { + throw new ServiceException("工序已锁定,禁止重排"); + } + + LocalDateTime start = req.getTargetStartTime(); + LocalDateTime end = req.getTargetEndTime(); + if (start.isAfter(end) || start.equals(end)) { + throw new ServiceException("目标开始时间必须早于结束时间"); + } + + // 冲突 & 锁定校验(排除自身) + Long lineId = req.getTargetLineId(); + Long excludeId = origin.getOperationId(); + + LocalDateTime conflictEnd = apsAutoScheduleMapper.selectFirstConflictEndTime(lineId, start, end, excludeId); + if (conflictEnd != null) { + throw new ServiceException("目标时间窗存在产线冲突,无法重排"); + } + LocalDateTime lockEnd = apsAutoScheduleMapper.selectFirstHitLockEndTime(lineId, start, end, origin.getPlanId(), excludeId); + if (lockEnd != null) { + throw new ServiceException("目标时间窗命中锁定区间,无法重排"); + } + + // 备份 before + String beforeJson = com.alibaba.fastjson2.JSON.toJSONString(origin); + + origin.setLineId(lineId); + origin.setStartTime(start); + origin.setEndTime(end); + origin.setUpdateBy(operator); + apsOperationMapper.updateSlot(origin); + + // after + String afterJson = com.alibaba.fastjson2.JSON.toJSONString(origin); + + ApsScheduleChangeLogEntity log = new ApsScheduleChangeLogEntity(); + log.setOperationId(origin.getOperationId()); + log.setChangeType("RESCHEDULE"); + log.setBeforeValue(beforeJson); + log.setAfterValue(afterJson); + log.setChangeReason(req.getReason()); + log.setChangeBy(operator); + apsOperationMapper.insertChangeLog(log); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsPlanServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsPlanServiceImpl.java new file mode 100644 index 00000000..e6b89e42 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsPlanServiceImpl.java @@ -0,0 +1,299 @@ +package com.klp.aps.service.impl; + +import com.klp.aps.domain.dto.ApsConvertFromProductReq; +import com.klp.common.exception.ServiceException; +import com.klp.common.utils.StringUtils; +import com.klp.aps.domain.dto.ApsPlanCreateReq; +import com.klp.aps.domain.dto.ApsWmsOrderCreateReq; +import com.klp.aps.domain.dto.ApsWmsOrderDetailCreateReq; +import com.klp.aps.domain.entity.ApsSchedulePlanDetailEntity; +import com.klp.aps.domain.entity.ApsSchedulePlanEntity; +import com.klp.aps.domain.row.ApsCrmOrderItemRow; +import com.klp.aps.domain.row.ApsCrmOrderRow; +import com.klp.aps.domain.row.ApsOrderDetailRow; +import com.klp.aps.domain.row.ApsSchedulePlanRow; +import com.klp.aps.domain.row.ApsWmsOrderRow; +import com.klp.aps.mapper.ApsAutoScheduleMapper; +import com.klp.aps.mapper.ApsPlanMapper; +import com.klp.aps.service.ApsPlanService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +@RequiredArgsConstructor +@Service +public class ApsPlanServiceImpl implements ApsPlanService { + + private final ApsPlanMapper apsPlanMapper; + private final ApsAutoScheduleMapper apsAutoScheduleMapper; + + private static final DateTimeFormatter PLAN_CODE_TIME = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + + @Transactional(rollbackFor = Exception.class) + @Override + public Long convertFromCrmOrder(String crmOrderId, String operator) { + return convertFromCrmOrderWithItems(crmOrderId, null, operator); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long convertFromCrmOrderWithItems(String crmOrderId, List crmItemIds, String operator) { + ApsCrmOrderRow crmOrder = apsPlanMapper.selectCrmOrderById(crmOrderId); + if (crmOrder == null) { + throw new ServiceException("CRM 订单不存在"); + } + List items = apsPlanMapper.selectCrmOrderItemsByOrderId(crmOrderId); + if (items == null || items.isEmpty()) { + throw new ServiceException("CRM 订单明细为空,无法转换"); + } + if (crmItemIds != null && !crmItemIds.isEmpty()) { + items = items.stream().filter(i -> crmItemIds.contains(i.getItemId())).collect(java.util.stream.Collectors.toList()); + if (items.isEmpty()) { + throw new ServiceException("所选 CRM 明细为空,无法转换"); + } + } + + String wmsOrderCode = "CRM-" + crmOrder.getOrderCode(); + ApsWmsOrderRow existed = apsPlanMapper.selectWmsOrderByCode(wmsOrderCode); + if (existed != null && existed.getOrderId() != null) { + return existed.getOrderId(); + } + + Long customerId = null; + try { + if (StringUtils.isNotBlank(crmOrder.getCustomerId())) { + customerId = Long.valueOf(crmOrder.getCustomerId()); + } + } catch (Exception ignored) { + } + + ApsWmsOrderCreateReq orderCreateReq = new ApsWmsOrderCreateReq(); + orderCreateReq.setOrderCode(wmsOrderCode); + orderCreateReq.setCustomerId(customerId); + orderCreateReq.setCustomerName(crmOrder.getCustomerId()); + orderCreateReq.setSalesManager(crmOrder.getSalesman()); + orderCreateReq.setOrderStatus(0); + orderCreateReq.setRemark("由 CRM 订单 " + crmOrder.getOrderCode() + " 转换"); + orderCreateReq.setTaxAmount(crmOrder.getOrderAmount()); + orderCreateReq.setNoTaxAmount(crmOrder.getOrderAmount()); + orderCreateReq.setCreateBy(operator); + orderCreateReq.setUpdateBy(operator); + apsPlanMapper.insertWmsOrder(orderCreateReq); + + ApsWmsOrderRow created = apsPlanMapper.selectWmsOrderByCode(wmsOrderCode); + if (created == null || created.getOrderId() == null) { + throw new ServiceException("转换失败:WMS 订单未生成"); + } + + for (ApsCrmOrderItemRow item : items) { + Long productId = parseLong(item.getProductType()); + if (productId == null) { + continue; + } + BigDecimal qty = BigDecimal.valueOf(item.getProductNum() == null ? 0 : item.getProductNum()); + BigDecimal taxPrice = item.getContractPrice() == null ? BigDecimal.ZERO : item.getContractPrice(); + ApsWmsOrderDetailCreateReq detailCreateReq = new ApsWmsOrderDetailCreateReq(); + detailCreateReq.setOrderId(created.getOrderId()); + detailCreateReq.setProductId(productId); + detailCreateReq.setQuantity(qty); + detailCreateReq.setUnit("件"); + detailCreateReq.setRemark(item.getRemark()); + detailCreateReq.setTaxPrice(taxPrice); + detailCreateReq.setNoTaxPrice(taxPrice); + detailCreateReq.setGroupId(null); + detailCreateReq.setDelFlag(0); + detailCreateReq.setCreateBy(operator); + detailCreateReq.setUpdateBy(operator); + apsPlanMapper.insertWmsOrderDetail(detailCreateReq); + } + + return created.getOrderId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long createPlan(ApsPlanCreateReq req, String operator) { + List orderDetails = apsPlanMapper.selectOrderDetailsByOrderId(req.getOrderId()); + if (orderDetails == null || orderDetails.isEmpty()) { + throw new ServiceException("订单明细为空,无法创建排产计划(orderId=" + req.getOrderId() + ")"); + } + + Long selectedLineId = req.getLineId(); + if (selectedLineId == null) { + selectedLineId = apsPlanMapper.selectAnyProductionLineId(); + } + if (selectedLineId == null) { + throw new ServiceException("未找到可用产线(wms_production_line 为空),无法创建排产明细"); + } + + String planCode = req.getPlanCode(); + if (StringUtils.isBlank(planCode)) { + planCode = genPlanCode(); + } + String version = StringUtils.isBlank(req.getVersion()) ? "V1" : req.getVersion().trim(); + + ApsSchedulePlanEntity plan = new ApsSchedulePlanEntity(); + plan.setPlanCode(planCode); + plan.setVersion(version); + plan.setOrderId(req.getOrderId()); + plan.setStatus(0); + plan.setRemark(req.getRemark()); + plan.setDelFlag(0); + plan.setPriority(req.getPriority()); + plan.setStartDate(req.getStartDate()); + plan.setEndDate(req.getEndDate()); + plan.setCreateBy(operator); + plan.setUpdateBy(operator); + + apsPlanMapper.insertSchedulePlan(plan); + if (plan.getPlanId() == null) { + throw new ServiceException("创建排产计划失败:未回填 planId"); + } + + LocalDate detailStart = resolveDetailStart(req.getStartDate()); + LocalDate detailEnd = resolveDetailEnd(req.getEndDate(), detailStart); + + for (ApsOrderDetailRow od : orderDetails) { + ApsSchedulePlanDetailEntity detail = new ApsSchedulePlanDetailEntity(); + detail.setPlanId(plan.getPlanId()); + detail.setLineId(selectedLineId); + + // wms_schedule_plan_detail.task_id 当前无外键约束,MVP 先用订单明细ID占位,后续接入生产任务时再替换 + detail.setTaskId(od.getDetailId()); + detail.setProductId(od.getProductId()); + detail.setQuantity(scale4(od.getQuantity())); + detail.setStartDate(detailStart); + detail.setEndDate(detailEnd); + detail.setRemark(req.getRemark()); + detail.setDelFlag(0); + detail.setCreateBy(operator); + detail.setUpdateBy(operator); + + apsPlanMapper.insertSchedulePlanDetail(detail); + } + + return plan.getPlanId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long convertFromProduct(Long productId, BigDecimal quantity, String productName, String remark, String operator) { + ApsConvertFromProductReq one = new ApsConvertFromProductReq(); + one.setProductId(productId); + one.setQuantity(quantity); + one.setProductName(productName); + one.setRemark(remark); + return convertFromProducts(java.util.Collections.singletonList(one), remark, operator); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long convertFromProducts(List items, String remark, String operator) { + if (items == null || items.isEmpty()) { + throw new ServiceException("items 不能为空"); + } + + String code = "PRD-" + LocalDateTime.now().format(PLAN_CODE_TIME) + ThreadLocalRandom.current().nextInt(100, 999); + ApsWmsOrderCreateReq orderCreateReq = new ApsWmsOrderCreateReq(); + orderCreateReq.setOrderCode(code); + orderCreateReq.setCustomerId(null); + orderCreateReq.setCustomerName("产品直排"); + orderCreateReq.setSalesManager(operator); + orderCreateReq.setOrderStatus(0); + orderCreateReq.setRemark(StringUtils.isBlank(remark) ? "APS_PRODUCT_DIRECT_BATCH" : remark); + orderCreateReq.setTaxAmount(BigDecimal.ZERO); + orderCreateReq.setNoTaxAmount(BigDecimal.ZERO); + orderCreateReq.setCreateBy(operator); + orderCreateReq.setUpdateBy(operator); + apsPlanMapper.insertWmsOrder(orderCreateReq); + + ApsWmsOrderRow created = apsPlanMapper.selectWmsOrderByCode(code); + if (created == null || created.getOrderId() == null) { + throw new ServiceException("创建库存订单失败"); + } + + for (ApsConvertFromProductReq item : items) { + if (item == null || item.getProductId() == null) { + continue; + } + if (item.getQuantity() == null || item.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { + continue; + } + ApsWmsOrderDetailCreateReq detailCreateReq = new ApsWmsOrderDetailCreateReq(); + detailCreateReq.setOrderId(created.getOrderId()); + detailCreateReq.setProductId(item.getProductId()); + detailCreateReq.setQuantity(scale4(item.getQuantity())); + detailCreateReq.setUnit("件"); + detailCreateReq.setRemark(StringUtils.isBlank(item.getProductName()) ? "产品直排" : ("产品直排:" + item.getProductName())); + detailCreateReq.setTaxPrice(BigDecimal.ZERO); + detailCreateReq.setNoTaxPrice(BigDecimal.ZERO); + detailCreateReq.setGroupId(null); + detailCreateReq.setDelFlag(0); + detailCreateReq.setCreateBy(operator); + detailCreateReq.setUpdateBy(operator); + apsPlanMapper.insertWmsOrderDetail(detailCreateReq); + } + + return created.getOrderId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void publishPlan(Long planId, String operator) { + ApsSchedulePlanRow plan = apsAutoScheduleMapper.selectPlanByIdForUpdate(planId); + if (plan == null) { + throw new ServiceException("排产计划不存在"); + } + Integer status = plan.getStatus(); + if (status == null || status != 1) { + throw new ServiceException("仅已排产状态的计划可发布"); + } + // 约定:0=新建 1=已排产 2=已发布/生产中 + apsAutoScheduleMapper.updatePlanStatus(planId, 2, operator); + } + + private static String genPlanCode() { + int rnd = ThreadLocalRandom.current().nextInt(1000, 10000); + return "APS" + LocalDateTime.now().format(PLAN_CODE_TIME) + rnd; + } + + private static LocalDate resolveDetailStart(LocalDateTime planStart) { + return planStart != null ? planStart.toLocalDate() : LocalDate.now(); + } + + private static LocalDate resolveDetailEnd(LocalDateTime planEnd, LocalDate fallbackStart) { + LocalDate d = planEnd != null ? planEnd.toLocalDate() : null; + return d != null ? d : fallbackStart; + } + + private static BigDecimal scale4(BigDecimal v) { + if (v == null) { + return BigDecimal.ZERO.setScale(4); + } + try { + return v.setScale(4, RoundingMode.HALF_UP); + } catch (Exception e) { + return new BigDecimal(v.toPlainString()).setScale(4, RoundingMode.HALF_UP); + } + } + + private static Long parseLong(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return Long.valueOf(text.trim()); + } catch (Exception e) { + return null; + } + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsScheduleSheetServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsScheduleSheetServiceImpl.java new file mode 100644 index 00000000..d728e345 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsScheduleSheetServiceImpl.java @@ -0,0 +1,232 @@ +package com.klp.aps.service.impl; + +import com.klp.common.exception.ServiceException; +import com.klp.aps.domain.dto.ApsScheduleSheetQueryReq; +import com.klp.aps.domain.dto.ApsScheduleSheetSupplementSaveReq; +import com.klp.aps.domain.vo.ApsScheduleSheetResp; +import com.klp.aps.domain.vo.ApsScheduleSheetRowVo; +import com.klp.aps.mapper.ApsScheduleSheetMapper; +import com.klp.aps.service.ApsScheduleSheetService; +import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RequiredArgsConstructor +@Service +public class ApsScheduleSheetServiceImpl implements ApsScheduleSheetService { + + private final ApsScheduleSheetMapper apsScheduleSheetMapper; + + @Override + public ApsScheduleSheetResp query(ApsScheduleSheetQueryReq req) { + List rows = apsScheduleSheetMapper.selectSheetRows(req); + + ApsScheduleSheetResp resp = new ApsScheduleSheetResp(); + Map header = new HashMap<>(); + header.put("planId", req.getPlanId()); + header.put("orderId", req.getOrderId()); + header.put("lineId", req.getLineId()); + header.put("queryStart", req.getQueryStart()); + header.put("queryEnd", req.getQueryEnd()); + resp.setHeader(header); + resp.setRows(rows); + + ApsScheduleSheetResp.Summary s = new ApsScheduleSheetResp.Summary(); + s.setTotalCount(rows == null ? 0 : rows.size()); + s.setTotalPlanQty(sumPlanQty(rows)); + s.setTotalRawNetWeight(sumRawWeight(rows)); + resp.setSummary(s); + return resp; + } + + @Override + @org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class) + public void saveSupplement(ApsScheduleSheetSupplementSaveReq req, String operator) { + if (req == null || req.getRows() == null || req.getRows().isEmpty()) { + throw new ServiceException("保存数据不能为空"); + } + for (ApsScheduleSheetSupplementSaveReq.Row row : req.getRows()) { + if (row == null || row.getOperationId() == null) { + continue; + } + Map remarkMap = new LinkedHashMap<>(); + remarkMap.put("rawMaterialId", row.getRawMaterialId()); + remarkMap.put("rawCoilNos", row.getRawCoilNos()); + remarkMap.put("rawNetWeight", row.getRawNetWeight()); + remarkMap.put("rawPackaging", row.getRawPackaging()); + remarkMap.put("rawEdgeReq", row.getRawEdgeReq()); + remarkMap.put("rawCoatingType", row.getRawCoatingType()); + remarkMap.put("rawLocation", row.getRawLocation()); + String remarkJson = com.alibaba.fastjson2.JSON.toJSONString(remarkMap); + + apsScheduleSheetMapper.updateOperationSupplement(row.getOperationId(), remarkJson, operator); + apsScheduleSheetMapper.deleteOperationCoils(row.getOperationId(), operator); + if (row.getRawMaterialId() != null) { + apsScheduleSheetMapper.insertOperationCoil(row.getOperationId(), row.getRawMaterialId(), operator); + } + } + } + + @Override + public void exportExcel(ApsScheduleSheetQueryReq req, HttpServletResponse response) { + ApsScheduleSheetResp data = query(req); + List rows = data.getRows(); + + try (Workbook wb = new XSSFWorkbook()) { + Sheet sheet = wb.createSheet("APS排产表"); + + int r = 0; + // 标题行(合并 0~14 列,高度加倍,字体加大加粗居中) + Row title = sheet.createRow(r++); + title.setHeightInPoints(40f); + Cell t0 = title.createCell(0); + t0.setCellValue("统一排产表(冷轧/酸轧等机组通用)"); + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 14)); + + CellStyle titleStyle = wb.createCellStyle(); + titleStyle.setAlignment(HorizontalAlignment.CENTER); + titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); + XSSFFont titleFont = ((XSSFWorkbook) wb).createFont(); + titleFont.setBold(true); + titleFont.setFontHeightInPoints((short) 16); + titleStyle.setFont(titleFont); + t0.setCellStyle(titleStyle); + + // 查询条件行 + Row info = sheet.createRow(r++); + info.createCell(0).setCellValue("时间范围:"); + info.createCell(1).setCellValue(fmt(req.getQueryStart()) + " ~ " + fmt(req.getQueryEnd())); + if (req.getLineId() != null) { + info.createCell(3).setCellValue("产线ID:"); + info.createCell(4).setCellValue(String.valueOf(req.getLineId())); + } + if (req.getPlanId() != null) { + info.createCell(6).setCellValue("计划ID:"); + info.createCell(7).setCellValue(String.valueOf(req.getPlanId())); + } + + // 表头 + Row head = sheet.createRow(r++); + int c = 0; + head.createCell(c++).setCellValue("产线"); + head.createCell(c++).setCellValue("计划号"); + head.createCell(c++).setCellValue("订单号"); + head.createCell(c++).setCellValue("客户"); + head.createCell(c++).setCellValue("业务员"); + head.createCell(c++).setCellValue("产品"); + head.createCell(c++).setCellValue("计划数量"); + head.createCell(c++).setCellValue("开始时间"); + head.createCell(c++).setCellValue("结束时间"); + head.createCell(c++).setCellValue("原料卷号"); + head.createCell(c++).setCellValue("钢卷位置"); + head.createCell(c++).setCellValue("原料净重"); + head.createCell(c++).setCellValue("包装要求"); + head.createCell(c++).setCellValue("切边要求"); + head.createCell(c++).setCellValue("镀层种类"); + + if (rows != null) { + for (ApsScheduleSheetRowVo row : rows) { + Row rr = sheet.createRow(r++); + int cc = 0; + rr.createCell(cc++).setCellValue(nvl(row.getLineName(), row.getLineId())); + rr.createCell(cc++).setCellValue(nvl(row.getPlanCode(), row.getPlanId())); + rr.createCell(cc++).setCellValue(nvl(row.getOrderCode(), row.getOrderId())); + rr.createCell(cc++).setCellValue(nvl(row.getCustomerName(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getSalesman(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getProductName(), "")); + rr.createCell(cc++).setCellValue(row.getPlanQty() == null ? "" : row.getPlanQty().toPlainString()); + rr.createCell(cc++).setCellValue(nvl(row.getStartTime(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getEndTime(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getRawCoilNos(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getRawLocation(), "")); + rr.createCell(cc++).setCellValue(row.getRawNetWeight() == null ? "" : row.getRawNetWeight().toPlainString()); + rr.createCell(cc++).setCellValue(nvl(row.getRawPackaging(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getRawEdgeReq(), "")); + rr.createCell(cc++).setCellValue(nvl(row.getRawCoatingType(), "")); + } + } + + // 合计行 + ApsScheduleSheetResp.Summary s = data.getSummary(); + Row sum = sheet.createRow(r++); + sum.createCell(0).setCellValue("合计"); + sum.createCell(6).setCellValue(s.getTotalPlanQty() == null ? "" : s.getTotalPlanQty().toPlainString()); + sum.createCell(10).setCellValue(s.getTotalRawNetWeight() == null ? "" : s.getTotalRawNetWeight().toPlainString()); + + // 自动列宽(前 14 列) + for (int i = 0; i < 14; i++) { + sheet.autoSizeColumn(i, true); + int w = sheet.getColumnWidth(i); + sheet.setColumnWidth(i, Math.min(Math.max(w, 3000), 12000)); + } + + String filename = "aps_schedule_sheet_" + System.currentTimeMillis() + ".xlsx"; + String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8.name()); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded); + + try (ServletOutputStream os = response.getOutputStream()) { + wb.write(os); + os.flush(); + } + } catch (IOException e) { + throw new ServiceException("导出失败:" + e.getMessage()); + } + } + + private static BigDecimal sumPlanQty(List rows) { + BigDecimal sum = BigDecimal.ZERO; + if (rows == null) return sum; + for (ApsScheduleSheetRowVo r : rows) { + if (r.getPlanQty() != null) { + sum = sum.add(r.getPlanQty()); + } + } + return sum; + } + + private static BigDecimal sumRawWeight(List rows) { + BigDecimal sum = BigDecimal.ZERO; + if (rows == null) return sum; + for (ApsScheduleSheetRowVo r : rows) { + if (r.getRawNetWeight() != null) { + sum = sum.add(r.getRawNetWeight()); + } + } + return sum; + } + + private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private static String fmt(java.time.LocalDateTime t) { + return t == null ? "" : t.format(DT); + } + + private static String nvl(String s, Object fallback) { + if (s != null && !s.isEmpty()) return s; + return fallback == null ? "" : String.valueOf(fallback); + } +} + diff --git a/klp-aps/src/main/java/com/klp/aps/service/impl/ApsShiftTemplateServiceImpl.java b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsShiftTemplateServiceImpl.java new file mode 100644 index 00000000..0e0948e5 --- /dev/null +++ b/klp-aps/src/main/java/com/klp/aps/service/impl/ApsShiftTemplateServiceImpl.java @@ -0,0 +1,116 @@ +package com.klp.aps.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.exception.ServiceException; +import com.klp.common.utils.StringUtils; +import com.klp.aps.domain.bo.ApsShiftTemplateBo; +import com.klp.aps.domain.entity.ApsShiftTemplateEntity; +import com.klp.aps.domain.vo.ApsShiftTemplateVo; +import com.klp.aps.mapper.ApsShiftTemplateMapper; +import com.klp.aps.service.ApsShiftTemplateService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班次模板Service业务层处理 + */ +@RequiredArgsConstructor +@Service +public class ApsShiftTemplateServiceImpl implements ApsShiftTemplateService { + + private final ApsShiftTemplateMapper baseMapper; + + @Override + public ApsShiftTemplateVo queryById(Long shiftId) { + ApsShiftTemplateEntity entity = baseMapper.selectById(shiftId); + ApsShiftTemplateVo vo = BeanUtil.toBean(entity, ApsShiftTemplateVo.class); + if (vo != null && vo.getCrossDay() != null) { + vo.setCrossDayName(vo.getCrossDay() == 1 ? "跨天" : "不跨天"); + } + return vo; + } + + @Override + public TableDataInfo queryPageList(ApsShiftTemplateBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectPage(pageQuery.build(), lqw); + List voList = result.getRecords().stream() + .map(entity -> { + ApsShiftTemplateVo vo = BeanUtil.toBean(entity, ApsShiftTemplateVo.class); + if (vo.getCrossDay() != null) { + vo.setCrossDayName(vo.getCrossDay() == 1 ? "跨天" : "不跨天"); + } + return vo; + }) + .collect(Collectors.toList()); + TableDataInfo build = TableDataInfo.build(voList); + build.setTotal(result.getTotal()); + return build; + } + + @Override + public List queryList(ApsShiftTemplateBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List list = baseMapper.selectList(lqw); + return list.stream() + .map(entity -> { + ApsShiftTemplateVo vo = BeanUtil.toBean(entity, ApsShiftTemplateVo.class); + if (vo.getCrossDay() != null) { + vo.setCrossDayName(vo.getCrossDay() == 1 ? "跨天" : "不跨天"); + } + return vo; + }) + .collect(Collectors.toList()); + } + + private LambdaQueryWrapper buildQueryWrapper(ApsShiftTemplateBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getShiftCode()), ApsShiftTemplateEntity::getShiftCode, bo.getShiftCode()); + lqw.like(StringUtils.isNotBlank(bo.getShiftName()), ApsShiftTemplateEntity::getShiftName, bo.getShiftName()); + return lqw; + } + + @Override + public Boolean insertByBo(ApsShiftTemplateBo bo) { + ApsShiftTemplateEntity entity = BeanUtil.toBean(bo, ApsShiftTemplateEntity.class); + validEntityBeforeSave(entity); + return baseMapper.insert(entity) > 0; + } + + @Override + public Boolean updateByBo(ApsShiftTemplateBo bo) { + ApsShiftTemplateEntity entity = BeanUtil.toBean(bo, ApsShiftTemplateEntity.class); + validEntityBeforeSave(entity); + return baseMapper.updateById(entity) > 0; + } + + private void validEntityBeforeSave(ApsShiftTemplateEntity entity) { + ApsShiftTemplateEntity exist = baseMapper.selectByShiftCode(entity.getShiftCode()); + if (exist != null && !exist.getShiftId().equals(entity.getShiftId())) { + throw new ServiceException("班次编码已存在"); + } + if (entity.getCrossDay() == null) { + entity.setCrossDay(0); + } + if (entity.getEfficiencyRate() == null) { + entity.setEfficiencyRate(java.math.BigDecimal.ONE); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO: 可以添加业务校验,例如检查是否被日历班次配置使用 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsAutoScheduleMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsAutoScheduleMapper.xml new file mode 100644 index 00000000..c9722545 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsAutoScheduleMapper.xml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + DELETE FROM wms_schedule_operation + WHERE plan_id = #{planId} + AND locked_flag = 0 + + + + + + + + INSERT INTO wms_schedule_operation + ( + plan_id, + detail_id, + order_id, + order_detail_id, + product_id, + process_id, + line_id, + sequence_no, + plan_qty, + start_time, + end_time, + setup_minutes, + status, + locked_flag, + remark, + create_by, + update_by + ) + VALUES + ( + #{planId}, + #{detailId}, + #{orderId}, + #{orderDetailId}, + #{productId}, + #{processId}, + #{lineId}, + #{sequenceNo}, + #{planQty}, + #{startTime}, + #{endTime}, + #{setupMinutes}, + #{status}, + #{lockedFlag}, + #{remark}, + #{createBy}, + #{updateBy} + ) + + + + INSERT INTO wms_schedule_change_log + ( + operation_id, + change_type, + before_value, + after_value, + change_reason, + change_by + ) + VALUES + ( + #{operationId}, + #{changeType}, + #{beforeValue}, + #{afterValue}, + #{changeReason}, + #{changeBy} + ) + + + + UPDATE wms_schedule_plan + SET status = #{status}, + update_by = #{updateBy}, + update_time = CURRENT_TIMESTAMP + WHERE plan_id = #{planId} + AND del_flag = 0 + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsCalendarMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsCalendarMapper.xml new file mode 100644 index 00000000..d4ef045b --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsCalendarMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + SELECT calendar_id, calendar_date, calendar_type, factory_code, remark, + create_by, create_time, update_by, update_time + FROM wms_calendar + + + + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsCalendarShiftMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsCalendarShiftMapper.xml new file mode 100644 index 00000000..7a2303b7 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsCalendarShiftMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsGanttMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsGanttMapper.xml new file mode 100644 index 00000000..49af8740 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsGanttMapper.xml @@ -0,0 +1,50 @@ + + + + + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsLineCapabilityMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsLineCapabilityMapper.xml new file mode 100644 index 00000000..197d5939 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsLineCapabilityMapper.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsLockMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsLockMapper.xml new file mode 100644 index 00000000..6065f119 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsLockMapper.xml @@ -0,0 +1,49 @@ + + + + + + INSERT INTO wms_schedule_lock + ( + lock_type, + plan_id, + line_id, + operation_id, + lock_start_time, + lock_end_time, + lock_reason, + status, + create_by, + update_by + ) + VALUES + ( + #{lockType}, + #{planId}, + #{lineId}, + #{operationId}, + #{lockStartTime}, + #{lockEndTime}, + #{lockReason}, + #{status}, + #{createBy}, + #{updateBy} + ) + + + + UPDATE wms_schedule_lock + SET status = #{status}, + update_by = #{updateBy}, + update_time = CURRENT_TIMESTAMP + WHERE lock_id = #{lockId} + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsOperationMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsOperationMapper.xml new file mode 100644 index 00000000..d789d205 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsOperationMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE wms_schedule_operation + SET line_id = #{lineId}, + start_time = #{startTime}, + end_time = #{endTime}, + update_by = #{updateBy}, + update_time = CURRENT_TIMESTAMP + WHERE operation_id = #{operationId} + + + + INSERT INTO wms_schedule_change_log + ( + operation_id, + change_type, + before_value, + after_value, + change_reason, + change_by + ) + VALUES + ( + #{operationId}, + #{changeType}, + #{beforeValue}, + #{afterValue}, + #{changeReason}, + #{changeBy} + ) + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsPlanMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsPlanMapper.xml new file mode 100644 index 00000000..9263861b --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsPlanMapper.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + INSERT INTO wms_order + ( + order_code, + customer_id, + customer_name, + sales_manager, + order_status, + remark, + tax_amount, + no_tax_amount, + del_flag, + create_by, + update_by + ) + VALUES + ( + #{orderCode}, + #{customerId}, + #{customerName}, + #{salesManager}, + #{orderStatus}, + #{remark}, + #{taxAmount}, + #{noTaxAmount}, + 0, + #{createBy}, + #{updateBy} + ) + + + + INSERT INTO wms_order_detail + ( + order_id, + product_id, + quantity, + unit, + remark, + tax_price, + no_tax_price, + group_id, + del_flag, + create_by, + update_by + ) + VALUES + ( + #{orderId}, + #{productId}, + #{quantity}, + #{unit}, + #{remark}, + #{taxPrice}, + #{noTaxPrice}, + #{groupId}, + IFNULL(#{delFlag}, 0), + #{createBy}, + #{updateBy} + ) + + + + + + INSERT INTO wms_schedule_plan + ( + plan_code, + version, + order_id, + status, + remark, + del_flag, + create_by, + update_by, + priority, + start_date, + end_date + ) + VALUES + ( + #{planCode}, + #{version}, + #{orderId}, + #{status}, + #{remark}, + #{delFlag}, + #{createBy}, + #{updateBy}, + #{priority}, + #{startDate}, + #{endDate} + ) + + + + INSERT INTO wms_schedule_plan_detail + ( + plan_id, + line_id, + task_id, + product_id, + quantity, + start_date, + end_date, + remark, + del_flag, + create_by, + update_by + ) + VALUES + ( + #{planId}, + #{lineId}, + #{taskId}, + #{productId}, + #{quantity}, + #{startDate}, + #{endDate}, + #{remark}, + #{delFlag}, + #{createBy}, + #{updateBy} + ) + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsScheduleSheetMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsScheduleSheetMapper.xml new file mode 100644 index 00000000..482f3d06 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsScheduleSheetMapper.xml @@ -0,0 +1,143 @@ + + + + + + + + UPDATE wms_schedule_operation_coil + SET del_flag = 1, + update_by = #{updateBy}, + update_time = CURRENT_TIMESTAMP + WHERE operation_id = #{operationId} + AND del_flag = 0 + + + + INSERT INTO wms_schedule_operation_coil + ( + operation_id, + coil_id, + create_by, + update_by, + del_flag + ) + VALUES + ( + #{operationId}, + #{coilId}, + #{createBy}, + #{createBy}, + 0 + ) + + + + UPDATE wms_schedule_operation + SET remark = #{remarkJson}, + update_by = #{updateBy}, + update_time = CURRENT_TIMESTAMP + WHERE operation_id = #{operationId} + + + + diff --git a/klp-da/src/main/resources/mapper/da/aps/ApsShiftTemplateMapper.xml b/klp-da/src/main/resources/mapper/da/aps/ApsShiftTemplateMapper.xml new file mode 100644 index 00000000..9c24f0c0 --- /dev/null +++ b/klp-da/src/main/resources/mapper/da/aps/ApsShiftTemplateMapper.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/klp-ui/src/api/aps/aps.js b/klp-ui/src/api/aps/aps.js new file mode 100644 index 00000000..74354cf5 --- /dev/null +++ b/klp-ui/src/api/aps/aps.js @@ -0,0 +1,344 @@ +import request from '@/utils/request' + +// CRM 订单转换为库存订单 +export function convertFromCrm(data) { + return request({ + url: '/aps/order/convert-from-crm', + method: 'post', + data + }) +} + +// 查询 CRM 订单明细 +export function listCrmOrderItems(query) { + return request({ + url: '/crm/orderItem/list', + method: 'get', + params: query + }) +} + +// 产品直排转换为库存订单(单产品) +export function convertFromProduct(data) { + return request({ + url: '/aps/order/convert-from-product', + method: 'post', + data + }) +} + +// 产品直排转换为库存订单(多产品) +export function convertFromProducts(data) { + return request({ + url: '/aps/order/convert-from-products', + method: 'post', + data + }) +} + +// 创建排产计划 +export function createPlan(data) { + return request({ + url: '/aps/plan/create', + method: 'post', + data + }) +} + +// 自动排程 +export function autoSchedule(data) { + return request({ + url: '/aps/plan/auto-schedule', + method: 'post', + data + }) +} + +// 发布计划 +export function publishPlan(planId) { + return request({ + url: `/aps/plan/publish/${planId}`, + method: 'post' + }) +} + +// 甘特查询 +export function fetchGantt(params) { + return request({ + url: '/aps/gantt', + method: 'get', + params + }) +} + +// 工厂日历聚合(后端计算) +export function fetchFactoryCalendar(params) { + return request({ + url: '/aps/factory-calendar', + method: 'get', + params + }) +} + +// 单工序重排 +export function rescheduleOperation(data) { + return request({ + url: '/aps/operation/reschedule', + method: 'post', + data + }) +} + +// 创建锁定 +export function createLock(data) { + return request({ + url: '/aps/lock', + method: 'post', + data + }) +} + +// 解除锁定 +export function releaseLock(lockId) { + return request({ + url: `/aps/lock/release/${lockId}`, + method: 'post' + }) +} + +/** + * 统一排产表(按模板/机组导出与展示) + * 说明: + * - templateKey: cold_rolling / acid_pickling / slitting / galvanizing + * - 后端建议返回:{ header: {...}, rows: [...], summary: {...} } + */ +export function fetchScheduleSheet(params) { + return request({ + url: '/aps/schedule-sheet', + method: 'get', + params, + timeout: 120000 + }) +} + +export function exportScheduleSheet(params) { + return request({ + url: '/aps/schedule-sheet/export', + method: 'get', + params, + responseType: 'blob', + timeout: 120000 + }) +} + +export function saveScheduleSheetSupplement(data) { + return request({ + url: '/aps/schedule-sheet/supplement/save', + method: 'post', + data, + timeout: 120000 + }) +} + +// ========== 基础数据管理 ========== + +// 工厂日历 +export function listCalendar(query) { + return request({ + url: '/aps/calendar/list', + method: 'get', + params: query + }) +} + +export function getCalendar(calendarId) { + return request({ + url: '/aps/calendar/' + calendarId, + method: 'get' + }) +} + +export function addCalendar(data) { + return request({ + url: '/aps/calendar', + method: 'post', + data + }) +} + +export function updateCalendar(data) { + return request({ + url: '/aps/calendar', + method: 'put', + data + }) +} + +export function delCalendar(calendarIds) { + return request({ + url: '/aps/calendar/' + calendarIds, + method: 'delete' + }) +} + +export function exportCalendar(query) { + return request({ + url: '/aps/calendar/export', + method: 'post', + params: query, + responseType: 'blob' + }) +} + +// 班次模板 +export function listShiftTemplate(query) { + return request({ + url: '/aps/shift-template/list', + method: 'get', + params: query + }) +} + +export function getShiftTemplate(shiftId) { + return request({ + url: '/aps/shift-template/' + shiftId, + method: 'get' + }) +} + +export function addShiftTemplate(data) { + return request({ + url: '/aps/shift-template', + method: 'post', + data + }) +} + +export function updateShiftTemplate(data) { + return request({ + url: '/aps/shift-template', + method: 'put', + data + }) +} + +export function delShiftTemplate(shiftIds) { + return request({ + url: '/aps/shift-template/' + shiftIds, + method: 'delete' + }) +} + +export function exportShiftTemplate(query) { + return request({ + url: '/aps/shift-template/export', + method: 'post', + params: query, + responseType: 'blob' + }) +} + +// 日历班次配置 +export function listCalendarShift(query) { + return request({ + url: '/aps/calendar-shift/list', + method: 'get', + params: query + }) +} + +export function getCalendarShift(configId) { + return request({ + url: '/aps/calendar-shift/' + configId, + method: 'get' + }) +} + +export function addCalendarShift(data) { + return request({ + url: '/aps/calendar-shift', + method: 'post', + data + }) +} + +export function updateCalendarShift(data) { + return request({ + url: '/aps/calendar-shift', + method: 'put', + data + }) +} + +export function delCalendarShift(configIds) { + return request({ + url: '/aps/calendar-shift/' + configIds, + method: 'delete' + }) +} + +export function exportCalendarShift(query) { + return request({ + url: '/aps/calendar-shift/export', + method: 'post', + params: query, + responseType: 'blob' + }) +} + +export function generateCalendarShiftWorkdayYear(year, overwriteExisting = false) { + return request({ + url: `/aps/calendar-shift/generate-workday-year/${year}`, + method: 'post', + params: { overwriteExisting }, + timeout: 600000 + }) +} + +// 产线能力 +export function listLineCapability(query) { + return request({ + url: '/aps/line-capability/list', + method: 'get', + params: query + }) +} + +export function getLineCapability(capabilityId) { + return request({ + url: '/aps/line-capability/' + capabilityId, + method: 'get' + }) +} + +export function addLineCapability(data) { + return request({ + url: '/aps/line-capability', + method: 'post', + data + }) +} + +export function updateLineCapability(data) { + return request({ + url: '/aps/line-capability', + method: 'put', + data + }) +} + +export function delLineCapability(capabilityIds) { + return request({ + url: '/aps/line-capability/' + capabilityIds, + method: 'delete' + }) +} + +export function exportLineCapability(query) { + return request({ + url: '/aps/line-capability/export', + method: 'post', + params: query, + responseType: 'blob' + }) +} diff --git a/klp-ui/src/api/wms/coil.js b/klp-ui/src/api/wms/coil.js index 82d278a7..559bc4ba 100644 --- a/klp-ui/src/api/wms/coil.js +++ b/klp-ui/src/api/wms/coil.js @@ -9,6 +9,14 @@ export function listMaterialCoil(query) { }) } +export function getMaterialCoilLocationGrid(query) { + return request({ + url: '/wms/materialCoil/locationGrid', + method: 'get', + params: query + }) +} + export function exportMaterialCoil(query) { return request({ url: '/wms/materialCoil/export', diff --git a/klp-ui/src/router/index.js b/klp-ui/src/router/index.js index 60f82715..a7cc2ad5 100644 --- a/klp-ui/src/router/index.js +++ b/klp-ui/src/router/index.js @@ -100,6 +100,31 @@ export const constantRoutes = [ meta: { title: '个人中心', icon: 'user' } } ] + }, + { + path: '/aps', + component: Layout, + hidden: true, + children: [ + { + path: 'lineCapability', + component: () => import('@/views/aps/lineCapability/index'), + name: 'ApsLineCapability', + meta: { title: '产线能力' } + }, + { + path: 'processManage', + component: () => import('@/views/aps/processManage/index'), + name: 'ApsProcessManage', + meta: { title: '工序管理' } + }, + { + path: 'factory-calendar', + component: () => import('@/views/aps/factoryCalendar'), + name: 'ApsFactoryCalendarPage', + meta: { title: '工厂总日历' } + } + ] } ] diff --git a/klp-ui/src/views/aps/calendar/index.vue b/klp-ui/src/views/aps/calendar/index.vue new file mode 100644 index 00000000..71de900b --- /dev/null +++ b/klp-ui/src/views/aps/calendar/index.vue @@ -0,0 +1,512 @@ + + + + + diff --git a/klp-ui/src/views/aps/calendarShift/index.vue b/klp-ui/src/views/aps/calendarShift/index.vue new file mode 100644 index 00000000..f50730a0 --- /dev/null +++ b/klp-ui/src/views/aps/calendarShift/index.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/klp-ui/src/views/aps/factoryCalendar.vue b/klp-ui/src/views/aps/factoryCalendar.vue new file mode 100644 index 00000000..5aceb418 --- /dev/null +++ b/klp-ui/src/views/aps/factoryCalendar.vue @@ -0,0 +1,583 @@ + + + + + diff --git a/klp-ui/src/views/aps/index.vue b/klp-ui/src/views/aps/index.vue new file mode 100644 index 00000000..c64b739b --- /dev/null +++ b/klp-ui/src/views/aps/index.vue @@ -0,0 +1,1580 @@ + + + + + + diff --git a/klp-ui/src/views/aps/lineCapability/index.vue b/klp-ui/src/views/aps/lineCapability/index.vue new file mode 100644 index 00000000..23687c43 --- /dev/null +++ b/klp-ui/src/views/aps/lineCapability/index.vue @@ -0,0 +1,486 @@ + + + + + diff --git a/klp-ui/src/views/aps/processManage/index.vue b/klp-ui/src/views/aps/processManage/index.vue new file mode 100644 index 00000000..d3f108ff --- /dev/null +++ b/klp-ui/src/views/aps/processManage/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/klp-ui/src/views/aps/sheet.vue b/klp-ui/src/views/aps/sheet.vue new file mode 100644 index 00000000..62fff6e4 --- /dev/null +++ b/klp-ui/src/views/aps/sheet.vue @@ -0,0 +1,756 @@ + + + + + diff --git a/klp-ui/src/views/aps/sheets/SheetColumns.vue b/klp-ui/src/views/aps/sheets/SheetColumns.vue new file mode 100644 index 00000000..e78fa79c --- /dev/null +++ b/klp-ui/src/views/aps/sheets/SheetColumns.vue @@ -0,0 +1,43 @@ + + diff --git a/klp-ui/src/views/aps/sheets/templates.js b/klp-ui/src/views/aps/sheets/templates.js new file mode 100644 index 00000000..1e4ed4df --- /dev/null +++ b/klp-ui/src/views/aps/sheets/templates.js @@ -0,0 +1,42 @@ +/** + * 统一排产表:模板配置 + * + * 设计目标: + * - 用同一套 UI 渲染不同机组的“排产表样式”(多级表头/字段差异) + * - 后端返回统一的 rowModel(行数据)后,前端仅做字段映射和展示 + * + * 约定: + * - columns 支持多级:{ label, children } 或叶子列 { label, prop, width, minWidth, fixed, align } + * - summary:用于底部合计显示(哪些字段求和) + */ + +export const APS_SHEET_TEMPLATES = [ + { + key: 'unified', + name: '统一排产表', + columns: [ + { label: '产线', prop: 'lineName', minWidth: 120 }, + { label: '计划号', prop: 'planCode', minWidth: 140 }, + { label: '订单号', prop: 'orderCode', minWidth: 140 }, + { label: '客户', prop: 'customerName', minWidth: 140 }, + { label: '业务员', prop: 'salesman', width: 100 }, + { label: '产品', prop: 'productName', minWidth: 140 }, + { label: '原料钢卷', prop: 'rawMaterialId', minWidth: 220 }, + { label: '原料卷号', prop: 'rawCoilNos', minWidth: 220 }, + { label: '钢卷位置', prop: 'rawLocation', minWidth: 140 }, + { label: '包装要求', prop: 'rawPackaging', minWidth: 140 }, + { label: '切边要求', prop: 'rawEdgeReq', minWidth: 120 }, + { label: '镀层种类', prop: 'rawCoatingType', width: 110 }, + { label: '原料净重', prop: 'rawNetWeight', width: 110, align: 'right' }, + { label: '计划数量', prop: 'planQty', width: 100, align: 'right' }, + { label: '开始时间', prop: 'startTime', width: 170 }, + { label: '结束时间', prop: 'endTime', width: 170 } + ], + summary: { sumFields: ['planQty', 'rawNetWeight'] } + } +] + +export function getTemplateByKey(key) { + return APS_SHEET_TEMPLATES.find(t => t.key === key) || APS_SHEET_TEMPLATES[0] +} + diff --git a/klp-ui/src/views/aps/shiftTemplate/index.vue b/klp-ui/src/views/aps/shiftTemplate/index.vue new file mode 100644 index 00000000..b6c41f9e --- /dev/null +++ b/klp-ui/src/views/aps/shiftTemplate/index.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/klp-ui/src/views/wms/order/panels/orderPage.vue b/klp-ui/src/views/wms/order/panels/orderPage.vue index 661522ce..90591994 100644 --- a/klp-ui/src/views/wms/order/panels/orderPage.vue +++ b/klp-ui/src/views/wms/order/panels/orderPage.vue @@ -40,12 +40,34 @@ - + @@ -64,6 +86,23 @@
+ +
+
+ 当前订单:{{ form.orderCode || form.orderId }} +
+
+ + 排产 + +
+
@@ -460,6 +499,34 @@ export default { }).catch(() => { this.$modal.msgError("转化失败"); }); + }, + + /** 跳转到 APS 排产中心,携带当前订单标识 */ + goApsSchedule(row) { + if (!row || !row.orderId) { + this.$modal.msgWarning("当前订单数据异常,无法进入排产"); + return; + } + this.$router + .push({ + path: "/aps/index", + query: { orderId: row.orderId, orderCode: row.orderCode } + }) + .catch(() => {}); + }, + + /** 从订单详情一键排产并查看结果 */ + goApsOneKeySchedule() { + if (!this.form || !this.form.orderId) { + this.$modal.msgWarning("请先选择需要排产的订单"); + return; + } + this.$router + .push({ + path: "/aps/index", + query: { orderId: this.form.orderId, orderCode: this.form.orderCode, autoOneKey: "1" } + }) + .catch(() => {}); } } }; @@ -485,6 +552,17 @@ export default { margin-bottom: 16px; } +.order-detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 8px; +} +.order-detail-title { + font-weight: 600; + font-size: 14px; +} + ::v-deep .el-input-group__append, ::v-deep .el-input-group__prepend { width: 20px !important; diff --git a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java index 6df85839..6607302e 100644 --- a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java +++ b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java @@ -5,9 +5,9 @@ import java.util.Map; import java.util.Arrays; import java.util.stream.Collectors; -import com.klp.common.core.domain.AjaxResult; import com.klp.domain.vo.WmsMaterialCoilExportVo; import com.klp.domain.vo.WmsMaterialCoilDeliveryExportVo; +import com.klp.domain.vo.WmsMaterialCoilLocationGridVo; import lombok.RequiredArgsConstructor; import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.*; @@ -50,6 +50,20 @@ public class WmsMaterialCoilController extends BaseController { return iWmsMaterialCoilService.queryPageList(bo, pageQuery); } + /** + * 原料钢卷库位分布查询(先库位,再钢卷映射) + */ + @GetMapping("/locationGrid") + public R locationGrid( + @RequestParam @NotNull(message = "库区ID不能为空") Long actualWarehouseId, + @RequestParam(required = false, defaultValue = "raw_material") String itemType, + @RequestParam(required = false) String enterCoilNo, + @RequestParam(required = false) String currentCoilNo, + @RequestParam(required = false) String manufacturer + ) { + return R.ok(iWmsMaterialCoilService.queryLocationGrid(actualWarehouseId, itemType, enterCoilNo, currentCoilNo, manufacturer)); + } + /** * 导出钢卷物料表列表(完整字段版本) */ diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilLocationGridVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilLocationGridVo.java new file mode 100644 index 00000000..f223db52 --- /dev/null +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilLocationGridVo.java @@ -0,0 +1,21 @@ +package com.klp.domain.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 原料钢卷库位分布响应 + */ +@Data +public class WmsMaterialCoilLocationGridVo { + + /** 库位列表(用于绘制库位网格) */ + private List warehouses; + + /** 钢卷列表(用于映射到库位) */ + private List coils; + + /** 钢卷总数 */ + private Integer total; +} diff --git a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java index 758ed2a9..1381e453 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java @@ -1,6 +1,7 @@ package com.klp.service; import com.klp.domain.vo.WmsMaterialCoilDeliveryExportVo; +import com.klp.domain.vo.WmsMaterialCoilLocationGridVo; import com.klp.domain.vo.WmsMaterialCoilVo; import com.klp.domain.bo.WmsMaterialCoilBo; import com.klp.common.core.page.TableDataInfo; @@ -215,15 +216,16 @@ public interface IWmsMaterialCoilService { Map cancelSpecialSplit(@NotNull(message = "待操作记录ID不能为空") Long pendingActionId); - /** - * 钢卷退货操作 - * 将钢卷退货到退货仓,创建新钢卷记录,将原钢卷设置为历史钢卷 - * - * @param coilId 原钢卷ID - * @return 退货后的新钢卷信息 - */ -WmsMaterialCoilVo returnCoil(@NotNull(message = "钢卷ID不能为空") Long coilId); - List getCoilTrimStatistics(); + WmsMaterialCoilVo returnCoil(@NotNull(message = "钢卷ID不能为空") Long coilId); + + /** + * 原料钢卷库位分布查询:先返回库位,再返回该库位下钢卷 + */ + WmsMaterialCoilLocationGridVo queryLocationGrid(Long actualWarehouseId, + String itemType, + String enterCoilNo, + String currentCoilNo, + String manufacturer); } diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java index 1bed0abc..f2eff18c 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java @@ -856,6 +856,39 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { } } } + @Override + public WmsMaterialCoilLocationGridVo queryLocationGrid(Long actualWarehouseId, + String itemType, + String enterCoilNo, + String currentCoilNo, + String manufacturer) { + WmsMaterialCoilLocationGridVo result = new WmsMaterialCoilLocationGridVo(); + if (actualWarehouseId == null) { + result.setWarehouses(Collections.emptyList()); + result.setCoils(Collections.emptyList()); + result.setTotal(0); + return result; + } + + WmsActualWarehouseBo warehouseBo = new WmsActualWarehouseBo(); + warehouseBo.setParentId(actualWarehouseId); + List warehouses = actualWarehouseService.queryList(warehouseBo); + + WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo(); + coilBo.setActualWarehouseId(actualWarehouseId); + coilBo.setItemType(StringUtils.isBlank(itemType) ? "raw_material" : itemType); + coilBo.setEnterCoilNo(enterCoilNo); + coilBo.setCurrentCoilNo(currentCoilNo); + coilBo.setItemManufacturer(manufacturer); + coilBo.setDataType(1); + List coils = this.queryList(coilBo); + + result.setWarehouses(warehouses); + result.setCoils(coils); + result.setTotal(coils.size()); + return result; + } + /** * 查询钢卷物料表列表 */ diff --git a/script/sql/mysql/klp-oa.sql b/script/sql/mysql/klp-oa.sql index 92ce4633..7876079f 100644 --- a/script/sql/mysql/klp-oa.sql +++ b/script/sql/mysql/klp-oa.sql @@ -4342,4 +4342,191 @@ CREATE TABLE `xxl_job_user` ( UNIQUE INDEX `i_username`(`username` ASC) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; +-- ============================= +-- APS 排产补充表(wms_*) +-- ============================= + +-- 1) 工厂日历 +DROP TABLE IF EXISTS `wms_calendar`; +CREATE TABLE `wms_calendar` ( + `calendar_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `calendar_date` date NOT NULL COMMENT '日期', + `calendar_type` tinyint NOT NULL DEFAULT 1 COMMENT '日历类型:1工作日 2周末 3法定假日 4停机日', + `factory_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '工厂编码', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`calendar_id`) USING BTREE, + UNIQUE KEY `uk_calendar_date_factory` (`calendar_date`,`factory_code`) USING BTREE, + KEY `idx_calendar_type` (`calendar_type`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='工厂日历' ROW_FORMAT=Dynamic; + +-- 2) 班次模板 +DROP TABLE IF EXISTS `wms_shift_template`; +CREATE TABLE `wms_shift_template` ( + `shift_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `shift_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '班次编码', + `shift_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '班次名称', + `start_time` time NOT NULL COMMENT '开始时间', + `end_time` time NOT NULL COMMENT '结束时间', + `cross_day` tinyint NOT NULL DEFAULT 0 COMMENT '是否跨天:0否 1是', + `efficiency_rate` decimal(6,4) NOT NULL DEFAULT 1.0000 COMMENT '班次效率系数', + `is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用:0否 1是', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`shift_id`) USING BTREE, + UNIQUE KEY `uk_shift_code` (`shift_code`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='班次模板' ROW_FORMAT=Dynamic; + +-- 3) 日历班次配置(某天 + 某产线 + 某班次) +DROP TABLE IF EXISTS `wms_calendar_shift`; +CREATE TABLE `wms_calendar_shift` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `calendar_date` date NOT NULL COMMENT '日期', + `line_id` bigint NOT NULL COMMENT '产线ID', + `shift_id` bigint NOT NULL COMMENT '班次ID', + `planned_hours` decimal(8,2) NOT NULL DEFAULT 8.00 COMMENT '计划工时', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1可排产 2停机', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_calendar_shift` (`calendar_date`,`line_id`,`shift_id`) USING BTREE, + KEY `idx_cs_line_date` (`line_id`,`calendar_date`) USING BTREE, + KEY `idx_cs_shift` (`shift_id`) USING BTREE, + CONSTRAINT `fk_cs_line` FOREIGN KEY (`line_id`) REFERENCES `wms_production_line` (`line_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_cs_shift` FOREIGN KEY (`shift_id`) REFERENCES `wms_shift_template` (`shift_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='日历班次配置' ROW_FORMAT=Dynamic; + +-- 4) 产线能力(支持产品维度 / 工序维度) +DROP TABLE IF EXISTS `wms_line_capability`; +CREATE TABLE `wms_line_capability` ( + `capability_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `line_id` bigint NOT NULL COMMENT '产线ID', + `product_id` bigint DEFAULT NULL COMMENT '产品ID,可空(按工序维度时可空)', + `process_id` bigint DEFAULT NULL COMMENT '工序ID,可空(按产品维度时可空)', + `capacity_per_hour` decimal(18,4) NOT NULL COMMENT '每小时产能', + `setup_minutes` int NOT NULL DEFAULT 0 COMMENT '换型准备时间(分钟)', + `priority` int NOT NULL DEFAULT 100 COMMENT '优先级(越小越优先)', + `is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用:0否 1是', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`capability_id`) USING BTREE, + UNIQUE KEY `uk_line_prod_proc` (`line_id`,`product_id`,`process_id`) USING BTREE, + KEY `idx_lc_line` (`line_id`) USING BTREE, + KEY `idx_lc_product` (`product_id`) USING BTREE, + KEY `idx_lc_process` (`process_id`) USING BTREE, + CONSTRAINT `fk_lc_line` FOREIGN KEY (`line_id`) REFERENCES `wms_production_line` (`line_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_lc_product` FOREIGN KEY (`product_id`) REFERENCES `wms_product` (`product_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_lc_process` FOREIGN KEY (`process_id`) REFERENCES `wms_processe` (`process_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='产线能力(产品/工序可生产关系)' ROW_FORMAT=Dynamic; + +-- 5) 工序级排程记录(核心) +DROP TABLE IF EXISTS `wms_schedule_operation`; +CREATE TABLE `wms_schedule_operation` ( + `operation_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `plan_id` bigint NOT NULL COMMENT '排产计划ID', + `detail_id` bigint DEFAULT NULL COMMENT '排产明细ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `order_detail_id` bigint NOT NULL COMMENT '订单明细ID', + `product_id` bigint NOT NULL COMMENT '产品ID', + `process_id` bigint NOT NULL COMMENT '工序ID', + `line_id` bigint NOT NULL COMMENT '产线ID', + `sequence_no` int NOT NULL COMMENT '工序顺序', + `plan_qty` decimal(18,4) NOT NULL COMMENT '计划数量', + `start_time` datetime NOT NULL COMMENT '计划开始时间', + `end_time` datetime NOT NULL COMMENT '计划结束时间', + `setup_minutes` int NOT NULL DEFAULT 0 COMMENT '换型时长', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0待执行 1执行中 2已完成 3已锁定 4取消', + `locked_flag` tinyint NOT NULL DEFAULT 0 COMMENT '是否锁定:0否 1是', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`operation_id`) USING BTREE, + KEY `idx_so_plan` (`plan_id`) USING BTREE, + KEY `idx_so_line_time` (`line_id`,`start_time`,`end_time`) USING BTREE, + KEY `idx_so_order_detail` (`order_detail_id`) USING BTREE, + KEY `idx_so_process` (`process_id`) USING BTREE, + CONSTRAINT `fk_so_plan` FOREIGN KEY (`plan_id`) REFERENCES `wms_schedule_plan` (`plan_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_detail` FOREIGN KEY (`detail_id`) REFERENCES `wms_schedule_plan_detail` (`detail_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_order` FOREIGN KEY (`order_id`) REFERENCES `wms_order` (`order_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_order_detail` FOREIGN KEY (`order_detail_id`) REFERENCES `wms_order_detail` (`detail_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_product` FOREIGN KEY (`product_id`) REFERENCES `wms_product` (`product_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_process` FOREIGN KEY (`process_id`) REFERENCES `wms_processe` (`process_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_so_line` FOREIGN KEY (`line_id`) REFERENCES `wms_production_line` (`line_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='工序级排产记录' ROW_FORMAT=Dynamic; + +-- 5b) 工序级排程-钢卷关系 +DROP TABLE IF EXISTS `wms_schedule_operation_coil`; +CREATE TABLE `wms_schedule_operation_coil` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `operation_id` bigint NOT NULL COMMENT '工序排程ID(关联 wms_schedule_operation.operation_id)', + `coil_id` bigint NOT NULL COMMENT '钢卷ID(关联 wms_material_coil.coil_id)', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + `del_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0=正常,1=已删除)', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_soc_operation` (`operation_id`) USING BTREE, + KEY `idx_soc_coil` (`coil_id`) USING BTREE, + CONSTRAINT `fk_soc_operation` FOREIGN KEY (`operation_id`) REFERENCES `wms_schedule_operation` (`operation_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_soc_coil` FOREIGN KEY (`coil_id`) REFERENCES `wms_material_coil` (`coil_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='工序排程钢卷关系表' ROW_FORMAT=Dynamic; + +-- 6) 排产锁定(冻结机制) +DROP TABLE IF EXISTS `wms_schedule_lock`; +CREATE TABLE `wms_schedule_lock` ( + `lock_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `lock_type` tinyint NOT NULL COMMENT '锁定类型:1计划 2产线+时间窗 3工序记录', + `plan_id` bigint DEFAULT NULL COMMENT '计划ID', + `line_id` bigint DEFAULT NULL COMMENT '产线ID', + `operation_id` bigint DEFAULT NULL COMMENT '工序排程ID', + `lock_start_time` datetime DEFAULT NULL COMMENT '锁定开始时间', + `lock_end_time` datetime DEFAULT NULL COMMENT '锁定结束时间', + `lock_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '锁定原因', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1生效 0失效', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`lock_id`) USING BTREE, + KEY `idx_sl_plan` (`plan_id`) USING BTREE, + KEY `idx_sl_line_time` (`line_id`,`lock_start_time`,`lock_end_time`) USING BTREE, + KEY `idx_sl_operation` (`operation_id`) USING BTREE, + CONSTRAINT `fk_sl_plan` FOREIGN KEY (`plan_id`) REFERENCES `wms_schedule_plan` (`plan_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_sl_line` FOREIGN KEY (`line_id`) REFERENCES `wms_production_line` (`line_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_sl_operation` FOREIGN KEY (`operation_id`) REFERENCES `wms_schedule_operation` (`operation_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='排产锁定表' ROW_FORMAT=Dynamic; + +-- 7) 排程变更日志 +DROP TABLE IF EXISTS `wms_schedule_change_log`; +CREATE TABLE `wms_schedule_change_log` ( + `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `operation_id` bigint NOT NULL COMMENT '工序排程ID', + `change_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '变更类型:CREATE/UPDATE/DELETE/RESCHEDULE/LOCK/UNLOCK', + `before_value` json DEFAULT NULL COMMENT '变更前快照', + `after_value` json DEFAULT NULL COMMENT '变更后快照', + `change_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '变更原因', + `change_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变更时间', + `change_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '变更人', + PRIMARY KEY (`log_id`) USING BTREE, + KEY `idx_scl_operation` (`operation_id`) USING BTREE, + KEY `idx_scl_time` (`change_time`) USING BTREE, + CONSTRAINT `fk_scl_operation` FOREIGN KEY (`operation_id`) REFERENCES `wms_schedule_operation` (`operation_id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='排产变更日志' ROW_FORMAT=Dynamic; + SET FOREIGN_KEY_CHECKS = 1;