This commit is contained in:
2026-06-02 16:38:48 +08:00
129 changed files with 8149 additions and 386 deletions

View File

@@ -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)
}

View File

@@ -0,0 +1,82 @@
-- MES 停机记录建表 + 菜单 + 按钮 + 租户 admin 授权可整文件一次执行
-- 权限前缀mes:mes_xsl_downtime_record:*
-- 依赖mes_xsl_equipment_ledgermes_xsl_downtime_type父菜单 设备管理
-- FlywayV3.9.2_117__mes_xsl_downtime_record.sql
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `mes_xsl_downtime_record` (
`id` varchar(32) NOT NULL COMMENT '主键',
`equipment_ledger_id` varchar(32) NOT NULL COMMENT '设备台账主键 mes_xsl_equipment_ledger.id',
`equipment_code` varchar(500) DEFAULT NULL COMMENT '设备编号冗余',
`equipment_name` varchar(500) DEFAULT NULL COMMENT '设备名称冗余',
`downtime_type_id` varchar(32) NOT NULL COMMENT '停机类型主键 mes_xsl_downtime_type.id',
`downtime_type_name` varchar(500) DEFAULT NULL COMMENT '停机类型名称冗余',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`equipment_part_id` varchar(32) DEFAULT NULL COMMENT '设备部位主键 mes_xsl_equipment_part.id',
`equipment_part_name` varchar(500) DEFAULT NULL COMMENT '设备部位名称冗余',
`inspect_maintain_item_id` varchar(32) DEFAULT NULL COMMENT '点检及保养项目主键 mes_xsl_inspect_maintain_item.id',
`inspect_maintain_item_name` varchar(500) DEFAULT NULL COMMENT '点检项目名称冗余',
`maintenance_result` varchar(500) DEFAULT NULL COMMENT '维修结果',
`maintainer_user_id` varchar(32) DEFAULT NULL COMMENT '维修人用户ID',
`maintainer_username` varchar(500) DEFAULT NULL COMMENT '维修人账号',
`maintainer_realname` varchar(500) DEFAULT NULL COMMENT '维修人姓名',
`maintenance_filled_flag` varchar(1) DEFAULT '0' COMMENT '是否已录入维修结果字典yn1是0否',
`tenant_id` int DEFAULT NULL COMMENT '租户',
`sys_org_code` varchar(500) DEFAULT NULL COMMENT '部门',
`create_by` varchar(500) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(500) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int DEFAULT '0' COMMENT '删除标记0正常1删除',
PRIMARY KEY (`id`),
KEY `idx_mdr_equipment` (`equipment_ledger_id`),
KEY `idx_mdr_downtime_type` (`downtime_type_id`),
KEY `idx_mdr_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES停机记录';
SET @mes_tenant_id = 1002;
SET @mes_equip_pid = (
SELECT `id` FROM `sys_permission`
WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` = '设备管理'
LIMIT 1
);
SET @mes_equip_pid = IFNULL(@mes_equip_pid, '1860000000000000133');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
VALUES ('1860000000000000201', @mes_equip_pid, '停机记录', '/xslmes/mesXslDowntimeRecord', 'xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList', 'MesXslDowntimeRecordList', 1, NULL, '1', 12, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `url` = VALUES(`url`), `component` = VALUES(`component`), `component_name` = VALUES(`component_name`),
`menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`), `sort_no` = VALUES(`sort_no`),
`is_route` = VALUES(`is_route`), `is_leaf` = VALUES(`is_leaf`), `hidden` = VALUES(`hidden`), `status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`),
`keep_alive` = VALUES(`keep_alive`), `internal_or_external` = VALUES(`internal_or_external`), `icon` = 'ant-design:history-outlined';
UPDATE `sys_permission` SET `icon` = 'ant-design:history-outlined' WHERE `id` = '1860000000000000201' AND `del_flag` = 0;
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`) VALUES
('1860000000000000202', '1860000000000000201', '新增', 2, 'mes:mes_xsl_downtime_record:add', '1', '1', 0, 'admin', NOW()),
('1860000000000000203', '1860000000000000201', '编辑', 2, 'mes:mes_xsl_downtime_record:edit', '1', '1', 0, 'admin', NOW()),
('1860000000000000204', '1860000000000000201', '删除', 2, 'mes:mes_xsl_downtime_record:delete', '1', '1', 0, 'admin', NOW()),
('1860000000000000205', '1860000000000000201', '批量删除', 2, 'mes:mes_xsl_downtime_record:deleteBatch', '1', '1', 0, 'admin', NOW()),
('1860000000000000206', '1860000000000000201', '导出', 2, 'mes:mes_xsl_downtime_record:exportXls', '1', '1', 0, 'admin', NOW()),
('1860000000000000207', '1860000000000000201', '导入', 2, 'mes:mes_xsl_downtime_record:importExcel', '1', '1', 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`),
`status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`);
INSERT INTO `sys_role_permission`(`id`, `role_id`, `permission_id`, `operate_date`, `operate_ip`)
SELECT REPLACE(UUID(), '-', ''), r.`id`, p.`id`, 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 (
'1860000000000000201',
'1860000000000000202', '1860000000000000203', '1860000000000000204', '1860000000000000205',
'1860000000000000206', '1860000000000000207'
)
AND NOT EXISTS (
SELECT 1 FROM `sys_role_permission` rp
WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
);

View File

