桌面端新增密炼计划获取
This commit is contained in:
@@ -13,6 +13,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanSaveAllVO;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -29,10 +30,13 @@ public class MesXslMixingProductionPlanController
|
||||
extends JeecgController<MesXslMixingProductionPlan, IMesXslMixingProductionPlanService> {
|
||||
|
||||
private final IMesXslMixingProductionPlanService mixingProductionPlanService;
|
||||
private final MesXslStompNotifyService stompNotify;
|
||||
|
||||
public MesXslMixingProductionPlanController(
|
||||
IMesXslMixingProductionPlanService mixingProductionPlanService) {
|
||||
IMesXslMixingProductionPlanService mixingProductionPlanService,
|
||||
MesXslStompNotifyService stompNotify) {
|
||||
this.mixingProductionPlanService = mixingProductionPlanService;
|
||||
this.stompNotify = stompNotify;
|
||||
}
|
||||
|
||||
@Operation(summary = "密炼生产计划维护-分页列表查询")
|
||||
@@ -56,6 +60,9 @@ public class MesXslMixingProductionPlanController
|
||||
@PostMapping("/saveAll")
|
||||
public Result<String> saveAll(@RequestBody MesXslMixingProductionPlanSaveAllVO req) {
|
||||
mixingProductionPlanService.saveAllRows(req == null ? null : req.getRows());
|
||||
//update-begin---author:jiangxh ---date:20260617 for:【密炼计划】整表保存后广播桌面端同步-----------
|
||||
stompNotify.publishMixingProductionPlanChanged("saveAll", null);
|
||||
//update-end---author:jiangxh ---date:20260617 for:【密炼计划】整表保存后广播桌面端同步-----------
|
||||
return Result.OK("保存成功");
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,14 @@ public class MesXslStompNotifyService {
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260617 for:【快检实验标准】桌面端只读同步 STOMP-----------
|
||||
|
||||
//update-begin---author:jiangxh ---date:20260617 for:【密炼计划】桌面端只读同步 STOMP-----------
|
||||
/** 广播密炼生产计划变更事件到 /topic/sync/mes-mixing-production-plans */
|
||||
public void publishMixingProductionPlanChanged(String action, String mixingProductionPlanId) {
|
||||
publish("/topic/sync/mes-mixing-production-plans", "MIXING_PRODUCTION_PLAN_CHANGED",
|
||||
"mixingProductionPlanId", mixingProductionPlanId, action);
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260617 for:【密炼计划】桌面端只读同步 STOMP-----------
|
||||
|
||||
// ─────────────────────────── 私有辅助 ────────────────────────────
|
||||
|
||||
private void publish(String topic, String cmd, String idKey, String idValue, String action) {
|
||||
|
||||
374
jeecg-boot/scan_mixing_plan.json
Normal file
374
jeecg-boot/scan_mixing_plan.json
Normal file
@@ -0,0 +1,374 @@
|
||||
{
|
||||
"scanKeyword": "MesXslMixingProductionPlan",
|
||||
"entityClass": "MesXslMixingProductionPlan",
|
||||
"tableName": "mes_xsl_mixing_production_plan",
|
||||
"javaEntityFile": "jeecg-boot\\jeecg-boot-module\\jeecg-module-xslmes\\src\\main\\java\\org\\jeecg\\modules\\xslmes\\entity\\MesXslMixingProductionPlan.java",
|
||||
"hasIzEnable": false,
|
||||
"hasCodeUniqueness": false,
|
||||
"uniquenessFields": [],
|
||||
"backendArch": {
|
||||
"unifiedAnonCtrl": "jeecg-boot\\jeecg-boot-module\\jeecg-module-xslmes\\src\\main\\java\\org\\jeecg\\modules\\xslmes\\controller\\MesXslDesktopAnonController.java",
|
||||
"registeredInAnonCtrl": true,
|
||||
"anonEndpoints": [
|
||||
"list"
|
||||
],
|
||||
"stompNotifySvc": "jeecg-boot\\jeecg-boot-module\\jeecg-module-xslmes\\src\\main\\java\\org\\jeecg\\modules\\xslmes\\service\\MesXslStompNotifyService.java",
|
||||
"registeredInStompSvc": false,
|
||||
"bizCtrlFile": "jeecg-boot\\jeecg-boot-module\\jeecg-module-xslmes\\src\\main\\java\\org\\jeecg\\modules\\xslmes\\controller\\MesXslMixingProductionPlanController.java",
|
||||
"bizCtrlUsesSharedNotify": false,
|
||||
"bizCtrlHasPrivatePublish": false
|
||||
},
|
||||
"wpfRegistrationStatus": {
|
||||
"syncModuleService": false,
|
||||
"syncModuleCoordinator": false,
|
||||
"navigationView": false,
|
||||
"stompSubscribe": false,
|
||||
"menuRegistered": false,
|
||||
"tenantMenuRegistered": false,
|
||||
"syncModuleFilePath": "yy-admin-master\\YY.Admin\\Module\\SyncModule.cs",
|
||||
"navExtFilePath": "yy-admin-master\\YY.Admin\\Module\\NavigationExtensions.cs",
|
||||
"stompWsFilePath": "yy-admin-master\\YY.Admin\\Infrastructure\\Hubs\\StompWebSocketService.cs",
|
||||
"menuSeedFilePath": "yy-admin-master\\YY.Admin.Core\\SeedData\\SysMenuSeedData.cs",
|
||||
"summary": "✗ 待完成: SyncModule服务注册, SyncModule协调器注册, NavigationExtensions视图注册, STOMP订阅, 菜单注册"
|
||||
},
|
||||
"menuSuggestion": {
|
||||
"parentMenuId": 1300150000101,
|
||||
"parentMenuTitle": "基础资料",
|
||||
"nextMenuId": 1300150011401,
|
||||
"nextOrderNo": 113,
|
||||
"menuIdPattern": "130015001{N}01,N 每次 +1(1→101,2→201...)",
|
||||
"alreadyExists": false,
|
||||
"existingMenuId": null
|
||||
},
|
||||
"apiPrefix": "/xslmes/mesXslMixingProductionPlan",
|
||||
"stompCmd": "MIXING_PRODUCTION_PLAN_CHANGED",
|
||||
"stompTopic": "/topic/sync/mes-mixing-production-plans",
|
||||
"stompSubscriptionId": "sub-mes-xsl-mixing-production-plan",
|
||||
"syncMode": "B",
|
||||
"syncModeReason": "有/anon/免密端点,适合模式B",
|
||||
"filterFields": [
|
||||
"machineId",
|
||||
"machineName",
|
||||
"planNo",
|
||||
"planType",
|
||||
"materialName"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"javaName": "sortNo",
|
||||
"csName": "SortNo",
|
||||
"sqlName": "sort_no",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "machineId",
|
||||
"csName": "MachineId",
|
||||
"sqlName": "machine_id",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": "id"
|
||||
},
|
||||
{
|
||||
"javaName": "machineName",
|
||||
"csName": "MachineName",
|
||||
"sqlName": "machine_name",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "shiftFlag",
|
||||
"csName": "ShiftFlag",
|
||||
"sqlName": "shift_flag",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planDate",
|
||||
"csName": "PlanDate",
|
||||
"sqlName": "plan_date",
|
||||
"javaType": "Date",
|
||||
"csType": "DateTime?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planNo",
|
||||
"csName": "PlanNo",
|
||||
"sqlName": "plan_no",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planId",
|
||||
"csName": "PlanId",
|
||||
"sqlName": "plan_id",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planType",
|
||||
"csName": "PlanType",
|
||||
"sqlName": "plan_type",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "sourceOrderId",
|
||||
"csName": "SourceOrderId",
|
||||
"sqlName": "source_order_id",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "materialId",
|
||||
"csName": "MaterialId",
|
||||
"sqlName": "material_id",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "materialName",
|
||||
"csName": "MaterialName",
|
||||
"sqlName": "material_name",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "orderNo",
|
||||
"csName": "OrderNo",
|
||||
"sqlName": "order_no",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "orderDate",
|
||||
"csName": "OrderDate",
|
||||
"sqlName": "order_date",
|
||||
"javaType": "Date",
|
||||
"csType": "DateTime?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "formulaName",
|
||||
"csName": "FormulaName",
|
||||
"sqlName": "formula_name",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planWeight",
|
||||
"csName": "PlanWeight",
|
||||
"sqlName": "plan_weight",
|
||||
"javaType": "BigDecimal",
|
||||
"csType": "double?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "plannedCarCount",
|
||||
"csName": "PlannedCarCount",
|
||||
"sqlName": "planned_car_count",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "scheduledCarCount",
|
||||
"csName": "ScheduledCarCount",
|
||||
"sqlName": "scheduled_car_count",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "finishedCarCount",
|
||||
"csName": "FinishedCarCount",
|
||||
"sqlName": "finished_car_count",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "planCount",
|
||||
"csName": "PlanCount",
|
||||
"sqlName": "plan_count",
|
||||
"javaType": "Integer",
|
||||
"csType": "int?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
{
|
||||
"javaName": "remark",
|
||||
"csName": "Remark",
|
||||
"sqlName": "remark",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": false,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
}
|
||||
],
|
||||
"pkField": {
|
||||
"javaName": "id",
|
||||
"csName": "Id",
|
||||
"sqlName": "id",
|
||||
"javaType": "String",
|
||||
"csType": "string?",
|
||||
"comment": "",
|
||||
"isPk": true,
|
||||
"isAudit": false,
|
||||
"isIzEnable": false,
|
||||
"required": false,
|
||||
"dictCode": null
|
||||
},
|
||||
"auditFields": [
|
||||
"TenantId",
|
||||
"SysOrgCode",
|
||||
"CreateBy",
|
||||
"CreateTime",
|
||||
"UpdateBy",
|
||||
"UpdateTime",
|
||||
"DelFlag"
|
||||
],
|
||||
"dbConfig": {
|
||||
"url": "jdbc:mysql://localhost:3306/jeecg-boot-dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai",
|
||||
"username": "root",
|
||||
"configFile": "jeecg-boot\\jeecg-boot-module\\jeecg-boot-module-airag\\src\\main\\resources\\application.yml"
|
||||
},
|
||||
"dbColumns": [],
|
||||
"csEntityStub": "public class MesXslMixingProductionPlan\n{\n public string? Id { get; set; }\n public int? SortNo { get; set; }\n public string? MachineId { get; set; } [Dict:id]\n public string? MachineName { get; set; }\n public int? ShiftFlag { get; set; }\n public DateTime? PlanDate { get; set; }\n public string? PlanNo { get; set; }\n public string? PlanId { get; set; }\n public string? PlanType { get; set; }\n public string? SourceOrderId { get; set; }\n public string? MaterialId { get; set; }\n public string? MaterialName { get; set; }\n public string? OrderNo { get; set; }\n public DateTime? OrderDate { get; set; }\n public string? FormulaName { get; set; }\n public double? PlanWeight { get; set; }\n public int? PlannedCarCount { get; set; }\n public int? ScheduledCarCount { get; set; }\n public int? FinishedCarCount { get; set; }\n public int? PlanCount { get; set; }\n public string? Remark { get; set; }\n public int? TenantId { get; set; }\n public string? SysOrgCode { get; set; }\n public string? CreateBy { get; set; }\n public DateTime? CreateTime { get; set; }\n public string? UpdateBy { get; set; }\n public DateTime? UpdateTime { get; set; }\n public int? DelFlag { get; set; }\n // 只读显示属性:\n // public string StatusText => Status == \"1\" ? \"停用\" : \"启用\";\n}",
|
||||
"generationHints": {
|
||||
"eventClassName": "MesXslMixingProductionPlanChangedEvent",
|
||||
"serviceInterface": "IMixingProductionPlanService",
|
||||
"serviceImpl": "MixingProductionPlanService",
|
||||
"syncCoordinator": "MixingProductionPlanSyncCoordinator",
|
||||
"listViewModel": "MixingProductionPlanListViewModel",
|
||||
"editDialogViewModel": "MixingProductionPlanEditDialogViewModel",
|
||||
"listView": "MixingProductionPlanListView",
|
||||
"editDialogView": "MixingProductionPlanEditDialogView",
|
||||
"pendingOpsFile": "mes-xsl-mixing-production-plan-pending-ops.json",
|
||||
"cacheFile": "mes-xsl-mixing-production-plan-cache.json",
|
||||
"nextMenuId": 1300150011401,
|
||||
"nextMenuOrderNo": 113,
|
||||
"backendFilesToModify": [
|
||||
"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\\service\\MesXslStompNotifyService.java",
|
||||
"jeecg-boot\\jeecg-boot-module\\jeecg-module-xslmes\\src\\main\\java\\org\\jeecg\\modules\\xslmes\\controller\\MesXslMixingProductionPlanController.java",
|
||||
"jeecg-boot-base-core/.../ShiroConfig.java"
|
||||
],
|
||||
"wpfFilesToModify": [
|
||||
"yy-admin-master\\YY.Admin\\Module\\SyncModule.cs",
|
||||
"yy-admin-master\\YY.Admin\\Module\\NavigationExtensions.cs",
|
||||
"yy-admin-master\\YY.Admin\\Infrastructure\\Hubs\\StompWebSocketService.cs",
|
||||
"yy-admin-master\\YY.Admin.Core\\SeedData\\SysMenuSeedData.cs",
|
||||
"YY.Admin.Core/SeedData/SysTenantMenuSeedData.cs"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace YY.Admin.Core.Events;
|
||||
|
||||
public class MixingProductionPlanChangedPayload
|
||||
{
|
||||
public string Action { get; set; } = string.Empty;
|
||||
public string? MixingProductionPlanId { get; set; }
|
||||
}
|
||||
|
||||
public class MixingProductionPlanChangedEvent : PubSubEvent<MixingProductionPlanChangedPayload> { }
|
||||
@@ -0,0 +1,28 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
/// <summary>密炼生产计划(MES 只读同步)</summary>
|
||||
public interface IMixingProductionPlanService
|
||||
{
|
||||
Task<MixingProductionPlanPageResult> PageAsync(
|
||||
int pageNo, int pageSize,
|
||||
DateTime? planDateFrom = null,
|
||||
DateTime? planDateTo = null,
|
||||
string? machineName = null,
|
||||
int? shiftFlag = null,
|
||||
string? planNo = null,
|
||||
string? materialName = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default);
|
||||
|
||||
/// <returns>本地缓存是否有变更(有差异才写入)</returns>
|
||||
Task<bool> SyncFromRemoteAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public record MixingProductionPlanPageResult(
|
||||
List<MesXslMixingProductionPlan> Records,
|
||||
long Total,
|
||||
int PageNo,
|
||||
int PageSize);
|
||||
@@ -14,7 +14,8 @@ public interface IRubberQuickTestStdService
|
||||
|
||||
Task<MesXslRubberQuickTestStd?> GetByIdAsync(string id, CancellationToken ct = default);
|
||||
|
||||
Task SyncFromRemoteAsync(CancellationToken ct = default);
|
||||
/// <returns>本地缓存是否有变更(有差异才写入)</returns>
|
||||
Task<bool> SyncFromRemoteAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public record RubberQuickTestStdPageResult(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
/// <summary>密炼生产计划维护(桌面端快检记录筛选用)</summary>
|
||||
/// <summary>密炼生产计划维护(MES 数据源,桌面端只读同步)</summary>
|
||||
public class MesXslMixingProductionPlan
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
@@ -8,21 +8,43 @@ public class MesXslMixingProductionPlan
|
||||
public string? MachineId { get; set; }
|
||||
public string? MachineName { get; set; }
|
||||
|
||||
public string? MorningPlanId { get; set; }
|
||||
public string? MorningPlanType { get; set; }
|
||||
public string? MorningOrderNo { get; set; }
|
||||
public DateTime? MorningOrderDate { get; set; }
|
||||
public string? MorningFormulaName { get; set; }
|
||||
/// <summary>班次标识:1早班 2中班 3晚班</summary>
|
||||
public int? ShiftFlag { get; set; }
|
||||
|
||||
public string? NoonPlanId { get; set; }
|
||||
public string? NoonPlanType { get; set; }
|
||||
public string? NoonOrderNo { get; set; }
|
||||
public DateTime? NoonOrderDate { get; set; }
|
||||
public string? NoonFormulaName { get; set; }
|
||||
/// <summary>计划日期(密炼日期)</summary>
|
||||
public DateTime? PlanDate { get; set; }
|
||||
|
||||
public string? NightPlanId { get; set; }
|
||||
public string? NightPlanType { get; set; }
|
||||
public string? NightOrderNo { get; set; }
|
||||
public DateTime? NightOrderDate { get; set; }
|
||||
public string? NightFormulaName { get; set; }
|
||||
public string? PlanNo { get; set; }
|
||||
public string? PlanId { get; set; }
|
||||
public string? PlanType { get; set; }
|
||||
public string? SourceOrderId { get; set; }
|
||||
public string? MaterialId { get; set; }
|
||||
public string? MaterialName { get; set; }
|
||||
public string? OrderNo { get; set; }
|
||||
public DateTime? OrderDate { get; set; }
|
||||
public string? FormulaName { get; set; }
|
||||
public double? PlanWeight { get; set; }
|
||||
public int? PlannedCarCount { get; set; }
|
||||
public int? ScheduledCarCount { get; set; }
|
||||
public int? FinishedCarCount { get; set; }
|
||||
/// <summary>计划数量(MES plan_count)</summary>
|
||||
public int? PlanCount { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public int? TenantId { get; set; }
|
||||
public string? SysOrgCode { get; set; }
|
||||
public string? CreateBy { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
public string? UpdateBy { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
public int? DelFlag { get; set; }
|
||||
|
||||
public string ShiftFlagText => ShiftFlag switch
|
||||
{
|
||||
1 => "早班",
|
||||
2 => "中班",
|
||||
3 => "晚班",
|
||||
_ => ShiftFlag?.ToString() ?? string.Empty
|
||||
};
|
||||
|
||||
public string PlanDateText => PlanDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace YY.Admin.Core.Helper;
|
||||
|
||||
/// <summary>MES 只读数据:远端列表与本地缓存按 Id 对比合并</summary>
|
||||
public static class MesReadOnlyCacheMergeHelper
|
||||
{
|
||||
public sealed record MergeResult(int Added, int Updated, int Removed)
|
||||
{
|
||||
public bool HasChanges => Added > 0 || Updated > 0 || Removed > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对比远端与本地,返回合并后的列表及变更统计。
|
||||
/// 内容相同则保留本地副本;<paramref name="mergeUpdated"/> 可定制变更行的合并策略(如保留本地子表)。
|
||||
/// </summary>
|
||||
public static (List<T> Merged, MergeResult Stats) Merge<T>(
|
||||
IReadOnlyList<T> local,
|
||||
IReadOnlyList<T> remote,
|
||||
Func<T, string?> getId,
|
||||
Func<T, T, bool> isContentEqual,
|
||||
Func<T, T> clone,
|
||||
Func<T, T, T>? mergeUpdated = null)
|
||||
{
|
||||
var localById = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var item in local)
|
||||
{
|
||||
var id = getId(item);
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
localById[id] = item;
|
||||
}
|
||||
|
||||
var remoteIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var merged = new List<T>(remote.Count);
|
||||
int added = 0, updated = 0;
|
||||
|
||||
foreach (var remoteItem in remote)
|
||||
{
|
||||
var id = getId(remoteItem);
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
merged.Add(clone(remoteItem));
|
||||
added++;
|
||||
continue;
|
||||
}
|
||||
|
||||
remoteIds.Add(id);
|
||||
|
||||
if (!localById.TryGetValue(id, out var localItem))
|
||||
{
|
||||
merged.Add(clone(remoteItem));
|
||||
added++;
|
||||
}
|
||||
else if (!isContentEqual(localItem, remoteItem))
|
||||
{
|
||||
merged.Add(mergeUpdated != null ? mergeUpdated(localItem, remoteItem) : clone(remoteItem));
|
||||
updated++;
|
||||
}
|
||||
else
|
||||
{
|
||||
merged.Add(clone(localItem));
|
||||
}
|
||||
}
|
||||
|
||||
int removed = localById.Keys.Count(id => !remoteIds.Contains(id));
|
||||
return (merged, new MergeResult(added, updated, removed));
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="快检记录", Path="/xslmes/rubberQuickTestOperation", Name="rubberQuickTestOperation", Component="RubberQuickTestOperationView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=111 },
|
||||
// 胶料快检实验标准(桌面端只读)
|
||||
new SysMenu{ Id=1300150011301, Pid=1300150000101, Title="胶料快检实验标准", Path="/xslmes/mesXslRubberQuickTestStd", Name="mesXslRubberQuickTestStd", Component="RubberQuickTestStdListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=112 },
|
||||
// 密炼计划
|
||||
new SysMenu{ Id=1300150011401, Pid=1300150000101, Title="密炼计划", Path="/xslmes/mesXslMixingProductionPlan", Name="mesXslMixingProductionPlan", Component="MixingProductionPlanListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 },
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011201},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011301},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011401},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012111},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012121},
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Prism.Events;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Services;
|
||||
namespace YY.Admin.Services.Service.MixingProductionPlan;
|
||||
|
||||
/// <summary>密炼生产计划:MES 只读拉取 + 本地缓存,断网读缓存,联网刷新</summary>
|
||||
public class MixingProductionPlanService : IMixingProductionPlanService, 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 _cacheFilePath;
|
||||
private List<MesXslMixingProductionPlan> _localCache = new();
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Converters = { new NullableDateTimeJsonConverter() }
|
||||
};
|
||||
|
||||
public MixingProductionPlanService(
|
||||
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);
|
||||
_cacheFilePath = Path.Combine(appDataDir, "mes-xsl-mixing-production-plan-cache.json");
|
||||
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[密炼计划] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
_ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
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<MixingProductionPlanPageResult> PageAsync(
|
||||
int pageNo, int pageSize,
|
||||
DateTime? planDateFrom = null,
|
||||
DateTime? planDateTo = null,
|
||||
string? machineName = null,
|
||||
int? shiftFlag = null,
|
||||
string? planNo = null,
|
||||
string? materialName = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 列表拉取失败,使用本地缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
List<MesXslMixingProductionPlan> source;
|
||||
lock (_cacheLock)
|
||||
source = _localCache.Select(Clone).ToList();
|
||||
|
||||
var filtered = ApplyFilters(source, planDateFrom, planDateTo, machineName, shiftFlag, planNo, materialName);
|
||||
var total = filtered.Count;
|
||||
var records = filtered
|
||||
.Skip(Math.Max(0, (pageNo - 1) * pageSize))
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
return new MixingProductionPlanPageResult(records, total, pageNo, pageSize);
|
||||
}
|
||||
|
||||
public async Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 全量拉取失败,使用本地缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
return _localCache.Select(Clone).ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> SyncFromRemoteAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _syncLock.WaitAsync(ct).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!_networkMonitor.IsOnline)
|
||||
return false;
|
||||
|
||||
var all = new List<MesXslMixingProductionPlan>();
|
||||
int pageNo = 1;
|
||||
const int pageSize = 500;
|
||||
while (true)
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["pageNo"] = pageNo.ToString();
|
||||
query["pageSize"] = pageSize.ToString();
|
||||
query["tenantId"] = DefaultTenantId.ToString();
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixingProductionPlan/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);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl)) break;
|
||||
|
||||
var page = resultEl.GetProperty("records")
|
||||
.Deserialize<List<MesXslMixingProductionPlan>>(_jsonOpts) ?? new();
|
||||
all.AddRange(page);
|
||||
|
||||
long total = 0;
|
||||
if (resultEl.TryGetProperty("total", out var totalEl)) total = totalEl.GetInt64();
|
||||
if (all.Count >= total || page.Count < pageSize) break;
|
||||
pageNo++;
|
||||
}
|
||||
|
||||
List<MesXslMixingProductionPlan> localSnapshot;
|
||||
lock (_cacheLock)
|
||||
localSnapshot = _localCache.Select(Clone).ToList();
|
||||
|
||||
var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge(
|
||||
localSnapshot,
|
||||
all,
|
||||
x => x.Id,
|
||||
IsPlanContentEqual,
|
||||
Clone);
|
||||
|
||||
if (!stats.HasChanges)
|
||||
{
|
||||
_logger.Information($"[密炼计划] 与 MES 对比无差异,跳过更新 count={merged.Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = merged;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
_logger.Information(
|
||||
$"[密炼计划] 差异同步完成 total={merged.Count} 新增={stats.Added} 变更={stats.Updated} 删除={stats.Removed}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 远程同步失败:{ex.Message}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = "reconnect" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 重连同步失败:{ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static bool IsPlanContentEqual(MesXslMixingProductionPlan a, MesXslMixingProductionPlan b) =>
|
||||
string.Equals(GetPlanFingerprint(a), GetPlanFingerprint(b), StringComparison.Ordinal);
|
||||
|
||||
private static string GetPlanFingerprint(MesXslMixingProductionPlan x) =>
|
||||
JsonSerializer.Serialize(Clone(x), _jsonOpts);
|
||||
private static List<MesXslMixingProductionPlan> ApplyFilters(
|
||||
List<MesXslMixingProductionPlan> source,
|
||||
DateTime? planDateFrom,
|
||||
DateTime? planDateTo,
|
||||
string? machineName,
|
||||
int? shiftFlag,
|
||||
string? planNo,
|
||||
string? materialName)
|
||||
{
|
||||
IEnumerable<MesXslMixingProductionPlan> q = source;
|
||||
if (planDateFrom.HasValue)
|
||||
q = q.Where(x => x.PlanDate?.Date >= planDateFrom.Value.Date);
|
||||
if (planDateTo.HasValue)
|
||||
q = q.Where(x => x.PlanDate?.Date <= planDateTo.Value.Date);
|
||||
if (!string.IsNullOrWhiteSpace(machineName))
|
||||
q = q.Where(x => (x.MachineName ?? "").Contains(machineName.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
if (shiftFlag.HasValue && shiftFlag.Value > 0)
|
||||
q = q.Where(x => x.ShiftFlag == shiftFlag.Value);
|
||||
if (!string.IsNullOrWhiteSpace(planNo))
|
||||
q = q.Where(x => (x.PlanNo ?? "").Contains(planNo.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrWhiteSpace(materialName))
|
||||
q = q.Where(x => (x.MaterialName ?? "").Contains(materialName.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return q
|
||||
.OrderByDescending(x => x.PlanDate ?? DateTime.MinValue)
|
||||
.ThenBy(x => x.SortNo ?? int.MaxValue)
|
||||
.ThenBy(x => x.MachineName)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void LoadCacheFromDisk()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_cacheFilePath)) return;
|
||||
_localCache = JsonSerializer.Deserialize<List<MesXslMixingProductionPlan>>(
|
||||
File.ReadAllText(_cacheFilePath), _jsonOpts) ?? new();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_localCache = new();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveCacheToDiskUnsafe() =>
|
||||
File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localCache, _jsonOpts));
|
||||
|
||||
private static MesXslMixingProductionPlan Clone(MesXslMixingProductionPlan x) => new()
|
||||
{
|
||||
Id = x.Id,
|
||||
SortNo = x.SortNo,
|
||||
MachineId = x.MachineId,
|
||||
MachineName = x.MachineName,
|
||||
ShiftFlag = x.ShiftFlag,
|
||||
PlanDate = x.PlanDate,
|
||||
PlanNo = x.PlanNo,
|
||||
PlanId = x.PlanId,
|
||||
PlanType = x.PlanType,
|
||||
SourceOrderId = x.SourceOrderId,
|
||||
MaterialId = x.MaterialId,
|
||||
MaterialName = x.MaterialName,
|
||||
OrderNo = x.OrderNo,
|
||||
OrderDate = x.OrderDate,
|
||||
FormulaName = x.FormulaName,
|
||||
PlanWeight = x.PlanWeight,
|
||||
PlannedCarCount = x.PlannedCarCount,
|
||||
ScheduledCarCount = x.ScheduledCarCount,
|
||||
FinishedCarCount = x.FinishedCarCount,
|
||||
PlanCount = x.PlanCount,
|
||||
Remark = x.Remark,
|
||||
TenantId = x.TenantId,
|
||||
SysOrgCode = x.SysOrgCode,
|
||||
CreateBy = x.CreateBy,
|
||||
CreateTime = x.CreateTime,
|
||||
UpdateBy = x.UpdateBy,
|
||||
UpdateTime = x.UpdateTime,
|
||||
DelFlag = x.DelFlag
|
||||
};
|
||||
|
||||
private sealed class NullableDateTimeJsonConverter : JsonConverter<DateTime?>
|
||||
{
|
||||
private static readonly string[] Formats =
|
||||
[
|
||||
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.fff",
|
||||
"yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ss.fff",
|
||||
"yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ",
|
||||
"yyyy-MM-dd"
|
||||
];
|
||||
|
||||
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,63 @@
|
||||
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.MixingProductionPlan;
|
||||
|
||||
public class MixingProductionPlanSyncCoordinator : ISingletonDependency
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public MixingProductionPlanSyncCoordinator(
|
||||
IEventAggregator eventAggregator,
|
||||
SyncPollManager pollManager,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
|
||||
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("密炼计划", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_logger.Information("[密炼计划] MixingProductionPlanSyncCoordinator 已启动");
|
||||
}
|
||||
|
||||
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("MIXING_PRODUCTION_PLAN_CHANGED", StringComparison.OrdinalIgnoreCase) ?? true)
|
||||
return;
|
||||
|
||||
doc.RootElement.TryGetProperty("action", out var actionEl);
|
||||
doc.RootElement.TryGetProperty("mixingProductionPlanId", out var idEl);
|
||||
|
||||
var changed = new MixingProductionPlanChangedPayload
|
||||
{
|
||||
Action = actionEl.GetString() ?? string.Empty,
|
||||
MixingProductionPlanId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
|
||||
};
|
||||
_logger.Information($"[密炼计划] STOMP action={changed.Action}, id={changed.MixingProductionPlanId}");
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 处理 STOMP 命令失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public class RubberQuickTestOperationService : IRubberQuickTestOperationService,
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly INetworkMonitor _networkMonitor;
|
||||
private readonly IMixingProductionPlanService _mixingProductionPlanService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOpts = new()
|
||||
@@ -27,11 +28,13 @@ public class RubberQuickTestOperationService : IRubberQuickTestOperationService,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
INetworkMonitor networkMonitor,
|
||||
IMixingProductionPlanService mixingProductionPlanService,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_networkMonitor = networkMonitor;
|
||||
_mixingProductionPlanService = mixingProductionPlanService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -40,32 +43,7 @@ public class RubberQuickTestOperationService : IRubberQuickTestOperationService,
|
||||
|
||||
public async Task<List<MesXslMixingProductionPlan>> GetMixingProductionPlansAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (!_networkMonitor.IsOnline)
|
||||
throw new InvalidOperationException("网络未连接,无法加载密炼生产计划");
|
||||
|
||||
var result = new List<MesXslMixingProductionPlan>();
|
||||
int pageNo = 1;
|
||||
const int pageSize = 500;
|
||||
while (true)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslMixingProductionPlan/anon/list?pageNo={pageNo}&pageSize={pageSize}&tenantId={DefaultTenantId}";
|
||||
using var client = _httpClientFactory.CreateClient("JeecgApi");
|
||||
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);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl)) break;
|
||||
if (resultEl.TryGetProperty("records", out var recordsEl))
|
||||
{
|
||||
var page = recordsEl.Deserialize<List<MesXslMixingProductionPlan>>(_jsonOpts);
|
||||
if (page != null) result.AddRange(page);
|
||||
}
|
||||
long total = 0;
|
||||
if (resultEl.TryGetProperty("total", out var totalEl)) total = totalEl.GetInt64();
|
||||
if (result.Count >= total || (resultEl.TryGetProperty("records", out var r2) && r2.GetArrayLength() < pageSize)) break;
|
||||
pageNo++;
|
||||
}
|
||||
|
||||
var result = await _mixingProductionPlanService.GetAllCachedAsync(ct).ConfigureAwait(false);
|
||||
_logger.Information($"[快检记录] 加载密炼生产计划 {result.Count} 条");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Web;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.Services.Service.RubberQuickTestStd;
|
||||
@@ -115,7 +116,7 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
var entity = resultEl.Deserialize<MesXslRubberQuickTestStd>(_jsonOpts);
|
||||
if (entity != null)
|
||||
{
|
||||
UpsertLocalCacheMain(entity);
|
||||
UpsertIfChanged(entity);
|
||||
return CloneMain(entity);
|
||||
}
|
||||
}
|
||||
@@ -134,14 +135,14 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SyncFromRemoteAsync(CancellationToken ct = default)
|
||||
public async Task<bool> SyncFromRemoteAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _syncLock.WaitAsync(ct).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
if (!_networkMonitor.IsOnline)
|
||||
return;
|
||||
return false;
|
||||
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["pageNo"] = "1";
|
||||
@@ -158,16 +159,37 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
var records = doc.RootElement.GetProperty("result").GetProperty("records")
|
||||
.Deserialize<List<MesXslRubberQuickTestStd>>(_jsonOpts) ?? new();
|
||||
|
||||
List<MesXslRubberQuickTestStd> localSnapshot;
|
||||
lock (_cacheLock)
|
||||
localSnapshot = _localCache.Select(CloneMain).ToList();
|
||||
|
||||
var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge(
|
||||
localSnapshot,
|
||||
records,
|
||||
x => x.Id,
|
||||
IsStdListContentEqual,
|
||||
CloneMain,
|
||||
MergeStdUpdated);
|
||||
|
||||
if (!stats.HasChanges)
|
||||
{
|
||||
_logger.Information($"[快检实验标准] 与 MES 对比无差异,跳过更新 count={merged.Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_localCache = records.Select(CloneMain).ToList();
|
||||
_localCache = merged;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
_logger.Information($"[快检实验标准] 同步完成 count={records.Count}");
|
||||
_logger.Information(
|
||||
$"[快检实验标准] 差异同步完成 total={merged.Count} 新增={stats.Added} 变更={stats.Updated} 删除={stats.Removed}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[快检实验标准] 远程同步失败:{ex.Message}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -180,7 +202,8 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = "reconnect" });
|
||||
});
|
||||
@@ -202,19 +225,67 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
return q.OrderByDescending(x => x.CreateTime ?? DateTime.MinValue).ToList();
|
||||
}
|
||||
|
||||
private void UpsertLocalCacheMain(MesXslRubberQuickTestStd entity)
|
||||
private bool UpsertIfChanged(MesXslRubberQuickTestStd entity)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entity.Id)) return;
|
||||
if (string.IsNullOrWhiteSpace(entity.Id)) return false;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var idx = _localCache.FindIndex(x => string.Equals(x.Id, entity.Id, StringComparison.OrdinalIgnoreCase));
|
||||
var copy = CloneMain(entity);
|
||||
if (idx >= 0) _localCache[idx] = copy;
|
||||
else _localCache.Insert(0, copy);
|
||||
if (idx >= 0)
|
||||
{
|
||||
if (IsStdDetailContentEqual(_localCache[idx], entity))
|
||||
return false;
|
||||
_localCache[idx] = CloneMain(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_localCache.Insert(0, CloneMain(entity));
|
||||
}
|
||||
SaveCacheToDiskUnsafe();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsStdListContentEqual(MesXslRubberQuickTestStd a, MesXslRubberQuickTestStd b) =>
|
||||
string.Equals(GetStdListFingerprint(a), GetStdListFingerprint(b), StringComparison.Ordinal);
|
||||
|
||||
private static bool IsStdDetailContentEqual(MesXslRubberQuickTestStd a, MesXslRubberQuickTestStd b) =>
|
||||
string.Equals(GetStdDetailFingerprint(a), GetStdDetailFingerprint(b), StringComparison.Ordinal);
|
||||
|
||||
private static string GetStdListFingerprint(MesXslRubberQuickTestStd x)
|
||||
{
|
||||
var snap = CloneMain(x);
|
||||
snap.LineList = null;
|
||||
return JsonSerializer.Serialize(snap, _jsonOpts);
|
||||
}
|
||||
|
||||
private static string GetStdDetailFingerprint(MesXslRubberQuickTestStd x) =>
|
||||
JsonSerializer.Serialize(CloneMain(x), _jsonOpts);
|
||||
|
||||
private static MesXslRubberQuickTestStd MergeStdUpdated(MesXslRubberQuickTestStd local, MesXslRubberQuickTestStd remote)
|
||||
{
|
||||
var copy = CloneMain(remote);
|
||||
if ((copy.LineList == null || copy.LineList.Count == 0) && local.LineList is { Count: > 0 })
|
||||
{
|
||||
copy.LineList = local.LineList.Select(l => new MesXslRubberQuickTestStdLine
|
||||
{
|
||||
Id = l.Id,
|
||||
StdId = l.StdId,
|
||||
DataPointId = l.DataPointId,
|
||||
PointName = l.PointName,
|
||||
LowerLimit = l.LowerLimit,
|
||||
UpperLimit = l.UpperLimit,
|
||||
LowerWarn = l.LowerWarn,
|
||||
UpperWarn = l.UpperWarn,
|
||||
TargetValue = l.TargetValue,
|
||||
SortNo = l.SortNo
|
||||
}).ToList();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void UpsertLocalCacheMain(MesXslRubberQuickTestStd entity) => UpsertIfChanged(entity);
|
||||
|
||||
private void LoadCacheFromDisk()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,7 +2,6 @@ using Prism.Events;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Events;
|
||||
|
||||
namespace YY.Admin.Services.Service.RubberQuickTestStd;
|
||||
|
||||
@@ -21,8 +20,6 @@ public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
|
||||
|
||||
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("胶料快检实验标准", () =>
|
||||
{
|
||||
@@ -34,14 +31,6 @@ public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
|
||||
_logger.Information("[快检实验标准] RubberQuickTestStdSyncCoordinator 已启动");
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
if (!payload.IsOnline) return;
|
||||
_logger.Information("[快检实验标准] 网络恢复,触发补偿刷新");
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = "reconnect" });
|
||||
}
|
||||
|
||||
private void OnRemoteCommand(RemoteCommandPayload payload)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -178,6 +178,10 @@ public class StompWebSocketService : ISignalRService
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-rubber-quick-test-stds", "/topic/sync/mes-rubber-quick-test-stds"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
// 密炼生产计划变更:订阅 /topic/sync/mes-mixing-production-plans
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-mes-xsl-mixing-production-plan", "/topic/sync/mes-mixing-production-plans"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 订阅服务端 PONG 回复(应用层假在线检测)
|
||||
await SendFrameAsync(
|
||||
|
||||
@@ -20,6 +20,7 @@ using YY.Admin.Views.Print;
|
||||
using YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
using YY.Admin.Views.RubberQuickTest;
|
||||
using YY.Admin.Views.RubberQuickTestStd;
|
||||
using YY.Admin.Views.MixingProductionPlan;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
@@ -101,6 +102,8 @@ namespace YY.Admin
|
||||
containerRegistry.RegisterForNavigation<RubberQuickTestOperationView>();
|
||||
// 胶料快检实验标准(只读)
|
||||
containerRegistry.RegisterForNavigation<RubberQuickTestStdListView>();
|
||||
// 密炼计划(只读)
|
||||
containerRegistry.RegisterForNavigation<MixingProductionPlanListView>();
|
||||
// 打印设置
|
||||
containerRegistry.RegisterForNavigation<PrintSettingsView>();
|
||||
// 打印模板列表
|
||||
|
||||
@@ -29,6 +29,7 @@ using YY.Admin.Services.Service.WeightRecord;
|
||||
using YY.Admin.Services.Service.Print;
|
||||
using YY.Admin.Services.Service.RubberQuickTest;
|
||||
using YY.Admin.Services.Service.RubberQuickTestStd;
|
||||
using YY.Admin.Services.Service.MixingProductionPlan;
|
||||
|
||||
namespace YY.Admin.Module;
|
||||
|
||||
@@ -95,6 +96,10 @@ public class SyncModule : IModule
|
||||
containerRegistry.RegisterSingleton<IRubberQuickTestStdService, RubberQuickTestStdService>();
|
||||
containerRegistry.RegisterSingleton<RubberQuickTestStdSyncCoordinator>();
|
||||
|
||||
// 密炼计划(MES 只读同步)
|
||||
containerRegistry.RegisterSingleton<IMixingProductionPlanService, MixingProductionPlanService>();
|
||||
containerRegistry.RegisterSingleton<MixingProductionPlanSyncCoordinator>();
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddTransient<DisconnectGuardHandler>();
|
||||
serviceCollection.AddHttpClient("JeecgApi", (sp, client) =>
|
||||
@@ -167,6 +172,8 @@ public class SyncModule : IModule
|
||||
_ = containerProvider.Resolve<PrintBizTemplateBindSyncCoordinator>();
|
||||
// 胶料快检实验标准只读同步协调器
|
||||
_ = containerProvider.Resolve<RubberQuickTestStdSyncCoordinator>();
|
||||
// 密炼计划只读同步协调器
|
||||
_ = containerProvider.Resolve<MixingProductionPlanSyncCoordinator>();
|
||||
}
|
||||
|
||||
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
|
||||
|
||||
@@ -162,6 +162,11 @@ namespace YY.Admin.ViewModels.Control
|
||||
["/xslmes/mesXslRubberQuickTestStd"] = "RubberQuickTestStdListView",
|
||||
["mesXslRubberQuickTestStd"] = "RubberQuickTestStdListView",
|
||||
|
||||
// 已实现页面:密炼计划(只读)
|
||||
["MixingProductionPlanListView"] = "MixingProductionPlanListView",
|
||||
["/xslmes/mesXslMixingProductionPlan"] = "MixingProductionPlanListView",
|
||||
["mesXslMixingProductionPlan"] = "MixingProductionPlanListView",
|
||||
|
||||
// 已实现页面:打印设置
|
||||
["PrintSettingsView"] = "PrintSettingsView",
|
||||
["/system/printSettings"] = "PrintSettingsView",
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using HandyControl.Controls;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
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.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.MixingProductionPlan;
|
||||
|
||||
public class MixingProductionPlanListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IMixingProductionPlanService _planService;
|
||||
private SubscriptionToken? _changedToken;
|
||||
|
||||
private ObservableCollection<MesXslMixingProductionPlan> _items = new();
|
||||
public ObservableCollection<MesXslMixingProductionPlan> Items
|
||||
{
|
||||
get => _items;
|
||||
set => SetProperty(ref _items, 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 DateTime? _filterPlanDateFrom;
|
||||
public DateTime? FilterPlanDateFrom { get => _filterPlanDateFrom; set => SetProperty(ref _filterPlanDateFrom, value); }
|
||||
|
||||
private DateTime? _filterPlanDateTo;
|
||||
public DateTime? FilterPlanDateTo { get => _filterPlanDateTo; set => SetProperty(ref _filterPlanDateTo, value); }
|
||||
|
||||
private string? _filterMachineName;
|
||||
public string? FilterMachineName { get => _filterMachineName; set => SetProperty(ref _filterMachineName, value); }
|
||||
|
||||
private int? _filterShiftFlag;
|
||||
public int? FilterShiftFlag { get => _filterShiftFlag; set => SetProperty(ref _filterShiftFlag, value); }
|
||||
|
||||
private string? _filterPlanNo;
|
||||
public string? FilterPlanNo { get => _filterPlanNo; set => SetProperty(ref _filterPlanNo, value); }
|
||||
|
||||
private string? _filterMaterialName;
|
||||
public string? FilterMaterialName { get => _filterMaterialName; set => SetProperty(ref _filterMaterialName, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, int?>> ShiftOptions { get; } = new()
|
||||
{
|
||||
new KeyValuePair<string, int?>("全部", null),
|
||||
new KeyValuePair<string, int?>("早班", 1),
|
||||
new KeyValuePair<string, int?>("中班", 2),
|
||||
new KeyValuePair<string, int?>("晚班", 3)
|
||||
};
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public MixingProductionPlanListViewModel(
|
||||
IMixingProductionPlanService planService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_planService = planService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterPlanDateFrom = null;
|
||||
FilterPlanDateTo = null;
|
||||
FilterMachineName = null;
|
||||
FilterShiftFlag = null;
|
||||
FilterPlanNo = null;
|
||||
FilterMaterialName = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
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<MixingProductionPlanChangedEvent>()
|
||||
.Subscribe(async _ => await LoadAsync(), ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
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 _planService.PageAsync(
|
||||
PageNo, PageSize,
|
||||
FilterPlanDateFrom, FilterPlanDateTo,
|
||||
FilterMachineName, FilterShiftFlag,
|
||||
FilterPlanNo, FilterMaterialName);
|
||||
Items = new ObservableCollection<MesXslMixingProductionPlan>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载密炼计划失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_changedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Unsubscribe(_changedToken);
|
||||
_changedToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,10 @@ public class MixingPlanShiftOption
|
||||
public string? PlanId { get; set; }
|
||||
public string? OrderNo { get; set; }
|
||||
public string? FormulaName { get; set; }
|
||||
public string? MaterialName { get; set; }
|
||||
public string DisplayText => string.IsNullOrWhiteSpace(OrderNo)
|
||||
? FormulaName ?? string.Empty
|
||||
: $"{OrderNo} | {FormulaName}";
|
||||
? MaterialName ?? FormulaName ?? string.Empty
|
||||
: $"{OrderNo} | {MaterialName ?? FormulaName}";
|
||||
}
|
||||
|
||||
public class QuickTestInspectCellViewModel : BindableBase
|
||||
@@ -217,7 +218,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
|
||||
public string? ProductionOrderNo => _selectedPlan?.OrderNo;
|
||||
public string? MachineName => _selectedPlan?.MachineName ?? SelectedMachine;
|
||||
public string? WorkShiftDisplay => SelectedShift?.Name ?? string.Empty;
|
||||
public string? RubberMaterialName => _selectedPlan?.FormulaName;
|
||||
public string? RubberMaterialName => _selectedPlan?.MaterialName ?? _selectedPlan?.FormulaName;
|
||||
|
||||
private string? _trainNo;
|
||||
public string? TrainNo
|
||||
@@ -286,32 +287,25 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
|
||||
_allShiftOptions.Clear();
|
||||
foreach (var row in _allPlans)
|
||||
{
|
||||
AddShiftOption(row, "morning", "1", "早班", row.MorningPlanId, row.MorningOrderDate, row.MorningOrderNo, row.MorningFormulaName);
|
||||
AddShiftOption(row, "noon", "2", "中班", row.NoonPlanId, row.NoonOrderDate, row.NoonOrderNo, row.NoonFormulaName);
|
||||
AddShiftOption(row, "night", "3", "晚班", row.NightPlanId, row.NightOrderDate, row.NightOrderNo, row.NightFormulaName);
|
||||
if (string.IsNullOrWhiteSpace(row.PlanId)) continue;
|
||||
var shiftCode = row.ShiftFlag?.ToString() ?? string.Empty;
|
||||
_allShiftOptions.Add(new MixingPlanShiftOption
|
||||
{
|
||||
PlanRowId = row.Id ?? string.Empty,
|
||||
MachineId = row.MachineId,
|
||||
MachineName = row.MachineName,
|
||||
ShiftKey = shiftCode,
|
||||
ShiftCode = shiftCode,
|
||||
ShiftName = row.ShiftFlagText,
|
||||
OrderDate = row.PlanDate,
|
||||
PlanId = row.PlanId,
|
||||
OrderNo = row.OrderNo,
|
||||
FormulaName = row.FormulaName,
|
||||
MaterialName = row.MaterialName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddShiftOption(
|
||||
MesXslMixingProductionPlan row, string shiftKey, string shiftCode, string shiftName,
|
||||
string? planId, DateTime? orderDate, string? orderNo, string? formulaName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(planId) || string.IsNullOrWhiteSpace(orderNo)) return;
|
||||
_allShiftOptions.Add(new MixingPlanShiftOption
|
||||
{
|
||||
PlanRowId = row.Id ?? string.Empty,
|
||||
MachineId = row.MachineId,
|
||||
MachineName = row.MachineName,
|
||||
ShiftKey = shiftKey,
|
||||
ShiftCode = shiftCode,
|
||||
ShiftName = shiftName,
|
||||
OrderDate = orderDate,
|
||||
PlanId = planId,
|
||||
OrderNo = orderNo,
|
||||
FormulaName = formulaName
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable<MixingPlanShiftOption> FilteredByDate =>
|
||||
_allShiftOptions.Where(o => o.OrderDate?.Date == MixingDate.Date);
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<UserControl x:Class="YY.Admin.Views.MixingProductionPlan.MixingProductionPlanListView"
|
||||
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:DatePicker SelectedDate="{Binding FilterPlanDateFrom}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="密炼日期起"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.Placeholder="开始日期"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:DatePicker SelectedDate="{Binding FilterPlanDateTo}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="密炼日期止"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.Placeholder="结束日期"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterMachineName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="机台名称"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
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:ComboBox SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding ShiftOptions}"
|
||||
SelectedValue="{Binding FilterShiftFlag}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="班次"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
hc:InfoElement.Placeholder="全部"/>
|
||||
</hc:Col>
|
||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
|
||||
<hc:TextBox Text="{Binding FilterPlanNo, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="计划号"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
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 FilterMaterialName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="胶料名称"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
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>
|
||||
<TextBlock Text="数据来自 MES 密炼生产计划维护,桌面端只读;断网时显示本地缓存,联网后自动刷新"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
FontSize="12"/>
|
||||
</hc:UniformSpacingPanel>
|
||||
</Border>
|
||||
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding Items}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
SelectionMode="Single"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#FFEDEDED"
|
||||
VerticalGridLinesBrush="Transparent"
|
||||
HeadersVisibility="All"
|
||||
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}"
|
||||
Style="{StaticResource CusDataGridStyle}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="密炼日期" Binding="{Binding PlanDateText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="110"/>
|
||||
<DataGridTextColumn Header="机台名称" Binding="{Binding MachineName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="班次" Binding="{Binding ShiftFlagText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
<DataGridTextColumn Header="计划号" Binding="{Binding PlanNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="计划数量" Binding="{Binding PlanCount}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="胶料名称" Binding="{Binding MaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="*"/>
|
||||
</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.MixingProductionPlan;
|
||||
|
||||
public partial class MixingProductionPlanListView : UserControl
|
||||
{
|
||||
public MixingProductionPlanListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user