feat(biz): 新增成品入出库功能并优化销售发货界面
This commit is contained in:
parent
14b19dc223
commit
70b9038a1e
@ -66,9 +66,9 @@ public class SaleDeliveryController {
|
||||
@Operation(summary = "获得销售出库单主")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('tso:sale-delivery:query')")
|
||||
public CommonResult<SaleDeliveryRespVO> getSaleDelivery(@RequestParam("id") Integer id) {
|
||||
SaleDeliveryDO saleDelivery = saleDeliveryService.getSaleDelivery(id);
|
||||
return success(BeanUtils.toBean(saleDelivery, SaleDeliveryRespVO.class));
|
||||
public CommonResult<SaleDeliverySaveReqVO> getSaleDelivery(@RequestParam("id") Integer id) {
|
||||
SaleDeliverySaveReqVO saleDelivery = saleDeliveryService.getSaleDeliveryWithDetails(id);
|
||||
return success(BeanUtils.toBean(saleDelivery, SaleDeliverySaveReqVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
|
||||
@ -2,12 +2,15 @@ package com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledelivery.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledeliverydetail.vo.SaleDeliveryDetailRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 销售出库单主 Response VO")
|
||||
@Data
|
||||
@ -28,6 +31,7 @@ public class SaleDeliveryRespVO {
|
||||
|
||||
@Schema(description = "单据日期")
|
||||
@ExcelProperty("单据日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate deliveryDate;
|
||||
|
||||
@Schema(description = "客户id", example = "11842")
|
||||
@ -118,4 +122,7 @@ public class SaleDeliveryRespVO {
|
||||
@ExcelProperty("发货数量")
|
||||
private BigDecimal deliveriedQty;
|
||||
|
||||
@Schema(description = "出库明细列表")
|
||||
private List<SaleDeliveryDetailRespVO> detailList;
|
||||
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledelivery.vo;
|
||||
|
||||
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledeliverydetail.vo.SaleDeliveryDetailSaveReqVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 销售出库单主新增/修改 Request VO")
|
||||
@Data
|
||||
@ -85,4 +87,6 @@ public class SaleDeliverySaveReqVO {
|
||||
@Schema(description = "发货数量")
|
||||
private BigDecimal deliveriedQty;
|
||||
|
||||
private List<SaleDeliveryDetailSaveReqVO> detailList;
|
||||
|
||||
}
|
||||
|
||||
@ -45,4 +45,11 @@ public interface SaleDeliveryMapper extends BaseMapperX<SaleDeliveryDO> {
|
||||
.orderByDesc(SaleDeliveryDO::getId));
|
||||
}
|
||||
|
||||
default String selectMaxSaleDeliveryNo() {
|
||||
SaleDeliveryDO saleDelivery = selectOne(new LambdaQueryWrapperX<SaleDeliveryDO>()
|
||||
.orderByDesc(SaleDeliveryDO::getSaleDeliveryNo)
|
||||
.last("LIMIT 1"));
|
||||
return saleDelivery != null ? saleDelivery.getSaleDeliveryNo() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
package com.ningxia.yunxi.chemmes.module.biz.dal.mysql.saledeliverydetail;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.ningxia.yunxi.chemmes.framework.common.pojo.PageResult;
|
||||
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.saledeliverydetail.vo.SaleDeliveryDetailPageReqVO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.saledeliverydetail.SaleDeliveryDetailDO;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 销售出库单子 Mapper
|
||||
@ -15,6 +20,15 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface SaleDeliveryDetailMapper extends BaseMapperX<SaleDeliveryDetailDO> {
|
||||
|
||||
default List<SaleDeliveryDetailDO> selectBySaleDeliveryId(Integer saleDeliveryId) {
|
||||
return selectList(SaleDeliveryDetailDO::getSaleDeliveryId, saleDeliveryId);
|
||||
}
|
||||
|
||||
@Delete("DELETE FROM tso_sale_delivery_detail WHERE sale_delivery_id = #{saleDeliveryId}")
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
int physicalDeleteBySaleDeliveryId(@Param("saleDeliveryId") Integer saleDeliveryId);
|
||||
|
||||
|
||||
default PageResult<SaleDeliveryDetailDO> selectPage(SaleDeliveryDetailPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<SaleDeliveryDetailDO>()
|
||||
.betweenIfPresent(SaleDeliveryDetailDO::getCreateTime, reqVO.getCreateTime())
|
||||
|
||||
@ -64,6 +64,9 @@ public class OrderServiceImpl implements OrderService {
|
||||
if ("9".equals(order.getOrdStatus())) {
|
||||
updateReqVO.setOrdStatus("1");
|
||||
}
|
||||
if ("2".equals(order.getOrdStatus())) {
|
||||
updateReqVO.setOrdStatus("3");
|
||||
}
|
||||
|
||||
// 更新主表
|
||||
OrderDO updateObj = BeanUtils.toBean(updateReqVO, OrderDO.class);
|
||||
|
||||
@ -44,6 +44,14 @@ public interface SaleDeliveryService {
|
||||
*/
|
||||
SaleDeliveryDO getSaleDelivery(Integer id);
|
||||
|
||||
/**
|
||||
* 获得销售出库单详情(包含明细)
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 销售出库单详情
|
||||
*/
|
||||
SaleDeliverySaveReqVO getSaleDeliveryWithDetails(Integer id);
|
||||
|
||||
/**
|
||||
* 获得销售出库单主分页
|
||||
*
|
||||
|
||||
@ -4,12 +4,19 @@ import com.ningxia.yunxi.chemmes.framework.common.pojo.PageResult;
|
||||
import com.ningxia.yunxi.chemmes.framework.common.util.object.BeanUtils;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledelivery.vo.SaleDeliveryPageReqVO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledelivery.vo.SaleDeliverySaveReqVO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.saledeliverydetail.vo.SaleDeliveryDetailSaveReqVO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.saledelivery.SaleDeliveryDO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.saledeliverydetail.SaleDeliveryDetailDO;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.saledelivery.SaleDeliveryMapper;
|
||||
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.saledeliverydetail.SaleDeliveryDetailMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ningxia.yunxi.chemmes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@ -25,30 +32,78 @@ public class SaleDeliveryServiceImpl implements SaleDeliveryService {
|
||||
@Resource
|
||||
private SaleDeliveryMapper saleDeliveryMapper;
|
||||
|
||||
@Resource
|
||||
private SaleDeliveryDetailMapper saleDeliveryDetailMapper;
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Integer createSaleDelivery(SaleDeliverySaveReqVO createReqVO) {
|
||||
String saleDeliveryNo = generateSaleDeliveryNo();
|
||||
createReqVO.setSaleDeliveryNo(saleDeliveryNo);
|
||||
// 插入
|
||||
SaleDeliveryDO saleDelivery = BeanUtils.toBean(createReqVO, SaleDeliveryDO.class);
|
||||
saleDeliveryMapper.insert(saleDelivery);
|
||||
|
||||
createSaleDeliveryDetailList(saleDelivery.getId(), createReqVO.getDetailList());
|
||||
|
||||
// 返回
|
||||
return saleDelivery.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateSaleDelivery(SaleDeliverySaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateSaleDeliveryExists(updateReqVO.getId());
|
||||
// 更新
|
||||
SaleDeliveryDO updateObj = BeanUtils.toBean(updateReqVO, SaleDeliveryDO.class);
|
||||
saleDeliveryMapper.updateById(updateObj);
|
||||
updateSaleDeliveryDetailList(updateReqVO.getId(), updateReqVO.getDetailList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建销售出库单子表列表
|
||||
*/
|
||||
private void createSaleDeliveryDetailList(Integer saleDeliveryId, List<SaleDeliveryDetailSaveReqVO> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<SaleDeliveryDetailDO> saleDeliveryDetails = BeanUtils.toBean(list, SaleDeliveryDetailDO.class);
|
||||
saleDeliveryDetails.forEach(detail ->
|
||||
detail.setSaleDeliveryId(saleDeliveryId)
|
||||
.setId(null)
|
||||
);
|
||||
saleDeliveryDetailMapper.insertBatch(saleDeliveryDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新销售出库单子表列表
|
||||
*/
|
||||
private void updateSaleDeliveryDetailList(Integer saleDeliveryId, List<SaleDeliveryDetailSaveReqVO> list) {
|
||||
// 先删除旧的子表记录
|
||||
deleteSaleDeliveryDetailBySaleDeliveryId(saleDeliveryId);
|
||||
// 再插入新的子表记录
|
||||
createSaleDeliveryDetailList(saleDeliveryId, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据销售出库单ID删除子表记录
|
||||
*/
|
||||
private void deleteSaleDeliveryDetailBySaleDeliveryId(Integer saleDeliveryId) {
|
||||
saleDeliveryDetailMapper.physicalDeleteBySaleDeliveryId(saleDeliveryId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteSaleDelivery(Integer id) {
|
||||
// 校验存在
|
||||
validateSaleDeliveryExists(id);
|
||||
// 删除
|
||||
saleDeliveryMapper.deleteById(id);
|
||||
// 删除子表 物理删除
|
||||
saleDeliveryDetailMapper.physicalDeleteBySaleDeliveryId(id);
|
||||
}
|
||||
|
||||
private void validateSaleDeliveryExists(Integer id) {
|
||||
@ -62,9 +117,49 @@ public class SaleDeliveryServiceImpl implements SaleDeliveryService {
|
||||
return saleDeliveryMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaleDeliverySaveReqVO getSaleDeliveryWithDetails(Integer id) {
|
||||
// 查询主表
|
||||
SaleDeliveryDO saleDelivery = saleDeliveryMapper.selectById(id);
|
||||
if (saleDelivery == null) {
|
||||
throw exception("销售出库单主不存在");
|
||||
}
|
||||
|
||||
// 转换为主表VO
|
||||
SaleDeliverySaveReqVO saleDeliveryVO = BeanUtils.toBean(saleDelivery, SaleDeliverySaveReqVO.class);
|
||||
|
||||
// 查询子表
|
||||
List<SaleDeliveryDetailDO> detailList = saleDeliveryDetailMapper.selectBySaleDeliveryId(id);
|
||||
// 转换为子表VO列表
|
||||
saleDeliveryVO.setDetailList(BeanUtils.toBean(detailList, SaleDeliveryDetailSaveReqVO.class));
|
||||
|
||||
return saleDeliveryVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<SaleDeliveryDO> getSaleDeliveryPage(SaleDeliveryPageReqVO pageReqVO) {
|
||||
return saleDeliveryMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 生成销售出库单号
|
||||
* 规则:SC + 年份(4位) + 月份(2位) + 流水号(3位)
|
||||
*/
|
||||
private String generateSaleDeliveryNo() {
|
||||
String ym = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
|
||||
String maxSaleDeliveryNo = saleDeliveryMapper.selectMaxSaleDeliveryNo();
|
||||
|
||||
if (maxSaleDeliveryNo == null || maxSaleDeliveryNo.length() < 9
|
||||
|| !maxSaleDeliveryNo.substring(2, 8).equals(ym)) {
|
||||
return "SC" + ym + "001";
|
||||
} else {
|
||||
String prefix = maxSaleDeliveryNo.substring(0, 8);
|
||||
int sequence = Integer.parseInt(maxSaleDeliveryNo.substring(8));
|
||||
sequence++;
|
||||
return prefix + String.format("%03d", sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
mes-ui/mes-ui-admin-vue3/.gitignore
vendored
71
mes-ui/mes-ui-admin-vue3/.gitignore
vendored
@ -1,12 +1,61 @@
|
||||
node_modules
|
||||
# 测试文件夹
|
||||
tests/
|
||||
test-results/
|
||||
test-reports/
|
||||
html-report/
|
||||
|
||||
# 截图文件
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
|
||||
# 测试日志
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# pytest 缓存
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Node.js 依赖
|
||||
node_modules/
|
||||
|
||||
# 构建输出
|
||||
dist/
|
||||
build/
|
||||
*.zip
|
||||
|
||||
# 编辑器配置
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
/dist*
|
||||
*-lock.*
|
||||
pnpm-debug
|
||||
auto-*.d.ts
|
||||
.idea
|
||||
.history
|
||||
/.trae/
|
||||
Thumbs.db
|
||||
|
||||
# 环境变量
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Python 字节码
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
|
||||
# 虚拟环境
|
||||
venv/
|
||||
env/
|
||||
.venv/
|
||||
/pytest.ini
|
||||
/pnpm-lock.yaml
|
||||
|
||||
48
mes-ui/mes-ui-admin-vue3/src/api/biz/prostorage/index.ts
Normal file
48
mes-ui/mes-ui-admin-vue3/src/api/biz/prostorage/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface ProStorageVO {
|
||||
id: number
|
||||
billNo: string
|
||||
operatorType: boolean
|
||||
businessType: number
|
||||
remark: string
|
||||
status: boolean
|
||||
billDate: localdate
|
||||
operatorId: number
|
||||
operatorName: string
|
||||
relarionNo: string
|
||||
relarionId: number
|
||||
billType: string
|
||||
sourceNo: string
|
||||
sourceId: number
|
||||
}
|
||||
|
||||
// 查询成品入/出库分页
|
||||
export const getProStoragePage = async (params) => {
|
||||
return await request.get({ url: `/twm/pro-storage/page`, params })
|
||||
}
|
||||
|
||||
// 查询成品入/出库详情
|
||||
export const getProStorage = async (id: number) => {
|
||||
return await request.get({ url: `/twm/pro-storage/get?id=` + id })
|
||||
}
|
||||
|
||||
// 新增成品入/出库
|
||||
export const createProStorage = async (data: ProStorageVO) => {
|
||||
return await request.post({ url: `/twm/pro-storage/create`, data })
|
||||
}
|
||||
|
||||
// 修改成品入/出库
|
||||
export const updateProStorage = async (data: ProStorageVO) => {
|
||||
return await request.put({ url: `/twm/pro-storage/update`, data })
|
||||
}
|
||||
|
||||
// 删除成品入/出库
|
||||
export const deleteProStorage = async (id: number) => {
|
||||
return await request.delete({ url: `/twm/pro-storage/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出成品入/出库 Excel
|
||||
export const exportProStorage = async (params) => {
|
||||
return await request.download({ url: `/twm/pro-storage/export-excel`, params })
|
||||
}
|
||||
56
mes-ui/mes-ui-admin-vue3/src/api/biz/prostoragelog/index.ts
Normal file
56
mes-ui/mes-ui-admin-vue3/src/api/biz/prostoragelog/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface ProStorageLogVO {
|
||||
id: number
|
||||
stockId: number
|
||||
description: string
|
||||
status: boolean
|
||||
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
|
||||
inOutType: boolean
|
||||
billType: number
|
||||
storageAft: number
|
||||
storageBef: number
|
||||
stockItemId: number
|
||||
}
|
||||
|
||||
// 查询成品入/出库日志分页
|
||||
export const getProStorageLogPage = async (params) => {
|
||||
return await request.get({ url: `/twm/pro-storage-log/page`, params })
|
||||
}
|
||||
|
||||
// 查询成品入/出库日志详情
|
||||
export const getProStorageLog = async (id: number) => {
|
||||
return await request.get({ url: `/twm/pro-storage-log/get?id=` + id })
|
||||
}
|
||||
|
||||
// 新增成品入/出库日志
|
||||
export const createProStorageLog = async (data: ProStorageLogVO) => {
|
||||
return await request.post({ url: `/twm/pro-storage-log/create`, data })
|
||||
}
|
||||
|
||||
// 修改成品入/出库日志
|
||||
export const updateProStorageLog = async (data: ProStorageLogVO) => {
|
||||
return await request.put({ url: `/twm/pro-storage-log/update`, data })
|
||||
}
|
||||
|
||||
// 删除成品入/出库日志
|
||||
export const deleteProStorageLog = async (id: number) => {
|
||||
return await request.delete({ url: `/twm/pro-storage-log/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出成品入/出库日志 Excel
|
||||
export const exportProStorageLog = async (params) => {
|
||||
return await request.download({ url: `/twm/pro-storage-log/export-excel`, params })
|
||||
}
|
||||
56
mes-ui/mes-ui-admin-vue3/src/api/biz/prostoragemat/index.ts
Normal file
56
mes-ui/mes-ui-admin-vue3/src/api/biz/prostoragemat/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
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 })
|
||||
}
|
||||
331
mes-ui/mes-ui-admin-vue3/src/views/biz/components/MoneyInput.vue
Normal file
331
mes-ui/mes-ui-admin-vue3/src/views/biz/components/MoneyInput.vue
Normal file
@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<!--
|
||||
通用金额输入组件
|
||||
支持小数位数限制、负数控制、前后缀显示等功能
|
||||
-->
|
||||
<el-input
|
||||
v-model="displayValue"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:clearable="clearable"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
<!-- 前缀模板:根据 showPrefix 控制显示 -->
|
||||
<template v-if="showPrefix" #prefix>{{ prefix }}</template>
|
||||
<!-- 后缀模板:根据 showSuffix 控制显示 -->
|
||||
<template v-if="showSuffix" #suffix>{{ suffix }}</template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 通用金额输入组件 MoneyInput
|
||||
*
|
||||
* 功能特性:
|
||||
* - 自动限制小数位数
|
||||
* - 可配置是否允许负数输入
|
||||
* - 支持自定义前后缀符号(如 ¥、$、% 等)
|
||||
* - 自动格式化显示,失焦时自动补全小数位
|
||||
* - 输入时自动过滤非法字符
|
||||
*
|
||||
* 使用示例:
|
||||
* ```vue
|
||||
* <!-- 基本用法:人民币金额,2位小数 -->
|
||||
* <MoneyInput v-model="amount" />
|
||||
*
|
||||
* <!-- 订单数量:整数,不允许负数,无符号 -->
|
||||
* <MoneyInput
|
||||
* v-model="quantity"
|
||||
* :decimal-places="0"
|
||||
* :allow-negative="false"
|
||||
* :show-prefix="false"
|
||||
* />
|
||||
*
|
||||
* <!-- 美元金额 -->
|
||||
* <MoneyInput
|
||||
* v-model="usdAmount"
|
||||
* prefix="$"
|
||||
* :decimal-places="2"
|
||||
* />
|
||||
*
|
||||
* <!-- 百分比显示:2位小数,显示%后缀 -->
|
||||
* <MoneyInput
|
||||
* v-model="rate"
|
||||
* :decimal-places="2"
|
||||
* suffix="%"
|
||||
* :show-suffix="true"
|
||||
* :show-prefix="false"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
/**
|
||||
* Props 定义
|
||||
*/
|
||||
interface Props {
|
||||
/**
|
||||
* 绑定值(双向绑定)
|
||||
*/
|
||||
modelValue?: number | string
|
||||
|
||||
/**
|
||||
* 输入框占位文本
|
||||
* @default '请输入'
|
||||
*/
|
||||
placeholder?: string
|
||||
|
||||
/**
|
||||
* 是否禁用输入框
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean
|
||||
|
||||
/**
|
||||
* 是否只读
|
||||
* @default false
|
||||
*/
|
||||
readonly?: boolean
|
||||
|
||||
/**
|
||||
* 是否显示清除按钮
|
||||
* @default true
|
||||
*/
|
||||
clearable?: boolean
|
||||
|
||||
/**
|
||||
* 小数位数
|
||||
* @default 2
|
||||
*/
|
||||
decimalPlaces?: number
|
||||
|
||||
/**
|
||||
* 是否允许输入负数
|
||||
* @default true
|
||||
*/
|
||||
allowNegative?: boolean
|
||||
|
||||
/**
|
||||
* 前缀符号(如 ¥、$)
|
||||
* @default '¥'
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
/**
|
||||
* 后缀符号(如 %、元)
|
||||
* @default ''
|
||||
*/
|
||||
suffix?: string
|
||||
|
||||
/**
|
||||
* 是否显示前缀
|
||||
* @default true
|
||||
*/
|
||||
showPrefix?: boolean
|
||||
|
||||
/**
|
||||
* 是否显示后缀
|
||||
* @default false
|
||||
*/
|
||||
showSuffix?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Props 默认值配置
|
||||
*/
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: undefined,
|
||||
placeholder: '请输入',
|
||||
disabled: false,
|
||||
readonly: false,
|
||||
clearable: true,
|
||||
decimalPlaces: 2,
|
||||
allowNegative: true,
|
||||
prefix: '¥',
|
||||
suffix: '',
|
||||
showPrefix: true,
|
||||
showSuffix: false
|
||||
})
|
||||
|
||||
/**
|
||||
* 事件定义
|
||||
*/
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
* 值变化事件(双向绑定)
|
||||
* @param value - 新的值(number | string | undefined)
|
||||
*/
|
||||
'update:modelValue': [value: number | string | undefined]
|
||||
|
||||
/**
|
||||
* 值改变事件(失焦时触发)
|
||||
* @param value - 新的值(number | string | undefined)
|
||||
*/
|
||||
'change': [value: number | string | undefined]
|
||||
}>()
|
||||
|
||||
/**
|
||||
* 显示值(格式化后的字符串)
|
||||
*/
|
||||
const displayValue = ref<string>('')
|
||||
|
||||
/**
|
||||
* 初始化显示值
|
||||
* 将 modelValue 转换为格式化后的字符串
|
||||
*/
|
||||
const initDisplayValue = () => {
|
||||
if (props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== '') {
|
||||
const num = Number(props.modelValue)
|
||||
if (!isNaN(num)) {
|
||||
displayValue.value = num.toFixed(props.decimalPlaces)
|
||||
} else {
|
||||
displayValue.value = ''
|
||||
}
|
||||
} else {
|
||||
displayValue.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化显示值
|
||||
initDisplayValue()
|
||||
|
||||
/**
|
||||
* 监听 modelValue 变化,同步更新显示值
|
||||
*/
|
||||
watch(() => props.modelValue, () => {
|
||||
initDisplayValue()
|
||||
}, { immediate: true })
|
||||
|
||||
/**
|
||||
* 处理输入事件
|
||||
* 过滤非法字符,限制小数位数
|
||||
*/
|
||||
const handleInput = (value: string) => {
|
||||
let inputValue = value
|
||||
|
||||
// 处理负数:如果允许负数,保留一个负号在开头
|
||||
if (props.allowNegative) {
|
||||
inputValue = inputValue.replace(/[^\d.-]/g, '')
|
||||
const negativeCount = (inputValue.match(/-/g) || []).length
|
||||
if (negativeCount > 1) {
|
||||
inputValue = '-' + inputValue.replace(/-/g, '').slice(1)
|
||||
}
|
||||
if (inputValue.indexOf('-') > 0) {
|
||||
inputValue = inputValue.replace(/-/g, '')
|
||||
}
|
||||
} else {
|
||||
// 不允许负数:只保留数字和小数点
|
||||
inputValue = inputValue.replace(/[^\d.]/g, '')
|
||||
}
|
||||
|
||||
// 正整数模式(decimalPlaces 为 0):不允许输入小数点
|
||||
if (props.decimalPlaces === 0) {
|
||||
inputValue = inputValue.replace(/\./g, '')
|
||||
displayValue.value = inputValue
|
||||
return
|
||||
}
|
||||
|
||||
// 输入以小数点开头时,自动在前面补 0
|
||||
if (inputValue.startsWith('.')) {
|
||||
inputValue = '0' + inputValue
|
||||
}
|
||||
|
||||
// 负数情况下,小数点在负号后直接输入时补 0
|
||||
if (inputValue.startsWith('-') && inputValue.length > 1 && inputValue[1] === '.') {
|
||||
inputValue = '-0' + inputValue.slice(1)
|
||||
}
|
||||
|
||||
// 限制小数位数
|
||||
const dotIndex = inputValue.indexOf('.')
|
||||
if (dotIndex !== -1) {
|
||||
const integerPart = inputValue.slice(0, dotIndex)
|
||||
const decimalPart = inputValue.slice(dotIndex + 1)
|
||||
const limitedDecimal = decimalPart.slice(0, props.decimalPlaces)
|
||||
inputValue = integerPart + '.' + limitedDecimal
|
||||
}
|
||||
|
||||
// 只允许一个小数点
|
||||
const dotCount = (inputValue.match(/\./g) || []).length
|
||||
if (dotCount > 1) {
|
||||
const firstDotIndex = inputValue.indexOf('.')
|
||||
inputValue = inputValue.slice(0, firstDotIndex) + inputValue.slice(firstDotIndex).replace(/\./g, '')
|
||||
}
|
||||
|
||||
// 特殊情况处理:单独的负号或小数点
|
||||
if (inputValue === '-' || inputValue === '.') {
|
||||
displayValue.value = inputValue
|
||||
return
|
||||
}
|
||||
|
||||
// 验证并处理负数限制
|
||||
if (inputValue !== '' && inputValue !== '-') {
|
||||
const num = Number(inputValue)
|
||||
if (!isNaN(num)) {
|
||||
if (!props.allowNegative && num < 0) {
|
||||
inputValue = Math.abs(num).toFixed(props.decimalPlaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayValue.value = inputValue
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理失焦事件
|
||||
* 格式化最终值并触发事件
|
||||
*/
|
||||
const handleBlur = () => {
|
||||
let value = displayValue.value
|
||||
|
||||
// 空值处理
|
||||
if (value === '' || value === '-' || value === '.') {
|
||||
displayValue.value = ''
|
||||
emit('update:modelValue', undefined)
|
||||
emit('change', undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证数字有效性
|
||||
const num = Number(value)
|
||||
if (isNaN(num)) {
|
||||
displayValue.value = ''
|
||||
emit('update:modelValue', undefined)
|
||||
emit('change', undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// 负数限制处理
|
||||
if (!props.allowNegative && num < 0) {
|
||||
const absNum = Math.abs(num)
|
||||
displayValue.value = absNum.toFixed(props.decimalPlaces)
|
||||
emit('update:modelValue', absNum)
|
||||
emit('change', absNum)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化并输出
|
||||
displayValue.value = num.toFixed(props.decimalPlaces)
|
||||
emit('update:modelValue', num)
|
||||
emit('change', num)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理聚焦事件
|
||||
* 聚焦时移除格式化,显示原始数值
|
||||
*/
|
||||
const handleFocus = () => {
|
||||
if (displayValue.value !== '') {
|
||||
const num = Number(displayValue.value)
|
||||
if (!isNaN(num)) {
|
||||
displayValue.value = num.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 组件样式 */
|
||||
</style>
|
||||
@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<Dialog :title="'选择库存'" v-model="dialogVisible" width="1500px">
|
||||
<!-- 搜索区域 -->
|
||||
<el-form :model="queryParams" inline class="mb-4">
|
||||
<el-form-item label="仓库">
|
||||
<el-select
|
||||
v-model="queryParams.storeHouseId"
|
||||
placeholder="请选择仓库"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
@change="handleStoreHouseChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in storeHouseList"
|
||||
:key="item.id"
|
||||
:label="item.storeHouseName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="库区">
|
||||
<el-select
|
||||
v-model="queryParams.storeAreaId"
|
||||
placeholder="请选择库区"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in storeAreaList"
|
||||
:key="item.id"
|
||||
:label="item.storeAreaName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery" type="primary">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 库存列表 -->
|
||||
<el-table
|
||||
ref="inventoryTableRef"
|
||||
v-loading="loading"
|
||||
:data="inventoryList"
|
||||
:show-overflow-tooltip="true"
|
||||
:height="400"
|
||||
border
|
||||
:row-key="'id'"
|
||||
:reserve-selection="true"
|
||||
>
|
||||
<el-table-column type="selection" width="50px" align="center" />
|
||||
<el-table-column label="序号" type="index" width="60px" align="center" />
|
||||
<el-table-column label="仓库" align="center" prop="storeHouseName" width="120px" />
|
||||
<el-table-column label="库区" align="center" prop="storeAreaName" width="120px" />
|
||||
<el-table-column label="批次号" align="center" prop="lotNo" width="120px" />
|
||||
<el-table-column label="库存袋数" align="center" prop="packQty" width="100px" />
|
||||
<el-table-column label="单袋规格" align="center" prop="bagSpec" width="100px" />
|
||||
<el-table-column label="库存数量" align="center" prop="yardQty" width="100px" />
|
||||
<el-table-column label="最早入库日期" align="center" prop="earStoreDate" width="130px" />
|
||||
<el-table-column label="产品编码" align="center" prop="matCode" width="120px" />
|
||||
<el-table-column label="产品名称" align="center" prop="matName" width="180px" />
|
||||
<el-table-column label="规格型号" align="center" prop="spec" width="120px" />
|
||||
<el-table-column label="单位" align="center" prop="unit" width="80px" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
class="mb-4 mt-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="handleSave" type="primary">保存</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import * as InventoryApi from '@/api/biz/prostorageinventory'
|
||||
import * as StoreHouseApi from '@/api/biz/storehouse'
|
||||
import * as StoreAreaApi from '@/api/biz/storearea'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const inventoryList = ref([])
|
||||
const total = ref(0)
|
||||
const inventoryTableRef = ref(null)
|
||||
|
||||
const emit = defineEmits(['select', 'close'])
|
||||
|
||||
// 已选择的库存ID列表(用于记住已选择的项,下次打开自动选中)
|
||||
const selectedInventoryIds = ref<number[]>([])
|
||||
|
||||
// 设置已选择的ID(从父组件传入)
|
||||
const setSelectedIds = (ids: number[]) => {
|
||||
selectedInventoryIds.value = ids
|
||||
}
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
storeHouseId: undefined,
|
||||
storeAreaId: undefined,
|
||||
matName: undefined,
|
||||
spec: undefined,
|
||||
})
|
||||
|
||||
// 仓储列表(条件:store_type = '3' and enabled_status = 1)
|
||||
const storeHouseList = ref([])
|
||||
|
||||
// 库区列表(条件:store_house_id = 选中的仓储ID and enabled_status = 1)
|
||||
const storeAreaList = ref([])
|
||||
|
||||
/** 获取仓储列表 */
|
||||
const getStoreHouseList = async () => {
|
||||
try {
|
||||
// 使用分页接口,传入条件:store_type = '3' and enabled_status = 1
|
||||
const data = await StoreHouseApi.getStoreHousePage({
|
||||
storeType: '3',
|
||||
enabledStatus: 1,
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
storeHouseList.value = data.list || []
|
||||
} catch (error) {
|
||||
console.error('获取仓储列表失败:', error)
|
||||
storeHouseList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取库区列表 */
|
||||
const getStoreAreaList = async (storeHouseId?: number) => {
|
||||
storeAreaList.value = []
|
||||
if (!storeHouseId) return
|
||||
|
||||
try {
|
||||
// 使用分页接口,传入条件:store_house_id = 选中的仓储ID and enabled_status = 1
|
||||
const data = await StoreAreaApi.getStoreAreaPage({
|
||||
storeHouseId,
|
||||
enabledStatus: 1,
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
storeAreaList.value = data.list || []
|
||||
} catch (error) {
|
||||
console.error('获取库区列表失败:', error)
|
||||
storeAreaList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 仓储变更时刷新库区列表 */
|
||||
const handleStoreHouseChange = (val) => {
|
||||
queryParams.storeAreaId = undefined
|
||||
getStoreAreaList(val)
|
||||
}
|
||||
|
||||
/** 获取库存列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
...queryParams
|
||||
}
|
||||
|
||||
// 根据用户需求添加模糊查询条件
|
||||
if (queryParams.storeHouseId) {
|
||||
params.storeHouseName = { like: '%仓库%' }
|
||||
}
|
||||
if (queryParams.storeAreaId) {
|
||||
params.storeAreaName = { like: '%库区%' }
|
||||
}
|
||||
|
||||
const data = await InventoryApi.getProStorageInventoryPage(params)
|
||||
// 按批次号升序排列
|
||||
inventoryList.value = (data.list || []).sort((a, b) => {
|
||||
const lotA = a.lotNo || ''
|
||||
const lotB = b.lotNo || ''
|
||||
return lotA.localeCompare(lotB)
|
||||
})
|
||||
total.value = data.total || 0
|
||||
|
||||
// 数据加载完成后,自动选中已选择的行
|
||||
setTimeout(() => {
|
||||
setDefaultSelection()
|
||||
}, 100)
|
||||
} catch (error) {
|
||||
console.error('获取库存列表失败:', error)
|
||||
inventoryList.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置默认选中 */
|
||||
const setDefaultSelection = () => {
|
||||
if (!inventoryTableRef.value || !selectedInventoryIds.value.length) return
|
||||
|
||||
inventoryList.value.forEach(row => {
|
||||
if (selectedInventoryIds.value.includes(row.id)) {
|
||||
inventoryTableRef.value.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置 */
|
||||
const resetQuery = () => {
|
||||
queryParams.storeHouseId = undefined
|
||||
queryParams.storeAreaId = undefined
|
||||
queryParams.matName = undefined
|
||||
queryParams.spec = undefined
|
||||
queryParams.pageNo = 1
|
||||
storeAreaList.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 保存按钮 */
|
||||
const handleSave = () => {
|
||||
if (!inventoryTableRef.value) return
|
||||
|
||||
// 获取选中的行 - 使用 getSelectionRows() 方法
|
||||
const selectedRows = inventoryTableRef.value.getSelectionRows ? inventoryTableRef.value.getSelectionRows() : []
|
||||
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请至少选择一条库存记录')
|
||||
return
|
||||
}
|
||||
|
||||
// 转换数据格式
|
||||
const selectData = selectedRows.map(row => ({
|
||||
id: row.id,
|
||||
storeHouseId: row.storeHouseId,
|
||||
storeHouseName: row.storeHouseName,
|
||||
storeAreaId: row.storeAreaId,
|
||||
storeAreaName: row.storeAreaName,
|
||||
lotNo: row.lotNo,
|
||||
packQty: row.packQty,
|
||||
bagSpec: row.bagSpec,
|
||||
yardQty: row.yardQty,
|
||||
earStoreDate: row.earStoreDate,
|
||||
matCode: row.matCode,
|
||||
matName: row.matName,
|
||||
spec: row.spec,
|
||||
unit: row.unit,
|
||||
materialId: row.materialId,
|
||||
}))
|
||||
|
||||
emit('select', selectData)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
// 初始化仓储列表
|
||||
getStoreHouseList()
|
||||
// 执行搜索
|
||||
// handleQuery()
|
||||
}
|
||||
|
||||
defineExpose({ open, setSelectedIds })
|
||||
|
||||
watch(dialogVisible, (val) => {
|
||||
if (!val) {
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -9,6 +9,7 @@
|
||||
>
|
||||
<!-- 基础信息 -->
|
||||
<el-card title="基础信息" class="mb-4">
|
||||
<span>基础信息</span>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="销售订单" prop="saleOrdId">
|
||||
@ -84,13 +85,12 @@
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="发货数量" prop="deliveriedQty">
|
||||
<el-input
|
||||
v-model="formData.deliveriedQty"
|
||||
placeholder="请输入发货数量"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="formData.remaimQty || 0"
|
||||
/>
|
||||
<MoneyInput
|
||||
v-model="formData.deliveriedQty"
|
||||
:decimal-places="0"
|
||||
:allow-negative="false"
|
||||
:show-prefix="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -110,6 +110,7 @@
|
||||
|
||||
<!-- 收货信息 -->
|
||||
<el-card title="收货信息" class="mb-4">
|
||||
<span>收货信息</span>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="联系人" prop="contact">
|
||||
@ -130,59 +131,61 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 产品信息 -->
|
||||
<el-card title="产品信息">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<span></span>
|
||||
<el-button type="primary" plain @click="addProductItem">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="productList" show-summary border :summary-method="getSummary">
|
||||
<el-table-column label="序号" type="index" width="60px" />
|
||||
<el-table-column label="仓库(*)" prop="warehouse" width="100px">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<span>产品信息</span>
|
||||
<el-button type="primary" plain @click="addProductItem" class="ml-4">新增</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="productList" show-summary border :summary-method="getSummary">
|
||||
<el-table-column label="序号" type="index" width="60px" align="center" />
|
||||
<el-table-column label="仓库(*)" prop="warehouse" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.warehouse" placeholder="请输入仓库" />
|
||||
<el-input v-model="scope.row.warehouse" placeholder="从库存选择" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库区(*)" prop="warehouseArea" width="100px">
|
||||
<el-table-column label="库区(*)" prop="warehouseArea" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.warehouseArea" placeholder="请输入库区" />
|
||||
<el-input v-model="scope.row.warehouseArea" placeholder="从库存选择" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="批次号(*)" prop="batchNo" width="120px">
|
||||
<el-table-column label="批次号(*)" prop="batchNo" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.batchNo" placeholder="请输入批次号" />
|
||||
<el-input v-model="scope.row.batchNo" placeholder="从库存选择" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存数量" prop="stockQty" width="100px">
|
||||
<el-table-column label="库存数量" prop="stockQty" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.stockQty" placeholder="库存数量" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存袋数" prop="stockBag" width="100px">
|
||||
<el-table-column label="库存袋数" prop="stockBag" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.stockBag" placeholder="库存袋数" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单袋规格" prop="bagSpec" width="100px">
|
||||
<el-table-column label="单袋规格" prop="bagSpec" width="150px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.bagSpec" placeholder="手动录入" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发货袋数(*)" prop="deliveriedBag" width="120px">
|
||||
<el-table-column label="发货袋数(*)" prop="deliveriedBag" width="120px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.deliveriedBag" placeholder="请输入" />
|
||||
<el-input v-model="scope.row.deliveriedBag" placeholder="请输入" @input="refreshTable" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发货数量(*)" prop="deliveriedQty" width="120px">
|
||||
<el-table-column label="发货数量(*)" prop="deliveriedQty" width="120px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.deliveriedQty" placeholder="请输入" />
|
||||
<el-input v-model="scope.row.deliveriedQty" placeholder="请输入" @input="refreshTable" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" width="150px">
|
||||
<el-table-column label="备注" prop="remark" width="120px" align="center">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.remark" placeholder="文本-手动录入" />
|
||||
<el-input v-model="scope.row.remark" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80px">
|
||||
<el-table-column label="操作" width="80px" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
@ -205,6 +208,9 @@
|
||||
|
||||
<!-- 订单选择弹窗 -->
|
||||
<OrderSelectDialog ref="orderSelectRef" @select="handleOrderSelect" />
|
||||
|
||||
<!-- 库存选择弹窗 -->
|
||||
<ProStorageInventorySelectDialog ref="inventorySelectRef" @select="handleInventorySelect" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -214,6 +220,7 @@ import * as OrderApi from '@/api/biz/tsoorder'
|
||||
import * as CustomerApi from '@/api/biz/customer'
|
||||
import { watch } from 'vue'
|
||||
import OrderSelectDialog from './OrderSelectDialog.vue'
|
||||
import ProStorageInventorySelectDialog from '../prostorageinventory/ProStorageInventorySelectDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
@ -229,6 +236,8 @@ const saleOrderOptions = ref([])
|
||||
// 订单选择弹窗
|
||||
const orderSelectRef = ref()
|
||||
|
||||
// 库存选择弹窗
|
||||
const inventorySelectRef = ref()
|
||||
|
||||
// 获取今日日期
|
||||
const getToday = () => {
|
||||
@ -267,19 +276,7 @@ const formData = reactive({
|
||||
})
|
||||
|
||||
// 产品列表
|
||||
const productList = ref([
|
||||
{
|
||||
warehouse: '',
|
||||
warehouseArea: '',
|
||||
batchNo: '',
|
||||
stockQty: '',
|
||||
stockBag: '',
|
||||
bagSpec: '',
|
||||
deliveriedBag: '',
|
||||
deliveriedQty: '',
|
||||
remark: '',
|
||||
},
|
||||
])
|
||||
const productList = ref([])
|
||||
|
||||
// 合计
|
||||
const totalBag = computed(() => {
|
||||
@ -294,15 +291,32 @@ const totalQty = computed(() => {
|
||||
const getSummary = (param: any) => {
|
||||
const { columns, data } = param
|
||||
const sums: any[] = []
|
||||
|
||||
// 确保数据数组有效
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
columns.forEach((column: any, index: number) => {
|
||||
sums[index] = index === 0 ? '合计' : ''
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
// 计算发货袋数合计
|
||||
const totalDeliveriedBag = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedBag) || 0), 0)
|
||||
|
||||
// 计算发货数量合计
|
||||
const totalDeliveriedQty = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedQty) || 0), 0)
|
||||
|
||||
// 使用列索引来匹配(更可靠)
|
||||
// 列顺序:0-序号, 1-仓库, 2-库区, 3-批次号, 4-库存数量, 5-库存袋数, 6-单袋规格, 7-发货袋数, 8-发货数量, 9-备注, 10-操作
|
||||
columns.forEach((column: any, index: number) => {
|
||||
if (index === 0) {
|
||||
sums[index] = '合计'
|
||||
} else if (column.prop === 'deliveriedBag') {
|
||||
const total = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedBag) || 0), 0)
|
||||
sums[index] = total
|
||||
} else if (column.prop === 'deliveriedQty') {
|
||||
const total = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedQty) || 0), 0)
|
||||
sums[index] = total
|
||||
} else if (index === 7) {
|
||||
// 发货袋数列
|
||||
sums[index] = totalDeliveriedBag
|
||||
} else if (index === 8) {
|
||||
// 发货数量列
|
||||
sums[index] = totalDeliveriedQty
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
@ -310,6 +324,14 @@ const getSummary = (param: any) => {
|
||||
return sums
|
||||
}
|
||||
|
||||
/** 强制刷新表格(用于实时更新合计) */
|
||||
const refreshTable = () => {
|
||||
// 通过修改key强制表格重新渲染
|
||||
if (productList.value.length > 0) {
|
||||
productList.value = [...productList.value]
|
||||
}
|
||||
}
|
||||
|
||||
// 监听弹窗关闭
|
||||
watch(dialogVisible, (val) => {
|
||||
if (!val) {
|
||||
@ -319,7 +341,7 @@ watch(dialogVisible, (val) => {
|
||||
|
||||
const formRules = reactive({
|
||||
saleOrdId: [{ required: true, message: '销售订单不能为空', trigger: 'change' }],
|
||||
deliveryDate: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
ordDate: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
deliveryStatus: [{ required: true, message: '单据状态不能为空', trigger: 'change' }],
|
||||
deliveriedQty: [
|
||||
{ required: true, message: '发货数量不能为空', trigger: 'change' },
|
||||
@ -334,6 +356,13 @@ const formRules = reactive({
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
custName: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
|
||||
materialName: [{ required: true, message: '产品名称不能为空', trigger: 'change' }],
|
||||
spec: [{ required: true, message: '规格型号不能为空', trigger: 'change' }],
|
||||
saleDeliveryNo: [{ required: true, message: '出库单号不能为空', trigger: 'change' }],
|
||||
ordQty: [{ required: true, message: '订单数量不能为空', trigger: 'change' }],
|
||||
remaimQty: [{ required: true, message: '剩余数量不能为空', trigger: 'change' }],
|
||||
deliveriedQty: [{ required: true, message: '发货数量不能为空', trigger: 'change' }],
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
@ -487,16 +516,45 @@ const loadCustomerContact = async (custId: number) => {
|
||||
|
||||
/** 添加产品项 */
|
||||
const addProductItem = () => {
|
||||
productList.value.push({
|
||||
warehouse: '',
|
||||
warehouseArea: '',
|
||||
batchNo: '',
|
||||
stockQty: '',
|
||||
stockBag: '',
|
||||
bagSpec: '',
|
||||
deliveriedBag: '',
|
||||
deliveriedQty: '',
|
||||
remark: '',
|
||||
// 获取已选择的库存ID列表
|
||||
const selectedIds = productList.value
|
||||
.filter(item => item.inventoryId)
|
||||
.map(item => item.inventoryId)
|
||||
|
||||
// 设置已选择的ID,以便弹窗打开时自动选中
|
||||
inventorySelectRef.value.setSelectedIds(selectedIds)
|
||||
|
||||
// 打开弹窗
|
||||
inventorySelectRef.value.open()
|
||||
}
|
||||
|
||||
/** 处理库存选择 */
|
||||
const handleInventorySelect = (data: any[]) => {
|
||||
// data 是选中的库存记录数组
|
||||
if (!data || data.length === 0) return
|
||||
|
||||
// 遍历选中的库存记录,根据 id 判断是否已存在
|
||||
data.forEach(inventory => {
|
||||
const exists = productList.value.find(item => item.inventoryId === inventory.id)
|
||||
|
||||
if (!exists) {
|
||||
// 不存在则新增
|
||||
productList.value.push({
|
||||
inventoryId: inventory.id, // 库存ID,用于判断是否已存在
|
||||
storeHouseId: inventory.storeHouseId, // 仓储ID
|
||||
storeAreaId: inventory.storeAreaId, // 库区ID
|
||||
warehouse: inventory.storeHouseName, // 仓库名称
|
||||
warehouseArea: inventory.storeAreaName, // 库区名称
|
||||
batchNo: inventory.lotNo, // 批次号
|
||||
stockQty: inventory.yardQty, // 库存数量
|
||||
stockBag: inventory.packQty, // 库存袋数
|
||||
bagSpec: inventory.bagSpec, // 单袋规格
|
||||
deliveriedBag: '', // 发货袋数
|
||||
deliveriedQty: '', // 发货数量
|
||||
remark: '', // 备注
|
||||
})
|
||||
}
|
||||
// 如果已存在,不做处理
|
||||
})
|
||||
}
|
||||
|
||||
@ -510,16 +568,50 @@ const removeProductItem = (index: number) => {
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success', 'close'])
|
||||
|
||||
/** 验证发货数量 */
|
||||
const validateDeliveryQty = () => {
|
||||
const remaimQty = parseInt(formData.remaimQty) || 0
|
||||
const totalDeliveriedQty = totalQty.value
|
||||
|
||||
if (totalDeliveriedQty > remaimQty) {
|
||||
message.warning(`发货总数量(${totalDeliveriedQty})不能超过剩余数量(${remaimQty})`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
// 校验发货数量
|
||||
if (!validateDeliveryQty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
// 将产品信息数据传输到 detailList
|
||||
const detailList = productList.value.map(item => ({
|
||||
id: item.id,
|
||||
inventoryId: item.inventoryId,
|
||||
storeHouseId: item.storeHouseId,
|
||||
storeAreaId: item.storeAreaId,
|
||||
warehouse: item.warehouse,
|
||||
warehouseArea: item.warehouseArea,
|
||||
batchNo: item.batchNo,
|
||||
stockQty: item.stockQty,
|
||||
stockBag: item.stockBag,
|
||||
bagSpec: item.bagSpec,
|
||||
deliveriedBag: item.deliveriedBag,
|
||||
deliveriedQty: item.deliveriedQty,
|
||||
remark: item.remark,
|
||||
}))
|
||||
|
||||
const data = {
|
||||
...formData,
|
||||
items: productList.value,
|
||||
detailList,
|
||||
}
|
||||
|
||||
if (formType.value === 'create') {
|
||||
@ -538,16 +630,38 @@ const submitForm = async () => {
|
||||
|
||||
/** 提交确认 */
|
||||
const submitAudit = async () => {
|
||||
// 校验发货数量
|
||||
if (!validateDeliveryQty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
// 将产品信息数据传输到 detailList
|
||||
const detailList = productList.value.map(item => ({
|
||||
id: item.id,
|
||||
inventoryId: item.inventoryId,
|
||||
storeHouseId: item.storeHouseId,
|
||||
storeAreaId: item.storeAreaId,
|
||||
warehouse: item.warehouse,
|
||||
warehouseArea: item.warehouseArea,
|
||||
batchNo: item.batchNo,
|
||||
stockQty: item.stockQty,
|
||||
stockBag: item.stockBag,
|
||||
bagSpec: item.bagSpec,
|
||||
deliveriedBag: item.deliveriedBag,
|
||||
deliveriedQty: item.deliveriedQty,
|
||||
remark: item.remark,
|
||||
}))
|
||||
|
||||
const data = {
|
||||
...formData,
|
||||
deliveryStatus: '2', // 已确认
|
||||
items: productList.value,
|
||||
detailList,
|
||||
}
|
||||
|
||||
if (formType.value === 'create') {
|
||||
@ -593,17 +707,7 @@ const resetForm = () => {
|
||||
ordDate: null,
|
||||
})
|
||||
|
||||
productList.value = [{
|
||||
warehouse: '',
|
||||
warehouseArea: '',
|
||||
batchNo: '',
|
||||
stockQty: '',
|
||||
stockBag: '',
|
||||
bagSpec: '',
|
||||
deliveriedBag: '',
|
||||
deliveriedQty: '',
|
||||
remark: '',
|
||||
}]
|
||||
productList.value = []
|
||||
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
@ -71,90 +71,92 @@
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 主列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column type="selection" width="55px" />
|
||||
<el-table-column label="序号" align="center" type="index" width="60px"/>
|
||||
<el-table-column label="出库单号" align="center" prop="saleDeliveryNo" width="140px" />
|
||||
<el-table-column label="单据日期" align="center" prop="ordDate" width="120px" />
|
||||
<el-table-column label="客户名称" align="center" prop="custName" width="120px" />
|
||||
<el-table-column label="产品名称" align="center" prop="materialName" width="120px" />
|
||||
<el-table-column label="产品规格" align="center" prop="spec" width="120px" />
|
||||
<el-table-column label="销售订单号" align="center" prop="saleOrdNo" width="140px" />
|
||||
<el-table-column label="订单数量" align="center" prop="ordQty" width="100px" />
|
||||
<el-table-column label="剩余数量" align="center" prop="remaimQty" width="100px" />
|
||||
<el-table-column label="发货数量" align="center" prop="deliveriedQty" width="100px" />
|
||||
<el-table-column label="单位" align="center" prop="unit" width="80px" />
|
||||
<el-table-column label="出库人" align="center" prop="deliveryEmpName" width="100px" />
|
||||
<el-table-column label="交货方式" align="center" prop="deliveryType" width="100px">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.DELIVERY_METHOD" :value="scope.row.deliveryType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系人" align="center" prop="contact" width="100px" />
|
||||
<el-table-column label="联系电话" align="center" prop="conPhone" width="120px" />
|
||||
<el-table-column label="联系地址" align="center" prop="conAddress" />
|
||||
<el-table-column label="单据状态" align="center" prop="deliveryStatus" width="100px">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BILL_STATUS" :value="scope.row.deliveryStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['tso:sale-delivery:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['tso:sale-delivery:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="viewDetail(scope.row)"
|
||||
v-hasPermi="['tso:sale-delivery:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<!-- 表格区域:主列表 + 明细表格上下各占一半 -->
|
||||
<ContentWrap class="!p-15px">
|
||||
<div style="display: flex; flex-direction: column; height: calc(100vh - 280px);">
|
||||
<!-- 主列表 -->
|
||||
<div style="flex: 1; min-height: 0;">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">出库单列表</div>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" border @row-click="handleRowClick" style="height: calc(50vh - 220px);">
|
||||
<el-table-column type="selection" width="55px" align="right" fixed="left" />
|
||||
<el-table-column label="序号" align="center" type="index" width="60px" fixed="left" />
|
||||
<el-table-column label="出库单号" align="center" prop="saleDeliveryNo" width="130px" fixed="left" />
|
||||
<el-table-column label="单据日期" align="center" prop="deliveryDate" width="110px" fixed="left" />
|
||||
<el-table-column label="客户名称" align="center" prop="custName" width="250px" />
|
||||
<el-table-column label="产品名称" align="center" prop="materialName" width="160px" />
|
||||
<el-table-column label="产品规格" align="center" prop="spec" width="120px" />
|
||||
<el-table-column label="销售订单号" align="center" prop="saleOrdNo" width="140px" />
|
||||
<el-table-column label="订单数量" align="center" prop="ordQty" width="100px" />
|
||||
<el-table-column label="剩余数量" align="center" prop="remaimQty" width="100px" />
|
||||
<el-table-column label="发货数量" align="center" prop="deliveriedQty" width="100px" />
|
||||
<el-table-column label="单位" align="center" prop="unit" width="80px" />
|
||||
<el-table-column label="出库人" align="center" prop="deliveryEmpName" width="100px" />
|
||||
<el-table-column label="交货方式" align="center" prop="deliveryType" width="100px">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.DELIVERY_METHOD" :value="scope.row.deliveryType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系人" align="center" prop="contact" width="100px" />
|
||||
<el-table-column label="联系电话" align="center" prop="conPhone" width="120px" />
|
||||
<el-table-column label="联系地址" align="center" prop="conAddress" width="180px" />
|
||||
<el-table-column label="单据状态" align="center" prop="deliveryStatus" width="100px" fixed="right">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BILL_STATUS" :value="scope.row.deliveryStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="170px" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['tso:sale-delivery:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['tso:sale-delivery:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="viewDetail(scope.row)"
|
||||
v-hasPermi="['tso:sale-delivery:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
style="margin-top: 8px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 明细表格区域 -->
|
||||
<ContentWrap v-if="selectedRow">
|
||||
<el-table :data="selectedRow.items" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="序号" align="center" type="index" width="60px"/>
|
||||
<el-table-column label="库区" align="center" prop="warehouseArea" width="100px" />
|
||||
<el-table-column label="库位" align="center" prop="warehouseLoc" width="100px" />
|
||||
<el-table-column label="批次号" align="center" prop="batchNo" width="120px" />
|
||||
<el-table-column label="发货数量" align="center" prop="deliveriedQty" width="100px" />
|
||||
<el-table-column label="发货袋数" align="center" prop="deliveriedBag" width="100px" />
|
||||
<el-table-column label="单据规格" align="center" prop="spec" width="120px" />
|
||||
<el-table-column label="单位" align="center" prop="unit" width="80px" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
</el-table>
|
||||
<div class="mt-2 flex justify-end">
|
||||
<span class="font-bold">合计: </span>
|
||||
<span>发货数量: {{ totalDeliveriedQty }}</span>
|
||||
<span class="ml-4">发货袋数: {{ totalDeliveriedBag }}</span>
|
||||
<!-- 明细表格 -->
|
||||
<div style="flex: 1; min-height: 0;">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">出库单明细</div>
|
||||
<el-table v-loading="detailLoading" :data="detailList" :stripe="true" :show-overflow-tooltip="true" border :summary-method="getDetailSummary" show-summary>
|
||||
<el-table-column label="序号" align="center" type="index" width="60px"/>
|
||||
<el-table-column label="库区" align="center" prop="warehouseArea" width="100px" />
|
||||
<el-table-column label="库位" align="center" prop="warehouseLoc" width="100px" />
|
||||
<el-table-column label="批次号" align="center" prop="batchNo" width="120px" />
|
||||
<el-table-column label="发货数量" align="center" prop="deliveriedQty" width="100px" />
|
||||
<el-table-column label="发货袋数" align="center" prop="deliveriedBag" width="100px" />
|
||||
<el-table-column label="单据规格" align="center" prop="spec" width="120px" />
|
||||
<el-table-column label="单位" align="center" prop="unit" width="80px" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
@ -163,7 +165,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SaleDeliveryApi from '@/api/biz/saledelivery'
|
||||
@ -179,6 +181,8 @@ const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const selectedRow = ref(null)
|
||||
const detailLoading = ref(false)
|
||||
const detailList = ref([])
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
@ -192,16 +196,31 @@ const queryParams = reactive({
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 合计数量
|
||||
const totalDeliveriedQty = computed(() => {
|
||||
if (!selectedRow.value?.items) return 0
|
||||
return selectedRow.value.items.reduce((sum, item) => sum + (item.deliveriedQty || 0), 0)
|
||||
})
|
||||
|
||||
const totalDeliveriedBag = computed(() => {
|
||||
if (!selectedRow.value?.items) return 0
|
||||
return selectedRow.value.items.reduce((sum, item) => sum + (item.deliveriedBag || 0), 0)
|
||||
})
|
||||
/** 明细表格合计 */
|
||||
const getDetailSummary = (param: any) => {
|
||||
const { columns, data } = param
|
||||
const sums: any[] = []
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
columns.forEach((column: any, index: number) => {
|
||||
sums[index] = index === 0 ? '合计' : ''
|
||||
})
|
||||
return sums
|
||||
}
|
||||
const totalDeliveriedBag = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedBag) || 0), 0)
|
||||
const totalDeliveriedQty = data.reduce((sum: number, item: any) => sum + (parseInt(item.deliveriedQty) || 0), 0)
|
||||
columns.forEach((column: any, index: number) => {
|
||||
if (index === 0) {
|
||||
sums[index] = '合计'
|
||||
} else if (index === 4) {
|
||||
sums[index] = totalDeliveriedQty
|
||||
} else if (index === 5) {
|
||||
sums[index] = totalDeliveriedBag
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@ -210,6 +229,15 @@ const getList = async () => {
|
||||
const data = await SaleDeliveryApi.getSaleDeliveryPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
|
||||
// 自动加载第一条数据的详情
|
||||
if (list.value.length > 0) {
|
||||
await viewDetail(list.value[0])
|
||||
} else {
|
||||
// 没有数据时清空明细
|
||||
detailList.value = []
|
||||
selectedRow.value = null
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@ -244,13 +272,24 @@ const handleDelete = async (id: number) => {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 行点击事件 */
|
||||
const handleRowClick = async (row: any) => {
|
||||
await viewDetail(row)
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const viewDetail = async (row: any) => {
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const data = await SaleDeliveryApi.getSaleDelivery(row.id)
|
||||
selectedRow.value = data
|
||||
// 使用 detailList 显示明细数据
|
||||
detailList.value = data.items || data.detailList || []
|
||||
} catch (e) {
|
||||
console.error('获取详情失败', e)
|
||||
detailList.value = []
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -62,16 +62,16 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="业务部门" prop="saleDeptId" >
|
||||
<el-input
|
||||
v-model="formData.saleDeptName"
|
||||
<el-tree-select
|
||||
v-model="formData.saleDeptId"
|
||||
:data="deptList"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择业务部门"
|
||||
readonly
|
||||
@click="openDeptSelect"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ep:caret-bottom" />
|
||||
</template>
|
||||
</el-input>
|
||||
filterable
|
||||
check-strictly
|
||||
class="w-full"
|
||||
@change="handleDeptChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@ -181,12 +181,18 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="税率" prop="taxRate" >
|
||||
<el-input v-model="formData.taxRate" placeholder="请输入税率" class="text-right" />
|
||||
<MoneyInput v-model="formData.taxRate" :decimal-places="2" suffix="%" :show-suffix="true" :show-prefix="false" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="含税总金额" prop="totalAmount" >
|
||||
<el-input v-model="formData.totalAmount" placeholder="请输入含税总金额" class="text-right" />
|
||||
<MoneyInput
|
||||
v-model="formData.totalAmount"
|
||||
:decimal-places="2"
|
||||
:allow-negative="false"
|
||||
:show-prefix="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@ -245,12 +251,23 @@
|
||||
|
||||
<el-table-column label="订单数量(*)" align="center" prop="ordQty">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.ordQty" placeholder="请输入" />
|
||||
<MoneyInput
|
||||
v-model="scope.row.ordQty"
|
||||
:decimal-places="2"
|
||||
:allow-negative="false"
|
||||
:show-prefix="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税单价(*)" align="center" prop="priceTax">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.priceTax" placeholder="请输入" />
|
||||
<MoneyInput
|
||||
v-model="scope.row.priceTax"
|
||||
:decimal-places="2"
|
||||
:allow-negative="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途(*)" align="center" width="120px">
|
||||
@ -358,11 +375,14 @@ import { ref, reactive, nextTick } from 'vue'
|
||||
import * as OrderApi from '@/api/biz/tsoorder/'
|
||||
import * as CustomerApi from '@/api/biz/customer'
|
||||
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { getDeptSimpleName } from '@/api/system/dept'
|
||||
import { getDeptSimpleName, getSimpleDeptList } from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import MaterialSelect from '@/views/biz/material/MaterialSelect.vue'
|
||||
import DeptTree from '@/views/system/user/DeptTree.vue'
|
||||
import MoneyInput from '@/views/biz/components/MoneyInput.vue'
|
||||
|
||||
// 获取当日日期
|
||||
const getToday = () => {
|
||||
@ -388,6 +408,7 @@ const deptSelectVisible = ref(false)
|
||||
const deptTreeRef = ref()
|
||||
const userList = ref<UserApi.UserVO[]>([])
|
||||
const userSelectLoading = ref(false)
|
||||
const deptList = ref<DeptApi.DeptVO[]>([])
|
||||
|
||||
// 文件上传相关
|
||||
const uploadRef = ref()
|
||||
@ -486,10 +507,21 @@ const unwrapOrderDetail = (raw: Record<string, any>) => {
|
||||
return raw
|
||||
}
|
||||
|
||||
/** 获取业务部门列表 */
|
||||
const loadDeptList = async () => {
|
||||
try {
|
||||
const list = await getSimpleDeptList()
|
||||
deptList.value = handleTree(list) as DeptApi.DeptVO[]
|
||||
} catch (e) {
|
||||
console.error('获取业务部门列表失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const open = async (type: string, id?: number) => {
|
||||
formType.value = type
|
||||
dialogTitle.value = t('action.' + type)
|
||||
resetForm()
|
||||
await loadDeptList()
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
@ -752,6 +784,18 @@ const openMaterialSelect = async () => {
|
||||
materialSelectRef.value?.open(selectedIds)
|
||||
}
|
||||
|
||||
// 部门选择变更处理
|
||||
const handleDeptChange = (deptId: number) => {
|
||||
const dept = deptList.value.find(d => d.id === deptId)
|
||||
if (dept) {
|
||||
formData.saleDeptName = dept.name
|
||||
}
|
||||
// 选择部门后,清空并重新加载该部门下的用户
|
||||
formData.saleMan = undefined
|
||||
formData.saleManName = undefined
|
||||
searchUsers('')
|
||||
}
|
||||
|
||||
// 打开部门选择弹窗
|
||||
const openDeptSelect = () => {
|
||||
deptSelectVisible.value = true
|
||||
|
||||
@ -156,6 +156,15 @@
|
||||
v-hasPermi="['biz:order:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.ordStatus === '2'"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['biz:order:update']"
|
||||
>
|
||||
订单变更
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
|
||||
Loading…
Reference in New Issue
Block a user