@@ -0,0 +1,86 @@
-- MES 胶料小料锁定原因字典 + 建表 + 菜单质量管理下+ 按钮 + 租户 admin 授权可整文件一次执行
-- 权限前缀mes:mes_xsl_rubber_small_lock_reason:*
-- 菜单 ID 1860000000000000208按钮 209-214
-- 修改租户 SET @mes_tenant_id
SET NAMES utf8mb4;
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
SELECT REPLACE(UUID(), '-', ''), 'MES胶料小料锁定类型', 'xslmes_rubber_small_lock_type', '锁定/解锁', 0, 'admin', NOW(), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_rubber_small_lock_type' AND `del_flag` = 0);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '锁定', 'lock', 1, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'lock');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '解锁', 'unlock', 2, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'unlock');
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
SELECT REPLACE(UUID(), '-', ''), 'MES胶料小料锁定条码类型', 'xslmes_rubber_small_lock_barcode_type', '小料/胶料', 0, 'admin', NOW(), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND `del_flag` = 0);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '小料', 'small', 1, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'small');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '胶料', 'rubber', 2, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'rubber');
CREATE TABLE IF NOT EXISTS `mes_xsl_rubber_small_lock_reason` (
`id` varchar(32) NOT NULL COMMENT '主键',
`reason_code` varchar(16) NOT NULL COMMENT '编号租户内从001递增自动生成只读',
`lock_type` varchar(16) NOT NULL COMMENT '类型字典xslmes_rubber_small_lock_typelock锁定unlock解锁',
`barcode_type` varchar(16) NOT NULL COMMENT '条码类型字典xslmes_rubber_small_lock_barcode_typesmall小料rubber胶料',
`tenant_id` int DEFAULT NULL COMMENT '租户',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '部门',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int DEFAULT '0' COMMENT '删除标记0正常1删除',
PRIMARY KEY (`id`),
KEY `idx_mrslr_tenant_code` (`tenant_id`, `reason_code`),
KEY `idx_mrslr_tenant_lock` (`tenant_id`, `lock_type`, `barcode_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES胶料小料锁定原因';
SET @mes_tenant_id = 1002;
SET @mes_quality_pid = IFNULL(
(SELECT `id` FROM `sys_permission` WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` = '质量管理' LIMIT 1),
'1860000000000000162'
);
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
VALUES ('1860000000000000208', @mes_quality_pid, '胶料小料锁定原因', '/xslmes/mesXslRubberSmallLockReason', 'xslmes/mesXslRubberSmallLockReason/MesXslRubberSmallLockReasonList', 'MesXslRubberSmallLockReasonList', 1, NULL, '1', 6, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `url` = VALUES(`url`), `component` = VALUES(`component`), `component_name` = VALUES(`component_name`),
`menu_type` = VALUES(`menu_type`), `sort_no` = VALUES(`sort_no`), `is_route` = VALUES(`is_route`), `is_leaf` = 0, `hidden` = 0, `status` = '1', `del_flag` = 0, `keep_alive` = VALUES(`keep_alive`);
UPDATE `sys_permission` SET `icon` = 'ant-design:lock-outlined' WHERE `id` = '1860000000000000208' AND `del_flag` = 0;
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `is_leaf`, `status`, `del_flag`, `create_by`, `create_time`) VALUES
('1860000000000000209', '1860000000000000208', '新增', 2, 'mes:mes_xsl_rubber_small_lock_reason:add', '1', 1, '1', 0, 'admin', NOW()),
('1860000000000000210', '1860000000000000208', '编辑', 2, 'mes:mes_xsl_rubber_small_lock_reason:edit', '1', 1, '1', 0, 'admin', NOW()),
('1860000000000000211', '1860000000000000208', '删除', 2, 'mes:mes_xsl_rubber_small_lock_reason:delete', '1', 1, '1', 0, 'admin', NOW()),
('1860000000000000212', '1860000000000000208', '批量删除', 2, 'mes:mes_xsl_rubber_small_lock_reason:deleteBatch', '1', 1, '1', 0, 'admin', NOW()),
('1860000000000000213', '1860000000000000208', '导出', 2, 'mes:mes_xsl_rubber_small_lock_reason:exportXls', '1', 1, '1', 0, 'admin', NOW()),
('1860000000000000214', '1860000000000000208', '导入', 2, 'mes:mes_xsl_rubber_small_lock_reason:importExcel', '1', 1, '1', 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`),
`is_leaf` = 1, `status` = '1', `del_flag` = 0;
INSERT INTO `sys_role_permission`(`id`, `role_id`, `permission_id`, `operate_date`, `operate_ip`)
SELECT REPLACE(UUID(), '-', ''), r.`id`, p.`id`, 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 (
'1860000000000000208',
'1860000000000000209', '1860000000000000210', '1860000000000000211', '1860000000000000212',
'1860000000000000213', '1860000000000000214'
)
AND NOT EXISTS (
SELECT 1 FROM `sys_role_permission` rp
WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
);

View File

@@ -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");
// 打印模板免密接口(供桌面端调用)

View File

@@ -521,3 +521,141 @@ 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
-- author:jiangxh---date:20250602--for: 【MES】停机记录建表+菜单+CRUD+列表录入维修结果弹窗 ---
jeecg-boot/db/mes-xsl-downtime-record-menu-permission.sql
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_117__mes_xsl_downtime_record.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslDowntimeRecord.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/vo/MesXslDowntimeRecordMaintenanceDTO.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslDowntimeRecordMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslDowntimeRecordService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslDowntimeRecordServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDowntimeRecordController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslInspectMaintainItemController.java
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeRecordModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeRecordMaintenanceModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeTypeSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslInspectMaintainItemSelectModal.vue
-- author:jiangxh---date:20250602--for: 【MES】停机记录弹窗日期时间选择器下拉被裁剪导致点击无反应 ---
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.data.ts
-- author:jiangxh---date:20250602--for: 【MES】停机记录维修项目按设备部位筛选点检保养项目均含小部位 ---
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslInspectMaintainItemSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeRecordMaintenanceModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.data.ts
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslDowntimeRecordServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslInspectMaintainItemController.java
-- author:jiangxh---date:20250602--for: 【MES】设备台账列表所属工序查询改为工序选择器 ---
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedger.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedgerList.vue
-- author:jiangxh---date:20250602--for: 【MES】设备管理各功能列表查询与表单一致改为选择器 ---
jeecgboot-vue3/src/views/xslmes/components/MesSearchPickerInput.vue
jeecgboot-vue3/src/views/xslmes/utils/mesSearchPickerUtil.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentCategory/MesXslEquipmentCategory.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentCategory/MesXslEquipmentCategoryList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentType/MesXslEquipmentType.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentType/MesXslEquipmentTypeList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentPart/MesXslEquipmentPart.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentPart/MesXslEquipmentPartList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentSubPart/MesXslEquipmentSubPart.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentSubPart/MesXslEquipmentSubPartList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedger.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedgerList.vue
jeecgboot-vue3/src/views/xslmes/mesXslSparePart/MesXslSparePart.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslSparePart/MesXslSparePartList.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeMainType/MesXslDowntimeMainType.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeMainType/MesXslDowntimeMainTypeList.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeType/MesXslDowntimeType.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeType/MesXslDowntimeTypeList.vue
jeecgboot-vue3/src/views/xslmes/mesXslInspectMaintainItem/MesXslInspectMaintainItem.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslInspectMaintainItem/MesXslInspectMaintainItemList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/MesXslEquipInspectConfig.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/MesXslEquipInspectConfigList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectRecord/MesXslEquipInspectRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectRecord/MesXslEquipInspectRecordList.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList.vue
-- author:jiangxh---date:20250602--for: 【MES】胶料小料锁定原因质量管理菜单、编号001自增、类型与条码类型字典 ---
jeecg-boot/db/mes-xsl-rubber-small-lock-reason-menu-permission.sql
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_118__mes_xsl_rubber_small_lock_reason.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslRubberSmallLockReason.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslRubberSmallLockReasonMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRubberSmallLockReasonService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRubberSmallLockReasonServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberSmallLockReasonController.java
jeecgboot-vue3/src/views/xslmes/mesXslRubberSmallLockReason/MesXslRubberSmallLockReason.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslRubberSmallLockReason/MesXslRubberSmallLockReason.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslRubberSmallLockReason/MesXslRubberSmallLockReasonList.vue
jeecgboot-vue3/src/views/xslmes/mesXslRubberSmallLockReason/components/MesXslRubberSmallLockReasonModal.vue

View File

@@ -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) {

View File

@@ -0,0 +1,220 @@
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 java.util.Date;
import lombok.extern.slf4j.Slf4j;
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.constant.CommonConstant;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeRecord;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeType;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeRecordService;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeTypeService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentLedgerService;
import org.jeecg.modules.xslmes.vo.MesXslDowntimeRecordMaintenanceDTO;
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/mesXslDowntimeRecord")
@Slf4j
public class MesXslDowntimeRecordController extends JeecgController<MesXslDowntimeRecord, IMesXslDowntimeRecordService> {
@Autowired
private IMesXslDowntimeRecordService mesXslDowntimeRecordService;
@Autowired
private IMesXslEquipmentLedgerService mesXslEquipmentLedgerService;
@Autowired
private IMesXslDowntimeTypeService mesXslDowntimeTypeService;
@Operation(summary = "MES停机记录-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslDowntimeRecord>> queryPageList(
MesXslDowntimeRecord model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslDowntimeRecord> queryWrapper = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录列表默认按开始时间倒序-----------
queryWrapper.orderByDesc("start_time").orderByDesc("create_time");
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录列表默认按开始时间倒序-----------
Page<MesXslDowntimeRecord> page = new Page<>(pageNo, pageSize);
IPage<MesXslDowntimeRecord> pageList = mesXslDowntimeRecordService.page(page, queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "MES停机记录-添加")
@Operation(summary = "MES停机记录-添加")
@RequiresPermissions("mes:mes_xsl_downtime_record:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody MesXslDowntimeRecord model) {
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验-----------
String err = validateForSave(model);
if (err != null) {
return Result.error(err);
}
clearMaintenanceFieldsForFormSave(model);
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验-----------
mesXslDowntimeRecordService.save(model);
return Result.OK("添加成功!");
}
@AutoLog(value = "MES停机记录-编辑")
@Operation(summary = "MES停机记录-编辑")
@RequiresPermissions("mes:mes_xsl_downtime_record:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesXslDowntimeRecord model) {
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验-----------
String err = validateForSave(model);
if (err != null) {
return Result.error(err);
}
MesXslDowntimeRecord old = mesXslDowntimeRecordService.getById(model.getId());
if (old == null) {
return Result.error("未找到对应数据");
}
preserveMaintenanceFields(model, old);
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验-----------
mesXslDowntimeRecordService.updateById(model);
return Result.OK("编辑成功!");
}
@AutoLog(value = "MES停机记录-录入维修结果")
@Operation(summary = "MES停机记录-录入维修结果")
@RequiresPermissions("mes:mes_xsl_downtime_record:edit")
@PostMapping(value = "/saveMaintenanceResult")
public Result<String> saveMaintenanceResult(@RequestBody MesXslDowntimeRecordMaintenanceDTO dto) {
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录录入维修结果-----------
mesXslDowntimeRecordService.saveMaintenanceResult(dto);
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录录入维修结果-----------
return Result.OK("录入成功!");
}
@AutoLog(value = "MES停机记录-删除")
@Operation(summary = "MES停机记录-通过id删除")
@RequiresPermissions("mes:mes_xsl_downtime_record:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
mesXslDowntimeRecordService.removeById(id);
return Result.OK("删除成功!");
}
@AutoLog(value = "MES停机记录-批量删除")
@Operation(summary = "MES停机记录-批量删除")
@RequiresPermissions("mes:mes_xsl_downtime_record:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
mesXslDowntimeRecordService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
@Operation(summary = "MES停机记录-通过id查询")
@GetMapping(value = "/queryById")
public Result<MesXslDowntimeRecord> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslDowntimeRecord entity = mesXslDowntimeRecordService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@RequiresPermissions("mes:mes_xsl_downtime_record:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslDowntimeRecord model) {
return super.exportXls(request, model, MesXslDowntimeRecord.class, "MES停机记录");
}
@RequiresPermissions("mes:mes_xsl_downtime_record:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, MesXslDowntimeRecord.class);
}
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验、关联设备与停机类型回填-----------
private String validateForSave(MesXslDowntimeRecord model) {
if (model == null) {
return "参数无效";
}
if (oConvertUtils.isEmpty(model.getEquipmentLedgerId())) {
return "请选择机台";
}
MesXslEquipmentLedger ledger = mesXslEquipmentLedgerService.getById(model.getEquipmentLedgerId());
if (ledger == null
|| (ledger.getDelFlag() != null && CommonConstant.DEL_FLAG_1.equals(ledger.getDelFlag()))) {
return "所选设备不存在";
}
model.setEquipmentCode(ledger.getEquipmentCode());
model.setEquipmentName(ledger.getEquipmentName());
if (oConvertUtils.isEmpty(model.getDowntimeTypeId())) {
return "请选择停机原因";
}
MesXslDowntimeType downtimeType = mesXslDowntimeTypeService.getById(model.getDowntimeTypeId());
if (downtimeType == null
|| (downtimeType.getDelFlag() != null && CommonConstant.DEL_FLAG_1.equals(downtimeType.getDelFlag()))) {
return "所选停机类型不存在";
}
if (!"0".equals(downtimeType.getStatus())) {
return "所选停机类型未启用";
}
model.setDowntimeTypeName(downtimeType.getDowntimeType());
if (model.getStartTime() == null) {
return "开始时间不能为空";
}
Date endTime = model.getEndTime();
if (endTime != null && endTime.before(model.getStartTime())) {
return "结束时间不能早于开始时间";
}
if (oConvertUtils.isEmpty(model.getMaintenanceFilledFlag())) {
model.setMaintenanceFilledFlag("0");
}
return null;
}
private void clearMaintenanceFieldsForFormSave(MesXslDowntimeRecord model) {
model.setEquipmentPartId(null);
model.setEquipmentPartName(null);
model.setInspectMaintainItemId(null);
model.setInspectMaintainItemName(null);
model.setMaintenanceResult(null);
model.setMaintainerUserId(null);
model.setMaintainerUsername(null);
model.setMaintainerRealname(null);
model.setMaintenanceFilledFlag("0");
}
private void preserveMaintenanceFields(MesXslDowntimeRecord model, MesXslDowntimeRecord old) {
model.setEquipmentPartId(old.getEquipmentPartId());
model.setEquipmentPartName(old.getEquipmentPartName());
model.setInspectMaintainItemId(old.getInspectMaintainItemId());
model.setInspectMaintainItemName(old.getInspectMaintainItemName());
model.setMaintenanceResult(old.getMaintenanceResult());
model.setMaintainerUserId(old.getMaintainerUserId());
model.setMaintainerUsername(old.getMaintainerUsername());
model.setMaintainerRealname(old.getMaintainerRealname());
model.setMaintenanceFilledFlag(
oConvertUtils.isEmpty(old.getMaintenanceFilledFlag()) ? "0" : old.getMaintenanceFilledFlag());
}
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录保存前校验、关联设备与停机类型回填-----------
}

View File

@@ -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【密炼物料皮重策略】维护人自动回填当前登录用户-----------
}

View File

@@ -0,0 +1,212 @@
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.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
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.exception.JeecgBootException;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRubberSmallLockReason;
import org.jeecg.modules.xslmes.service.IMesXslRubberSmallLockReasonService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
/**
* MES 胶料小料锁定原因
*/
@Tag(name = "MES胶料小料锁定原因")
@RestController
@RequestMapping("/xslmes/mesXslRubberSmallLockReason")
@Slf4j
public class MesXslRubberSmallLockReasonController
extends JeecgController<MesXslRubberSmallLockReason, IMesXslRubberSmallLockReasonService> {
@Autowired
private IMesXslRubberSmallLockReasonService mesXslRubberSmallLockReasonService;
@Operation(summary = "MES胶料小料锁定原因-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberSmallLockReason>> queryPageList(
MesXslRubberSmallLockReason model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslRubberSmallLockReason> queryWrapper =
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
Page<MesXslRubberSmallLockReason> page = new Page<>(pageNo, pageSize);
IPage<MesXslRubberSmallLockReason> pageList = mesXslRubberSmallLockReasonService.page(page, queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "MES胶料小料锁定原因-添加")
@Operation(summary = "MES胶料小料锁定原因-添加")
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody MesXslRubberSmallLockReason model) {
//update-begin---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因新增校验与自动编号-----------
if (oConvertUtils.isEmpty(model.getLockType())) {
return Result.error("类型不能为空");
}
if (oConvertUtils.isEmpty(model.getBarcodeType())) {
return Result.error("条码类型不能为空");
}
model.setReasonCode(null);
try {
mesXslRubberSmallLockReasonService.save(model);
} catch (JeecgBootException e) {
return Result.error(e.getMessage());
}
//update-end---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因新增校验与自动编号-----------
return Result.OK("添加成功!");
}
@AutoLog(value = "MES胶料小料锁定原因-编辑")
@Operation(summary = "MES胶料小料锁定原因-编辑")
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesXslRubberSmallLockReason model) {
//update-begin---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因编辑仅改类型与条码类型、编号只读-----------
if (oConvertUtils.isEmpty(model.getId())) {
return Result.error("缺少主键");
}
if (oConvertUtils.isEmpty(model.getLockType())) {
return Result.error("类型不能为空");
}
if (oConvertUtils.isEmpty(model.getBarcodeType())) {
return Result.error("条码类型不能为空");
}
MesXslRubberSmallLockReason old = mesXslRubberSmallLockReasonService.getById(model.getId());
if (old == null) {
return Result.error("未找到对应数据");
}
old.setLockType(model.getLockType());
old.setBarcodeType(model.getBarcodeType());
try {
mesXslRubberSmallLockReasonService.updateById(old);
} catch (JeecgBootException e) {
return Result.error(e.getMessage());
}
//update-end---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因编辑仅改类型与条码类型、编号只读-----------
return Result.OK("编辑成功!");
}
@AutoLog(value = "MES胶料小料锁定原因-删除")
@Operation(summary = "MES胶料小料锁定原因-通过id删除")
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
mesXslRubberSmallLockReasonService.removeById(id);
return Result.OK("删除成功!");
}
@AutoLog(value = "MES胶料小料锁定原因-批量删除")
@Operation(summary = "MES胶料小料锁定原因-批量删除")
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
mesXslRubberSmallLockReasonService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
@Operation(summary = "MES胶料小料锁定原因-通过id查询")
@GetMapping(value = "/queryById")
public Result<MesXslRubberSmallLockReason> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslRubberSmallLockReason entity = mesXslRubberSmallLockReasonService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@Operation(summary = "预览下一编号001起")
@GetMapping(value = "/nextReasonCode")
public Result<String> nextReasonCode() {
MesXslRubberSmallLockReason ctx = new MesXslRubberSmallLockReason();
return Result.OK(mesXslRubberSmallLockReasonService.generateNextReasonCode(ctx));
}
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslRubberSmallLockReason model) {
return super.exportXls(request, model, MesXslRubberSmallLockReason.class, "MES胶料小料锁定原因");
}
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
//update-begin---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因导入类型与条码类型必填编号可空则自动生成-----------
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> ent : fileMap.entrySet()) {
MultipartFile file = ent.getValue();
ImportParams params = new ImportParams();
params.setTitleRows(2);
params.setHeadRows(1);
params.setNeedSave(true);
try {
List<MesXslRubberSmallLockReason> list =
ExcelImportUtil.importExcel(file.getInputStream(), MesXslRubberSmallLockReason.class, params);
if (list == null) {
list = List.of();
}
for (int i = 0; i < list.size(); i++) {
MesXslRubberSmallLockReason row = list.get(i);
int rowNo = i + 1;
if (row == null) {
return Result.error("文件导入失败:第 " + rowNo + " 条数据无效(空行)");
}
if (oConvertUtils.isEmpty(row.getLockType())) {
return Result.error("文件导入失败:第 " + rowNo + " 条类型不能为空");
}
if (oConvertUtils.isEmpty(row.getBarcodeType())) {
return Result.error("文件导入失败:第 " + rowNo + " 条条码类型不能为空");
}
if (oConvertUtils.isNotEmpty(row.getReasonCode())) {
row.setReasonCode(row.getReasonCode().trim());
} else {
row.setReasonCode(null);
}
}
for (MesXslRubberSmallLockReason row : list) {
try {
mesXslRubberSmallLockReasonService.save(row);
} catch (JeecgBootException e) {
return Result.error("文件导入失败:" + e.getMessage());
}
}
log.info("胶料小料锁定原因Excel导入完成行数={}", list.size());
return Result.ok("文件导入成功!数据行数:" + list.size());
} catch (Exception e) {
String msg = e.getMessage();
log.error(msg, e);
return Result.error("文件导入失败:" + e.getMessage());
} finally {
try {
file.getInputStream().close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
//update-end---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因导入类型与条码类型必填编号可空则自动生成-----------
return Result.error("文件导入失败!");
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,106 @@
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.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 停机记录(表 mes_xsl_downtime_record
*/
@Data
@TableName("mes_xsl_downtime_record")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES停机记录")
public class MesXslDowntimeRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Schema(description = "设备台账主键 mes_xsl_equipment_ledger.id")
private String equipmentLedgerId;
@Excel(name = "设备编号", width = 18)
@Schema(description = "设备编号冗余")
private String equipmentCode;
@Excel(name = "机台", width = 22)
@Schema(description = "设备名称冗余")
private String equipmentName;
@Schema(description = "停机类型主键 mes_xsl_downtime_type.id")
private String downtimeTypeId;
@Excel(name = "停机原因", width = 24)
@Schema(description = "停机类型名称冗余")
private String downtimeTypeName;
@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")
@Schema(description = "开始时间")
private Date startTime;
@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")
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "设备部位主键 mes_xsl_equipment_part.id")
private String equipmentPartId;
@Excel(name = "设备部位", width = 18)
@Schema(description = "设备部位名称冗余")
private String equipmentPartName;
@Schema(description = "点检及保养项目主键 mes_xsl_inspect_maintain_item.id")
private String inspectMaintainItemId;
@Excel(name = "维修项目", width = 22)
@Schema(description = "点检项目名称冗余")
private String inspectMaintainItemName;
@Excel(name = "维修结果", width = 28)
@Schema(description = "维修结果")
private String maintenanceResult;
@Schema(description = "维修人用户ID")
private String maintainerUserId;
@Schema(description = "维修人账号")
private String maintainerUsername;
@Excel(name = "维修人", width = 12)
@Schema(description = "维修人姓名")
private String maintainerRealname;
@Excel(name = "是否已录入维修", width = 14, dicCode = "yn")
@Dict(dicCode = "yn")
@Schema(description = "是否已录入维修结果字典yn1是0否")
private String maintenanceFilledFlag;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
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 Integer delFlag;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,67 @@
package org.jeecg.modules.xslmes.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
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.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_rubber_small_lock_reason")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES胶料小料锁定原因")
public class MesXslRubberSmallLockReason implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "编号", width = 12)
@Schema(description = "编号租户内从001递增自动生成只读")
private String reasonCode;
@Excel(name = "类型", width = 12, dicCode = "xslmes_rubber_small_lock_type")
@Dict(dicCode = "xslmes_rubber_small_lock_type")
@Schema(description = "类型字典lock锁定 unlock解锁")
private String lockType;
@Excel(name = "条码类型", width = 12, dicCode = "xslmes_rubber_small_lock_barcode_type")
@Dict(dicCode = "xslmes_rubber_small_lock_barcode_type")
@Schema(description = "条码类型字典small小料 rubber胶料")
private String barcodeType;
private Integer tenantId;
private String sysOrgCode;
@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;
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;
@TableLogic
@Schema(description = "删除状态0正常 1已删除")
private Integer delFlag;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeRecord;
/**
* MES 停机记录
*/
public interface MesXslDowntimeRecordMapper extends BaseMapper<MesXslDowntimeRecord> {}

View File

@@ -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> {}

View File

@@ -0,0 +1,22 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.jeecg.modules.xslmes.entity.MesXslRubberSmallLockReason;
/**
* MES 胶料小料锁定原因 Mapper
*/
@Mapper
public interface MesXslRubberSmallLockReasonMapper extends BaseMapper<MesXslRubberSmallLockReason> {
//update-begin---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因租户内最大三位数字编号-----------
@Select(
"SELECT IFNULL(MAX(CAST(reason_code AS UNSIGNED)), 0) FROM mes_xsl_rubber_small_lock_reason "
+ "WHERE del_flag = 0 AND reason_code REGEXP '^[0-9]+$' "
+ "AND (#{tenantId} IS NULL OR tenant_id = #{tenantId})")
Integer selectMaxNumericReasonCode(@Param("tenantId") Integer tenantId);
//update-end---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因租户内最大三位数字编号-----------
}

View File

@@ -0,0 +1,16 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeRecord;
import org.jeecg.modules.xslmes.vo.MesXslDowntimeRecordMaintenanceDTO;
/**
* MES 停机记录
*/
public interface IMesXslDowntimeRecordService extends IService<MesXslDowntimeRecord> {
/**
* 录入维修结果
*/
void saveMaintenanceResult(MesXslDowntimeRecordMaintenanceDTO dto);
}

View File

@@ -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【密炼物料皮重策略】保存前校验生效日期重叠含物料规格-----------
}

View File

@@ -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 -&gt; 累计货物皮重;查不到的 billNo 不会出现在 map 中
*/
Map<String, BigDecimal> sumCargoTareByBillNos(Collection<String> billNos);
/**
* 给磅单列表/详情填充由原料入场记录衍生的 transient 字段(已入场重量、货物皮重)。
*/
void fillWeightRecordDerivedFields(List<MesXslWeightRecord> records);
/**
* 结存入库并汇总原材料库存。
* <p>

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.xslmes.entity.MesXslRubberSmallLockReason;
public interface IMesXslRubberSmallLockReasonService extends IService<MesXslRubberSmallLockReason> {
/** 生成下一编号001 起,三位数字,租户维度) */
String generateNextReasonCode(MesXslRubberSmallLockReason context);
}

View File

@@ -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) {

View File

@@ -0,0 +1,92 @@
package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeRecord;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentPart;
import org.jeecg.modules.xslmes.entity.MesXslInspectMaintainItem;
import org.jeecg.modules.xslmes.mapper.MesXslDowntimeRecordMapper;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeRecordService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentPartService;
import org.jeecg.modules.xslmes.service.IMesXslInspectMaintainItemService;
import org.jeecg.modules.xslmes.vo.MesXslDowntimeRecordMaintenanceDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* MES 停机记录
*/
@Service
public class MesXslDowntimeRecordServiceImpl extends ServiceImpl<MesXslDowntimeRecordMapper, MesXslDowntimeRecord>
implements IMesXslDowntimeRecordService {
@Autowired
private IMesXslEquipmentPartService mesXslEquipmentPartService;
@Autowired
private IMesXslInspectMaintainItemService mesXslInspectMaintainItemService;
//update-begin---author:jiangxh ---date:20250602 for【MES】停机记录录入维修结果-----------
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMaintenanceResult(MesXslDowntimeRecordMaintenanceDTO dto) {
if (dto == null || oConvertUtils.isEmpty(dto.getId())) {
throw new JeecgBootException("停机记录不存在");
}
MesXslDowntimeRecord record = this.getById(dto.getId());
if (record == null
|| (record.getDelFlag() != null && CommonConstant.DEL_FLAG_1.equals(record.getDelFlag()))) {
throw new JeecgBootException("停机记录不存在");
}
if (oConvertUtils.isEmpty(dto.getEquipmentPartId())) {
throw new JeecgBootException("请选择设备部位");
}
if (oConvertUtils.isEmpty(dto.getInspectMaintainItemId())) {
throw new JeecgBootException("请选择维修项目");
}
if (oConvertUtils.isEmpty(dto.getMaintenanceResult()) || dto.getMaintenanceResult().trim().isEmpty()) {
throw new JeecgBootException("维修结果不能为空");
}
if (oConvertUtils.isEmpty(dto.getMaintainerUserId())) {
throw new JeecgBootException("请选择维修人");
}
MesXslEquipmentPart part = mesXslEquipmentPartService.getById(dto.getEquipmentPartId());
if (part == null
|| (part.getDelFlag() != null && CommonConstant.DEL_FLAG_1.equals(part.getDelFlag()))) {
throw new JeecgBootException("设备部位不存在");
}
MesXslInspectMaintainItem item = mesXslInspectMaintainItemService.getById(dto.getInspectMaintainItemId());
if (item == null
|| (item.getDelFlag() != null && CommonConstant.DEL_FLAG_1.equals(item.getDelFlag()))) {
throw new JeecgBootException("维修项目不存在");
}
if (!dto.getEquipmentPartId().equals(item.getEquipmentPartId())) {
throw new JeecgBootException("维修项目与所选设备部位不匹配");
}
record.setEquipmentPartId(part.getId());
record.setEquipmentPartName(part.getPartName());
record.setInspectMaintainItemId(item.getId());
record.setInspectMaintainItemName(item.getItemName());
record.setMaintenanceResult(dto.getMaintenanceResult().trim());
record.setMaintainerUserId(dto.getMaintainerUserId());
record.setMaintainerUsername(trimToNull(dto.getMaintainerUsername()));
record.setMaintainerRealname(trimToNull(dto.getMaintainerRealname()));
record.setMaintenanceFilledFlag("1");
this.updateById(record);
}
private static String trimToNull(String value) {
if (value == null) {
return null;
}
String v = value.trim();
return v.isEmpty() ? null : v;
}
//update-end---author:jiangxh ---date:20250602 for【MES】停机记录录入维修结果-----------
}

View File

@@ -773,7 +773,7 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl<MesXslFormulaSpecM
//update-begin---author:cursor ---date:20260601 for【XSLMES-20260601-A62】生成混炼示方改为同步B/F段胶至胶料信息-----------
/**
* 生成混炼示方时,将 B 段/F 段胶料同步写入「胶料信息」(mes_material)
* 胶料类别取配合示方所选胶料代号」对应胶料的类别;不再写入密炼物料,也不再维护比重
* 除 ERP 编号留空、编码/名称为示方编号外,其余字段按配合示方所选胶料信息赋值
*/
private void syncGeneratedRubberMixerMaterial(
MesXslFormulaMixingGenerateRowVO row,

View File

@@ -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【密炼物料皮重策略】保存前校验与冗余回填-----------
}

View File

@@ -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) {

View File

@@ -0,0 +1,62 @@
package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.common.MesXslTenantUtils;
import org.jeecg.modules.xslmes.entity.MesXslRubberSmallLockReason;
import org.jeecg.modules.xslmes.mapper.MesXslRubberSmallLockReasonMapper;
import org.jeecg.modules.xslmes.service.IMesXslRubberSmallLockReasonService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MesXslRubberSmallLockReasonServiceImpl
extends ServiceImpl<MesXslRubberSmallLockReasonMapper, MesXslRubberSmallLockReason>
implements IMesXslRubberSmallLockReasonService {
//update-begin---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因编号001递增、类型与条码类型必填-----------
@Override
public String generateNextReasonCode(MesXslRubberSmallLockReason context) {
Integer tenantId = MesXslTenantUtils.resolveTenantId(context != null ? context.getTenantId() : null);
Integer max = baseMapper.selectMaxNumericReasonCode(tenantId);
int next = (max == null ? 0 : max) + 1;
if (next > 999) {
throw new IllegalStateException("编号已超过999请联系管理员");
}
return String.format("%03d", next);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean save(MesXslRubberSmallLockReason entity) {
validateRequiredFields(entity);
if (oConvertUtils.isEmpty(entity.getReasonCode())) {
entity.setReasonCode(generateNextReasonCode(entity));
}
return super.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateById(MesXslRubberSmallLockReason entity) {
if (oConvertUtils.isEmpty(entity.getId())) {
throw new JeecgBootException("缺少主键");
}
validateRequiredFields(entity);
return super.updateById(entity);
}
private void validateRequiredFields(MesXslRubberSmallLockReason entity) {
if (entity == null) {
throw new JeecgBootException("数据不能为空");
}
if (oConvertUtils.isEmpty(entity.getLockType())) {
throw new JeecgBootException("类型不能为空");
}
if (oConvertUtils.isEmpty(entity.getBarcodeType())) {
throw new JeecgBootException("条码类型不能为空");
}
}
//update-end---author:jiangxh ---date:20250602 for【MES】胶料小料锁定原因编号001递增、类型与条码类型必填-----------
}

View File

@@ -0,0 +1,33 @@
package org.jeecg.modules.xslmes.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 停机记录-录入维修结果入参
*/
@Data
@Schema(description = "停机记录-录入维修结果")
public class MesXslDowntimeRecordMaintenanceDTO {
@Schema(description = "停机记录主键", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "设备部位主键")
private String equipmentPartId;
@Schema(description = "点检及保养项目主键")
private String inspectMaintainItemId;
@Schema(description = "维修结果")
private String maintenanceResult;
@Schema(description = "维修人用户ID")
private String maintainerUserId;
@Schema(description = "维修人账号")
private String maintainerUsername;
@Schema(description = "维修人姓名")
private String maintainerRealname;
}

View File

@@ -540,3 +540,52 @@ 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
-- author:jiangxh---date:20250602--for: 【MES】停机记录建表+菜单+CRUD+列表录入维修结果弹窗 ---
jeecg-boot/db/mes-xsl-downtime-record-menu-permission.sql
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_117__mes_xsl_downtime_record.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslDowntimeRecord.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/vo/MesXslDowntimeRecordMaintenanceDTO.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslDowntimeRecordMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslDowntimeRecordService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslDowntimeRecordServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDowntimeRecordController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslInspectMaintainItemController.java
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeRecordModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeRecordMaintenanceModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslDowntimeTypeSelectModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslDowntimeRecord/components/MesXslInspectMaintainItemSelectModal.vue

View File

@@ -0,0 +1,91 @@
-- MES 停机记录建表 + 菜单 + 按钮 + 租户 admin 授权
-- 权限前缀mes:mes_xsl_downtime_record:*
-- 父菜单设备管理依赖设备台账停机类型
-- 独立脚本jeecg-boot/db/mes-xsl-downtime-record-menu-permission.sql
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `mes_xsl_downtime_record` (
`id` varchar(32) NOT NULL COMMENT '主键',
`equipment_ledger_id` varchar(32) NOT NULL COMMENT '设备台账主键 mes_xsl_equipment_ledger.id',
`equipment_code` varchar(500) DEFAULT NULL COMMENT '设备编号冗余',
`equipment_name` varchar(500) DEFAULT NULL COMMENT '设备名称冗余',
`downtime_type_id` varchar(32) NOT NULL COMMENT '停机类型主键 mes_xsl_downtime_type.id',
`downtime_type_name` varchar(500) DEFAULT NULL COMMENT '停机类型名称冗余',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`equipment_part_id` varchar(32) DEFAULT NULL COMMENT '设备部位主键 mes_xsl_equipment_part.id',
`equipment_part_name` varchar(500) DEFAULT NULL COMMENT '设备部位名称冗余',
`inspect_maintain_item_id` varchar(32) DEFAULT NULL COMMENT '点检及保养项目主键 mes_xsl_inspect_maintain_item.id',
`inspect_maintain_item_name` varchar(500) DEFAULT NULL COMMENT '点检项目名称冗余',
`maintenance_result` varchar(500) DEFAULT NULL COMMENT '维修结果',
`maintainer_user_id` varchar(32) DEFAULT NULL COMMENT '维修人用户ID',
`maintainer_username` varchar(500) DEFAULT NULL COMMENT '维修人账号',
`maintainer_realname` varchar(500) DEFAULT NULL COMMENT '维修人姓名',
`maintenance_filled_flag` varchar(1) DEFAULT '0' COMMENT '是否已录入维修结果字典yn1是0否',
`tenant_id` int DEFAULT NULL COMMENT '租户',
`sys_org_code` varchar(500) DEFAULT NULL COMMENT '部门',
`create_by` varchar(500) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(500) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int DEFAULT '0' COMMENT '删除标记0正常1删除',
PRIMARY KEY (`id`),
KEY `idx_mdr_equipment` (`equipment_ledger_id`),
KEY `idx_mdr_downtime_type` (`downtime_type_id`),
KEY `idx_mdr_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES停机记录';
SET @mes_tenant_id = 1002;
SET @mes_equip_pid = (
SELECT `id` FROM `sys_permission`
WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` = '设备管理'
LIMIT 1
);
SET @mes_equip_pid = IFNULL(@mes_equip_pid, '1860000000000000133');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
SELECT '1860000000000000201', @mes_equip_pid, '停机记录', '/xslmes/mesXslDowntimeRecord', 'xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList', 'MesXslDowntimeRecordList', 1, NULL, '1', 12, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000201');
UPDATE `sys_permission` SET
`parent_id` = @mes_equip_pid, `name` = '停机记录', `url` = '/xslmes/mesXslDowntimeRecord',
`component` = 'xslmes/mesXslDowntimeRecord/MesXslDowntimeRecordList', `component_name` = 'MesXslDowntimeRecordList',
`menu_type` = 1, `sort_no` = 12, `is_route` = 1, `is_leaf` = 0, `hidden` = 0, `status` = '1', `del_flag` = 0,
`keep_alive` = 1, `internal_or_external` = 0, `icon` = 'ant-design:history-outlined'
WHERE `id` = '1860000000000000201';
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000202', '1860000000000000201', '新增', 2, 'mes:mes_xsl_downtime_record:add', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000202');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000203', '1860000000000000201', '编辑', 2, 'mes:mes_xsl_downtime_record:edit', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000203');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000204', '1860000000000000201', '删除', 2, 'mes:mes_xsl_downtime_record:delete', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000204');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000205', '1860000000000000201', '批量删除', 2, 'mes:mes_xsl_downtime_record:deleteBatch', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000205');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000206', '1860000000000000201', '导出', 2, 'mes:mes_xsl_downtime_record:exportXls', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000206');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`)
SELECT '1860000000000000207', '1860000000000000201', '导入', 2, 'mes:mes_xsl_downtime_record:importExcel', '1', '1', 0, 'admin', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1860000000000000207');
INSERT INTO `sys_role_permission`(`id`, `role_id`, `permission_id`, `operate_date`, `operate_ip`)
SELECT REPLACE(UUID(), '-', ''), r.`id`, p.`id`, 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 (
'1860000000000000201',
'1860000000000000202', '1860000000000000203', '1860000000000000204', '1860000000000000205',
'1860000000000000206', '1860000000000000207'
)
AND NOT EXISTS (
SELECT 1 FROM `sys_role_permission` rp
WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
);

View File

@@ -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
);

