feat(biz): 添加产品批次重量配置和退库功能模块

This commit is contained in:
zxy 2026-06-04 14:48:54 +08:00
parent 8ae537466d
commit 4a04aa40e4
25 changed files with 2618 additions and 117 deletions

1
.gitignore vendored
View File

@ -54,3 +54,4 @@ application-my.yaml
### Trae ###
/.trae/
/mes-ui/mes-ui-admin-vue3/.agent-browser/

View File

@ -103,7 +103,7 @@ public class ProPackPageReqVO extends PageParam {
private Integer lineId;
@Schema(description = "入库状态( 1 待入库 2 部分入库 3 已入库)", example = "1")
private String packStatus;
private String [] packStatus;
@Schema(description = "损耗数量")
private BigDecimal lossQty;

View File

@ -3,6 +3,8 @@ package com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostorage.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 成品入/出库新增/修改 Request VO")
@Data
public class ProStorageInsertReqVO {
@ -18,4 +20,8 @@ public class ProStorageInsertReqVO {
private String storeAreCd;
private String storeAreaName;
//退库信息
private BigDecimal returnQty;
private Integer returnBagQty;
}

View File

@ -10,6 +10,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import static com.ningxia.yunxi.chemmes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
import static com.ningxia.yunxi.chemmes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 成品入/出库分页 Request VO")
@ -22,7 +23,7 @@ public class ProStoragePageReqVO extends PageParam {
private String billNo;
@Schema(description = "操作类型 1为入库2为出库", example = "1")
private Boolean operatorType;
private String operatorType;
@Schema(description = "业务类型 (10 生产入库11 盘盈入库12 其它入库 21 成品出库 22 盘亏出库 23生产退库)", example = "1")
private Integer businessType;
@ -38,7 +39,7 @@ public class ProStoragePageReqVO extends PageParam {
private LocalDateTime[] createTime;
@Schema(description = "业务日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate[] billDate;
@Schema(description = "操作人id", example = "21958")

View File

@ -3,9 +3,8 @@ package com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostorage.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "管理后台 - 成品入/出库新增/修改 Request VO")
@Data
@ -14,45 +13,46 @@ public class ProStorageSaveReqVO {
@Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26146")
private Integer id;
@Schema(description = "单据编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "单据编号不能为空")
private String billNo;
@Schema(description = "操作类型 1为入库2为出库", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "操作类型 1为入库2为出库不能为空")
private Boolean operatorType;
@Schema(description = "业务类型 (10 生产入库11 盘盈入库12 其它入库 21 成品出库 22 盘亏出库 23生产退库)", example = "1")
private Integer businessType;
// @Schema(description = "单据编号", requiredMode = Schema.RequiredMode.REQUIRED)
// @NotEmpty(message = "单据编号不能为空")
// private String billNo;
//
// @Schema(description = "操作类型 1为入库2为出库", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
// @NotNull(message = "操作类型 1为入库2为出库不能为空")
// private Boolean operatorType;
//
// @Schema(description = "业务类型 (10 生产入库11 盘盈入库12 其它入库 21 成品出库 22 盘亏出库 23生产退库)", example = "1")
// private Integer businessType;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "状态1 已创建2 提交", example = "1")
private Boolean status;
private String status;
@Schema(description = "业务日期")
private LocalDate billDate;
@Schema(description = "操作人id", example = "21958")
private Integer operatorId;
// @Schema(description = "操作人id", example = "21958")
// private Integer operatorId;
//
// @Schema(description = "操作人", example = "张三")
// private String operatorName;
//
// @Schema(description = "关联单号")
// private String relarionNo;
@Schema(description = "操作人", example = "张三")
private String operatorName;
// @Schema(description = "关联单号id", example = "10903")
// private Integer relarionId;
@Schema(description = "关联单号")
private String relarionNo;
@Schema(description = "关联单号id", example = "10903")
private Integer relarionId;
@Schema(description = "单据类型(1 标准采购申请 2设备采购申请 3 标准采购退料 4设备采购退料)", example = "1")
private String billType;
@Schema(description = "来源单号")
private String sourceNo;
@Schema(description = "来源单号id", example = "27114")
private Integer sourceId;
// @Schema(description = "单据类型(1 标准采购申请 2设备采购申请 3 标准采购退料 4设备采购退料)", example = "1")
// private String billType;
//
// @Schema(description = "来源单号")
// private String sourceNo;
//
// @Schema(description = "来源单号id", example = "27114")
// private Integer sourceId;
List<ProStorageInsertReqVO> details;
}

View File

@ -70,6 +70,15 @@ public class ProStorageMatController {
return success(BeanUtils.toBean(proStorageMat, ProStorageMatRespVO.class));
}
@GetMapping("/getByStockId")
@Operation(summary = "获得成品入/出库子")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('twm:pro-storage-mat:query')")
public CommonResult<List<ProStorageMatRespVO>> getProStorageMatByStockId(@RequestParam("stockId") Integer stockId) {
List<ProStorageMatDO> proStorageMatByStockId = proStorageMatService.getProStorageMatByStockId(stockId);
return success(BeanUtils.toBean(proStorageMatByStockId, ProStorageMatRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得成品入/出库子分页")
@PreAuthorize("@ss.hasPermission('twm:pro-storage-mat:query')")

View File

@ -1,10 +1,8 @@
package com.ningxia.yunxi.chemmes.module.biz.dal.mysql.propack;
import java.util.*;
import com.ningxia.yunxi.chemmes.framework.common.pojo.PageResult;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.mapper.BaseMapperX;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.propack.vo.ProPackPageReqVO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.propack.ProPackDO;
import org.apache.ibatis.annotations.Mapper;
@ -51,7 +49,7 @@ public interface ProPackMapper extends BaseMapperX<ProPackDO> {
.eqIfPresent(ProPackDO::getLineCd, reqVO.getLineCd())
.likeIfPresent(ProPackDO::getLineName, reqVO.getLineName())
.eqIfPresent(ProPackDO::getLineId, reqVO.getLineId())
.eqIfPresent(ProPackDO::getPackStatus, reqVO.getPackStatus())
.inIfPresent(ProPackDO::getPackStatus, reqVO.getPackStatus())
.eqIfPresent(ProPackDO::getLossQty, reqVO.getLossQty())
.eqIfPresent(ProPackDO::getJudgResult, reqVO.getJudgResult())
.eqIfPresent(ProPackDO::getInspStatus, reqVO.getInspStatus())
@ -62,4 +60,4 @@ public interface ProPackMapper extends BaseMapperX<ProPackDO> {
.orderByDesc(ProPackDO::getProDate));
}
}
}

View File

@ -18,7 +18,7 @@ public interface ProStorageMapper extends BaseMapperX<ProStorageDO> {
default PageResult<ProStorageDO> selectPage(ProStoragePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<ProStorageDO>()
.eqIfPresent(ProStorageDO::getBillNo, reqVO.getBillNo())
.likeIfPresent(ProStorageDO::getBillNo, reqVO.getBillNo())
.eqIfPresent(ProStorageDO::getOperatorType, reqVO.getOperatorType())
.eqIfPresent(ProStorageDO::getBusinessType, reqVO.getBusinessType())
.eqIfPresent(ProStorageDO::getRemark, reqVO.getRemark())

View File

@ -7,6 +7,8 @@ import com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostoragemat.vo.Pr
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.prostoragemat.ProStorageMatDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 成品入/出库子 Mapper
*
@ -42,4 +44,11 @@ public interface ProStorageMatMapper extends BaseMapperX<ProStorageMatDO> {
.orderByDesc(ProStorageMatDO::getId));
}
default List<ProStorageMatDO> selectByStockId(Integer id) {
return selectList(new LambdaQueryWrapperX<ProStorageMatDO>().eq(ProStorageMatDO::getStockId, id));
}
default int deleteByStockId(Integer id) {
return delete(new LambdaQueryWrapperX<ProStorageMatDO>().eq(ProStorageMatDO::getStockId, id));
}
}

View File

@ -2,7 +2,6 @@ package com.ningxia.yunxi.chemmes.module.biz.service.prostorage;
import com.ningxia.yunxi.chemmes.framework.common.pojo.PageResult;
import com.ningxia.yunxi.chemmes.framework.common.util.CodeGenerateUtils;
import com.ningxia.yunxi.chemmes.framework.common.util.object.BeanUtils;
import com.ningxia.yunxi.chemmes.framework.security.core.util.SecurityFrameworkUtils;
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostorage.vo.ProStorageInsertReqVO;
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostorage.vo.ProStoragePageReqVO;
@ -51,25 +50,100 @@ public class ProStorageServiceImpl implements ProStorageService {
@Override
public Integer createProStorage(ProStorageSaveReqVO createReqVO) {
// 插入
ProStorageDO proStorage = BeanUtils.toBean(createReqVO, ProStorageDO.class);
proStorageMapper.insert(proStorage);
// 返回
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
AdminUserDO user = adminUserService.getUser(loginUserId);
String nickname = user.getNickname();
ProStorageDO proStorage = saveProStorage(23, loginUserId, nickname, "", null, createReqVO.getRemark());
saveMat(createReqVO, proStorage);
if ("2".equals(createReqVO.getStatus())) {
process(proStorage, loginUserId, nickname);
}
return proStorage.getId();
}
private void process(ProStorageDO proStorage, Long loginUserId, String nickname) {
List<ProStorageMatDO> proStorageMatDOS = proStorageMatMapper.selectByStockId(proStorage.getId());
for (ProStorageMatDO proStorageMatDO : proStorageMatDOS) {
ProStorageInventoryDO storageInventoryDO = proStorageInventoryService.getProStorageInventory(Long.valueOf(proStorageMatDO.getId()));
storageInventoryDO.setYardQty(storageInventoryDO.getYardQty().subtract(proStorageMatDO.getOperatorQty()));
storageInventoryDO.setPackQty(storageInventoryDO.getPackQty() - proStorageMatDO.getBagQty());
proStorageInventoryMapper.updateById(storageInventoryDO);
ProStorageMatDO proStorageMat = new ProStorageMatDO();
proStorageMat.setStockId(proStorage.getId());
proStorageMat.setDescription("");
proStorageMat.setStoreHouseId(storageInventoryDO.getStoreHouseId());
proStorageMat.setStoreAreaId(storageInventoryDO.getStoreAreaId());
proStorageMat.setStoreHouseCd(storageInventoryDO.getStoreHouseCd());
proStorageMat.setStoreHouseName(storageInventoryDO.getStoreHouseName());
proStorageMat.setStoreAreCd(storageInventoryDO.getStoreAreCd());
proStorageMat.setStoreAreaName(storageInventoryDO.getStoreAreaName());
proStorageMat.setMaterialId(storageInventoryDO.getMaterialId());
proStorageMat.setMatName(storageInventoryDO.getMatName());
proStorageMat.setMatCode(storageInventoryDO.getMatCode());
proStorageMat.setSpec(storageInventoryDO.getSpec());
proStorageMat.setUnit(storageInventoryDO.getUnit());
proStorageMat.setLotNo(storageInventoryDO.getLotNo());
proStorageMat.setOperatorQty(proStorageMatDO.getOperatorQty());
// proStorageMat.setSourceId(0L);
// proStorageMat.setRelarionId(0);
// proStorageMat.setBagSpec(0);
// proStorageMat.setBagQty(0);
// proStorageMat.setPlanId(0);
// proStorageMat.setProNo("");
proStorageMat.setInventBillNo(storageInventoryDO.getInventBillNo());
proStorageMatMapper.insert(proStorageMat);
ProStorageLogDO proStorageLog = new ProStorageLogDO();
// proStorageLog.setStockId(0);
// proStorageLog.setDescription("");
// proStorageLog.setStatus("");
proStorageLog.setStoreHouseId(storageInventoryDO.getStoreHouseId());
proStorageLog.setStoreAreaId(storageInventoryDO.getStoreAreaId());
proStorageLog.setStoreHouseCd(storageInventoryDO.getStoreHouseCd());
proStorageLog.setStoreHouseName(storageInventoryDO.getStoreHouseName());
proStorageLog.setStoreAreCd(storageInventoryDO.getStoreAreCd());
proStorageLog.setStoreAreaName(storageInventoryDO.getStoreAreaName());
proStorageLog.setMaterialId(storageInventoryDO.getMaterialId());
proStorageLog.setMatName(storageInventoryDO.getMatName());
proStorageLog.setMatCode(storageInventoryDO.getMatCode());
proStorageLog.setSpec(storageInventoryDO.getSpec());
proStorageLog.setUnit(storageInventoryDO.getUnit());
proStorageLog.setLotNo(storageInventoryDO.getLotNo());
proStorageLog.setOperatorQty(proStorageMatDO.getOperatorQty());
proStorageLog.setOperatorType("2");
proStorageLog.setBusinessType("23");
// proStorageLog.setStorageAft(new BigDecimal("0"));
// proStorageLog.setStorageBef(new BigDecimal("0"));
// proStorageLog.setStockItemId(0);
proStorageLog.setBillDate(LocalDate.now());
proStorageLog.setOperatorId(String.valueOf(loginUserId));
proStorageLog.setOperatorName(nickname);
proStorageLog.setRelarionNo(proStorage.getBillNo());
proStorageLog.setRelarionId(proStorage.getId());
proStorageLog.setRelarionDetailId(proStorageMat.getId());
// proStorageLog.setDpstNo("");
proStorageLog.setInventBillNo(storageInventoryDO.getInventBillNo());
proStorageLogService.saveProStorageLog(proStorageLog);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveProStorage(List<ProStorageInsertReqVO> reqVOS) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
AdminUserDO user = adminUserService.getUser(loginUserId);
String nickname = user.getNickname();
for (ProStorageInsertReqVO createReqVO : reqVOS) {
ProPackDO proPackDO = proPackMapper.selectById(createReqVO.getId());
ProStorageDO proStorage = saveProStorage(loginUserId, nickname, proPackDO);
ProStorageDO proStorage = saveProStorage(10, loginUserId, nickname, proPackDO.getPackNo(), proPackDO.getId(), "");
ProStorageInventoryDO storageInventoryDO = svaeStorageInventory(createReqVO, proPackDO);
saveLog(createReqVO, proStorage, proPackDO, storageInventoryDO, loginUserId, nickname);
proPackDO.setPackStatus("3");
proPackMapper.updateById(proPackDO);
}
}
@ -113,18 +187,19 @@ public class ProStorageServiceImpl implements ProStorageService {
}
@NotNull
private ProStorageDO saveProStorage(Long loginUserId, String nickname, ProPackDO proPackDO) {
private ProStorageDO saveProStorage(Integer businessType, Long loginUserId, String nickname,
String relationNo, Integer relationId, String remark) {
ProStorageDO proStorage = new ProStorageDO();
proStorage.setBillNo(generateBillNo("2"));
proStorage.setOperatorType(1);
proStorage.setBusinessType(10);
proStorage.setRemark("");
proStorage.setOperatorType(2);
proStorage.setBusinessType(businessType);
proStorage.setRemark(remark);
proStorage.setStatus(2);
proStorage.setBillDate(LocalDate.now());
proStorage.setOperatorId(loginUserId);
proStorage.setOperatorName(nickname);
proStorage.setRelarionNo(proPackDO.getPackNo());
proStorage.setRelarionId(proPackDO.getId());
proStorage.setRelarionNo(relationNo);
proStorage.setRelarionId(relationId);
// proStorage.setBillType("0");
// proStorage.setSourceNo();
// proStorage.setSourceId();
@ -194,11 +269,45 @@ public class ProStorageServiceImpl implements ProStorageService {
@Override
public void updateProStorage(ProStorageSaveReqVO updateReqVO) {
// 校验存在
validateProStorageExists(updateReqVO.getId());
// 更新
ProStorageDO updateObj = BeanUtils.toBean(updateReqVO, ProStorageDO.class);
proStorageMapper.updateById(updateObj);
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
AdminUserDO user = adminUserService.getUser(loginUserId);
String nickname = user.getNickname();
ProStorageDO proStorage = saveProStorage(23, loginUserId, nickname, "", null, updateReqVO.getRemark());
proStorageMatMapper.deleteByStockId(proStorage.getId());
saveMat(updateReqVO, proStorage);
if ("2".equals(updateReqVO.getStatus())) {
process(proStorage, loginUserId, nickname);
}
}
private void saveMat(ProStorageSaveReqVO updateReqVO, ProStorageDO proStorage) {
for (ProStorageInsertReqVO detail : updateReqVO.getDetails()) {
ProStorageInventoryDO storageInventoryDO = proStorageInventoryService.getProStorageInventory(Long.valueOf(detail.getId()));
ProStorageMatDO proStorageMat = new ProStorageMatDO();
proStorageMat.setStockId(proStorage.getId());
proStorageMat.setDescription("");
proStorageMat.setStoreHouseId(storageInventoryDO.getStoreHouseId());
proStorageMat.setStoreAreaId(storageInventoryDO.getStoreAreaId());
proStorageMat.setStoreHouseCd(storageInventoryDO.getStoreHouseCd());
proStorageMat.setStoreHouseName(storageInventoryDO.getStoreHouseName());
proStorageMat.setStoreAreCd(storageInventoryDO.getStoreAreCd());
proStorageMat.setStoreAreaName(storageInventoryDO.getStoreAreaName());
proStorageMat.setMaterialId(storageInventoryDO.getMaterialId());
proStorageMat.setMatName(storageInventoryDO.getMatName());
proStorageMat.setMatCode(storageInventoryDO.getMatCode());
proStorageMat.setSpec(storageInventoryDO.getSpec());
proStorageMat.setUnit(storageInventoryDO.getUnit());
proStorageMat.setLotNo(storageInventoryDO.getLotNo());
proStorageMat.setOperatorQty(detail.getReturnQty());
// proStorageMat.setSourceId(0L);
// proStorageMat.setRelarionId(0);
// proStorageMat.setBagSpec(0);
// proStorageMat.setBagQty(0);
// proStorageMat.setProNo("");
proStorageMat.setInventBillNo(storageInventoryDO.getInventBillNo());
proStorageMatMapper.insert(proStorageMat);
}
}
@Override
@ -227,9 +336,9 @@ public class ProStorageServiceImpl implements ProStorageService {
@Override
public String generateBillNo(String operatorType) {
String prefix = "CK";
String prefix = "RKD";
if ("2".equals(operatorType)) {
prefix = "RKD";
prefix = "CKD";
}
String maxBillNo = proStorageMapper.selectMaxBillNo();
return CodeGenerateUtils.generateBillNo(prefix, maxBillNo);

View File

@ -6,6 +6,7 @@ import com.ningxia.yunxi.chemmes.module.biz.controller.admin.prostoragemat.vo.Pr
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.prostoragemat.ProStorageMatDO;
import javax.validation.Valid;
import java.util.List;
/**
* 成品入/出库子 Service 接口
@ -44,6 +45,8 @@ public interface ProStorageMatService {
*/
ProStorageMatDO getProStorageMat(Integer id);
List<ProStorageMatDO> getProStorageMatByStockId(Integer id);
/**
* 获得成品入/出库子分页
*

View File

@ -11,6 +11,8 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static com.ningxia.yunxi.chemmes.framework.common.exception.util.ServiceExceptionUtil.exception;
/**
@ -62,6 +64,11 @@ public class ProStorageMatServiceImpl implements ProStorageMatService {
return proStorageMatMapper.selectById(id);
}
@Override
public List<ProStorageMatDO> getProStorageMatByStockId(Integer id) {
return proStorageMatMapper.selectByStockId(id);
}
@Override
public PageResult<ProStorageMatDO> getProStorageMatPage(ProStorageMatPageReqVO pageReqVO) {
return proStorageMatMapper.selectPage(pageReqVO);

View File

@ -0,0 +1,12 @@
{
"mcpServers": {
"lanhu-browser": {
"command": "node",
"args": ["./mcp-lanhu-server/index.js"],
"env": {
"EDGE_PROFILE_PATH": "./.agent-browser/profile",
"LANHU_BASE_URL": "https://lanhuapp.com"
}
}
}
}

View File

@ -0,0 +1,41 @@
import request from '@/config/axios'
export interface LotConfigVO {
id: number
remark: string
matCode: string
matName: string
matId: number
lotWgt: number
enabledStatus: boolean
}
// 查询产品批次重量配置分页
export const getLotConfigPage = async (params) => {
return await request.get({ url: `/biz/lot-config/page`, params })
}
// 查询产品批次重量配置详情
export const getLotConfig = async (id: number) => {
return await request.get({ url: `/biz/lot-config/get?id=` + id })
}
// 新增产品批次重量配置
export const createLotConfig = async (data: LotConfigVO) => {
return await request.post({ url: `/biz/lot-config/create`, data })
}
// 修改产品批次重量配置
export const updateLotConfig = async (data: LotConfigVO) => {
return await request.put({ url: `/biz/lot-config/update`, data })
}
// 删除产品批次重量配置
export const deleteLotConfig = async (id: number) => {
return await request.delete({ url: `/biz/lot-config/delete?id=` + id })
}
// 导出产品批次重量配置 Excel
export const exportLotConfig = async (params) => {
return await request.download({ url: `/biz/lot-config/export-excel`, params })
}

View File

@ -15,6 +15,7 @@ export interface ProStorageVO {
billType: string
sourceNo: string
sourceId: number
details: ProPackVO[]
}
export interface ProPackVO {
@ -23,7 +24,7 @@ export interface ProPackVO {
storeAreaId: number
storeHouseCd: string
storeHouseName: string
storeAreaCd: string
storeAreCd: string
storeAreaName: string
}

View File

@ -1,56 +1,61 @@
import request from '@/config/axios'
export interface ProStorageMatVO {
id: number
stockId: number
description: string
storeHouseId: number
storeAreaId: number
storeHouseCd: string
storeHouseName: string
storeAreCd: string
storeAreaName: string
materialId: number
matName: string
matCode: string
spec: string
unit: string
lotNo: string
operatorQty: number
sourceId: number
relarionId: number
bagSpec: number
bagQty: number
planId: number
proNo: string
}
// 查询成品入/出库子分页
export const getProStorageMatPage = async (params) => {
return await request.get({ url: `/twm/pro-storage-mat/page`, params })
}
// 查询成品入/出库子详情
export const getProStorageMat = async (id: number) => {
return await request.get({ url: `/twm/pro-storage-mat/get?id=` + id })
}
// 新增成品入/出库子
export const createProStorageMat = async (data: ProStorageMatVO) => {
return await request.post({ url: `/twm/pro-storage-mat/create`, data })
}
// 修改成品入/出库子
export const updateProStorageMat = async (data: ProStorageMatVO) => {
return await request.put({ url: `/twm/pro-storage-mat/update`, data })
}
// 删除成品入/出库子
export const deleteProStorageMat = async (id: number) => {
return await request.delete({ url: `/twm/pro-storage-mat/delete?id=` + id })
}
// 导出成品入/出库子 Excel
export const exportProStorageMat = async (params) => {
return await request.download({ url: `/twm/pro-storage-mat/export-excel`, params })
}
import request from '@/config/axios'
export interface ProStorageMatVO {
id: number
stockId: number
description: string
storeHouseId: number
storeAreaId: number
storeHouseCd: string
storeHouseName: string
storeAreCd: string
storeAreaName: string
materialId: number
matName: string
matCode: string
spec: string
unit: string
lotNo: string
operatorQty: number
sourceId: number
relarionId: number
bagSpec: number
bagQty: number
planId: number
proNo: string
}
// 查询成品入/出库子分页
export const getProStorageMatPage = async (params) => {
return await request.get({ url: `/twm/pro-storage-mat/page`, params })
}
// 查询成品入/出库子详情
export const getProStorageMat = async (id: number) => {
return await request.get({ url: `/twm/pro-storage-mat/get?id=` + id })
}
// 查询成品入/出库子分页
export const getProStorageMatPageByStockId = async (stockId: number) => {
return await request.get({ url: `/twm/pro-storage-mat/getByStockId?stockId=` + stockId })
}
// 新增成品入/出库子
export const createProStorageMat = async (data: ProStorageMatVO) => {
return await request.post({ url: `/twm/pro-storage-mat/create`, data })
}
// 修改成品入/出库子
export const updateProStorageMat = async (data: ProStorageMatVO) => {
return await request.put({ url: `/twm/pro-storage-mat/update`, data })
}
// 删除成品入/出库子
export const deleteProStorageMat = async (id: number) => {
return await request.delete({ url: `/twm/pro-storage-mat/delete?id=` + id })
}
// 导出成品入/出库子 Excel
export const exportProStorageMat = async (params) => {
return await request.download({ url: `/twm/pro-storage-mat/export-excel`, params })
}

View File

@ -74,6 +74,7 @@ declare module 'vue' {
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
@ -90,6 +91,7 @@ declare module 'vue' {
InputPassword: typeof import('./../components/InputPassword/src/InputPassword.vue')['default']
InputWithColor: typeof import('./../components/InputWithColor/index.vue')['default']
MagicCubeEditor: typeof import('./../components/MagicCubeEditor/index.vue')['default']
MaterialSelectDialog: typeof import('./../views/biz/proreturn/MaterialSelectDialog.vue')['default']
Pagination: typeof import('./../components/Pagination/index.vue')['default']
ProcessDesigner: typeof import('./../components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue')['default']
ProcessPalette: typeof import('./../components/bpmnProcessDesigner/package/palette/ProcessPalette.vue')['default']
@ -97,6 +99,8 @@ declare module 'vue' {
Propack: typeof import('./../api/biz/propack/index.ts')['default']
ProPackForm: typeof import('./../views/biz/prostorage/ProPackForm.vue')['default']
PropertiesPanel: typeof import('./../components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue')['default']
Proreturn: typeof import('./../views/biz/proreturn/index.vue')['default']
ProReturnForm: typeof import('./../views/biz/proreturn/ProReturnForm.vue')['default']
Prostorage: typeof import('./../views/biz/prostorage/index.vue')['default']
ProStorageForm: typeof import('./../views/biz/prostorage/ProStorageForm.vue')['default']
Qrcode: typeof import('./../components/Qrcode/src/Qrcode.vue')['default']
@ -109,6 +113,9 @@ declare module 'vue' {
ShortcutDateRangePicker: typeof import('./../components/ShortcutDateRangePicker/index.vue')['default']
SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default']
Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default']
StorageForm: typeof import('./../views/biz/proreturn/StorageForm.vue')['default']
Storagemat: typeof import('./../views/biz/storagemat/index.vue')['default']
StorageMatForm: typeof import('./../views/biz/storagemat/StorageMatForm.vue')['default']
SummaryCard: typeof import('./../components/SummaryCard/index.vue')['default']
Table: typeof import('./../components/Table/src/Table.vue')['default']
Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default']

View File

@ -0,0 +1,175 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="产品名称" prop="matName" required>
<div class="flex items-center gap-2">
<el-input v-model="formData.matName" placeholder="请选择产品名称" :disabled="formType === 'update'" class="flex-1" @click="openMaterialSelect"/>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格型号" prop="spec">
<el-input v-model="formData.spec" placeholder="请输入规格型号" :disabled="formType === 'update'"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="批次重量" prop="lotWgt" required>
<MoneyInput
v-model="formData.lotWgt"
:decimal-places="0"
:allow-negative="false"
:show-prefix="false"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="enabledStatus">
<el-select v-model="formData.enabledStatus" placeholder="请选择状态">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入描述" :rows="3" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<!-- 物料选择弹窗 -->
<MaterialSelect ref="materialSelectRef" @confirm="handleMaterialSelect" />
</template>
<script setup lang="ts">
import * as LotConfigApi from '@/api/biz/lotconfig'
import { watch } from 'vue'
import MaterialSelect from '@/views/biz/material/MaterialSelect.vue'
import MoneyInput from '@/views/biz/components/MoneyInput.vue'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
matCode: undefined,
matId: undefined,
matName: undefined,
spec: undefined,
lotWgt: undefined,
enabledStatus: 0,
description: undefined,
})
//
const materialSelectRef = ref()
/** 打开物料选择弹窗 */
const openMaterialSelect = () => {
materialSelectRef.value?.open()
}
/** 处理物料选择结果 */
const handleMaterialSelect = (items: any[]) => {
if (items && items.length > 0) {
const item = items[0]
formData.value.matName = item.matName
formData.value.spec = item.spec
formData.value.matCode = item.matCode
formData.value.matId = item.id
}
}
/** 弹窗关闭时通知父组件 */
watch(dialogVisible, (val) => {
if (!val) {
emit('close')
}
})
const formRules = reactive({
matName: [{ required: true, message: '产品名称不能为空', trigger: 'change' }],
spec: [{ required: true, message: '规格型号不能为空', trigger: 'change' }],
lotWgt: [{ required: true, message: '批次重量不能为空', trigger: 'blur' }],
enabledStatus: [{ required: true, message: '启用状态不能为空', trigger: 'change' }],
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await LotConfigApi.getLotConfig(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success', 'close']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as LotConfigApi.LotConfigVO
if (formType.value === 'create') {
await LotConfigApi.createLotConfig(data)
message.success(t('common.createSuccess'))
} else {
await LotConfigApi.updateLotConfig(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
matName: undefined,
matCode: undefined,
matId: undefined,
spec: undefined,
lotWgt: undefined,
enabledStatus: 0,
description: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,185 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="产品名称" prop="matName">
<el-input
v-model="queryParams.matName"
placeholder="请输入产品名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="启用状态" prop="enabledStatus">
<el-select
v-model="queryParams.enabledStatus"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['biz:lot-config:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="序号" align="center" type="index" width="60px"/>
<el-table-column label="产品编码" align="center" prop="matCode" />
<el-table-column label="产品名称" align="center" prop="matName" />
<el-table-column label="规格型号" align="center" prop="matId" />
<el-table-column label="批次重量" align="center" prop="lotWgt" />
<el-table-column label="状态" align="center" prop="enabledStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_STATUS" :value="scope.row.enabledStatus" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="创建人" align="center" prop="creatorName" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['biz:lot-config:update']"
>
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<LotConfigForm ref="formRef" @success="getList" @close="handleQuery"/>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as LotConfigApi from '@/api/biz/lotconfig'
import LotConfigForm from './LotConfigForm.vue'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'LotConfig' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
matName: undefined,
enabledStatus: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await LotConfigApi.getLotConfigPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await LotConfigApi.deleteLotConfig(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await LotConfigApi.exportLotConfig(queryParams)
download.excel(data, '产品批次重量配置.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
//
})
</script>

View File

@ -0,0 +1,441 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="90%">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<!-- 基本信息 -->
<div class="mb-6">
<div style="font-weight: bold; margin-bottom: 15px;">基本信息</div>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="业务类型" prop="businessType">
<el-select
v-model="formData.businessType"
placeholder="请选择"
:disabled="formType === 'detail'"
class="!w-full"
>
<el-option label="成品出库" value="21" />
<el-option label="盘亏出库" value="22" />
<el-option label="生产退库" value="23" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="退库单号" prop="billNo">
<el-input
v-model="formData.billNo"
placeholder="保存时自动生成"
disabled
class="!w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="退库日期" prop="billDate">
<el-date-picker
v-model="formData.billDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择日期"
:disabled="formType === 'detail'"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="单据状态" prop="status">
<el-select v-model="formData.status" disabled class="!w-full">
<el-option label="已创建" value="1" />
<el-option label="已提交" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
placeholder="请输入"
:disabled="formType === 'detail'"
class="!w-full"
type="textarea"
:rows="2"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 明细信息 -->
<div>
<div style="font-weight: bold; margin-bottom: 15px; display: flex; align-items: center;">
<span>明细信息</span>
<el-button
type="primary"
@click="openMaterialSelect"
v-if="formType !== 'detail'"
class="ml-4"
>
<Icon icon="ep:plus" class="mr-5px" />新增
</el-button>
</div>
<el-table
:data="detailList"
:stripe="true"
:show-overflow-tooltip="true"
border
style="width: 100%;"
>
<el-table-column label="序号" type="index" min-width="60px" align="center" />
<el-table-column label="产品名称" min-width="150px" align="center">
<template #default="scope">
<span>{{ scope.row.productName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="规格型号" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.spec || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="批次号" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.lotNo || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="库存数量" min-width="100px" align="center">
<template #default="scope">
<span>{{ scope.row.stockQty || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="库存袋数" min-width="100px" align="center">
<template #default="scope">
<span>{{ scope.row.stockBagQty || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="退库数量(*)" min-width="120px" align="center">
<template #default="scope">
<el-input
v-model="scope.row.returnQty"
placeholder="手动录入"
:disabled="formType === 'detail'"
class="bg-yellow-100"
@input="calculateBagQty(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="退库袋数(*)" min-width="120px" align="center">
<template #default="scope">
<el-input
v-model="scope.row.returnBagQty"
placeholder="手动录入"
:disabled="formType === 'detail'"
class="bg-yellow-100"
/>
</template>
</el-table-column>
<el-table-column label="仓储名称" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.storeHouseName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="库区名称" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.storeAreaName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="单位" min-width="80px" align="center">
<template #default="scope">
<span>{{ scope.row.unit || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="产品编码" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.productCode || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="单袋规格" min-width="100px" align="center">
<template #default="scope">
<span>{{ scope.row.bagSpec || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="生产计划号" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.planNo || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="存货账单号" min-width="120px" align="center">
<template #default="scope">
<span>{{ scope.row.stockBillNo || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" min-width="80px" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="formType !== 'detail'"
link
type="danger"
@click="deleteDetailRow(scope.$index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<!-- 库存选择弹窗 -->
<ProStorageInventorySelectDialog
ref="inventorySelectRef"
@select="handleInventorySelect"
@close="handleInventoryClose"
/>
<template #footer>
<div >
<el-button @click="submitForm('save')" type="primary" :disabled="formLoading">
保存
</el-button>
<el-button @click="submitForm('confirm')" type="primary" :disabled="formLoading">
确认
</el-button>
<el-button @click="dialogVisible = false">
取消
</el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import * as ProStorageApi from '@/api/biz/prostorage'
import * as ProStorageMatApi from '@/api/biz/prostoragemat'
import ProStorageInventorySelectDialog from '@/views/biz/prostorageinventory/ProStorageInventorySelectDialog.vue'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) //
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
billNo: undefined,
businessType: '23', // 退
billDate: new Date().toISOString().split('T')[0], //
status: '1', //
remark: undefined,
})
//
const detailList = ref([])
//
const inventorySelectRef = ref()
/** 打开库存选择弹窗 */
const openMaterialSelect = () => {
inventorySelectRef.value?.open()
}
/** 处理库存选择确认 */
const handleInventorySelect = (selectedInventory: any[]) => {
if (selectedInventory && selectedInventory.length > 0) {
selectedInventory.forEach(inventory => {
detailList.value.push({
id: inventory.id,
productName: inventory.matName,
spec: inventory.spec,
lotNo: inventory.lotNo,
stockQty: inventory.yardQty,
stockBagQty: inventory.packQty,
returnQty: undefined,
returnBagQty: undefined,
storeHouseName: inventory.storeHouseName,
storeAreaName: inventory.storeAreaName,
unit: inventory.unit,
productCode: inventory.matCode,
bagSpec: inventory.bagSpec,
planNo: inventory.planNo,
stockBillNo: inventory.inventBillNo,
materialId: inventory.materialId,
storeHouseId: inventory.storeHouseId,
storeAreaId: inventory.storeAreaId,
})
})
}
}
/** 处理库存选择关闭 */
const handleInventoryClose = () => {
//
}
/** 弹窗关闭时通知父组件 */
watch(dialogVisible, (val) => {
if (!val) {
emit('close')
}
})
const formRules = reactive({
businessType: [{ required: true, message: '业务类型不能为空', trigger: 'change' }],
billDate: [{ required: true, message: '退库日期不能为空', trigger: 'change' }],
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
const data = await ProStorageApi.getProStorage(id)
formData.value = {
...formData.value,
...data,
}
//
await loadDetailList(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 加载明细列表 */
const loadDetailList = async (stockId: number) => {
try {
const data = await ProStorageMatApi.getProStorageMatPageByStockId(stockId)
detailList.value = (data.list || []).map(item => ({
...item,
returnQty: item.operatorQty, // 退
returnBagQty: item.bagQty, // 退
}))
} catch (error) {
console.error('加载明细列表失败:', error)
detailList.value = []
}
}
/** 删除明细行 */
const deleteDetailRow = (index: number) => {
detailList.value.splice(index, 1)
}
/** 计算袋数(根据退库数量和单袋规格) */
const calculateBagQty = (row: any) => {
// 退
const returnQty = parseFloat(row.returnQty) || 0
const stockQty = parseFloat(row.stockQty) || 0
if (returnQty > stockQty) {
message.warning('退库数量不能大于库存数量')
row.returnQty = stockQty.toString()
}
//
if (row.returnQty && row.bagSpec) {
row.returnBagQty = Math.ceil(parseFloat(row.returnQty) / parseFloat(row.bagSpec))
}
}
/** 提交表单 */
const emit = defineEmits(['success', 'close']) // success
const submitForm = async (action: string) => {
//
await formRef.value.validate()
//
if (detailList.value.length === 0) {
message.error('请至少添加一条明细')
return
}
//
for (let i = 0; i < detailList.value.length; i++) {
const row = detailList.value[i]
if (!row.returnQty) {
message.error(`${i + 1}行退库数量不能为空`)
return
}
if (!row.returnBagQty) {
message.error(`${i + 1}行退库袋数不能为空`)
return
}
}
//
formLoading.value = true
try {
const submitData = {
...formData.value,
details: detailList.value.map(item => ({
id: item.id,
returnQty: item.returnQty, // 退
returnBagQty: item.returnBagQty, // 退
})),
}
if (formType.value === 'create') {
if (action === 'confirm') {
submitData.status = '2' //
}
await ProStorageApi.createProStorage(submitData)
message.success(t('common.createSuccess'))
} else {
if (action === 'confirm') {
submitData.status = '2' //
}
await ProStorageApi.updateProStorage(submitData)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} catch (error) {
console.error('提交失败:', error)
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
billNo: undefined,
businessType: '23',
billDate: new Date().toISOString().split('T')[0], //
status: '1',
remark: undefined,
}
detailList.value = []
formRef.value?.resetFields()
}
</script>
<style scoped>
.bg-yellow-100 {
background-color: #fef3c7;
}
</style>

View File

@ -0,0 +1,309 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="退库日期" prop="billDate">
<el-date-picker
v-model="queryParams.billDate"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="退库单号" prop="billNo">
<el-input
v-model="queryParams.billNo"
placeholder="请输入退库单号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择单据状态"
clearable
class="!w-200px"
>
<el-option label="已创建" value="1" />
<el-option label="已提交" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['twm:storage:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 退库单信息主表 -->
<ContentWrap>
<el-card class="mb-10px">
<template #header>
<span class="font-bold">退库单信息</span>
</template>
<el-table
ref="masterTableRef"
v-loading="loading"
:data="masterList"
:stripe="true"
:show-overflow-tooltip="true"
@row-click="handleMasterRowClick"
highlight-current-row
>
<el-table-column type="selection" width="55px" align="center" />
<el-table-column label="序号" align="center" type="index" width="60px" />
<el-table-column label="业务类型" align="center" prop="businessType" min-width="100px">
<template #default="scope">
<span>{{ getBusinessTypeName(scope.row.businessType) }}</span>
</template>
</el-table-column>
<el-table-column label="退库单号" align="center" prop="billNo" min-width="150px" />
<el-table-column label="单据状态" align="center" prop="status" min-width="100px">
<template #default="scope">
<span :class="scope.row.status === '1' ? 'text-blue' : 'text-green'">
{{ scope.row.status === '1' ? '已创建' : '已提交' }}
</span>
</template>
</el-table-column>
<el-table-column label="退库日期" align="center" prop="billDate" :formatter="dateFormatter2" min-width="120px" />
<el-table-column label="退库人员" align="center" prop="operatorName" min-width="100px" />
<el-table-column label="操作" align="center" width="200px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['twm:storage:update']"
>
编辑
</el-button>
<el-button
link
type="info"
@click="handleViewDetail(scope.row)"
>
详情
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['twm:storage:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 主表分页 -->
<Pagination
:total="masterTotal"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getMasterList"
/>
</el-card>
<!-- 退库明细信息子表 -->
<el-card>
<template #header>
<span class="font-bold">退库明细信息</span>
</template>
<el-table
v-loading="subLoading"
:data="subList"
:stripe="true"
:show-overflow-tooltip="true"
show-summary
:summary-method="getSummary"
>
<el-table-column label="序号" align="center" type="index" width="60px" />
<el-table-column label="产品编码" align="center" prop="matCode" min-width="120px" />
<el-table-column label="产品名称" align="center" prop="matName" min-width="150px" />
<el-table-column label="规格型号" align="center" prop="spec" min-width="120px" />
<el-table-column label="退库数量" align="center" prop="operatorQty" min-width="100px" />
<el-table-column label="退库袋数" align="center" prop="bagQty" min-width="100px" />
<el-table-column label="批次号" align="center" prop="lotNo" min-width="120px" />
<el-table-column label="仓储名称" align="center" prop="storeHouseName" min-width="120px" />
<el-table-column label="库区名称" align="center" prop="storeAreaName" min-width="120px" />
<el-table-column label="单位" align="center" prop="unit" min-width="80px" :formatter="getUnitName" />
</el-table>
</el-card>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<ProReturnForm ref="formRef" @success="handleMasterRowClick(currentMasterRow)" @close="handleQuery"/>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { dateFormatter2, getCurrentSimMonthRange } from '@/utils/formatTime'
import * as ProStorageApi from '@/api/biz/prostorage'
import * as ProStorageMatApi from '@/api/biz/prostoragemat'
import ProReturnForm from './ProReturnForm.vue'
import { getUnitName, getDictOptions, DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'ProReturn' })
const message = useMessage() //
const { t } = useI18n() //
// ========== ==========
const loading = ref(false) //
const masterList = ref([]) //
const masterTotal = ref(0) //
const masterTableRef = ref() //
const currentMasterRow = ref(null) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
billNo: undefined,
billDate: getCurrentSimMonthRange(),
status: undefined,
operatorType: "2",
})
const queryFormRef = ref() //
/** 获取业务类型名称 */
const getBusinessTypeName = (type) => {
const typeMap = {
'21': '成品出库',
'22': '盘亏出库',
'23': '生产退库',
}
return typeMap[type] || type
}
/** 查询主表列表 */
const getMasterList = async () => {
loading.value = true
try {
const data = await ProStorageApi.getProStoragePage(queryParams)
masterList.value = data.list
masterTotal.value = data.total
//
if (data.list && data.list.length > 0) {
handleMasterRowClick(data.list[0])
}
} finally {
loading.value = false
}
}
/** 主表行点击事件 */
const handleMasterRowClick = async (row) => {
if (!row) return
currentMasterRow.value = row
await getSubList(row.id)
}
// ========== ==========
const subLoading = ref(false) //
const subList = ref([]) //
/** 查询子表列表 */
const getSubList = async (stockId: number) => {
if (!stockId) {
subList.value = []
return
}
subLoading.value = true
try {
const data = await ProStorageMatApi.getProStorageMatPageByStockId(stockId)
subList.value = data || []
} finally {
subLoading.value = false
}
}
/** 子表汇总方法 */
const getSummary = (param) => {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计'
} else if (column.prop === 'operatorQty') {
const values = data.map(item => Number(item[column.prop]) || 0)
sums[index] = values.reduce((prev, curr) => prev + curr, 0)
} else if (column.prop === 'bagQty') {
const values = data.map(item => Number(item[column.prop]) || 0)
sums[index] = values.reduce((prev, curr) => prev + curr, 0)
} else {
sums[index] = ''
}
})
return sums
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getMasterList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 查看详情 */
const handleViewDetail = (row) => {
handleMasterRowClick(row)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await ProStorageApi.deleteProStorage(id)
message.success(t('common.delSuccess'))
//
await getMasterList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
//
})
</script>
<style scoped>
.text-blue {
color: #409EFF;
}
.text-green {
color: #67C23A;
}
</style>

View File

@ -0,0 +1,282 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-form-item label="包装单号(PK+年份+月份+4位流水号" prop="packNo">
<el-input v-model="formData.packNo" placeholder="请输入包装单号(PK+年份+月份+4位流水号" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="产品id" prop="materialId">
<el-input v-model="formData.materialId" placeholder="请输入产品id" />
</el-form-item>
<el-form-item label="产品编码" prop="materialCode">
<el-input v-model="formData.materialCode" placeholder="请输入产品编码" />
</el-form-item>
<el-form-item label="产品名称" prop="materialName">
<el-input v-model="formData.materialName" placeholder="请输入产品名称" />
</el-form-item>
<el-form-item label="规格型号" prop="spec">
<el-input v-model="formData.spec" placeholder="请输入规格型号" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model="formData.unit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="包装实绩数量" prop="packQtyAct">
<el-input v-model="formData.packQtyAct" placeholder="请输入包装实绩数量" />
</el-form-item>
<el-form-item label="包装袋数" prop="packBagQty">
<el-input v-model="formData.packBagQty" placeholder="请输入包装袋数" />
</el-form-item>
<el-form-item label="单袋规格" prop="bagSpec">
<el-input v-model="formData.bagSpec" placeholder="请输入单袋规格" />
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input v-model="formData.lotNo" placeholder="请输入批次号" />
</el-form-item>
<el-form-item label="物料类型(1 原材料 2 半成品 3 成品 4 联产品 )" prop="matType">
<el-select v-model="formData.matType" placeholder="请选择物料类型(1 原材料 2 半成品 3 成品 4 联产品 )">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="包装开始时间" prop="packBgDtime">
<el-date-picker
v-model="formData.packBgDtime"
type="date"
value-format="x"
placeholder="选择包装开始时间"
/>
</el-form-item>
<el-form-item label="包装结束时间" prop="outEndDtime">
<el-date-picker
v-model="formData.outEndDtime"
type="date"
value-format="x"
placeholder="选择包装结束时间"
/>
</el-form-item>
<el-form-item label="包装日期" prop="proDate">
<el-date-picker
v-model="formData.proDate"
type="date"
value-format="x"
placeholder="选择包装日期"
/>
</el-form-item>
<el-form-item label="包装班组" prop="groupCd">
<el-input v-model="formData.groupCd" placeholder="请输入包装班组" />
</el-form-item>
<el-form-item label="包装班次" prop="shiftCd">
<el-input v-model="formData.shiftCd" placeholder="请输入包装班次" />
</el-form-item>
<el-form-item label="生产计划id" prop="planId">
<el-input v-model="formData.planId" placeholder="请输入生产计划id" />
</el-form-item>
<el-form-item label="生产计划号" prop="planNo">
<el-input v-model="formData.planNo" placeholder="请输入生产计划号" />
</el-form-item>
<el-form-item label="机台编码" prop="machineCd">
<el-input v-model="formData.machineCd" placeholder="请输入机台编码" />
</el-form-item>
<el-form-item label="机台名称" prop="machineName">
<el-input v-model="formData.machineName" placeholder="请输入机台名称" />
</el-form-item>
<el-form-item label="机台id" prop="machineId">
<el-input v-model="formData.machineId" placeholder="请输入机台id" />
</el-form-item>
<el-form-item label="产线编码" prop="lineCd">
<el-input v-model="formData.lineCd" placeholder="请输入产线编码" />
</el-form-item>
<el-form-item label="产线名称" prop="lineName">
<el-input v-model="formData.lineName" placeholder="请输入产线名称" />
</el-form-item>
<el-form-item label="产线id" prop="lineId">
<el-input v-model="formData.lineId" placeholder="请输入产线id" />
</el-form-item>
<el-form-item label="入库状态( 1 待入库 2 部分入库 3 已入库)" prop="packStatus">
<el-radio-group v-model="formData.packStatus">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="损耗数量" prop="lossQty">
<el-input v-model="formData.lossQty" placeholder="请输入损耗数量" />
</el-form-item>
<el-form-item label="判定结果(0 合格 1 不合格 2让步接收)" prop="judgResult">
<el-input v-model="formData.judgResult" placeholder="请输入判定结果(0 合格 1 不合格 2让步接收)" />
</el-form-item>
<el-form-item label="检验状态0-未检验 1已检验" prop="inspStatus">
<el-radio-group v-model="formData.inspStatus">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="包装理论数量" prop="packQtyTheory">
<el-input v-model="formData.packQtyTheory" placeholder="请输入包装理论数量" />
</el-form-item>
<el-form-item label="检验日期" prop="inspDate">
<el-date-picker
v-model="formData.inspDate"
type="date"
value-format="x"
placeholder="选择检验日期"
/>
</el-form-item>
<el-form-item label="检验人员id" prop="checkUserId">
<el-input v-model="formData.checkUserId" placeholder="请输入检验人员id" />
</el-form-item>
<el-form-item label="检验人员名称" prop="checkUserName">
<el-input v-model="formData.checkUserName" placeholder="请输入检验人员名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as ProPackApi from '@/api/biz/propack'
import { watch } from 'vue'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
packNo: undefined,
remark: undefined,
materialId: undefined,
materialCode: undefined,
materialName: undefined,
spec: undefined,
unit: undefined,
packQtyAct: undefined,
packBagQty: undefined,
bagSpec: undefined,
lotNo: undefined,
matType: undefined,
packBgDtime: undefined,
outEndDtime: undefined,
proDate: undefined,
groupCd: undefined,
shiftCd: undefined,
planId: undefined,
planNo: undefined,
machineCd: undefined,
machineName: undefined,
machineId: undefined,
lineCd: undefined,
lineName: undefined,
lineId: undefined,
packStatus: undefined,
lossQty: undefined,
judgResult: undefined,
inspStatus: undefined,
packQtyTheory: undefined,
inspDate: undefined,
checkUserId: undefined,
checkUserName: undefined,
})
/** 弹窗关闭时通知父组件 */
watch(dialogVisible, (val) => {
if (!val) {
emit('close')
}
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProPackApi.getProPack(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success', 'close']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as ProPackApi.ProPackVO
if (formType.value === 'create') {
await ProPackApi.createProPack(data)
message.success(t('common.createSuccess'))
} else {
await ProPackApi.updateProPack(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
packNo: undefined,
remark: undefined,
materialId: undefined,
materialCode: undefined,
materialName: undefined,
spec: undefined,
unit: undefined,
packQtyAct: undefined,
packBagQty: undefined,
bagSpec: undefined,
lotNo: undefined,
matType: undefined,
packBgDtime: undefined,
outEndDtime: undefined,
proDate: undefined,
groupCd: undefined,
shiftCd: undefined,
planId: undefined,
planNo: undefined,
machineCd: undefined,
machineName: undefined,
machineId: undefined,
lineCd: undefined,
lineName: undefined,
lineId: undefined,
packStatus: undefined,
lossQty: undefined,
judgResult: undefined,
inspStatus: undefined,
packQtyTheory: undefined,
inspDate: undefined,
checkUserId: undefined,
checkUserName: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,338 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="包装日期" prop="proDate">
<el-date-picker
v-model="queryParams.proDate"
value-format="YYYY-MM-DD"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="包装机台" prop="machineCd">
<el-select
v-model="queryParams.machineCd"
placeholder="请选择包装机台"
clearable
class="!w-240px"
>
<el-option
v-for="item in machineOptions"
:key="item.id"
:label="item.machineName"
:value="item.machineCd"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
<el-button type="primary" plain>
<Icon icon="ep:grid" class="mr-5px" /> 垛位推荐
</el-button>
<el-button type="success" plain @click="handleStorage">
<Icon icon="ep:download" class="mr-5px" /> 入库
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" ref="tableRef">
<el-table-column type="selection" width="55px" align="center" fixed="left" />
<el-table-column label="序号" align="center" type="index" width="60px" fixed="left" />
<el-table-column label="产品名称" align="center" prop="materialName" width="150px" fixed="left" />
<el-table-column label="规格型号" align="center" prop="spec" width="120px" fixed="left" />
<el-table-column label="批次号" align="center" prop="lotNo" width="150px" fixed="left" />
<el-table-column label="仓储 (*)" min-width="150px" align="center">
<template #default="scope">
<!-- pack_status = 3 时显示文本其他状态显示下拉框 -->
<template v-if="scope.row.packStatus === '3'">
<span>{{ scope.row.warehouseName}}</span>
</template>
<el-select
v-else
v-model="scope.row.storeHouseId"
placeholder="请选择仓储"
class="!w-full"
@change="handleWarehouseChange(scope.row)"
>
<el-option
v-for="item in warehouseOptions"
:key="item.id"
:label="item.storeHouseName"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="库区 (*)" min-width="150px" align="center">
<template #default="scope">
<!-- pack_status = 3 时显示文本其他状态显示下拉框 -->
<template v-if="scope.row.packStatus === '3'">
<span>{{ scope.row.areaName }}</span>
</template>
<el-select
v-else
v-model="scope.row.storeAreaId"
placeholder="请选择库区"
class="!w-full"
@change="handleAreaChange(scope.row)"
>
<el-option
v-for="item in (scope.row.areaOptions || [])"
:key="item.id"
:label="item.storeAreaName"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="是否检验" align="center" prop="inspStatus" width="100px">
<template #default="scope">
<el-tag :type="scope.row.inspStatus === '1' ? 'success' : 'info'">
{{ scope.row.inspStatus === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="检验结果" align="center" prop="judgResult" width="100px">
<template #default="scope">
<el-tag :type="scope.row.judgResult === '0' ? 'success' : (scope.row.judgResult === '1' ? 'danger' : 'warning')">
{{ scope.row.judgResult === '0' ? '合格' : (scope.row.judgResult === '1' ? '不合格' : '让步接收') }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="包装袋数" align="center" prop="packBagQty" width="100px" />
<el-table-column label="单袋规格" align="center" prop="bagSpec" width="100px" />
<el-table-column label="包装数量" align="center" prop="packQtyAct" width="100px" />
<el-table-column label="包装机台" align="center" prop="machineName" width="120px" />
<el-table-column label="活化炉机台" align="center" prop="activationMachineName" width="120px" />
<el-table-column label="生产计划号" align="center" prop="planNo" width="150px" />
<el-table-column label="单位" align="center" prop="unit" width="80px" />
<el-table-column label="包装日期" align="center" prop="proDate" width="110px" :formatter="dateFormatter2" />
<el-table-column label="包装单号" align="center" prop="packNo" width="180px" />
<el-table-column label="班次" align="center" prop="shiftCd" width="100px" />
<el-table-column label="产品编码" align="center" prop="materialCode" width="180px" />
<el-table-column label="备注" align="center" prop="remark" width="180px" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { dateFormatter2, getCurrentSimMonthRange } from '@/utils/formatTime'
import * as ProPackApi from '@/api/biz/propack'
import * as ProStorageApi from '@/api/biz/prostorage'
import * as WarehouseApi from '@/api/biz/storehouse'
import * as AreaApi from '@/api/biz/storearea'
import * as MachineApi from '@/api/biz/machine'
defineOptions({ name: 'ProStorage' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref([]) //
const total = ref(0) //
const warehouseOptions = ref([]) //
const machineOptions = ref([]) //
const tableRef = ref() //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
proDate: getCurrentSimMonthRange(),
machineCd: undefined,
packStatus: ["1", "2"]
})
const queryFormRef = ref() //
/** 加载包装机台列表 */
const loadMachineList = async () => {
try {
const data = await MachineApi.getMachineDropdown({})
machineOptions.value = data || []
} catch (error) {
console.error('加载包装机台列表失败:', error)
}
}
/** 加载仓库列表 */
const loadWarehouseList = async () => {
try {
const list = await WarehouseApi.getStoreHouseSelect("1")
warehouseOptions.value = (list || []).map((item: any) => ({
...item,
id: Number(item.id)
}))
} catch (error) {
console.error('加载仓库列表失败:', error)
}
}
/** 处理仓库变化,重新加载库区 */
const handleWarehouseChange = async (row: any) => {
row.storeAreaId = undefined //
row.storeAreaName = undefined
if (row.storeHouseId) {
//
const warehouse = warehouseOptions.value.find((w: any) => w.id === row.storeHouseId)
if (warehouse) {
row.storeHouseCd = warehouse.storeHouseCd
row.storeHouseName = warehouse.storeHouseName
}
try {
const list = await AreaApi.getStoreAreaSelect(row.storeHouseId)
row.areaOptions = (list || []).map((area: any) => ({
...area,
id: Number(area.id)
}))
} catch (error) {
console.error('加载库区列表失败:', error)
row.areaOptions = []
}
} else {
row.areaOptions = []
row.storeHouseCd = undefined
row.storeHouseName = undefined
}
}
/** 处理库区变化,获取库区信息 */
const handleAreaChange = (row: any) => {
if (row.storeAreaId && row.areaOptions) {
const area = row.areaOptions.find((a: any) => a.id === row.storeAreaId)
if (area) {
row.storeAreCd = area.storeAreCd
row.storeAreaName = area.storeAreaName
}
} else {
row.storeAreCd = undefined
row.storeAreaName = undefined
}
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProPackApi.getProPackPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 入库操作 */
const handleStorage = async () => {
//
const selectedRows = tableRef.value?.getSelectionRows() || []
if (selectedRows.length === 0) {
message.warning('请至少选择一条记录')
return
}
//
const invalidRows = []
const validData = []
selectedRows.forEach((row, index) => {
// packStatus = 3
if (row.packStatus === '3') {
invalidRows.push(`${index + 1}条记录(批次号: ${row.lotNo})已入库,无法重复入库`)
return
}
//
if (!row.storeHouseId) {
invalidRows.push(`${index + 1}条记录(批次号: ${row.lotNo})请选择仓储`)
return
}
//
if (!row.storeAreaId) {
invalidRows.push(`${index + 1}条记录(批次号: ${row.lotNo})请选择库区`)
return
}
//
validData.push({
id: row.id,
storeHouseId: row.storeHouseId,
storeAreaId: row.storeAreaId,
storeHouseCd: row.storeHouseCd,
storeHouseName: row.storeHouseName,
storeAreCd: row.storeAreCd,
storeAreaName: row.storeAreaName,
lotNo: row.lotNo,
})
})
//
if (invalidRows.length > 0) {
message.error(invalidRows.join('\n'))
return
}
//
try {
// API
await ProStorageApi.saveProStorage(validData)
console.log('准备入库的数据:', validData)
message.success('入库成功')
//
handleQuery()
//
tableRef.value?.clearSelection()
} catch (error) {
console.error('入库失败:', error)
message.error('入库失败')
}
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 初始化 **/
onMounted(() => {
//
loadMachineList()
//
loadWarehouseList()
//
})
</script>

View File

@ -0,0 +1,187 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-form-item label="入/出库Id" prop="stockId">
<el-input v-model="formData.stockId" placeholder="请输入入/出库Id" />
</el-form-item>
<el-form-item label="备注" prop="description">
<Editor v-model="formData.description" height="150px" />
</el-form-item>
<el-form-item label="仓储id" prop="storeHouseId">
<el-input v-model="formData.storeHouseId" placeholder="请输入仓储id" />
</el-form-item>
<el-form-item label="库区id" prop="storeAreaId">
<el-input v-model="formData.storeAreaId" placeholder="请输入库区id" />
</el-form-item>
<el-form-item label="仓储编码" prop="storeHouseCd">
<el-input v-model="formData.storeHouseCd" placeholder="请输入仓储编码" />
</el-form-item>
<el-form-item label="仓储名称" prop="storeHouseName">
<el-input v-model="formData.storeHouseName" placeholder="请输入仓储名称" />
</el-form-item>
<el-form-item label="库区编码" prop="storeAreCd">
<el-input v-model="formData.storeAreCd" placeholder="请输入库区编码" />
</el-form-item>
<el-form-item label="库区名称" prop="storeAreaName">
<el-input v-model="formData.storeAreaName" placeholder="请输入库区名称" />
</el-form-item>
<el-form-item label="物料id" prop="materialId">
<el-input v-model="formData.materialId" placeholder="请输入物料id" />
</el-form-item>
<el-form-item label="物料名称" prop="matName">
<el-input v-model="formData.matName" placeholder="请输入物料名称" />
</el-form-item>
<el-form-item label="物料编码" prop="matCode">
<el-input v-model="formData.matCode" placeholder="请输入物料编码" />
</el-form-item>
<el-form-item label="规格型号" prop="spec">
<el-input v-model="formData.spec" placeholder="请输入规格型号" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model="formData.unit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input v-model="formData.lotNo" placeholder="请输入批次号" />
</el-form-item>
<el-form-item label="操作数量" prop="operatorQty">
<el-input v-model="formData.operatorQty" placeholder="请输入操作数量" />
</el-form-item>
<el-form-item label="采购数量" prop="purQty">
<el-input v-model="formData.purQty" placeholder="请输入采购数量" />
</el-form-item>
<el-form-item label="来源数量" prop="sourceQty">
<el-input v-model="formData.sourceQty" placeholder="请输入来源数量" />
</el-form-item>
<el-form-item label="来源单号id" prop="sourceId">
<el-input v-model="formData.sourceId" placeholder="请输入来源单号id" />
</el-form-item>
<el-form-item label="关联单号id" prop="relarionId">
<el-input v-model="formData.relarionId" placeholder="请输入关联单号id" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as StorageMatApi from '@/api/twm/storagemat'
import { watch } from 'vue'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
stockId: undefined,
description: undefined,
storeHouseId: undefined,
storeAreaId: undefined,
storeHouseCd: undefined,
storeHouseName: undefined,
storeAreCd: undefined,
storeAreaName: undefined,
materialId: undefined,
matName: undefined,
matCode: undefined,
spec: undefined,
unit: undefined,
lotNo: undefined,
operatorQty: undefined,
purQty: undefined,
sourceQty: undefined,
sourceId: undefined,
relarionId: undefined,
})
/** 弹窗关闭时通知父组件 */
watch(dialogVisible, (val) => {
if (!val) {
emit('close')
}
})
const formRules = reactive({
stockId: [{ required: true, message: '入/出库Id不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await StorageMatApi.getStorageMat(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success', 'close']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as StorageMatApi.StorageMatVO
if (formType.value === 'create') {
await StorageMatApi.createStorageMat(data)
message.success(t('common.createSuccess'))
} else {
await StorageMatApi.updateStorageMat(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
stockId: undefined,
description: undefined,
storeHouseId: undefined,
storeAreaId: undefined,
storeHouseCd: undefined,
storeHouseName: undefined,
storeAreCd: undefined,
storeAreaName: undefined,
materialId: undefined,
matName: undefined,
matCode: undefined,
spec: undefined,
unit: undefined,
lotNo: undefined,
operatorQty: undefined,
purQty: undefined,
sourceQty: undefined,
sourceId: undefined,
relarionId: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,375 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="入/出库Id" prop="stockId">
<el-input
v-model="queryParams.stockId"
placeholder="请输入入/出库Id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="仓储id" prop="storeHouseId">
<el-input
v-model="queryParams.storeHouseId"
placeholder="请输入仓储id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="库区id" prop="storeAreaId">
<el-input
v-model="queryParams.storeAreaId"
placeholder="请输入库区id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="仓储编码" prop="storeHouseCd">
<el-input
v-model="queryParams.storeHouseCd"
placeholder="请输入仓储编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="仓储名称" prop="storeHouseName">
<el-input
v-model="queryParams.storeHouseName"
placeholder="请输入仓储名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="库区编码" prop="storeAreCd">
<el-input
v-model="queryParams.storeAreCd"
placeholder="请输入库区编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="库区名称" prop="storeAreaName">
<el-input
v-model="queryParams.storeAreaName"
placeholder="请输入库区名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="物料id" prop="materialId">
<el-input
v-model="queryParams.materialId"
placeholder="请输入物料id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="物料名称" prop="matName">
<el-input
v-model="queryParams.matName"
placeholder="请输入物料名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="物料编码" prop="matCode">
<el-input
v-model="queryParams.matCode"
placeholder="请输入物料编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="规格型号" prop="spec">
<el-input
v-model="queryParams.spec"
placeholder="请输入规格型号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input
v-model="queryParams.unit"
placeholder="请输入单位"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input
v-model="queryParams.lotNo"
placeholder="请输入批次号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="操作数量" prop="operatorQty">
<el-input
v-model="queryParams.operatorQty"
placeholder="请输入操作数量"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="采购数量" prop="purQty">
<el-input
v-model="queryParams.purQty"
placeholder="请输入采购数量"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="来源数量" prop="sourceQty">
<el-input
v-model="queryParams.sourceQty"
placeholder="请输入来源数量"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="来源单号id" prop="sourceId">
<el-input
v-model="queryParams.sourceId"
placeholder="请输入来源单号id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="关联单号id" prop="relarionId">
<el-input
v-model="queryParams.relarionId"
placeholder="请输入关联单号id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['twm:storage-mat:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['twm:storage-mat:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="序号" align="center" type="index" width="60px"/>
<el-table-column label="入/出库Id" align="center" prop="stockId" />
<el-table-column label="备注" align="center" prop="description" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="仓储id" align="center" prop="storeHouseId" />
<el-table-column label="库区id" align="center" prop="storeAreaId" />
<el-table-column label="仓储编码" align="center" prop="storeHouseCd" />
<el-table-column label="仓储名称" align="center" prop="storeHouseName" />
<el-table-column label="库区编码" align="center" prop="storeAreCd" />
<el-table-column label="库区名称" align="center" prop="storeAreaName" />
<el-table-column label="物料id" align="center" prop="materialId" />
<el-table-column label="物料名称" align="center" prop="matName" />
<el-table-column label="物料编码" align="center" prop="matCode" />
<el-table-column label="规格型号" align="center" prop="spec" />
<el-table-column label="单位" align="center" prop="unit" />
<el-table-column label="批次号" align="center" prop="lotNo" />
<el-table-column label="操作数量" align="center" prop="operatorQty" />
<el-table-column label="采购数量" align="center" prop="purQty" />
<el-table-column label="来源数量" align="center" prop="sourceQty" />
<el-table-column label="来源单号id" align="center" prop="sourceId" />
<el-table-column label="关联单号id" align="center" prop="relarionId" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['twm:storage-mat:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['twm:storage-mat:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<StorageMatForm ref="formRef" @success="getList" @close="handleQuery"/>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as StorageMatApi from '@/api/twm/storagemat'
import StorageMatForm from './StorageMatForm.vue'
defineOptions({ name: 'StorageMat' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
stockId: undefined,
description: undefined,
createTime: [],
storeHouseId: undefined,
storeAreaId: undefined,
storeHouseCd: undefined,
storeHouseName: undefined,
storeAreCd: undefined,
storeAreaName: undefined,
materialId: undefined,
matName: undefined,
matCode: undefined,
spec: undefined,
unit: undefined,
lotNo: undefined,
operatorQty: undefined,
purQty: undefined,
sourceQty: undefined,
sourceId: undefined,
relarionId: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StorageMatApi.getStorageMatPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await StorageMatApi.deleteStorageMat(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await StorageMatApi.exportStorageMat(queryParams)
download.excel(data, '入/出库物料.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
//
})
</script>