新增密炼物料皮重策略功能,包括相关实体、服务、控制器及接口,支持桌面端免密CRUD操作,优化打印记录与原料入场记录的衍生字段填充逻辑,提升用户体验。
This commit is contained in:
@@ -324,17 +324,26 @@ func waitForWindowsPrintCompletion(printerName string, existingIDs map[int]bool,
|
||||
|
||||
queued := false
|
||||
jobID := 0
|
||||
sumatraDone := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-cmdDone:
|
||||
sumatraDone = true
|
||||
if err != nil && !queued {
|
||||
return fmt.Errorf("sumatra print failed: %v", err)
|
||||
}
|
||||
// Sumatra 已正常退出且 spooler 未出现新任务:部分驱动/打印机直接出纸,不经过队列
|
||||
if err == nil && !queued {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if !queued && sumatraDone {
|
||||
return nil
|
||||
}
|
||||
if !queued && now.After(appearDeadline) {
|
||||
return fmt.Errorf("print job not queued within %s", printQueueAppearTimeout)
|
||||
}
|
||||
|
||||
@@ -211,6 +211,10 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslWarehouse/anon/**", "anon");
|
||||
// MES库区管理免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslWarehouseArea/anon/**", "anon");
|
||||
// MES密炼物料皮重策略免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslMixerMaterialTareStrategy/anon/**", "anon");
|
||||
// MES单位只读免密接口(供桌面端单位下拉调用)
|
||||
filterChainDefinitionMap.put("/xslmes/mesXslUnit/anon/**", "anon");
|
||||
// MES密炼物料管理免密接口(供桌面端调用)
|
||||
filterChainDefinitionMap.put("/mes/material/mixerMaterial/anon/**", "anon");
|
||||
// 打印模板免密接口(供桌面端调用)
|
||||
|
||||
@@ -521,3 +521,68 @@ jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】审批IM消息升级为可跳转业务卡片(biz_record):点击可定位到对应单据,无法定位功能页时退回纯文本 ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
|
||||
|
||||
-- author:cursor---date:20250602--for: 【密炼物料皮重策略】桌面端同步 ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/MesXslStompNotifyService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialTareStrategyController.java
|
||||
jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
|
||||
|
||||
-- author:cursor---date:20250602--for: 【密炼物料皮重策略】新增物料规格/托盘重量,皮重改名为包装物重量 ---
|
||||
jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_118__mes_xsl_mixer_material_tare_strategy_fields.sql
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialTareStrategy.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialTareStrategyServiceImpl.java
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslMixerMaterialTareStrategy.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/MixerMaterialTareStrategy/MixerMaterialTareStrategyService.cs
|
||||
yy-admin-master/YY.Admin/Views/MixerMaterialTareStrategy/MixerMaterialTareStrategyListView.xaml
|
||||
yy-admin-master/YY.Admin/Views/MixerMaterialTareStrategy/MixerMaterialTareStrategyEditDialogView.xaml
|
||||
yy-admin-master/YY.Admin/ViewModels/MixerMaterialTareStrategy/MixerMaterialTareStrategyEditDialogViewModel.cs
|
||||
yy-admin-master/YY.Admin/ViewModels/MixerMaterialTareStrategy/MixerMaterialTareStrategyListViewModel.cs
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategy.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/components/MesXslMixerMaterialTareStrategyModal.vue
|
||||
|
||||
-- author:cursor---date:20250602--for: 【原料入场/原材料卡片】皮重字段落库 ---
|
||||
jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_119__mes_xsl_raw_material_entry_tare_fields.sql
|
||||
jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_120__mes_xsl_raw_material_card_tare_fields.sql
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslRawMaterialEntry.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslRawMaterialCard.java
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslRawMaterialEntry.cs
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslRawMaterialCard.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/RawMaterialEntry/RawMaterialEntryService.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/RawMaterialCard/RawMaterialCardService.cs
|
||||
yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryEditDialogViewModel.cs
|
||||
yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryOperationViewModel.cs
|
||||
yy-admin-master/YY.Admin/ViewModels/RawMaterialCard/RawMaterialCardEditDialogViewModel.cs
|
||||
|
||||
-- author:cursor---date:20250602--for: 【原料入场/原材料卡片】列表展示皮重相关字段 ---
|
||||
yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialEntryListView.xaml
|
||||
yy-admin-master/YY.Admin/Views/RawMaterialCard/RawMaterialCardListView.xaml
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialCard/MesXslRawMaterialCard.data.ts
|
||||
|
||||
-- author:cursor---date:20250602--for: 【原料入场/原材料卡片】后端列表与详情展示皮重字段 ---
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslRawMaterialWorkshopRemain.java
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialEntry/MesXslRawMaterialEntry.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialCard/MesXslRawMaterialCard.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWorkshopRemain/MesXslRawMaterialWorkshopRemain.data.ts
|
||||
|
||||
-- author:cursor---date:20250602--for: 【磅单记录】列表展示货物皮重(关联入场记录托盘及皮重合计,不落库) ---
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslWeightRecord.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialEntryService.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialEntryServiceImpl.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWeightRecordController.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs
|
||||
yy-admin-master/YY.Admin.Core/Util/CargoTareWeightCalculator.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs
|
||||
yy-admin-master/YY.Admin/Views/WeightRecord/WeightRecordListView.xaml
|
||||
yy-admin-master/YY.Admin/Views/RawMaterialEntry/WeightRecordPickerDialogView.xaml
|
||||
|
||||
-- author:cursor---date:20250602--for: 【磅单记录】列表展示原料重量(净重-货物皮重,不落库) ---
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslWeightRecord.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialEntryServiceImpl.java
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslWeightRecord/MesXslWeightRecord.data.ts
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslWeightRecord.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/WeightRecord/WeightRecordService.cs
|
||||
yy-admin-master/YY.Admin/Views/WeightRecord/WeightRecordListView.xaml
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.jeecg.modules.xslmes.entity.MesXslCustomer;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslSupplier;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialTareStrategy;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslUnit;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslVehicle;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouse;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea;
|
||||
@@ -35,6 +37,8 @@ import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslSupplierService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslMixerMaterialTareStrategyService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslUnitService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslVehicleService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseService;
|
||||
@@ -60,6 +64,8 @@ import org.apache.commons.lang3.StringUtils;
|
||||
* ShiroConfig 白名单:
|
||||
* /xslmes/mesXslVehicle/anon/**
|
||||
* /xslmes/mesXslCustomer/anon/**
|
||||
* /xslmes/mesXslMixerMaterialTareStrategy/anon/**
|
||||
* /xslmes/mesXslUnit/anon/**
|
||||
*/
|
||||
@Tag(name = "桌面端免密接口")
|
||||
@RestController
|
||||
@@ -75,6 +81,8 @@ public class MesXslDesktopAnonController {
|
||||
private final IMesXslRawMaterialCardService rawMaterialCardService;
|
||||
private final IMesXslWarehouseService warehouseService;
|
||||
private final IMesXslWarehouseAreaService warehouseAreaService;
|
||||
private final IMesXslMixerMaterialTareStrategyService tareStrategyService;
|
||||
private final IMesXslUnitService unitService;
|
||||
private final MesXslStompNotifyService stompNotify;
|
||||
private final IPrintBizTemplateBindService printBizTemplateBindService;
|
||||
private final IPrintTemplateService printTemplateService;
|
||||
@@ -390,6 +398,7 @@ public class MesXslDesktopAnonController {
|
||||
QueryWrapper<MesXslWeightRecord> qw = QueryGenerator.initQueryWrapper(mesXslWeightRecord, req.getParameterMap());
|
||||
qw.orderByDesc("create_time");
|
||||
IPage<MesXslWeightRecord> page = weightRecordService.page(new Page<>(pageNo, pageSize), qw);
|
||||
rawMaterialEntryService.fillWeightRecordDerivedFields(page.getRecords());
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@@ -397,7 +406,11 @@ public class MesXslDesktopAnonController {
|
||||
@GetMapping("/xslmes/mesXslWeightRecord/anon/queryById")
|
||||
public Result<MesXslWeightRecord> weightRecordAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslWeightRecord entity = weightRecordService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
rawMaterialEntryService.fillWeightRecordDerivedFields(Collections.singletonList(entity));
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
@Operation(summary = "磅单-免密添加")
|
||||
@@ -828,6 +841,90 @@ public class MesXslDesktopAnonController {
|
||||
return Result.OK("该值可用!");
|
||||
}
|
||||
|
||||
// ═══════════════════════════ 密炼物料皮重策略 ═══════════════════════════
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端免密CRUD-----------
|
||||
@Operation(summary = "密炼物料皮重策略-免密分页列表查询")
|
||||
@GetMapping("/xslmes/mesXslMixerMaterialTareStrategy/anon/list")
|
||||
public Result<IPage<MesXslMixerMaterialTareStrategy>> tareStrategyAnonList(
|
||||
MesXslMixerMaterialTareStrategy model,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslMixerMaterialTareStrategy> qw = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
|
||||
qw.orderByDesc("effective_start_date", "create_time");
|
||||
IPage<MesXslMixerMaterialTareStrategy> page = tareStrategyService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼物料皮重策略-免密通过id查询")
|
||||
@GetMapping("/xslmes/mesXslMixerMaterialTareStrategy/anon/queryById")
|
||||
public Result<MesXslMixerMaterialTareStrategy> tareStrategyAnonQueryById(@RequestParam(name = "id") String id) {
|
||||
MesXslMixerMaterialTareStrategy entity = tareStrategyService.getById(id);
|
||||
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼物料皮重策略-免密添加")
|
||||
@PostMapping("/xslmes/mesXslMixerMaterialTareStrategy/anon/add")
|
||||
public Result<String> tareStrategyAnonAdd(@RequestBody MesXslMixerMaterialTareStrategy model) {
|
||||
String err = tareStrategyService.validateBeforeSave(model, false);
|
||||
if (err != null) {
|
||||
return Result.error(err);
|
||||
}
|
||||
tareStrategyService.save(model);
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("add", model.getId());
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼物料皮重策略-免密编辑")
|
||||
@RequestMapping(value = "/xslmes/mesXslMixerMaterialTareStrategy/anon/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> tareStrategyAnonEdit(@RequestBody MesXslMixerMaterialTareStrategy model) {
|
||||
if (oConvertUtils.isEmpty(model.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
String err = tareStrategyService.validateBeforeSave(model, true);
|
||||
if (err != null) {
|
||||
return Result.error(err);
|
||||
}
|
||||
boolean ok = tareStrategyService.updateById(model);
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("edit", model.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼物料皮重策略-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslMixerMaterialTareStrategy/anon/delete")
|
||||
public Result<String> tareStrategyAnonDelete(@RequestParam(name = "id") String id) {
|
||||
tareStrategyService.removeById(id);
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("delete", id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼物料皮重策略-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslMixerMaterialTareStrategy/anon/deleteBatch")
|
||||
public Result<String> tareStrategyAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
tareStrategyService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("batchDelete", ids);
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端免密CRUD-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端单位下拉只读-----------
|
||||
@Operation(summary = "单位-免密分页列表查询(供桌面端单位下拉)")
|
||||
@GetMapping("/xslmes/mesXslUnit/anon/list")
|
||||
public Result<IPage<MesXslUnit>> unitAnonList(
|
||||
MesXslUnit mesXslUnit,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "1000") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslUnit> qw = QueryGenerator.initQueryWrapper(mesXslUnit, req.getParameterMap());
|
||||
IPage<MesXslUnit> page = unitService.page(new Page<>(pageNo, pageSize), qw);
|
||||
return Result.OK(page);
|
||||
}
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端单位下拉只读-----------
|
||||
|
||||
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
|
||||
|
||||
private void applyWeightNetAndBillType(MesXslWeightRecord record) {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialTareStrategy;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslMixerMaterialTareStrategyService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* MES 密炼物料皮重策略
|
||||
*/
|
||||
@Tag(name = "MES密炼物料皮重策略")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslMixerMaterialTareStrategy")
|
||||
@Slf4j
|
||||
public class MesXslMixerMaterialTareStrategyController
|
||||
extends JeecgController<MesXslMixerMaterialTareStrategy, IMesXslMixerMaterialTareStrategyService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslMixerMaterialTareStrategyService mesXslMixerMaterialTareStrategyService;
|
||||
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Operation(summary = "MES密炼物料皮重策略-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslMixerMaterialTareStrategy>> queryPageList(
|
||||
MesXslMixerMaterialTareStrategy model,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslMixerMaterialTareStrategy> queryWrapper =
|
||||
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
|
||||
queryWrapper.orderByDesc("effective_start_date", "create_time");
|
||||
Page<MesXslMixerMaterialTareStrategy> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslMixerMaterialTareStrategy> pageList = mesXslMixerMaterialTareStrategyService.page(page, queryWrapper);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@AutoLog(value = "MES密炼物料皮重策略-添加")
|
||||
@Operation(summary = "MES密炼物料皮重策略-添加")
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:add")
|
||||
@PostMapping(value = "/add")
|
||||
public Result<String> add(@RequestBody MesXslMixerMaterialTareStrategy model) {
|
||||
fillMaintainBy(model);
|
||||
String err = mesXslMixerMaterialTareStrategyService.validateBeforeSave(model, false);
|
||||
if (err != null) {
|
||||
return Result.error(err);
|
||||
}
|
||||
mesXslMixerMaterialTareStrategyService.save(model);
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("add", model.getId());
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "MES密炼物料皮重策略-编辑")
|
||||
@Operation(summary = "MES密炼物料皮重策略-编辑")
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslMixerMaterialTareStrategy model) {
|
||||
fillMaintainBy(model);
|
||||
String err = mesXslMixerMaterialTareStrategyService.validateBeforeSave(model, true);
|
||||
if (err != null) {
|
||||
return Result.error(err);
|
||||
}
|
||||
mesXslMixerMaterialTareStrategyService.updateById(model);
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("edit", model.getId());
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "MES密炼物料皮重策略-删除")
|
||||
@Operation(summary = "MES密炼物料皮重策略-通过id删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslMixerMaterialTareStrategyService.removeById(id);
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("delete", id);
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "MES密炼物料皮重策略-批量删除")
|
||||
@Operation(summary = "MES密炼物料皮重策略-批量删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
mesXslMixerMaterialTareStrategyService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
stompNotify.publishMixerMaterialTareStrategyChanged("batchDelete", ids);
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "MES密炼物料皮重策略-通过id查询")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslMixerMaterialTareStrategy> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslMixerMaterialTareStrategy entity = mesXslMixerMaterialTareStrategyService.getById(id);
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslMixerMaterialTareStrategy model) {
|
||||
return super.exportXls(request, model, MesXslMixerMaterialTareStrategy.class, "密炼物料皮重策略");
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_mixer_material_tare_strategy:importExcel")
|
||||
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, MesXslMixerMaterialTareStrategy.class);
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】维护人自动回填当前登录用户-----------
|
||||
private void fillMaintainBy(MesXslMixerMaterialTareStrategy model) {
|
||||
LoginUser loginUser = null;
|
||||
try {
|
||||
loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
} catch (Exception e) {
|
||||
log.debug("获取登录用户失败", e);
|
||||
}
|
||||
if (loginUser != null && oConvertUtils.isNotEmpty(loginUser.getUsername())) {
|
||||
model.setMaintainBy(loginUser.getUsername());
|
||||
}
|
||||
}
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】维护人自动回填当前登录用户-----------
|
||||
}
|
||||
@@ -27,9 +27,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 地磅数据记录
|
||||
@@ -57,7 +55,7 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
Page<MesXslWeightRecord> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslWeightRecord> pageList = mesXslWeightRecordService.page(page, queryWrapper);
|
||||
fillEnteredWeight(pageList.getRecords());
|
||||
rawMaterialEntryService.fillWeightRecordDerivedFields(pageList.getRecords());
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@@ -120,7 +118,7 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
||||
if (record == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
fillEnteredWeight(Collections.singletonList(record));
|
||||
fillWeightRecordDerivedFields(Collections.singletonList(record));
|
||||
return Result.OK(record);
|
||||
}
|
||||
|
||||
@@ -187,27 +185,7 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给一批磅单记录批量填充「已入场重量」(transient 字段,不入库)。
|
||||
* 数据来源:所有引用本榜单(bill_no 匹配)的原料入场记录的拆码明细的 (份数×每份重量) 累计。
|
||||
* 实现上为避免 N+1,先收集所有 billNo,再一次 IN 查询累计。
|
||||
*/
|
||||
private void fillEnteredWeight(List<MesXslWeightRecord> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> billNos = records.stream()
|
||||
.map(MesXslWeightRecord::getBillNo)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (billNos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, BigDecimal> sumMap = rawMaterialEntryService.sumEnteredWeightByBillNos(billNos);
|
||||
for (MesXslWeightRecord r : records) {
|
||||
BigDecimal v = (r.getBillNo() == null) ? null : sumMap.get(r.getBillNo());
|
||||
r.setEnteredWeight(v != null ? v : BigDecimal.ZERO);
|
||||
}
|
||||
private void fillWeightRecordDerivedFields(List<MesXslWeightRecord> records) {
|
||||
rawMaterialEntryService.fillWeightRecordDerivedFields(records);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* MES 密炼物料皮重策略
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_mixer_material_tare_strategy")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "MES密炼物料皮重策略")
|
||||
public class MesXslMixerMaterialTareStrategy implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Excel(name = "密炼物料", width = 18, dictTable = "mes_mixer_material", dicText = "material_name", dicCode = "id")
|
||||
@Dict(dictTable = "mes_mixer_material", dicText = "material_name", dicCode = "id")
|
||||
@Schema(description = "密炼物料ID(关联 mes_mixer_material.id)")
|
||||
private String mixerMaterialId;
|
||||
|
||||
@Excel(name = "密炼物料名称", width = 20)
|
||||
@Schema(description = "密炼物料名称冗余")
|
||||
private String mixerMaterialName;
|
||||
|
||||
@Excel(name = "供应商", width = 18, dictTable = "mes_xsl_supplier", dicText = "supplier_name", dicCode = "id")
|
||||
@Dict(dictTable = "mes_xsl_supplier", dicText = "supplier_name", dicCode = "id")
|
||||
@Schema(description = "供应商ID(关联 mes_xsl_supplier.id)")
|
||||
private String supplierId;
|
||||
|
||||
@Excel(name = "供应商名称", width = 20)
|
||||
@Schema(description = "供应商名称冗余")
|
||||
private String supplierName;
|
||||
|
||||
@Excel(name = "物料规格", width = 16)
|
||||
@Schema(description = "物料规格(与密炼物料、供应商、生效日期共同参与唯一性校验,不同规格可分别维护)")
|
||||
private String materialSpec;
|
||||
|
||||
@Excel(name = "包装物重量", width = 12)
|
||||
@Schema(description = "包装物重量")
|
||||
private BigDecimal tareWeight;
|
||||
|
||||
@Excel(name = "托盘重量", width = 12)
|
||||
@Schema(description = "托盘重量")
|
||||
private BigDecimal palletWeight;
|
||||
|
||||
@Schema(description = "单位ID(关联 mes_xsl_unit.id)")
|
||||
private String unitId;
|
||||
|
||||
@Excel(name = "单位", width = 10)
|
||||
@Schema(description = "单位名称冗余")
|
||||
private String unitName;
|
||||
|
||||
@Excel(name = "生效开始日期", width = 14, format = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "生效开始日期")
|
||||
private Date effectiveStartDate;
|
||||
|
||||
@Excel(name = "生效截止日期", width = 14, format = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "生效截止日期")
|
||||
private Date effectiveEndDate;
|
||||
|
||||
@Excel(name = "维护人", width = 12, dictTable = "sys_user", dicText = "realname", dicCode = "username")
|
||||
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
|
||||
@Schema(description = "维护人(登录账号)")
|
||||
private String maintainBy;
|
||||
|
||||
@Excel(name = "创建人", width = 12)
|
||||
private String createBy;
|
||||
|
||||
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@Excel(name = "修改人", width = 12)
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
private String sysOrgCode;
|
||||
private Integer delFlag;
|
||||
}
|
||||
@@ -86,6 +86,14 @@ public class MesXslRawMaterialCard implements Serializable {
|
||||
@Schema(description = "总重")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "包装物皮重", width = 12)
|
||||
@Schema(description = "包装物皮重(KG)")
|
||||
private BigDecimal packagingTare;
|
||||
|
||||
@Excel(name = "托盘重量", width = 12)
|
||||
@Schema(description = "托盘重量(KG)")
|
||||
private BigDecimal palletWeight;
|
||||
|
||||
@Excel(name = "剩余重量", width = 12)
|
||||
@Schema(description = "剩余重量")
|
||||
private BigDecimal remainingWeight;
|
||||
|
||||
@@ -89,6 +89,10 @@ public class MesXslRawMaterialEntry implements Serializable {
|
||||
@Schema(description = "总重(KG)")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "托盘及皮重合计", width = 14)
|
||||
@Schema(description = "托盘及皮重(合计)")
|
||||
private BigDecimal palletTareTotal;
|
||||
|
||||
// 总份数 / 每份总重 / 每份包数:从 数值类型 升级为 字符串类型,
|
||||
// 支持桌面端「拆码明细」多行拼接保存(如 20/1/ 与 100/200/)。
|
||||
@Excel(name = "总份数", width = 12)
|
||||
@@ -99,6 +103,17 @@ public class MesXslRawMaterialEntry implements Serializable {
|
||||
@Schema(description = "每份总重(KG)(支持多行拆码明细拼接,如 100/200/)")
|
||||
private String portionWeight;
|
||||
|
||||
@Excel(name = "包装物皮重", width = 14)
|
||||
@Schema(description = "拆码明细包装物皮重拼接(以 / 分隔,末尾带 /)")
|
||||
private String portionPackagingTare;
|
||||
|
||||
@Excel(name = "托盘重量", width = 14)
|
||||
@Schema(description = "拆码明细托盘重量拼接(以 / 分隔,末尾带 /)")
|
||||
private String portionPalletWeight;
|
||||
|
||||
@Schema(description = "拆码明细皮重策略ID拼接(以 / 分隔,末尾带 /)")
|
||||
private String portionTareStrategyIds;
|
||||
|
||||
@Excel(name = "每份包数", width = 12)
|
||||
@Schema(description = "每份包数(支持多行拆码明细拼接)")
|
||||
private String portionPackages;
|
||||
|
||||
@@ -56,6 +56,14 @@ public class MesXslRawMaterialWorkshopRemain extends JeecgEntity {
|
||||
@Schema(description = "总重")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "包装物皮重", width = 12)
|
||||
@Schema(description = "包装物皮重(KG)")
|
||||
private BigDecimal packagingTare;
|
||||
|
||||
@Excel(name = "托盘重量", width = 12)
|
||||
@Schema(description = "托盘重量(KG)")
|
||||
private BigDecimal palletWeight;
|
||||
|
||||
@Excel(name = "剩余重量", width = 12)
|
||||
@Schema(description = "剩余重量")
|
||||
private BigDecimal remainingWeight;
|
||||
|
||||
@@ -43,7 +43,10 @@ import java.util.Date;
|
||||
"driverName",
|
||||
"driverPhone",
|
||||
"billType",
|
||||
"tenantId"
|
||||
"tenantId",
|
||||
"enteredWeight",
|
||||
"cargoTareWeight",
|
||||
"rawMaterialWeight"
|
||||
})
|
||||
public class MesXslWeightRecord extends JeecgEntity implements Serializable {
|
||||
|
||||
@@ -129,4 +132,20 @@ public class MesXslWeightRecord extends JeecgEntity implements Serializable {
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "已入场重量(KG),由原料入场记录的拆码明细实时累计")
|
||||
private BigDecimal enteredWeight;
|
||||
|
||||
/**
|
||||
* 货物皮重(KG)—— 实时计算,不落库。
|
||||
* 数据来源:所有引用本榜单(bill_no 匹配)的原料入场记录的 pallet_tare_total(托盘及皮重合计)累加。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "货物皮重(KG),关联原料入场记录的托盘及皮重合计累计")
|
||||
private BigDecimal cargoTareWeight;
|
||||
|
||||
/**
|
||||
* 原料重量(KG)—— 实时计算,不落库。
|
||||
* 公式:净重(KG) - 货物皮重(KG)。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "原料重量(KG)=净重-货物皮重")
|
||||
private BigDecimal rawMaterialWeight;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialTareStrategy;
|
||||
|
||||
/**
|
||||
* MES 密炼物料皮重策略 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MesXslMixerMaterialTareStrategyMapper extends BaseMapper<MesXslMixerMaterialTareStrategy> {}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialTareStrategy;
|
||||
|
||||
/**
|
||||
* MES 密炼物料皮重策略
|
||||
*/
|
||||
public interface IMesXslMixerMaterialTareStrategyService extends IService<MesXslMixerMaterialTareStrategy> {
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】保存前校验生效日期重叠(含物料规格)-----------
|
||||
/**
|
||||
* 校验同一租户、同一供应商、同一密炼物料、同一物料规格在生效日期内是否已存在记录。
|
||||
* 同一密炼物料不同规格可分别维护;仅规格相同且生效日期重叠时不允许重复。
|
||||
*
|
||||
* @param entity 待保存实体
|
||||
* @param isUpdate 是否编辑
|
||||
* @return 错误信息,null 表示通过
|
||||
*/
|
||||
String validateBeforeSave(MesXslMixerMaterialTareStrategy entity, boolean isUpdate);
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】保存前校验生效日期重叠(含物料规格)-----------
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
|
||||
@@ -62,6 +63,19 @@ public interface IMesXslRawMaterialEntryService extends IService<MesXslRawMateri
|
||||
*/
|
||||
Map<String, BigDecimal> sumEnteredWeightByBillNos(Collection<String> billNos);
|
||||
|
||||
/**
|
||||
* 按榜单号批量统计「货物皮重」(托盘及皮重合计累加)。
|
||||
*
|
||||
* @param billNos 榜单号集合
|
||||
* @return billNo -> 累计货物皮重;查不到的 billNo 不会出现在 map 中
|
||||
*/
|
||||
Map<String, BigDecimal> sumCargoTareByBillNos(Collection<String> billNos);
|
||||
|
||||
/**
|
||||
* 给磅单列表/详情填充由原料入场记录衍生的 transient 字段(已入场重量、货物皮重)。
|
||||
*/
|
||||
void fillWeightRecordDerivedFields(List<MesXslWeightRecord> records);
|
||||
|
||||
/**
|
||||
* 结存入库并汇总原材料库存。
|
||||
* <p>
|
||||
|
||||
@@ -75,6 +75,14 @@ public class MesXslStompNotifyService {
|
||||
publish("/topic/sync/print-templates", "PRINT_TEMPLATE_CHANGED", "templateId", templateId, action);
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
/** 广播密炼物料皮重策略变更事件到 /topic/sync/mes-mixer-material-tare-strategies */
|
||||
public void publishMixerMaterialTareStrategyChanged(String action, String tareStrategyId) {
|
||||
publish("/topic/sync/mes-mixer-material-tare-strategies", "MES_MIXER_MATERIAL_TARE_STRATEGY_CHANGED",
|
||||
"tareStrategyId", tareStrategyId, action);
|
||||
}
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】桌面端同步-----------
|
||||
|
||||
// ─────────────────────────── 私有辅助 ────────────────────────────
|
||||
|
||||
private void publish(String topic, String cmd, String idKey, String idValue, String action) {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import java.util.Date;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.mes.material.entity.MesMixerMaterial;
|
||||
import org.jeecg.modules.mes.material.service.IMesMixerMaterialService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialTareStrategy;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslSupplier;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslUnit;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslMixerMaterialTareStrategyMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslMixerMaterialTareStrategyService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslSupplierService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslUnitService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* MES 密炼物料皮重策略
|
||||
*/
|
||||
@Service
|
||||
public class MesXslMixerMaterialTareStrategyServiceImpl
|
||||
extends ServiceImpl<MesXslMixerMaterialTareStrategyMapper, MesXslMixerMaterialTareStrategy>
|
||||
implements IMesXslMixerMaterialTareStrategyService {
|
||||
|
||||
@Autowired
|
||||
private IMesMixerMaterialService mesMixerMaterialService;
|
||||
|
||||
@Autowired
|
||||
private IMesXslSupplierService mesXslSupplierService;
|
||||
|
||||
@Autowired
|
||||
private IMesXslUnitService mesXslUnitService;
|
||||
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】保存前校验与冗余回填-----------
|
||||
@Override
|
||||
public String validateBeforeSave(MesXslMixerMaterialTareStrategy entity, boolean isUpdate) {
|
||||
if (oConvertUtils.isEmpty(entity.getMixerMaterialId())) {
|
||||
return "请选择密炼物料";
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getSupplierId())) {
|
||||
return "请选择供应商";
|
||||
}
|
||||
if (entity.getTareWeight() == null) {
|
||||
return "请填写包装物重量";
|
||||
}
|
||||
if (entity.getPalletWeight() != null && entity.getPalletWeight().signum() < 0) {
|
||||
return "托盘重量不能为负数";
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getUnitId())) {
|
||||
return "请选择单位";
|
||||
}
|
||||
Date startDate = entity.getEffectiveStartDate();
|
||||
Date endDate = entity.getEffectiveEndDate();
|
||||
if (startDate == null || endDate == null) {
|
||||
return "请填写完整的生效日期";
|
||||
}
|
||||
if (startDate.after(endDate)) {
|
||||
return "生效开始日期不能晚于截止日期";
|
||||
}
|
||||
|
||||
MesMixerMaterial mixerMaterial = mesMixerMaterialService.getById(entity.getMixerMaterialId());
|
||||
if (mixerMaterial == null) {
|
||||
return "所选密炼物料不存在,请重新选择";
|
||||
}
|
||||
MesXslSupplier supplier = mesXslSupplierService.getById(entity.getSupplierId());
|
||||
if (supplier == null) {
|
||||
return "所选供应商不存在,请重新选择";
|
||||
}
|
||||
MesXslUnit unit = mesXslUnitService.getById(entity.getUnitId());
|
||||
if (unit == null) {
|
||||
return "所选单位不存在,请重新选择";
|
||||
}
|
||||
|
||||
entity.setMixerMaterialName(mixerMaterial.getMaterialName());
|
||||
entity.setSupplierName(supplier.getSupplierName());
|
||||
entity.setUnitName(unit.getUnitName());
|
||||
//update-begin---author:cursor ---date:20250602 for:【密炼物料皮重策略】重叠校验增加物料规格维度-----------
|
||||
if (entity.getMaterialSpec() != null) {
|
||||
entity.setMaterialSpec(entity.getMaterialSpec().trim());
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getMaterialSpec())) {
|
||||
entity.setMaterialSpec(null);
|
||||
}
|
||||
|
||||
String normalizedSpec = oConvertUtils.isEmpty(entity.getMaterialSpec()) ? "" : entity.getMaterialSpec();
|
||||
LambdaQueryWrapper<MesXslMixerMaterialTareStrategy> overlapQw = new LambdaQueryWrapper<>();
|
||||
overlapQw.eq(MesXslMixerMaterialTareStrategy::getMixerMaterialId, entity.getMixerMaterialId())
|
||||
.eq(MesXslMixerMaterialTareStrategy::getSupplierId, entity.getSupplierId())
|
||||
.le(MesXslMixerMaterialTareStrategy::getEffectiveStartDate, endDate)
|
||||
.ge(MesXslMixerMaterialTareStrategy::getEffectiveEndDate, startDate)
|
||||
.apply("IFNULL(TRIM(material_spec), '') = {0}", normalizedSpec);
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】重叠校验增加物料规格维度-----------
|
||||
if (entity.getTenantId() != null) {
|
||||
overlapQw.eq(MesXslMixerMaterialTareStrategy::getTenantId, entity.getTenantId());
|
||||
}
|
||||
if (isUpdate && oConvertUtils.isNotEmpty(entity.getId())) {
|
||||
overlapQw.ne(MesXslMixerMaterialTareStrategy::getId, entity.getId());
|
||||
}
|
||||
if (count(overlapQw) > 0) {
|
||||
return "同一租户、同一供应商、同一密炼物料且物料规格相同的时间段内,已存在策略,请勿重复维护";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//update-end---author:cursor ---date:20250602 for:【密炼物料皮重策略】保存前校验与冗余回填-----------
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialEntryMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInventoryService;
|
||||
@@ -217,6 +218,75 @@ public class MesXslRawMaterialEntryServiceImpl
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, BigDecimal> sumCargoTareByBillNos(Collection<String> billNos) {
|
||||
if (billNos == null || billNos.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Set<String> distinct = billNos.stream()
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
if (distinct.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
LambdaQueryWrapper<MesXslRawMaterialEntry> qw = new LambdaQueryWrapper<>();
|
||||
qw.in(MesXslRawMaterialEntry::getBillNo, distinct)
|
||||
.select(MesXslRawMaterialEntry::getBillNo, MesXslRawMaterialEntry::getPalletTareTotal);
|
||||
List<MesXslRawMaterialEntry> rows = this.list(qw);
|
||||
Map<String, BigDecimal> result = new HashMap<>();
|
||||
for (MesXslRawMaterialEntry row : rows) {
|
||||
if (row.getBillNo() == null || row.getPalletTareTotal() == null) {
|
||||
continue;
|
||||
}
|
||||
result.merge(row.getBillNo(), row.getPalletTareTotal(), BigDecimal::add);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillWeightRecordDerivedFields(List<MesXslWeightRecord> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> billNos = records.stream()
|
||||
.map(MesXslWeightRecord::getBillNo)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (billNos.isEmpty()) {
|
||||
for (MesXslWeightRecord r : records) {
|
||||
r.setEnteredWeight(BigDecimal.ZERO);
|
||||
r.setCargoTareWeight(BigDecimal.ZERO);
|
||||
applyRawMaterialWeight(r);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<String, BigDecimal> enteredMap = sumEnteredWeightByBillNos(billNos);
|
||||
Map<String, BigDecimal> cargoTareMap = sumCargoTareByBillNos(billNos);
|
||||
for (MesXslWeightRecord r : records) {
|
||||
String billNo = r.getBillNo();
|
||||
if (billNo == null || billNo.isBlank()) {
|
||||
r.setEnteredWeight(BigDecimal.ZERO);
|
||||
r.setCargoTareWeight(BigDecimal.ZERO);
|
||||
} else {
|
||||
r.setEnteredWeight(enteredMap.getOrDefault(billNo, BigDecimal.ZERO));
|
||||
r.setCargoTareWeight(cargoTareMap.getOrDefault(billNo, BigDecimal.ZERO));
|
||||
}
|
||||
applyRawMaterialWeight(r);
|
||||
}
|
||||
}
|
||||
|
||||
/** 原料重量 = 净重 - 货物皮重(不落库) */
|
||||
private static void applyRawMaterialWeight(MesXslWeightRecord record) {
|
||||
BigDecimal net = record.getNetWeight();
|
||||
if (net == null) {
|
||||
record.setRawMaterialWeight(null);
|
||||
return;
|
||||
}
|
||||
BigDecimal cargo = record.getCargoTareWeight() != null ? record.getCargoTareWeight() : BigDecimal.ZERO;
|
||||
record.setRawMaterialWeight(net.subtract(cargo));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void batchStockInAndSyncInventory(Collection<String> ids) {
|
||||
|
||||
@@ -540,3 +540,34 @@ jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTes
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTestRecord.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/components/MesXslRubberQuickTestRecordModal.vue
|
||||
jeecgboot-vue3/src/views/mes/material/MesMaterialList.vue
|
||||
|
||||
-- author:cursor---date:20250602--for: 【密炼物料皮重策略】建表、CRUD、生效日期重叠校验、菜单授权 ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_117__mes_xsl_mixer_material_tare_strategy.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialTareStrategy.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialTareStrategyMapper.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialTareStrategyService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialTareStrategyServiceImpl.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialTareStrategyController.java
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategyList.vue
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategy.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategy.api.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/components/MesXslMixerMaterialTareStrategyModal.vue
|
||||
|
||||
-- author:cursor---date:20250602--for: 【密炼物料皮重策略】新增物料规格/托盘重量,皮重改名为包装物重量 ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_118__mes_xsl_mixer_material_tare_strategy_fields.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialTareStrategy.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialTareStrategyServiceImpl.java
|
||||
yy-admin-master/YY.Admin.Core/Entity/MesXslMixerMaterialTareStrategy.cs
|
||||
yy-admin-master/YY.Admin.Services/Service/MixerMaterialTareStrategy/MixerMaterialTareStrategyService.cs
|
||||
yy-admin-master/YY.Admin/Views/MixerMaterialTareStrategy/MixerMaterialTareStrategyListView.xaml
|
||||
yy-admin-master/YY.Admin/Views/MixerMaterialTareStrategy/MixerMaterialTareStrategyEditDialogView.xaml
|
||||
yy-admin-master/YY.Admin/ViewModels/MixerMaterialTareStrategy/MixerMaterialTareStrategyEditDialogViewModel.cs
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategy.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/components/MesXslMixerMaterialTareStrategyModal.vue
|
||||
|
||||
-- author:cursor---date:20250602--for: 【密炼物料皮重策略】重叠校验明确纳入物料规格维度 ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialTareStrategyService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialTareStrategyServiceImpl.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialTareStrategy.java
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategy.data.ts
|
||||
yy-admin-master/YY.Admin/Views/MixerMaterialTareStrategy/MixerMaterialTareStrategyEditDialogView.xaml
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
-- 密炼物料皮重策略:建表 + 菜单(挂 MES基础资料)+ admin 授权
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_mixer_material_tare_strategy` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`mixer_material_id` varchar(32) NOT NULL COMMENT '密炼物料ID(关联 mes_mixer_material.id)',
|
||||
`mixer_material_name` varchar(200) DEFAULT NULL COMMENT '密炼物料名称冗余',
|
||||
`supplier_id` varchar(36) NOT NULL COMMENT '供应商ID(关联 mes_xsl_supplier.id)',
|
||||
`supplier_name` varchar(100) DEFAULT NULL COMMENT '供应商名称冗余',
|
||||
`tare_weight` decimal(12,3) NOT NULL COMMENT '皮重',
|
||||
`unit_id` varchar(36) DEFAULT NULL COMMENT '单位ID(关联 mes_xsl_unit.id)',
|
||||
`unit_name` varchar(64) DEFAULT NULL COMMENT '单位名称冗余',
|
||||
`effective_start_date` date NOT NULL COMMENT '生效开始日期',
|
||||
`effective_end_date` date NOT NULL COMMENT '生效截止日期',
|
||||
`maintain_by` varchar(50) DEFAULT NULL COMMENT '维护人(登录账号)',
|
||||
`sys_org_code` varchar(64) DEFAULT NULL 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 '修改时间',
|
||||
`del_flag` int NOT NULL DEFAULT 0 COMMENT '逻辑删除(0正常 1已删除)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_mxmts_tenant_supplier_material` (`tenant_id`, `supplier_id`, `mixer_material_id`),
|
||||
KEY `idx_mxmts_effective_dates` (`effective_start_date`, `effective_end_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES密炼物料皮重策略';
|
||||
|
||||
SET @mes_tenant_id = 1002;
|
||||
|
||||
SET @mes_base_pid = (
|
||||
SELECT MIN(`id`) FROM `sys_permission`
|
||||
WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` IN ('MES基础资料', 'MES资料')
|
||||
);
|
||||
SET @mes_base_pid = IFNULL(@mes_base_pid, '1860000000000000001');
|
||||
|
||||
UPDATE `sys_permission`
|
||||
SET `is_leaf` = 0, `update_time` = NOW()
|
||||
WHERE `id` = @mes_base_pid AND `is_leaf` = 1;
|
||||
|
||||
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
|
||||
'177925970995580', @mes_base_pid, '密炼物料皮重策略', '/xslmes/mesXslMixerMaterialTareStrategy',
|
||||
'xslmes/mesXslMixerMaterialTareStrategy/MesXslMixerMaterialTareStrategyList', 1, 'MesXslMixerMaterialTareStrategyList', NULL,
|
||||
1, NULL, '0', 18.00, 0, 'ant-design:database-outlined', 0, 1,
|
||||
0, 0, 'MES密炼物料皮重策略', 'admin', NOW(), 'admin', NOW(),
|
||||
0, 0, '1', 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_permission`
|
||||
WHERE `id` = '177925970995580'
|
||||
OR (`del_flag` = 0 AND `menu_type` = 1 AND `name` = '密炼物料皮重策略' AND `parent_id` = @mes_base_pid)
|
||||
);
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995581', '177925970995580', '新增', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:add', '1', 1.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995581');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995582', '177925970995580', '编辑', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:edit', '1', 2.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995582');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995583', '177925970995580', '删除', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:delete', '1', 3.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995583');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995584', '177925970995580', '批量删除', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:deleteBatch', '1', 4.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995584');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995585', '177925970995580', '导出', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:exportXls', '1', 5.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995585');
|
||||
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
SELECT '177925970995586', '177925970995580', '导入', 2, 'xslmes:mes_xsl_mixer_material_tare_strategy:importExcel', '1', 6.00, 0, 1, 0, '1', 0, 'admin', NOW()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995586');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
CROSS JOIN `sys_permission` p
|
||||
WHERE r.`tenant_id` = @mes_tenant_id
|
||||
AND r.`role_code` = 'admin'
|
||||
AND p.`id` IN (
|
||||
'177925970995580',
|
||||
'177925970995581',
|
||||
'177925970995582',
|
||||
'177925970995583',
|
||||
'177925970995584',
|
||||
'177925970995585',
|
||||
'177925970995586'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp
|
||||
WHERE rp.`role_id` = r.id AND rp.`permission_id` = p.id
|
||||
);
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
CROSS JOIN `sys_permission` p
|
||||
WHERE r.`role_code` = 'admin'
|
||||
AND r.`tenant_id` IS NULL
|
||||
AND p.`id` IN (
|
||||
'177925970995580',
|
||||
'177925970995581',
|
||||
'177925970995582',
|
||||
'177925970995583',
|
||||
'177925970995584',
|
||||
'177925970995585',
|
||||
'177925970995586'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp
|
||||
WHERE rp.`role_id` = r.id AND rp.`permission_id` = p.id
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 密炼物料皮重策略:新增物料规格、托盘重量;皮重字段注释改为包装物重量
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `mes_xsl_mixer_material_tare_strategy`
|
||||
ADD COLUMN `material_spec` varchar(200) DEFAULT NULL COMMENT '物料规格' AFTER `supplier_name`,
|
||||
ADD COLUMN `pallet_weight` decimal(12,3) DEFAULT NULL COMMENT '托盘重量' AFTER `tare_weight`;
|
||||
|
||||
ALTER TABLE `mes_xsl_mixer_material_tare_strategy`
|
||||
MODIFY COLUMN `tare_weight` decimal(12,3) NOT NULL COMMENT '包装物重量';
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 原料入场记录:托盘及皮重合计 + 拆码明细皮重策略相关字段
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `mes_xsl_raw_material_entry`
|
||||
ADD COLUMN `pallet_tare_total` decimal(12,3) DEFAULT NULL COMMENT '托盘及皮重(合计)' AFTER `total_weight`,
|
||||
ADD COLUMN `portion_packaging_tare` varchar(500) DEFAULT NULL COMMENT '拆码明细包装物皮重拼接(以/分隔,末尾带/)' AFTER `portion_weight`,
|
||||
ADD COLUMN `portion_pallet_weight` varchar(500) DEFAULT NULL COMMENT '拆码明细托盘重量拼接(以/分隔,末尾带/)' AFTER `portion_packaging_tare`,
|
||||
ADD COLUMN `portion_tare_strategy_ids` varchar(1000) DEFAULT NULL COMMENT '拆码明细皮重策略ID拼接(以/分隔,末尾带/)' AFTER `portion_pallet_weight`;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 原材料卡片:包装物皮重、托盘重量
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `mes_xsl_raw_material_card`
|
||||
ADD COLUMN `packaging_tare` decimal(12,3) DEFAULT NULL COMMENT '包装物皮重(KG)' AFTER `total_weight`,
|
||||
ADD COLUMN `pallet_weight` decimal(12,3) DEFAULT NULL COMMENT '托盘重量(KG)' AFTER `packaging_tare`;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslMixerMaterialTareStrategy/list',
|
||||
save = '/xslmes/mesXslMixerMaterialTareStrategy/add',
|
||||
edit = '/xslmes/mesXslMixerMaterialTareStrategy/edit',
|
||||
deleteOne = '/xslmes/mesXslMixerMaterialTareStrategy/delete',
|
||||
deleteBatch = '/xslmes/mesXslMixerMaterialTareStrategy/deleteBatch',
|
||||
importExcel = '/xslmes/mesXslMixerMaterialTareStrategy/importExcel',
|
||||
exportXls = '/xslmes/mesXslMixerMaterialTareStrategy/exportXls',
|
||||
queryById = '/xslmes/mesXslMixerMaterialTareStrategy/queryById',
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
export const queryById = (params) => defHttp.get({ url: Api.queryById, params });
|
||||
|
||||
export const deleteOne = (params, handleSuccess) =>
|
||||
defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => handleSuccess());
|
||||
|
||||
export const batchDelete = (params, handleSuccess) =>
|
||||
defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }).then(() => handleSuccess());
|
||||
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
const url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url, params }, { successMessageMode: 'none' });
|
||||
};
|
||||
|
||||
export const getExportUrl = Api.exportXls;
|
||||
export const getImportUrl = Api.importExcel;
|
||||
@@ -0,0 +1,153 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '密炼物料名称', align: 'center', dataIndex: 'mixerMaterialName', width: 140 },
|
||||
{ title: '供应商名称', align: 'center', dataIndex: 'supplierName', width: 140 },
|
||||
{ title: '物料规格', align: 'center', dataIndex: 'materialSpec', width: 120 },
|
||||
{ title: '包装物重量', align: 'center', dataIndex: 'tareWeight', width: 110 },
|
||||
{ title: '托盘重量', align: 'center', dataIndex: 'palletWeight', width: 100 },
|
||||
{ title: '单位', align: 'center', dataIndex: 'unitName', width: 80 },
|
||||
{ title: '生效开始日期', align: 'center', dataIndex: 'effectiveStartDate', width: 120, customRender: ({ text }) => (text ? String(text).substring(0, 10) : '') },
|
||||
{ title: '生效截止日期', align: 'center', dataIndex: 'effectiveEndDate', width: 120, customRender: ({ text }) => (text ? String(text).substring(0, 10) : '') },
|
||||
{ title: '维护人', align: 'center', dataIndex: 'maintainBy_dictText', width: 100 },
|
||||
{ title: '创建时间', align: 'center', dataIndex: 'createTime', width: 165 },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '密炼物料',
|
||||
field: 'mixerMaterialId',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'mes_mixer_material,material_name,id',
|
||||
placeholder: '请选择密炼物料',
|
||||
},
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{ label: '密炼物料名称', field: 'mixerMaterialName', component: 'JInput', colProps: { span: 6 } },
|
||||
{
|
||||
label: '供应商',
|
||||
field: 'supplierId',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'mes_xsl_supplier,supplier_name,id',
|
||||
placeholder: '请选择供应商',
|
||||
},
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{ label: '供应商名称', field: 'supplierName', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '物料规格', field: 'materialSpec', component: 'JInput', colProps: { span: 6 } },
|
||||
{
|
||||
label: '生效日期',
|
||||
field: 'effectiveDateRange',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: ['开始日期', '截止日期'],
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{ label: '', field: 'id', component: 'Input', show: false },
|
||||
{
|
||||
label: '密炼物料',
|
||||
field: 'mixerMaterialName',
|
||||
component: 'Input',
|
||||
slot: 'mixerMaterialPicker',
|
||||
dynamicRules: () => [{ required: true, message: '请选择密炼物料' }],
|
||||
},
|
||||
{ label: '', field: 'mixerMaterialId', component: 'Input', show: false },
|
||||
{
|
||||
label: '供应商',
|
||||
field: 'supplierName',
|
||||
component: 'Input',
|
||||
slot: 'supplierPicker',
|
||||
dynamicRules: () => [{ required: true, message: '请选择供应商' }],
|
||||
},
|
||||
{ label: '', field: 'supplierId', component: 'Input', show: false },
|
||||
{
|
||||
label: '物料规格',
|
||||
field: 'materialSpec',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入物料规格', maxlength: 200 },
|
||||
helpMessage: '同一租户/供应商/密炼物料下,不同规格可分别维护;规格相同且生效日期重叠时不允许重复',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '包装物重量',
|
||||
field: 'tareWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, precision: 3, style: { width: '100%' }, placeholder: '请输入包装物重量' },
|
||||
dynamicRules: () => [{ required: true, message: '请填写包装物重量' }],
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '托盘重量',
|
||||
field: 'palletWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, precision: 3, style: { width: '100%' }, placeholder: '请输入托盘重量' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '单位',
|
||||
field: 'unitName',
|
||||
component: 'Input',
|
||||
slot: 'unitPicker',
|
||||
dynamicRules: () => [{ required: true, message: '请选择单位' }],
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{ label: '', field: 'unitId', component: 'Input', show: false },
|
||||
{
|
||||
label: '生效开始日期',
|
||||
field: 'effectiveStartDate',
|
||||
component: 'DatePicker',
|
||||
componentProps: { valueFormat: 'YYYY-MM-DD', style: { width: '100%' }, placeholder: '请选择开始日期' },
|
||||
dynamicRules: () => [{ required: true, message: '请选择生效开始日期' }],
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '生效截止日期',
|
||||
field: 'effectiveEndDate',
|
||||
component: 'DatePicker',
|
||||
componentProps: { valueFormat: 'YYYY-MM-DD', style: { width: '100%' }, placeholder: '请选择截止日期' },
|
||||
dynamicRules: () => [{ required: true, message: '请选择生效截止日期' }],
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '维护人',
|
||||
field: 'maintainBy',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true, placeholder: '保存时自动带出当前登录用户' },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
];
|
||||
|
||||
export const superQuerySchema = {
|
||||
mixerMaterialId: {
|
||||
title: '密炼物料',
|
||||
order: 0,
|
||||
view: 'sel_search',
|
||||
dictTable: 'mes_mixer_material',
|
||||
dictCode: 'id',
|
||||
dictText: 'material_name',
|
||||
},
|
||||
mixerMaterialName: { title: '密炼物料名称', order: 1, view: 'text' },
|
||||
supplierId: {
|
||||
title: '供应商',
|
||||
order: 2,
|
||||
view: 'sel_search',
|
||||
dictTable: 'mes_xsl_supplier',
|
||||
dictCode: 'id',
|
||||
dictText: 'supplier_name',
|
||||
},
|
||||
supplierName: { title: '供应商名称', order: 3, view: 'text' },
|
||||
materialSpec: { title: '物料规格', order: 4, view: 'text' },
|
||||
tareWeight: { title: '包装物重量', order: 5, view: 'number' },
|
||||
palletWeight: { title: '托盘重量', order: 6, view: 'number' },
|
||||
unitName: { title: '单位', order: 7, view: 'text' },
|
||||
effectiveStartDate: { title: '生效开始日期', order: 8, view: 'date' },
|
||||
effectiveEndDate: { title: '生效截止日期', order: 9, view: 'date' },
|
||||
maintainBy: { title: '维护人', order: 10, view: 'text' },
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-auth="'xslmes:mes_xsl_mixer_material_tare_strategy:add'"
|
||||
@click="handleAdd"
|
||||
preIcon="ant-design:plus-outlined"
|
||||
>
|
||||
新增
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-auth="'xslmes:mes_xsl_mixer_material_tare_strategy:exportXls'"
|
||||
preIcon="ant-design:export-outlined"
|
||||
@click="onExportXls"
|
||||
>
|
||||
导出
|
||||
</a-button>
|
||||
<j-upload-button
|
||||
type="primary"
|
||||
v-auth="'xslmes:mes_xsl_mixer_material_tare_strategy:importExcel'"
|
||||
preIcon="ant-design:import-outlined"
|
||||
@click="onImportXls"
|
||||
>
|
||||
导入
|
||||
</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button v-auth="'xslmes:mes_xsl_mixer_material_tare_strategy:deleteBatch'">
|
||||
批量操作
|
||||
<Icon icon="mdi:chevron-down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: 'xslmes:mes_xsl_mixer_material_tare_strategy:edit',
|
||||
},
|
||||
]"
|
||||
:dropDownActions="getDropDownAction(record)"
|
||||
/>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<MesXslMixerMaterialTareStrategyModal @register="registerModal" @success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslMixerMaterialTareStrategy" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import Icon from '/@/components/Icon';
|
||||
import MesXslMixerMaterialTareStrategyModal from './components/MesXslMixerMaterialTareStrategyModal.vue';
|
||||
import { columns, searchFormSchema, superQuerySchema } from './MesXslMixerMaterialTareStrategy.data';
|
||||
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslMixerMaterialTareStrategy.api';
|
||||
|
||||
const queryParam = reactive<any>({});
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
const { tableContext, onExportXls, onImportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '密炼物料皮重策略',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
labelWidth: 110,
|
||||
autoSubmitOnEnter: true,
|
||||
showAdvancedButton: true,
|
||||
fieldMapToTime: [['effectiveDateRange', ['effectiveEndDate_begin', 'effectiveStartDate_end'], 'YYYY-MM-DD']],
|
||||
},
|
||||
actionColumn: {
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
slots: { customRender: 'action' },
|
||||
},
|
||||
beforeFetch: (params) => Object.assign(params, queryParam),
|
||||
},
|
||||
exportConfig: {
|
||||
name: '密炼物料皮重策略',
|
||||
url: getExportUrl,
|
||||
params: queryParam,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
success: handleSuccess,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const superQueryConfig = reactive(superQuerySchema);
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).forEach((k) => {
|
||||
queryParam[k] = params[k];
|
||||
});
|
||||
reload();
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
openModal(true, { isUpdate: false, showFooter: true });
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
openModal(true, { record, isUpdate: true, showFooter: true });
|
||||
}
|
||||
|
||||
function handleDetail(record: Recordable) {
|
||||
openModal(true, { record, isUpdate: true, showFooter: false });
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
deleteOne({ id: record.id }, handleSuccess);
|
||||
}
|
||||
|
||||
function batchHandleDelete() {
|
||||
batchDelete({ ids: selectedRowKeys.value.join(',') }, handleSuccess);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
reload();
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
|
||||
function getDropDownAction(record: Recordable) {
|
||||
return [
|
||||
{ label: '详情', onClick: handleDetail.bind(null, record) },
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record) },
|
||||
auth: 'xslmes:mes_xsl_mixer_material_tare_strategy:delete',
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="860" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm">
|
||||
<template #mixerMaterialPicker="{ model }">
|
||||
<a-input-group compact style="display: flex; width: 100%">
|
||||
<a-input
|
||||
v-model:value="model.mixerMaterialName"
|
||||
read-only
|
||||
placeholder="请点击选择密炼物料"
|
||||
style="flex: 1"
|
||||
:disabled="isDetail"
|
||||
/>
|
||||
<a-button type="primary" :disabled="isDetail" @click="openMixerSelect">选择</a-button>
|
||||
<a-button v-if="model.mixerMaterialId && !isDetail" @click="clearMixer(model)">清除</a-button>
|
||||
</a-input-group>
|
||||
</template>
|
||||
<template #supplierPicker="{ model }">
|
||||
<a-input-group compact style="display: flex; width: 100%">
|
||||
<a-input
|
||||
v-model:value="model.supplierName"
|
||||
read-only
|
||||
placeholder="请点击选择供应商"
|
||||
style="flex: 1"
|
||||
:disabled="isDetail"
|
||||
/>
|
||||
<a-button type="primary" :disabled="isDetail" @click="openSupplierSelect">选择</a-button>
|
||||
<a-button v-if="model.supplierId && !isDetail" @click="clearSupplier(model)">清除</a-button>
|
||||
</a-input-group>
|
||||
</template>
|
||||
<template #unitPicker="{ model }">
|
||||
<a-input-group compact style="display: flex; width: 100%">
|
||||
<a-input
|
||||
v-model:value="model.unitName"
|
||||
read-only
|
||||
placeholder="请点击选择单位"
|
||||
style="flex: 1"
|
||||
:disabled="isDetail"
|
||||
/>
|
||||
<a-button type="primary" :disabled="isDetail" @click="openUnitSelect">选择</a-button>
|
||||
<a-button v-if="model.unitId && !isDetail" @click="clearUnit(model)">清除</a-button>
|
||||
</a-input-group>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<MesMixerMaterialSelectModal @register="registerMixerModal" @select="onMixerSelect" />
|
||||
<MesXslSupplierSelectModal @register="registerSupplierModal" @select="onSupplierSelect" />
|
||||
<MesXslUnitSelectModal @register="registerUnitModal" @select="onUnitSelect" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref } from 'vue';
|
||||
import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import MesMixerMaterialSelectModal from '/@/views/mes/material/modules/MesMixerMaterialSelectModal.vue';
|
||||
import MesXslSupplierSelectModal from '/@/views/xslmes/mesXslVehicle/components/MesXslSupplierSelectModal.vue';
|
||||
import MesXslUnitSelectModal from '/@/views/xslmes/mesXslVehicle/components/MesXslUnitSelectModal.vue';
|
||||
import { formSchema } from '../MesXslMixerMaterialTareStrategy.data';
|
||||
import { saveOrUpdate } from '../MesXslMixerMaterialTareStrategy.api';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const userStore = useUserStore();
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
const isDetail = ref(false);
|
||||
|
||||
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
|
||||
labelWidth: 130,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { span: 24 },
|
||||
});
|
||||
|
||||
const [registerMixerModal, { openModal: openMixerModal }] = useModal();
|
||||
const [registerSupplierModal, { openModal: openSupplierModal }] = useModal();
|
||||
const [registerUnitModal, { openModal: openUnitModal }] = useModal();
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
isDetail.value = !data?.showFooter;
|
||||
if (unref(isUpdate)) {
|
||||
await setFieldsValue({ ...data.record });
|
||||
} else {
|
||||
await setFieldsValue({ maintainBy: userStore.getUserInfo?.username || '' });
|
||||
}
|
||||
setProps({ disabled: !data?.showFooter });
|
||||
});
|
||||
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : unref(isDetail) ? '详情' : '编辑'));
|
||||
|
||||
function openMixerSelect() {
|
||||
openMixerModal(true, {});
|
||||
}
|
||||
|
||||
function onMixerSelect(payload: Recordable | null) {
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
setFieldsValue({
|
||||
mixerMaterialId: payload.mixerMaterialId,
|
||||
mixerMaterialName: payload.materialName || '',
|
||||
});
|
||||
}
|
||||
|
||||
function clearMixer(model: Recordable) {
|
||||
model.mixerMaterialId = '';
|
||||
model.mixerMaterialName = '';
|
||||
}
|
||||
|
||||
function openSupplierSelect() {
|
||||
openSupplierModal(true, {});
|
||||
}
|
||||
|
||||
function onSupplierSelect(payload: Recordable | null) {
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
setFieldsValue({
|
||||
supplierId: payload.supplierId || payload.id,
|
||||
supplierName: payload.supplierName || '',
|
||||
});
|
||||
}
|
||||
|
||||
function clearSupplier(model: Recordable) {
|
||||
model.supplierId = '';
|
||||
model.supplierName = '';
|
||||
}
|
||||
|
||||
function openUnitSelect() {
|
||||
openUnitModal(true, {});
|
||||
}
|
||||
|
||||
function onUnitSelect(payload: { unitId: string; unitName: string }) {
|
||||
setFieldsValue({
|
||||
unitId: payload.unitId || undefined,
|
||||
unitName: payload.unitName || '',
|
||||
});
|
||||
}
|
||||
|
||||
function clearUnit(model: Recordable) {
|
||||
model.unitId = '';
|
||||
model.unitName = '';
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
if (!values.mixerMaterialId) {
|
||||
createMessage.warning('请选择密炼物料');
|
||||
return;
|
||||
}
|
||||
if (!values.supplierId) {
|
||||
createMessage.warning('请选择供应商');
|
||||
return;
|
||||
}
|
||||
if (!values.unitId) {
|
||||
createMessage.warning('请选择单位');
|
||||
return;
|
||||
}
|
||||
if (values.effectiveStartDate && values.effectiveEndDate && values.effectiveStartDate > values.effectiveEndDate) {
|
||||
createMessage.warning('生效开始日期不能晚于截止日期');
|
||||
return;
|
||||
}
|
||||
if (values.palletWeight != null && values.palletWeight < 0) {
|
||||
createMessage.warning('托盘重量不能为负数');
|
||||
return;
|
||||
}
|
||||
if (values.materialSpec) {
|
||||
values.materialSpec = String(values.materialSpec).trim();
|
||||
} else {
|
||||
values.materialSpec = undefined;
|
||||
}
|
||||
setModalProps({ confirmLoading: true });
|
||||
await saveOrUpdate(values, unref(isUpdate));
|
||||
createMessage.success(unref(isUpdate) ? '编辑成功' : '新增成功');
|
||||
closeModal();
|
||||
emit('success');
|
||||
} catch (e: any) {
|
||||
if (e?.errorFields) {
|
||||
const firstField = e.errorFields[0];
|
||||
if (firstField) {
|
||||
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -54,6 +54,18 @@ export const columns: BasicColumn[] = [
|
||||
dataIndex: 'totalWeight',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '包装物皮重',
|
||||
align: 'center',
|
||||
dataIndex: 'packagingTare',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '托盘重量',
|
||||
align: 'center',
|
||||
dataIndex: 'palletWeight',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '剩余重量',
|
||||
align: 'center',
|
||||
@@ -238,6 +250,20 @@ export const formSchemaAdd: FormSchema[] = [
|
||||
componentProps: { placeholder: '请输入总重', precision: 3 },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '包装物皮重',
|
||||
field: 'packagingTare',
|
||||
component: 'InputNumber',
|
||||
componentProps: { disabled: true, precision: 3, style: { width: '100%' } },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '托盘重量',
|
||||
field: 'palletWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { disabled: true, precision: 3, style: { width: '100%' } },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
label: '剩余重量',
|
||||
field: 'remainingWeight',
|
||||
@@ -321,6 +347,20 @@ export const formSchemaEdit: FormSchema[] = [
|
||||
componentProps: { disabled: true },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '包装物皮重',
|
||||
field: 'packagingTare',
|
||||
component: 'InputNumber',
|
||||
componentProps: { disabled: true, precision: 3, style: { width: '100%' } },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '托盘重量',
|
||||
field: 'palletWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { disabled: true, precision: 3, style: { width: '100%' } },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '剩余数量',
|
||||
field: 'remainingQuantity',
|
||||
@@ -352,10 +392,12 @@ export const superQuerySchema = {
|
||||
materialName: { title: '物料名称', order: 3, view: 'text' },
|
||||
supplierName: { title: '供应商名称', order: 4, view: 'text' },
|
||||
totalWeight: { title: '总重', order: 5, view: 'number' },
|
||||
remainingWeight: { title: '剩余重量', order: 6, view: 'number' },
|
||||
remainingQuantity: { title: '剩余数量', order: 7, view: 'number' },
|
||||
status: { title: '状态', order: 8, view: 'list', dictCode: 'xslmes_card_status' },
|
||||
testResult: { title: '检测结果', order: 9, view: 'list', dictCode: 'xslmes_test_result' },
|
||||
warehouseArea: { title: '库区', order: 10, view: 'text' },
|
||||
createTime: { title: '创建时间', order: 11, view: 'datetime' },
|
||||
packagingTare: { title: '包装物皮重', order: 6, view: 'number' },
|
||||
palletWeight: { title: '托盘重量', order: 7, view: 'number' },
|
||||
remainingWeight: { title: '剩余重量', order: 8, view: 'number' },
|
||||
remainingQuantity: { title: '剩余数量', order: 9, view: 'number' },
|
||||
status: { title: '状态', order: 10, view: 'list', dictCode: 'xslmes_card_status' },
|
||||
testResult: { title: '检测结果', order: 11, view: 'list', dictCode: 'xslmes_test_result' },
|
||||
warehouseArea: { title: '库区', order: 12, view: 'text' },
|
||||
createTime: { title: '创建时间', order: 13, view: 'datetime' },
|
||||
};
|
||||
|
||||
@@ -17,8 +17,11 @@ export const columns: BasicColumn[] = [
|
||||
{ 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: 'palletTareTotal', width: 120 },
|
||||
{ title: '总份数', align: 'center', dataIndex: 'totalPortions', width: 80 },
|
||||
{ title: '每份总重(KG)', align: 'center', dataIndex: 'portionWeight', width: 110 },
|
||||
{ title: '包装物皮重', align: 'center', dataIndex: 'portionPackagingTare', width: 110, ellipsis: true },
|
||||
{ title: '托盘重量', align: 'center', dataIndex: 'portionPalletWeight', width: 100, ellipsis: true },
|
||||
{ title: '每份包数', align: 'center', dataIndex: 'portionPackages', width: 80 },
|
||||
{ title: '检测结果', align: 'center', dataIndex: 'testResult_dictText', width: 90 },
|
||||
{ title: '检测状态', align: 'center', dataIndex: 'testStatus_dictText', width: 90 },
|
||||
@@ -155,6 +158,12 @@ export const formSchema: FormSchema[] = [
|
||||
component: 'InputNumber',
|
||||
componentProps: { min: 0, precision: 2, placeholder: '请输入总重', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '托盘及皮重(合计)',
|
||||
field: 'palletTareTotal',
|
||||
component: 'InputNumber',
|
||||
componentProps: { disabled: true, precision: 3, style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
// 字段升级为字符串类型,支持桌面端拆码明细多行拼接(如 20/1/)
|
||||
label: '总份数',
|
||||
@@ -168,6 +177,18 @@ export const formSchema: FormSchema[] = [
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入每份总重(多行明细用 / 拼接)', style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '包装物皮重',
|
||||
field: 'portionPackagingTare',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true, placeholder: '拆码明细拼接(/ 分隔)' },
|
||||
},
|
||||
{
|
||||
label: '托盘重量',
|
||||
field: 'portionPalletWeight',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true, placeholder: '拆码明细拼接(/ 分隔)' },
|
||||
},
|
||||
{
|
||||
label: '每份包数',
|
||||
field: 'portionPackages',
|
||||
@@ -258,7 +279,8 @@ export const superQuerySchema = {
|
||||
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' },
|
||||
palletTareTotal: { title: '托盘及皮重(合计)', order: 7, view: 'number' },
|
||||
testStatus: { title: '检测状态', order: 8, view: 'list', dictCode: 'xslmes_test_status' },
|
||||
isSpecialAdoption: { title: '是否特采', order: 9, view: 'list', dictCode: 'yn' },
|
||||
status: { title: '状态', order: 10, view: 'list', dictCode: 'xslmes_entry_status' },
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ export const columns: BasicColumn[] = [
|
||||
{ title: '供应商', align: 'center', dataIndex: 'supplierName', width: 160, ellipsis: true },
|
||||
{ title: '保质期', align: 'center', dataIndex: 'shelfLife', width: 120 },
|
||||
{ title: '总重', align: 'center', dataIndex: 'totalWeight', width: 110 },
|
||||
{ title: '包装物皮重', align: 'center', dataIndex: 'packagingTare', width: 110 },
|
||||
{ title: '托盘重量', align: 'center', dataIndex: 'palletWeight', width: 100 },
|
||||
{ title: '剩余重量', align: 'center', dataIndex: 'remainingWeight', width: 110 },
|
||||
{ title: '剩余数量', align: 'center', dataIndex: 'remainingQuantity', width: 110 },
|
||||
{ title: '检测结果', align: 'center', dataIndex: 'testResult_dictText', width: 110 },
|
||||
@@ -64,9 +66,11 @@ export const superQuerySchema = {
|
||||
supplierName: { title: '供应商', order: 4, view: 'text' },
|
||||
shelfLife: { title: '保质期', order: 5, view: 'text' },
|
||||
totalWeight: { title: '总重', order: 6, view: 'number' },
|
||||
remainingWeight: { title: '剩余重量', order: 7, view: 'number' },
|
||||
remainingQuantity: { title: '剩余数量', order: 8, view: 'number' },
|
||||
testResult: { title: '检测结果', order: 9, view: 'list', dictCode: 'xslmes_test_result' },
|
||||
status: { title: '状态', order: 10, view: 'list', dictCode: 'xslmes_card_status' },
|
||||
priorityPickup: { title: '优先使用', order: 11, view: 'list', dictCode: 'yn' },
|
||||
packagingTare: { title: '包装物皮重', order: 7, view: 'number' },
|
||||
palletWeight: { title: '托盘重量', order: 8, view: 'number' },
|
||||
remainingWeight: { title: '剩余重量', order: 9, view: 'number' },
|
||||
remainingQuantity: { title: '剩余数量', order: 10, view: 'number' },
|
||||
testResult: { title: '检测结果', order: 11, view: 'list', dictCode: 'xslmes_test_result' },
|
||||
status: { title: '状态', order: 12, view: 'list', dictCode: 'xslmes_card_status' },
|
||||
priorityPickup: { title: '优先使用', order: 13, view: 'list', dictCode: 'yn' },
|
||||
};
|
||||
|
||||
@@ -31,6 +31,30 @@ export const columns: BasicColumn[] = [
|
||||
return Number.isInteger(n) ? String(n) : n.toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '货物皮重',
|
||||
align: 'center',
|
||||
dataIndex: 'cargoTareWeight',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
if (text === null || text === undefined || text === '') return '0';
|
||||
const n = Number(text);
|
||||
if (Number.isNaN(n)) return String(text);
|
||||
return Number.isInteger(n) ? String(n) : n.toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '原料重量',
|
||||
align: 'center',
|
||||
dataIndex: 'rawMaterialWeight',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
if (text === null || text === undefined || text === '') return '';
|
||||
const n = Number(text);
|
||||
if (Number.isNaN(n)) return String(text);
|
||||
return Number.isInteger(n) ? String(n) : n.toFixed(2);
|
||||
},
|
||||
},
|
||||
{ title: '司机', align: 'center', dataIndex: 'driverName', width: 90 },
|
||||
{ title: '手机号', align: 'center', dataIndex: 'driverPhone', width: 120 },
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"typeRoots": ["./node_modules/@types/", "./types","./node_modules"],
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"/@/*": ["src/*"],
|
||||
"/#/*": ["types/*"],
|
||||
|
||||
3
scan_tare_strategy.json
Normal file
3
scan_tare_strategy.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"error": "未找到匹配 'MesXslMixerMaterialTareStrategy' 的实体类,建议使用完整类名如 MesXslVehicle"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace YY.Admin.Core.Events;
|
||||
|
||||
public class MixerMaterialTareStrategyChangedPayload
|
||||
{
|
||||
public string Action { get; set; } = string.Empty;
|
||||
public string? TareStrategyId { get; set; }
|
||||
}
|
||||
|
||||
public class MixerMaterialTareStrategyChangedEvent : PubSubEvent<MixerMaterialTareStrategyChangedPayload> { }
|
||||
@@ -0,0 +1,28 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
public interface IMixerMaterialTareStrategyService
|
||||
{
|
||||
Task<MixerMaterialTareStrategyPageResult> PageAsync(
|
||||
int pageNo,
|
||||
int pageSize,
|
||||
string? mixerMaterialName = null,
|
||||
string? supplierName = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<MesXslMixerMaterialTareStrategy?> GetByIdAsync(string id, CancellationToken ct = default);
|
||||
Task<bool> AddAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct = default);
|
||||
Task<bool> EditAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct = default);
|
||||
Task<bool> DeleteAsync(string id, CancellationToken ct = default);
|
||||
Task<List<MesXslUnit>> GetUnitsAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>拉取全部策略(用于原料入场拆码明细自动/手动匹配)。</summary>
|
||||
Task<IReadOnlyList<MesXslMixerMaterialTareStrategy>> GetAllForMatchAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public record MixerMaterialTareStrategyPageResult(
|
||||
List<MesXslMixerMaterialTareStrategy> Records,
|
||||
long Total,
|
||||
int PageNo,
|
||||
int PageSize);
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
public class MesXslMixerMaterialTareStrategy
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public int? TenantId { get; set; }
|
||||
public string? MixerMaterialId { get; set; }
|
||||
public string? MixerMaterialName { get; set; }
|
||||
public string? SupplierId { get; set; }
|
||||
public string? SupplierName { get; set; }
|
||||
public string? MaterialSpec { get; set; }
|
||||
public decimal? TareWeight { get; set; }
|
||||
public decimal? PalletWeight { get; set; }
|
||||
public string? UnitId { get; set; }
|
||||
public string? UnitName { get; set; }
|
||||
public DateTime? EffectiveStartDate { get; set; }
|
||||
public DateTime? EffectiveEndDate { get; set; }
|
||||
public string? MaintainBy { get; set; }
|
||||
public string? CreateBy { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
public string? UpdateBy { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
public string? SysOrgCode { get; set; }
|
||||
public int? DelFlag { get; set; }
|
||||
|
||||
public string EffectiveStartDateText =>
|
||||
EffectiveStartDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||||
|
||||
public string EffectiveEndDateText =>
|
||||
EffectiveEndDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||||
}
|
||||
@@ -17,6 +17,10 @@ public class MesXslRawMaterialCard
|
||||
public string? ManufacturerMaterialName { get; set; }
|
||||
public string? ShelfLife { get; set; }
|
||||
public decimal? TotalWeight { get; set; }
|
||||
/// <summary>包装物皮重(KG)</summary>
|
||||
public decimal? PackagingTare { get; set; }
|
||||
/// <summary>托盘重量(KG)</summary>
|
||||
public decimal? PalletWeight { get; set; }
|
||||
public decimal? RemainingWeight { get; set; }
|
||||
public int? RemainingQuantity { get; set; }
|
||||
|
||||
|
||||
@@ -17,11 +17,15 @@ public class MesXslRawMaterialEntry
|
||||
public string? ManufacturerMaterialName { get; set; }
|
||||
public string? ShelfLife { get; set; }
|
||||
public double? TotalWeight { get; set; }
|
||||
public double? PalletTareTotal { get; set; }
|
||||
|
||||
// 总份数 / 每份总重 / 每份包数:与后端同步升级为字符串,
|
||||
// 用于持久化「拆码明细」多行拼接(如 20/1/、100/200/)。
|
||||
public string? TotalPortions { get; set; }
|
||||
public string? PortionWeight { get; set; }
|
||||
public string? PortionPackagingTare { get; set; }
|
||||
public string? PortionPalletWeight { get; set; }
|
||||
public string? PortionTareStrategyIds { get; set; }
|
||||
public string? PortionPackages { get; set; }
|
||||
// 拆码明细各行库位的拼接(以 / 分隔,末尾带 /,如 1F-A01/1F-A02/)。
|
||||
// 与 WarehouseLocation(基础资料整票级单值)独立,专供明细行回填。
|
||||
|
||||
9
yy-admin-master/YY.Admin.Core/Entity/MesXslUnit.cs
Normal file
9
yy-admin-master/YY.Admin.Core/Entity/MesXslUnit.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
public class MesXslUnit
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? UnitCode { get; set; }
|
||||
public string? UnitName { get; set; }
|
||||
public int? TenantId { get; set; }
|
||||
}
|
||||
@@ -40,6 +40,17 @@ public class MesXslWeightRecord
|
||||
/// </summary>
|
||||
public double? EnteredWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 货物皮重(KG) —— 后端/本地实时计算,不入库。
|
||||
/// 来源:所有引用本榜单(BillNo)的原料入场记录 pallet_tare_total(托盘及皮重合计)累加。
|
||||
/// </summary>
|
||||
public double? CargoTareWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原料重量(KG) —— 实时计算,不入库。公式:净重 - 货物皮重。
|
||||
/// </summary>
|
||||
public double? RawMaterialWeight { get; set; }
|
||||
|
||||
/// <summary>司机姓名</summary>
|
||||
public string? DriverName { get; set; }
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
new SysMenu{ Id=1300150010901, Pid=1300150000101, Title="原材料卡片", Path="/xslmes/mesXslRawMaterialCard", Name="mesXslRawMaterialCard", Component="RawMaterialCardListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=108 },
|
||||
// 库区管理
|
||||
new SysMenu{ Id=1300150011001, Pid=1300150000101, Title="库区管理", Path="/xslmes/mesXslWarehouseArea", Name="mesXslWarehouseArea", Component="WarehouseAreaListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=109 },
|
||||
// 密炼物料皮重策略
|
||||
new SysMenu{ Id=1300150011101, Pid=1300150000101, Title="密炼物料皮重策略", Path="/xslmes/mesXslMixerMaterialTareStrategy", Name="mesXslMixerMaterialTareStrategy", Component="MixerMaterialTareStrategyListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 },
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010801},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010901},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011001},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012111},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012121},
|
||||
|
||||
@@ -268,6 +268,8 @@ namespace YY.Admin.Core.SqlSugar
|
||||
if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
|
||||
// 关闭全量种子时首启可能无菜单数据;补一份基准菜单,避免打包版本左侧空白
|
||||
EnsureBaselineSysMenuSeed(db, config);
|
||||
// 旧库升级:按种子补全缺失菜单及租户/角色授权(仅插入缺失项)
|
||||
EnsureIncrementalDesktopMenuSeed(db, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -392,6 +394,106 @@ namespace YY.Admin.Core.SqlSugar
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 旧库升级:sys_menu 已有数据时,按 SysMenuSeedData 补全缺失菜单,并同步租户菜单、管理员角色菜单授权。
|
||||
/// </summary>
|
||||
private static void EnsureIncrementalDesktopMenuSeed(SqlSugarScope db, DbConnectionConfig config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.Equals(config.ConfigId.ToString(), SqlSugarConst.MainConfigId, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.DbType != DbType.Sqlite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
||||
var menuEntityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
|
||||
if (!dbProvider.DbMaintenance.IsAnyTable(menuEntityInfo.DbTableName, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbProvider.Queryable<SysMenu>().ClearFilter().Count() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var seedMenus = new SysMenuSeedData().HasData().ToList();
|
||||
var existingMenuIds = dbProvider.Queryable<SysMenu>().ClearFilter()
|
||||
.Select(m => m.Id).ToList().ToHashSet();
|
||||
var missingMenus = seedMenus.Where(m => !existingMenuIds.Contains(m.Id)).ToList();
|
||||
if (missingMenus.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var menu in missingMenus)
|
||||
{
|
||||
if (menu.CreateTime == default)
|
||||
{
|
||||
menu.CreateTime = DateTime.Parse("2022-02-10 00:00:00");
|
||||
}
|
||||
|
||||
menu.Status = StatusEnum.Enable;
|
||||
}
|
||||
|
||||
dbProvider.Insertable(missingMenus).ExecuteCommand();
|
||||
|
||||
const long defaultTenantId = 1300000000001;
|
||||
var tenantSeed = new SysTenantMenuSeedData().HasData()
|
||||
.Where(t => t.TenantId == defaultTenantId)
|
||||
.ToList();
|
||||
var existingTenantMenuIds = dbProvider.Queryable<SysTenantMenu>()
|
||||
.Where(t => t.TenantId == defaultTenantId)
|
||||
.Select(t => t.MenuId)
|
||||
.ToList()
|
||||
.ToHashSet();
|
||||
|
||||
var tenantMenusToInsert = tenantSeed
|
||||
.Where(t => missingMenus.Any(m => m.Id == t.MenuId) && !existingTenantMenuIds.Contains(t.MenuId))
|
||||
.Select(t => new SysTenantMenu { Id = t.MenuId, TenantId = t.TenantId, MenuId = t.MenuId })
|
||||
.ToList();
|
||||
if (tenantMenusToInsert.Count > 0)
|
||||
{
|
||||
dbProvider.Insertable(tenantMenusToInsert).ExecuteCommand();
|
||||
}
|
||||
|
||||
var adminRole = dbProvider.Queryable<SysRole>().OrderBy(r => r.Id).First();
|
||||
if (adminRole == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existingRoleMenuIds = dbProvider.Queryable<SysRoleMenu>()
|
||||
.Where(r => r.RoleId == adminRole.Id)
|
||||
.Select(r => r.MenuId)
|
||||
.ToList()
|
||||
.ToHashSet();
|
||||
var roleMenusToInsert = missingMenus
|
||||
.Where(m => !existingRoleMenuIds.Contains(m.Id))
|
||||
.Select(m => new SysRoleMenu
|
||||
{
|
||||
Id = m.Id + (adminRole.Id % 1300000000000),
|
||||
RoleId = adminRole.Id,
|
||||
MenuId = m.Id,
|
||||
})
|
||||
.ToList();
|
||||
if (roleMenusToInsert.Count > 0)
|
||||
{
|
||||
dbProvider.Insertable(roleMenusToInsert).ExecuteCommand();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 启动阶段不因增量菜单失败而阻断
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 兼容旧库:补齐桌面端「登录设置」所需的 sys_config 配置项(升级前库可能缺少这些 code)
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Util;
|
||||
|
||||
/// <summary>
|
||||
/// 「货物皮重」桌面端本地累计计算器。
|
||||
/// 与后端 IMesXslRawMaterialEntryService.sumCargoTareByBillNos 保持同一口径:
|
||||
/// 同一榜单(BillNo)下所有原料入场记录的 PalletTareTotal(托盘及皮重合计)累加。
|
||||
/// </summary>
|
||||
public static class CargoTareWeightCalculator
|
||||
{
|
||||
/// <summary>按 BillNo 分组累计「货物皮重」。</summary>
|
||||
public static Dictionary<string, double> SumByBillNos(
|
||||
IEnumerable<MesXslRawMaterialEntry> entries,
|
||||
IEnumerable<string?> billNos)
|
||||
{
|
||||
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var b in billNos)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(b)) keys.Add(b!);
|
||||
}
|
||||
var result = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.BillNo) || !keys.Contains(e.BillNo!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (e.PalletTareTotal is not { } tare || tare == 0d)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (result.TryGetValue(e.BillNo!, out var acc))
|
||||
{
|
||||
result[e.BillNo!] = acc + tare;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[e.BillNo!] = tare;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Globalization;
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Services.Service.MixerMaterialTareStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// 密炼物料皮重策略匹配:按密炼物料、供应商、入场日期、每份重量(对应策略物料规格)筛选。
|
||||
/// </summary>
|
||||
public static class MixerMaterialTareStrategyMatcher
|
||||
{
|
||||
public static List<MesXslMixerMaterialTareStrategy> FilterCandidates(
|
||||
IEnumerable<MesXslMixerMaterialTareStrategy> strategies,
|
||||
string? mixerMaterialId,
|
||||
string? supplierId,
|
||||
DateTime? entryDate,
|
||||
double? portionWeight)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mixerMaterialId) || string.IsNullOrWhiteSpace(supplierId))
|
||||
{
|
||||
return new List<MesXslMixerMaterialTareStrategy>();
|
||||
}
|
||||
|
||||
var entryDay = entryDate?.Date;
|
||||
return strategies
|
||||
.Where(s => string.Equals(s.MixerMaterialId, mixerMaterialId, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(s => string.Equals(s.SupplierId, supplierId, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(s => IsEntryDateInRange(entryDay, s.EffectiveStartDate, s.EffectiveEndDate))
|
||||
.Where(s => MaterialSpecMatchesPortionWeight(s.MaterialSpec, portionWeight))
|
||||
.OrderByDescending(s => s.EffectiveStartDate ?? DateTime.MinValue)
|
||||
.ThenByDescending(s => s.CreateTime ?? DateTime.MinValue)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static MesXslMixerMaterialTareStrategy? PickBestMatch(
|
||||
IEnumerable<MesXslMixerMaterialTareStrategy> strategies,
|
||||
string? mixerMaterialId,
|
||||
string? supplierId,
|
||||
DateTime? entryDate,
|
||||
double? portionWeight)
|
||||
{
|
||||
var list = FilterCandidates(strategies, mixerMaterialId, supplierId, entryDate, portionWeight);
|
||||
return list.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static bool IsEntryDateInRange(DateTime? entryDay, DateTime? start, DateTime? end)
|
||||
{
|
||||
if (!entryDay.HasValue || !start.HasValue || !end.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var day = entryDay.Value.Date;
|
||||
return day >= start.Value.Date && day <= end.Value.Date;
|
||||
}
|
||||
|
||||
/// <summary>策略「物料规格」与拆码明细「每份重量」比对(支持数值或文本形式)。</summary>
|
||||
public static bool MaterialSpecMatchesPortionWeight(string? materialSpec, double? portionWeight)
|
||||
{
|
||||
if (!portionWeight.HasValue)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(materialSpec);
|
||||
}
|
||||
|
||||
var weight = portionWeight.Value;
|
||||
if (string.IsNullOrWhiteSpace(materialSpec))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var spec = materialSpec.Trim();
|
||||
if (double.TryParse(spec, NumberStyles.Float, CultureInfo.InvariantCulture, out var specNum))
|
||||
{
|
||||
return Math.Abs(specNum - weight) < 0.0001d;
|
||||
}
|
||||
|
||||
var weightText = weight.ToString("0.##", CultureInfo.InvariantCulture);
|
||||
return string.Equals(spec, weightText, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(spec, weight.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using Prism.Events;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.Services.Service.MixerMaterialTareStrategy;
|
||||
|
||||
public class MixerMaterialTareStrategyService : IMixerMaterialTareStrategyService, ISingletonDependency
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly INetworkMonitor _networkMonitor;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly SemaphoreSlim _syncLock = new(1, 1);
|
||||
private readonly object _cacheLock = new();
|
||||
private readonly string _pendingOpsFilePath;
|
||||
private readonly string _cacheFilePath;
|
||||
private List<TareStrategyPendingOperation> _pendingOps = new();
|
||||
private List<MesXslMixerMaterialTareStrategy> _localCache = new();
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Converters = { new NullableDateTimeJsonConverter() }
|
||||
};
|
||||
|
||||
public MixerMaterialTareStrategyService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
INetworkMonitor networkMonitor,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_networkMonitor = networkMonitor;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
|
||||
var appDataDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"YY.Admin", "sync-cache");
|
||||
Directory.CreateDirectory(appDataDir);
|
||||
_pendingOpsFilePath = Path.Combine(appDataDir, "mes-xsl-tare-strategy-pending-ops.json");
|
||||
_cacheFilePath = Path.Combine(appDataDir, "mes-xsl-tare-strategy-cache.json");
|
||||
|
||||
LoadPendingOpsFromDisk();
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[密炼物料皮重策略同步] 服务初始化,缓存={_localCache.Count},待上传={_pendingOps.Count},在线={_networkMonitor.IsOnline}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
_ = Task.Run(() => SyncAfterReconnectAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private const int MaxPendingRetries = 5;
|
||||
|
||||
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
|
||||
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
|
||||
|
||||
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
|
||||
|
||||
public async Task<MixerMaterialTareStrategyPageResult> PageAsync(
|
||||
int pageNo, int pageSize,
|
||||
string? mixerMaterialName = null, string? supplierName = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
List<MesXslMixerMaterialTareStrategy>? source = null;
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = await FetchRemoteListAsync(ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = source.Select(Clone).ToList();
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
source = null;
|
||||
_logger.Warning($"[密炼物料皮重策略列表] 远端拉取失败,回退缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
source ??= _localCache.Select(Clone).ToList();
|
||||
source = ApplyPendingOpsSnapshotUnsafe(source);
|
||||
}
|
||||
|
||||
var filtered = ApplyFilters(source, mixerMaterialName, supplierName);
|
||||
var total = filtered.Count;
|
||||
var records = filtered.Skip(Math.Max(0, (pageNo - 1) * pageSize)).Take(pageSize).ToList();
|
||||
return new MixerMaterialTareStrategyPageResult(records, total, pageNo, pageSize);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<MesXslMixerMaterialTareStrategy>> GetAllForMatchAsync(CancellationToken ct = default)
|
||||
{
|
||||
var page = await PageAsync(1, 10000, null, null, ct).ConfigureAwait(false);
|
||||
return page.Records;
|
||||
}
|
||||
|
||||
public async Task<MesXslMixerMaterialTareStrategy?> GetByIdAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl)) return null;
|
||||
return resultEl.Deserialize<MesXslMixerMaterialTareStrategy>(_jsonOpts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略详情] 远端查询失败 id={id},回退缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
return _localCache.FirstOrDefault(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase)) is { } found
|
||||
? Clone(found) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> AddAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct = default)
|
||||
{
|
||||
if (!strategy.TenantId.HasValue || strategy.TenantId.Value <= 0)
|
||||
strategy.TenantId = DefaultTenantId;
|
||||
|
||||
var local = Clone(strategy);
|
||||
if (string.IsNullOrWhiteSpace(local.Id))
|
||||
local.Id = $"local-{Guid.NewGuid():N}";
|
||||
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await RemoteAddAsync(local, ct).ConfigureAwait(false);
|
||||
if (ok) { UpsertLocalCache(local); return true; }
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略新增] 远端失败,转离线入队:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
EnqueuePendingOperation(new TareStrategyPendingOperation
|
||||
{
|
||||
OpType = TareStrategyOperationType.Add,
|
||||
TareStrategyId = local.Id,
|
||||
Strategy = local
|
||||
});
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> EditAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct = default)
|
||||
{
|
||||
if (!strategy.TenantId.HasValue || strategy.TenantId.Value <= 0)
|
||||
strategy.TenantId = DefaultTenantId;
|
||||
|
||||
var local = Clone(strategy);
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (ok, _) = await RemoteEditAsync(local, ct).ConfigureAwait(false);
|
||||
if (ok) { UpsertLocalCache(local); return true; }
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略编辑] 远端失败,转离线入队:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
EnqueuePendingOperation(new TareStrategyPendingOperation
|
||||
{
|
||||
OpType = TareStrategyOperationType.Edit,
|
||||
TareStrategyId = local.Id,
|
||||
Strategy = local,
|
||||
AnchorUpdateTime = local.UpdateTime
|
||||
});
|
||||
UpsertLocalCache(local);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await RemoteDeleteAsync(id, ct).ConfigureAwait(false);
|
||||
if (ok) { RemoveFromLocalCache(id); return true; }
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略删除] 远端失败,转离线入队:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? anchor;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
anchor = _localCache.FirstOrDefault(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase))?.UpdateTime;
|
||||
}
|
||||
EnqueuePendingOperation(new TareStrategyPendingOperation
|
||||
{
|
||||
OpType = TareStrategyOperationType.Delete,
|
||||
TareStrategyId = id,
|
||||
AnchorUpdateTime = anchor
|
||||
});
|
||||
RemoveFromLocalCache(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<MesXslUnit>> GetUnitsAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (!_networkMonitor.IsOnline)
|
||||
return new List<MesXslUnit>();
|
||||
|
||||
try
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["pageNo"] = "1";
|
||||
query["pageSize"] = "1000";
|
||||
query["tenantId"] = DefaultTenantId.ToString();
|
||||
var url = $"{BaseUrl}/xslmes/mesXslUnit/anon/list?{query}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return doc.RootElement.GetProperty("result").GetProperty("records")
|
||||
.Deserialize<List<MesXslUnit>>(_jsonOpts) ?? new();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略] 单位列表拉取失败:{ex.Message}");
|
||||
return new List<MesXslUnit>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<MesXslMixerMaterialTareStrategy>> FetchRemoteListAsync(CancellationToken ct)
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["pageNo"] = "1";
|
||||
query["pageSize"] = "10000";
|
||||
query["tenantId"] = DefaultTenantId.ToString();
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/list?{query}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return doc.RootElement.GetProperty("result").GetProperty("records")
|
||||
.Deserialize<List<MesXslMixerMaterialTareStrategy>>(_jsonOpts) ?? new();
|
||||
}
|
||||
|
||||
private async Task<bool> RemoteAddAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/add?tenantId={DefaultTenantId}";
|
||||
var payload = Clone(strategy);
|
||||
if (IsLocalTempId(payload.Id)) payload.Id = null;
|
||||
return await PostJsonAsync(url, payload, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, bool IsVersionConflict)> RemoteEditAsync(MesXslMixerMaterialTareStrategy strategy, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/edit?tenantId={DefaultTenantId}";
|
||||
return await PostJsonCheckVersionAsync(url, strategy, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool> RemoteDeleteAsync(string id, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/delete?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false);
|
||||
return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool> PostJsonAsync(string url, object body, CancellationToken ct)
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json");
|
||||
using var client = CreateClient();
|
||||
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
|
||||
return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, bool IsVersionConflict)> PostJsonCheckVersionAsync(string url, object body, CancellationToken ct)
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json");
|
||||
using var client = CreateClient();
|
||||
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return (false, false);
|
||||
try
|
||||
{
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
int code = 200;
|
||||
if (doc.RootElement.TryGetProperty("code", out var codeEl)) code = codeEl.GetInt32();
|
||||
if (code == 200) return (true, false);
|
||||
if (doc.RootElement.TryGetProperty("message", out var msgEl))
|
||||
{
|
||||
var msg = msgEl.GetString() ?? "";
|
||||
if (msg.Contains("已被他人修改")) return (false, true);
|
||||
}
|
||||
return (false, false);
|
||||
}
|
||||
catch { return (true, false); }
|
||||
}
|
||||
|
||||
private static async Task<bool> IsSuccessResultAsync(HttpResponseMessage resp, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
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 { return true; }
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(() => SyncAfterReconnectAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private async Task SyncAfterReconnectAsync(CancellationToken ct)
|
||||
{
|
||||
var pushResult = await PushPendingOnReconnectAsync(ct).ConfigureAwait(false);
|
||||
if (!_networkMonitor.IsOnline) return;
|
||||
try
|
||||
{
|
||||
var remote = await FetchRemoteListAsync(ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = remote.Select(Clone).ToList();
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
_eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>()
|
||||
.Publish(new MixerMaterialTareStrategyChangedPayload { Action = "pull" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略重连] 全量回拉失败:{ex.Message}");
|
||||
}
|
||||
|
||||
var hasActivity = pushResult.PushedCount > 0 || pushResult.ConflictCount > 0 || pushResult.NewRecordsPushed > 0;
|
||||
if (hasActivity)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Publish(new SyncConflictPayload
|
||||
{
|
||||
EntityName = "密炼物料皮重策略",
|
||||
PushedCount = pushResult.PushedCount,
|
||||
ConflictCount = pushResult.ConflictCount,
|
||||
NewRecordsPushed = pushResult.NewRecordsPushed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<PushPendingResult> PushPendingOnReconnectAsync(CancellationToken ct)
|
||||
{
|
||||
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false))
|
||||
return new PushPendingResult(0, 0, 0);
|
||||
try
|
||||
{
|
||||
List<TareStrategyPendingOperation> snapshot;
|
||||
lock (_cacheLock) { snapshot = _pendingOps.OrderBy(x => x.CreatedAt).ToList(); }
|
||||
|
||||
int pushed = 0, conflicts = 0, newPushed = 0;
|
||||
foreach (var op in snapshot)
|
||||
{
|
||||
if (!_networkMonitor.IsOnline) break;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (!_pendingOps.Any(x => x.Id == op.Id)) continue;
|
||||
}
|
||||
|
||||
var result = await ExecutePendingOpWithConflictAsync(op, ct).ConfigureAwait(false);
|
||||
if (!result.Ok)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
op.RetryCount++;
|
||||
if (op.RetryCount >= MaxPendingRetries)
|
||||
{
|
||||
_pendingOps.RemoveAll(x => x.Id == op.Id);
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
continue;
|
||||
}
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.IsConflict)
|
||||
{
|
||||
conflicts++;
|
||||
if (!string.IsNullOrWhiteSpace(result.EntityId))
|
||||
RemovePendingOpsByTareStrategyId(result.EntityId);
|
||||
}
|
||||
else if (op.OpType == TareStrategyOperationType.Add)
|
||||
{
|
||||
newPushed++;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_pendingOps.RemoveAll(x => x.Id == op.Id);
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pushed++;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_pendingOps.RemoveAll(x => x.Id == op.Id);
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PushPendingResult(pushed, conflicts, newPushed);
|
||||
}
|
||||
finally { _syncLock.Release(); }
|
||||
}
|
||||
|
||||
private sealed record PushPendingResult(int PushedCount, int ConflictCount, int NewRecordsPushed);
|
||||
private sealed record PendingReplayResult(bool Ok, bool IsConflict, string? EntityId);
|
||||
|
||||
private async Task<PendingReplayResult> ExecutePendingOpWithConflictAsync(TareStrategyPendingOperation op, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
return op.OpType switch
|
||||
{
|
||||
TareStrategyOperationType.Add => await ExecuteAddAsync(op, ct).ConfigureAwait(false),
|
||||
TareStrategyOperationType.Edit => await ExecuteEditWithConflictAsync(op, ct).ConfigureAwait(false),
|
||||
TareStrategyOperationType.Delete => await ExecuteDeleteWithConflictAsync(op, ct).ConfigureAwait(false),
|
||||
_ => new PendingReplayResult(true, false, null)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略回放] 执行失败 op={op.OpType}:{ex.Message}");
|
||||
return new PendingReplayResult(false, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<PendingReplayResult> ExecuteAddAsync(TareStrategyPendingOperation op, CancellationToken ct)
|
||||
{
|
||||
var ok = op.Strategy != null && await RemoteAddAsync(op.Strategy, ct).ConfigureAwait(false);
|
||||
return ok
|
||||
? new PendingReplayResult(true, false, op.TareStrategyId)
|
||||
: new PendingReplayResult(false, false, null);
|
||||
}
|
||||
|
||||
private async Task<PendingReplayResult> ExecuteEditWithConflictAsync(TareStrategyPendingOperation op, CancellationToken ct)
|
||||
{
|
||||
var id = op.Strategy?.Id;
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
return new PendingReplayResult(false, false, null);
|
||||
|
||||
var remote = await FetchRemoteSingleAsync(id!, ct).ConfigureAwait(false);
|
||||
if (remote != null && op.AnchorUpdateTime != null && remote.UpdateTime != op.AnchorUpdateTime)
|
||||
{
|
||||
UpsertLocalCache(remote);
|
||||
return new PendingReplayResult(true, true, id);
|
||||
}
|
||||
|
||||
var (ok, isVersionConflict) = await RemoteEditAsync(op.Strategy!, ct).ConfigureAwait(false);
|
||||
if (isVersionConflict)
|
||||
{
|
||||
var fresh = await FetchRemoteSingleAsync(id!, ct).ConfigureAwait(false);
|
||||
if (fresh != null) UpsertLocalCache(fresh);
|
||||
return new PendingReplayResult(true, true, id);
|
||||
}
|
||||
return ok
|
||||
? new PendingReplayResult(true, false, id)
|
||||
: new PendingReplayResult(false, false, null);
|
||||
}
|
||||
|
||||
private async Task<PendingReplayResult> ExecuteDeleteWithConflictAsync(TareStrategyPendingOperation op, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(op.TareStrategyId))
|
||||
return new PendingReplayResult(false, false, null);
|
||||
|
||||
var id = op.TareStrategyId!;
|
||||
var remote = await FetchRemoteSingleAsync(id, ct).ConfigureAwait(false);
|
||||
if (remote == null)
|
||||
return new PendingReplayResult(true, false, id);
|
||||
|
||||
if (op.AnchorUpdateTime != null && remote.UpdateTime != op.AnchorUpdateTime)
|
||||
{
|
||||
UpsertLocalCache(remote);
|
||||
return new PendingReplayResult(true, true, id);
|
||||
}
|
||||
|
||||
var ok = await RemoteDeleteAsync(id, ct).ConfigureAwait(false);
|
||||
return ok
|
||||
? new PendingReplayResult(true, false, id)
|
||||
: new PendingReplayResult(false, false, null);
|
||||
}
|
||||
|
||||
private void RemovePendingOpsByTareStrategyId(string tareStrategyId)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_pendingOps.RemoveAll(x =>
|
||||
(x.TareStrategyId != null && string.Equals(x.TareStrategyId, tareStrategyId, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(x.Strategy?.Id != null && string.Equals(x.Strategy.Id, tareStrategyId, StringComparison.OrdinalIgnoreCase)));
|
||||
SavePendingOpsToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<MesXslMixerMaterialTareStrategy?> FetchRemoteSingleAsync(string id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixerMaterialTareStrategy/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("result", out var resultEl))
|
||||
return resultEl.Deserialize<MesXslMixerMaterialTareStrategy>(_jsonOpts);
|
||||
return null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private static List<MesXslMixerMaterialTareStrategy> ApplyFilters(
|
||||
List<MesXslMixerMaterialTareStrategy> source,
|
||||
string? mixerMaterialName, string? supplierName)
|
||||
{
|
||||
IEnumerable<MesXslMixerMaterialTareStrategy> q = source;
|
||||
if (!string.IsNullOrWhiteSpace(mixerMaterialName))
|
||||
q = q.Where(c => (c.MixerMaterialName ?? "").Contains(mixerMaterialName, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(supplierName))
|
||||
q = q.Where(c => (c.SupplierName ?? "").Contains(supplierName, StringComparison.OrdinalIgnoreCase));
|
||||
return q.OrderByDescending(c => c.EffectiveStartDate ?? DateTime.MinValue).ToList();
|
||||
}
|
||||
|
||||
private List<MesXslMixerMaterialTareStrategy> ApplyPendingOpsSnapshotUnsafe(List<MesXslMixerMaterialTareStrategy> source)
|
||||
{
|
||||
var map = source.Where(c => !string.IsNullOrWhiteSpace(c.Id))
|
||||
.ToDictionary(c => c.Id!, Clone, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var op in _pendingOps.OrderBy(x => x.CreatedAt))
|
||||
{
|
||||
switch (op.OpType)
|
||||
{
|
||||
case TareStrategyOperationType.Add:
|
||||
case TareStrategyOperationType.Edit:
|
||||
if (op.Strategy?.Id != null) map[op.Strategy.Id] = Clone(op.Strategy);
|
||||
break;
|
||||
case TareStrategyOperationType.Delete:
|
||||
if (op.TareStrategyId != null) map.Remove(op.TareStrategyId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return map.Values.ToList();
|
||||
}
|
||||
|
||||
private void EnqueuePendingOperation(TareStrategyPendingOperation op)
|
||||
{
|
||||
lock (_cacheLock) { _pendingOps.Add(op); SavePendingOpsToDiskUnsafe(); }
|
||||
}
|
||||
|
||||
private void UpsertLocalCache(MesXslMixerMaterialTareStrategy strategy)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var idx = _localCache.FindIndex(c => string.Equals(c.Id, strategy.Id, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0) _localCache[idx] = Clone(strategy);
|
||||
else _localCache.Insert(0, Clone(strategy));
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromLocalCache(string id)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache.RemoveAll(c => string.Equals(c.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPendingOpsFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_pendingOpsFilePath)) return;
|
||||
_pendingOps = JsonSerializer.Deserialize<List<TareStrategyPendingOperation>>(
|
||||
File.ReadAllText(_pendingOpsFilePath), _jsonOpts) ?? new();
|
||||
}
|
||||
catch { _pendingOps = new(); }
|
||||
}
|
||||
|
||||
private void LoadCacheFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_cacheFilePath)) return;
|
||||
_localCache = JsonSerializer.Deserialize<List<MesXslMixerMaterialTareStrategy>>(
|
||||
File.ReadAllText(_cacheFilePath), _jsonOpts) ?? new();
|
||||
}
|
||||
catch { _localCache = new(); }
|
||||
}
|
||||
|
||||
private void SavePendingOpsToDiskUnsafe() =>
|
||||
File.WriteAllText(_pendingOpsFilePath, JsonSerializer.Serialize(_pendingOps, _jsonOpts));
|
||||
|
||||
private void SaveCacheToDiskUnsafe() =>
|
||||
File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localCache, _jsonOpts));
|
||||
|
||||
private static bool IsLocalTempId(string? id) =>
|
||||
!string.IsNullOrWhiteSpace(id) && id.StartsWith("local-", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static MesXslMixerMaterialTareStrategy Clone(MesXslMixerMaterialTareStrategy c) => new()
|
||||
{
|
||||
Id = c.Id,
|
||||
TenantId = c.TenantId,
|
||||
MixerMaterialId = c.MixerMaterialId,
|
||||
MixerMaterialName = c.MixerMaterialName,
|
||||
SupplierId = c.SupplierId,
|
||||
SupplierName = c.SupplierName,
|
||||
MaterialSpec = c.MaterialSpec,
|
||||
TareWeight = c.TareWeight,
|
||||
PalletWeight = c.PalletWeight,
|
||||
UnitId = c.UnitId,
|
||||
UnitName = c.UnitName,
|
||||
EffectiveStartDate = c.EffectiveStartDate,
|
||||
EffectiveEndDate = c.EffectiveEndDate,
|
||||
MaintainBy = c.MaintainBy,
|
||||
CreateBy = c.CreateBy,
|
||||
CreateTime = c.CreateTime,
|
||||
UpdateBy = c.UpdateBy,
|
||||
UpdateTime = c.UpdateTime,
|
||||
SysOrgCode = c.SysOrgCode,
|
||||
DelFlag = c.DelFlag
|
||||
};
|
||||
|
||||
private sealed class TareStrategyPendingOperation
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public TareStrategyOperationType OpType { get; set; }
|
||||
public string? TareStrategyId { get; set; }
|
||||
public MesXslMixerMaterialTareStrategy? Strategy { get; set; }
|
||||
public DateTime? AnchorUpdateTime { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
private enum TareStrategyOperationType { Add = 1, Edit = 2, Delete = 3 }
|
||||
|
||||
private sealed class NullableDateTimeJsonConverter : JsonConverter<DateTime?>
|
||||
{
|
||||
private static readonly string[] Formats =
|
||||
[
|
||||
"yyyy-MM-dd", "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)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null) return null;
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var raw = reader.GetString();
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
if (DateTime.TryParseExact(raw, Formats, System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.AssumeLocal, out var dt)) return dt;
|
||||
if (DateTime.TryParse(raw, out var fb)) return fb;
|
||||
}
|
||||
throw new JsonException($"无法转换为 DateTime?,token={reader.TokenType}");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value.HasValue) writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
else writer.WriteNullValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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.MixerMaterialTareStrategy;
|
||||
|
||||
public class MixerMaterialTareStrategySyncCoordinator : ISingletonDependency
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public MixerMaterialTareStrategySyncCoordinator(
|
||||
IEventAggregator eventAggregator,
|
||||
SyncPollManager pollManager,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("密炼物料皮重策略", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>()
|
||||
.Publish(new MixerMaterialTareStrategyChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_logger.Information("[密炼物料皮重策略推送] MixerMaterialTareStrategySyncCoordinator 已启动");
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
if (!payload.IsOnline) return;
|
||||
_logger.Information("[密炼物料皮重策略推送] 网络恢复,触发补偿刷新");
|
||||
_eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>()
|
||||
.Publish(new MixerMaterialTareStrategyChangedPayload { Action = "reconnect" });
|
||||
}
|
||||
|
||||
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("MES_MIXER_MATERIAL_TARE_STRATEGY_CHANGED", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
doc.RootElement.TryGetProperty("action", out var actionEl);
|
||||
doc.RootElement.TryGetProperty("tareStrategyId", out var idEl);
|
||||
|
||||
var changed = new MixerMaterialTareStrategyChangedPayload
|
||||
{
|
||||
Action = actionEl.GetString() ?? string.Empty,
|
||||
TareStrategyId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
|
||||
};
|
||||
_logger.Information($"[密炼物料皮重策略推送] action={changed.Action}, tareStrategyId={changed.TareStrategyId}");
|
||||
_eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>().Publish(changed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼物料皮重策略推送] 处理STOMP命令失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +1003,8 @@ public class RawMaterialCardService : IRawMaterialCardService, ISingletonDepende
|
||||
ManufacturerMaterialName = input.ManufacturerMaterialName,
|
||||
ShelfLife = input.ShelfLife,
|
||||
TotalWeight = input.TotalWeight,
|
||||
PackagingTare = input.PackagingTare,
|
||||
PalletWeight = input.PalletWeight,
|
||||
RemainingWeight = input.RemainingWeight,
|
||||
RemainingQuantity = input.RemainingQuantity,
|
||||
Status = input.Status,
|
||||
|
||||
@@ -894,8 +894,13 @@ public class RawMaterialEntryService : IRawMaterialEntryService, ISingletonDepen
|
||||
WeightRecordId = e.WeightRecordId, BillNo = e.BillNo, MaterialId = e.MaterialId,
|
||||
MaterialName = e.MaterialName, SupplyCustomer = e.SupplyCustomer, SupplierId = e.SupplierId,
|
||||
SupplierName = e.SupplierName, ManufacturerMaterialName = e.ManufacturerMaterialName,
|
||||
ShelfLife = e.ShelfLife, TotalWeight = e.TotalWeight, TotalPortions = e.TotalPortions,
|
||||
PortionWeight = e.PortionWeight, PortionPackages = e.PortionPackages,
|
||||
ShelfLife = e.ShelfLife, TotalWeight = e.TotalWeight, PalletTareTotal = e.PalletTareTotal,
|
||||
TotalPortions = e.TotalPortions,
|
||||
PortionWeight = e.PortionWeight,
|
||||
PortionPackagingTare = e.PortionPackagingTare,
|
||||
PortionPalletWeight = e.PortionPalletWeight,
|
||||
PortionTareStrategyIds = e.PortionTareStrategyIds,
|
||||
PortionPackages = e.PortionPackages,
|
||||
PortionWarehouseLocations = e.PortionWarehouseLocations,
|
||||
PortionDetailIds = e.PortionDetailIds,
|
||||
PortionCardFlags = e.PortionCardFlags,
|
||||
|
||||
@@ -113,18 +113,15 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
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();
|
||||
// 当前页结果按本地入场记录缓存,按 BillNo 实时累计「已入场重量」(与后端口径一致)。
|
||||
// 放在分页之后做:避免对全量 source 做不必要的计算。
|
||||
FillEnteredWeightFromLocalEntries(records);
|
||||
// 当前页结果按本地入场记录缓存,按 BillNo 实时累计「已入场重量」「货物皮重」(与后端口径一致)。
|
||||
FillEntryDerivedFieldsFromLocalEntries(records);
|
||||
return new WeightRecordPageResult(records, total, pageNo, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给一批磅单记录批量填充「已入场重量」。
|
||||
/// 数据来源:本地 RawMaterialEntry 缓存的拆码明细字段(totalPortions / portionWeight)。
|
||||
/// 与后端 sumEnteredWeightByBillNos 同口径,确保离线场景也能正确显示。
|
||||
/// 给一批磅单记录批量填充由原料入场记录衍生的 transient 字段。
|
||||
/// </summary>
|
||||
private void FillEnteredWeightFromLocalEntries(List<MesXslWeightRecord> records)
|
||||
private void FillEntryDerivedFieldsFromLocalEntries(List<MesXslWeightRecord> records)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
{
|
||||
@@ -137,22 +134,38 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
.ToList();
|
||||
if (billNos.Count == 0)
|
||||
{
|
||||
// 全部磅单都没有 BillNo:保留服务端返回值(若有),避免无端置 0。
|
||||
foreach (var r in records)
|
||||
{
|
||||
ApplyRawMaterialWeight(r);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var entries = _rawMaterialEntryService.GetCachedSnapshot();
|
||||
var sumMap = EnteredWeightCalculator.SumByBillNos(entries, billNos!);
|
||||
var enteredMap = EnteredWeightCalculator.SumByBillNos(entries, billNos!);
|
||||
var cargoTareMap = CargoTareWeightCalculator.SumByBillNos(entries, billNos!);
|
||||
foreach (var r in records)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(r.BillNo))
|
||||
{
|
||||
r.EnteredWeight = 0d;
|
||||
r.CargoTareWeight = 0d;
|
||||
ApplyRawMaterialWeight(r);
|
||||
continue;
|
||||
}
|
||||
r.EnteredWeight = sumMap.TryGetValue(r.BillNo, out var v) ? v : 0d;
|
||||
r.EnteredWeight = enteredMap.TryGetValue(r.BillNo, out var entered) ? entered : 0d;
|
||||
r.CargoTareWeight = cargoTareMap.TryGetValue(r.BillNo, out var cargoTare) ? cargoTare : 0d;
|
||||
ApplyRawMaterialWeight(r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>原料重量 = 净重 - 货物皮重(不落库)</summary>
|
||||
private static void ApplyRawMaterialWeight(MesXslWeightRecord record)
|
||||
{
|
||||
record.RawMaterialWeight = record.NetWeight.HasValue
|
||||
? record.NetWeight.Value - (record.CargoTareWeight ?? 0d)
|
||||
: null;
|
||||
}
|
||||
|
||||
public async Task<MesXslWeightRecord?> GetByIdAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
MesXslWeightRecord? record = null;
|
||||
@@ -190,7 +203,7 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
|
||||
if (record != null)
|
||||
{
|
||||
FillEnteredWeightFromLocalEntries(new List<MesXslWeightRecord> { record });
|
||||
FillEntryDerivedFieldsFromLocalEntries(new List<MesXslWeightRecord> { record });
|
||||
}
|
||||
return record;
|
||||
}
|
||||
@@ -725,8 +738,10 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
GrossWeight = input.GrossWeight,
|
||||
TareWeight = input.TareWeight,
|
||||
NetWeight = input.NetWeight,
|
||||
// 「已入场重量」由实时聚合写入,Clone 也要原样传递,避免本地缓存 / Pending 重放后被抹掉
|
||||
// transient 聚合字段:Clone 原样传递,避免本地缓存 / Pending 重放后被抹掉
|
||||
EnteredWeight = input.EnteredWeight,
|
||||
CargoTareWeight = input.CargoTareWeight,
|
||||
RawMaterialWeight = input.RawMaterialWeight,
|
||||
DriverName = input.DriverName,
|
||||
DriverPhone = input.DriverPhone,
|
||||
BillType = input.BillType,
|
||||
|
||||
@@ -162,6 +162,10 @@ public class StompWebSocketService : ISignalRService
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-warehouse-areas", "/topic/sync/mes-warehouse-areas"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
// 密炼物料皮重策略变更:订阅 /topic/sync/mes-mixer-material-tare-strategies
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-mixer-material-tare-strategies", "/topic/sync/mes-mixer-material-tare-strategies"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
// 打印模板变更:订阅 /topic/sync/print-templates
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-print-templates", "/topic/sync/print-templates"),
|
||||
|
||||
@@ -17,6 +17,7 @@ using YY.Admin.Views.RawMaterialCard;
|
||||
using YY.Admin.Views.WarehouseArea;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
using YY.Admin.Views.Print;
|
||||
using YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
@@ -92,6 +93,8 @@ namespace YY.Admin
|
||||
containerRegistry.RegisterForNavigation<RawMaterialCardListView>();
|
||||
// 库区管理
|
||||
containerRegistry.RegisterForNavigation<WarehouseAreaListView>();
|
||||
// 密炼物料皮重策略
|
||||
containerRegistry.RegisterForNavigation<MixerMaterialTareStrategyListView>();
|
||||
// 打印设置
|
||||
containerRegistry.RegisterForNavigation<PrintSettingsView>();
|
||||
// 打印模板列表
|
||||
|
||||
@@ -17,6 +17,7 @@ using YY.Admin.Services.Service;
|
||||
using YY.Admin.Services.Service.Category;
|
||||
using YY.Admin.Services.Service.Customer;
|
||||
using YY.Admin.Services.Service.Dict;
|
||||
using YY.Admin.Services.Service.MixerMaterialTareStrategy;
|
||||
using YY.Admin.Services.Service.MixerMaterial;
|
||||
using YY.Admin.Services.Service.Supplier;
|
||||
using YY.Admin.Services.Service.RawMaterialCard;
|
||||
@@ -69,6 +70,9 @@ public class SyncModule : IModule
|
||||
// 库区管理:免密 API 直连 + STOMP 实时通知
|
||||
containerRegistry.RegisterSingleton<IWarehouseAreaService, WarehouseAreaService>();
|
||||
containerRegistry.RegisterSingleton<WarehouseAreaSyncCoordinator>();
|
||||
// 密炼物料皮重策略:免密 API 直连 + STOMP 实时通知
|
||||
containerRegistry.RegisterSingleton<IMixerMaterialTareStrategyService, MixerMaterialTareStrategyService>();
|
||||
containerRegistry.RegisterSingleton<MixerMaterialTareStrategySyncCoordinator>();
|
||||
// 分类字典:启动同步 + 断线重连补刷
|
||||
containerRegistry.RegisterSingleton<CategorySyncCoordinator>();
|
||||
// 数据字典:启动同步 + 断线重连补刷
|
||||
@@ -143,6 +147,8 @@ public class SyncModule : IModule
|
||||
_ = containerProvider.Resolve<RawMaterialCardSyncCoordinator>();
|
||||
// 强制实例化库区同步协调器
|
||||
_ = containerProvider.Resolve<WarehouseAreaSyncCoordinator>();
|
||||
// 强制实例化密炼物料皮重策略同步协调器
|
||||
_ = containerProvider.Resolve<MixerMaterialTareStrategySyncCoordinator>();
|
||||
// 强制实例化分类字典同步协调器
|
||||
_ = containerProvider.Resolve<CategorySyncCoordinator>();
|
||||
// 强制实例化数据字典同步协调器
|
||||
|
||||
@@ -147,6 +147,11 @@ namespace YY.Admin.ViewModels.Control
|
||||
["/xslmes/mesXslWarehouseArea"] = "WarehouseAreaListView",
|
||||
["mesXslWarehouseArea"] = "WarehouseAreaListView",
|
||||
|
||||
// 已实现页面:密炼物料皮重策略
|
||||
["MixerMaterialTareStrategyListView"] = "MixerMaterialTareStrategyListView",
|
||||
["/xslmes/mesXslMixerMaterialTareStrategy"] = "MixerMaterialTareStrategyListView",
|
||||
["mesXslMixerMaterialTareStrategy"] = "MixerMaterialTareStrategyListView",
|
||||
|
||||
// 已实现页面:打印设置
|
||||
["PrintSettingsView"] = "PrintSettingsView",
|
||||
["/system/printSettings"] = "PrintSettingsView",
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.ViewModels.RawMaterialEntry;
|
||||
using YY.Admin.ViewModels.WeightRecord;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
|
||||
namespace YY.Admin.ViewModels.MixerMaterialTareStrategy;
|
||||
|
||||
public class MixerMaterialTareStrategyEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IMixerMaterialTareStrategyService _tareStrategyService;
|
||||
|
||||
private MesXslMixerMaterialTareStrategy? _strategy;
|
||||
public MesXslMixerMaterialTareStrategy? Strategy
|
||||
{
|
||||
get => _strategy;
|
||||
set => SetProperty(ref _strategy, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Strategy?.Id) || (Strategy?.Id?.StartsWith("local-") ?? false);
|
||||
public string DialogTitle => IsAddMode ? "新增密炼物料皮重策略" : "编辑密炼物料皮重策略";
|
||||
|
||||
public ObservableCollection<MesXslUnit> UnitOptions { get; } = new();
|
||||
|
||||
private MesMixerMaterial? _selectedMaterial;
|
||||
public MesMixerMaterial? SelectedMaterial
|
||||
{
|
||||
get => _selectedMaterial;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedMaterial, value);
|
||||
RaisePropertyChanged(nameof(SelectedMaterialDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedMaterial));
|
||||
if (Strategy != null && value != null)
|
||||
{
|
||||
Strategy.MixerMaterialId = value.Id;
|
||||
Strategy.MixerMaterialName = value.MaterialName;
|
||||
RaisePropertyChanged(nameof(Strategy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedMaterialDisplay => _selectedMaterial != null
|
||||
? $"[{_selectedMaterial.MaterialCode}] {_selectedMaterial.MaterialName}"
|
||||
: (string.IsNullOrWhiteSpace(Strategy?.MixerMaterialName) ? "请选择密炼物料" : Strategy!.MixerMaterialName!);
|
||||
|
||||
public bool HasSelectedMaterial =>
|
||||
_selectedMaterial != null || !string.IsNullOrWhiteSpace(Strategy?.MixerMaterialId);
|
||||
|
||||
private MesXslSupplier? _selectedSupplier;
|
||||
public MesXslSupplier? SelectedSupplier
|
||||
{
|
||||
get => _selectedSupplier;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedSupplier, value);
|
||||
RaisePropertyChanged(nameof(SelectedSupplierDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
if (Strategy != null && value != null)
|
||||
{
|
||||
Strategy.SupplierId = value.Id;
|
||||
Strategy.SupplierName = value.SupplierName;
|
||||
RaisePropertyChanged(nameof(Strategy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedSupplierDisplay => _selectedSupplier != null
|
||||
? $"[{_selectedSupplier.SupplierCode}] {_selectedSupplier.SupplierName}"
|
||||
: (string.IsNullOrWhiteSpace(Strategy?.SupplierName) ? "请选择供应商" : Strategy!.SupplierName!);
|
||||
|
||||
public bool HasSelectedSupplier =>
|
||||
_selectedSupplier != null || !string.IsNullOrWhiteSpace(Strategy?.SupplierId);
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
public DelegateCommand OpenMaterialPickerCommand { get; }
|
||||
public DelegateCommand ClearMaterialCommand { get; }
|
||||
public DelegateCommand OpenSupplierPickerCommand { get; }
|
||||
public DelegateCommand ClearSupplierCommand { get; }
|
||||
|
||||
public MixerMaterialTareStrategyEditDialogViewModel(
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_tareStrategyService = tareStrategyService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
OpenMaterialPickerCommand = new DelegateCommand(async () => await OpenMaterialPickerAsync());
|
||||
ClearMaterialCommand = new DelegateCommand(ClearMaterialSelection);
|
||||
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
|
||||
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
|
||||
_ = LoadUnitsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadUnitsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var units = await _tareStrategyService.GetUnitsAsync();
|
||||
UnitOptions.Clear();
|
||||
foreach (var unit in units.Where(u => !string.IsNullOrWhiteSpace(u.Id)))
|
||||
UnitOptions.Add(unit);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UnitOptions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Strategy = new MesXslMixerMaterialTareStrategy();
|
||||
_selectedMaterial = null;
|
||||
_selectedSupplier = null;
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(SelectedMaterialDisplay));
|
||||
RaisePropertyChanged(nameof(SelectedSupplierDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedMaterial));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslMixerMaterialTareStrategy strategy)
|
||||
{
|
||||
Strategy = new MesXslMixerMaterialTareStrategy
|
||||
{
|
||||
Id = strategy.Id,
|
||||
TenantId = strategy.TenantId,
|
||||
MixerMaterialId = strategy.MixerMaterialId,
|
||||
MixerMaterialName = strategy.MixerMaterialName,
|
||||
SupplierId = strategy.SupplierId,
|
||||
SupplierName = strategy.SupplierName,
|
||||
MaterialSpec = strategy.MaterialSpec,
|
||||
TareWeight = strategy.TareWeight,
|
||||
PalletWeight = strategy.PalletWeight,
|
||||
UnitId = strategy.UnitId,
|
||||
UnitName = strategy.UnitName,
|
||||
EffectiveStartDate = strategy.EffectiveStartDate,
|
||||
EffectiveEndDate = strategy.EffectiveEndDate,
|
||||
MaintainBy = strategy.MaintainBy,
|
||||
UpdateTime = strategy.UpdateTime
|
||||
};
|
||||
_selectedMaterial = null;
|
||||
_selectedSupplier = null;
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(SelectedMaterialDisplay));
|
||||
RaisePropertyChanged(nameof(SelectedSupplierDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedMaterial));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
}
|
||||
|
||||
private async Task OpenMaterialPickerAsync()
|
||||
{
|
||||
RawMaterialEntryMaterialPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await HandyControl.Controls.Dialog.Show<RawMaterialEntryMaterialPickerDialogView>()
|
||||
.Initialize<RawMaterialEntryMaterialPickerDialogViewModel>(vm =>
|
||||
{
|
||||
pickerVm = vm;
|
||||
vm.Initialize(Strategy?.MixerMaterialId, Strategy?.MixerMaterialName);
|
||||
})
|
||||
.GetResultAsync<bool>();
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed || pickerVm?.SelectedMaterial == null) return;
|
||||
SelectedMaterial = pickerVm.SelectedMaterial;
|
||||
}
|
||||
|
||||
private void ClearMaterialSelection()
|
||||
{
|
||||
_selectedMaterial = null;
|
||||
RaisePropertyChanged(nameof(SelectedMaterial));
|
||||
RaisePropertyChanged(nameof(SelectedMaterialDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedMaterial));
|
||||
if (Strategy == null) return;
|
||||
Strategy.MixerMaterialId = null;
|
||||
Strategy.MixerMaterialName = null;
|
||||
RaisePropertyChanged(nameof(Strategy));
|
||||
}
|
||||
|
||||
private async Task OpenSupplierPickerAsync()
|
||||
{
|
||||
SupplierPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await HandyControl.Controls.Dialog.Show<SupplierPickerDialogView>()
|
||||
.Initialize<SupplierPickerDialogViewModel>(vm => pickerVm = vm)
|
||||
.GetResultAsync<bool>();
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed || pickerVm?.SelectedSupplier == null) return;
|
||||
SelectedSupplier = pickerVm.SelectedSupplier;
|
||||
}
|
||||
|
||||
private void ClearSupplierSelection()
|
||||
{
|
||||
_selectedSupplier = null;
|
||||
RaisePropertyChanged(nameof(SelectedSupplier));
|
||||
RaisePropertyChanged(nameof(SelectedSupplierDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
if (Strategy == null) return;
|
||||
Strategy.SupplierId = null;
|
||||
Strategy.SupplierName = null;
|
||||
RaisePropertyChanged(nameof(Strategy));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Strategy == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Strategy.MixerMaterialId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请选择密炼物料!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Strategy.SupplierId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请选择供应商!");
|
||||
return;
|
||||
}
|
||||
if (!Strategy.TareWeight.HasValue)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请填写包装物重量!");
|
||||
return;
|
||||
}
|
||||
if (Strategy.PalletWeight.HasValue && Strategy.PalletWeight.Value < 0)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("托盘重量不能为负数!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Strategy.UnitId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请选择单位!");
|
||||
return;
|
||||
}
|
||||
if (!Strategy.EffectiveStartDate.HasValue || !Strategy.EffectiveEndDate.HasValue)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请填写完整的生效日期!");
|
||||
return;
|
||||
}
|
||||
if (Strategy.EffectiveStartDate.Value.Date > Strategy.EffectiveEndDate.Value.Date)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("生效开始日期不能晚于截止日期!");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedUnit = UnitOptions.FirstOrDefault(u => string.Equals(u.Id, Strategy.UnitId, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedUnit != null)
|
||||
Strategy.UnitName = selectedUnit.UnitName;
|
||||
|
||||
Strategy.MaintainBy = AppSession.CurrentUser?.Account ?? Strategy.MaintainBy;
|
||||
Strategy.MaterialSpec = string.IsNullOrWhiteSpace(Strategy.MaterialSpec)
|
||||
? null
|
||||
: Strategy.MaterialSpec.Trim();
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _tareStrategyService.AddAsync(Strategy);
|
||||
if (ok) HandyControl.Controls.MessageBox.Success("新增成功!");
|
||||
else { HandyControl.Controls.MessageBox.Error("新增失败!"); return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _tareStrategyService.EditAsync(Strategy);
|
||||
if (!ok) { HandyControl.Controls.MessageBox.Error("编辑失败!"); return; }
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
|
||||
namespace YY.Admin.ViewModels.MixerMaterialTareStrategy;
|
||||
|
||||
public class MixerMaterialTareStrategyListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IMixerMaterialTareStrategyService _tareStrategyService;
|
||||
private SubscriptionToken? _changedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
private ObservableCollection<MesXslMixerMaterialTareStrategy> _strategies = new();
|
||||
public ObservableCollection<MesXslMixerMaterialTareStrategy> Strategies
|
||||
{
|
||||
get => _strategies;
|
||||
set => SetProperty(ref _strategies, value);
|
||||
}
|
||||
|
||||
private long _total;
|
||||
public long Total { get => _total; set => SetProperty(ref _total, value); }
|
||||
|
||||
private int _pageNo = 1;
|
||||
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
|
||||
|
||||
private int _pageSize = 20;
|
||||
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
|
||||
|
||||
private string? _filterMixerMaterialName;
|
||||
public string? FilterMixerMaterialName
|
||||
{
|
||||
get => _filterMixerMaterialName;
|
||||
set => SetProperty(ref _filterMixerMaterialName, value);
|
||||
}
|
||||
|
||||
private string? _filterSupplierName;
|
||||
public string? FilterSupplierName
|
||||
{
|
||||
get => _filterSupplierName;
|
||||
set => SetProperty(ref _filterSupplierName, value);
|
||||
}
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslMixerMaterialTareStrategy> EditCommand { get; }
|
||||
public DelegateCommand<MesXslMixerMaterialTareStrategy> DeleteCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public MixerMaterialTareStrategyListViewModel(
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_tareStrategyService = tareStrategyService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterMixerMaterialName = null;
|
||||
FilterSupplierName = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslMixerMaterialTareStrategy>(async s => await ShowEditDialogAsync(s));
|
||||
DeleteCommand = new DelegateCommand<MesXslMixerMaterialTareStrategy>(async s => await DeleteAsync(s));
|
||||
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
|
||||
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
|
||||
|
||||
_changedToken = _eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>()
|
||||
.Subscribe(async p => await OnChangedAsync(p), ThreadOption.UIThread);
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task OnChangedAsync(MixerMaterialTareStrategyChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.TareStrategyId))
|
||||
await RefreshSingleAsync(payload.TareStrategyId!);
|
||||
else
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _tareStrategyService.GetByIdAsync(id);
|
||||
if (updated == null) return;
|
||||
var idx = Strategies.ToList().FindIndex(s => string.Equals(s.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0) Strategies[idx] = updated;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[密炼物料皮重策略] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "密炼物料皮重策略", StringComparison.OrdinalIgnoreCase)) return;
|
||||
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0)
|
||||
parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0)
|
||||
parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0)
|
||||
parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
|
||||
if (parts.Count == 0) return;
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0) Growl.Warning(message);
|
||||
else Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"密炼物料皮重策略列表初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _tareStrategyService.PageAsync(
|
||||
PageNo, PageSize, FilterMixerMaterialName, FilterSupplierName);
|
||||
Strategies = new ObservableCollection<MesXslMixerMaterialTareStrategy>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载密炼物料皮重策略失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<MixerMaterialTareStrategyEditDialogView>()
|
||||
.Initialize<MixerMaterialTareStrategyEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开新增对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslMixerMaterialTareStrategy strategy)
|
||||
{
|
||||
if (strategy == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<MixerMaterialTareStrategyEditDialogView>()
|
||||
.Initialize<MixerMaterialTareStrategyEditDialogViewModel>(vm => vm.InitializeForEdit(strategy))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开编辑对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslMixerMaterialTareStrategy strategy)
|
||||
{
|
||||
if (strategy?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
$"确定删除【{strategy.MixerMaterialName} / {strategy.SupplierName}】的包装物重量策略?此操作不可恢复!",
|
||||
"确认删除", MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _tareStrategyService.DeleteAsync(strategy.Id);
|
||||
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
|
||||
else Growl.Error("删除失败!");
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_changedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<MixerMaterialTareStrategyChangedEvent>().Unsubscribe(_changedToken);
|
||||
_changedToken = null;
|
||||
}
|
||||
if (_syncConflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
|
||||
_syncConflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,8 @@ public class RawMaterialCardEditDialogViewModel : BaseViewModel, IDialogResultab
|
||||
ManufacturerMaterialName = card.ManufacturerMaterialName,
|
||||
ShelfLife = card.ShelfLife,
|
||||
TotalWeight = card.TotalWeight,
|
||||
PackagingTare = card.PackagingTare,
|
||||
PalletWeight = card.PalletWeight,
|
||||
RemainingWeight = card.RemainingWeight,
|
||||
RemainingQuantity = card.RemainingQuantity,
|
||||
Status = card.Status,
|
||||
|
||||
@@ -9,6 +9,7 @@ using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Services.Service.MixerMaterialTareStrategy;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
using YY.Admin.ViewModels.WeightRecord;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
@@ -22,8 +23,9 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
protected IRawMaterialEntryService EntryService => _entryService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IMixerMaterialService _mixerMaterialService;
|
||||
|
||||
// 加载完物料后用于回填 Edit 模式选中项
|
||||
private readonly IMixerMaterialTareStrategyService _tareStrategyService;
|
||||
private readonly ISupplierService _supplierService;
|
||||
private bool _suppressTareStrategyRefresh;
|
||||
protected string? _pendingMaterialId;
|
||||
|
||||
private MesXslRawMaterialEntry? _entry;
|
||||
@@ -49,6 +51,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(SelectedMaterialDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedMaterial));
|
||||
|
||||
_ = RefreshAllRowsTareStrategyAsync();
|
||||
|
||||
// 新增模式自动生成条码/批次号
|
||||
if (IsAddMode && !string.IsNullOrEmpty(value.MaterialCode))
|
||||
_ = AutoGenerateBarcodeAsync(value.MaterialCode);
|
||||
@@ -106,6 +110,27 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? EntryTimeInput
|
||||
{
|
||||
get => Entry?.EntryTime;
|
||||
set
|
||||
{
|
||||
if (Entry == null || Entry.EntryTime == value) return;
|
||||
Entry.EntryTime = value;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
_ = RefreshAllRowsTareStrategyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>基础资料「托盘及皮重(合计)」= Σ 份数 × (包装物皮重 + 托盘重量)。</summary>
|
||||
public double PalletTareTotalDisplay => SplitCodeDetails.Sum(row =>
|
||||
{
|
||||
var portions = row.Portions ?? 0;
|
||||
var packaging = row.PackagingTare ?? 0d;
|
||||
var pallet = row.PalletWeight ?? 0d;
|
||||
return portions * (packaging + pallet);
|
||||
});
|
||||
|
||||
public ObservableCollection<MesMixerMaterial> MaterialOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestResultOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestStatusOptions { get; } = new();
|
||||
@@ -174,21 +199,27 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
public DelegateCommand ClearWeightRecordCommand { get; }
|
||||
public DelegateCommand OpenSupplierPickerCommand { get; }
|
||||
public DelegateCommand ClearSupplierCommand { get; }
|
||||
/// <summary>
|
||||
/// 拆码明细 - 库位选择命令(弹出「库区选择」弹窗,单选)。CommandParameter 为当前行 RawMaterialSplitDetailItem。
|
||||
/// </summary>
|
||||
/// <summary>拆码明细 - 库位选择命令(弹出「库区选择」弹窗,单选)。CommandParameter 为当前行 RawMaterialSplitDetailItem。</summary>
|
||||
public DelegateCommand<RawMaterialSplitDetailItem> OpenWarehouseAreaPickerCommand { get; }
|
||||
/// <summary>拆码明细 - 手动选择皮重策略。</summary>
|
||||
public DelegateCommand<RawMaterialSplitDetailItem> OpenTareStrategyPickerCommand { get; }
|
||||
/// <summary>拆码明细 - 清除手动策略并按规则重新自动匹配。</summary>
|
||||
public DelegateCommand<RawMaterialSplitDetailItem> ResetTareStrategyCommand { get; }
|
||||
|
||||
public RawMaterialEntryEditDialogViewModel(
|
||||
IRawMaterialEntryService entryService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IMixerMaterialService mixerMaterialService,
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
ISupplierService supplierService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_entryService = entryService;
|
||||
_dictSyncService = dictSyncService;
|
||||
_mixerMaterialService = mixerMaterialService;
|
||||
_tareStrategyService = tareStrategyService;
|
||||
_supplierService = supplierService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
ResetCommand = new DelegateCommand(InitializeForAdd);
|
||||
@@ -201,6 +232,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
|
||||
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
|
||||
OpenWarehouseAreaPickerCommand = new DelegateCommand<RawMaterialSplitDetailItem>(async row => await OpenWarehouseAreaPickerAsync(row));
|
||||
OpenTareStrategyPickerCommand = new DelegateCommand<RawMaterialSplitDetailItem>(async row => await OpenTareStrategyPickerAsync(row));
|
||||
ResetTareStrategyCommand = new DelegateCommand<RawMaterialSplitDetailItem>(async row => await ResetTareStrategyAsync(row));
|
||||
SplitCodeDetails.CollectionChanged += OnSplitCodeDetailsCollectionChanged;
|
||||
_ = LoadAllAsync();
|
||||
}
|
||||
@@ -349,6 +382,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(EntryTimeInput));
|
||||
RaisePropertyChanged(nameof(PalletTareTotalDisplay));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
@@ -367,8 +402,13 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
MaterialId = entry.MaterialId, MaterialCode = entry.MaterialCode, MaterialName = entry.MaterialName,
|
||||
SupplyCustomer = entry.SupplyCustomer, SupplierId = entry.SupplierId, SupplierName = entry.SupplierName,
|
||||
ManufacturerMaterialName = entry.ManufacturerMaterialName,
|
||||
ShelfLife = entry.ShelfLife, TotalWeight = entry.TotalWeight, TotalPortions = entry.TotalPortions,
|
||||
PortionWeight = entry.PortionWeight, PortionPackages = entry.PortionPackages,
|
||||
ShelfLife = entry.ShelfLife, TotalWeight = entry.TotalWeight, PalletTareTotal = entry.PalletTareTotal,
|
||||
TotalPortions = entry.TotalPortions,
|
||||
PortionWeight = entry.PortionWeight,
|
||||
PortionPackagingTare = entry.PortionPackagingTare,
|
||||
PortionPalletWeight = entry.PortionPalletWeight,
|
||||
PortionTareStrategyIds = entry.PortionTareStrategyIds,
|
||||
PortionPackages = entry.PortionPackages,
|
||||
PortionWarehouseLocations = entry.PortionWarehouseLocations,
|
||||
PortionDetailIds = entry.PortionDetailIds,
|
||||
PortionCardFlags = entry.PortionCardFlags,
|
||||
@@ -398,6 +438,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(EntryTimeInput));
|
||||
RaisePropertyChanged(nameof(PalletTareTotalDisplay));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
@@ -503,6 +545,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.BillNo = selected.BillNo;
|
||||
Entry.SupplierName = selected.SenderUnit;
|
||||
Entry.SupplierId = null;
|
||||
await ApplySupplierFromDisplayNameAsync(selected.SenderUnit);
|
||||
// 选择榜单后,自动把「剩余可入场量 = 净重 - 已入场重量」带入「总重」,
|
||||
// 用户仍可手动编辑;若净重为空则保留原值,避免误清空。
|
||||
if (selected.NetWeight.HasValue)
|
||||
@@ -514,6 +557,76 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
}
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(EntryTimeInput));
|
||||
RaisePropertyChanged(nameof(PalletTareTotalDisplay));
|
||||
await RefreshAllRowsTareStrategyAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按供应商全称/简称反查 ID(榜单「发货单位」与手动填写的供应商名称均可用)。
|
||||
/// </summary>
|
||||
private async Task ApplySupplierFromDisplayNameAsync(string? displayName)
|
||||
{
|
||||
if (Entry == null || string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var supplier = await FindSupplierByDisplayNameAsync(displayName);
|
||||
if (supplier == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Entry.SupplierId = supplier.Id;
|
||||
Entry.SupplierName = supplier.SupplierName
|
||||
?? supplier.SupplierShortName
|
||||
?? displayName.Trim();
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private async Task<MesXslSupplier?> FindSupplierByDisplayNameAsync(string? displayName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = displayName.Trim();
|
||||
try
|
||||
{
|
||||
var page = await _supplierService.PageAsync(1, 5000);
|
||||
var exact = page.Records.FirstOrDefault(s =>
|
||||
string.Equals(s.SupplierName?.Trim(), name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(s.SupplierShortName?.Trim(), name, StringComparison.OrdinalIgnoreCase));
|
||||
if (exact != null)
|
||||
{
|
||||
return exact;
|
||||
}
|
||||
|
||||
return page.Records.FirstOrDefault(s =>
|
||||
(s.SupplierName ?? "").Contains(name, StringComparison.OrdinalIgnoreCase)
|
||||
|| (s.SupplierShortName ?? "").Contains(name, StringComparison.OrdinalIgnoreCase)
|
||||
|| name.Contains(s.SupplierShortName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
|| name.Contains(s.SupplierName ?? "", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 皮重策略匹配前确保 SupplierId 已解析(榜单仅带出名称时补全 ID)。
|
||||
/// </summary>
|
||||
private async Task EnsureSupplierIdForTareMatchAsync()
|
||||
{
|
||||
if (Entry == null || !string.IsNullOrWhiteSpace(Entry.SupplierId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApplySupplierFromDisplayNameAsync(Entry.SupplierName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -617,6 +730,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.SupplierId = null;
|
||||
Entry.SupplierName = null;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
_ = RefreshAllRowsTareStrategyAsync();
|
||||
}
|
||||
|
||||
private async Task OpenSupplierPickerAsync()
|
||||
@@ -638,6 +752,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.SupplierId = selected.Id;
|
||||
Entry.SupplierName = selected.SupplierName;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
await RefreshAllRowsTareStrategyAsync();
|
||||
}
|
||||
|
||||
private void ClearSupplierSelection()
|
||||
@@ -646,8 +761,128 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.SupplierId = null;
|
||||
Entry.SupplierName = null;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
_ = RefreshAllRowsTareStrategyAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshAllRowsTareStrategyAsync()
|
||||
{
|
||||
if (_suppressTareStrategyRefresh) return;
|
||||
await EnsureSupplierIdForTareMatchAsync();
|
||||
foreach (var row in SplitCodeDetails.ToList())
|
||||
{
|
||||
if (!row.IsManualTareStrategy)
|
||||
await ApplyAutoTareStrategyToRowAsync(row);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyAutoTareStrategyToRowAsync(RawMaterialSplitDetailItem row)
|
||||
{
|
||||
if (_suppressTareStrategyRefresh || row.IsManualTareStrategy || row.HasCard) return;
|
||||
|
||||
try
|
||||
{
|
||||
var strategies = await _tareStrategyService.GetAllForMatchAsync();
|
||||
var match = MixerMaterialTareStrategyMatcher.PickBestMatch(
|
||||
strategies,
|
||||
Entry?.MaterialId,
|
||||
Entry?.SupplierId,
|
||||
Entry?.EntryTime,
|
||||
row.PortionWeight);
|
||||
ApplyTareStrategyToRow(row, match, manual: false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ApplyTareStrategyToRow(row, null, manual: false);
|
||||
}
|
||||
|
||||
RaisePalletTareTotalChanged();
|
||||
}
|
||||
|
||||
private void ApplyTareStrategyToRow(
|
||||
RawMaterialSplitDetailItem row,
|
||||
MesXslMixerMaterialTareStrategy? strategy,
|
||||
bool manual)
|
||||
{
|
||||
if (strategy == null)
|
||||
{
|
||||
row.PackagingTare = 0d;
|
||||
row.PalletWeight = 0d;
|
||||
row.TareStrategyId = null;
|
||||
row.TareStrategyDisplay = "未匹配(0)";
|
||||
}
|
||||
else
|
||||
{
|
||||
row.PackagingTare = strategy.TareWeight.HasValue ? (double)strategy.TareWeight.Value : 0d;
|
||||
row.PalletWeight = strategy.PalletWeight.HasValue ? (double)strategy.PalletWeight.Value : 0d;
|
||||
row.TareStrategyId = strategy.Id;
|
||||
row.TareStrategyDisplay = BuildTareStrategyDisplay(strategy);
|
||||
}
|
||||
|
||||
row.IsManualTareStrategy = manual && strategy != null;
|
||||
}
|
||||
|
||||
private static string BuildTareStrategyDisplay(MesXslMixerMaterialTareStrategy strategy)
|
||||
{
|
||||
var spec = string.IsNullOrWhiteSpace(strategy.MaterialSpec) ? "-" : strategy.MaterialSpec;
|
||||
var pkg = strategy.TareWeight?.ToString("0.##") ?? "0";
|
||||
var pallet = strategy.PalletWeight?.ToString("0.##") ?? "0";
|
||||
return $"规格:{spec} 包装:{pkg} 托盘:{pallet}";
|
||||
}
|
||||
|
||||
private async Task OpenTareStrategyPickerAsync(RawMaterialSplitDetailItem? row)
|
||||
{
|
||||
if (row == null || row.HasCard) return;
|
||||
if (string.IsNullOrWhiteSpace(Entry?.MaterialId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请先选择密炼物料!");
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureSupplierIdForTareMatchAsync();
|
||||
if (string.IsNullOrWhiteSpace(Entry?.SupplierId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("未能匹配到供应商档案,请手动选择供应商,或检查榜单发货单位是否与供应商简称/全称一致!");
|
||||
return;
|
||||
}
|
||||
|
||||
TareStrategyPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await SuspendEmbeddedPrintPreviewAirspaceWhileAsync(async () =>
|
||||
{
|
||||
return await HandyControl.Controls.Dialog.Show<TareStrategyPickerDialogView>()
|
||||
.Initialize<TareStrategyPickerDialogViewModel>(vm =>
|
||||
{
|
||||
pickerVm = vm;
|
||||
vm.Initialize(Entry!.MaterialId, Entry.SupplierId, Entry.EntryTime, row.PortionWeight, row.TareStrategyId);
|
||||
})
|
||||
.GetResultAsync<bool>();
|
||||
});
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed) return;
|
||||
if (pickerVm?.SelectedStrategy == null)
|
||||
{
|
||||
row.IsManualTareStrategy = false;
|
||||
await ApplyAutoTareStrategyToRowAsync(row);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyTareStrategyToRow(row, pickerVm.SelectedStrategy, manual: true);
|
||||
RaisePalletTareTotalChanged();
|
||||
}
|
||||
|
||||
private async Task ResetTareStrategyAsync(RawMaterialSplitDetailItem? row)
|
||||
{
|
||||
if (row == null || row.HasCard) return;
|
||||
row.IsManualTareStrategy = false;
|
||||
await ApplyAutoTareStrategyToRowAsync(row);
|
||||
}
|
||||
|
||||
private void RaisePalletTareTotalChanged() => RaisePropertyChanged(nameof(PalletTareTotalDisplay));
|
||||
|
||||
private void RecalculateShelfLife(MesMixerMaterial? material)
|
||||
{
|
||||
if (Entry == null || material?.ShelfLifeDays == null || material.ShelfLifeDays <= 0)
|
||||
@@ -719,63 +954,68 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
|
||||
protected void InitializeSplitCodeDetailsFromEntry()
|
||||
{
|
||||
SplitCodeDetails.Clear();
|
||||
|
||||
// 六个字段都是按拆码明细多行拼接的字符串(末尾带 /),解析回明细列表
|
||||
var portionsArr = SplitJoinedValues(Entry?.TotalPortions);
|
||||
var weightArr = SplitJoinedValues(Entry?.PortionWeight);
|
||||
var packagesArr = SplitJoinedValues(Entry?.PortionPackages);
|
||||
var locationsArr = SplitJoinedValues(Entry?.PortionWarehouseLocations);
|
||||
var idsArr = SplitJoinedValues(Entry?.PortionDetailIds);
|
||||
var cardFlagsArr = SplitJoinedValues(Entry?.PortionCardFlags);
|
||||
// 历史记录可能没存 PortionCardFlags:用 print_flag 推断(与原行为兼容)
|
||||
var fallbackToPrintFlag = cardFlagsArr.Length == 0
|
||||
&& string.Equals(Entry?.PrintFlag, "1", StringComparison.Ordinal);
|
||||
var rowCount = Math.Max(1, Math.Max(Math.Max(portionsArr.Length, weightArr.Length),
|
||||
Math.Max(Math.Max(packagesArr.Length, locationsArr.Length), idsArr.Length)));
|
||||
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
_suppressTareStrategyRefresh = true;
|
||||
try
|
||||
{
|
||||
var locationFromArr = GetAt(locationsArr, i);
|
||||
// 兼容历史数据:明细库位拼接字段为空时,首行回退到 Entry.WarehouseLocation
|
||||
// (早期版本里整票级库位曾被反写过,避免老记录打开后明细全空)
|
||||
var locationFallback = (i == 0 && string.IsNullOrWhiteSpace(locationFromArr))
|
||||
? Entry?.WarehouseLocation
|
||||
: locationFromArr;
|
||||
SplitCodeDetails.Clear();
|
||||
|
||||
var item = new RawMaterialSplitDetailItem
|
||||
var portionsArr = SplitJoinedValues(Entry?.TotalPortions);
|
||||
var weightArr = SplitJoinedValues(Entry?.PortionWeight);
|
||||
var packagingTareArr = SplitJoinedValues(Entry?.PortionPackagingTare);
|
||||
var palletWeightArr = SplitJoinedValues(Entry?.PortionPalletWeight);
|
||||
var strategyIdsArr = SplitJoinedValues(Entry?.PortionTareStrategyIds);
|
||||
var packagesArr = SplitJoinedValues(Entry?.PortionPackages);
|
||||
var locationsArr = SplitJoinedValues(Entry?.PortionWarehouseLocations);
|
||||
var idsArr = SplitJoinedValues(Entry?.PortionDetailIds);
|
||||
var cardFlagsArr = SplitJoinedValues(Entry?.PortionCardFlags);
|
||||
var fallbackToPrintFlag = cardFlagsArr.Length == 0
|
||||
&& string.Equals(Entry?.PrintFlag, "1", StringComparison.Ordinal);
|
||||
var rowCount = Math.Max(1, Math.Max(Math.Max(portionsArr.Length, weightArr.Length),
|
||||
Math.Max(Math.Max(packagesArr.Length, locationsArr.Length), idsArr.Length)));
|
||||
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
Portions = TryParseInt(GetAt(portionsArr, i)),
|
||||
PortionWeight = TryParseDouble(GetAt(weightArr, i)),
|
||||
PortionPackages = TryParseInt(GetAt(packagesArr, i)),
|
||||
WarehouseLocation = locationFallback,
|
||||
};
|
||||
// 仅当后端已持久化了该行 ID 时才覆盖默认值,确保跨次保持稳定;
|
||||
// 历史记录无 ID 字段时使用构造期生成的新 GUID(之后保存会回填)。
|
||||
var existingId = GetAt(idsArr, i);
|
||||
if (!string.IsNullOrWhiteSpace(existingId))
|
||||
{
|
||||
item.Id = existingId;
|
||||
var locationFromArr = GetAt(locationsArr, i);
|
||||
var locationFallback = (i == 0 && string.IsNullOrWhiteSpace(locationFromArr))
|
||||
? Entry?.WarehouseLocation
|
||||
: locationFromArr;
|
||||
|
||||
var strategyId = GetAt(strategyIdsArr, i);
|
||||
var item = new RawMaterialSplitDetailItem
|
||||
{
|
||||
Portions = TryParseInt(GetAt(portionsArr, i)),
|
||||
PortionWeight = TryParseDouble(GetAt(weightArr, i)),
|
||||
PortionPackages = TryParseInt(GetAt(packagesArr, i)),
|
||||
WarehouseLocation = locationFallback,
|
||||
PackagingTare = TryParseDouble(GetAt(packagingTareArr, i)) ?? 0d,
|
||||
PalletWeight = TryParseDouble(GetAt(palletWeightArr, i)) ?? 0d,
|
||||
TareStrategyId = strategyId,
|
||||
IsManualTareStrategy = !string.IsNullOrWhiteSpace(strategyId),
|
||||
};
|
||||
item.TareStrategyDisplay = string.IsNullOrWhiteSpace(strategyId)
|
||||
? "未匹配(0)"
|
||||
: $"策略ID:{strategyId}";
|
||||
|
||||
var existingId = GetAt(idsArr, i);
|
||||
if (!string.IsNullOrWhiteSpace(existingId))
|
||||
item.Id = existingId;
|
||||
|
||||
var flagAt = GetAt(cardFlagsArr, i);
|
||||
if (!string.IsNullOrWhiteSpace(flagAt))
|
||||
item.HasCard = string.Equals(flagAt, "1", StringComparison.Ordinal);
|
||||
else if (fallbackToPrintFlag && !string.IsNullOrWhiteSpace(existingId))
|
||||
item.HasCard = true;
|
||||
|
||||
SplitCodeDetails.Add(item);
|
||||
}
|
||||
// HasCard 行级解析(优先级 1):从 PortionCardFlags 按位读取,"1"=已生成。
|
||||
// 这是「保存后新增未生成行不被误判为已打印」的关键 — 新增行保存时 HasCard=false 持久化为 "0",
|
||||
// 重新加载时回填 false,「生成原材料卡片」就能正确把它列入待生成清单。
|
||||
var flagAt = GetAt(cardFlagsArr, i);
|
||||
if (!string.IsNullOrWhiteSpace(flagAt))
|
||||
{
|
||||
item.HasCard = string.Equals(flagAt, "1", StringComparison.Ordinal);
|
||||
}
|
||||
else if (fallbackToPrintFlag && !string.IsNullOrWhiteSpace(existingId))
|
||||
{
|
||||
// 优先级 2(兼容历史记录):未存 PortionCardFlags 时,沿用旧逻辑——
|
||||
// 持久化 ID 存在 + 整票已打印 ⇒ 视为已生成卡片。
|
||||
item.HasCard = true;
|
||||
}
|
||||
// 否则保持构造默认值 false(新增态、未打印整票等场景)
|
||||
SplitCodeDetails.Add(item);
|
||||
|
||||
RaisePropertyChanged(nameof(SplitCodeTableHeight));
|
||||
RaisePalletTareTotalChanged();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressTareStrategyRefresh = false;
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(SplitCodeTableHeight));
|
||||
}
|
||||
|
||||
private static string[] SplitJoinedValues(string? value)
|
||||
@@ -798,8 +1038,10 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
|
||||
private void AddSplitDetailRow()
|
||||
{
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem());
|
||||
var row = new RawMaterialSplitDetailItem();
|
||||
SplitCodeDetails.Add(row);
|
||||
RaisePropertyChanged(nameof(SplitCodeTableHeight));
|
||||
_ = ApplyAutoTareStrategyToRowAsync(row);
|
||||
}
|
||||
|
||||
private void RemoveSplitDetailRow(RawMaterialSplitDetailItem? item)
|
||||
@@ -841,32 +1083,52 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
|
||||
private void OnSplitDetailItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// 用户填写或修改某行的「份数」时,按公式自动算出该行「每份重量」:
|
||||
// 每份重量 = (总重 - Σ其他行的份数×每份重量) / 当前行份数
|
||||
// 用户修改某行的「份数」时:仅当该行「每份重量」为空才按公式自动计算
|
||||
if (e.PropertyName == nameof(RawMaterialSplitDetailItem.Portions)
|
||||
&& sender is RawMaterialSplitDetailItem row)
|
||||
{
|
||||
RecalculatePortionWeightForRow(row);
|
||||
}
|
||||
|
||||
if (e.PropertyName == nameof(RawMaterialSplitDetailItem.PortionWeight)
|
||||
&& sender is RawMaterialSplitDetailItem weightRow)
|
||||
{
|
||||
// 清空每份重量后,若份数已填则立即按公式重算(配合 PortionWeightText 空值绑定)
|
||||
if (!weightRow.PortionWeight.HasValue)
|
||||
{
|
||||
RecalculatePortionWeightForRow(weightRow);
|
||||
}
|
||||
|
||||
if (!weightRow.IsManualTareStrategy && !weightRow.HasCard)
|
||||
{
|
||||
_ = ApplyAutoTareStrategyToRowAsync(weightRow);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.PropertyName is nameof(RawMaterialSplitDetailItem.Portions)
|
||||
or nameof(RawMaterialSplitDetailItem.PortionWeight)
|
||||
or nameof(RawMaterialSplitDetailItem.PortionPackages))
|
||||
or nameof(RawMaterialSplitDetailItem.PortionPackages)
|
||||
or nameof(RawMaterialSplitDetailItem.PackagingTare)
|
||||
or nameof(RawMaterialSplitDetailItem.PalletWeight))
|
||||
{
|
||||
RaiseSplitDisplayPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拆码明细某行的「份数」变化时,按公式重算该行「每份重量」:
|
||||
/// 拆码明细某行的「份数」变化时,仅当该行「每份重量」为空时按公式重算:
|
||||
/// 公式:每份重量 = (总重 - 其他行 Σ份数×每份重量) / 当前行份数
|
||||
/// — 用户单独手改「每份重量」不触发;
|
||||
/// — 已填写「每份重量」后再改份数,不覆盖用户输入;
|
||||
/// — 总重为空 / ≤0、当前份数 ≤0、剩余总重 ≤0 时跳过;
|
||||
/// — 结果四舍五入到两位小数;
|
||||
/// — 写入 row.PortionWeight 会触发 PropertyChanged(PortionWeight),不会再次进入本方法(PropertyName 已不是 Portions),不会形成循环。
|
||||
/// — 结果四舍五入到两位小数。
|
||||
/// </summary>
|
||||
private void RecalculatePortionWeightForRow(RawMaterialSplitDetailItem row)
|
||||
{
|
||||
if (row.PortionWeight.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Entry?.TotalWeight is not { } total || total <= 0d)
|
||||
{
|
||||
return;
|
||||
@@ -898,6 +1160,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionPackagesDisplay));
|
||||
RaisePalletTareTotalChanged();
|
||||
}
|
||||
|
||||
private string JoinSplitValue(Func<RawMaterialSplitDetailItem, string?> selector, bool withTrailingSlash)
|
||||
@@ -938,6 +1201,11 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
|
||||
Entry.TotalPortions = JoinSplitValue(it => it.Portions?.ToString(CultureInfo.InvariantCulture), true);
|
||||
Entry.PortionWeight = JoinSplitValue(it => FormatNullableDecimal(it.PortionWeight), true);
|
||||
Entry.PortionPackagingTare = JoinSplitValue(it => FormatNullableDecimal(it.PackagingTare), true);
|
||||
Entry.PortionPalletWeight = JoinSplitValue(it => FormatNullableDecimal(it.PalletWeight), true);
|
||||
Entry.PortionTareStrategyIds = JoinSplitValue(
|
||||
it => it.IsManualTareStrategy && !string.IsNullOrWhiteSpace(it.TareStrategyId) ? it.TareStrategyId : null,
|
||||
true);
|
||||
Entry.PortionPackages = JoinSplitValue(it => it.PortionPackages?.ToString(CultureInfo.InvariantCulture), true);
|
||||
Entry.PortionWarehouseLocations = JoinSplitValue(it => string.IsNullOrWhiteSpace(it.WarehouseLocation) ? null : it.WarehouseLocation.Trim(), true);
|
||||
// 持久化每行 GUID,行序与上方四个字段对齐;JoinSplitValue 会过滤空,但 Id 在构造时即生成不会为空。
|
||||
@@ -947,6 +1215,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
// 「生成原材料卡片」只对 HasCard=false 的行加卡 → 必须把真实的 HasCard 持久化,
|
||||
// 否则重新加载后无法区分「已生成」与「保存后新增的未生成行」。
|
||||
Entry.PortionCardFlags = JoinSplitValue(it => it.HasCard ? "1" : "0", true);
|
||||
Entry.PalletTareTotal = PalletTareTotalDisplay;
|
||||
}
|
||||
|
||||
private double CalculateSplitCodeTableHeight()
|
||||
@@ -983,7 +1252,37 @@ public class RawMaterialSplitDetailItem : BindableBase
|
||||
public double? PortionWeight
|
||||
{
|
||||
get => _portionWeight;
|
||||
set => SetProperty(ref _portionWeight, value);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _portionWeight, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(PortionWeightText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每份重量文本(供 TextBox 绑定;空字符串表示未填写,避免 double? 直接绑定无法清空)。
|
||||
/// </summary>
|
||||
public string PortionWeightText
|
||||
{
|
||||
get => _portionWeight.HasValue
|
||||
? _portionWeight.Value.ToString("0.##", CultureInfo.InvariantCulture)
|
||||
: string.Empty;
|
||||
set
|
||||
{
|
||||
var text = value?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
PortionWeight = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
|
||||
{
|
||||
PortionWeight = parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int? _portionPackages;
|
||||
@@ -1000,6 +1299,42 @@ public class RawMaterialSplitDetailItem : BindableBase
|
||||
set => SetProperty(ref _warehouseLocation, value);
|
||||
}
|
||||
|
||||
private double? _packagingTare;
|
||||
public double? PackagingTare
|
||||
{
|
||||
get => _packagingTare;
|
||||
set => SetProperty(ref _packagingTare, value);
|
||||
}
|
||||
|
||||
private double? _palletWeight;
|
||||
public double? PalletWeight
|
||||
{
|
||||
get => _palletWeight;
|
||||
set => SetProperty(ref _palletWeight, value);
|
||||
}
|
||||
|
||||
private string? _tareStrategyId;
|
||||
public string? TareStrategyId
|
||||
{
|
||||
get => _tareStrategyId;
|
||||
set => SetProperty(ref _tareStrategyId, value);
|
||||
}
|
||||
|
||||
private string? _tareStrategyDisplay = "未匹配(0)";
|
||||
public string? TareStrategyDisplay
|
||||
{
|
||||
get => _tareStrategyDisplay;
|
||||
set => SetProperty(ref _tareStrategyDisplay, value);
|
||||
}
|
||||
|
||||
private bool _isManualTareStrategy;
|
||||
/// <summary>用户手动选择皮重策略后为 true,自动匹配不再覆盖。</summary>
|
||||
public bool IsManualTareStrategy
|
||||
{
|
||||
get => _isManualTareStrategy;
|
||||
set => SetProperty(ref _isManualTareStrategy, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 该行是否已生成原材料卡片(运行时状态,不直接持久化)。
|
||||
/// 加载时根据 Entry.PrintFlag + Id 是否来自持久化的 PortionDetailIds 推断;
|
||||
|
||||
@@ -242,13 +242,15 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
IRawMaterialEntryService entryService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IMixerMaterialService mixerMaterialService,
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
ISupplierService supplierService,
|
||||
IRawMaterialCardService rawMaterialCardService,
|
||||
IPrintDotService printDotService,
|
||||
IPrintBizTemplateBindService printBizTemplateBindService,
|
||||
IPrintTemplateService printTemplateService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, container, regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, tareStrategyService, supplierService, container, regionManager)
|
||||
{
|
||||
_rawMaterialCardService = rawMaterialCardService;
|
||||
_printDotService = printDotService;
|
||||
@@ -802,6 +804,8 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
ManufacturerMaterialName = Entry.ManufacturerMaterialName,
|
||||
ShelfLife = Entry.ShelfLife,
|
||||
TotalWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
PackagingTare = detail.PackagingTare.HasValue ? (decimal?)detail.PackagingTare.Value : null,
|
||||
PalletWeight = detail.PalletWeight.HasValue ? (decimal?)detail.PalletWeight.Value : null,
|
||||
RemainingWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingQuantity = detail.PortionPackages,
|
||||
WarehouseArea = detail.WarehouseLocation,
|
||||
@@ -845,9 +849,10 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
RaisePropertyChanged(nameof(CanResplit));
|
||||
|
||||
string? printError = null;
|
||||
if (newCardCount > 0 && generatedCards.Count > 0)
|
||||
{
|
||||
var printError = await PrintGeneratedRawMaterialCardsAsync(selectedPrinterName, generatedCards);
|
||||
printError = await PrintGeneratedRawMaterialCardsAsync(selectedPrinterName, generatedCards);
|
||||
if (!string.IsNullOrWhiteSpace(printError))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning($"卡片已生成 {newCardCount} 张,但打印存在异常:{printError}");
|
||||
@@ -855,7 +860,12 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
}
|
||||
|
||||
if (failCount == 0)
|
||||
HandyControl.Controls.MessageBox.Success($"已生成并打印 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(printError))
|
||||
HandyControl.Controls.MessageBox.Success($"已生成并打印 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
||||
else
|
||||
HandyControl.Controls.MessageBox.Success($"已生成 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新;请检查打印机后补打标签。");
|
||||
}
|
||||
else
|
||||
HandyControl.Controls.MessageBox.Warning($"本次共尝试生成 {newCardCount + failCount} 张,成功 {newCardCount} 张,失败 {failCount} 张。失败的行未标记为「已打印」,可检查网络后再次点击「生成原材料卡片」重试。");
|
||||
}
|
||||
@@ -950,6 +960,8 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
ManufacturerMaterialName = entry.ManufacturerMaterialName,
|
||||
ShelfLife = entry.ShelfLife,
|
||||
TotalWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
PackagingTare = detail.PackagingTare.HasValue ? (decimal?)detail.PackagingTare.Value : null,
|
||||
PalletWeight = detail.PalletWeight.HasValue ? (decimal?)detail.PalletWeight.Value : null,
|
||||
RemainingWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingQuantity = detail.PortionPackages,
|
||||
WarehouseArea = detail.WarehouseLocation,
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service.MixerMaterialTareStrategy;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
/// <summary>
|
||||
/// 拆码明细手动选择「密炼物料皮重策略」弹窗。
|
||||
/// </summary>
|
||||
public class TareStrategyPickerDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IMixerMaterialTareStrategyService _tareStrategyService;
|
||||
|
||||
private string? _materialId;
|
||||
private string? _supplierId;
|
||||
private DateTime? _entryDate;
|
||||
private double? _portionWeight;
|
||||
|
||||
public ObservableCollection<MesXslMixerMaterialTareStrategy> Records { get; } = new();
|
||||
|
||||
private MesXslMixerMaterialTareStrategy? _selectedStrategy;
|
||||
public MesXslMixerMaterialTareStrategy? SelectedStrategy
|
||||
{
|
||||
get => _selectedStrategy;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedStrategy, value);
|
||||
ConfirmCommand.RaiseCanExecuteChanged();
|
||||
RaisePropertyChanged(nameof(SelectedStrategyDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedStrategyDisplay => _selectedStrategy == null
|
||||
? "请选中一条策略后确认,或点「使用自动匹配」"
|
||||
: $"规格:{_selectedStrategy.MaterialSpec ?? "-"} 包装物:{_selectedStrategy.TareWeight:0.###} 托盘:{_selectedStrategy.PalletWeight:0.###}";
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand ConfirmCommand { get; }
|
||||
public DelegateCommand UseAutoMatchCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public TareStrategyPickerDialogViewModel(
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_tareStrategyService = tareStrategyService;
|
||||
ConfirmCommand = new DelegateCommand(ConfirmSelection, () => SelectedStrategy != null);
|
||||
UseAutoMatchCommand = new DelegateCommand(UseAutoMatch);
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
string? materialId,
|
||||
string? supplierId,
|
||||
DateTime? entryDate,
|
||||
double? portionWeight,
|
||||
string? currentStrategyId)
|
||||
{
|
||||
_materialId = materialId;
|
||||
_supplierId = supplierId;
|
||||
_entryDate = entryDate;
|
||||
_portionWeight = portionWeight;
|
||||
_ = LoadAsync(currentStrategyId);
|
||||
}
|
||||
|
||||
private async Task LoadAsync(string? currentStrategyId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var all = await _tareStrategyService.GetAllForMatchAsync();
|
||||
var filtered = MixerMaterialTareStrategyMatcher.FilterCandidates(
|
||||
all, _materialId, _supplierId, _entryDate, _portionWeight);
|
||||
|
||||
Records.Clear();
|
||||
foreach (var item in filtered)
|
||||
Records.Add(item);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(currentStrategyId))
|
||||
{
|
||||
SelectedStrategy = Records.FirstOrDefault(r =>
|
||||
string.Equals(r.Id, currentStrategyId, StringComparison.OrdinalIgnoreCase))
|
||||
?? all.FirstOrDefault(r => string.Equals(r.Id, currentStrategyId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Records.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfirmSelection()
|
||||
{
|
||||
Result = true;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
|
||||
private void UseAutoMatch()
|
||||
{
|
||||
SelectedStrategy = null;
|
||||
Result = true;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<UserControl x:Class="YY.Admin.Views.MixerMaterialTareStrategy.MixerMaterialTareStrategyEditDialogView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
Width="760"
|
||||
MinHeight="560">
|
||||
|
||||
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<hc:SimplePanel Margin="20">
|
||||
<TextBlock FontSize="18" Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
Text="{Binding DialogTitle}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
<Button Width="22" Height="22" Command="hc:ControlCommands.Close"
|
||||
Style="{StaticResource ButtonIcon}" Foreground="{DynamicResource PrimaryBrush}"
|
||||
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"
|
||||
Padding="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,4,4,0"/>
|
||||
</hc:SimplePanel>
|
||||
|
||||
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True">
|
||||
<StackPanel Margin="20,0,20,0">
|
||||
<hc:Row Gutter="10">
|
||||
|
||||
<hc:Col Span="24">
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox Grid.Column="0"
|
||||
Text="{Binding SelectedMaterialDisplay, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="密炼物料"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"/>
|
||||
<Button Grid.Column="1" Margin="8,0,0,0" Content="选择" Width="70"
|
||||
Command="{Binding OpenMaterialPickerCommand}" Style="{StaticResource ButtonPrimary}"/>
|
||||
<Button Grid.Column="2" Margin="8,0,0,0" Content="清除" Width="70"
|
||||
Command="{Binding ClearMaterialCommand}" Style="{StaticResource ButtonDefault}"/>
|
||||
</Grid>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="24">
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox Grid.Column="0"
|
||||
Text="{Binding SelectedSupplierDisplay, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="供应商"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"/>
|
||||
<Button Grid.Column="1" Margin="8,0,0,0" Content="选择" Width="70"
|
||||
Command="{Binding OpenSupplierPickerCommand}" Style="{StaticResource ButtonPrimary}"/>
|
||||
<Button Grid.Column="2" Margin="8,0,0,0" Content="清除" Width="70"
|
||||
Command="{Binding ClearSupplierCommand}" Style="{StaticResource ButtonDefault}"/>
|
||||
</Grid>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="24">
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<hc:TextBox Text="{Binding Strategy.MaterialSpec, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="物料规格"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入物料规格"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,4"/>
|
||||
<TextBlock Text="同一供应商/密炼物料下,不同规格可分别维护;规格相同且生效日期重叠时不允许重复"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"
|
||||
Margin="90,0,0,0"/>
|
||||
</StackPanel>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="12">
|
||||
<hc:NumericUpDown Value="{Binding Strategy.TareWeight}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="4"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="包装物重量"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
hc:InfoElement.Placeholder="请输入包装物重量"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="12">
|
||||
<hc:NumericUpDown Value="{Binding Strategy.PalletWeight}"
|
||||
Minimum="0"
|
||||
DecimalPlaces="4"
|
||||
Style="{StaticResource NumericUpDownPlus}"
|
||||
hc:InfoElement.Title="托盘重量"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="请输入托盘重量"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="12">
|
||||
<hc:ComboBox SelectedValuePath="Id"
|
||||
DisplayMemberPath="UnitName"
|
||||
ItemsSource="{Binding UnitOptions}"
|
||||
SelectedValue="{Binding Strategy.UnitId, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="单位"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="12">
|
||||
<hc:DatePicker SelectedDate="{Binding Strategy.EffectiveStartDate, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="生效开始"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
<hc:Col Span="12">
|
||||
<hc:DatePicker SelectedDate="{Binding Strategy.EffectiveEndDate, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Title="生效截止"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
Margin="0,0,0,16"/>
|
||||
</hc:Col>
|
||||
|
||||
</hc:Row>
|
||||
</StackPanel>
|
||||
</hc:ScrollViewer>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="20">
|
||||
<Button Content="取消" Command="{Binding CancelCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,15,0" Width="100"/>
|
||||
<Button Content="确定" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
|
||||
public partial class MixerMaterialTareStrategyEditDialogView : UserControl
|
||||
{
|
||||
public MixerMaterialTareStrategyEditDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<UserControl x:Class="YY.Admin.Views.MixerMaterialTareStrategy.MixerMaterialTareStrategyListView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" CornerRadius="4" Margin="0 0 -10 0">
|
||||
<hc:Row>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterMixerMaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="密炼物料"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入密炼物料名称"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterSupplierName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="供应商"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="65"
|
||||
hc:InfoElement.Placeholder="请输入供应商名称"
|
||||
hc:InfoElement.ShowClearButton="True"/>
|
||||
</hc:Col>
|
||||
</hc:Row>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Margin="0,10">
|
||||
<hc:UniformSpacingPanel Spacing="10">
|
||||
<Button Style="{StaticResource ButtonPrimary}" Command="{Binding SearchCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="Search"/>
|
||||
<TextBlock Text="搜索" Style="{StaticResource IconButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource ButtonDefault}" Command="{Binding ResetCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="Refresh"/>
|
||||
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource ButtonSuccess}" Command="{Binding AddCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="Plus"/>
|
||||
<TextBlock Text="新增" Style="{StaticResource IconButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</hc:UniformSpacingPanel>
|
||||
</Border>
|
||||
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding Strategies}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="FullRow"
|
||||
RowHeaderWidth="55"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#FFEDEDED"
|
||||
VerticalGridLinesBrush="Transparent"
|
||||
HeadersVisibility="All"
|
||||
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}"
|
||||
Style="{StaticResource CusDataGridStyle}"
|
||||
hc:DataGridAttach.ShowSelectAllButton="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.RowHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
|
||||
</DataTemplate>
|
||||
</DataGrid.RowHeaderTemplate>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="密炼物料" Binding="{Binding MixerMaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="供应商" Binding="{Binding SupplierName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="物料规格" Binding="{Binding MaterialSpec}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
|
||||
<DataGridTextColumn Header="包装物重量" Binding="{Binding TareWeight}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="托盘重量" Binding="{Binding PalletWeight}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="单位" Binding="{Binding UnitName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTextColumn Header="生效开始" Binding="{Binding EffectiveStartDateText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="110"/>
|
||||
<DataGridTextColumn Header="生效截止" Binding="{Binding EffectiveEndDateText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="110"/>
|
||||
<DataGridTextColumn Header="维护人" Binding="{Binding MaintainBy}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTemplateColumn Header="操作" Width="140" CellStyle="{StaticResource CusOperDataGridCellStyle}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<hc:UniformSpacingPanel Spacing="5">
|
||||
<Border Style="{DynamicResource DataGridOpeButtonStyle}">
|
||||
<Border.InputBindings>
|
||||
<MouseBinding Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}" MouseAction="LeftClick"/>
|
||||
</Border.InputBindings>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="SquareEditOutline" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="修改" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{DynamicResource DataGridOpeButtonStyle}">
|
||||
<Border.InputBindings>
|
||||
<MouseBinding Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}" MouseAction="LeftClick"/>
|
||||
</Border.InputBindings>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="TrashCanOutline" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="删除" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</hc:UniformSpacingPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<TextBlock Text="{Binding Total, StringFormat=共 {0} 条}" VerticalAlignment="Center" Margin="0,0,16,0"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"/>
|
||||
<Button Content="上一页" Command="{Binding PrevPageCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,4,0" Width="80"/>
|
||||
<TextBlock Text="{Binding PageNo, StringFormat=第 {0} 页}" VerticalAlignment="Center" Margin="8,0"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Button Content="下一页" Command="{Binding NextPageCommand}" Style="{StaticResource ButtonDefault}" Width="80"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
|
||||
public partial class MixerMaterialTareStrategyListView : UserControl
|
||||
{
|
||||
public MixerMaterialTareStrategyListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -467,20 +467,20 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window
|
||||
{
|
||||
if (_printDotService == null)
|
||||
{
|
||||
HandyControl.Controls.Growl.Warning("PrintDot 服务不可用");
|
||||
HandyControl.Controls.MessageBox.Warning("PrintDot 服务不可用");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_templateJson) || _templateJson == "{}")
|
||||
{
|
||||
HandyControl.Controls.Growl.Warning("当前模板没有可打印内容");
|
||||
HandyControl.Controls.MessageBox.Warning("当前模板没有可打印内容");
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = PrinterCombo.SelectedItem as PrintDotPrinter;
|
||||
if (selected == null || string.IsNullOrWhiteSpace(selected.Name))
|
||||
{
|
||||
HandyControl.Controls.Growl.Warning("请先选择打印机");
|
||||
HandyControl.Controls.MessageBox.Warning("请先选择打印机");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -521,13 +521,13 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window
|
||||
await _printDotService.PrintAsync(selected.Name, pdfBase64, jobName, copies: 1);
|
||||
|
||||
SetStatus("打印任务已发送");
|
||||
HandyControl.Controls.Growl.Success("打印任务已发送至 PrintDot");
|
||||
HandyControl.Controls.MessageBox.Success("打印任务已发送至 PrintDot");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 顶部状态栏单行显示首行,完整多行处理步骤在 Growl 弹窗中展示
|
||||
// 独立预览窗无 Growl 面板,用状态栏 + MessageBox 展示错误,避免 NullReferenceException
|
||||
SetStatus($"打印失败:{ex.Message}");
|
||||
HandyControl.Controls.Growl.Error($"打印失败:{ex.Message}");
|
||||
HandyControl.Controls.MessageBox.Error($"打印失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
<DataGridTextColumn Header="厂家物料名称" Binding="{Binding ManufacturerMaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
|
||||
<DataGridTextColumn Header="保质期" Binding="{Binding ShelfLife}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="总重(kg)" Binding="{Binding TotalWeight, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="包装物皮重" Binding="{Binding PackagingTare, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="95"/>
|
||||
<DataGridTextColumn Header="托盘重量" Binding="{Binding PalletWeight, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="剩余重量" Binding="{Binding RemainingWeight, StringFormat=N3}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="剩余数量" Binding="{Binding RemainingQuantity}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTextColumn Header="状态" Binding="{Binding StatusText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="70"/>
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
<DataGridTextColumn Header="供料客户" Binding="{Binding SupplyCustomer}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
|
||||
<DataGridTextColumn Header="供应商名称" Binding="{Binding SupplierName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="总重(KG)" Binding="{Binding TotalWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="托盘及皮重(合计)" Binding="{Binding PalletTareTotal, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
|
||||
<DataGridTextColumn Header="总份数" Binding="{Binding TotalPortions}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="70"/>
|
||||
<DataGridTextColumn Header="检测结果" Binding="{Binding TestResultText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="85"/>
|
||||
<DataGridTextColumn Header="检测状态" Binding="{Binding TestStatusText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="85"/>
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
<RowDefinition Height="36"/> <!-- 厂家物料名称 / 保质期 -->
|
||||
<RowDefinition Height="40"/> <!-- 总重 / 总份数 — 多 4px 给 NumericUpDown 上下间距,否则 spinner 箭头会被裁切 -->
|
||||
<RowDefinition Height="36"/> <!-- 每份总重 / 每份包数 -->
|
||||
<RowDefinition Height="36"/> <!-- 托盘及皮重(合计) -->
|
||||
<RowDefinition Height="36"/> <!-- 库位 / 卸货人 -->
|
||||
<RowDefinition Height="60"/> <!-- 备注 -->
|
||||
</Grid.RowDefinitions>
|
||||
@@ -205,7 +206,7 @@
|
||||
<Border Grid.Row="1" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,3">
|
||||
<!-- 自定义日期时间选择器:日历 + 时/分/秒 三列 + 此刻/确定 -->
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding Entry.EntryTime, Mode=TwoWay}"
|
||||
<ctrls:DateTimeListPicker SelectedDateTime="{Binding EntryTimeInput, Mode=TwoWay}"
|
||||
DateTimeFormat="yyyy-MM-dd HH:mm:ss"
|
||||
Placeholder="请选择入场时间"/>
|
||||
</Border>
|
||||
@@ -458,14 +459,30 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ===== Row 7: 库位 / 卸货人 ===== -->
|
||||
<!-- ===== Row 7: 托盘及皮重(合计) ===== -->
|
||||
<Border Grid.Row="7" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="托盘及皮重(合计)" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="11" Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
TextWrapping="Wrap" TextAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
|
||||
<TextBlock Text="{Binding PalletTareTotalDisplay, StringFormat={}{0:0.##}}"
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
ToolTip="由拆码明细自动汇总:Σ份数×(包装物皮重+托盘重量)"/>
|
||||
</Border>
|
||||
|
||||
<!-- ===== Row 8: 库位 / 卸货人 ===== -->
|
||||
<Border Grid.Row="8" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="库位" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="1"
|
||||
<Border Grid.Row="8" Grid.Column="1"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.WarehouseLocation, UpdateSourceTrigger=PropertyChanged}"
|
||||
@@ -490,13 +507,13 @@
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="2"
|
||||
<Border Grid.Row="8" Grid.Column="2"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="卸货人" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="7" Grid.Column="3"
|
||||
<Border Grid.Row="8" Grid.Column="3"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.UnloadOperator, UpdateSourceTrigger=PropertyChanged}"
|
||||
@@ -522,14 +539,14 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ===== Row 8: 备注(末行,无内部底边线) ===== -->
|
||||
<Border Grid.Row="8" Grid.Column="0"
|
||||
<!-- ===== Row 9: 备注(末行,无内部底边线) ===== -->
|
||||
<Border Grid.Row="9" Grid.Column="0"
|
||||
Background="{DynamicResource SecondaryRegionBrush}"
|
||||
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock Text="备注" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<Border Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="3" Padding="4,4">
|
||||
<Border Grid.Row="9" Grid.Column="1" Grid.ColumnSpan="3" Padding="4,4">
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Entry.Remark, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" AcceptsReturn="True"
|
||||
@@ -644,42 +661,54 @@
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
<StackPanel>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel MinWidth="820">
|
||||
<!-- 表头 -->
|
||||
<Grid Background="{DynamicResource SecondaryRegionBrush}" Height="40">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="130"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="份数"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock Grid.Column="1" Text="每份重量(KG)"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock Grid.Column="2" Text="每份包数"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<!-- 库位列:保存时非必填;点击「生成原材料卡片」时必填,ToolTip 给出说明 -->
|
||||
<TextBlock Grid.Column="3" Text="库位"
|
||||
<TextBlock Grid.Column="3" Text="包装物皮重"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
ToolTip="自动匹配皮重策略;点击可手动选择"/>
|
||||
<TextBlock Grid.Column="4" Text="托盘重量"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
ToolTip="由皮重策略自动匹配,未匹配时为0"/>
|
||||
<TextBlock Grid.Column="5" Text="库位"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
ToolTip="生成原材料卡片时为必填项"/>
|
||||
<TextBlock Grid.Column="4" Text="打印标记"
|
||||
<TextBlock Grid.Column="6" Text="打印标记"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
<TextBlock Grid.Column="5" Text="操作"
|
||||
<TextBlock Grid.Column="7" Text="操作"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" FontSize="13"
|
||||
FontWeight="SemiBold" FontSize="12"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -736,19 +765,21 @@
|
||||
</DataTemplate.Resources>
|
||||
<Grid Height="44">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="130"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding Portions, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource LockableSplitTextBoxStyle}"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBox Text="{Binding PortionWeight, UpdateSourceTrigger=PropertyChanged}"
|
||||
<TextBox Text="{Binding PortionWeightText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||||
Style="{StaticResource LockableSplitTextBoxStyle}"/>
|
||||
</Border>
|
||||
<Border Grid.Column="2" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
@@ -756,6 +787,59 @@
|
||||
Style="{StaticResource LockableSplitTextBoxStyle}"/>
|
||||
</Border>
|
||||
<Border Grid.Column="3" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<Button Command="{Binding DataContext.OpenTareStrategyPickerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTip="{Binding TareStrategyDisplay}"
|
||||
Cursor="Hand"
|
||||
VerticalAlignment="Center"
|
||||
Height="32"
|
||||
Margin="4,0"
|
||||
Focusable="False">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
<Setter Property="ToolTip" Value="点击选择皮重策略"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding HasCard}" Value="True">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="该行已生成原材料卡片,不可修改。如需调整请先点「重新拆码」清空全部卡片"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd"
|
||||
BorderBrush="#D9D9D9" BorderThickness="1"
|
||||
CornerRadius="2"
|
||||
Background="White">
|
||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Text="{Binding PackagingTare, StringFormat={}{0:0.##}}"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="#4096FF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#F0F7FF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#F5F5F5"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Border>
|
||||
<Border Grid.Column="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="{Binding PalletWeight, StringFormat={}{0:0.##}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
ToolTip="{Binding TareStrategyDisplay}"/>
|
||||
</Border>
|
||||
<Border Grid.Column="5" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<!--
|
||||
InputBindings 内 RelativeSource 不在可视树中,查找会静默失败。
|
||||
改用 Button + ControlTemplate:Command 写在 Button 元素上,可视树正常,RelativeSource 可靠。
|
||||
@@ -836,11 +920,7 @@
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Border>
|
||||
<!--
|
||||
打印标记列:行级状态(HasCard),与「继续拆码」流程契合。
|
||||
旧行(已生成卡片)显示绿色「已打印」;新增的待生成行显示灰色「未打印」。
|
||||
-->
|
||||
<Border Grid.Column="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<Border Grid.Column="6" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<Border CornerRadius="2" Padding="6,2" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Border.Style>
|
||||
@@ -873,7 +953,7 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Column="5" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<Border Grid.Column="7" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
|
||||
<!-- 行级:HasCard==true(该行已生成卡片)时隐藏删除按钮,新增的待生成行可正常删除 -->
|
||||
<Button Content="删除"
|
||||
Command="{Binding DataContext.RemoveSplitDetailCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
@@ -897,6 +977,7 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<UserControl x:Class="YY.Admin.Views.RawMaterialEntry.TareStrategyPickerDialogView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Width="820" MinHeight="420">
|
||||
|
||||
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Margin="20,16,20,8" Text="选择皮重策略"
|
||||
FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||
|
||||
<DataGrid Grid.Row="1" Margin="16,0"
|
||||
ItemsSource="{Binding Records}"
|
||||
SelectedItem="{Binding SelectedStrategy}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
HeadersVisibility="Column">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="物料规格" Binding="{Binding MaterialSpec}" Width="100"/>
|
||||
<DataGridTextColumn Header="包装物重量" Binding="{Binding TareWeight}" Width="100"/>
|
||||
<DataGridTextColumn Header="托盘重量" Binding="{Binding PalletWeight}" Width="100"/>
|
||||
<DataGridTextColumn Header="单位" Binding="{Binding UnitName}" Width="80"/>
|
||||
<DataGridTextColumn Header="生效开始" Binding="{Binding EffectiveStartDate, StringFormat=yyyy-MM-dd}" Width="110"/>
|
||||
<DataGridTextColumn Header="生效截止" Binding="{Binding EffectiveEndDate, StringFormat=yyyy-MM-dd}" Width="110"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="2" Margin="16,12">
|
||||
<TextBlock Text="{Binding SelectedStrategyDisplay}"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
TextWrapping="Wrap" Margin="0,0,0,10"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Content="取消" Command="{Binding CancelCommand}" Style="{StaticResource ButtonDefault}" Width="100" Margin="0,0,10,0"/>
|
||||
<Button Content="使用自动匹配" Command="{Binding UseAutoMatchCommand}" Style="{StaticResource ButtonDefault}" Width="120" Margin="0,0,10,0"/>
|
||||
<Button Content="确认选择" Command="{Binding ConfirmCommand}" Style="{StaticResource ButtonPrimary}" Width="100"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace YY.Admin.Views.RawMaterialEntry;
|
||||
|
||||
public partial class TareStrategyPickerDialogView
|
||||
{
|
||||
public TareStrategyPickerDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,7 @@
|
||||
<DataGridTextColumn Header="供应商(发货单位)" Binding="{Binding SenderUnit}" Width="*"/>
|
||||
<DataGridTextColumn Header="净重(KG)" Binding="{Binding NetWeight, StringFormat=N2}" Width="90"/>
|
||||
<DataGridTextColumn Header="已入场(KG)" Binding="{Binding EnteredWeight, StringFormat=N2}" Width="100"/>
|
||||
<DataGridTextColumn Header="货物皮重" Binding="{Binding CargoTareWeight, StringFormat=N2}" Width="90"/>
|
||||
<DataGridTextColumn Header="称重日期" Binding="{Binding WeighDate, StringFormat='yyyy-MM-dd'}" Width="110"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
<DataGridTextColumn Header="皮重(KG)" Binding="{Binding TareWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="净重(KG)" Binding="{Binding NetWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="已入场重量(KG)" Binding="{Binding EnteredWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
|
||||
<DataGridTextColumn Header="货物皮重" Binding="{Binding CargoTareWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="原料重量" Binding="{Binding RawMaterialWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
|
||||
<DataGridTextColumn Header="司机" Binding="{Binding DriverName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="手机号" Binding="{Binding DriverPhone}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
|
||||
<DataGridTextColumn Header="创建时间" Binding="{Binding CreateTime, StringFormat='yyyy-MM-dd HH:mm'}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
|
||||
|
||||
@@ -179,12 +179,54 @@ WHERE NOT EXISTS (
|
||||
grantLoginSettingsRole.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// 基础资料下「密炼物料皮重策略」菜单
|
||||
long tareStrategyMenuId = 1300150011101;
|
||||
var ensureTareStrategy = conn.CreateCommand();
|
||||
ensureTareStrategy.Transaction = tx;
|
||||
ensureTareStrategy.CommandText = @"
|
||||
INSERT INTO sys_menu (id, pid, type, name, path, component, title, icon, is_iframe, is_hide, is_keep_alive, is_affix, order_no, status, create_time)
|
||||
SELECT $id, $pid, 2, 'mesXslMixerMaterialTareStrategy', '/xslmes/mesXslMixerMaterialTareStrategy', 'MixerMaterialTareStrategyListView', '密炼物料皮重策略', '', 0, 0, 1, 0, 110, 1, datetime('now')
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = $id OR name = 'mesXslMixerMaterialTareStrategy');
|
||||
";
|
||||
ensureTareStrategy.Parameters.AddWithValue("$id", tareStrategyMenuId);
|
||||
ensureTareStrategy.Parameters.AddWithValue("$pid", baseMenuId);
|
||||
ensureTareStrategy.ExecuteNonQuery();
|
||||
|
||||
var grantTareStrategyTenant = conn.CreateCommand();
|
||||
grantTareStrategyTenant.Transaction = tx;
|
||||
grantTareStrategyTenant.CommandText = @"
|
||||
INSERT INTO sys_tenant_menu (tenant_id, menu_id)
|
||||
SELECT 1300000000001, $menuId
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_tenant_menu t WHERE t.tenant_id = 1300000000001 AND t.menu_id = $menuId
|
||||
);
|
||||
";
|
||||
grantTareStrategyTenant.Parameters.AddWithValue("$menuId", tareStrategyMenuId);
|
||||
grantTareStrategyTenant.ExecuteNonQuery();
|
||||
|
||||
if (roleObj != null && roleObj != DBNull.Value)
|
||||
{
|
||||
var adminRoleId = Convert.ToInt64(roleObj);
|
||||
var grantTareStrategyRole = conn.CreateCommand();
|
||||
grantTareStrategyRole.Transaction = tx;
|
||||
grantTareStrategyRole.CommandText = @"
|
||||
INSERT INTO sys_role_menu (id, role_id, menu_id)
|
||||
SELECT (SELECT IFNULL(MAX(id),0)+1 FROM sys_role_menu), $roleId, $menuId
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu r WHERE r.role_id = $roleId AND r.menu_id = $menuId
|
||||
);
|
||||
";
|
||||
grantTareStrategyRole.Parameters.AddWithValue("$roleId", adminRoleId);
|
||||
grantTareStrategyRole.Parameters.AddWithValue("$menuId", tareStrategyMenuId);
|
||||
grantTareStrategyRole.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
var verify = conn.CreateCommand();
|
||||
verify.Transaction = tx;
|
||||
verify.CommandText = @"
|
||||
SELECT id, pid, title, path, name, component, type, status, order_no
|
||||
FROM sys_menu
|
||||
WHERE title IN ('基础资料','车辆管理','客户管理')
|
||||
WHERE title IN ('基础资料','车辆管理','客户管理','密炼物料皮重策略')
|
||||
ORDER BY title, id;
|
||||
";
|
||||
using var reader = verify.ExecuteReader();
|
||||
|
||||
11
yy-admin-master/scripts/MenuFixTemp/MenuFixTemp.csproj
Normal file
11
yy-admin-master/scripts/MenuFixTemp/MenuFixTemp.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
37
yy-admin-master/scripts/MenuFixTemp/Program.cs
Normal file
37
yy-admin-master/scripts/MenuFixTemp/Program.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
var dbPath = args[0];
|
||||
using var conn = new SqliteConnection($"Data Source={dbPath}");
|
||||
conn.Open();
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
void Exec(string sql) {
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.Transaction = tx;
|
||||
cmd.CommandText = sql;
|
||||
Console.WriteLine($"{sql.Split('\n')[0].Trim()} => {cmd.ExecuteNonQuery()}");
|
||||
}
|
||||
|
||||
Exec(@"INSERT INTO sys_menu (id, pid, type, name, path, component, title, icon, is_iframe, is_hide, is_keep_alive, is_affix, order_no, status, create_time)
|
||||
SELECT 1300150011101, 1300150000101, 2, 'mesXslMixerMaterialTareStrategy', '/xslmes/mesXslMixerMaterialTareStrategy', 'MixerMaterialTareStrategyListView', '密炼物料皮重策略', '', 0, 0, 1, 0, 110, 1, datetime('now')
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 1300150011101 OR name = 'mesXslMixerMaterialTareStrategy');");
|
||||
|
||||
Exec(@"INSERT INTO sys_tenant_menu (id, tenant_id, menu_id)
|
||||
SELECT 1300150011101, 1300000000001, 1300150011101
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_tenant_menu WHERE tenant_id = 1300000000001 AND menu_id = 1300150011101);");
|
||||
|
||||
Exec(@"INSERT INTO sys_role_menu (id, role_id, menu_id)
|
||||
SELECT (SELECT IFNULL(MAX(id),0)+1 FROM sys_role_menu),
|
||||
(SELECT id FROM sys_role ORDER BY id LIMIT 1),
|
||||
1300150011101
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu rm
|
||||
WHERE rm.role_id = (SELECT id FROM sys_role ORDER BY id LIMIT 1) AND rm.menu_id = 1300150011101
|
||||
);");
|
||||
|
||||
tx.Commit();
|
||||
|
||||
using var q = conn.CreateCommand();
|
||||
q.CommandText = "SELECT id, pid, title, name, status FROM sys_menu WHERE name='mesXslMixerMaterialTareStrategy';";
|
||||
using var r = q.ExecuteReader();
|
||||
while (r.Read()) Console.WriteLine($"OK: id={r.GetInt64(0)} pid={r.GetInt64(1)} title={r.GetString(2)} status={r.GetInt32(4)}");
|
||||
50
yy-admin-master/scripts/ensure_tare_strategy_menu.ps1
Normal file
50
yy-admin-master/scripts/ensure_tare_strategy_menu.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
# 为已有桌面端 SQLite 库补「密炼物料皮重策略」菜单(幂等)
|
||||
$dbPath = Join-Path $env:LOCALAPPDATA "YY.Admin\Data\Admin.NET.db"
|
||||
if (-not (Test-Path $dbPath)) {
|
||||
Write-Host "未找到数据库: $dbPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$proj = Join-Path $PSScriptRoot "MenuFixTemp"
|
||||
New-Item -ItemType Directory -Force -Path $proj | Out-Null
|
||||
|
||||
@'
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
'@ | Set-Content "$proj\MenuFixTemp.csproj" -Encoding UTF8
|
||||
|
||||
@'
|
||||
using Microsoft.Data.Sqlite;
|
||||
var dbPath = args[0];
|
||||
using var conn = new SqliteConnection($"Data Source={dbPath}");
|
||||
conn.Open();
|
||||
using var tx = conn.BeginTransaction();
|
||||
void Exec(string sql) {
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.Transaction = tx;
|
||||
cmd.CommandText = sql;
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
Exec(@"INSERT INTO sys_menu (id, pid, type, name, path, component, title, icon, is_iframe, is_hide, is_keep_alive, is_affix, order_no, status, create_time)
|
||||
SELECT 1300150011101, 1300150000101, 2, 'mesXslMixerMaterialTareStrategy', '/xslmes/mesXslMixerMaterialTareStrategy', 'MixerMaterialTareStrategyListView', '密炼物料皮重策略', '', 0, 0, 1, 0, 110, 1, datetime('now')
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 1300150011101 OR name = 'mesXslMixerMaterialTareStrategy');");
|
||||
Exec(@"INSERT INTO sys_tenant_menu (id, tenant_id, menu_id)
|
||||
SELECT 1300150011101, 1300000000001, 1300150011101
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_tenant_menu WHERE tenant_id = 1300000000001 AND menu_id = 1300150011101);");
|
||||
Exec(@"INSERT INTO sys_role_menu (id, role_id, menu_id)
|
||||
SELECT (SELECT IFNULL(MAX(id),0)+1 FROM sys_role_menu), (SELECT id FROM sys_role ORDER BY id LIMIT 1), 1300150011101
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = (SELECT id FROM sys_role ORDER BY id LIMIT 1) AND rm.menu_id = 1300150011101);");
|
||||
tx.Commit();
|
||||
Console.WriteLine("菜单补全完成");
|
||||
'@ | Set-Content "$proj\Program.cs" -Encoding UTF8
|
||||
|
||||
dotnet run --project "$proj\MenuFixTemp.csproj" -- "$dbPath"
|
||||
Reference in New Issue
Block a user