View File

@@ -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 '包装物重量';

View File

@@ -0,0 +1,82 @@
-- MES 胶料小料锁定原因字典 + 建表 + 菜单质量管理下+ 按钮 + 租户 admin 授权
-- 权限前缀mes:mes_xsl_rubber_small_lock_reason:*
SET NAMES utf8mb4;
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
SELECT REPLACE(UUID(), '-', ''), 'MES胶料小料锁定类型', 'xslmes_rubber_small_lock_type', '锁定/解锁', 0, 'admin', NOW(), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_rubber_small_lock_type' AND `del_flag` = 0);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '锁定', 'lock', 1, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'lock');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '解锁', 'unlock', 2, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'unlock');
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
SELECT REPLACE(UUID(), '-', ''), 'MES胶料小料锁定条码类型', 'xslmes_rubber_small_lock_barcode_type', '小料/胶料', 0, 'admin', NOW(), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND `del_flag` = 0);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '小料', 'small', 1, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'small');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `sort_order`, `status`, `create_by`, `create_time`)
SELECT REPLACE(UUID(), '-', ''), d.id, '胶料', 'rubber', 2, 1, 'admin', NOW() FROM `sys_dict` d
WHERE d.`dict_code` = 'xslmes_rubber_small_lock_barcode_type' AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'rubber');
CREATE TABLE IF NOT EXISTS `mes_xsl_rubber_small_lock_reason` (
`id` varchar(32) NOT NULL COMMENT '主键',
`reason_code` varchar(16) NOT NULL COMMENT '编号租户内从001递增自动生成只读',
`lock_type` varchar(16) NOT NULL COMMENT '类型字典xslmes_rubber_small_lock_typelock锁定unlock解锁',
`barcode_type` varchar(16) NOT NULL COMMENT '条码类型字典xslmes_rubber_small_lock_barcode_typesmall小料rubber胶料',
`tenant_id` int DEFAULT NULL COMMENT '租户',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '部门',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int DEFAULT '0' COMMENT '删除标记0正常1删除',
PRIMARY KEY (`id`),
KEY `idx_mrslr_tenant_code` (`tenant_id`, `reason_code`),
KEY `idx_mrslr_tenant_lock` (`tenant_id`, `lock_type`, `barcode_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES胶料小料锁定原因';
SET @mes_tenant_id = 1002;
SET @mes_quality_pid = IFNULL(
(SELECT `id` FROM `sys_permission` WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` = '质量管理' LIMIT 1),
'1860000000000000162'
);
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
VALUES ('1860000000000000208', @mes_quality_pid, '胶料小料锁定原因', '/xslmes/mesXslRubberSmallLockReason', 'xslmes/mesXslRubberSmallLockReason/MesXslRubberSmallLockReasonList', 'MesXslRubberSmallLockReasonList', 1, NULL, '1', 6, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `url` = VALUES(`url`), `component` = VALUES(`component`),
`component_name` = VALUES(`component_name`), `sort_no` = VALUES(`sort_no`), `is_leaf` = 0, `keep_alive` = VALUES(`keep_alive`);
UPDATE `sys_permission` SET `icon` = 'ant-design:lock-outlined' WHERE `id` = '1860000000000000208' AND `del_flag` = 0;
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`) VALUES
('1860000000000000209', '1860000000000000208', '新增', 2, 'mes:mes_xsl_rubber_small_lock_reason:add', '1', '1', 0, 'admin', NOW()),
('1860000000000000210', '1860000000000000208', '编辑', 2, 'mes:mes_xsl_rubber_small_lock_reason:edit', '1', '1', 0, 'admin', NOW()),
('1860000000000000211', '1860000000000000208', '删除', 2, 'mes:mes_xsl_rubber_small_lock_reason:delete', '1', '1', 0, 'admin', NOW()),
('1860000000000000212', '1860000000000000208', '批量删除', 2, 'mes:mes_xsl_rubber_small_lock_reason:deleteBatch', '1', '1', 0, 'admin', NOW()),
('1860000000000000213', '1860000000000000208', '导出', 2, 'mes:mes_xsl_rubber_small_lock_reason:exportXls', '1', '1', 0, 'admin', NOW()),
('1860000000000000214', '1860000000000000208', '导入', 2, 'mes:mes_xsl_rubber_small_lock_reason:importExcel', '1', '1', 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`), `name` = VALUES(`name`);
INSERT INTO `sys_role_permission`(`id`, `role_id`, `permission_id`, `operate_date`, `operate_ip`)
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, 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 (
'1860000000000000208',
'1860000000000000209', '1860000000000000210', '1860000000000000211', '1860000000000000212',
'1860000000000000213', '1860000000000000214'
)
AND NOT EXISTS (
SELECT 1 FROM sys_role_permission rp
WHERE rp.role_id = r.id AND rp.permission_id = p.id
);

