diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java index 0a3deeb..18ab6b2 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java @@ -379,6 +379,7 @@ public class MesXslDesktopAnonController { mesXslWeightRecord.getGrossWeight().subtract(mesXslWeightRecord.getTareWeight())); } applyWeightBillType(mesXslWeightRecord); + applyMaterialTypeDefault(mesXslWeightRecord); weightRecordService.save(mesXslWeightRecord); stompNotify.publishWeightRecordChanged("add", mesXslWeightRecord.getId()); return Result.OK("添加成功!"); @@ -396,6 +397,7 @@ public class MesXslDesktopAnonController { mesXslWeightRecord.getGrossWeight().subtract(mesXslWeightRecord.getTareWeight())); } applyWeightBillType(mesXslWeightRecord); + applyMaterialTypeDefault(mesXslWeightRecord); boolean ok = weightRecordService.updateById(mesXslWeightRecord); if (!ok) { return Result.error("数据已被他人修改,请刷新后重试"); @@ -436,6 +438,13 @@ public class MesXslDesktopAnonController { } } + private void applyMaterialTypeDefault(MesXslWeightRecord record) { + if (oConvertUtils.isEmpty(record.getMaterialType())) { + // 默认“自动”,桌面端手动勾选时会传“2” + record.setMaterialType("1"); + } + } + private void applyVehicleBelong(MesXslVehicle v) { if (oConvertUtils.isEmpty(v.getVehicleBelong())) { return; diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialEntryController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialEntryController.java new file mode 100644 index 0000000..6d878e4 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialEntryController.java @@ -0,0 +1,109 @@ +package org.jeecg.modules.xslmes.controller; + +import java.util.Arrays; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.system.query.QueryGenerator; +import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry; +import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; + +import org.jeecg.common.system.base.controller.JeecgController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.jeecg.common.aspect.annotation.AutoLog; +import org.apache.shiro.authz.annotation.RequiresPermissions; + +/** + * @Description: 原料入场记录 + * @Author: jeecg-boot + * @Date: 2026-05-07 + * @Version: V1.0 + */ +@Tag(name = "原料入场记录") +@RestController +@RequestMapping("/xslmes/mesXslRawMaterialEntry") +@Slf4j +public class MesXslRawMaterialEntryController extends JeecgController { + + @Autowired + private IMesXslRawMaterialEntryService mesXslRawMaterialEntryService; + + @Operation(summary = "原料入场记录-分页列表查询") + @GetMapping(value = "/list") + public Result> queryPageList(MesXslRawMaterialEntry mesXslRawMaterialEntry, + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest req) { + QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(mesXslRawMaterialEntry, req.getParameterMap()); + Page page = new Page<>(pageNo, pageSize); + IPage pageList = mesXslRawMaterialEntryService.page(page, queryWrapper); + return Result.OK(pageList); + } + + @AutoLog(value = "原料入场记录-添加") + @Operation(summary = "原料入场记录-添加") + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:add") + @PostMapping(value = "/add") + public Result add(@RequestBody MesXslRawMaterialEntry mesXslRawMaterialEntry) { + mesXslRawMaterialEntryService.save(mesXslRawMaterialEntry); + return Result.OK("添加成功!"); + } + + @AutoLog(value = "原料入场记录-编辑") + @Operation(summary = "原料入场记录-编辑") + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:edit") + @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST}) + public Result edit(@RequestBody MesXslRawMaterialEntry mesXslRawMaterialEntry) { + mesXslRawMaterialEntryService.updateById(mesXslRawMaterialEntry); + return Result.OK("编辑成功!"); + } + + @AutoLog(value = "原料入场记录-通过id删除") + @Operation(summary = "原料入场记录-通过id删除") + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:delete") + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam(name = "id", required = true) String id) { + mesXslRawMaterialEntryService.removeById(id); + return Result.OK("删除成功!"); + } + + @AutoLog(value = "原料入场记录-批量删除") + @Operation(summary = "原料入场记录-批量删除") + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:deleteBatch") + @DeleteMapping(value = "/deleteBatch") + public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) { + this.mesXslRawMaterialEntryService.removeByIds(Arrays.asList(ids.split(","))); + return Result.OK("批量删除成功!"); + } + + @Operation(summary = "原料入场记录-通过id查询") + @GetMapping(value = "/queryById") + public Result queryById(@RequestParam(name = "id", required = true) String id) { + MesXslRawMaterialEntry mesXslRawMaterialEntry = mesXslRawMaterialEntryService.getById(id); + if (mesXslRawMaterialEntry == null) { + return Result.error("未找到对应数据"); + } + return Result.OK(mesXslRawMaterialEntry); + } + + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:exportXls") + @RequestMapping(value = "/exportXls") + public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialEntry mesXslRawMaterialEntry) { + return super.exportXls(request, mesXslRawMaterialEntry, MesXslRawMaterialEntry.class, "原料入场记录"); + } + + @RequiresPermissions("xslmes:mes_xsl_raw_material_entry:importExcel") + @RequestMapping(value = "/importExcel", method = RequestMethod.POST) + public Result importExcel(HttpServletRequest request, HttpServletResponse response) { + return super.importExcel(request, response, MesXslRawMaterialEntry.class); + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWeightRecordController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWeightRecordController.java index 3dc88b4..4f76fb6 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWeightRecordController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWeightRecordController.java @@ -65,6 +65,7 @@ public class MesXslWeightRecordController extends JeecgController edit(@RequestBody MesXslWeightRecord mesXslWeightRecord) { computeBillType(mesXslWeightRecord); + applyMaterialTypeDefault(mesXslWeightRecord); computeNetWeight(mesXslWeightRecord); mesXslWeightRecordService.updateById(mesXslWeightRecord); stompNotifyService.publishWeightRecordChanged("edit", mesXslWeightRecord.getId()); @@ -147,4 +149,11 @@ public class MesXslWeightRecordController extends JeecgController { +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialEntryMapper.xml b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialEntryMapper.xml new file mode 100644 index 0000000..d17f69c --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialEntryMapper.xml @@ -0,0 +1,4 @@ + + + + diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialEntryService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialEntryService.java new file mode 100644 index 0000000..e0d95e7 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialEntryService.java @@ -0,0 +1,13 @@ +package org.jeecg.modules.xslmes.service; + +import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * @Description: 原料入场记录 + * @Author: jeecg-boot + * @Date: 2026-05-07 + * @Version: V1.0 + */ +public interface IMesXslRawMaterialEntryService extends IService { +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java index 8a9691e..4f3301d 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java @@ -45,6 +45,16 @@ public class MesXslStompNotifyService { publish("/topic/sync/mes-mixer-materials", "MIXER_MATERIAL_CHANGED", "mixerMaterialId", mixerMaterialId, action); } + /** 广播分类字典变更事件到 /topic/sync/sys-categories */ + public void publishSysCategoryChanged(String action, String categoryId) { + publish("/topic/sync/sys-categories", "SYS_CATEGORY_CHANGED", "categoryId", categoryId, action); + } + + /** 广播数据字典变更事件到 /topic/sync/sys-dicts */ + public void publishSysDictChanged(String action, String dictId) { + publish("/topic/sync/sys-dicts", "SYS_DICT_CHANGED", "dictId", dictId, action); + } + // ─────────────────────────── 私有辅助 ──────────────────────────── private void publish(String topic, String cmd, String idKey, String idValue, String action) { diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialEntryServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialEntryServiceImpl.java new file mode 100644 index 0000000..e266e34 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialEntryServiceImpl.java @@ -0,0 +1,17 @@ +package org.jeecg.modules.xslmes.service.impl; + +import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry; +import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialEntryMapper; +import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +/** + * @Description: 原料入场记录 + * @Author: jeecg-boot + * @Date: 2026-05-07 + * @Version: V1.0 + */ +@Service +public class MesXslRawMaterialEntryServiceImpl extends ServiceImpl implements IMesXslRawMaterialEntryService { +} diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java index a16e3ad..c93e5b2 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java @@ -28,6 +28,7 @@ import org.jeecgframework.poi.excel.entity.ImportParams; import org.jeecgframework.poi.excel.entity.enmus.ExcelType; import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -51,6 +52,8 @@ import java.util.stream.Collectors; public class SysCategoryController { @Autowired private ISysCategoryService sysCategoryService; + @Autowired + private SimpMessagingTemplate stompTemplate; /** * 分类编码0 @@ -128,6 +131,7 @@ public class SysCategoryController { try { sysCategoryService.addSysCategory(sysCategory); result.success("添加成功!"); + publishCategoryChanged("add", sysCategory.getId()); } catch (Exception e) { log.error(e.getMessage(),e); result.error500("操作失败"); @@ -149,10 +153,11 @@ public class SysCategoryController { }else { sysCategoryService.updateSysCategory(sysCategory); result.success("修改成功!"); + publishCategoryChanged("edit", sysCategory.getId()); } return result; } - + /** * 通过id删除 * @param id @@ -167,11 +172,11 @@ public class SysCategoryController { }else { this.sysCategoryService.deleteSysCategory(id); result.success("删除成功!"); + publishCategoryChanged("delete", id); } - return result; } - + /** * 批量删除 * @param ids @@ -185,10 +190,11 @@ public class SysCategoryController { }else { this.sysCategoryService.deleteSysCategory(ids); result.success("删除成功!"); + publishCategoryChanged("deleteBatch", null); } return result; } - + /** * 通过id查询 * @param id @@ -625,5 +631,16 @@ public class SysCategoryController { } } + private void publishCategoryChanged(String action, String categoryId) { + try { + String json = "{\"cmd\":\"SYS_CATEGORY_CHANGED\",\"action\":\"" + action + "\"" + + (categoryId != null ? ",\"categoryId\":\"" + categoryId + "\"" : "") + + ",\"timestamp\":" + System.currentTimeMillis() + "}"; + stompTemplate.convertAndSend("/topic/sync/sys-categories", json); + } catch (Exception e) { + log.debug("广播分类字典变更失败: {}", e.getMessage()); + } + } + } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java index 947b2bc..378ef5d 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java @@ -44,6 +44,7 @@ import io.swagger.v3.oas.annotations.Operation; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -77,6 +78,8 @@ public class SysDictController { @Autowired private RedisUtil redisUtil; @Autowired + private SimpMessagingTemplate stompTemplate; + @Autowired private ShiroRealm shiroRealm; @RequestMapping(value = "/list", method = RequestMethod.GET) @@ -502,6 +505,7 @@ public class SysDictController { sysDict.setDelFlag(CommonConstant.DEL_FLAG_0); sysDictService.save(sysDict); result.success("保存成功!"); + publishDictChanged("add", sysDict.getId()); } catch (Exception e) { log.error(e.getMessage(),e); result.error500("操作失败"); @@ -526,6 +530,7 @@ public class SysDictController { boolean ok = sysDictService.updateById(sysDict); if(ok) { result.success("编辑成功!"); + publishDictChanged("edit", sysDict.getId()); } } return result; @@ -544,6 +549,7 @@ public class SysDictController { boolean ok = sysDictService.removeById(id); if(ok) { result.success("删除成功!"); + publishDictChanged("delete", id); }else{ result.error500("删除失败!"); } @@ -565,6 +571,7 @@ public class SysDictController { }else { sysDictService.removeByIds(Arrays.asList(ids.split(","))); result.success("删除成功!"); + publishDictChanged("deleteBatch", null); } return result; } @@ -870,4 +877,15 @@ public class SysDictController { sysDictService.editDictByLowAppId(sysDictVo); return Result.ok("编辑成功"); } + + private void publishDictChanged(String action, String dictId) { + try { + String json = "{\"cmd\":\"SYS_DICT_CHANGED\",\"action\":\"" + action + "\"" + + (dictId != null ? ",\"dictId\":\"" + dictId + "\"" : "") + + ",\"timestamp\":" + System.currentTimeMillis() + "}"; + stompTemplate.convertAndSend("/topic/sync/sys-dicts", json); + } catch (Exception e) { + log.debug("广播数据字典变更失败: {}", e.getMessage()); + } + } } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java index d0fcd64..c4658da 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java @@ -20,6 +20,7 @@ import org.jeecg.modules.system.entity.SysDictItem; import org.jeecg.modules.system.service.ISysDictItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -48,6 +49,8 @@ public class SysDictItemController { @Autowired private ISysDictItemService sysDictItemService; + @Autowired + private SimpMessagingTemplate stompTemplate; /** * @功能:查询字典数据 @@ -83,6 +86,7 @@ public class SysDictItemController { sysDictItem.setCreateTime(new Date()); sysDictItemService.save(sysDictItem); result.success("保存成功!"); + publishDictChanged("add", sysDictItem.getId()); } catch (Exception e) { log.error(e.getMessage(),e); result.error500("操作失败"); @@ -109,11 +113,12 @@ public class SysDictItemController { //TODO 返回false说明什么? if(ok) { result.success("编辑成功!"); + publishDictChanged("edit", sysDictItem.getId()); } } return result; } - + /** * @功能:删除字典数据 * @param id @@ -131,11 +136,12 @@ public class SysDictItemController { boolean ok = sysDictItemService.removeById(id); if(ok) { result.success("删除成功!"); + publishDictChanged("delete", id); } } return result; } - + /** * @功能:批量删除字典数据 * @param ids @@ -151,6 +157,7 @@ public class SysDictItemController { }else { this.sysDictItemService.removeByIds(Arrays.asList(ids.split(","))); result.success("删除成功!"); + publishDictChanged("deleteBatch", null); } return result; } @@ -182,5 +189,15 @@ public class SysDictItemController { return Result.error("该值不可用,系统中已存在!"); } } - + + private void publishDictChanged(String action, String itemId) { + try { + String json = "{\"cmd\":\"SYS_DICT_CHANGED\",\"action\":\"" + action + "\"" + + (itemId != null ? ",\"dictId\":\"" + itemId + "\"" : "") + "}"; + stompTemplate.convertAndSend("/topic/sync/sys-dicts", json); + } catch (Exception e) { + log.warn("[数据字典] STOMP推送失败:{}", e.getMessage()); + } + } + } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_36__mes_xsl_weight_record_mixer_material.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_36__mes_xsl_weight_record_mixer_material.sql new file mode 100644 index 0000000..51187f4 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_36__mes_xsl_weight_record_mixer_material.sql @@ -0,0 +1,3 @@ +ALTER TABLE mes_xsl_weight_record + ADD COLUMN mixer_material_ids VARCHAR(2000) NULL COMMENT '密炼物料ID(分号分隔)', + ADD COLUMN mixer_material_names VARCHAR(2000) NULL COMMENT '密炼物料名称(分号分隔)'; diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_37__drop_weight_record_goods_name.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_37__drop_weight_record_goods_name.sql new file mode 100644 index 0000000..d0f1939 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_37__drop_weight_record_goods_name.sql @@ -0,0 +1 @@ +ALTER TABLE mes_xsl_weight_record DROP COLUMN goods_name; diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_38__mes_xsl_weight_record_material_type.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_38__mes_xsl_weight_record_material_type.sql new file mode 100644 index 0000000..c16f263 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_38__mes_xsl_weight_record_material_type.sql @@ -0,0 +1,48 @@ +-- 磅单新增物料类型字段 + 字典(幂等) + +-- 1) 表结构:物料类型(1自动 2手动) +SET @material_type_exists := ( + SELECT COUNT(1) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'mes_xsl_weight_record' + AND COLUMN_NAME = 'material_type' +); +SET @ddl_material_type := IF( + @material_type_exists = 0, + 'ALTER TABLE `mes_xsl_weight_record` ADD COLUMN `material_type` varchar(10) DEFAULT NULL COMMENT ''物料类型(字典xslmes_weight_material_type:1自动 2手动)'' AFTER `mixer_material_names`', + 'SELECT 1' +); +PREPARE stmt_material_type FROM @ddl_material_type; +EXECUTE stmt_material_type; +DEALLOCATE PREPARE stmt_material_type; + +-- 2) 历史数据默认自动 +UPDATE `mes_xsl_weight_record` +SET `material_type` = '1' +WHERE `material_type` IS NULL OR `material_type` = ''; + +-- 3) 字典主表 +INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`) +SELECT REPLACE(UUID(), '-', ''), 'MES磅单物料类型', 'xslmes_weight_material_type', '磅单密炼物料来源类型:自动/手动', 0, 'admin', NOW(), 0, 0 +WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_weight_material_type' AND `del_flag` = 0); + +-- 4) 字典项:自动 +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '自动', '1', 1, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_weight_material_type' + AND NOT EXISTS ( + SELECT 1 FROM `sys_dict_item` i + WHERE i.`dict_id` = d.`id` AND i.`item_value` = '1' + ); + +-- 5) 字典项:手动 +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '手动', '2', 2, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_weight_material_type' + AND NOT EXISTS ( + SELECT 1 FROM `sys_dict_item` i + WHERE i.`dict_id` = d.`id` AND i.`item_value` = '2' + ); diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_39__mes_xsl_raw_material_entry.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_39__mes_xsl_raw_material_entry.sql new file mode 100644 index 0000000..91bc397 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_39__mes_xsl_raw_material_entry.sql @@ -0,0 +1,155 @@ +-- 原料入场记录:建表 + 字典 + 菜单权限(幂等) + +-- ===================== 1. 建表 ===================== +CREATE TABLE IF NOT EXISTS `mes_xsl_raw_material_entry` ( + `id` varchar(36) NOT NULL COMMENT '主键', + `barcode` varchar(100) DEFAULT NULL COMMENT '条码', + `batch_no` varchar(100) DEFAULT NULL COMMENT '批次号', + `entry_time` datetime DEFAULT NULL COMMENT '入场时间', + `weight_record_id` varchar(36) DEFAULT NULL COMMENT '榜单ID', + `bill_no` varchar(100) DEFAULT NULL COMMENT '榜单号', + `material_id` varchar(36) DEFAULT NULL COMMENT '物料ID', + `material_name` varchar(200) DEFAULT NULL COMMENT '物料名称', + `supply_customer` varchar(200) DEFAULT NULL COMMENT '供料客户', + `supplier_id` varchar(36) DEFAULT NULL COMMENT '供应商ID', + `supplier_name` varchar(200) DEFAULT NULL COMMENT '供应商名称', + `manufacturer_material_name` varchar(200) DEFAULT NULL COMMENT '厂家物料名称', + `shelf_life` varchar(100) DEFAULT NULL COMMENT '保质期', + `total_weight` decimal(10,2) DEFAULT NULL COMMENT '总重(KG)', + `total_portions` int DEFAULT NULL COMMENT '总份数', + `portion_weight` decimal(10,2) DEFAULT NULL COMMENT '每份总重(KG)', + `portion_packages` int DEFAULT NULL COMMENT '每份包数', + `test_result` varchar(10) DEFAULT NULL COMMENT '检测结果(字典 xslmes_test_result:0未检 1合格 2不合格)', + `test_status` varchar(10) DEFAULT NULL COMMENT '检测状态(字典 xslmes_test_status:0送样 1已批准)', + `print_flag` varchar(10) DEFAULT NULL COMMENT '打印标记(字典 xslmes_print_flag:1已打印 0未打印)', + `stock_balance` varchar(2) DEFAULT NULL COMMENT '入库结存(字典 yn:1是 0否)', + `warehouse_location` varchar(100) DEFAULT NULL COMMENT '库位', + `unload_operator` varchar(100) DEFAULT NULL COMMENT '卸货人', + `is_special_adoption` varchar(2) DEFAULT NULL COMMENT '是否特采(字典 yn:1是 0否)', + `special_adoption_operator` varchar(100) DEFAULT NULL COMMENT '特采操作人', + `special_adoption_time` datetime DEFAULT NULL COMMENT '特采时间', + `special_adoption_reason` text COMMENT '特采原因', + `status` varchar(10) DEFAULT NULL COMMENT '状态(字典 xslmes_entry_status)', + `remark` text COMMENT '备注', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `tenant_id` int DEFAULT 1002 COMMENT '租户ID', + PRIMARY KEY (`id`), + KEY `idx_rme_barcode` (`barcode`), + KEY `idx_rme_batch_no` (`batch_no`), + KEY `idx_rme_entry_time` (`entry_time`), + KEY `idx_rme_bill_no` (`bill_no`), + KEY `idx_rme_material_id` (`material_id`), + KEY `idx_rme_supplier_id` (`supplier_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='原料入场记录'; + +-- ===================== 2. 检测结果字典 ===================== +INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`) +SELECT REPLACE(UUID(), '-', ''), 'MES检测结果', 'xslmes_test_result', '原料入场检测结果:未检/合格/不合格', 0, 'admin', NOW(), 0, 1002 +WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_test_result' AND `del_flag` = 0); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '未检', '0', 1, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_test_result' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '0'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '合格', '1', 2, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_test_result' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '1'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '不合格', '2', 3, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_test_result' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '2'); + +-- ===================== 3. 检测状态字典 ===================== +INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`) +SELECT REPLACE(UUID(), '-', ''), 'MES检测状态', 'xslmes_test_status', '原料入场检测状态:送样/已批准', 0, 'admin', NOW(), 0, 1002 +WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_test_status' AND `del_flag` = 0); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '送样', '0', 1, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_test_status' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '0'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '已批准', '1', 2, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_test_status' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '1'); + +-- ===================== 4. 打印标记字典 ===================== +INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`) +SELECT REPLACE(UUID(), '-', ''), 'MES打印标记', 'xslmes_print_flag', '原料入场打印标记:已打印/未打印', 0, 'admin', NOW(), 0, 1002 +WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_print_flag' AND `del_flag` = 0); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '已打印', '1', 1, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_print_flag' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '1'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '未打印', '0', 2, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_print_flag' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '0'); + +-- ===================== 3. 入场记录状态字典 ===================== +INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`) +SELECT REPLACE(UUID(), '-', ''), 'MES入场记录状态', 'xslmes_entry_status', '原料入场记录状态:待处理/已入库/已拒收', 0, 'admin', NOW(), 0, 1002 +WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_entry_status' AND `del_flag` = 0); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '待处理', '0', 1, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_entry_status' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '0'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '已入库', '1', 2, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_entry_status' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '1'); + +INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`) +SELECT REPLACE(UUID(), '-', ''), d.id, '已拒收', '2', 3, 1, 'admin', NOW() +FROM `sys_dict` d +WHERE d.`dict_code` = 'xslmes_entry_status' + AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = '2'); + +-- ===================== 4. 菜单权限(父菜单:MES XSL 1900000000000000300)===================== +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000530', '1900000000000000300', '原料入场记录', '/xslmes/mesXslRawMaterialEntry', 'xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntryList', 1, NULL, NULL, 1, NULL, '0', 11.00, 0, 'ant-design:file-text-outlined', 0, 1, 0, 0, '原料入场记录', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000530'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000531', '1900000000000000530', '添加', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:add', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000531'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000532', '1900000000000000530', '编辑', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:edit', '1', 2.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000532'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000533', '1900000000000000530', '删除', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:delete', '1', 3.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000533'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000534', '1900000000000000530', '批量删除', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:deleteBatch', '1', 4.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000534'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000535', '1900000000000000530', '导出', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:exportXls', '1', 5.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000535'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) +SELECT '1900000000000000536', '1900000000000000530', '导入', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry:importExcel', '1', 6.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000536'); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.api.ts new file mode 100644 index 0000000..7197324 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.api.ts @@ -0,0 +1,45 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/xslmes/mesXslRawMaterialEntry/list', + save = '/xslmes/mesXslRawMaterialEntry/add', + edit = '/xslmes/mesXslRawMaterialEntry/edit', + deleteOne = '/xslmes/mesXslRawMaterialEntry/delete', + deleteBatch = '/xslmes/mesXslRawMaterialEntry/deleteBatch', + importExcel = '/xslmes/mesXslRawMaterialEntry/importExcel', + exportXls = '/xslmes/mesXslRawMaterialEntry/exportXls', +} + +export const getExportUrl = Api.exportXls; +export const getImportUrl = Api.importExcel; + +export const list = (params) => defHttp.get({ url: Api.list, params }); + +export const deleteOne = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +export const saveOrUpdate = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.data.ts b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.data.ts new file mode 100644 index 0000000..3bf091f --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.data.ts @@ -0,0 +1,263 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { title: '条码', align: 'center', dataIndex: 'barcode', width: 160 }, + { title: '批次号', align: 'center', dataIndex: 'batchNo', width: 140 }, + { + title: '入场时间', + align: 'center', + dataIndex: 'entryTime', + width: 160, + customRender: ({ text }) => (!text ? '' : text.length > 19 ? text.substring(0, 19) : text), + }, + { title: '榜单号', align: 'center', dataIndex: 'billNo', width: 160 }, + { title: '物料名称', align: 'center', dataIndex: 'materialName', width: 140, ellipsis: true }, + { title: '供料客户', align: 'center', dataIndex: 'supplyCustomer', width: 140, ellipsis: true }, + { title: '供应商名称', align: 'center', dataIndex: 'supplierName', width: 140, ellipsis: true }, + { title: '厂家物料名称', align: 'center', dataIndex: 'manufacturerMaterialName', width: 140, ellipsis: true }, + { title: '保质期', align: 'center', dataIndex: 'shelfLife', width: 100 }, + { title: '总重(KG)', align: 'center', dataIndex: 'totalWeight', width: 100 }, + { title: '总份数', align: 'center', dataIndex: 'totalPortions', width: 80 }, + { title: '每份总重(KG)', align: 'center', dataIndex: 'portionWeight', width: 110 }, + { title: '每份包数', align: 'center', dataIndex: 'portionPackages', width: 80 }, + { title: '检测结果', align: 'center', dataIndex: 'testResult_dictText', width: 90 }, + { title: '检测状态', align: 'center', dataIndex: 'testStatus_dictText', width: 90 }, + { title: '打印标记', align: 'center', dataIndex: 'printFlag_dictText', width: 90 }, + { title: '入库结存', align: 'center', dataIndex: 'stockBalance_dictText', width: 90 }, + { title: '库位', align: 'center', dataIndex: 'warehouseLocation', width: 100 }, + { title: '卸货人', align: 'center', dataIndex: 'unloadOperator', width: 90 }, + { title: '是否特采', align: 'center', dataIndex: 'isSpecialAdoption_dictText', width: 80 }, + { title: '特采操作人', align: 'center', dataIndex: 'specialAdoptionOperator', width: 100 }, + { + title: '特采时间', + align: 'center', + dataIndex: 'specialAdoptionTime', + width: 160, + customRender: ({ text }) => (!text ? '' : text.length > 19 ? text.substring(0, 19) : text), + }, + { title: '特采原因', align: 'center', dataIndex: 'specialAdoptionReason', width: 160, ellipsis: true }, + { title: '状态', align: 'center', dataIndex: 'status_dictText', width: 80 }, + { title: '备注', align: 'center', dataIndex: 'remark', width: 160, ellipsis: true }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + width: 160, + customRender: ({ text }) => (!text ? '' : text.length > 19 ? text.substring(0, 19) : text), + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { label: '条码', field: 'barcode', component: 'JInput', colProps: { span: 6 } }, + { label: '批次号', field: 'batchNo', component: 'JInput', colProps: { span: 6 } }, + { label: '榜单号', field: 'billNo', component: 'JInput', colProps: { span: 6 } }, + { label: '物料名称', field: 'materialName', component: 'JInput', colProps: { span: 6 } }, + { label: '供应商名称', field: 'supplierName', component: 'JInput', colProps: { span: 6 } }, + { + label: '检测结果', + field: 'testResult', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_test_result' }, + colProps: { span: 6 }, + }, + { + label: '检测状态', + field: 'testStatus', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_test_status' }, + colProps: { span: 6 }, + }, + { + label: '是否特采', + field: 'isSpecialAdoption', + component: 'JDictSelectTag', + componentProps: { dictCode: 'yn' }, + colProps: { span: 6 }, + }, + { + label: '状态', + field: 'status', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_entry_status' }, + colProps: { span: 6 }, + }, + { + label: '入场时间', + field: 'entryTime', + component: 'RangePicker', + componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' }, + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { label: '', field: 'id', component: 'Input', show: false }, + { label: '', field: 'weightRecordId', component: 'Input', show: false }, + { label: '', field: 'materialId', component: 'Input', show: false }, + { label: '', field: 'supplierId', component: 'Input', show: false }, + { + label: '条码', + field: 'barcode', + component: 'Input', + componentProps: { placeholder: '请输入条码' }, + }, + { + label: '批次号', + field: 'batchNo', + component: 'Input', + componentProps: { placeholder: '请输入批次号' }, + }, + { + label: '入场时间', + field: 'entryTime', + component: 'DatePicker', + componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss', placeholder: '请选择入场时间' }, + }, + { + label: '榜单号', + field: 'billNo', + component: 'Input', + componentProps: { placeholder: '请输入榜单号' }, + }, + { + label: '物料名称', + field: 'materialName', + component: 'Input', + componentProps: { placeholder: '请输入物料名称' }, + }, + { + label: '供料客户', + field: 'supplyCustomer', + component: 'Input', + componentProps: { placeholder: '请输入供料客户' }, + }, + { + label: '供应商名称', + field: 'supplierName', + component: 'Input', + componentProps: { placeholder: '请输入供应商名称' }, + }, + { + label: '厂家物料名称', + field: 'manufacturerMaterialName', + component: 'Input', + componentProps: { placeholder: '请输入厂家物料名称' }, + }, + { + label: '保质期', + field: 'shelfLife', + component: 'Input', + componentProps: { placeholder: '请输入保质期' }, + }, + { + label: '总重(KG)', + field: 'totalWeight', + component: 'InputNumber', + componentProps: { min: 0, precision: 2, placeholder: '请输入总重', style: { width: '100%' } }, + }, + { + label: '总份数', + field: 'totalPortions', + component: 'InputNumber', + componentProps: { min: 0, placeholder: '请输入总份数', style: { width: '100%' } }, + }, + { + label: '每份总重(KG)', + field: 'portionWeight', + component: 'InputNumber', + componentProps: { min: 0, precision: 2, placeholder: '请输入每份总重', style: { width: '100%' } }, + }, + { + label: '每份包数', + field: 'portionPackages', + component: 'InputNumber', + componentProps: { min: 0, placeholder: '请输入每份包数', style: { width: '100%' } }, + }, + { + label: '检测结果', + field: 'testResult', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_test_result', placeholder: '请选择检测结果' }, + }, + { + label: '检测状态', + field: 'testStatus', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_test_status', placeholder: '请选择检测状态' }, + }, + { + label: '打印标记', + field: 'printFlag', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_print_flag', placeholder: '请选择' }, + }, + { + label: '入库结存', + field: 'stockBalance', + component: 'JDictSelectTag', + componentProps: { dictCode: 'yn', placeholder: '请选择' }, + }, + { + label: '库位', + field: 'warehouseLocation', + component: 'Input', + componentProps: { placeholder: '请输入库位' }, + }, + { + label: '卸货人', + field: 'unloadOperator', + component: 'Input', + componentProps: { placeholder: '请输入卸货人' }, + }, + { + label: '是否特采', + field: 'isSpecialAdoption', + component: 'JDictSelectTag', + componentProps: { dictCode: 'yn', placeholder: '请选择' }, + }, + { + label: '特采操作人', + field: 'specialAdoptionOperator', + component: 'Input', + componentProps: { placeholder: '请输入特采操作人' }, + }, + { + label: '特采时间', + field: 'specialAdoptionTime', + component: 'DatePicker', + componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss', placeholder: '请选择特采时间' }, + }, + { + label: '特采原因', + field: 'specialAdoptionReason', + component: 'InputTextArea', + componentProps: { rows: 3, placeholder: '请输入特采原因' }, + colProps: { span: 24 }, + }, + { + label: '状态', + field: 'status', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_entry_status', placeholder: '请选择状态' }, + }, + { + label: '备注', + field: 'remark', + component: 'InputTextArea', + componentProps: { rows: 3, placeholder: '请输入备注' }, + colProps: { span: 24 }, + }, +]; + +export const superQuerySchema = { + barcode: { title: '条码', order: 0, view: 'text' }, + batchNo: { title: '批次号', order: 1, view: 'text' }, + entryTime: { title: '入场时间', order: 2, view: 'datetime' }, + billNo: { title: '榜单号', order: 3, view: 'text' }, + materialName: { title: '物料名称', order: 4, view: 'text' }, + supplierName: { title: '供应商名称', order: 5, view: 'text' }, + totalWeight: { title: '总重(KG)', order: 6, view: 'number' }, + testStatus: { title: '检测状态', order: 7, view: 'list', dictCode: 'xslmes_test_status' }, + isSpecialAdoption: { title: '是否特采', order: 8, view: 'list', dictCode: 'yn' }, + status: { title: '状态', order: 9, view: 'list', dictCode: 'xslmes_entry_status' }, +}; diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntryList.vue b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntryList.vue new file mode 100644 index 0000000..c4979a0 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntryList.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/components/MesXslRawMaterialEntryModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/components/MesXslRawMaterialEntryModal.vue new file mode 100644 index 0000000..a3d7648 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/components/MesXslRawMaterialEntryModal.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts b/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts index 24fff98..f4fd5d5 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts @@ -13,7 +13,8 @@ export const columns: BasicColumn[] = [ { title: '车号', align: 'center', dataIndex: 'plateNumber', width: 120 }, { title: '发货单位', align: 'center', dataIndex: 'senderUnit', width: 160, ellipsis: true }, { title: '收货单位', align: 'center', dataIndex: 'receiverUnit', width: 160, ellipsis: true }, - { title: '货物名称', align: 'center', dataIndex: 'goodsName', width: 140, ellipsis: true }, + { title: '密炼物料', align: 'center', dataIndex: 'mixerMaterialNames', width: 160, ellipsis: true }, + { title: '类型', align: 'center', dataIndex: 'materialType_dictText', width: 90 }, { title: '毛重(KG)', align: 'center', dataIndex: 'grossWeight', width: 100 }, { title: '皮重(KG)', align: 'center', dataIndex: 'tareWeight', width: 100 }, { title: '净重(KG)', align: 'center', dataIndex: 'netWeight', width: 100 }, @@ -77,10 +78,17 @@ export const formSchema: FormSchema[] = [ componentProps: { placeholder: '出厂时自动带出客户简称' }, }, { - label: '货物名称', - field: 'goodsName', + label: '密炼物料', + field: 'mixerMaterialNames', component: 'Input', - componentProps: { placeholder: '请输入货物名称' }, + componentProps: { placeholder: '由桌面端称重操作填入,可手动编辑' }, + }, + { + label: '类型', + field: 'materialType', + component: 'JDictSelectTag', + componentProps: { dictCode: 'xslmes_weight_material_type' }, + defaultValue: '1', }, { label: '毛重(KG)', @@ -119,6 +127,7 @@ export const superQuerySchema = { inoutDirection: { title: '进出方向', order: 1, view: 'list', dictCode: 'xslmes_inout_direction' }, plateNumber: { title: '车号', order: 2, view: 'text' }, weighDate: { title: '称重日期', order: 3, view: 'date' }, - grossWeight: { title: '毛重(KG)', order: 4, view: 'number' }, - netWeight: { title: '净重(KG)', order: 5, view: 'number' }, + materialType: { title: '类型', order: 4, view: 'list', dictCode: 'xslmes_weight_material_type' }, + grossWeight: { title: '毛重(KG)', order: 5, view: 'number' }, + netWeight: { title: '净重(KG)', order: 6, view: 'number' }, }; diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecordList.vue b/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecordList.vue index 77fab00..1e75202 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecordList.vue +++ b/jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecordList.vue @@ -46,6 +46,8 @@ api: list, columns, canResize: true, + // 更新列配置缓存 key,避免用户历史列顺序覆盖默认顺序 + tableSetting: { cacheKey: 'mesXslWeightRecordColumns_v20260507' }, formConfig: { schemas: searchFormSchema, autoSubmitOnEnter: true, diff --git a/yy-admin-master/YY.Admin.Core/Core/Events/CategoryChangedEvent.cs b/yy-admin-master/YY.Admin.Core/Core/Events/CategoryChangedEvent.cs new file mode 100644 index 0000000..59229e0 --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Core/Events/CategoryChangedEvent.cs @@ -0,0 +1,11 @@ +using Prism.Events; + +namespace YY.Admin.Core.Events; + +public class CategoryChangedPayload +{ + public string Action { get; set; } = string.Empty; + public string? CategoryId { get; set; } +} + +public class CategoryChangedEvent : PubSubEvent { } diff --git a/yy-admin-master/YY.Admin.Core/Core/Events/DictChangedEvent.cs b/yy-admin-master/YY.Admin.Core/Core/Events/DictChangedEvent.cs new file mode 100644 index 0000000..d7c8738 --- /dev/null +++ b/yy-admin-master/YY.Admin.Core/Core/Events/DictChangedEvent.cs @@ -0,0 +1,11 @@ +using Prism.Events; + +namespace YY.Admin.Core.Events; + +public class DictChangedPayload +{ + public string Action { get; set; } = string.Empty; + public string? DictId { get; set; } +} + +public class DictChangedEvent : PubSubEvent { } diff --git a/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs b/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs index a736e9b..ecca2b7 100644 --- a/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs +++ b/yy-admin-master/YY.Admin.Core/Core/Services/IMixerMaterialService.cs @@ -4,6 +4,8 @@ namespace YY.Admin.Core.Services; public record MixerMaterialPageResult(List Records, long Total, int Current, int Size); +public record MaterialCategoryNode(string Id, string? Name, string? Code, List Children); + public interface IMixerMaterialService { Task PageAsync( @@ -21,7 +23,10 @@ public interface IMixerMaterialService Task EditAsync(MesMixerMaterial material, CancellationToken ct = default); Task DeleteAsync(string id, CancellationToken ct = default); Task DeleteBatchAsync(string ids, CancellationToken ct = default); + Task>> GetMajorCategoryOptionsAsync(CancellationToken ct = default); Task>> GetMinorCategoryOptionsAsync(string majorCategoryId, CancellationToken ct = default); -} + Task> GetMaterialCategoryTreeAsync(CancellationToken ct = default); + Task SyncFromRemoteAsync(CancellationToken ct = default); +} diff --git a/yy-admin-master/YY.Admin.Core/Core/Services/IWeightRecordService.cs b/yy-admin-master/YY.Admin.Core/Core/Services/IWeightRecordService.cs index 3015a36..a58722f 100644 --- a/yy-admin-master/YY.Admin.Core/Core/Services/IWeightRecordService.cs +++ b/yy-admin-master/YY.Admin.Core/Core/Services/IWeightRecordService.cs @@ -9,8 +9,8 @@ public interface IWeightRecordService string? filterBillNo = null, string? filterPlateNumber = null, string? filterInoutDirection = null, - string? filterGoodsName = null, string? filterDriverName = null, + string? filterMixerMaterialName = null, CancellationToken ct = default); Task GetByIdAsync(string id, CancellationToken ct = default); diff --git a/yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs b/yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs index fc49b25..42a5e1b 100644 --- a/yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs +++ b/yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs @@ -25,9 +25,6 @@ public class MesXslWeightRecord /// 收货单位(出厂时为客户简称) public string? ReceiverUnit { get; set; } - /// 货物名称 - public string? GoodsName { get; set; } - /// 毛重(KG),实际称量 public double? GrossWeight { get; set; } @@ -46,6 +43,15 @@ public class MesXslWeightRecord /// 单据类型:1已称毛重 2称重完成 public string? BillType { get; set; } + /// 密炼物料ID(分号分隔) + public string? MixerMaterialIds { get; set; } + + /// 密炼物料名称(分号分隔) + public string? MixerMaterialNames { get; set; } + + /// 物料类型:1自动 2手动 + public string? MaterialType { get; set; } + public int? TenantId { get; set; } public string? CreateBy { get; set; } public DateTime? CreateTime { get; set; } @@ -58,4 +64,7 @@ public class MesXslWeightRecord /// 单据类型显示文本(由 ViewModel 填充) public string BillTypeText { get; set; } = string.Empty; + + /// 物料类型显示文本(由 ViewModel 填充) + public string MaterialTypeText { get; set; } = string.Empty; } diff --git a/yy-admin-master/YY.Admin.Services/Service/Category/CategorySyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/Category/CategorySyncCoordinator.cs new file mode 100644 index 0000000..fbcd25c --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Category/CategorySyncCoordinator.cs @@ -0,0 +1,80 @@ +using Prism.Events; +using System.Text.Json; +using YY.Admin.Core; +using YY.Admin.Core.Events; +using YY.Admin.Core.Services; + +namespace YY.Admin.Services.Service.Category; + +public class CategorySyncCoordinator : ISingletonDependency +{ + private readonly IEventAggregator _eventAggregator; + private readonly IJeecgCategorySyncService _categorySyncService; + private readonly ILoggerService _logger; + + public CategorySyncCoordinator( + IEventAggregator eventAggregator, + IJeecgCategorySyncService categorySyncService, + ILoggerService logger) + { + _eventAggregator = eventAggregator; + _categorySyncService = categorySyncService; + _logger = logger; + + _eventAggregator.GetEvent() + .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); + _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + + _logger.Information("[分类字典] CategorySyncCoordinator 已启动"); + _ = Task.Run(() => SyncAndPublishAsync("startup", null)); + } + + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + if (!payload.IsOnline) return; + _logger.Information("[分类字典] 网络恢复,触发补偿刷新"); + _ = Task.Run(() => SyncAndPublishAsync("reconnect", null)); + } + + private void OnRemoteCommand(RemoteCommandPayload payload) + { + try + { + var json = payload.CommandJson ?? string.Empty; + if (string.IsNullOrWhiteSpace(json)) return; + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("cmd", out var cmdEl)) return; + if (!cmdEl.GetString().Equals("SYS_CATEGORY_CHANGED", StringComparison.OrdinalIgnoreCase)) return; + + doc.RootElement.TryGetProperty("action", out var actionEl); + doc.RootElement.TryGetProperty("categoryId", out var idEl); + var action = actionEl.GetString() ?? string.Empty; + var categoryId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null; + + _logger.Information($"[分类字典] 收到变更信号 action={action}"); + _ = Task.Run(() => SyncAndPublishAsync(action, categoryId)); + } + catch (Exception ex) + { + _logger.Warning($"[分类字典] 处理STOMP命令失败:{ex.Message}"); + } + } + + private async Task SyncAndPublishAsync(string action, string? categoryId) + { + try + { + var count = await _categorySyncService.SyncFromJeecgAsync().ConfigureAwait(false); + if (count > 0) + _logger.Information($"[分类字典] 同步完成,共处理 {count} 条"); + + _eventAggregator.GetEvent() + .Publish(new CategoryChangedPayload { Action = action, CategoryId = categoryId }); + } + catch (Exception ex) + { + _logger.Warning($"[分类字典] 同步失败:{ex.Message}"); + } + } +} diff --git a/yy-admin-master/YY.Admin.Services/Service/Dict/DictSyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/Dict/DictSyncCoordinator.cs new file mode 100644 index 0000000..396aee6 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/Dict/DictSyncCoordinator.cs @@ -0,0 +1,80 @@ +using Prism.Events; +using System.Text.Json; +using YY.Admin.Core; +using YY.Admin.Core.Events; +using YY.Admin.Core.Services; + +namespace YY.Admin.Services.Service.Dict; + +public class DictSyncCoordinator : ISingletonDependency +{ + private readonly IEventAggregator _eventAggregator; + private readonly IJeecgDictSyncService _dictSyncService; + private readonly ILoggerService _logger; + + public DictSyncCoordinator( + IEventAggregator eventAggregator, + IJeecgDictSyncService dictSyncService, + ILoggerService logger) + { + _eventAggregator = eventAggregator; + _dictSyncService = dictSyncService; + _logger = logger; + + _eventAggregator.GetEvent() + .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); + _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + + _logger.Information("[数据字典] DictSyncCoordinator 已启动"); + _ = Task.Run(() => SyncAndPublishAsync("startup", null)); + } + + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + if (!payload.IsOnline) return; + _logger.Information("[数据字典] 网络恢复,触发补偿刷新"); + _ = Task.Run(() => SyncAndPublishAsync("reconnect", null)); + } + + private void OnRemoteCommand(RemoteCommandPayload payload) + { + try + { + var json = payload.CommandJson ?? string.Empty; + if (string.IsNullOrWhiteSpace(json)) return; + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("cmd", out var cmdEl)) return; + if (!cmdEl.GetString().Equals("SYS_DICT_CHANGED", StringComparison.OrdinalIgnoreCase)) return; + + doc.RootElement.TryGetProperty("action", out var actionEl); + doc.RootElement.TryGetProperty("dictId", out var idEl); + var action = actionEl.GetString() ?? string.Empty; + var dictId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null; + + _logger.Information($"[数据字典] 收到变更信号 action={action}"); + _ = Task.Run(() => SyncAndPublishAsync(action, dictId)); + } + catch (Exception ex) + { + _logger.Warning($"[数据字典] 处理STOMP命令失败:{ex.Message}"); + } + } + + private async Task SyncAndPublishAsync(string action, string? dictId) + { + try + { + var count = await _dictSyncService.SyncFromJeecgAsync().ConfigureAwait(false); + if (count > 0) + _logger.Information($"[数据字典] 同步完成,共处理 {count} 条"); + + _eventAggregator.GetEvent() + .Publish(new DictChangedPayload { Action = action, DictId = dictId }); + } + catch (Exception ex) + { + _logger.Warning($"[数据字典] 同步失败:{ex.Message}"); + } + } +} diff --git a/yy-admin-master/YY.Admin.Services/Service/Dict/IJeecgDictSyncService.cs b/yy-admin-master/YY.Admin.Services/Service/Dict/IJeecgDictSyncService.cs index d1e16b5..1937edb 100644 --- a/yy-admin-master/YY.Admin.Services/Service/Dict/IJeecgDictSyncService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/Dict/IJeecgDictSyncService.cs @@ -13,4 +13,9 @@ public interface IJeecgDictSyncService /// 从桌面端本地字典镜像表读取指定字典编码的选项。 /// Task>> GetDictOptionsAsync(string dictCode, bool includeAll = false); + + /// + /// 获取所有字典分组(字典编码 + 字典名称),用于左侧树形导航。 + /// + Task>> GetDictGroupsAsync(); } diff --git a/yy-admin-master/YY.Admin.Services/Service/Dict/JeecgDictSyncService.cs b/yy-admin-master/YY.Admin.Services/Service/Dict/JeecgDictSyncService.cs index 4c3319b..51ecfb4 100644 --- a/yy-admin-master/YY.Admin.Services/Service/Dict/JeecgDictSyncService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/Dict/JeecgDictSyncService.cs @@ -26,7 +26,7 @@ public class JeecgDictSyncService : IJeecgDictSyncService, ISingletonDependency { var statusFilter = input.Status.HasValue ? (int?)input.Status.Value : null; var query = _dbContext.Queryable().ClearFilter() - .WhereIF(!string.IsNullOrWhiteSpace(input.DictCode), x => x.DictCode != null && x.DictCode.Contains(input.DictCode)) + .WhereIF(!string.IsNullOrWhiteSpace(input.DictCode), x => x.DictCode != null && x.DictCode == input.DictCode) .WhereIF(!string.IsNullOrWhiteSpace(input.DictName), x => x.DictName != null && x.DictName.Contains(input.DictName)) .WhereIF(!string.IsNullOrWhiteSpace(input.ItemText), x => x.ItemText != null && x.ItemText.Contains(input.ItemText)) .WhereIF(!string.IsNullOrWhiteSpace(input.ItemValue), x => x.ItemValue != null && x.ItemValue.Contains(input.ItemValue)); @@ -164,6 +164,22 @@ public class JeecgDictSyncService : IJeecgDictSyncService, ISingletonDependency return synced; } + public async Task>> GetDictGroupsAsync() + { + var rows = await _dbContext.Queryable() + .ClearFilter() + .Where(x => x.DictCode != null) + .OrderBy(x => x.DictCode) + .Select(x => new JeecgSysDictItem { DictCode = x.DictCode, DictName = x.DictName }) + .ToListAsync(); + + return rows + .Where(x => !string.IsNullOrWhiteSpace(x.DictCode)) + .DistinctBy(x => x.DictCode) + .Select(x => new KeyValuePair(x.DictCode!, x.DictName ?? x.DictCode!)) + .ToList(); + } + public async Task>> GetDictOptionsAsync(string dictCode, bool includeAll = false) { var result = new List>(); diff --git a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs index 62711a6..c05fb60 100644 --- a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialService.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.Configuration; +using SqlSugar; +using System.IO; using System.Net.Http; using System.Text; using System.Text.Json; @@ -14,7 +16,12 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; + private readonly ISqlSugarClient _dbContext; private readonly ILoggerService _logger; + private readonly SemaphoreSlim _syncLock = new(1, 1); + private readonly object _cacheLock = new(); + private readonly string _cacheFilePath; + private List _localCache = []; private static readonly JsonSerializerOptions _jsonOpts = new() { @@ -26,36 +33,158 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency public MixerMaterialService( IHttpClientFactory httpClientFactory, IConfiguration configuration, + ISqlSugarClient dbContext, ILoggerService logger) { _httpClientFactory = httpClientFactory; _configuration = configuration; + _dbContext = dbContext; _logger = logger; + + var appDataDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "YY.Admin", "sync-cache"); + Directory.CreateDirectory(appDataDir); + _cacheFilePath = Path.Combine(appDataDir, "mixer-material-cache.json"); + + _ = LoadCacheAsync(); } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); - public async Task PageAsync( - int pageNo, - int pageSize, - string? materialCode = null, - string? materialName = null, - string? erpCode = null, - string? majorCategoryId = null, - string? minorCategoryId = null, + private async Task LoadCacheAsync() + { + try + { + if (!File.Exists(_cacheFilePath)) return; + var json = await File.ReadAllTextAsync(_cacheFilePath).ConfigureAwait(false); + var list = JsonSerializer.Deserialize>(json, _jsonOpts); + if (list != null) + lock (_cacheLock) { _localCache = list; } + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 加载本地缓存失败:{ex.Message}"); + } + } + + private async Task SaveCacheAsync(List list) + { + try + { + var json = JsonSerializer.Serialize(list, _jsonOpts); + await File.WriteAllTextAsync(_cacheFilePath, json).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 保存本地缓存失败:{ex.Message}"); + } + } + + public async Task SyncFromRemoteAsync(CancellationToken ct = default) + { + if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false)) return; + try + { + var all = new List(); + var pageNo = 1; + const int pageSize = 500; + + while (true) + { + var qs = HttpUtility.ParseQueryString(string.Empty); + qs["pageNo"] = pageNo.ToString(); + qs["pageSize"] = pageSize.ToString(); + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/list?{qs}"; + using var client = CreateClient(); + var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) break; + + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("result", out var resultEl)) break; + var records = resultEl.TryGetProperty("records", out var recEl) + ? recEl.Deserialize>(_jsonOpts) ?? [] + : []; + + all.AddRange(records); + if (records.Count < pageSize) break; + pageNo++; + } + + if (all.Count > 0) + { + lock (_cacheLock) { _localCache = all; } + await SaveCacheAsync(all).ConfigureAwait(false); + _logger.Information($"[密炼物料] 同步完成,共 {all.Count} 条"); + } + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 远程同步失败:{ex.Message}"); + } + finally + { + _syncLock.Release(); + } + } + + public Task PageAsync( + int pageNo, int pageSize, + string? materialCode = null, string? materialName = null, string? erpCode = null, + string? majorCategoryId = null, string? minorCategoryId = null, CancellationToken ct = default) { - var query = HttpUtility.ParseQueryString(string.Empty); - query["pageNo"] = pageNo.ToString(); - query["pageSize"] = pageSize.ToString(); - if (!string.IsNullOrWhiteSpace(materialCode)) query["materialCode"] = materialCode; - if (!string.IsNullOrWhiteSpace(materialName)) query["materialName"] = materialName; - if (!string.IsNullOrWhiteSpace(erpCode)) query["erpCode"] = erpCode; - if (!string.IsNullOrWhiteSpace(majorCategoryId)) query["majorCategoryId"] = majorCategoryId; - if (!string.IsNullOrWhiteSpace(minorCategoryId)) query["minorCategoryId"] = minorCategoryId; + List cache; + lock (_cacheLock) { cache = _localCache; } - var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/list?{query}"; + if (cache.Count > 0) + return Task.FromResult(PageFromCache(cache, pageNo, pageSize, + materialCode, materialName, erpCode, majorCategoryId, minorCategoryId)); + + return PageFromRemoteAsync(pageNo, pageSize, + materialCode, materialName, erpCode, majorCategoryId, minorCategoryId, ct); + } + + private static MixerMaterialPageResult PageFromCache( + List cache, int pageNo, int pageSize, + string? materialCode, string? materialName, string? erpCode, + string? majorCategoryId, string? minorCategoryId) + { + var q = cache.AsEnumerable(); + if (!string.IsNullOrWhiteSpace(materialCode)) + q = q.Where(x => x.MaterialCode?.Contains(materialCode, StringComparison.OrdinalIgnoreCase) == true); + if (!string.IsNullOrWhiteSpace(materialName)) + q = q.Where(x => x.MaterialName?.Contains(materialName, StringComparison.OrdinalIgnoreCase) == true); + if (!string.IsNullOrWhiteSpace(erpCode)) + q = q.Where(x => x.ErpCode?.Contains(erpCode, StringComparison.OrdinalIgnoreCase) == true); + if (!string.IsNullOrWhiteSpace(majorCategoryId)) + q = q.Where(x => x.MajorCategoryId == majorCategoryId); + if (!string.IsNullOrWhiteSpace(minorCategoryId)) + q = q.Where(x => x.MinorCategoryId == minorCategoryId); + + var filtered = q.ToList(); + var records = filtered.Skip((pageNo - 1) * pageSize).Take(pageSize).ToList(); + return new MixerMaterialPageResult(records, filtered.Count, pageNo, pageSize); + } + + private async Task PageFromRemoteAsync( + int pageNo, int pageSize, + string? materialCode, string? materialName, string? erpCode, + string? majorCategoryId, string? minorCategoryId, + CancellationToken ct) + { + var qs = HttpUtility.ParseQueryString(string.Empty); + qs["pageNo"] = pageNo.ToString(); + qs["pageSize"] = pageSize.ToString(); + if (!string.IsNullOrWhiteSpace(materialCode)) qs["materialCode"] = materialCode; + if (!string.IsNullOrWhiteSpace(materialName)) qs["materialName"] = materialName; + if (!string.IsNullOrWhiteSpace(erpCode)) qs["erpCode"] = erpCode; + if (!string.IsNullOrWhiteSpace(majorCategoryId)) qs["majorCategoryId"] = majorCategoryId; + if (!string.IsNullOrWhiteSpace(minorCategoryId)) qs["minorCategoryId"] = minorCategoryId; + + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/list?{qs}"; using var client = CreateClient(); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); @@ -63,13 +192,18 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); var result = doc.RootElement.GetProperty("result"); - var records = result.GetProperty("records").Deserialize>(_jsonOpts) ?? new(); + var records = result.GetProperty("records").Deserialize>(_jsonOpts) ?? []; var total = result.TryGetProperty("total", out var totalEl) ? totalEl.GetInt64() : records.Count; return new MixerMaterialPageResult(records, total, pageNo, pageSize); } public async Task GetByIdAsync(string id, CancellationToken ct = default) { + List cache; + lock (_cacheLock) { cache = _localCache; } + var local = cache.FirstOrDefault(x => x.Id == id); + if (local != null) return local; + var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/queryById?id={Uri.EscapeDataString(id)}"; using var client = CreateClient(); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); @@ -105,43 +239,75 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency public async Task>> GetMajorCategoryOptionsAsync(CancellationToken ct = default) { - var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pcode=XSLMES_MATERIAL"; - using var client = CreateClient(); - var resp = await client.GetAsync(url, ct).ConfigureAwait(false); - if (!resp.IsSuccessStatusCode) return []; - var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); - using var doc = JsonDocument.Parse(json); - var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; - return ParseTreeOptions(arr); + try + { + var root = await _dbContext.Queryable() + .ClearFilter().Where(x => x.Code == "XSLMES_MATERIAL").FirstAsync(); + if (root == null) return []; + + var majors = await _dbContext.Queryable() + .ClearFilter().Where(x => x.Pid == root.Id).OrderBy(x => x.Code).ToListAsync(); + + return majors.Where(x => !string.IsNullOrWhiteSpace(x.Id)) + .Select(x => new KeyValuePair(x.Name ?? x.Code ?? x.Id, x.Id)) + .ToList(); + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 加载大类选项失败:{ex.Message}"); + return []; + } } public async Task>> GetMinorCategoryOptionsAsync(string majorCategoryId, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(majorCategoryId)) return []; - var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pid={Uri.EscapeDataString(majorCategoryId)}"; - using var client = CreateClient(); - var resp = await client.GetAsync(url, ct).ConfigureAwait(false); - if (!resp.IsSuccessStatusCode) return []; - var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); - using var doc = JsonDocument.Parse(json); - var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; - return ParseTreeOptions(arr); + try + { + var minors = await _dbContext.Queryable() + .ClearFilter().Where(x => x.Pid == majorCategoryId).OrderBy(x => x.Code).ToListAsync(); + + return minors.Where(x => !string.IsNullOrWhiteSpace(x.Id)) + .Select(x => new KeyValuePair(x.Name ?? x.Code ?? x.Id, x.Id)) + .ToList(); + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 加载小类选项失败:{ex.Message}"); + return []; + } } - private static List> ParseTreeOptions(JsonElement root) + public async Task> GetMaterialCategoryTreeAsync(CancellationToken ct = default) { - var list = new List>(); - if (root.ValueKind != JsonValueKind.Array) return list; - foreach (var item in root.EnumerateArray()) + try { - var text = item.TryGetProperty("title", out var titleEl) ? titleEl.GetString() : null; - var key = item.TryGetProperty("key", out var keyEl) ? keyEl.GetString() : null; - if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(key)) + var root = await _dbContext.Queryable() + .ClearFilter().Where(x => x.Code == "XSLMES_MATERIAL").FirstAsync(); + if (root == null) return []; + + var majors = await _dbContext.Queryable() + .ClearFilter().Where(x => x.Pid == root.Id).OrderBy(x => x.Code).ToListAsync(); + if (majors.Count == 0) return []; + + var majorIds = majors.Select(x => x.Id).ToList(); + var allMinors = await _dbContext.Queryable() + .ClearFilter().Where(x => majorIds.Contains(x.Pid!)).OrderBy(x => x.Code).ToListAsync(); + + return majors.Select(major => { - list.Add(new KeyValuePair(text!, key!)); - } + var minorNodes = allMinors + .Where(m => m.Pid == major.Id) + .Select(m => new MaterialCategoryNode(m.Id, m.Name, m.Code, [])) + .ToList(); + return new MaterialCategoryNode(major.Id, major.Name, major.Code, minorNodes); + }).ToList(); + } + catch (Exception ex) + { + _logger.Warning($"[密炼物料] 加载分类树失败:{ex.Message}"); + return []; } - return list; } private async Task PostJsonAsync(string url, object body, CancellationToken ct) @@ -158,32 +324,20 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency { var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); - if (doc.RootElement.TryGetProperty("code", out var code)) - { - return code.GetInt32() == 200; - } - if (doc.RootElement.TryGetProperty("success", out var success)) - { - return success.GetBoolean(); - } - return true; - } - catch - { + if (doc.RootElement.TryGetProperty("code", out var code)) return code.GetInt32() == 200; + if (doc.RootElement.TryGetProperty("success", out var success)) return success.GetBoolean(); return true; } + catch { return true; } } private sealed class NullableDateTimeJsonConverter : JsonConverter { - private static readonly string[] SupportedFormats = + private static readonly string[] Formats = [ - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.fff", - "yyyy-MM-ddTHH:mm:ss", - "yyyy-MM-ddTHH:mm:ss.fff", - "yyyy-MM-ddTHH:mm:ssZ", - "yyyy-MM-ddTHH:mm:ss.fffZ" + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ" ]; public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -193,14 +347,9 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency { var raw = reader.GetString(); if (string.IsNullOrWhiteSpace(raw)) return null; - if (DateTime.TryParseExact(raw, SupportedFormats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) - { - return exact; - } - if (DateTime.TryParse(raw, out var fallback)) - { - return fallback; - } + if (DateTime.TryParseExact(raw, Formats, System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) return exact; + if (DateTime.TryParse(raw, out var fallback)) return fallback; } throw new JsonException($"无法将 JSON 值转换为 DateTime?,token={reader.TokenType}"); } @@ -212,4 +361,3 @@ public class MixerMaterialService : IMixerMaterialService, ISingletonDependency } } } - diff --git a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs index cdea6fe..52b3d84 100644 --- a/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs +++ b/yy-admin-master/YY.Admin.Services/Service/MixerMaterial/MixerMaterialSyncCoordinator.cs @@ -9,24 +9,36 @@ namespace YY.Admin.Services.Service.MixerMaterial; public class MixerMaterialSyncCoordinator : ISingletonDependency { private readonly IEventAggregator _eventAggregator; + private readonly IMixerMaterialService _mixerMaterialService; private readonly ILoggerService _logger; - public MixerMaterialSyncCoordinator(IEventAggregator eventAggregator, ILoggerService logger) + public MixerMaterialSyncCoordinator( + IEventAggregator eventAggregator, + IMixerMaterialService mixerMaterialService, + ILoggerService logger) { _eventAggregator = eventAggregator; + _mixerMaterialService = mixerMaterialService; _logger = logger; + _eventAggregator.GetEvent() .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); _eventAggregator.GetEvent() .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + _logger.Information("[密炼物料推送] MixerMaterialSyncCoordinator 已启动"); + _ = _mixerMaterialService.SyncFromRemoteAsync(); } private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) { if (!payload.IsOnline) return; - _eventAggregator.GetEvent() - .Publish(new MixerMaterialChangedPayload { Action = "reconnect" }); + _ = Task.Run(async () => + { + await _mixerMaterialService.SyncFromRemoteAsync().ConfigureAwait(false); + _eventAggregator.GetEvent() + .Publish(new MixerMaterialChangedPayload { Action = "reconnect" }); + }); } private void OnRemoteCommand(RemoteCommandPayload payload) @@ -43,16 +55,19 @@ public class MixerMaterialSyncCoordinator : ISingletonDependency doc.RootElement.TryGetProperty("action", out var actionEl); doc.RootElement.TryGetProperty("mixerMaterialId", out var idEl); if (idEl.ValueKind != JsonValueKind.String && doc.RootElement.TryGetProperty("id", out var id2El)) - { idEl = id2El; - } var changed = new MixerMaterialChangedPayload { Action = actionEl.GetString() ?? string.Empty, MixerMaterialId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null }; - _eventAggregator.GetEvent().Publish(changed); + + _ = Task.Run(async () => + { + await _mixerMaterialService.SyncFromRemoteAsync().ConfigureAwait(false); + _eventAggregator.GetEvent().Publish(changed); + }); } catch (Exception ex) { @@ -60,4 +75,3 @@ public class MixerMaterialSyncCoordinator : ISingletonDependency } } } - diff --git a/yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs b/yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs index 37c20eb..fda257e 100644 --- a/yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs @@ -74,8 +74,8 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency string? filterBillNo = null, string? filterPlateNumber = null, string? filterInoutDirection = null, - string? filterGoodsName = null, string? filterDriverName = null, + string? filterMixerMaterialName = null, CancellationToken ct = default) { List? source = null; @@ -104,7 +104,7 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency source = ApplyPendingOpsSnapshotUnsafe(source); } - var filtered = ApplyFilters(source, filterBillNo, filterPlateNumber, filterInoutDirection, filterGoodsName, filterDriverName); + var filtered = ApplyFilters(source, filterBillNo, filterPlateNumber, filterInoutDirection, filterDriverName, filterMixerMaterialName); var total = filtered.Count; var records = filtered.Skip(Math.Max(0, (pageNo - 1) * pageSize)).Take(pageSize).ToList(); return new WeightRecordPageResult(records, total, pageNo, pageSize); @@ -535,7 +535,8 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency private static List ApplyFilters( List source, - string? billNo, string? plateNumber, string? inoutDirection, string? goodsName, string? driverName) + string? billNo, string? plateNumber, string? inoutDirection, string? driverName, + string? mixerMaterialName = null) { IEnumerable q = source; if (!string.IsNullOrWhiteSpace(billNo)) @@ -544,10 +545,10 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency q = q.Where(v => (v.PlateNumber ?? "").Contains(plateNumber, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrWhiteSpace(inoutDirection)) q = q.Where(v => string.Equals(v.InoutDirection, inoutDirection, StringComparison.OrdinalIgnoreCase)); - if (!string.IsNullOrWhiteSpace(goodsName)) - q = q.Where(v => (v.GoodsName ?? "").Contains(goodsName, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrWhiteSpace(driverName)) q = q.Where(v => (v.DriverName ?? "").Contains(driverName, StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrWhiteSpace(mixerMaterialName)) + q = q.Where(v => (v.MixerMaterialNames ?? "").Contains(mixerMaterialName, StringComparison.OrdinalIgnoreCase)); return q.OrderByDescending(v => v.CreateTime ?? DateTime.MinValue).ToList(); } @@ -641,7 +642,9 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency PlateNumber = input.PlateNumber, SenderUnit = input.SenderUnit, ReceiverUnit = input.ReceiverUnit, - GoodsName = input.GoodsName, + MixerMaterialIds = input.MixerMaterialIds, + MixerMaterialNames = input.MixerMaterialNames, + MaterialType = input.MaterialType, GrossWeight = input.GrossWeight, TareWeight = input.TareWeight, NetWeight = input.NetWeight, @@ -655,7 +658,8 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency UpdateTime = input.UpdateTime, SysOrgCode = input.SysOrgCode, InoutDirectionText = input.InoutDirectionText, - BillTypeText = input.BillTypeText + BillTypeText = input.BillTypeText, + MaterialTypeText = input.MaterialTypeText }; private static bool IsLocalTempId(string? id) => diff --git a/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs b/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs index 565f2f3..d6a191a 100644 --- a/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs +++ b/yy-admin-master/YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs @@ -142,6 +142,14 @@ public class StompWebSocketService : ISignalRService await SendFrameAsync( BuildSubscribeFrame("sub-mes-mixer-material", "/topic/sync/mes-mixer-materials"), cancellationToken).ConfigureAwait(false); + // 分类字典变更:订阅 /topic/sync/sys-categories + await SendFrameAsync( + BuildSubscribeFrame("sub-sys-categories", "/topic/sync/sys-categories"), + cancellationToken).ConfigureAwait(false); + // 数据字典变更:订阅 /topic/sync/sys-dicts + await SendFrameAsync( + BuildSubscribeFrame("sub-sys-dicts", "/topic/sync/sys-dicts"), + cancellationToken).ConfigureAwait(false); // 订阅服务端 PONG 回复(应用层假在线检测) await SendFrameAsync( diff --git a/yy-admin-master/YY.Admin/Module/SyncModule.cs b/yy-admin-master/YY.Admin/Module/SyncModule.cs index 9f59519..d313637 100644 --- a/yy-admin-master/YY.Admin/Module/SyncModule.cs +++ b/yy-admin-master/YY.Admin/Module/SyncModule.cs @@ -13,9 +13,11 @@ using YY.Admin.Infrastructure.Hubs; using YY.Admin.Infrastructure.Network; using YY.Admin.Infrastructure.Storage; using YY.Admin.Infrastructure.Sync; +using YY.Admin.Services.Service.Category; using YY.Admin.Services.Service.Customer; -using YY.Admin.Services.Service.Supplier; +using YY.Admin.Services.Service.Dict; using YY.Admin.Services.Service.MixerMaterial; +using YY.Admin.Services.Service.Supplier; using YY.Admin.Services.Service.Vehicle; using YY.Admin.Services.Service.WeightRecord; @@ -50,6 +52,10 @@ public class SyncModule : IModule // 密炼物料信息:API直连 + STOMP实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); + // 分类字典:启动同步 + 断线重连补刷 + containerRegistry.RegisterSingleton(); + // 数据字典:启动同步 + 断线重连补刷 + containerRegistry.RegisterSingleton(); var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); @@ -104,6 +110,10 @@ public class SyncModule : IModule _ = containerProvider.Resolve(); // 强制实例化密炼物料同步协调器 _ = containerProvider.Resolve(); + // 强制实例化分类字典同步协调器 + _ = containerProvider.Resolve(); + // 强制实例化数据字典同步协调器 + _ = containerProvider.Resolve(); } private static IAsyncPolicy GetRetryPolicy() diff --git a/yy-admin-master/YY.Admin/Resources/Styles/HandyControl/Styles.xaml b/yy-admin-master/YY.Admin/Resources/Styles/HandyControl/Styles.xaml index f443804..e931b4b 100644 --- a/yy-admin-master/YY.Admin/Resources/Styles/HandyControl/Styles.xaml +++ b/yy-admin-master/YY.Admin/Resources/Styles/HandyControl/Styles.xaml @@ -64,6 +64,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml.cs b/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml.cs index 9a06562..a65c569 100644 --- a/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml.cs +++ b/yy-admin-master/YY.Admin/Views/MixerMaterial/MixerMaterialListView.xaml.cs @@ -1,4 +1,6 @@ +using System.Windows; using System.Windows.Controls; +using YY.Admin.ViewModels.MixerMaterial; namespace YY.Admin.Views.MixerMaterial; @@ -8,5 +10,10 @@ public partial class MixerMaterialListView : UserControl { InitializeComponent(); } -} + private void CategoryTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (DataContext is MixerMaterialListViewModel vm && e.NewValue is MixerMaterialListViewModel.CategoryFilterNode node) + _ = vm.OnTreeNodeSelectedAsync(node); + } +} diff --git a/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml b/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml index 8c81c9e..5fd13bf 100644 --- a/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml +++ b/yy-admin-master/YY.Admin/Views/SysManage/CategoryDictionaryManagementView.xaml @@ -65,12 +65,6 @@ - diff --git a/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml b/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml index 50d5f5e..d1927b9 100644 --- a/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml +++ b/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml @@ -1,8 +1,8 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml.cs b/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml.cs index f457509..39d1bb6 100644 --- a/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml.cs +++ b/yy-admin-master/YY.Admin/Views/SysManage/DataDictionaryManagementView.xaml.cs @@ -1,15 +1,22 @@ using System.Windows.Controls; +using YY.Admin.ViewModels.SysManage; namespace YY.Admin.Views.SysManage { - /// - /// DataDictionaryManagementView.xaml 的交互逻辑 - /// public partial class DataDictionaryManagementView : UserControl { public DataDictionaryManagementView() { InitializeComponent(); } + + private async void DictListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (DataContext is DataDictionaryManagementViewModel vm) + { + var node = (sender as ListBox)?.SelectedItem as DataDictionaryManagementViewModel.DictTreeNode; + await vm.OnDictTreeSelectedAsync(node); + } + } } } diff --git a/yy-admin-master/YY.Admin/Views/WeightRecord/MixerMaterialPickerDialogView.xaml b/yy-admin-master/YY.Admin/Views/WeightRecord/MixerMaterialPickerDialogView.xaml new file mode 100644 index 0000000..42068b7 --- /dev/null +++ b/yy-admin-master/YY.Admin/Views/WeightRecord/MixerMaterialPickerDialogView.xaml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +