feat:出库单修改功能

This commit is contained in:
zhouz
2026-07-24 10:35:50 +08:00
parent 8a9d488ac2
commit ac19f00697
14 changed files with 257 additions and 100 deletions

View File

@@ -70,10 +70,10 @@ public class IostorInvController {
} }
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新出库单主表") @Operation(summary = "更新出库单主表及明细")
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')")
public CommonResult<Boolean> updateIostorInv(@Valid @RequestBody IostorInvSaveReqVO updateReqVO) { public CommonResult<Boolean> updateIostorInv(@Valid @RequestBody IostorInvUpdateReqVO reqVO) {
iostorInvService.updateIostorInv(updateReqVO); iostorInvService.updateOutbound(reqVO);
return success(true); return success(true);
} }
@@ -99,9 +99,20 @@ public class IostorInvController {
@Operation(summary = "获得出入库单主表") @Operation(summary = "获得出入库单主表")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')") @PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
public CommonResult<IostorInvRespVO> getIostorInv(@RequestParam("id") String id) { public CommonResult<IostorInvRespVO> getIostorInv(@RequestParam("id") Long id) {
IostorInvDO iostorInv = iostorInvService.getIostorInv(id); IostorInvDO iostorInv = iostorInvService.getIostorInv(id);
return success(BeanUtils.toBean(iostorInv, IostorInvRespVO.class)); IostorInvRespVO respVO = BeanUtils.toBean(iostorInv, IostorInvRespVO.class);
respVO.setDetails(iostorInvService.getIostorInvDetails(id));
return success(respVO);
}
@GetMapping("/details")
@Operation(summary = "获得出入库单全部明细")
@Parameter(name = "iostorinvId", description = "出入库单主表标识", required = true)
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
public CommonResult<List<IostorInvDetailRespVO>> getIostorInvDetails(
@RequestParam("iostorinvId") Long iostorinvId) {
return success(iostorInvService.getIostorInvDetails(iostorinvId));
} }
@GetMapping("/page") @GetMapping("/page")

View File

@@ -19,6 +19,10 @@ public class IostorInvCreateReqVO {
@NotEmpty(message = "单据类型不能为空") @NotEmpty(message = "单据类型不能为空")
private String billType; private String billType;
@Schema(description = "单据状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "单据状态不能为空")
private String billStatus;
@Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "仓库标识不能为空") @NotEmpty(message = "仓库标识不能为空")
private String storId; private String storId;

View File

@@ -0,0 +1,14 @@
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 出入库单编辑明细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class IostorInvDetailRespVO extends IostorInvCreateReqVO.Detail {
@Schema(description = "物料名称")
private String materialName;
}

View File

@@ -130,4 +130,7 @@ public class IostorInvRespVO implements VO {
@ExcelProperty("回传时间") @ExcelProperty("回传时间")
private String uploadTime; private String uploadTime;
@Schema(description = "出入库单明细")
private List<IostorInvDetailRespVO> details;
} }

View File

@@ -0,0 +1,16 @@
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 出库单修改 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class IostorInvUpdateReqVO extends IostorInvCreateReqVO {
@Schema(description = "出入库单主表标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "出入库单主表标识不能为空")
private Long iostorinvId;
}

View File

@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryPageReqVO; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryPageReqVO;
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO;
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvDetailRespVO;
import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*; import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
/** /**
@@ -47,6 +48,9 @@ public interface IostorinvDtlMapper extends BaseMapperX<IostorinvDtlDO> {
List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodesForUpdate( List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodesForUpdate(
@Param("storId") String storId, @Param("vehicleCodes") Collection<String> vehicleCodes); @Param("storId") String storId, @Param("vehicleCodes") Collection<String> vehicleCodes);
/** 查询指定出入库单的编辑明细,并带出物料名称。 */
List<IostorInvDetailRespVO> selectEditDetails(@Param("iostorinvId") Long iostorinvId);
default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) { default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>() return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId()) .eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())