View File

@@ -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`;

View File

@@ -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`;

View File

@@ -0,0 +1,18 @@
<template>
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only :placeholder="placeholder" style="flex: 1" />
<a-button type="primary" @click="emit('open')">选择</a-button>
<a-button v-if="showClear" @click="emit('clear')">清除</a-button>
</a-input-group>
</template>
<script lang="ts" setup>
defineProps<{
model: Recordable;
field: string;
placeholder?: string;
showClear?: boolean;
}>();
const emit = defineEmits<{ (e: 'open'): void; (e: 'clear'): void }>();
</script>

View File

@@ -44,7 +44,14 @@ export const columns: BasicColumn[] = [
];
export const searchFormSchema: FormSchema[] = [
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'processOperationId', component: 'Input', show: false },
{
label: '所属工序',
field: 'processOperationName',
component: 'Input',
slot: 'processOperationPicker',
colProps: { span: 6 },
},
{ label: '停机类型', field: 'downtimeType', component: 'Input', colProps: { span: 6 } },
{
label: '是否启用',

View File

@@ -1,6 +1,16 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-processOperationPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择工序"
:show-clear="!!model.processOperationId"
@open="openProcessSelect"
@clear="clearModelFields(model, ['processOperationId', 'processOperationName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_downtime_main_type:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +56,7 @@
</template>
</BasicTable>
<MesXslDowntimeMainTypeModal @register="registerModal" @success="handleSuccess" />
<MesXslProcessOperationSelectModal @register="registerProcessModal" @select="onProcessSelect" />
</div>
</template>
@@ -54,11 +65,17 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslDowntimeMainTypeModal from './components/MesXslDowntimeMainTypeModal.vue';
import MesXslProcessOperationSelectModal from '/@/views/xslmes/mesXslEquipmentCategory/components/MesXslProcessOperationSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslDowntimeMainType.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslDowntimeMainType.api';
const SEARCH_ID_NAME_PAIRS = [{ idField: 'processOperationId', nameField: 'processOperationName' }];
const [registerModal, { openModal }] = useModal();
const [registerProcessModal, { openModal: openProcessModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +83,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
@@ -87,7 +105,19 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openProcessSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openProcessModal(true, { processOperationId: v.processOperationId });
}
function onProcessSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
processOperationId: payload.processOperationId || '',
processOperationName: payload.processOperationName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -0,0 +1,51 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/xslmes/mesXslDowntimeRecord/list',
save = '/xslmes/mesXslDowntimeRecord/add',
edit = '/xslmes/mesXslDowntimeRecord/edit',
deleteOne = '/xslmes/mesXslDowntimeRecord/delete',
deleteBatch = '/xslmes/mesXslDowntimeRecord/deleteBatch',
importExcel = '/xslmes/mesXslDowntimeRecord/importExcel',
exportXls = '/xslmes/mesXslDowntimeRecord/exportXls',
queryById = '/xslmes/mesXslDowntimeRecord/queryById',
saveMaintenanceResult = '/xslmes/mesXslDowntimeRecord/saveMaintenanceResult',
}
export const getExportUrl = Api.exportXls;
export const getImportUrl = Api.importExcel;
export const list = (params) => defHttp.get({ url: Api.list, params });
export const queryById = (params: { id: string }) => defHttp.get({ url: Api.queryById, params });
export const saveMaintenanceResult = (params) => defHttp.post({ url: Api.saveMaintenanceResult, params });
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url, params });
};

View File

@@ -0,0 +1,177 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
/** 弹窗内日期时间选择:下拉挂到 body避免被 Modal 滚动区裁剪 */
const dateTimePickerProps = {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
style: { width: '100%' },
getPopupContainer: () => document.body,
};
export const columns: BasicColumn[] = [
{ title: '机台', align: 'center', dataIndex: 'equipmentName', width: 150 },
{ title: '设备编号', align: 'center', dataIndex: 'equipmentCode', width: 130 },
{ title: '停机原因', align: 'center', dataIndex: 'downtimeTypeName', width: 150 },
{
title: '开始时间',
align: 'center',
dataIndex: 'startTime',
width: 165,
customRender: ({ text }) => (!text ? '' : String(text).length > 19 ? String(text).substring(0, 19) : text),
},
{
title: '结束时间',
align: 'center',
dataIndex: 'endTime',
width: 165,
customRender: ({ text }) => (!text ? '' : String(text).length > 19 ? String(text).substring(0, 19) : text),
},
{ title: '设备部位', align: 'center', dataIndex: 'equipmentPartName', width: 120 },
{ title: '维修项目', align: 'center', dataIndex: 'inspectMaintainItemName', width: 140 },
{ title: '维修结果', align: 'center', dataIndex: 'maintenanceResult', width: 160, ellipsis: true },
{ title: '维修人', align: 'center', dataIndex: 'maintainerRealname', width: 100 },
{ title: '是否已录入维修', align: 'center', dataIndex: 'maintenanceFilledFlag_dictText', width: 120 },
{ title: '创建人', align: 'center', dataIndex: 'createBy', width: 100 },
{
title: '创建时间',
align: 'center',
dataIndex: 'createTime',
width: 165,
customRender: ({ text }) => (!text ? '' : String(text).length > 19 ? String(text).substring(0, 19) : text),
},
];
export const searchFormSchema: FormSchema[] = [
{ label: '', field: 'equipmentLedgerId', component: 'Input', show: false },
{
label: '机台',
field: 'equipmentName',
component: 'Input',
slot: 'equipmentPicker',
colProps: { span: 6 },
},
{ label: '设备编号', field: 'equipmentCode', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'downtimeTypeId', component: 'Input', show: false },
{
label: '停机原因',
field: 'downtimeTypeName',
component: 'Input',
slot: 'downtimeTypePicker',
colProps: { span: 6 },
},
{
label: '是否已录入维修',
field: 'maintenanceFilledFlag',
component: 'JDictSelectTag',
componentProps: { dictCode: 'yn' },
colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [
{ label: '', field: 'id', component: 'Input', show: false },
{
label: '',
field: 'equipmentLedgerId',
component: 'Input',
show: false,
dynamicRules: () => [{ required: true, message: '请选择机台' }],
},
{
label: '机台',
field: 'equipmentName',
component: 'Input',
slot: 'equipmentPicker',
},
{
label: '',
field: 'downtimeTypeId',
component: 'Input',
show: false,
dynamicRules: () => [{ required: true, message: '请选择停机原因' }],
},
{
label: '停机原因',
field: 'downtimeTypeName',
component: 'Input',
slot: 'downtimeTypePicker',
},
{
label: '开始时间',
field: 'startTime',
component: 'DatePicker',
required: true,
componentProps: { ...dateTimePickerProps, placeholder: '请选择开始时间' },
},
{
label: '结束时间',
field: 'endTime',
component: 'DatePicker',
componentProps: { ...dateTimePickerProps, placeholder: '请选择结束时间' },
},
];
/** 录入维修结果弹窗 */
export const maintenanceFormSchema: FormSchema[] = [
{ label: '', field: 'id', component: 'Input', show: false },
{ label: '', field: 'equipmentLedgerId', component: 'Input', show: false },
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{ label: '', field: 'equipmentTypeId', component: 'Input', show: false },
{ label: '', field: 'maintainerUsername', component: 'Input', show: false },
{ label: '', field: 'maintainerRealname', component: 'Input', show: false },
{
label: '',
field: 'equipmentPartId',
component: 'Input',
show: false,
dynamicRules: () => [{ required: true, message: '请选择设备部位' }],
},
{
label: '设备部位',
field: 'equipmentPartName',
component: 'Input',
slot: 'equipmentPartPicker',
},
{
label: '',
field: 'inspectMaintainItemId',
component: 'Input',
show: false,
dynamicRules: () => [{ required: true, message: '请选择维修项目' }],
},
{
label: '维修项目',
field: 'inspectMaintainItemName',
component: 'Input',
slot: 'inspectItemPicker',
},
{
label: '维修结果',
field: 'maintenanceResult',
component: 'InputTextArea',
required: true,
componentProps: { rows: 3, placeholder: '手输维修结果' },
},
{
label: '维修人',
field: 'maintainerUserId',
component: 'JSelectUser',
required: true,
componentProps: ({ formActionType }) => ({
rowKey: 'id',
labelKey: 'realname',
isRadioSelection: true,
maxSelectCount: 1,
onOptionsChange: (options) => {
const row = options?.[0];
if (row && formActionType) {
formActionType.setFieldsValue({
maintainerUsername: row.username,
maintainerRealname: row.realname,
});
}
},
}),
},
];

View File

@@ -0,0 +1,198 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备"
:show-clear="!!model.equipmentLedgerId"
@open="openEquipmentSelect"
@clear="clearModelFields(model, ['equipmentLedgerId', 'equipmentName'])"
/>
</template>
<template #form-downtimeTypePicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择停机类型"
:show-clear="!!model.downtimeTypeId"
@open="openDowntimeTypeSelect"
@clear="clearModelFields(model, ['downtimeTypeId', 'downtimeTypeName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_downtime_record:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
</a-button>
<a-button
type="primary"
v-auth="'mes:mes_xsl_downtime_record:exportXls'"
preIcon="ant-design:export-outlined"
@click="onExportXls"
>
导出
</a-button>
<j-upload-button
type="primary"
v-auth="'mes:mes_xsl_downtime_record: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="'mes:mes_xsl_downtime_record:deleteBatch'">
批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
</template>
<template #action="{ record }">
<TableAction :actions="getTableActions(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<MesXslDowntimeRecordModal @register="registerModal" @success="handleSuccess" />
<MesXslDowntimeRecordMaintenanceModal @register="registerMaintenanceModal" @success="handleSuccess" />
<MesXslEquipmentLedgerSelectModal @register="registerEquipmentModal" @select="onEquipmentSelect" />
<MesXslDowntimeTypeSelectModal @register="registerDowntimeTypeModal" @select="onDowntimeTypeSelect" />
</div>
</template>
<script lang="ts" name="xslmes-mesXslDowntimeRecord" setup>
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslDowntimeRecordModal from './components/MesXslDowntimeRecordModal.vue';
import MesXslDowntimeRecordMaintenanceModal from './components/MesXslDowntimeRecordMaintenanceModal.vue';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
import MesXslDowntimeTypeSelectModal from './components/MesXslDowntimeTypeSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslDowntimeRecord.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslDowntimeRecord.api';
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'equipmentLedgerId', nameField: 'equipmentName' },
{ idField: 'downtimeTypeId', nameField: 'downtimeTypeName' },
];
const [registerModal, { openModal }] = useModal();
const [registerMaintenanceModal, { openModal: openMaintenanceModal }] = useModal();
const [registerEquipmentModal, { openModal: openEquipmentModal }] = useModal();
const [registerDowntimeTypeModal, { openModal: openDowntimeTypeModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '停机记录',
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
autoSubmitOnEnter: true,
showAdvancedButton: true,
},
actionColumn: {
width: 260,
fixed: 'right',
},
},
exportConfig: {
name: '停机记录',
url: getExportUrl,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openEquipmentSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openEquipmentModal(true, { equipmentLedgerId: v.equipmentLedgerId });
}
function onEquipmentSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentLedgerId: payload.equipmentLedgerId || '',
equipmentName: payload.equipmentName || '',
});
}
function openDowntimeTypeSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openDowntimeTypeModal(true, { downtimeTypeId: v.downtimeTypeId });
}
function onDowntimeTypeSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
downtimeTypeId: payload.downtimeTypeId || '',
downtimeTypeName: payload.downtimeTypeName || '',
});
}
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 handleEnterMaintenance(record: Recordable) {
openMaintenanceModal(true, { record });
}
function getTableActions(record: Recordable) {
return [
{
label: '录入维修结果',
onClick: handleEnterMaintenance.bind(null, record),
auth: 'mes:mes_xsl_downtime_record:edit',
},
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'mes:mes_xsl_downtime_record:edit' },
];
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
function getDropDownAction(record) {
return [
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record) },
auth: 'mes:mes_xsl_downtime_record:delete',
},
];
}
</script>

