新增原料入场记录的免密查询和删除功能,支持级联删除关联原材料卡片。重构相关服务和控制器以增强逻辑删除支持,优化数据处理流程,确保数据一致性和准确性。同时,更新前端表单以支持新功能。
This commit is contained in:
@@ -40,13 +40,16 @@ import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -511,19 +514,48 @@ public class MesXslDesktopAnonController {
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密查询关联原材料卡片")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialEntry/anon/linkedRawMaterialCards")
|
||||
public Result<List<MesXslRawMaterialCardBriefVO>> rawMaterialEntryAnonLinkedRawMaterialCards(
|
||||
@RequestParam(name = "ids") String ids) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
return Result.OK(rawMaterialEntryService.listLinkedRawMaterialCards(idList));
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialEntry/anon/delete")
|
||||
public Result<String> rawMaterialEntryAnonDelete(@RequestParam(name = "id") String id) {
|
||||
rawMaterialEntryService.removeById(id);
|
||||
stompNotify.publishRawMaterialEntryChanged("delete", id);
|
||||
public Result<?> rawMaterialEntryAnonDelete(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "true") boolean cascadeDeleteCards) {
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
rawMaterialEntryService.removeEntriesRespectingLinkedCards(List.of(id), cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"存在已生成的原材料卡片但未允许级联删除,请传 cascadeDeleteCards=true。", blocked.get());
|
||||
}
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialEntry/anon/deleteBatch")
|
||||
public Result<String> rawMaterialEntryAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
rawMaterialEntryService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialEntryChanged("batchDelete", ids);
|
||||
public Result<?> rawMaterialEntryAnonDeleteBatch(
|
||||
@RequestParam(name = "ids") String ids,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "true") boolean cascadeDeleteCards) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
rawMaterialEntryService.removeEntriesRespectingLinkedCards(idList, cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"存在已生成的原材料卡片但未允许级联删除,请传 cascadeDeleteCards=true。", blocked.get());
|
||||
}
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@@ -563,9 +595,11 @@ public class MesXslDesktopAnonController {
|
||||
if (oConvertUtils.isEmpty(entity.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
boolean ok = rawMaterialCardService.updateById(entity);
|
||||
boolean ok =
|
||||
rawMaterialCardService.updateEditableInventoryFields(
|
||||
entity, "桌面端免密编辑", "桌面端(免密)");
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
return Result.error("未找到数据或更新失败,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishRawMaterialCardChanged("edit", entity.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
|
||||
@@ -138,7 +138,12 @@ public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMa
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslRawMaterialCard mesXslRawMaterialCard) {
|
||||
mesXslRawMaterialCardService.updateById(mesXslRawMaterialCard);
|
||||
boolean ok =
|
||||
mesXslRawMaterialCardService.updateEditableInventoryFields(
|
||||
mesXslRawMaterialCard, "Web端原材料卡片", null);
|
||||
if (!ok) {
|
||||
return Result.error("未找到数据或更新失败,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishRawMaterialCardChanged("edit", mesXslRawMaterialCard.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志(只读查询)
|
||||
*/
|
||||
@Tag(name = "原材料卡片修改日志")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialCardEditLog")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialCardEditLogController
|
||||
extends JeecgController<MesXslRawMaterialCardEditLog, IMesXslRawMaterialCardEditLogService> {
|
||||
|
||||
@Operation(summary = "原材料卡片修改日志-分页列表查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:list")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialCardEditLog>> queryPageList(
|
||||
MesXslRawMaterialCardEditLog entity,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialCardEditLog> qw = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
|
||||
qw.orderByDesc("modify_time");
|
||||
Page<MesXslRawMaterialCardEditLog> page = new Page<>(pageNo, pageSize);
|
||||
return Result.OK(service.page(page, qw));
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片修改日志-通过id查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:list")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslRawMaterialCardEditLog> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslRawMaterialCardEditLog row = service.getById(id);
|
||||
if (row == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(row);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialCardEditLog entity) {
|
||||
return super.exportXls(request, entity, MesXslRawMaterialCardEditLog.class, "原材料卡片修改日志");
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
@@ -34,6 +35,7 @@ import org.jeecg.modules.xslmes.constant.MesXslPrintConstants;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -105,13 +107,36 @@ public class MesXslRawMaterialEntryController extends JeecgController<MesXslRawM
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-查询关联的已生成原材料卡片(删除前校验)")
|
||||
@GetMapping("/linkedRawMaterialCards")
|
||||
@RequiresPermissions(
|
||||
value = {"xslmes:mes_xsl_raw_material_entry:delete", "xslmes:mes_xsl_raw_material_entry:deleteBatch"},
|
||||
logical = Logical.OR)
|
||||
public Result<List<MesXslRawMaterialCardBriefVO>> linkedRawMaterialCards(
|
||||
@RequestParam(name = "ids", required = true) String ids) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
return Result.OK(mesXslRawMaterialEntryService.listLinkedRawMaterialCards(idList));
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-通过id删除")
|
||||
@Operation(summary = "原料入场记录-通过id删除")
|
||||
@Operation(summary = "原料入场记录-通过id删除(cascadeDeleteCards=true 时同时删除关联原材料卡片)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslRawMaterialEntryService.removeById(id);
|
||||
stompNotify.publishRawMaterialEntryChanged("delete", id);
|
||||
public Result<?> delete(
|
||||
@RequestParam(name = "id", required = true) String id,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "false") boolean cascadeDeleteCards) {
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
mesXslRawMaterialEntryService.removeEntriesRespectingLinkedCards(
|
||||
List.of(id), cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"该原料入场记录已生成原材料卡片,确认删除时将一并删除下列卡片。请传 cascadeDeleteCards=true 重试。",
|
||||
blocked.get());
|
||||
}
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@@ -130,12 +155,24 @@ public class MesXslRawMaterialEntryController extends JeecgController<MesXslRawM
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-批量删除")
|
||||
@Operation(summary = "原料入场记录-批量删除")
|
||||
@Operation(summary = "原料入场记录-批量删除(cascadeDeleteCards=true 时同时删除关联原材料卡片)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
this.mesXslRawMaterialEntryService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialEntryChanged("batchDelete", ids);
|
||||
public Result<?> deleteBatch(
|
||||
@RequestParam(name = "ids", required = true) String ids,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "false") boolean cascadeDeleteCards) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
mesXslRawMaterialEntryService.removeEntriesRespectingLinkedCards(idList, cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"选中记录关联的原材料卡片仍存在,确认删除时将一并删除下列卡片。请传 cascadeDeleteCards=true 重试。",
|
||||
blocked.get());
|
||||
}
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 原料入场删除日志:查询已逻辑删除的入场记录(不落单独日志表)
|
||||
*/
|
||||
@Tag(name = "原料入场删除日志")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialEntryDeleteLog")
|
||||
public class MesXslRawMaterialEntryDeleteLogController {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialEntryService mesXslRawMaterialEntryService;
|
||||
|
||||
@Operation(summary = "原料入场删除日志-分页查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry_delete_log:list")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialEntryDeleteLogVO>> queryPageList(
|
||||
MesXslRawMaterialEntry query,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
MesXslRawMaterialEntry q = query == null ? new MesXslRawMaterialEntry() : query;
|
||||
q.setDelFlag(null);
|
||||
QueryWrapper<MesXslRawMaterialEntry> qw = QueryGenerator.initQueryWrapper(q, req.getParameterMap());
|
||||
qw.eq("del_flag", CommonConstant.DEL_FLAG_1);
|
||||
qw.orderByDesc("update_time").orderByDesc("create_time");
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page = new Page<>(pageNo, pageSize);
|
||||
return Result.OK(mesXslRawMaterialEntryService.pageDeletedLog(page, qw));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志(编辑剩余量/库区时落库)
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_raw_material_card_edit_log")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "原材料卡片修改日志")
|
||||
public class MesXslRawMaterialCardEditLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Excel(name = "卡片ID", width = 28)
|
||||
@Schema(description = "原材料卡片ID")
|
||||
private String cardId;
|
||||
|
||||
@Excel(name = "条码", width = 22)
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Excel(name = "批次号", width = 18)
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Excel(name = "物料名称", width = 22)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "物料ID", width = 22)
|
||||
@Schema(description = "物料ID")
|
||||
private String materialId;
|
||||
|
||||
@Excel(name = "修改前重量", width = 14)
|
||||
@Schema(description = "修改前剩余重量")
|
||||
private BigDecimal beforeRemainingWeight;
|
||||
|
||||
@Excel(name = "修改前数量", width = 12)
|
||||
@Schema(description = "修改前剩余数量")
|
||||
private Integer beforeRemainingQty;
|
||||
|
||||
@Excel(name = "修改后重量", width = 14)
|
||||
@Schema(description = "修改后剩余重量")
|
||||
private BigDecimal afterRemainingWeight;
|
||||
|
||||
@Excel(name = "修改前库区", width = 16)
|
||||
@Schema(description = "修改前库区")
|
||||
private String beforeWarehouseArea;
|
||||
|
||||
@Excel(name = "修改后数量", width = 12)
|
||||
@Schema(description = "修改后剩余数量")
|
||||
private Integer afterRemainingQty;
|
||||
|
||||
@Excel(name = "修改后库区", width = 16)
|
||||
@Schema(description = "修改后库区")
|
||||
private String afterWarehouseArea;
|
||||
|
||||
@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 modifyTime;
|
||||
|
||||
@Excel(name = "修改人姓名", width = 16)
|
||||
@Schema(description = "修改人姓名")
|
||||
private String modifyByName;
|
||||
|
||||
@Excel(name = "租户ID", width = 10)
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Excel(name = "数值来源", width = 22)
|
||||
@Schema(description = "数值来源")
|
||||
private String dataSource;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
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 lombok.Data;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
@@ -204,6 +205,11 @@ public class MesXslRawMaterialEntry implements Serializable {
|
||||
@Schema(description = "更新日期")
|
||||
private Date updateTime;
|
||||
|
||||
/**逻辑删除(0正常 1已删除);删除接口改为打标,便于「删除日志」查询。*/
|
||||
@Schema(description = "逻辑删除(0正常 1已删除)")
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
public interface MesXslRawMaterialCardEditLogMapper extends BaseMapper<MesXslRawMaterialCardEditLog> {}
|
||||
@@ -1,7 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
|
||||
/**
|
||||
* @Description: 原料入场记录
|
||||
@@ -10,4 +16,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface MesXslRawMaterialEntryMapper extends BaseMapper<MesXslRawMaterialEntry> {
|
||||
|
||||
/**
|
||||
* 分页查询已逻辑删除的入场记录(自定义 SQL,不按未删除过滤)
|
||||
*/
|
||||
IPage<MesXslRawMaterialEntryDeleteLogVO> selectDeletedPage(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page,
|
||||
@Param(Constants.WRAPPER) QueryWrapper<MesXslRawMaterialEntry> queryWrapper);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.mapper.MesXslRawMaterialEntryMapper">
|
||||
|
||||
<select id="selectDeletedPage" resultType="org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO">
|
||||
SELECT
|
||||
t.barcode,
|
||||
t.batch_no,
|
||||
t.create_by,
|
||||
t.create_time,
|
||||
t.material_name
|
||||
FROM mes_xsl_raw_material_entry t
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
public interface IMesXslRawMaterialCardEditLogService extends IService<MesXslRawMaterialCardEditLog> {}
|
||||
@@ -10,4 +10,15 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface IMesXslRawMaterialCardService extends IService<MesXslRawMaterialCard> {
|
||||
|
||||
/**
|
||||
* 列表/桌面端「编辑」仅允许调整剩余数量、剩余重量与库区,其它字段忽略且不写库。
|
||||
*
|
||||
* @param incoming 至少包含 id;仅读取 remainingQuantity、remainingWeight、warehouseArea
|
||||
* @param dataSource 数值来源说明(如 Web端原材料卡片、桌面端免密编辑),空则默认 Web端
|
||||
* @param modifierRealnameOverride 修改人姓名;空则取当前登录用户真实姓名(免密接口可传入固定文案)
|
||||
* @return 是否存在主键且更新成功(受影响行大于 0)
|
||||
*/
|
||||
boolean updateEditableInventoryFields(
|
||||
MesXslRawMaterialCard incoming, String dataSource, String modifierRealnameOverride);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Description: 原料入场记录
|
||||
@@ -15,6 +22,26 @@ import java.util.Map;
|
||||
*/
|
||||
public interface IMesXslRawMaterialEntryService extends IService<MesXslRawMaterialEntry> {
|
||||
|
||||
/**
|
||||
* 原料入场删除日志:分页查询已逻辑删除的记录(仅条码/批次/创建人/创建时间/物料名称)。
|
||||
*/
|
||||
IPage<MesXslRawMaterialEntryDeleteLogVO> pageDeletedLog(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page,
|
||||
QueryWrapper<MesXslRawMaterialEntry> queryWrapper);
|
||||
|
||||
/**
|
||||
* 根据入场记录主键查询已生成的原材料卡片(按拆码明细 ID 优先,否则按条码+批次等降级关联)。
|
||||
*/
|
||||
List<MesXslRawMaterialCardBriefVO> listLinkedRawMaterialCards(Collection<String> entryIds);
|
||||
|
||||
/**
|
||||
* 删除入场记录;若存在关联卡片且 cascadeDeleteCards=false,不删除任何数据并返回关联列表。
|
||||
*
|
||||
* @return empty 表示已删除入场记录(并在 cascade 为 true 时删除关联卡片);非空则为被拦截时的关联卡片
|
||||
*/
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> removeEntriesRespectingLinkedCards(
|
||||
Collection<String> entryIds, boolean cascadeDeleteCards);
|
||||
|
||||
/**
|
||||
* 生成条码/批次号
|
||||
* 格式:QH + 物料编码 + 日期(yyMMdd) + 当日序号(001起)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardEditLogMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialCardEditLogServiceImpl
|
||||
extends ServiceImpl<MesXslRawMaterialCardEditLogMapper, MesXslRawMaterialCardEditLog>
|
||||
implements IMesXslRawMaterialCardEditLogService {}
|
||||
@@ -1,10 +1,20 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
@@ -13,5 +23,94 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialCardServiceImpl extends ServiceImpl<MesXslRawMaterialCardMapper, MesXslRawMaterialCard> implements IMesXslRawMaterialCardService {
|
||||
public class MesXslRawMaterialCardServiceImpl extends ServiceImpl<MesXslRawMaterialCardMapper, MesXslRawMaterialCard>
|
||||
implements IMesXslRawMaterialCardService {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialCardEditLogService editLogService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateEditableInventoryFields(
|
||||
MesXslRawMaterialCard incoming, String dataSource, String modifierRealnameOverride) {
|
||||
if (incoming == null || StringUtils.isBlank(incoming.getId())) {
|
||||
return false;
|
||||
}
|
||||
MesXslRawMaterialCard existing = getById(incoming.getId());
|
||||
if (existing == null) {
|
||||
return false;
|
||||
}
|
||||
String area = incoming.getWarehouseArea();
|
||||
String areaTrimmed = area == null ? null : StringUtils.trimToNull(area);
|
||||
|
||||
boolean weightChanged = !bigDecimalEquals(existing.getRemainingWeight(), incoming.getRemainingWeight());
|
||||
boolean qtyChanged = !Objects.equals(existing.getRemainingQuantity(), incoming.getRemainingQuantity());
|
||||
boolean areaChanged = !Objects.equals(normArea(existing.getWarehouseArea()), areaTrimmed);
|
||||
boolean anyChanged = weightChanged || qtyChanged || areaChanged;
|
||||
|
||||
boolean ok = lambdaUpdate()
|
||||
.eq(MesXslRawMaterialCard::getId, incoming.getId())
|
||||
.set(MesXslRawMaterialCard::getRemainingWeight, incoming.getRemainingWeight())
|
||||
.set(MesXslRawMaterialCard::getRemainingQuantity, incoming.getRemainingQuantity())
|
||||
.set(MesXslRawMaterialCard::getWarehouseArea, areaTrimmed)
|
||||
.update();
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
if (!anyChanged) {
|
||||
return true;
|
||||
}
|
||||
String ds = StringUtils.isNotBlank(dataSource) ? dataSource : "Web端原材料卡片";
|
||||
String modifier = resolveModifierName(modifierRealnameOverride);
|
||||
MesXslRawMaterialCardEditLog logRow = new MesXslRawMaterialCardEditLog();
|
||||
logRow.setCardId(existing.getId());
|
||||
logRow.setBarcode(existing.getBarcode());
|
||||
logRow.setBatchNo(existing.getBatchNo());
|
||||
logRow.setMaterialName(existing.getMaterialName());
|
||||
logRow.setMaterialId(existing.getMaterialId());
|
||||
logRow.setBeforeRemainingWeight(existing.getRemainingWeight());
|
||||
logRow.setBeforeRemainingQty(existing.getRemainingQuantity());
|
||||
logRow.setAfterRemainingWeight(incoming.getRemainingWeight());
|
||||
logRow.setBeforeWarehouseArea(existing.getWarehouseArea());
|
||||
logRow.setAfterRemainingQty(incoming.getRemainingQuantity());
|
||||
logRow.setAfterWarehouseArea(areaTrimmed);
|
||||
logRow.setModifyTime(new Date());
|
||||
logRow.setModifyByName(modifier);
|
||||
logRow.setTenantId(existing.getTenantId());
|
||||
logRow.setDataSource(ds);
|
||||
editLogService.save(logRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String normArea(String s) {
|
||||
return s == null ? null : StringUtils.trimToNull(s);
|
||||
}
|
||||
|
||||
private static boolean bigDecimalEquals(BigDecimal a, BigDecimal b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
return a.compareTo(b) == 0;
|
||||
}
|
||||
|
||||
private static String resolveModifierName(String override) {
|
||||
if (StringUtils.isNotBlank(override)) {
|
||||
return override;
|
||||
}
|
||||
try {
|
||||
Object p = SecurityUtils.getSubject().getPrincipal();
|
||||
if (p instanceof LoginUser) {
|
||||
String name = ((LoginUser) p).getRealname();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 免密或未登录场景
|
||||
}
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
@@ -12,6 +15,9 @@ import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInventoryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -20,13 +26,17 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -49,6 +59,117 @@ public class MesXslRawMaterialEntryServiceImpl
|
||||
private IMesXslWarehouseAreaService mesXslWarehouseAreaService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Override
|
||||
public IPage<MesXslRawMaterialEntryDeleteLogVO> pageDeletedLog(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page, QueryWrapper<MesXslRawMaterialEntry> queryWrapper) {
|
||||
return baseMapper.selectDeletedPage(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MesXslRawMaterialCardBriefVO> listLinkedRawMaterialCards(Collection<String> entryIds) {
|
||||
if (entryIds == null || entryIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> distinctIds = entryIds.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (distinctIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<MesXslRawMaterialEntry> entries = this.listByIds(distinctIds);
|
||||
Map<String, MesXslRawMaterialCard> byId = new LinkedHashMap<>();
|
||||
for (MesXslRawMaterialEntry entry : entries) {
|
||||
for (MesXslRawMaterialCard card : findLinkedCardsForEntry(entry)) {
|
||||
if (card.getId() != null) {
|
||||
byId.putIfAbsent(card.getId(), card);
|
||||
}
|
||||
}
|
||||
}
|
||||
return byId.values().stream().map(MesXslRawMaterialEntryServiceImpl::toCardBrief).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Optional<List<MesXslRawMaterialCardBriefVO>> removeEntriesRespectingLinkedCards(
|
||||
Collection<String> entryIds, boolean cascadeDeleteCards) {
|
||||
if (entryIds == null || entryIds.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<String> distinctIds = entryIds.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (distinctIds.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<MesXslRawMaterialCardBriefVO> linked = listLinkedRawMaterialCards(distinctIds);
|
||||
if (!linked.isEmpty() && !cascadeDeleteCards) {
|
||||
return Optional.of(linked);
|
||||
}
|
||||
if (!linked.isEmpty()) {
|
||||
List<String> cardIds = linked.stream()
|
||||
.map(MesXslRawMaterialCardBriefVO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (!cardIds.isEmpty()) {
|
||||
mesXslRawMaterialCardService.removeByIds(cardIds);
|
||||
stompNotify.publishRawMaterialCardChanged("batchDelete", String.join(",", cardIds));
|
||||
}
|
||||
}
|
||||
this.removeByIds(distinctIds);
|
||||
String action = distinctIds.size() > 1 ? "batchDelete" : "delete";
|
||||
stompNotify.publishRawMaterialEntryChanged(action, String.join(",", distinctIds));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** 单条入场记录关联的原材料卡片:优先拆码明细 GUID,其次条码+批次等 */
|
||||
private List<MesXslRawMaterialCard> findLinkedCardsForEntry(MesXslRawMaterialEntry entry) {
|
||||
if (entry == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String[] splitIds = splitJoined(entry.getPortionDetailIds());
|
||||
if (splitIds.length > 0) {
|
||||
return mesXslRawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.in(MesXslRawMaterialCard::getSplitDetailId, Arrays.asList(splitIds))
|
||||
.list();
|
||||
}
|
||||
String bc = normalizeText(entry.getBarcode());
|
||||
String bn = normalizeText(entry.getBatchNo());
|
||||
if (!bc.isEmpty() && !bn.isEmpty()) {
|
||||
return mesXslRawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.eq(MesXslRawMaterialCard::getBarcode, bc)
|
||||
.eq(MesXslRawMaterialCard::getBatchNo, bn)
|
||||
.list();
|
||||
}
|
||||
if (!bc.isEmpty()) {
|
||||
return mesXslRawMaterialCardService.lambdaQuery().eq(MesXslRawMaterialCard::getBarcode, bc).list();
|
||||
}
|
||||
if (!bn.isEmpty()) {
|
||||
return mesXslRawMaterialCardService.lambdaQuery().eq(MesXslRawMaterialCard::getBatchNo, bn).list();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static MesXslRawMaterialCardBriefVO toCardBrief(MesXslRawMaterialCard c) {
|
||||
MesXslRawMaterialCardBriefVO v = new MesXslRawMaterialCardBriefVO();
|
||||
v.setId(c.getId());
|
||||
v.setBarcode(c.getBarcode());
|
||||
v.setBatchNo(c.getBatchNo());
|
||||
v.setMaterialName(c.getMaterialName());
|
||||
v.setSplitDetailId(c.getSplitDetailId());
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateBarcode(String materialCode) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 原材料卡片简要信息(入场删除确认弹窗用)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原材料卡片简要信息")
|
||||
public class MesXslRawMaterialCardBriefVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "卡片主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Schema(description = "关联拆码明细行ID(可空)")
|
||||
private String splitDetailId;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
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 org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 原料入场「删除日志」列表专用 VO(仅展示字段,不落单独日志表)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原料入场删除日志展示")
|
||||
public class MesXslRawMaterialEntryDeleteLogVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
-- 一级菜单:MES原料仓储(目录;默认重定向至已有原材料库区看板)
|
||||
|
||||
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 '1900000000000000700', '', 'MES原料仓储', '/xslmesRawStorage', 'layouts/default/index', 1, NULL, '/xslmes/mesXslRawMaterialWarehouseBoard', 0, NULL, '0', 81.50, 0, 'ant-design:inbox-outlined', 0, 0, 0, 0, 'MES 原料仓储(一级目录,子菜单可后续挂接或从系统管理中调整)', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000700');
|
||||
|
||||
-- admin 角色授权(幂等)
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000700', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000700');
|
||||
@@ -0,0 +1,17 @@
|
||||
-- PrintDot WebSocket 地址字典(item_text 展示名称,item_value 为完整 ws 地址,可在系统字典中维护多条)
|
||||
|
||||
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
|
||||
SELECT REPLACE(UUID(), '-', ''), 'PrintDot WS地址', 'xslmes_print_dot_ws', '原材料卡片等页面桥接器 WebSocket 地址;文本为说明,值为完整 ws URL', 0, 'admin', NOW(), 0, 1002
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict` WHERE `dict_code` = 'xslmes_print_dot_ws' AND `del_flag` = 0);
|
||||
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
SELECT REPLACE(UUID(), '-', ''), d.id, '本机 PrintDot', 'ws://127.0.0.1:1122/ws', '本机默认端口', 1, 1, 'admin', NOW()
|
||||
FROM `sys_dict` d
|
||||
WHERE d.`dict_code` = 'xslmes_print_dot_ws'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'ws://127.0.0.1:1122/ws');
|
||||
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
SELECT REPLACE(UUID(), '-', ''), d.id, '本机(备用回环)', 'ws://localhost:1122/ws', '与 127.0.0.1 等价场景', 2, 1, 'admin', NOW()
|
||||
FROM `sys_dict` d
|
||||
WHERE d.`dict_code` = 'xslmes_print_dot_ws'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_dict_item` i WHERE i.`dict_id` = d.id AND i.`item_value` = 'ws://localhost:1122/ws');
|
||||
@@ -0,0 +1,53 @@
|
||||
-- 原材料卡片修改日志:表 + 菜单(挂到「MES原料仓储」1900000000000000700 下)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_raw_material_card_edit_log` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`card_id` varchar(32) NOT NULL COMMENT '原材料卡片ID',
|
||||
`barcode` varchar(100) DEFAULT NULL COMMENT '条码',
|
||||
`batch_no` varchar(100) DEFAULT NULL COMMENT '批次号',
|
||||
`material_name` varchar(200) DEFAULT NULL COMMENT '物料名称',
|
||||
`material_id` varchar(32) DEFAULT NULL COMMENT '物料ID',
|
||||
`before_remaining_weight` decimal(12,3) DEFAULT NULL COMMENT '修改前剩余重量',
|
||||
`before_remaining_qty` int DEFAULT NULL COMMENT '修改前剩余数量',
|
||||
`after_remaining_weight` decimal(12,3) DEFAULT NULL COMMENT '修改后剩余重量',
|
||||
`before_warehouse_area` varchar(100) DEFAULT NULL COMMENT '修改前库区',
|
||||
`after_remaining_qty` int DEFAULT NULL COMMENT '修改后剩余数量',
|
||||
`after_warehouse_area` varchar(100) DEFAULT NULL COMMENT '修改后库区',
|
||||
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||
`modify_by_name` varchar(100) DEFAULT NULL COMMENT '修改人姓名',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`data_source` varchar(100) DEFAULT NULL COMMENT '数值来源(Web端/桌面端等)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_rmcel_card_id` (`card_id`),
|
||||
KEY `idx_rmcel_barcode` (`barcode`),
|
||||
KEY `idx_rmcel_modify` (`modify_time`),
|
||||
KEY `idx_rmcel_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='原材料卡片修改日志';
|
||||
|
||||
-- 菜单
|
||||
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 '1900000000000000710', '1900000000000000700', '原材料卡片修改日志', '/xslmes/mesXslRawMaterialCardEditLog', 'xslmes/mesXslRawMaterialCardEditLog/MesXslRawMaterialCardEditLogList', 1, NULL, NULL, 1, NULL, '0', 13.00, 0, 'ant-design:history-outlined', 0, 1, 0, 0, '记录 Web/桌面端编辑原材料卡片剩余量与库区', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000710');
|
||||
|
||||
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 '1900000000000000711', '1900000000000000710', '查询', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card_edit_log:list', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000711');
|
||||
|
||||
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 '1900000000000000712', '1900000000000000710', '导出', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_card_edit_log:exportXls', '1', 2.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000712');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000710', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000710');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000711', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000711');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000712', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000712');
|
||||
@@ -0,0 +1,45 @@
|
||||
-- 原料入场记录:逻辑删除字段 + 「原料入场删除日志」只读菜单(MES原料仓储下)
|
||||
-- 说明:删除改为逻辑删除(del_flag=1),本页面仅查询已删除记录展示,不另建日志表。
|
||||
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mes_xsl_raw_material_entry'
|
||||
AND COLUMN_NAME = 'del_flag'
|
||||
);
|
||||
SET @ddl := IF(@col_exists > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `mes_xsl_raw_material_entry` ADD COLUMN `del_flag` int NOT NULL DEFAULT 0 COMMENT ''逻辑删除(0正常 1已删除)'' AFTER `tenant_id`'
|
||||
);
|
||||
PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @idx_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mes_xsl_raw_material_entry'
|
||||
AND INDEX_NAME = 'idx_rme_del_flag'
|
||||
);
|
||||
SET @idx_ddl := IF(@idx_exists > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `mes_xsl_raw_material_entry` ADD KEY `idx_rme_del_flag` (`del_flag`)'
|
||||
);
|
||||
PREPARE s2 FROM @idx_ddl; EXECUTE s2; DEALLOCATE PREPARE s2;
|
||||
|
||||
-- 菜单
|
||||
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 '1900000000000000720', '1900000000000000700', '原料入场删除日志', '/xslmes/mesXslRawMaterialEntryDeleteLog', 'xslmes/mesXslRawMaterialEntryDeleteLog/MesXslRawMaterialEntryDeleteLogList', 1, NULL, NULL, 1, NULL, '0', 14.00, 0, 'ant-design:delete-row-outlined', 0, 1, 0, 0, '查询已逻辑删除的原料入场记录(仅展示条码/批次/创建人/时间/物料名称)', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000720');
|
||||
|
||||
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 '1900000000000000721', '1900000000000000720', '查询', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_entry_delete_log:list', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000721');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000720', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000720');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000721', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000721');
|
||||
@@ -150,9 +150,7 @@ export const itemFormSchema: FormSchema[] = [
|
||||
if (!value) {
|
||||
return Promise.reject('请输入数据值');
|
||||
}
|
||||
if (new RegExp("[`~!@#$^&*()=|{}'.<>《》/?!¥()—【】‘;:”“。,、?]").test(value)) {
|
||||
return Promise.reject('数据值不能包含特殊字符!');
|
||||
}
|
||||
// 允许 URL 等场景(如 ws://host:port/path),重复性由 dictItemCheck 校验
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let params = {
|
||||
dictId: values.dictId,
|
||||
|
||||
@@ -155,7 +155,8 @@ export const searchFormSchema: FormSchema[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
/** 新增:完整字段 */
|
||||
export const formSchemaAdd: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
@@ -288,6 +289,62 @@ export const formSchema: FormSchema[] = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 编辑/详情:仅展示条码、批次号、物料、剩余数量、剩余重量、库区;
|
||||
* 条码/批次号/物料只读,仅剩余数量、剩余重量、库区可编辑(详情时由表单全局禁用)。
|
||||
*/
|
||||
export const formSchemaEdit: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '条码',
|
||||
field: 'barcode',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '批次号',
|
||||
field: 'batchNo',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '物料名称',
|
||||
field: 'materialName',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '剩余数量',
|
||||
field: 'remainingQuantity',
|
||||
component: 'InputNumber',
|
||||
componentProps: { placeholder: '请输入剩余数量', min: 0, precision: 0 },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '剩余重量',
|
||||
field: 'remainingWeight',
|
||||
component: 'InputNumber',
|
||||
componentProps: { placeholder: '请输入剩余重量', min: 0, precision: 3 },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
label: '库区',
|
||||
field: 'warehouseArea',
|
||||
component: 'Input',
|
||||
slot: 'warehouseAreaPickSlot',
|
||||
helpMessage: '编辑时点击输入框,在右侧列表中选择库区(保存库区编码)',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
];
|
||||
|
||||
export const superQuerySchema = {
|
||||
barcode: { title: '条码', order: 0, view: 'text' },
|
||||
batchNo: { title: '批次号', order: 1, view: 'text' },
|
||||
|
||||
@@ -7,23 +7,15 @@
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-checkbox v-model:checked="printDotEnabled" style="margin-left: 8px" @change="onPrintDotEnabledChange">
|
||||
PrintDot 桥接
|
||||
</a-checkbox>
|
||||
<a-input
|
||||
<JDictSelectTag
|
||||
v-model:value="printDotWsUrl"
|
||||
style="width: 220px; margin-left: 8px"
|
||||
placeholder="ws://127.0.0.1:1122/ws"
|
||||
@blur="persistPrintDotConfig"
|
||||
dictCode="xslmes_print_dot_ws"
|
||||
:showChooseOption="false"
|
||||
style="width: 280px; margin-left: 8px"
|
||||
placeholder="选择 PrintDot 地址"
|
||||
@change="onPrintDotWsUrlChange"
|
||||
/>
|
||||
<a-input-password
|
||||
v-model:value="printDotKey"
|
||||
style="width: 130px; margin-left: 8px"
|
||||
placeholder="密钥(可选)"
|
||||
autocomplete="new-password"
|
||||
@blur="persistPrintDotConfig"
|
||||
/>
|
||||
<a-button @click="downloadPrintPlugin">下载打印插件</a-button>
|
||||
<a-button v-if="!printDotConnected" style="margin-left: 8px" @click="downloadPrintPlugin">下载打印插件</a-button>
|
||||
<a-select
|
||||
v-model:value="selectedPrinterName"
|
||||
:options="printerOptions"
|
||||
@@ -33,20 +25,13 @@
|
||||
option-filter-prop="label"
|
||||
:placeholder="printerSelectPlaceholder"
|
||||
/>
|
||||
<a-button @click="() => refreshPrinterOptions(true)">刷新打印机</a-button>
|
||||
<a-input
|
||||
v-model:value="manualPrinterName"
|
||||
style="width: 150px; margin-left: 8px"
|
||||
placeholder="手动输入打印机名"
|
||||
@press-enter="addManualPrinter"
|
||||
/>
|
||||
<a-button @click="addManualPrinter">添加</a-button>
|
||||
<a-button style="margin-left: 8px" @click="() => refreshPrinterOptions(true)">刷新打印机</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
v-auth="'xslmes:mes_xsl_raw_material_card:edit'"
|
||||
:loading="printLoading"
|
||||
:disabled="selectedRowKeys.length === 0 || !printDotEnabled"
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
@click="handlePrintSelected"
|
||||
>
|
||||
<Icon icon="ant-design:printer-outlined" />
|
||||
@@ -101,6 +86,8 @@
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { initDictOptions } from '/@/utils/dict';
|
||||
import { JDictSelectTag } from '/@/components/Form';
|
||||
import MesXslRawMaterialCardModal from './components/MesXslRawMaterialCardModal.vue';
|
||||
import RawMaterialCardPrintPreviewModal from './components/RawMaterialCardPrintPreviewModal.vue';
|
||||
import { columns, searchFormSchema, superQuerySchema } from './MesXslRawMaterialCard.data';
|
||||
@@ -126,19 +113,20 @@
|
||||
} from '/@/views/print/template/utils/printDotBridge';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const LS_PRINT_DOT_ENABLED = 'qhmes_print_dot_enabled';
|
||||
const printDotEnabled = ref(localStorage.getItem(LS_PRINT_DOT_ENABLED) !== '0');
|
||||
const printDotCfg = getPrintDotBridgeConfig();
|
||||
const printDotWsUrl = ref(printDotCfg.wsUrl);
|
||||
const printDotKey = ref(printDotCfg.key);
|
||||
/** 与 Flyway 中 sys_dict.dict_code 一致 */
|
||||
const PRINT_DOT_WS_DICT = 'xslmes_print_dot_ws';
|
||||
const printDotWsUrl = ref('');
|
||||
/** 是否已成功连接桥并拿到响应(用于隐藏「下载打印插件」) */
|
||||
const printDotConnected = ref(false);
|
||||
|
||||
function persistPrintDotConfig() {
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, printDotKey.value);
|
||||
setPrintDotBridgeConfig(String(printDotWsUrl.value || '').trim(), '');
|
||||
void refreshPrinterOptions(false);
|
||||
}
|
||||
|
||||
function onPrintDotEnabledChange() {
|
||||
localStorage.setItem(LS_PRINT_DOT_ENABLED, printDotEnabled.value ? '1' : '0');
|
||||
function onPrintDotWsUrlChange() {
|
||||
printDotConnected.value = false;
|
||||
persistPrintDotConfig();
|
||||
}
|
||||
|
||||
function downloadPrintPlugin() {
|
||||
@@ -210,9 +198,12 @@
|
||||
/** 与打印模板列表共用 localStorage 键,打印机选择保持一致 */
|
||||
const printerOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const selectedPrinterName = ref<string>('__system_default__');
|
||||
const manualPrinterName = ref('');
|
||||
const printLoading = ref(false);
|
||||
|
||||
/** 固定 key,便于关闭 loading(避免返回值在某些场景下未能 removeNotice) */
|
||||
const PRINT_ROW_LOADING_KEY = 'mesXslRawMaterialCard-print-row';
|
||||
const PRINT_BATCH_LOADING_KEY = 'mesXslRawMaterialCard-print-batch';
|
||||
|
||||
/** 与打印模板列表启用 PrintDot 时一致:仅本机桥接打印机 */
|
||||
const printerSelectPlaceholder = '选择打印机(PrintDot 桥接)';
|
||||
|
||||
@@ -228,18 +219,13 @@
|
||||
optionMap.set('__system_default__', { label: '系统默认打印机', value: '__system_default__' });
|
||||
try {
|
||||
const dotList = await fetchPrintDotPrinters();
|
||||
printDotConnected.value = true;
|
||||
dotList.forEach((p) => {
|
||||
const name = String(p.name || '').trim();
|
||||
if (!name) return;
|
||||
const defMark = p.isDefault ? '(默认)' : '';
|
||||
optionMap.set(name, { label: `${name}${defMark}`, value: name });
|
||||
});
|
||||
if (selectedPrinterName.value && !optionMap.has(selectedPrinterName.value)) {
|
||||
optionMap.set(selectedPrinterName.value, {
|
||||
label: `${selectedPrinterName.value}(手动)`,
|
||||
value: selectedPrinterName.value,
|
||||
});
|
||||
}
|
||||
printerOptions.value = Array.from(optionMap.values());
|
||||
if (showMessage) {
|
||||
if (dotList.length) {
|
||||
@@ -249,12 +235,7 @@
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
if (selectedPrinterName.value && !optionMap.has(selectedPrinterName.value)) {
|
||||
optionMap.set(selectedPrinterName.value, {
|
||||
label: `${selectedPrinterName.value}(手动)`,
|
||||
value: selectedPrinterName.value,
|
||||
});
|
||||
}
|
||||
printDotConnected.value = false;
|
||||
printerOptions.value = Array.from(optionMap.values());
|
||||
if (showMessage) {
|
||||
createMessage.warning(`PrintDot:${e instanceof Error ? e.message : String(e)}`);
|
||||
@@ -262,18 +243,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addManualPrinter() {
|
||||
const name = String(manualPrinterName.value || '').trim();
|
||||
if (!name) return;
|
||||
const exists = printerOptions.value.some((item) => item.value === name);
|
||||
if (!exists) {
|
||||
printerOptions.value = [...printerOptions.value, { label: `${name}(手动)`, value: name }];
|
||||
}
|
||||
selectedPrinterName.value = name;
|
||||
manualPrinterName.value = '';
|
||||
createMessage.success('已添加打印机');
|
||||
}
|
||||
|
||||
async function executePrint(record: Recordable, options?: { silentSuccess?: boolean }) {
|
||||
try {
|
||||
const prep = (await prepareNativePrint(record.id as string)) as Record<string, unknown>;
|
||||
@@ -323,6 +292,7 @@
|
||||
localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY) ||
|
||||
'__system_default__',
|
||||
});
|
||||
/** 单行/批量外层会统一 toast,此处避免重复 success */
|
||||
if (!options?.silentSuccess) {
|
||||
createMessage.success('已通过 PrintDot 提交打印');
|
||||
}
|
||||
@@ -332,66 +302,94 @@
|
||||
}
|
||||
|
||||
function handlePrintSelected() {
|
||||
if (!printDotEnabled.value) {
|
||||
createMessage.warning('请先开启 PrintDot 桥接');
|
||||
return;
|
||||
}
|
||||
const rows = selectedRows.value || [];
|
||||
if (!rows.length) {
|
||||
createMessage.warning('请至少勾选一条记录后再点击「打印选中」');
|
||||
return;
|
||||
}
|
||||
printLoading.value = true;
|
||||
const hideLoadingMsg = createMessage.loading(`正在打印 ${rows.length} 条记录,请稍候…`, 0);
|
||||
createMessage.destroy(PRINT_BATCH_LOADING_KEY);
|
||||
createMessage.loading({
|
||||
content: `正在打印 ${rows.length} 条记录,请稍候…`,
|
||||
key: PRINT_BATCH_LOADING_KEY,
|
||||
duration: 0,
|
||||
});
|
||||
(async () => {
|
||||
let ok = 0;
|
||||
let firstError = '';
|
||||
for (const row of rows) {
|
||||
try {
|
||||
await executePrint(row, { silentSuccess: true });
|
||||
ok += 1;
|
||||
} catch (e: unknown) {
|
||||
if (!firstError) {
|
||||
firstError = e instanceof Error ? e.message : String(e);
|
||||
try {
|
||||
let ok = 0;
|
||||
let firstError = '';
|
||||
for (const row of rows) {
|
||||
try {
|
||||
await executePrint(row, { silentSuccess: true });
|
||||
ok += 1;
|
||||
} catch (e: unknown) {
|
||||
if (!firstError) {
|
||||
firstError = e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ok === rows.length) {
|
||||
createMessage.success(`已通过 PrintDot 提交 ${ok} 条打印任务`);
|
||||
} else {
|
||||
createMessage.warning(
|
||||
`打印完成:成功 ${ok},失败 ${rows.length - ok}${firstError ? `。首条错误:${firstError}` : ''}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
createMessage.destroy(PRINT_BATCH_LOADING_KEY);
|
||||
printLoading.value = false;
|
||||
}
|
||||
if (ok === rows.length) {
|
||||
createMessage.success(`已通过 PrintDot 提交 ${ok} 条打印任务`);
|
||||
} else {
|
||||
createMessage.warning(`打印完成:成功 ${ok},失败 ${rows.length - ok}${firstError ? `。首条错误:${firstError}` : ''}`);
|
||||
}
|
||||
hideLoadingMsg();
|
||||
printLoading.value = false;
|
||||
})();
|
||||
}
|
||||
|
||||
function handlePrintRow(record: Recordable) {
|
||||
if (!printDotEnabled.value) {
|
||||
createMessage.warning('请先开启 PrintDot 桥接');
|
||||
return;
|
||||
}
|
||||
async function handlePrintRow(record: Recordable) {
|
||||
printLoading.value = true;
|
||||
const hideLoadingMsg = createMessage.loading('正在生成 PDF 并提交打印,版面复杂时可能需数十秒,请稍候…', 0);
|
||||
executePrint(record)
|
||||
.then(() => {
|
||||
createMessage.success('已通过 PrintDot 提交打印');
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
createMessage.error(e instanceof Error ? e.message : String(e));
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoadingMsg();
|
||||
printLoading.value = false;
|
||||
});
|
||||
createMessage.destroy(PRINT_ROW_LOADING_KEY);
|
||||
createMessage.loading({
|
||||
content: '正在生成 PDF 并提交打印,版面复杂时可能需数十秒,请稍候…',
|
||||
key: PRINT_ROW_LOADING_KEY,
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await executePrint(record, { silentSuccess: true });
|
||||
createMessage.success('已通过 PrintDot 提交打印');
|
||||
} catch (e: unknown) {
|
||||
createMessage.error(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
createMessage.destroy(PRINT_ROW_LOADING_KEY);
|
||||
printLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const saved = localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY);
|
||||
if (saved) {
|
||||
selectedPrinterName.value = saved;
|
||||
onMounted(async () => {
|
||||
const cfg = getPrintDotBridgeConfig();
|
||||
// 密钥不再使用,写入空串
|
||||
setPrintDotBridgeConfig(cfg.wsUrl, '');
|
||||
printDotWsUrl.value = cfg.wsUrl || '';
|
||||
|
||||
try {
|
||||
const raw = await initDictOptions(PRINT_DOT_WS_DICT);
|
||||
const items = Array.isArray(raw) ? raw : [];
|
||||
const values = items
|
||||
.map((it: Recordable) => String(it.value ?? it.itemValue ?? '').trim())
|
||||
.filter(Boolean);
|
||||
const valueSet = new Set(values);
|
||||
if (valueSet.size && printDotWsUrl.value && !valueSet.has(String(printDotWsUrl.value).trim())) {
|
||||
printDotWsUrl.value = values[0];
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||||
} else if (valueSet.size && !printDotWsUrl.value.trim()) {
|
||||
printDotWsUrl.value = values[0];
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||||
}
|
||||
} catch {
|
||||
/* 字典未配置时沿用 localStorage */
|
||||
}
|
||||
refreshPrinterOptions(false);
|
||||
|
||||
const savedPrinter = localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY);
|
||||
if (savedPrinter) {
|
||||
selectedPrinterName.value = savedPrinter;
|
||||
}
|
||||
await refreshPrinterOptions(false);
|
||||
});
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
|
||||
@@ -1,6 +1,66 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="1000" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" name="MesXslRawMaterialCardForm" />
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
wrap-class-name="mes-raw-card-edit-modal"
|
||||
@register="registerModal"
|
||||
destroyOnClose
|
||||
:title="title"
|
||||
:width="modalWidth"
|
||||
:bodyStyle="modalBodyStyle"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<div :class="['raw-card-modal__layout', layoutClass]">
|
||||
<div class="raw-card-modal__form">
|
||||
<BasicForm @register="registerForm" name="MesXslRawMaterialCardForm">
|
||||
<template #warehouseAreaPickSlot="{ model, field }">
|
||||
<a-input
|
||||
:value="warehouseAreaTriggerText(model[field])"
|
||||
readonly
|
||||
:disabled="!formEditable"
|
||||
placeholder="点击后在右侧选择库区"
|
||||
class="raw-card-modal__area-trigger"
|
||||
@click="onWarehouseAreaTriggerClick"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:unordered-list-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</div>
|
||||
<aside v-if="showSidePane" class="raw-card-modal__area-pane">
|
||||
<div class="raw-card-modal__area-pane-head">
|
||||
<span class="raw-card-modal__area-pane-title">库区列表</span>
|
||||
<a-button type="link" size="small" @click="areaPanelVisible = false">收起</a-button>
|
||||
</div>
|
||||
<a-input
|
||||
v-model:value="areaSearch"
|
||||
allow-clear
|
||||
placeholder="筛选仓库 / 库区名称 / 编码"
|
||||
class="raw-card-modal__area-search"
|
||||
/>
|
||||
<a-spin :spinning="areaLoading" class="raw-card-modal__area-spin">
|
||||
<div class="raw-card-modal__area-list">
|
||||
<div
|
||||
v-for="row in filteredAreaRows"
|
||||
:key="row.id || row.areaCode"
|
||||
:class="[
|
||||
'raw-card-modal__area-item',
|
||||
{ 'is-active': (row.areaCode || '').trim() === (pickedAreaCode || '').trim() },
|
||||
]"
|
||||
@click="onPickWarehouseArea(row)"
|
||||
>
|
||||
<div class="raw-card-modal__area-item-name">{{ row.areaName || row.areaCode || '—' }}</div>
|
||||
<div class="raw-card-modal__area-item-meta">
|
||||
<span v-if="row.warehouseName" class="raw-card-modal__area-item-meta-item">仓库:{{ row.warehouseName }}</span>
|
||||
<span class="raw-card-modal__area-item-meta-item">编码:{{ row.areaCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-if="!filteredAreaRows.length && !areaLoading" description="无启用库区" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</aside>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@@ -8,36 +68,169 @@
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from '../MesXslRawMaterialCard.data';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { formSchemaAdd, formSchemaEdit } from '../MesXslRawMaterialCard.data';
|
||||
import { saveOrUpdate } from '../MesXslRawMaterialCard.api';
|
||||
import { list as warehouseAreaList } from '/@/views/xslmes/mesXslWarehouseArea/MesXslWarehouseArea.api';
|
||||
|
||||
/** 库区行(列表接口 records) */
|
||||
interface WarehouseAreaRow {
|
||||
id?: string;
|
||||
areaCode: string;
|
||||
areaName?: string;
|
||||
/** 所属仓库名称(列表接口冗余字段) */
|
||||
warehouseName?: string;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
/** 与标题逻辑一致:showFooter 为 true 表示可编辑(编辑),false 为详情 */
|
||||
const isDetail = ref(false);
|
||||
|
||||
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
|
||||
const formEditable = ref(true);
|
||||
const areaPanelVisible = ref(false);
|
||||
const areaSearch = ref('');
|
||||
const areaLoading = ref(false);
|
||||
const areaRows = ref<WarehouseAreaRow[]>([]);
|
||||
const pickedAreaCode = ref<string>('');
|
||||
|
||||
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField, resetSchema }] = useForm({
|
||||
labelWidth: 120,
|
||||
schemas: formSchema,
|
||||
schemas: formSchemaAdd,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { span: 12 },
|
||||
});
|
||||
|
||||
const showSidePane = computed(() => unref(isUpdate) && unref(formEditable) && unref(areaPanelVisible));
|
||||
|
||||
const modalWidth = computed(() => {
|
||||
if (!unref(isUpdate)) {
|
||||
return 1000;
|
||||
}
|
||||
return unref(showSidePane) ? 980 : 640;
|
||||
});
|
||||
|
||||
const modalBodyStyle = computed(() =>
|
||||
unref(isUpdate) ? { paddingTop: '12px', paddingBottom: '8px' } : undefined
|
||||
);
|
||||
|
||||
const layoutClass = computed(() => ({
|
||||
'raw-card-modal__layout--split': unref(showSidePane),
|
||||
}));
|
||||
|
||||
const filteredAreaRows = computed(() => {
|
||||
const q = unref(areaSearch).trim().toLowerCase();
|
||||
const rows = unref(areaRows);
|
||||
if (!q) {
|
||||
return rows;
|
||||
}
|
||||
return rows.filter(
|
||||
(r) =>
|
||||
(r.areaCode || '').toLowerCase().includes(q) ||
|
||||
(r.areaName || '').toLowerCase().includes(q) ||
|
||||
(r.warehouseName || '').toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
function warehouseAreaTriggerText(code: string | undefined | null) {
|
||||
const c = (code || '').trim();
|
||||
if (!c) {
|
||||
return '';
|
||||
}
|
||||
const row = unref(areaRows).find((r) => (r.areaCode || '').trim() === c);
|
||||
if (!row) {
|
||||
return c;
|
||||
}
|
||||
const wh = (row.warehouseName || '').trim();
|
||||
const an = (row.areaName || '').trim();
|
||||
if (wh && an) {
|
||||
return `${wh} · ${an}(${row.areaCode})`;
|
||||
}
|
||||
if (an) {
|
||||
return `${an}(${row.areaCode})`;
|
||||
}
|
||||
return row.areaCode || c;
|
||||
}
|
||||
|
||||
let warehouseAreasLoadPromise: Promise<void> | null = null;
|
||||
|
||||
async function loadEnabledWarehouseAreas() {
|
||||
if (warehouseAreasLoadPromise) {
|
||||
return warehouseAreasLoadPromise;
|
||||
}
|
||||
warehouseAreasLoadPromise = (async () => {
|
||||
areaLoading.value = true;
|
||||
try {
|
||||
const page = await warehouseAreaList({
|
||||
pageNo: 1,
|
||||
pageSize: 5000,
|
||||
status: '0',
|
||||
});
|
||||
const records = (page?.records || []) as WarehouseAreaRow[];
|
||||
areaRows.value = records;
|
||||
} catch {
|
||||
areaRows.value = [];
|
||||
} finally {
|
||||
areaLoading.value = false;
|
||||
}
|
||||
})();
|
||||
try {
|
||||
await warehouseAreasLoadPromise;
|
||||
} finally {
|
||||
warehouseAreasLoadPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function onWarehouseAreaTriggerClick() {
|
||||
if (!unref(formEditable)) {
|
||||
return;
|
||||
}
|
||||
areaPanelVisible.value = true;
|
||||
areaSearch.value = '';
|
||||
if (!unref(areaRows).length) {
|
||||
await loadEnabledWarehouseAreas();
|
||||
}
|
||||
}
|
||||
|
||||
function onPickWarehouseArea(row: WarehouseAreaRow) {
|
||||
if (!row?.areaCode || !unref(formEditable)) {
|
||||
return;
|
||||
}
|
||||
const code = String(row.areaCode).trim();
|
||||
pickedAreaCode.value = code;
|
||||
void setFieldsValue({ warehouseArea: code });
|
||||
}
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
areaPanelVisible.value = false;
|
||||
areaSearch.value = '';
|
||||
await resetFields();
|
||||
const isAdd = !data?.isUpdate;
|
||||
await resetSchema(isAdd ? formSchemaAdd : formSchemaEdit);
|
||||
await setProps({
|
||||
disabled: !data?.showFooter,
|
||||
baseColProps: { span: isAdd ? 12 : 24 },
|
||||
});
|
||||
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
isDetail.value = !!data?.showFooter;
|
||||
formEditable.value = !!data?.showFooter;
|
||||
if (unref(isUpdate)) {
|
||||
// 先写入行数据,避免等待库区列表接口导致整表单长时间空白
|
||||
pickedAreaCode.value = String(data.record?.warehouseArea || '').trim();
|
||||
await setFieldsValue({ ...data.record });
|
||||
void loadEnabledWarehouseAreas();
|
||||
} else {
|
||||
areaRows.value = [];
|
||||
pickedAreaCode.value = '';
|
||||
}
|
||||
setProps({ disabled: !data?.showFooter });
|
||||
});
|
||||
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
|
||||
|
||||
async function handleSubmit(v) {
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
let values = await validate();
|
||||
const values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
await saveOrUpdate(values, isUpdate.value);
|
||||
closeModal();
|
||||
@@ -57,6 +250,158 @@
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.raw-card-modal__layout {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
min-height: 200px;
|
||||
|
||||
&--split {
|
||||
.raw-card-modal__form {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.raw-card-modal__form {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-pane {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* 固定可视高度:仅列表区域滚动,避免整块侧栏把 Modal body 撑出滚动条 */
|
||||
height: min(440px, calc(100vh - 240px));
|
||||
max-height: calc(100vh - 240px);
|
||||
min-height: 240px;
|
||||
padding: 0 4px 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-pane-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 4px 6px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-pane-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-search {
|
||||
flex-shrink: 0;
|
||||
margin: 10px 0 8px;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-spin {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.ant-spin-nested-loading) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.ant-spin-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.ant-spin) {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.raw-card-modal__area-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
/* 明确上限,避免父级未约束高度时无法触发 overflow */
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
padding-right: 2px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-item {
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #1677ff;
|
||||
background: rgba(22, 119, 255, 0.06);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: #1677ff;
|
||||
background: rgba(22, 119, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.raw-card-modal__area-item-name {
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-item-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-item-meta-item {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-item-meta-item:first-of-type:last-of-type {
|
||||
/* 仅编码、无仓库名时占满一行 */
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.raw-card-modal__area-trigger {
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -64,3 +409,25 @@
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 挂载在 body 的 Modal 无法被 scoped 穿透;单独取消本弹窗外层 ScrollContainer 纵向滚动,由右侧库区列表自滚动 -->
|
||||
<style lang="less">
|
||||
.mes-raw-card-edit-modal {
|
||||
.ant-modal-body {
|
||||
.scroll-container {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.scroll-container .scrollbar__wrap {
|
||||
max-height: none !important;
|
||||
overflow-y: visible !important;
|
||||
}
|
||||
|
||||
/* ModalWrapper 内层包裹 slot 的容器,内联了 maxHeight */
|
||||
.scrollbar__view > div {
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslRawMaterialCardEditLog/list',
|
||||
exportXls = '/xslmes/mesXslRawMaterialCardEditLog/exportXls',
|
||||
}
|
||||
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
@@ -0,0 +1,45 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '条码', align: 'center', dataIndex: 'barcode', width: 240 },
|
||||
{ title: '批次号', align: 'center', dataIndex: 'batchNo', width: 220 },
|
||||
{ title: '物料名称', align: 'center', dataIndex: 'materialName', width: 320 },
|
||||
{ title: '修改前重量', align: 'center', dataIndex: 'beforeRemainingWeight', width: 110 },
|
||||
{ title: '修改前数量', align: 'center', dataIndex: 'beforeRemainingQty', width: 100 },
|
||||
{ title: '修改前库区', align: 'center', dataIndex: 'beforeWarehouseArea', width: 120 },
|
||||
{ title: '修改后重量', align: 'center', dataIndex: 'afterRemainingWeight', width: 110 },
|
||||
{ title: '修改后数量', align: 'center', dataIndex: 'afterRemainingQty', width: 100 },
|
||||
{ title: '修改后库区', align: 'center', dataIndex: 'afterWarehouseArea', width: 120 },
|
||||
{
|
||||
title: '修改时间',
|
||||
align: 'center',
|
||||
dataIndex: 'modifyTime',
|
||||
width: 165,
|
||||
},
|
||||
{ title: '修改人姓名', align: 'center', dataIndex: 'modifyByName', width: 120 },
|
||||
{ title: '数值来源', align: 'center', dataIndex: 'dataSource', width: 160, ellipsis: true },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{ label: '条码', field: 'barcode', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '批次号', field: 'batchNo', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '物料名称', field: 'materialName', component: 'Input', colProps: { span: 6 } },
|
||||
{ label: '修改人', field: 'modifyByName', component: 'Input', colProps: { span: 6 } },
|
||||
{
|
||||
label: '数值来源',
|
||||
field: 'dataSource',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
componentProps: { placeholder: '如 Web端原材料卡片' },
|
||||
},
|
||||
{
|
||||
label: '修改时间',
|
||||
field: 'modifyTime',
|
||||
component: 'RangePicker',
|
||||
colProps: { span: 8 },
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_card_edit_log:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">
|
||||
导出
|
||||
</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslRawMaterialCardEditLog" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { BasicTable } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, searchFormSchema } from './MesXslRawMaterialCardEditLog.data';
|
||||
import { list, getExportUrl } from './MesXslRawMaterialCardEditLog.api';
|
||||
|
||||
const queryParam = reactive<any>({});
|
||||
|
||||
const { tableContext, onExportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '原材料卡片修改日志',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
fieldMapToTime: [['modifyTime', ['modifyTime_begin', 'modifyTime_end'], 'YYYY-MM-DD HH:mm:ss']],
|
||||
},
|
||||
beforeFetch: (params) => Object.assign(params, queryParam),
|
||||
},
|
||||
exportConfig: {
|
||||
name: '原材料卡片修改日志',
|
||||
url: getExportUrl,
|
||||
params: queryParam,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable] = tableContext;
|
||||
</script>
|
||||
@@ -1,14 +1,11 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslRawMaterialEntry/list',
|
||||
enum Api { list = '/xslmes/mesXslRawMaterialEntry/list',
|
||||
save = '/xslmes/mesXslRawMaterialEntry/add',
|
||||
edit = '/xslmes/mesXslRawMaterialEntry/edit',
|
||||
deleteOne = '/xslmes/mesXslRawMaterialEntry/delete',
|
||||
deleteBatch = '/xslmes/mesXslRawMaterialEntry/deleteBatch',
|
||||
linkedRawMaterialCards = '/xslmes/mesXslRawMaterialEntry/linkedRawMaterialCards',
|
||||
batchStockIn = '/xslmes/mesXslRawMaterialEntry/batchStockIn',
|
||||
importExcel = '/xslmes/mesXslRawMaterialEntry/importExcel',
|
||||
exportXls = '/xslmes/mesXslRawMaterialEntry/exportXls',
|
||||
@@ -20,26 +17,29 @@ enum Api {
|
||||
export const getExportUrl = Api.exportXls;
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
/** 删除前:查询入场记录关联的已生成原材料卡片 */
|
||||
export const getLinkedRawMaterialCards = (ids: string) =>
|
||||
defHttp.get<MesXslRawMaterialCardBrief[]>({ url: Api.linkedRawMaterialCards, params: { ids } });
|
||||
|
||||
export interface MesXslRawMaterialCardBrief {
|
||||
id?: string;
|
||||
barcode?: string;
|
||||
batchNo?: string;
|
||||
materialName?: string;
|
||||
splitDetailId?: string;
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
export const deleteOne = (params, handleSuccess) => {
|
||||
export const deleteOne = (params: { id: string; cascadeDeleteCards?: boolean }, handleSuccess?: () => void) => {
|
||||
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
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 batchDelete = (params: { ids: string; cascadeDeleteCards?: boolean }, handleSuccess?: () => void) => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess?.();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -6,23 +6,16 @@
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_entry:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_entry:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_entry:stockIn'" preIcon="ant-design:check-circle-outlined" @click="handleStockIn"> 结存入库</a-button>
|
||||
<a-checkbox v-model:checked="printDotEnabled" style="margin-left: 8px" @change="onPrintDotEnabledChange">
|
||||
PrintDot 桥接
|
||||
</a-checkbox>
|
||||
<a-input
|
||||
<!-- 与原材料卡片一致:字典选桥接地址 + 打印机 + 刷新 -->
|
||||
<JDictSelectTag
|
||||
v-model:value="printDotWsUrl"
|
||||
style="width: 220px; margin-left: 8px"
|
||||
placeholder="ws://127.0.0.1:1122/ws"
|
||||
@blur="persistPrintDotConfig"
|
||||
dictCode="xslmes_print_dot_ws"
|
||||
:showChooseOption="false"
|
||||
style="width: 280px; margin-left: 8px"
|
||||
placeholder="选择打印桥接"
|
||||
@change="onPrintDotWsUrlChange"
|
||||
/>
|
||||
<a-input-password
|
||||
v-model:value="printDotKey"
|
||||
style="width: 130px; margin-left: 8px"
|
||||
placeholder="密钥(可选)"
|
||||
autocomplete="new-password"
|
||||
@blur="persistPrintDotConfig"
|
||||
/>
|
||||
<a-button @click="downloadPrintPlugin">下载打印插件</a-button>
|
||||
<a-button v-if="!printDotConnected" style="margin-left: 8px" @click="downloadPrintPlugin">下载打印插件</a-button>
|
||||
<a-select
|
||||
v-model:value="selectedPrinterName"
|
||||
:options="printerOptions"
|
||||
@@ -32,20 +25,13 @@
|
||||
option-filter-prop="label"
|
||||
:placeholder="printerSelectPlaceholder"
|
||||
/>
|
||||
<a-button @click="() => refreshPrinterOptions(true)">刷新打印机</a-button>
|
||||
<a-input
|
||||
v-model:value="manualPrinterName"
|
||||
style="width: 150px; margin-left: 8px"
|
||||
placeholder="手动输入打印机名"
|
||||
@press-enter="addManualPrinter"
|
||||
/>
|
||||
<a-button @click="addManualPrinter">添加</a-button>
|
||||
<a-button style="margin-left: 8px" @click="() => refreshPrinterOptions(true)">刷新打印机</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
v-auth="'xslmes:mes_xsl_raw_material_entry:edit'"
|
||||
:loading="printLoading"
|
||||
:disabled="selectedRowKeys.length === 0 || !printDotEnabled"
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
@click="handlePrintSelected"
|
||||
>
|
||||
<Icon icon="ant-design:printer-outlined" />
|
||||
@@ -80,11 +66,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslRawMaterialEntry" setup>
|
||||
import { onMounted, ref, reactive, watch } from 'vue';
|
||||
import { onMounted, ref, reactive, watch, h } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { initDictOptions } from '/@/utils/dict';
|
||||
import { JDictSelectTag } from '/@/components/Form';
|
||||
import MesXslRawMaterialEntryModal from './components/MesXslRawMaterialEntryModal.vue';
|
||||
import RawMaterialEntryPrintPreviewModal from './components/RawMaterialEntryPrintPreviewModal.vue';
|
||||
import { columns, searchFormSchema, superQuerySchema } from './MesXslRawMaterialEntry.data';
|
||||
@@ -95,7 +83,9 @@
|
||||
batchStockIn,
|
||||
getImportUrl,
|
||||
getExportUrl,
|
||||
getLinkedRawMaterialCards,
|
||||
prepareNativePrint,
|
||||
type MesXslRawMaterialCardBrief,
|
||||
} from './MesXslRawMaterialEntry.api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import {
|
||||
@@ -110,19 +100,21 @@
|
||||
} from '/@/views/print/template/utils/printDotBridge';
|
||||
|
||||
const { createConfirm, createMessage } = useMessage();
|
||||
const LS_PRINT_DOT_ENABLED = 'qhmes_print_dot_enabled';
|
||||
const printDotEnabled = ref(localStorage.getItem(LS_PRINT_DOT_ENABLED) !== '0');
|
||||
const printDotCfg = getPrintDotBridgeConfig();
|
||||
const printDotWsUrl = ref(printDotCfg.wsUrl);
|
||||
const printDotKey = ref(printDotCfg.key);
|
||||
/** 与 Flyway 中 sys_dict.dict_code 一致(与原材料卡片列表相同) */
|
||||
const PRINT_DOT_WS_DICT = 'xslmes_print_dot_ws';
|
||||
const printDotWsUrl = ref('');
|
||||
/** 是否已成功连接桥并拿到响应(用于显示「下载打印插件」) */
|
||||
const printDotConnected = ref(false);
|
||||
|
||||
function persistPrintDotConfig() {
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, printDotKey.value);
|
||||
// 与原材料卡片一致:桥接密钥固定为空,由本地 PrintDot 与字典地址对接
|
||||
setPrintDotBridgeConfig(String(printDotWsUrl.value || '').trim(), '');
|
||||
void refreshPrinterOptions(false);
|
||||
}
|
||||
|
||||
function onPrintDotEnabledChange() {
|
||||
localStorage.setItem(LS_PRINT_DOT_ENABLED, printDotEnabled.value ? '1' : '0');
|
||||
function onPrintDotWsUrlChange() {
|
||||
printDotConnected.value = false;
|
||||
persistPrintDotConfig();
|
||||
}
|
||||
|
||||
function downloadPrintPlugin() {
|
||||
@@ -193,7 +185,6 @@
|
||||
|
||||
const printerOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const selectedPrinterName = ref<string>('__system_default__');
|
||||
const manualPrinterName = ref('');
|
||||
const printLoading = ref(false);
|
||||
const printerSelectPlaceholder = '选择打印机(PrintDot 桥接)';
|
||||
|
||||
@@ -208,6 +199,7 @@
|
||||
optionMap.set('__system_default__', { label: '系统默认打印机', value: '__system_default__' });
|
||||
try {
|
||||
const dotList = await fetchPrintDotPrinters();
|
||||
printDotConnected.value = true;
|
||||
dotList.forEach((p) => {
|
||||
const name = String(p.name || '').trim();
|
||||
if (!name) return;
|
||||
@@ -229,6 +221,7 @@
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
printDotConnected.value = false;
|
||||
if (selectedPrinterName.value && !optionMap.has(selectedPrinterName.value)) {
|
||||
optionMap.set(selectedPrinterName.value, {
|
||||
label: `${selectedPrinterName.value}(手动)`,
|
||||
@@ -242,18 +235,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addManualPrinter() {
|
||||
const name = String(manualPrinterName.value || '').trim();
|
||||
if (!name) return;
|
||||
const exists = printerOptions.value.some((item) => item.value === name);
|
||||
if (!exists) {
|
||||
printerOptions.value = [...printerOptions.value, { label: `${name}(手动)`, value: name }];
|
||||
}
|
||||
selectedPrinterName.value = name;
|
||||
manualPrinterName.value = '';
|
||||
createMessage.success('已添加打印机');
|
||||
}
|
||||
|
||||
async function executePrint(record: Recordable, options?: { silentSuccess?: boolean }) {
|
||||
try {
|
||||
const prep = (await prepareNativePrint(record.id as string)) as Record<string, unknown>;
|
||||
@@ -310,8 +291,8 @@
|
||||
}
|
||||
|
||||
function handlePrintSelected() {
|
||||
if (!printDotEnabled.value) {
|
||||
createMessage.warning('请先开启 PrintDot 桥接');
|
||||
if (!String(printDotWsUrl.value || '').trim()) {
|
||||
createMessage.warning('请先选择打印桥接(PrintDot 地址)');
|
||||
return;
|
||||
}
|
||||
const rows = selectedRows.value || [];
|
||||
@@ -345,8 +326,8 @@
|
||||
}
|
||||
|
||||
function handlePrintRow(record: Recordable) {
|
||||
if (!printDotEnabled.value) {
|
||||
createMessage.warning('请先开启 PrintDot 桥接');
|
||||
if (!String(printDotWsUrl.value || '').trim()) {
|
||||
createMessage.warning('请先选择打印桥接(PrintDot 地址)');
|
||||
return;
|
||||
}
|
||||
printLoading.value = true;
|
||||
@@ -364,12 +345,35 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const cfg = getPrintDotBridgeConfig();
|
||||
// 密钥不使用,与原材料卡片列表一致
|
||||
setPrintDotBridgeConfig(cfg.wsUrl || '', '');
|
||||
printDotWsUrl.value = cfg.wsUrl || '';
|
||||
|
||||
try {
|
||||
const raw = await initDictOptions(PRINT_DOT_WS_DICT);
|
||||
const items = Array.isArray(raw) ? raw : [];
|
||||
const values = items
|
||||
.map((it: Recordable) => String(it.value ?? it.itemValue ?? '').trim())
|
||||
.filter(Boolean);
|
||||
const valueSet = new Set(values);
|
||||
if (valueSet.size && printDotWsUrl.value && !valueSet.has(String(printDotWsUrl.value).trim())) {
|
||||
printDotWsUrl.value = values[0];
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||||
} else if (valueSet.size && !String(printDotWsUrl.value || '').trim()) {
|
||||
printDotWsUrl.value = values[0];
|
||||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||||
}
|
||||
} catch {
|
||||
/* 字典未配置时沿用 localStorage */
|
||||
}
|
||||
|
||||
const saved = localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY);
|
||||
if (saved) {
|
||||
selectedPrinterName.value = saved;
|
||||
}
|
||||
refreshPrinterOptions(false);
|
||||
await refreshPrinterOptions(false);
|
||||
});
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
@@ -391,12 +395,107 @@
|
||||
openModal(true, { record, isUpdate: true, showFooter: false });
|
||||
}
|
||||
|
||||
async function handleDelete(record) {
|
||||
await deleteOne({ id: record.id }, handleSuccess);
|
||||
/** 删除确认框:关联卡片以列表形式展示 */
|
||||
function renderLinkedCardsConfirmContent(linked: MesXslRawMaterialCardBrief[], summaryText: string) {
|
||||
const labelStyle = { color: 'rgba(0,0,0,0.45)', display: 'inline-block', minWidth: '40px' as const };
|
||||
return h('div', { class: 'linked-raw-material-cards-confirm' }, [
|
||||
h('p', { style: { margin: '0 0 12px', color: 'rgba(0,0,0,0.88)' } }, summaryText),
|
||||
h(
|
||||
'ul',
|
||||
{
|
||||
style: {
|
||||
listStyle: 'none',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
maxHeight: '360px',
|
||||
overflowY: 'auto' as const,
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '6px',
|
||||
background: '#fafafa',
|
||||
},
|
||||
},
|
||||
linked.map((c, idx) =>
|
||||
h(
|
||||
'li',
|
||||
{
|
||||
key: c.id ?? `${c.barcode ?? ''}-${c.batchNo ?? ''}-${idx}`,
|
||||
style: {
|
||||
padding: '10px 12px',
|
||||
borderBottom: idx < linked.length - 1 ? '1px solid #f0f0f0' : 'none',
|
||||
background: '#fff',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('div', { style: { fontSize: '13px', lineHeight: '22px', color: 'rgba(0,0,0,0.88)', wordBreak: 'break-all' as const } }, [
|
||||
h('div', null, [h('span', { style: labelStyle }, '条码'), ':', c.barcode || '-']),
|
||||
h('div', null, [h('span', { style: labelStyle }, '批次'), ':', c.batchNo || '-']),
|
||||
h('div', null, [h('span', { style: labelStyle }, '物料'), ':', c.materialName || '-']),
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
try {
|
||||
const linked = await getLinkedRawMaterialCards(record.id as string);
|
||||
if (linked && linked.length > 0) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '已生成原材料卡片',
|
||||
width: 600,
|
||||
content: renderLinkedCardsConfirmContent(linked, `以下 ${linked.length} 张卡片将随入场记录一并删除:`),
|
||||
okText: '仍要删除',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await deleteOne({ id: record.id as string, cascadeDeleteCards: true }, handleSuccess);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否确认删除该条原料入场记录?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await deleteOne({ id: record.id as string, cascadeDeleteCards: false }, handleSuccess);
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
createMessage.error(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
}
|
||||
|
||||
async function batchHandleDelete() {
|
||||
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||
const ids = (selectedRowKeys.value || []).join(',');
|
||||
if (!ids) {
|
||||
createMessage.warning('请先勾选要删除的记录');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const linked = await getLinkedRawMaterialCards(ids);
|
||||
const cardList: MesXslRawMaterialCardBrief[] = linked && linked.length > 0 ? linked : [];
|
||||
const hasCards = cardList.length > 0;
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: hasCards ? '已生成原材料卡片' : '确认删除',
|
||||
width: hasCards ? 600 : 416,
|
||||
content: hasCards
|
||||
? renderLinkedCardsConfirmContent(cardList, `以下 ${cardList.length} 张卡片将随选中入场记录一并删除:`)
|
||||
: `是否删除选中的 ${selectedRowKeys.value.length} 条数据?`,
|
||||
okText: '确认删除',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await batchDelete({ ids, cascadeDeleteCards: !!hasCards }, handleSuccess);
|
||||
},
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
createMessage.error(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
}
|
||||
|
||||
function handleStockIn() {
|
||||
@@ -450,11 +549,7 @@
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
placement: 'topLeft',
|
||||
},
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: 'xslmes:mes_xsl_raw_material_entry:delete',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslRawMaterialEntryDeleteLog/list',
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
@@ -0,0 +1,31 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '条码', align: 'center', dataIndex: 'barcode', width: 240 },
|
||||
{ title: '批次', align: 'center', dataIndex: 'batchNo', width: 220 },
|
||||
{ title: '创建人', align: 'center', dataIndex: 'createBy', width: 120 },
|
||||
{
|
||||
title: '创建时间',
|
||||
align: 'center',
|
||||
dataIndex: 'createTime',
|
||||
width: 170,
|
||||
},
|
||||
{ title: '物料名称', align: 'center', dataIndex: 'materialName', width: 320 },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{ label: '条码', field: 'barcode', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '批次', field: 'batchNo', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '物料名称', field: 'materialName', component: 'Input', colProps: { span: 6 } },
|
||||
{ label: '创建人', field: 'createBy', component: 'Input', colProps: { span: 6 } },
|
||||
{
|
||||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
component: 'RangePicker',
|
||||
colProps: { span: 8 },
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslRawMaterialEntryDeleteLog" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { BasicTable } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, searchFormSchema } from './MesXslRawMaterialEntryDeleteLog.data';
|
||||
import { list } from './MesXslRawMaterialEntryDeleteLog.api';
|
||||
|
||||
const queryParam = reactive<any>({});
|
||||
|
||||
const { tableContext } = useListPage({
|
||||
tableProps: {
|
||||
title: '原料入场删除日志',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
fieldMapToTime: [['createTime', ['createTime_begin', 'createTime_end'], 'YYYY-MM-DD HH:mm:ss']],
|
||||
},
|
||||
beforeFetch: (params) => Object.assign(params, queryParam),
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable] = tableContext;
|
||||
</script>
|
||||
Reference in New Issue
Block a user