View File

@@ -4,6 +4,7 @@ import java.util.*;
import jakarta.validation.*; import jakarta.validation.*;
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.*; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.*;
import cn.code.nl.module.wms.dal.dataobject.iostorinv.IostorInvDO; import cn.code.nl.module.wms.dal.dataobject.iostorinv.IostorInvDO;
import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO;
import cn.code.nl.framework.common.pojo.PageResult; import cn.code.nl.framework.common.pojo.PageResult;
import cn.code.nl.framework.common.pojo.PageParam; import cn.code.nl.framework.common.pojo.PageParam;
@@ -53,6 +54,11 @@ public interface IostorInvService {
*/ */
void updateIostorInv(@Valid IostorInvSaveReqVO updateReqVO); void updateIostorInv(@Valid IostorInvSaveReqVO updateReqVO);
/**
* 更新出库单主表并替换全部明细。
*/
void updateOutbound(@Valid IostorInvUpdateReqVO reqVO);
/** /**
* 删除出入库单主表 * 删除出入库单主表
* *
@@ -73,7 +79,12 @@ public interface IostorInvService {
* @param id 编号 * @param id 编号
* @return 出入库单主表 * @return 出入库单主表
*/ */
IostorInvDO getIostorInv(String id); IostorInvDO getIostorInv(Long id);
/**
* 获得出入库单的全部明细。
*/
List<IostorInvDetailRespVO> getIostorInvDetails(Long id);
/** /**
* 获得出入库单主表分页 * 获得出入库单主表分页

View File

@@ -90,16 +90,24 @@ public class IostorInvServiceImpl implements IostorInvService {
iostorInv.setBillType(reqVO.getBillType()); iostorInv.setBillType(reqVO.getBillType());
iostorInv.setBizDate(reqVO.getBizDate()); iostorInv.setBizDate(reqVO.getBizDate());
iostorInv.setStorId(reqVO.getStorId()); iostorInv.setStorId(reqVO.getStorId());
iostorInv.setBillStatus("10"); iostorInv.setBillStatus(reqVO.getBillStatus());
iostorInv.setRemark(reqVO.getRemark()); iostorInv.setRemark(reqVO.getRemark());
iostorInv.setDetailCount(reqVO.getDetails().size()); iostorInv.setDetailCount(reqVO.getDetails().size());
iostorInv.setTotalWeight(totalWeight); iostorInv.setTotalWeight(totalWeight);
iostorInvMapper.insert(iostorInv); iostorInvMapper.insert(iostorInv);
for (int index = 0; index < reqVO.getDetails().size(); index++) { insertOutboundDetails(iostorInv.getIostorinvId(), reqVO.getBillStatus(),
IostorInvCreateReqVO.Detail detail = reqVO.getDetails().get(index); reqVO.getDetails(), inventorySnapshot);
return iostorInv.getIostorinvId();
}
private void insertOutboundDetails(Long iostorinvId, String billStatus,
List<IostorInvCreateReqVO.Detail> details,
Map<Long, AvailableInventoryRespVO> inventorySnapshot) {
for (int index = 0; index < details.size(); index++) {
IostorInvCreateReqVO.Detail detail = details.get(index);
IostorinvDtlDO detailDO = new IostorinvDtlDO(); IostorinvDtlDO detailDO = new IostorinvDtlDO();
detailDO.setIostorinvId(iostorInv.getIostorinvId()); detailDO.setIostorinvId(iostorinvId);
detailDO.setSeqNo(index + 1); detailDO.setSeqNo(index + 1);
AvailableInventoryRespVO inventory = inventorySnapshot.get(detail.getGroupId()); AvailableInventoryRespVO inventory = inventorySnapshot.get(detail.getGroupId());
detailDO.setMaterialCode(inventory == null ? detail.getMaterialCode() : inventory.getMaterialCode()); detailDO.setMaterialCode(inventory == null ? detail.getMaterialCode() : inventory.getMaterialCode());
@@ -108,7 +116,7 @@ public class IostorInvServiceImpl implements IostorInvService {
detailDO.setPlanQty(detail.getPlanQty()); detailDO.setPlanQty(detail.getPlanQty());
detailDO.setUnassignQty(detail.getPlanQty()); detailDO.setUnassignQty(detail.getPlanQty());
detailDO.setAssignQty(BigDecimal.ZERO); detailDO.setAssignQty(BigDecimal.ZERO);
detailDO.setBillStatus("10"); detailDO.setBillStatus(billStatus);
detailDO.setQtyUnitId(inventory == null ? detail.getQtyUnitId() : inventory.getQtyUnitId()); detailDO.setQtyUnitId(inventory == null ? detail.getQtyUnitId() : inventory.getQtyUnitId());
detailDO.setSourceBillCode(inventory == null ? detail.getSourceBillCode() : inventory.getExtCode()); detailDO.setSourceBillCode(inventory == null ? detail.getSourceBillCode() : inventory.getExtCode());
detailDO.setSourceBillType(inventory == null ? detail.getSourceBillType() : inventory.getExtType()); detailDO.setSourceBillType(inventory == null ? detail.getSourceBillType() : inventory.getExtType());
@@ -116,7 +124,6 @@ public class IostorInvServiceImpl implements IostorInvService {
detailDO.setRemark(detail.getRemark()); detailDO.setRemark(detail.getRemark());
iostorinvDtlMapper.insert(detailDO); iostorinvDtlMapper.insert(detailDO);
} }
return iostorInv.getIostorinvId();
} }
private Map<Long, AvailableInventoryRespVO> validateAndLockInventory(IostorInvCreateReqVO reqVO) { private Map<Long, AvailableInventoryRespVO> validateAndLockInventory(IostorInvCreateReqVO reqVO) {
@@ -199,6 +206,32 @@ public class IostorInvServiceImpl implements IostorInvService {
iostorInvMapper.updateById(updateObj); iostorInvMapper.updateById(updateObj);
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOutbound(IostorInvUpdateReqVO reqVO) {
validateIostorInvExists(String.valueOf(reqVO.getIostorinvId()));
Map<Long, AvailableInventoryRespVO> inventorySnapshot = validateAndLockInventory(reqVO);
BigDecimal totalWeight = reqVO.getDetails().stream()
.map(IostorInvCreateReqVO.Detail::getPlanQty)
.reduce(BigDecimal.ZERO, BigDecimal::add);
IostorInvDO updateObj = new IostorInvDO();
updateObj.setIostorinvId(reqVO.getIostorinvId());
updateObj.setBillType(reqVO.getBillType());
updateObj.setBillStatus(reqVO.getBillStatus());
updateObj.setBizDate(reqVO.getBizDate());
updateObj.setStorId(reqVO.getStorId());
updateObj.setRemark(reqVO.getRemark());
updateObj.setDetailCount(reqVO.getDetails().size());
updateObj.setTotalWeight(totalWeight);
iostorInvMapper.updateById(updateObj);
iostorinvDtlMapper.delete(new LambdaQueryWrapperX<IostorinvDtlDO>()
.eq(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId()));
insertOutboundDetails(reqVO.getIostorinvId(), reqVO.getBillStatus(),
reqVO.getDetails(), inventorySnapshot);
}
@Override @Override
public void deleteIostorInv(String id) { public void deleteIostorInv(String id) {
// 校验存在 // 校验存在
@@ -221,10 +254,15 @@ public class IostorInvServiceImpl implements IostorInvService {
} }
@Override @Override
public IostorInvDO getIostorInv(String id) { public IostorInvDO getIostorInv(Long id) {
return iostorInvMapper.selectById(id); return iostorInvMapper.selectById(id);
} }
@Override
public List<IostorInvDetailRespVO> getIostorInvDetails(Long id) {
return iostorinvDtlMapper.selectEditDetails(id);
}
@Override @Override
public PageResult<IostorInvDO> getIostorInvPage(IostorInvPageReqVO pageReqVO) { public PageResult<IostorInvDO> getIostorInvPage(IostorInvPageReqVO pageReqVO) {
PageResult<IostorInvDO> pageResult = iostorInvMapper.selectPage(pageReqVO); PageResult<IostorInvDO> pageResult = iostorInvMapper.selectPage(pageReqVO);

View File

@@ -92,4 +92,25 @@
FOR UPDATE FOR UPDATE
</select> </select>
<select id="selectEditDetails"
resultType="cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvDetailRespVO">
SELECT d.material_code,
d.material_id,
mb.material_name,
d.pcsn,
d.plan_qty,
d.qty_unit_id,
d.source_bill_code,
d.source_bill_type,
d.source_billdtl_id,
d.remark
FROM wms_iostorinvdtl d
LEFT JOIN base_materialbase mb
ON mb.material_id = d.material_id
AND mb.deleted = 0
WHERE d.iostorinv_id = #{iostorinvId}
AND d.deleted = 0
ORDER BY d.seq_no, d.iostorinvdtl_id
</select>
</mapper> </mapper>

View File

@@ -64,10 +64,9 @@ class IostorInvServiceLocalSpringTest {
@Test @Test
void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() { void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() {
String id = iostorInvService.createOutbound(buildRequest()); Long id = iostorInvService.createOutbound(buildRequest());
assertNotNull(id); assertNotNull(id);
assertFalse(id.isBlank());
jdbcTemplate.queryForObject("SELECT * FROM wms_iostorinv WHERE iostorinv_id = ?", (rs, rowNum) -> { jdbcTemplate.queryForObject("SELECT * FROM wms_iostorinv WHERE iostorinv_id = ?", (rs, rowNum) -> {
assertEquals("OUT-TEST-0001", rs.getString("bill_code")); assertEquals("OUT-TEST-0001", rs.getString("bill_code"));
assertEquals("OUT", rs.getString("io_type")); assertEquals("OUT", rs.getString("io_type"));
@@ -97,12 +96,12 @@ class IostorInvServiceLocalSpringTest {
assertFalse(details.get(0).detailId().isBlank()); assertFalse(details.get(0).detailId().isBlank());
assertFalse(details.get(1).detailId().isBlank()); assertFalse(details.get(1).detailId().isBlank());
assertNotEquals(details.get(0).detailId(), details.get(1).detailId()); assertNotEquals(details.get(0).detailId(), details.get(1).detailId());
assertEquals(id, details.get(0).headerId()); assertEquals(id.toString(), details.get(0).headerId());
assertEquals(id, details.get(1).headerId()); assertEquals(id.toString(), details.get(1).headerId());
assertEquals(new DetailRow(details.get(0).detailId(), id, 1, "MAT-01", "MID-01", "PCSN-01", assertEquals(new DetailRow(details.get(0).detailId(), id.toString(), 1, "MAT-01", "MID-01", "PCSN-01",
new BigDecimal("12.500"), BigDecimal.ZERO.setScale(3), new BigDecimal("12.500"), new BigDecimal("12.500"), BigDecimal.ZERO.setScale(3), new BigDecimal("12.500"),
"DB-KG", "数据库千克", "DB-SRC-01", "DB-TYPE", "DB-DTL-01", "明细一"), details.get(0)); "DB-KG", "数据库千克", "DB-SRC-01", "DB-TYPE", "DB-DTL-01", "明细一"), details.get(0));
assertEquals(new DetailRow(details.get(1).detailId(), id, 2, "MAT-02", "MID-02", "PCSN-02", assertEquals(new DetailRow(details.get(1).detailId(), id.toString(), 2, "MAT-02", "MID-02", "PCSN-02",
new BigDecimal("7.250"), BigDecimal.ZERO.setScale(3), new BigDecimal("7.250"), new BigDecimal("7.250"), BigDecimal.ZERO.setScale(3), new BigDecimal("7.250"),
"DB-KG", "数据库千克", "DB-SRC-02", "DB-TYPE", "DB-DTL-02", "明细二"), details.get(1)); "DB-KG", "数据库千克", "DB-SRC-02", "DB-TYPE", "DB-DTL-02", "明细二"), details.get(1));
} }
@@ -218,6 +217,7 @@ class IostorInvServiceLocalSpringTest {
private IostorInvCreateReqVO buildRequest() { private IostorInvCreateReqVO buildRequest() {
IostorInvCreateReqVO request = new IostorInvCreateReqVO(); IostorInvCreateReqVO request = new IostorInvCreateReqVO();
request.setBillType("销售出库"); request.setBillType("销售出库");
request.setBillStatus("生成");
request.setStorId("STOR-01"); request.setStorId("STOR-01");
request.setBizDate(LocalDateTime.of(2026, 7, 22, 0, 0)); request.setBizDate(LocalDateTime.of(2026, 7, 22, 0, 0));
request.setRemark("主表备注"); request.setRemark("主表备注");

View File

@@ -27,6 +27,7 @@ export namespace WmsIostorInvApi {
creator?: string; // 创建者标识 creator?: string; // 创建者标识
creatorName?: string; // 创建者名称 creatorName?: string; // 创建者名称
createTime?: Dayjs | string; // 创建时间 createTime?: Dayjs | string; // 创建时间
details?: OutboundDetail[]; // 出入库单明细
disOptid: string; // 分配人 disOptid: string; // 分配人
disTime?: Dayjs | string; // 分配时间 disTime?: Dayjs | string; // 分配时间
confirmOptid: string; // 确认人 confirmOptid: string; // 确认人
@@ -64,12 +65,18 @@ export namespace WmsIostorInvApi {
/** 出库单新增请求 */ /** 出库单新增请求 */
export interface OutboundCreateReq { export interface OutboundCreateReq {
billType: string; // 单据类型 billType: string; // 单据类型
billStatus: string; // 单据状态
storId: string; // 仓库标识 storId: string; // 仓库标识
bizDate: number; // 业务日期(毫秒时间戳) bizDate: number; // 业务日期(毫秒时间戳)
remark?: string; // 备注 remark?: string; // 备注
details: OutboundDetail[]; // 出库单明细 details: OutboundDetail[]; // 出库单明细
} }
/** 出库单修改请求 */
export interface OutboundUpdateReq extends OutboundCreateReq {
iostorinvId: string;
}
/** 可用库存 */ /** 可用库存 */
export interface AvailableInventory { export interface AvailableInventory {
groupId: number | string; // 组盘记录标识 groupId: number | string; // 组盘记录标识
@@ -119,6 +126,13 @@ export function getIostorInv(id: string) {
); );
} }
/** 查询出入库单全部明细 */
export function getIostorInvDetails(iostorinvId: string) {
return requestClient.get<WmsIostorInvApi.OutboundDisplayDetail[]>(
`/wms/iostor-inv/details?iostorinvId=${encodeURIComponent(iostorinvId)}`,
);
}
/** 新增出入库单主表 */ /** 新增出入库单主表 */
export function createIostorInv(data: WmsIostorInvApi.IostorInv) { export function createIostorInv(data: WmsIostorInvApi.IostorInv) {
return requestClient.post('/wms/iostor-inv/create', data); return requestClient.post('/wms/iostor-inv/create', data);
@@ -129,6 +143,11 @@ export function updateIostorInv(data: WmsIostorInvApi.IostorInv) {
return requestClient.put('/wms/iostor-inv/update', data); return requestClient.put('/wms/iostor-inv/update', data);
} }
/** 修改出库单主表及明细 */
export function updateOutbound(data: WmsIostorInvApi.OutboundUpdateReq) {
return requestClient.put('/wms/iostor-inv/update', data);
}
/** 删除出入库单主表 */ /** 删除出入库单主表 */
export function deleteIostorInv(id: number) { export function deleteIostorInv(id: number) {
return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`); return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`);

View File

@@ -9,37 +9,37 @@ import { Button, Input, InputNumber, message, Space, Table } from 'antdv-next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { getSimpleDictDataList } from '#/api/system/dict/data';
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr'; import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
import { import {
createOutbound, createOutbound,
getIostorInv, getIostorInv,
updateIostorInv, getIostorInvDetails,
updateOutbound,
} from '#/api/wms/iostorinv'; } from '#/api/wms/iostorinv';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useFormSchema } from '../data';
import { summarizeDetails } from './detail-summary'; import { summarizeDetails } from './detail-summary';
import InventorySelectComponent from './inventory-select.vue'; import InventorySelectComponent from './inventory-select.vue';
import ManualDetailComponent from './manual-detail.vue'; import ManualDetailComponent from './manual-detail.vue';
import { import {
buildOutboundPayload, buildOutboundPayload,
filterOutboundBillTypeOptions,
mergeOutboundInventory, mergeOutboundInventory,
type OutboundFormDetail, type OutboundFormDetail,
shouldApplyEditResult, shouldApplyEditResult,
shouldClearDetailsForWarehouseChange, shouldClearDetailsForWarehouseChange,
} from './outbound-form'; } from './outbound-form';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<WmsIostorInvApi.IostorInv>(); const formData = ref<WmsIostorInvApi.IostorInv>();
const details = ref<OutboundFormDetail[]>([]); const details = ref<OutboundFormDetail[]>([]);
const currentStorId = ref<string>(); const currentStorId = ref<string>();
let manualRowSequence = 0; let manualRowSequence = 0;
let billTypeRequest: null | ReturnType<typeof getSimpleDictDataList> = null;
let mainModalOpen = false; let mainModalOpen = false;
let mainOpenToken = 0; let mainOpenToken = 0;
let activeEditId: string | undefined; let activeEditId: string | undefined;
let suppressWarehouseChange = false;
const isEdit = computed(() => Boolean(formData.value?.iostorinvId)); const isEdit = computed(() => Boolean(formData.value?.iostorinvId));
const getTitle = computed(() => const getTitle = computed(() =>
@@ -48,17 +48,6 @@ const getTitle = computed(() =>
: '出库单新增', : '出库单新增',
); );
const [LegacyForm, legacyFormApi] = useVbenForm({
commonConfig: {
componentProps: { class: 'w-full' },
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useFormSchema(),
showDefaultActions: false,
});
async function handleWarehouseChange(nextStorId?: string) { async function handleWarehouseChange(nextStorId?: string) {
const shouldClear = shouldClearDetailsForWarehouseChange( const shouldClear = shouldClearDetailsForWarehouseChange(
currentStorId.value, currentStorId.value,
@@ -78,7 +67,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
labelWidth: 88, labelWidth: 88,
}, },
handleValuesChange: (values) => { handleValuesChange: (values) => {
if (Object.hasOwn(values, 'storId')) { if (!suppressWarehouseChange && Object.hasOwn(values, 'storId')) {
void handleWarehouseChange(values.storId); void handleWarehouseChange(values.storId);
} }
}, },
@@ -108,7 +97,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
component: 'Select', component: 'Select',
componentProps: { componentProps: {
allowClear: true, allowClear: true,
options: [], options: getDictOptions(DICT_TYPE.WMS_OUT_BILL_TYPE),
placeholder: '请选择业务类型', placeholder: '请选择业务类型',
}, },
fieldName: 'billType', fieldName: 'billType',
@@ -116,11 +105,15 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
rules: 'required', rules: 'required',
}, },
{ {
component: 'Input', component: 'Select',
componentProps: { disabled: true }, componentProps: {
defaultValue: '生成', allowClear: true,
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
placeholder: '请选择单据状态',
},
fieldName: 'billStatus', fieldName: 'billStatus',
label: '单据状态', label: '单据状态',
rules: 'required',
}, },
{ {
component: 'InputNumber', component: 'InputNumber',
@@ -164,28 +157,6 @@ const [ManualDetail, manualDetailApi] = useVbenModal({
destroyOnClose: true, destroyOnClose: true,
}); });
async function loadBillTypeOptions() {
billTypeRequest ??= getSimpleDictDataList();
const request = billTypeRequest;
try {
const dictItems = await request;
outboundFormApi.updateSchema([
{
componentProps: {
allowClear: true,
options: filterOutboundBillTypeOptions(dictItems),
placeholder: '请选择业务类型',
},
fieldName: 'billType',
},
]);
} catch {
message.error('出库业务类型加载失败,请稍后重试');
} finally {
if (billTypeRequest === request) billTypeRequest = null;
}
}
const columns = [ const columns = [
{ key: 'index', title: '序号', width: 60 }, { key: 'index', title: '序号', width: 60 },
{ dataIndex: 'materialCode', key: 'materialCode', title: '物料编码', width: 130 }, { dataIndex: 'materialCode', key: 'materialCode', title: '物料编码', width: 130 },
@@ -265,34 +236,23 @@ function validateDetails() {
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
if (isEdit.value) {
const { valid } = await legacyFormApi.validate();
if (!valid) return;
modalApi.lock();
try {
await updateIostorInv(
(await legacyFormApi.getValues()) as WmsIostorInvApi.IostorInv,
);
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
return;
}
const { valid } = await outboundFormApi.validate(); const { valid } = await outboundFormApi.validate();
if (!valid || !validateDetails()) return; if (!valid || !validateDetails()) return;
const values = await outboundFormApi.getValues(); const values = await outboundFormApi.getValues();
modalApi.lock(); modalApi.lock();
try { try {
await createOutbound( const payload = buildOutboundPayload(
buildOutboundPayload(
{ ...values, bizDate: dayjs(values.bizDate).valueOf() }, { ...values, bizDate: dayjs(values.bizDate).valueOf() },
details.value, details.value,
),
); );
if (isEdit.value) {
await updateOutbound({
...payload,
iostorinvId: formData.value!.iostorinvId,
});
} else {
await createOutbound(payload);
}
await modalApi.close(); await modalApi.close();
emit('success'); emit('success');
message.success($t('ui.actionMessage.operationSuccess')); message.success($t('ui.actionMessage.operationSuccess'));
@@ -318,17 +278,32 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined; formData.value = undefined;
details.value = []; details.value = [];
currentStorId.value = undefined; currentStorId.value = undefined;
suppressWarehouseChange = true;
try {
await outboundFormApi.resetForm(); await outboundFormApi.resetForm();
await outboundFormApi.updateSchema([
{
componentProps: {
allowClear: true,
disabled: false,
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
placeholder: '请选择单据状态',
},
fieldName: 'billStatus',
},
]);
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return; if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
await outboundFormApi.setValues({ await outboundFormApi.setValues({
billCode: '保存时自动生成', billCode: '保存时自动生成',
billStatus: '生成', billStatus: '10',
bizDate: dayjs(), bizDate: dayjs(),
detailCount: 0, detailCount: 0,
totalWeight: '0.000', totalWeight: '0.000',
}); });
} finally {
suppressWarehouseChange = false;
}
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return; if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
await loadBillTypeOptions();
return; return;
} }
const editId = data.iostorinvId; const editId = data.iostorinvId;
@@ -336,7 +311,10 @@ const [Modal, modalApi] = useVbenModal({
formData.value = data; formData.value = data;
modalApi.lock(); modalApi.lock();
try { try {
const editResult = await getIostorInv(editId); const [editResult, editDetails] = await Promise.all([
getIostorInv(editId),
getIostorInvDetails(editId),
]);
if ( if (
!shouldApplyEditResult( !shouldApplyEditResult(
mainModalOpen, mainModalOpen,
@@ -349,7 +327,38 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
formData.value = editResult; formData.value = editResult;
await legacyFormApi.setValues(editResult); suppressWarehouseChange = true;
try {
await outboundFormApi.resetForm();
await outboundFormApi.updateSchema([
{
componentProps: {
allowClear: true,
disabled: true,
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
placeholder: '请选择单据状态',
},
fieldName: 'billStatus',
},
]);
details.value = editDetails.map((detail, index) => ({
...detail,
rowKey: `edit-${editId}-${index}`,
}));
await outboundFormApi.setValues({
billCode: editResult.billCode,
billStatus: editResult.billStatus,
billType: editResult.billType,
bizDate: editResult.bizDate ? dayjs(editResult.bizDate) : undefined,
detailCount: details.value.length,
remark: editResult.remark,
storId: editResult.storId,
totalWeight: Number(editResult.totalWeight || 0).toFixed(3),
});
currentStorId.value = editResult.storId;
} finally {
suppressWarehouseChange = false;
}
} finally { } finally {
if ( if (
shouldApplyEditResult( shouldApplyEditResult(
@@ -369,8 +378,7 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal class="w-[1400px] max-w-[96vw]" :title="getTitle"> <Modal class="w-[1400px] max-w-[96vw]" :title="getTitle">
<LegacyForm v-if="isEdit" class="mx-4" /> <div class="px-4">
<div v-else class="px-4">
<OutboundForm /> <OutboundForm />
<div class="mb-3 mt-1 flex items-center justify-between"> <div class="mb-3 mt-1 flex items-center justify-between">
<span class="text-base font-medium">出库明细</span> <span class="text-base font-medium">出库明细</span>

View File

@@ -34,7 +34,13 @@ describe('buildOutboundPayload', () => {
it('将业务日期转换成毫秒,并剥离所有仅展示字段和汇总字段', () => { it('将业务日期转换成毫秒,并剥离所有仅展示字段和汇总字段', () => {
const bizDate = new Date('2026-07-22T08:30:00+08:00'); const bizDate = new Date('2026-07-22T08:30:00+08:00');
const payload = buildOutboundPayload( const payload = buildOutboundPayload(
{ billType: 'SALE', bizDate, remark: '主备注', storId: 'S-1' }, {
billStatus: '10',
billType: 'SALE',
bizDate,
remark: '主备注',
storId: 'S-1',
},
[ [
{ {
groupId: 11, groupId: 11,
@@ -79,7 +85,7 @@ describe('buildOutboundPayload', () => {
it('手工汇总行不附带库存引用字段', () => { it('手工汇总行不附带库存引用字段', () => {
const payload = buildOutboundPayload( const payload = buildOutboundPayload(
{ billType: 'SALE', bizDate: 1, storId: 'S-1' }, { billStatus: '10', billType: 'SALE', bizDate: 1, storId: 'S-1' },
[{ materialCode: 'M-1', planQty: 2 }], [{ materialCode: 'M-1', planQty: 2 }],
); );

View File

@@ -24,6 +24,7 @@ export function shouldApplyEditResult(
export interface OutboundFormValues { export interface OutboundFormValues {
billType?: string; billType?: string;
billStatus?: string;
bizDate?: number | string | { valueOf: () => number }; bizDate?: number | string | { valueOf: () => number };
remark?: string; remark?: string;
storId?: string; storId?: string;
@@ -48,6 +49,7 @@ export function buildOutboundPayload(
): WmsIostorInvApi.OutboundCreateReq { ): WmsIostorInvApi.OutboundCreateReq {
return { return {
billType: values.billType!, billType: values.billType!,
billStatus: values.billStatus!,
bizDate: Number(values.bizDate?.valueOf()), bizDate: Number(values.bizDate?.valueOf()),
details: details.map((detail) => ({ details: details.map((detail) => ({
groupId: detail.groupId, groupId: detail.groupId,