View File

@@ -0,0 +1,160 @@
<template>
<BasicModal v-bind="$attrs" destroyOnClose title="录入维修结果" width="640px" @register="registerModal" @ok="handleSubmit">
<BasicForm @register="registerForm">
<template #equipmentPartPicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请选择设备部位(非小部位)" style="flex: 1" />
<a-button type="primary" @click="openPartSelect">选择部位</a-button>
<a-button v-if="model.equipmentPartId" @click="clearPart">清除</a-button>
</a-input-group>
</template>
<template #inspectItemPicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请先选部位,再选点检项目" style="flex: 1" />
<a-button type="primary" :disabled="!model.equipmentPartId" @click="openItemSelect">选择项目</a-button>
<a-button v-if="model.inspectMaintainItemId" @click="clearItem">清除</a-button>
</a-input-group>
</template>
</BasicForm>
<MesXslEquipmentPartSelectModal @register="registerPartModal" @select="onPartSelect" />
<MesXslInspectMaintainItemSelectModal @register="registerItemModal" @select="onItemSelect" />
</BasicModal>
</template>
<script lang="ts" setup>
import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useMessage } from '/@/hooks/web/useMessage';
import { maintenanceFormSchema } from '../MesXslDowntimeRecord.data';
import { saveMaintenanceResult } from '../MesXslDowntimeRecord.api';
import { queryById as queryEquipmentById } from '/@/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedger.api';
import MesXslEquipmentPartSelectModal from '/@/views/xslmes/mesXslEquipmentSubPart/components/MesXslEquipmentPartSelectModal.vue';
import MesXslInspectMaintainItemSelectModal from './MesXslInspectMaintainItemSelectModal.vue';
const emit = defineEmits(['register', 'success']);
const { createMessage } = useMessage();
const [registerPartModal, { openModal: openPartModal }] = useModal();
const [registerItemModal, { openModal: openItemModal }] = useModal();
const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
labelWidth: 100,
schemas: maintenanceFormSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
setModalProps({ confirmLoading: false });
const record = data?.record;
if (!record?.id) {
return;
}
let equipmentCategoryId = '';
let equipmentTypeId = '';
if (record.equipmentLedgerId) {
try {
const raw = await queryEquipmentById({ id: record.equipmentLedgerId });
const ledger = (raw as any)?.id != null ? raw : (raw as any)?.result;
equipmentCategoryId = ledger?.equipmentCategoryId || '';
equipmentTypeId = ledger?.equipmentTypeId || '';
} catch {
// 忽略
}
}
await setFieldsValue({
id: record.id,
equipmentLedgerId: record.equipmentLedgerId,
equipmentCategoryId,
equipmentTypeId,
equipmentPartId: record.equipmentPartId,
equipmentPartName: record.equipmentPartName,
inspectMaintainItemId: record.inspectMaintainItemId,
inspectMaintainItemName: record.inspectMaintainItemName,
maintenanceResult: record.maintenanceResult,
maintainerUserId: record.maintainerUserId,
maintainerUsername: record.maintainerUsername,
maintainerRealname: record.maintainerRealname,
});
});
function openPartSelect() {
const vals = getFieldsValue();
if (!vals.equipmentCategoryId) {
createMessage.warning('无法获取设备类别,请确认停机记录已关联有效设备');
return;
}
openPartModal(true, {
equipmentCategoryId: vals.equipmentCategoryId,
equipmentPartId: vals.equipmentPartId,
});
}
function clearPart() {
setFieldsValue({
equipmentPartId: '',
equipmentPartName: '',
inspectMaintainItemId: '',
inspectMaintainItemName: '',
});
}
function onPartSelect(payload: { equipmentPartId?: string; equipmentPartName?: string }) {
const vals = getFieldsValue();
const pid = payload.equipmentPartId || '';
if (vals.inspectMaintainItemId && vals.equipmentPartId && vals.equipmentPartId !== pid) {
setFieldsValue({
equipmentPartId: pid,
equipmentPartName: payload.equipmentPartName || '',
inspectMaintainItemId: '',
inspectMaintainItemName: '',
});
} else {
setFieldsValue({
equipmentPartId: pid,
equipmentPartName: payload.equipmentPartName || '',
});
}
}
function openItemSelect() {
const vals = getFieldsValue();
if (!vals.equipmentPartId) {
createMessage.warning('请先选择设备部位');
return;
}
openItemModal(true, {
equipmentPartId: vals.equipmentPartId,
equipmentTypeId: vals.equipmentTypeId,
inspectMaintainItemId: vals.inspectMaintainItemId,
});
}
function clearItem() {
setFieldsValue({ inspectMaintainItemId: '', inspectMaintainItemName: '' });
}
function onItemSelect(payload: { inspectMaintainItemId?: string; inspectMaintainItemName?: string }) {
setFieldsValue({
inspectMaintainItemId: payload.inspectMaintainItemId || '',
inspectMaintainItemName: payload.inspectMaintainItemName || '',
});
}
async function handleSubmit() {
try {
const values = await validate();
if (!values.maintainerUserId) {
createMessage.warning('请选择维修人');
return;
}
setModalProps({ confirmLoading: true });
await saveMaintenanceResult(values);
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@@ -0,0 +1,108 @@
<template>
<BasicModal @register="registerModal" :title="title" width="720" v-bind="$attrs" destroyOnClose @ok="handleSubmit">
<BasicForm @register="registerForm">
<template #equipmentPicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请选择设备" style="flex: 1" />
<a-button type="primary" :disabled="isDetail" @click="openEquipmentSelect">选择设备</a-button>
<a-button v-if="model.equipmentLedgerId && !isDetail" @click="clearEquipment">清除</a-button>
</a-input-group>
</template>
<template #downtimeTypePicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请选择停机类型" style="flex: 1" />
<a-button type="primary" :disabled="isDetail" @click="openDowntimeTypeSelect">选择停机类型</a-button>
<a-button v-if="model.downtimeTypeId && !isDetail" @click="clearDowntimeType">清除</a-button>
</a-input-group>
</template>
</BasicForm>
<MesXslEquipmentLedgerSelectModal @register="registerEquipmentModal" @select="onEquipmentSelect" />
<MesXslDowntimeTypeSelectModal @register="registerDowntimeTypeModal" @select="onDowntimeTypeSelect" />
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, unref } from 'vue';
import dayjs from 'dayjs';
import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../MesXslDowntimeRecord.data';
import { saveOrUpdate } from '../MesXslDowntimeRecord.api';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
import MesXslDowntimeTypeSelectModal from './MesXslDowntimeTypeSelectModal.vue';
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
const [registerEquipmentModal, { openModal: openEquipmentModal }] = useModal();
const [registerDowntimeTypeModal, { openModal: openDowntimeTypeModal }] = useModal();
const [registerForm, { resetFields, setFieldsValue, validate, setProps, getFieldsValue }] = useForm({
labelWidth: 110,
schemas: formSchema,
showActionButtonGroup: false,
});
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({ startTime: dayjs().format('YYYY-MM-DD HH:mm:ss') });
}
setProps({ disabled: !data?.showFooter });
});
const title = computed(() =>
!unref(isUpdate) ? '新增停机记录' : unref(isDetail) ? '停机记录详情' : '编辑停机记录',
);
function openEquipmentSelect() {
const vals = getFieldsValue();
openEquipmentModal(true, { equipmentLedgerId: vals.equipmentLedgerId });
}
function clearEquipment() {
setFieldsValue({ equipmentLedgerId: '', equipmentName: '', equipmentCode: '' });
}
function onEquipmentSelect(payload: { equipmentLedgerId?: string; equipmentName?: string; equipmentCode?: string }) {
setFieldsValue({
equipmentLedgerId: payload.equipmentLedgerId || '',
equipmentName: payload.equipmentName || '',
equipmentCode: payload.equipmentCode || '',
});
}
function openDowntimeTypeSelect() {
const vals = getFieldsValue();
openDowntimeTypeModal(true, { downtimeTypeId: vals.downtimeTypeId });
}
function clearDowntimeType() {
setFieldsValue({ downtimeTypeId: '', downtimeTypeName: '' });
}
function onDowntimeTypeSelect(payload: { downtimeTypeId?: string; downtimeTypeName?: string }) {
setFieldsValue({
downtimeTypeId: payload.downtimeTypeId || '',
downtimeTypeName: payload.downtimeTypeName || '',
});
}
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, isUpdate.value);
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@@ -0,0 +1,96 @@
<template>
<BasicModal v-bind="$attrs" title="选择停机类型" :width="1000" @register="registerModal" @ok="handleOk">
<BasicTable @register="registerTable" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable } from '/@/components/Table';
import { list, queryById } from '/@/views/xslmes/mesXslDowntimeType/MesXslDowntimeType.api';
const emit = defineEmits(['register', 'select']);
const selectedRow = ref<Recordable | null>(null);
function handleSelectionChange(_keys: string[], rows: Recordable[]) {
selectedRow.value = rows?.[0] ?? null;
}
function fetchTypePage(params: Recordable) {
return list({ ...params, status: '0' });
}
const [registerTable, { reload, getSelectRowKeys, getSelectRows, setSelectedRowKeys, clearSelectedRowKeys }] = useTable({
api: fetchTypePage,
columns: [
{ title: '工序名称', dataIndex: 'processOperationName', width: 140 },
{ title: '所属主类型', dataIndex: 'downtimeMainTypeName', width: 140 },
{ title: '停机类型', dataIndex: 'downtimeType', width: 160 },
{ title: '故障等级', dataIndex: 'faultLevel', width: 100 },
],
rowKey: 'id',
useSearchForm: true,
formConfig: {
labelWidth: 90,
schemas: [
{ label: '停机类型', field: 'downtimeType', component: 'Input', colProps: { span: 8 } },
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 8 } },
],
},
pagination: { pageSize: 10 },
canResize: false,
showIndexColumn: false,
immediate: true,
rowSelection: {
type: 'radio',
columnWidth: 48,
onChange: handleSelectionChange,
},
clickToRowSelect: true,
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
selectedRow.value = null;
clearSelectedRowKeys?.();
setModalProps({ confirmLoading: false });
const tid = data?.downtimeTypeId as string | undefined;
if (tid) {
setSelectedRowKeys?.([tid]);
try {
const raw = await queryById({ id: tid });
const row = (raw as any)?.id != null ? raw : (raw as any)?.result;
if (row) {
selectedRow.value = row;
}
} catch {
// 忽略
}
}
reload();
});
async function handleOk() {
const keys = (getSelectRowKeys?.() || []) as string[];
let row = selectedRow.value || ((getSelectRows?.() || []) as Recordable[])[0];
if (!row && keys.length) {
try {
const raw = await queryById({ id: keys[0] });
row = (raw as any)?.id != null ? raw : (raw as any)?.result;
} catch {
// 忽略
}
}
if (!row?.id) {
emit('select', { downtimeTypeId: '', downtimeTypeName: '' });
closeModal();
return;
}
emit('select', {
downtimeTypeId: row.id,
downtimeTypeName: row.downtimeType || '',
});
closeModal();
}
</script>

View File

@@ -0,0 +1,117 @@
<template>
<BasicModal v-bind="$attrs" title="选择维修项目" :width="1000" @register="registerModal" @ok="handleOk">
<BasicTable @register="registerTable" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable } from '/@/components/Table';
import { list, queryById } from '/@/views/xslmes/mesXslInspectMaintainItem/MesXslInspectMaintainItem.api';
const emit = defineEmits(['register', 'select']);
const selectedRow = ref<Recordable | null>(null);
const filterEquipmentPartId = ref('');
const filterEquipmentTypeId = ref('');
function handleSelectionChange(_keys: string[], rows: Recordable[]) {
selectedRow.value = rows?.[0] ?? null;
}
function fetchItemPage(params: Recordable) {
const p: Recordable = { ...params };
if (filterEquipmentPartId.value) {
p.equipmentPartId = filterEquipmentPartId.value;
}
if (filterEquipmentTypeId.value) {
p.equipmentTypeId = filterEquipmentTypeId.value;
}
return list(p);
}
const [registerTable, { reload, getSelectRowKeys, getSelectRows, setSelectedRowKeys, clearSelectedRowKeys }] = useTable({
api: fetchItemPage,
columns: [
{ title: '项目编号', dataIndex: 'itemCode', width: 120 },
{ title: '项目名称', dataIndex: 'itemName', width: 140 },
{ title: '项目类别', dataIndex: 'itemCategory_dictText', width: 90 },
{ title: '设备部位', dataIndex: 'equipmentPartName', width: 120 },
{ title: '设备小部位', dataIndex: 'equipmentSubPartName', width: 120 },
{ title: '点检方式', dataIndex: 'inspectMethod_dictText', width: 90 },
{ title: '判断基准', dataIndex: 'judgmentCriteria', width: 160, ellipsis: true },
],
rowKey: 'id',
useSearchForm: true,
formConfig: {
labelWidth: 90,
schemas: [
{ label: '项目编号', field: 'itemCode', component: 'Input', colProps: { span: 8 } },
{ label: '项目名称', field: 'itemName', component: 'Input', colProps: { span: 8 } },
{
label: '项目类别',
field: 'itemCategory',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_im_item_category', placeholder: '点检/保养' },
colProps: { span: 8 },
},
],
},
pagination: { pageSize: 10 },
canResize: false,
showIndexColumn: false,
immediate: true,
rowSelection: {
type: 'radio',
columnWidth: 48,
onChange: handleSelectionChange,
},
clickToRowSelect: true,
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
filterEquipmentPartId.value = data?.equipmentPartId ? String(data.equipmentPartId) : '';
filterEquipmentTypeId.value = data?.equipmentTypeId ? String(data.equipmentTypeId) : '';
selectedRow.value = null;
clearSelectedRowKeys?.();
setModalProps({ confirmLoading: false });
const iid = data?.inspectMaintainItemId as string | undefined;
if (iid) {
setSelectedRowKeys?.([iid]);
try {
const raw = await queryById({ id: iid });
const row = (raw as any)?.id != null ? raw : (raw as any)?.result;
if (row) {
selectedRow.value = row;
}
} catch {
// 忽略
}
}
reload();
});
async function handleOk() {
const keys = (getSelectRowKeys?.() || []) as string[];
let row = selectedRow.value || ((getSelectRows?.() || []) as Recordable[])[0];
if (!row && keys.length) {
try {
const raw = await queryById({ id: keys[0] });
row = (raw as any)?.id != null ? raw : (raw as any)?.result;
} catch {
// 忽略
}
}
if (!row?.id) {
emit('select', { inspectMaintainItemId: '', inspectMaintainItemName: '' });
closeModal();
return;
}
emit('select', {
inspectMaintainItemId: row.id,
inspectMaintainItemName: row.itemName || '',
});
closeModal();
}
</script>

View File

@@ -47,8 +47,22 @@ export const columns: BasicColumn[] = [
];
export const searchFormSchema: FormSchema[] = [
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 6 } },
{ label: '主类型', field: 'downtimeMainTypeName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'processOperationId', component: 'Input', show: false },
{
label: '所属工序',
field: 'processOperationName',
component: 'Input',
slot: 'processOperationPicker',
colProps: { span: 6 },
},
{ label: '', field: 'downtimeMainTypeId', component: 'Input', show: false },
{
label: '所属主类型',
field: 'downtimeMainTypeName',
component: 'Input',
slot: 'downtimeMainTypePicker',
colProps: { span: 6 },
},
{ label: '停机类型', field: 'downtimeType', component: 'Input', colProps: { span: 6 } },
{
label: '是否启用',

View File

@@ -1,6 +1,26 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-processOperationPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择工序"
:show-clear="!!model.processOperationId"
@open="openProcessSelect"
@clear="clearProcess(model)"
/>
</template>
<template #form-downtimeMainTypePicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请先选工序,再选主类型"
:show-clear="!!model.downtimeMainTypeId"
@open="openMainTypeSelect"
@clear="clearModelFields(model, ['downtimeMainTypeId', 'downtimeMainTypeName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_downtime_type:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +66,8 @@
</template>
</BasicTable>
<MesXslDowntimeTypeModal @register="registerModal" @success="handleSuccess" />
<MesXslProcessOperationSelectModal @register="registerProcessModal" @select="onProcessSelect" />
<MesXslDowntimeMainTypeSelectModal @register="registerMainTypeModal" @select="onMainTypeSelect" />
</div>
</template>
@@ -53,12 +75,26 @@
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslDowntimeTypeModal from './components/MesXslDowntimeTypeModal.vue';
import MesXslProcessOperationSelectModal from '/@/views/xslmes/mesXslEquipmentCategory/components/MesXslProcessOperationSelectModal.vue';
import MesXslDowntimeMainTypeSelectModal from './components/MesXslDowntimeMainTypeSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslDowntimeType.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslDowntimeType.api';
const { createMessage } = useMessage();
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'processOperationId', nameField: 'processOperationName' },
{ idField: 'downtimeMainTypeId', nameField: 'downtimeMainTypeName' },
];
const [registerModal, { openModal }] = useModal();
const [registerProcessModal, { openModal: openProcessModal }] = useModal();
const [registerMainTypeModal, { openModal: openMainTypeModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +102,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
@@ -87,7 +124,58 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openProcessSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openProcessModal(true, { processOperationId: v.processOperationId });
}
function clearProcess(model: Recordable) {
clearModelFields(model, [
'processOperationId',
'processOperationName',
'downtimeMainTypeId',
'downtimeMainTypeName',
]);
}
function onProcessSelect(payload: Recordable) {
const v = getForm()?.getFieldsValue?.() || {};
const pid = payload.processOperationId || '';
if (v.downtimeMainTypeId && v.processOperationId && v.processOperationId !== pid) {
getForm()?.setFieldsValue?.({
processOperationId: pid,
processOperationName: payload.processOperationName || '',
downtimeMainTypeId: '',
downtimeMainTypeName: '',
});
} else {
getForm()?.setFieldsValue?.({
processOperationId: pid,
processOperationName: payload.processOperationName || '',
});
}
}
function openMainTypeSelect() {
const v = getForm()?.getFieldsValue?.() || {};
if (!v.processOperationId) {
createMessage.warning('请先选择所属工序');
return;
}
openMainTypeModal(true, {
processOperationId: v.processOperationId,
downtimeMainTypeId: v.downtimeMainTypeId,
});
}
function onMainTypeSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
downtimeMainTypeId: payload.downtimeMainTypeId || '',
downtimeMainTypeName: payload.downtimeMainTypeName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -16,7 +16,14 @@ export const columns: BasicColumn[] = [
];
export const searchFormSchema: FormSchema[] = [
{ label: '设备名称', field: 'equipmentName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'equipmentLedgerId', component: 'Input', show: false },
{
label: '设备名称',
field: 'equipmentName',
component: 'Input',
slot: 'equipmentLedgerPicker',
colProps: { span: 6 },
},
{ label: '设备编号', field: 'equipmentCode', component: 'Input', colProps: { span: 6 } },
{
label: '类型',

View File

@@ -1,6 +1,16 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentLedgerPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备台账"
:show-clear="!!model.equipmentLedgerId"
@open="openLedgerSelect"
@clear="clearModelFields(model, ['equipmentLedgerId', 'equipmentName'])"
/>
</template>
<template #tableTitle>
<a-button
type="primary"
@@ -51,6 +61,7 @@
</template>
</BasicTable>
<MesXslEquipInspectConfigModal @register="registerModal" @success="handleSuccess" />
<MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" />
</div>
</template>
@@ -59,11 +70,17 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipInspectConfigModal from './components/MesXslEquipInspectConfigModal.vue';
import MesXslEquipmentLedgerSelectModal from './components/MesXslEquipmentLedgerSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipInspectConfig.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslEquipInspectConfig.api';
const SEARCH_ID_NAME_PAIRS = [{ idField: 'equipmentLedgerId', nameField: 'equipmentName' }];
const [registerModal, { openModal }] = useModal();
const [registerLedgerModal, { openModal: openLedgerModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -71,6 +88,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
@@ -92,7 +110,19 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openLedgerSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openLedgerModal(true, { equipmentLedgerId: v.equipmentLedgerId });
}
function onLedgerSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentLedgerId: payload.equipmentLedgerId || '',
equipmentName: payload.equipmentName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -25,7 +25,14 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '记录编号', field: 'recordNo', component: 'Input', colProps: { span: 6 } },
{ label: '计划单号', field: 'planNo', component: 'Input', colProps: { span: 6 } },
{ label: '设备名称', field: 'equipmentName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'equipmentLedgerId', component: 'Input', show: false },
{
label: '设备名称',
field: 'equipmentName',
component: 'Input',
slot: 'equipmentLedgerPicker',
colProps: { span: 6 },
},
{
label: '类型',
field: 'recordType',

View File

@@ -1,6 +1,16 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentLedgerPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备台账"
:show-clear="!!model.equipmentLedgerId"
@open="openLedgerSelect"
@clear="clearModelFields(model, ['equipmentLedgerId', 'equipmentName'])"
/>
</template>
<template #tableTitle>
<a-button
type="primary"
@@ -31,6 +41,7 @@
</BasicTable>
<MesXslEquipInspectRecordModal @register="registerModal" @success="handleSuccess" />
<MesXslEquipInspectRecordHandleModal @register="registerHandleModal" @success="handleSuccess" />
<MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" />
</div>
</template>
@@ -40,15 +51,21 @@
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipInspectRecordModal from './components/MesXslEquipInspectRecordModal.vue';
import MesXslEquipInspectRecordHandleModal from './components/MesXslEquipInspectRecordHandleModal.vue';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipInspectRecord.data';
import { list, deleteOne, batchDelete, getExportUrl } from './MesXslEquipInspectRecord.api';
const { createMessage } = useMessage();
const SEARCH_ID_NAME_PAIRS = [{ idField: 'equipmentLedgerId', nameField: 'equipmentName' }];
const [registerModal, { openModal }] = useModal();
const [registerHandleModal, { openModal: openHandleModal }] = useModal();
const [registerLedgerModal, { openModal: openLedgerModal }] = useModal();
const { tableContext, onExportXls } = useListPage({
tableProps: {
@@ -56,6 +73,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
@@ -73,7 +91,19 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openLedgerSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openLedgerModal(true, { equipmentLedgerId: v.equipmentLedgerId });
}
function onLedgerSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentLedgerId: payload.equipmentLedgerId || '',
equipmentName: payload.equipmentName || '',
});
}
function handleEnterResult(record: Recordable) {
if (record.recordStatus !== 'pending') {

View File

@@ -19,7 +19,14 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '类别名称', field: 'categoryName', component: 'Input', colProps: { span: 6 } },
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'processOperationId', component: 'Input', show: false },
{
label: '所属工序',
field: 'processOperationName',
component: 'Input',
slot: 'processOperationPicker',
colProps: { span: 6 },
},
{
label: '点检区分',
field: 'inspectDistinct',

View File

@@ -1,6 +1,16 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-processOperationPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择工序"
:show-clear="!!model.processOperationId"
@open="openProcessSelect"
@clear="clearModelFields(model, ['processOperationId', 'processOperationName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_equipment_category:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +56,7 @@
</template>
</BasicTable>
<MesXslEquipmentCategoryModal @register="registerModal" @success="handleSuccess" />
<MesXslProcessOperationSelectModal @register="registerProcessModal" @select="onProcessSelect" />
</div>
</template>
@@ -54,11 +65,17 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipmentCategoryModal from './components/MesXslEquipmentCategoryModal.vue';
import MesXslProcessOperationSelectModal from './components/MesXslProcessOperationSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipmentCategory.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslEquipmentCategory.api';
const SEARCH_ID_NAME_PAIRS = [{ idField: 'processOperationId', nameField: 'processOperationName' }];
const [registerModal, { openModal }] = useModal();
const [registerProcessModal, { openModal: openProcessModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +83,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 120,
@@ -87,7 +105,19 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openProcessSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openProcessModal(true, { processOperationId: v.processOperationId });
}
function onProcessSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
processOperationId: payload.processOperationId || '',
processOperationName: payload.processOperationName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -20,7 +20,14 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '设备编号', field: 'equipmentCode', component: 'Input', colProps: { span: 6 } },
{ label: '设备名称', field: 'equipmentName', component: 'Input', colProps: { span: 6 } },
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'processOperationId', component: 'Input', show: false },
{
label: '所属工序',
field: 'processOperationName',
component: 'Input',
slot: 'processOperationPicker',
colProps: { span: 6 },
},
{
label: '设备状态',
field: 'equipmentStatus',
@@ -35,6 +42,38 @@ export const searchFormSchema: FormSchema[] = [
componentProps: { dictCode: 'yn' },
colProps: { span: 6 },
},
{ label: '', field: 'manufacturerId', component: 'Input', show: false },
{
label: '设备厂家',
field: 'manufacturerName',
component: 'Input',
slot: 'manufacturerPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{
label: '设备类别',
field: 'equipmentCategoryName',
component: 'Input',
slot: 'equipmentCategoryPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentTypeId', component: 'Input', show: false },
{
label: '设备类型',
field: 'equipmentTypeName',
component: 'Input',
slot: 'equipmentTypePicker',
colProps: { span: 6 },
},
{ label: '', field: 'factoryId', component: 'Input', show: false },
{
label: '所属工厂',
field: 'factoryName',
component: 'Input',
slot: 'factoryPicker',
colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [

View File

@@ -1,6 +1,56 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-processOperationPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择工序"
:show-clear="!!model.processOperationId"
@open="openProcessSelect"
@clear="clearModelFields(model, ['processOperationId', 'processOperationName'])"
/>
</template>
<template #form-manufacturerPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备厂家"
:show-clear="!!model.manufacturerId"
@open="openManufacturerSelect('manufacturer')"
@clear="clearModelFields(model, ['manufacturerId', 'manufacturerName'])"
/>
</template>
<template #form-equipmentCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备类别"
@@ -64,23 +114,56 @@
<a-button
type="primary"
v-auth="'mes:mes_xsl_equip_inspect_record:add'"
:disabled="selectedRowKeys.length === 0"
preIcon="ant-design:audit-outlined"
@click="handleBatchCreateRecord('inspect')"
>
点检
</a-button>
<a-button
type="primary"
v-auth="'mes:mes_xsl_equip_inspect_record:add'"
:disabled="selectedRowKeys.length === 0"
preIcon="ant-design:tool-outlined"
@click="handleBatchCreateRecord('maintain')"
>
保养
</a-button>
<a-button
type="primary"
v-auth="'mes:mes_xsl_equipment_ledger:exportXls'"
preIcon="ant-design:export-outlined"
@click="onExportXls"
>
导出
</a-button>
<j-upload-button
type="primary"
@@ -88,6 +171,7 @@
v-auth="'mes:mes_xsl_equipment_ledger:importExcel'"
preIcon="ant-design:import-outlined"
@click="onImportXls"
>
@@ -109,7 +193,65 @@
删除
</a-menu-item>
const [registerTable, { reload, getSelectRows }, { rowSelection, selectedRowKeys }] = tableContext;
</a-menu>
</template>
<a-button v-auth="'mes:mes_xsl_equipment_ledger:deleteBatch'">
批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'mes:mes_xsl_equipment_ledger:edit' },
]"
:dropDownActions="getDropDownAction(record)"
/>
</template>
</BasicTable>
<MesXslEquipmentLedgerModal @register="registerModal" @success="handleSuccess" />
<MesXslProcessOperationSelectModal @register="registerProcessModal" @select="onProcessSearchSelect" />
<MesXslManufacturerSelectModal
@register="registerManufacturerModal"
:modal-title="manufacturerModalTitle"
@select="onManufacturerSearchSelect"
/>
<MesXslEquipmentCategorySelectModal @register="registerCategoryModal" @select="onCategorySearchSelect" />
<MesXslEquipmentTypeSelectModal @register="registerTypeModal" @select="onTypeSearchSelect" />
</div>
</template>
<script lang="ts" name="xslmes-mesXslEquipmentLedger" setup>
import { computed, ref } from 'vue';

View File

@@ -20,7 +20,14 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '部位代码', field: 'partCode', component: 'Input', colProps: { span: 6 } },
{ label: '类别名称', field: 'equipmentCategoryName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{
label: '所属类别',
field: 'equipmentCategoryName',
component: 'Input',
slot: 'equipmentCategoryPicker',
colProps: { span: 6 },
},
{ label: '部位名称', field: 'partName', component: 'Input', colProps: { span: 6 } },
{
label: '是否启用',

View File

@@ -1,6 +1,16 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备类别"
:show-clear="!!model.equipmentCategoryId"
@open="openCategorySelect"
@clear="clearModelFields(model, ['equipmentCategoryId', 'equipmentCategoryName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_equipment_part:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +56,7 @@
</template>
</BasicTable>
<MesXslEquipmentPartModal @register="registerModal" @success="handleSuccess" />
<MesXslEquipmentCategorySelectModal @register="registerCategoryModal" @select="onCategorySelect" />
</div>
</template>
@@ -54,11 +65,17 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipmentPartModal from './components/MesXslEquipmentPartModal.vue';
import MesXslEquipmentCategorySelectModal from '/@/views/xslmes/mesXslEquipmentType/components/MesXslEquipmentCategorySelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipmentPart.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslEquipmentPart.api';
const SEARCH_ID_NAME_PAIRS = [{ idField: 'equipmentCategoryId', nameField: 'equipmentCategoryName' }];
const [registerModal, { openModal }] = useModal();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +83,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 120,
@@ -87,7 +105,19 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openCategorySelect() {
const v = getForm()?.getFieldsValue?.() || {};
openCategoryModal(true, { equipmentCategoryId: v.equipmentCategoryId });
}
function onCategorySelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentCategoryId: payload.equipmentCategoryId || '',
equipmentCategoryName: payload.equipmentCategoryName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -21,8 +21,22 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '小部位代码', field: 'subPartCode', component: 'Input', colProps: { span: 6 } },
{ label: '类别名称', field: 'equipmentCategoryName', component: 'Input', colProps: { span: 6 } },
{ label: '大部位名称', field: 'equipmentPartName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{
label: '所属类别',
field: 'equipmentCategoryName',
component: 'Input',
slot: 'equipmentCategoryPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentPartId', component: 'Input', show: false },
{
label: '所属大部位',
field: 'equipmentPartName',
component: 'Input',
slot: 'equipmentPartPicker',
colProps: { span: 6 },
},
{ label: '小部位名称', field: 'subPartName', component: 'Input', colProps: { span: 6 } },
{
label: '是否启用',

View File

@@ -1,6 +1,26 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备类别"
:show-clear="!!model.equipmentCategoryId"
@open="openCategorySelect"
@clear="clearCategory(model)"
/>
</template>
<template #form-equipmentPartPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备大部位"
:show-clear="!!model.equipmentPartId"
@open="openPartSelect"
@clear="clearModelFields(model, ['equipmentPartId', 'equipmentPartName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_equipment_sub_part:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +66,8 @@
</template>
</BasicTable>
<MesXslEquipmentSubPartModal @register="registerModal" @success="handleSuccess" />
<MesXslEquipmentCategorySelectModal @register="registerCategoryModal" @select="onCategorySelect" />
<MesXslEquipmentPartSelectModal @register="registerPartModal" @select="onPartSelect" />
</div>
</template>
@@ -53,12 +75,26 @@
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipmentSubPartModal from './components/MesXslEquipmentSubPartModal.vue';
import MesXslEquipmentCategorySelectModal from '/@/views/xslmes/mesXslEquipmentType/components/MesXslEquipmentCategorySelectModal.vue';
import MesXslEquipmentPartSelectModal from './components/MesXslEquipmentPartSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipmentSubPart.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslEquipmentSubPart.api';
const { createMessage } = useMessage();
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'equipmentCategoryId', nameField: 'equipmentCategoryName' },
{ idField: 'equipmentPartId', nameField: 'equipmentPartName' },
];
const [registerModal, { openModal }] = useModal();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const [registerPartModal, { openModal: openPartModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +102,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 120,
@@ -87,7 +124,44 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openCategorySelect() {
const v = getForm()?.getFieldsValue?.() || {};
openCategoryModal(true, { equipmentCategoryId: v.equipmentCategoryId });
}
function clearCategory(model: Recordable) {
clearModelFields(model, ['equipmentCategoryId', 'equipmentCategoryName', 'equipmentPartId', 'equipmentPartName']);
}
function onCategorySelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentCategoryId: payload.equipmentCategoryId || '',
equipmentCategoryName: payload.equipmentCategoryName || '',
equipmentPartId: '',
equipmentPartName: '',
});
}
function openPartSelect() {
const v = getForm()?.getFieldsValue?.() || {};
if (!v.equipmentCategoryId) {
createMessage.warning('请先选择所属设备类别');
return;
}
openPartModal(true, {
equipmentCategoryId: v.equipmentCategoryId,
equipmentPartId: v.equipmentPartId,
});
}
function onPartSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentPartId: payload.equipmentPartId || '',
equipmentPartName: payload.equipmentPartName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -18,8 +18,22 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '类型名称', field: 'typeName', component: 'Input', colProps: { span: 6 } },
{ label: '工序名称', field: 'processOperationName', component: 'Input', colProps: { span: 6 } },
{ label: '类别名称', field: 'equipmentCategoryName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'processOperationId', component: 'Input', show: false },
{
label: '所属工序',
field: 'processOperationName',
component: 'Input',
slot: 'processOperationPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{
label: '所属类别',
field: 'equipmentCategoryName',
component: 'Input',
slot: 'equipmentCategoryPicker',
colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [

View File

@@ -1,6 +1,26 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-processOperationPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择工序"
:show-clear="!!model.processOperationId"
@open="openProcessSelect"
@clear="clearModelFields(model, ['processOperationId', 'processOperationName'])"
/>
</template>
<template #form-equipmentCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备类别"
:show-clear="!!model.equipmentCategoryId"
@open="openCategorySelect"
@clear="clearModelFields(model, ['equipmentCategoryId', 'equipmentCategoryName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_equipment_type:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +66,8 @@
</template>
</BasicTable>
<MesXslEquipmentTypeModal @register="registerModal" @success="handleSuccess" />
<MesXslProcessOperationSelectModal @register="registerProcessModal" @select="onProcessSelect" />
<MesXslEquipmentCategorySelectModal @register="registerCategoryModal" @select="onCategorySelect" />
</div>
</template>
@@ -54,11 +76,22 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslEquipmentTypeModal from './components/MesXslEquipmentTypeModal.vue';
import MesXslProcessOperationSelectModal from '/@/views/xslmes/mesXslEquipmentCategory/components/MesXslProcessOperationSelectModal.vue';
import MesXslEquipmentCategorySelectModal from './components/MesXslEquipmentCategorySelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslEquipmentType.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslEquipmentType.api';
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'processOperationId', nameField: 'processOperationName' },
{ idField: 'equipmentCategoryId', nameField: 'equipmentCategoryName' },
];
const [registerModal, { openModal }] = useModal();
const [registerProcessModal, { openModal: openProcessModal }] = useModal();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +99,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 120,
@@ -87,7 +121,31 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openProcessSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openProcessModal(true, { processOperationId: v.processOperationId });
}
function onProcessSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
processOperationId: payload.processOperationId || '',
processOperationName: payload.processOperationName || '',
});
}
function openCategorySelect() {
const v = getForm()?.getFieldsValue?.() || {};
openCategoryModal(true, { equipmentCategoryId: v.equipmentCategoryId });
}
function onCategorySelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentCategoryId: payload.equipmentCategoryId || '',
equipmentCategoryName: payload.equipmentCategoryName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -26,7 +26,38 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '项目名称', field: 'itemName', component: 'Input', colProps: { span: 6 } },
{ label: '项目编号', field: 'itemCode', component: 'Input', colProps: { span: 6 } },
{ label: '设备类别', field: 'equipmentCategoryName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'equipmentCategoryId', component: 'Input', show: false },
{
label: '设备类别',
field: 'equipmentCategoryName',
component: 'Input',
slot: 'equipmentCategoryPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentTypeId', component: 'Input', show: false },
{
label: '设备类型',
field: 'equipmentTypeName',
component: 'Input',
slot: 'equipmentTypePicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentPartId', component: 'Input', show: false },
{
label: '设备部位',
field: 'equipmentPartName',
component: 'Input',
slot: 'equipmentPartPicker',
colProps: { span: 6 },
},
{ label: '', field: 'equipmentSubPartId', component: 'Input', show: false },
{
label: '设备小部位',
field: 'equipmentSubPartName',
component: 'Input',
slot: 'equipmentSubPartPicker',
colProps: { span: 6 },
},
{
label: '项目类别',
field: 'itemCategory',

View File

@@ -1,6 +1,46 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-equipmentCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择设备类别"
:show-clear="!!model.equipmentCategoryId"
@open="openCategorySelect"
@clear="clearCategory(model)"
/>
</template>
<template #form-equipmentTypePicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请先选类别,再选类型"
:show-clear="!!model.equipmentTypeId"
@open="openTypeSelect"
@clear="clearModelFields(model, ['equipmentTypeId', 'equipmentTypeName'])"
/>
</template>
<template #form-equipmentPartPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请先选类别,再选部位"
:show-clear="!!model.equipmentPartId"
@open="openPartSelect"
@clear="clearPart(model)"
/>
</template>
<template #form-equipmentSubPartPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请先选类别与部位,再选小部位"
:show-clear="!!model.equipmentSubPartId"
@open="openSubPartSelect"
@clear="clearModelFields(model, ['equipmentSubPartId', 'equipmentSubPartName'])"
/>
</template>
<template #tableTitle>
<a-button
type="primary"
@@ -51,6 +91,10 @@
</template>
</BasicTable>
<MesXslInspectMaintainItemModal @register="registerModal" @success="handleSuccess" />
<MesXslEquipmentCategorySelectModal @register="registerCategoryModal" @select="onCategorySelect" />
<MesXslEquipmentTypeFilterSelectModal @register="registerTypeModal" @select="onTypeSelect" />
<MesXslEquipmentPartSelectModal @register="registerPartModal" @select="onPartSelect" />
<MesXslEquipmentSubPartFilterSelectModal @register="registerSubPartModal" @select="onSubPartSelect" />
</div>
</template>
@@ -58,12 +102,32 @@
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslInspectMaintainItemModal from './components/MesXslInspectMaintainItemModal.vue';
import MesXslEquipmentCategorySelectModal from '/@/views/xslmes/mesXslEquipmentType/components/MesXslEquipmentCategorySelectModal.vue';
import MesXslEquipmentPartSelectModal from '/@/views/xslmes/mesXslEquipmentSubPart/components/MesXslEquipmentPartSelectModal.vue';
import MesXslEquipmentTypeFilterSelectModal from './components/MesXslEquipmentTypeFilterSelectModal.vue';
import MesXslEquipmentSubPartFilterSelectModal from './components/MesXslEquipmentSubPartFilterSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslInspectMaintainItem.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslInspectMaintainItem.api';
const { createMessage } = useMessage();
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'equipmentCategoryId', nameField: 'equipmentCategoryName' },
{ idField: 'equipmentTypeId', nameField: 'equipmentTypeName' },
{ idField: 'equipmentPartId', nameField: 'equipmentPartName' },
{ idField: 'equipmentSubPartId', nameField: 'equipmentSubPartName' },
];
const [registerModal, { openModal }] = useModal();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const [registerTypeModal, { openModal: openTypeModal }] = useModal();
const [registerPartModal, { openModal: openPartModal }] = useModal();
const [registerSubPartModal, { openModal: openSubPartModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -71,6 +135,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 100,
@@ -92,7 +157,106 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function clearCategory(model: Recordable) {
clearModelFields(model, [
'equipmentCategoryId',
'equipmentCategoryName',
'equipmentTypeId',
'equipmentTypeName',
'equipmentPartId',
'equipmentPartName',
'equipmentSubPartId',
'equipmentSubPartName',
]);
}
function clearPart(model: Recordable) {
clearModelFields(model, ['equipmentPartId', 'equipmentPartName', 'equipmentSubPartId', 'equipmentSubPartName']);
}
function openCategorySelect() {
const v = getForm()?.getFieldsValue?.() || {};
openCategoryModal(true, { equipmentCategoryId: v.equipmentCategoryId });
}
function onCategorySelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentCategoryId: payload.equipmentCategoryId || '',
equipmentCategoryName: payload.equipmentCategoryName || '',
equipmentTypeId: '',
equipmentTypeName: '',
equipmentPartId: '',
equipmentPartName: '',
equipmentSubPartId: '',
equipmentSubPartName: '',
});
}
function openTypeSelect() {
const v = getForm()?.getFieldsValue?.() || {};
if (!v.equipmentCategoryId) {
createMessage.warning('请先选择设备类别');
return;
}
openTypeModal(true, {
equipmentCategoryId: v.equipmentCategoryId,
equipmentTypeId: v.equipmentTypeId,
});
}
function onTypeSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentTypeId: payload.equipmentTypeId || '',
equipmentTypeName: payload.equipmentTypeName || '',
});
}
function openPartSelect() {
const v = getForm()?.getFieldsValue?.() || {};
if (!v.equipmentCategoryId) {
createMessage.warning('请先选择设备类别');
return;
}
openPartModal(true, {
equipmentCategoryId: v.equipmentCategoryId,
equipmentPartId: v.equipmentPartId,
});
}
function onPartSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentPartId: payload.equipmentPartId || '',
equipmentPartName: payload.equipmentPartName || '',
equipmentSubPartId: '',
equipmentSubPartName: '',
});
}
function openSubPartSelect() {
const v = getForm()?.getFieldsValue?.() || {};
if (!v.equipmentCategoryId) {
createMessage.warning('请先选择设备类别');
return;
}
if (!v.equipmentPartId) {
createMessage.warning('请先选择设备部位');
return;
}
openSubPartModal(true, {
equipmentCategoryId: v.equipmentCategoryId,
equipmentPartId: v.equipmentPartId,
equipmentSubPartId: v.equipmentSubPartId,
});
}
function onSubPartSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
equipmentSubPartId: payload.equipmentSubPartId || '',
equipmentSubPartName: payload.equipmentSubPartName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -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;

View File

@@ -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' },
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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' },
};

View File

@@ -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' },
};

View File

@@ -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' },
};

View File

@@ -68,6 +68,20 @@ export const searchFormSchema: FormSchema[] = [
},
{
label: '转子类型',
field: 'rotorType',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_rotor_type', placeholder: '大转子/小转子' },
colProps: { span: 6 },
},
];

View File

@@ -25,6 +25,57 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '单号', field: 'recordNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料名称', field: 'rubberMaterialName', component: 'Input', colProps: { span: 6 } },
{
label: '生产机台',
field: 'prodEquipmentLedgerId',
component: 'JDictSelectTag',
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择生产机台' },
colProps: { span: 6 },
},
{
label: '检验机台',
field: 'inspectEquipmentLedgerId',
component: 'JDictSelectTag',
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择检验机台' },
colProps: { span: 6 },
},
{
label: '班次',
field: 'workShift',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_shift', placeholder: '请选择班次' },
colProps: { span: 6 },
},
{
label: '班组',
field: 'workTeam',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_team', placeholder: '请选择班组' },
colProps: { span: 6 },
},
{
label: '检验类型',
field: 'quickTestTypeId',
component: 'JSearchSelect',
componentProps: {
dict: 'mes_xsl_rubber_quick_test_type,type_name,id',
async: true,
placeholder: '请选择检验类型',
},
colProps: { span: 6 },
},
{
label: '检验人',
field: 'inspectorUserId',
component: 'JSelectUser',
componentProps: {
rowKey: 'id',
labelKey: 'realname',
isRadioSelection: true,
maxSelectCount: 1,
},
colProps: { span: 6 },
},
{ label: '生产计划号', field: 'productionPlanNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料批次', field: 'rubberBatchNo', component: 'Input', colProps: { span: 6 } },
{

View File

@@ -26,8 +26,31 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '实验标准名称', field: 'stdName', component: 'Input', colProps: { span: 6 } },
{ label: '实验方法', field: 'testMethodName', component: 'Input', colProps: { span: 6 } },
{ label: '胶料名称', field: 'rubberMaterialName', component: 'Input', colProps: { span: 6 } },
{
label: '实验方法',
field: 'testMethodId',
component: 'JSearchSelect',
componentProps: {
dict: 'mes_xsl_rubber_quick_test_method,method_name,id',
async: true,
placeholder: '请选择实验方法',
},
colProps: { span: 6 },
},
{
label: '密炼机类型',
field: 'mixerType',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_mixer_type', placeholder: '请选择密炼机类型' },
colProps: { span: 6 },
},
{
label: '胶料名称',
field: 'rubberMaterialId',
component: 'JDictSelectTag',
componentProps: { dictCode: 'mes_material,material_name,id', placeholder: '请选择胶料信息' },
colProps: { span: 6 },
},
{
label: '启用状态',
field: 'enableStatus',

View File

@@ -0,0 +1,51 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/xslmes/mesXslRubberSmallLockReason/list',
nextReasonCode = '/xslmes/mesXslRubberSmallLockReason/nextReasonCode',
save = '/xslmes/mesXslRubberSmallLockReason/add',
edit = '/xslmes/mesXslRubberSmallLockReason/edit',
deleteOne = '/xslmes/mesXslRubberSmallLockReason/delete',
deleteBatch = '/xslmes/mesXslRubberSmallLockReason/deleteBatch',
importExcel = '/xslmes/mesXslRubberSmallLockReason/importExcel',
exportXls = '/xslmes/mesXslRubberSmallLockReason/exportXls',
queryById = '/xslmes/mesXslRubberSmallLockReason/queryById',
}
export const getExportUrl = Api.exportXls;
export const getImportUrl = Api.importExcel;
export const list = (params) => defHttp.get({ url: Api.list, params });
export const queryById = (params: { id: string }) => defHttp.get({ url: Api.queryById, params });
export const fetchNextReasonCode = () => defHttp.get({ url: Api.nextReasonCode }, { successMessageMode: 'none' });
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url, params });
};

View File

@@ -0,0 +1,71 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
export const columns: BasicColumn[] = [
{ title: '编号', align: 'center', dataIndex: 'reasonCode', width: 100 },
{ title: '类型', align: 'center', dataIndex: 'lockType_dictText', width: 100 },
{ title: '条码类型', align: 'center', dataIndex: 'barcodeType_dictText', width: 100 },
{ title: '创建人', align: 'center', dataIndex: 'createBy', width: 100 },
{
title: '创建日期',
align: 'center',
dataIndex: 'createTime',
width: 165,
customRender: ({ text }) => (!text ? '' : String(text).length > 19 ? String(text).substring(0, 19) : text),
},
];
export const searchFormSchema: FormSchema[] = [
{ label: '编号', field: 'reasonCode', component: 'Input', colProps: { span: 6 } },
{
label: '类型',
field: 'lockType',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_small_lock_type', placeholder: '请选择类型' },
colProps: { span: 6 },
},
{
label: '条码类型',
field: 'barcodeType',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_small_lock_barcode_type', placeholder: '请选择条码类型' },
colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [
{ label: '', field: 'id', component: 'Input', show: false },
{
label: '编号',
field: 'reasonCode',
component: 'Input',
componentProps: { readonly: true, placeholder: '保存时从001起自动生成' },
},
{
label: '类型',
field: 'lockType',
component: 'JDictSelectTag',
required: true,
componentProps: { dictCode: 'xslmes_rubber_small_lock_type', placeholder: '请选择类型' },
},
{
label: '条码类型',
field: 'barcodeType',
component: 'JDictSelectTag',
required: true,
componentProps: { dictCode: 'xslmes_rubber_small_lock_barcode_type', placeholder: '请选择条码类型' },
},
{
label: '创建人',
field: 'createBy',
component: 'Input',
componentProps: { readonly: true },
ifShow: ({ values }) => !!values?.id,
},
{
label: '创建日期',
field: 'createTime',
component: 'Input',
componentProps: { readonly: true },
ifShow: ({ values }) => !!values?.id,
},
];

View File

@@ -0,0 +1,136 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button
type="primary"
v-auth="'mes:mes_xsl_rubber_small_lock_reason:add'"
@click="handleAdd"
preIcon="ant-design:plus-outlined"
>
新增
</a-button>
<a-button
type="primary"
v-auth="'mes:mes_xsl_rubber_small_lock_reason:exportXls'"
preIcon="ant-design:export-outlined"
@click="onExportXls"
>
导出
</a-button>
<j-upload-button
type="primary"
v-auth="'mes:mes_xsl_rubber_small_lock_reason: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="'mes:mes_xsl_rubber_small_lock_reason:deleteBatch'">
批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: 'mes:mes_xsl_rubber_small_lock_reason:edit',
},
]"
:dropDownActions="getDropDownAction(record)"
/>
</template>
</BasicTable>
<MesXslRubberSmallLockReasonModal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="xslmes-mesXslRubberSmallLockReason" setup>
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesXslRubberSmallLockReasonModal from './components/MesXslRubberSmallLockReasonModal.vue';
import { columns, searchFormSchema } from './MesXslRubberSmallLockReason.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslRubberSmallLockReason.api';
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '胶料小料锁定原因',
api: list,
columns,
canResize: true,
formConfig: {
schemas: searchFormSchema,
labelWidth: 120,
autoSubmitOnEnter: true,
showAdvancedButton: true,
},
actionColumn: {
width: 200,
fixed: 'right',
},
},
exportConfig: {
name: '胶料小料锁定原因',
url: getExportUrl,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
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 });
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
function getDropDownAction(record) {
return [
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record) },
auth: 'mes:mes_xsl_rubber_small_lock_reason:delete',
},
];
}
</script>

View File

@@ -0,0 +1,57 @@
<template>
<BasicModal @register="registerModal" :title="title" width="50%" v-bind="$attrs" destroyOnClose @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../MesXslRubberSmallLockReason.data';
import { fetchNextReasonCode, saveOrUpdate } from '../MesXslRubberSmallLockReason.api';
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
const [registerForm, { resetFields, setFieldsValue, validate, setProps }] = useForm({
labelWidth: 120,
schemas: formSchema,
showActionButtonGroup: false,
});
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 {
try {
const nextCode = await fetchNextReasonCode();
await setFieldsValue({ reasonCode: nextCode });
} catch {
await setFieldsValue({ reasonCode: '' });
}
}
setProps({ disabled: !data?.showFooter });
});
const title = computed(() =>
!unref(isUpdate) ? '新增胶料小料锁定原因' : unref(isDetail) ? '锁定原因详情' : '编辑胶料小料锁定原因',
);
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, isUpdate.value);
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@@ -21,7 +21,22 @@ export const columns: BasicColumn[] = [
export const searchFormSchema: FormSchema[] = [
{ label: '备品件名称', field: 'sparePartName', component: 'Input', colProps: { span: 6 } },
{ label: '类别名称', field: 'sparePartsCategoryName', component: 'Input', colProps: { span: 6 } },
{ label: '', field: 'sparePartsCategoryId', component: 'Input', show: false },
{
label: '所属类别',
field: 'sparePartsCategoryName',
component: 'Input',
slot: 'sparePartsCategoryPicker',
colProps: { span: 6 },
},
{ label: '', field: 'unitId', component: 'Input', show: false },
{
label: '单位',
field: 'unitName',
component: 'Input',
slot: 'unitPicker',
colProps: { span: 6 },
},
{ label: '规格型号', field: 'specModel', component: 'Input', colProps: { span: 6 } },
];

View File

@@ -1,6 +1,26 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #form-sparePartsCategoryPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择备品件类别"
:show-clear="!!model.sparePartsCategoryId"
@open="openCategorySelect"
@clear="clearModelFields(model, ['sparePartsCategoryId', 'sparePartsCategoryName'])"
/>
</template>
<template #form-unitPicker="{ model, field }">
<MesSearchPickerInput
:model="model"
:field="field"
placeholder="请选择单位"
:show-clear="!!model.unitId"
@open="openUnitSelect"
@clear="clearModelFields(model, ['unitId', 'unitName'])"
/>
</template>
<template #tableTitle>
<a-button type="primary" v-auth="'mes:mes_xsl_spare_part:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
新增
@@ -46,6 +66,8 @@
</template>
</BasicTable>
<MesXslSparePartModal @register="registerModal" @success="handleSuccess" />
<MesXslSparePartsCategorySelectModal @register="registerCategoryModal" @select="onCategorySelect" />
<MesXslUnitSelectModal @register="registerUnitModal" @select="onUnitSelect" />
</div>
</template>
@@ -54,11 +76,22 @@
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesSearchPickerInput from '../components/MesSearchPickerInput.vue';
import MesXslSparePartModal from './components/MesXslSparePartModal.vue';
import MesXslSparePartsCategorySelectModal from '/@/views/xslmes/mesXslSparePartsCategory/components/MesXslSparePartsCategorySelectModal.vue';
import MesXslUnitSelectModal from '/@/views/xslmes/mesXslVehicle/components/MesXslUnitSelectModal.vue';
import { clearModelFields, createStripIdNameBeforeFetch } from '../utils/mesSearchPickerUtil';
import { columns, searchFormSchema } from './MesXslSparePart.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslSparePart.api';
const SEARCH_ID_NAME_PAIRS = [
{ idField: 'sparePartsCategoryId', nameField: 'sparePartsCategoryName' },
{ idField: 'unitId', nameField: 'unitName' },
];
const [registerModal, { openModal }] = useModal();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const [registerUnitModal, { openModal: openUnitModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -66,6 +99,7 @@
api: list,
columns,
canResize: true,
beforeFetch: createStripIdNameBeforeFetch(SEARCH_ID_NAME_PAIRS),
formConfig: {
schemas: searchFormSchema,
labelWidth: 110,
@@ -87,7 +121,31 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, getForm }, { rowSelection, selectedRowKeys }] = tableContext;
function openCategorySelect() {
const v = getForm()?.getFieldsValue?.() || {};
openCategoryModal(true, { sparePartsCategoryId: v.sparePartsCategoryId });
}
function onCategorySelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
sparePartsCategoryId: payload.sparePartsCategoryId || '',
sparePartsCategoryName: payload.sparePartsCategoryName || '',
});
}
function openUnitSelect() {
const v = getForm()?.getFieldsValue?.() || {};
openUnitModal(true, { unitId: v.unitId });
}
function onUnitSelect(payload: Recordable) {
getForm()?.setFieldsValue?.({
unitId: payload.unitId || '',
unitName: payload.unitName || '',
});
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });

View File

@@ -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 },
];

View File

@@ -0,0 +1,21 @@
/** 列表查询:有 id 时去掉冗余 name 字段,避免模糊查询 */
export type MesIdNamePair = { idField: string; nameField: string };
export function stripIdNameQueryParams(params: Recordable, pairs: MesIdNamePair[]) {
for (const { idField, nameField } of pairs) {
if (params[idField]) {
delete params[nameField];
}
}
return params;
}
export function createStripIdNameBeforeFetch(pairs: MesIdNamePair[]) {
return (params: Recordable) => stripIdNameQueryParams(params, pairs);
}
export function clearModelFields(model: Recordable, fields: string[]) {
for (const field of fields) {
model[field] = '';
}
}

View File

@@ -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
View File

@@ -0,0 +1,3 @@
{
"error": "未找到匹配 'MesXslMixerMaterialTareStrategy' 的实体类,建议使用完整类名如 MesXslVehicle"
}

View File

@@ -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> { }

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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; }

View File

@@ -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基础资料整票级单值独立专供明细行回填。

View 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; }
}

View File

@@ -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; }

View File

@@ -46,6 +46,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
new SysMenu{ Id=1300150010901, Pid=1300150000101, Title="原材料卡片", Path="/xslmes/mesXslRawMaterialCard", Name="mesXslRawMaterialCard", Component="RawMaterialCardListView", Icon="&#xe7ce;", 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="&#xe7ce;", 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="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 },
#endregion

View File

@@ -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},

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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}");
}
}
}

Some files were not shown because too many files have changed in this diff Show More