feat(asset): 新增资产退库功能模块
- 创建退库单新增页面,支持选择接收仓库和添加退库资产明细 - 实现可退库资产候选对象,包含原领用仓库和位置信息追踪功能 - 开发退库单主对象和明细对象,支持退库流程的状态管理和信息快照 - 构建退库单控制器,提供退库申请、编辑、提交、确认等完整业务流程 - 实现退库资产选择器,支持按原仓库来源筛选和防重复选择校验 - 添加退库单提交和确认功能,包含资产状态变更和使用人归属清理 - 集成仓库和位置服务,确保退库后资产仓位信息的准确更新main
parent
e4b77612e0
commit
0c7cd2b3df
@ -0,0 +1,22 @@
|
||||
package com.ruoyi.asset.constant;
|
||||
|
||||
/**
|
||||
* 退库单状态常量
|
||||
*
|
||||
* @author Yangk
|
||||
*/
|
||||
public final class ReturnOrderStatus
|
||||
{
|
||||
/** 草稿 */
|
||||
public static final String DRAFT = "DRAFT";
|
||||
|
||||
/** 待确认 */
|
||||
public static final String PENDING_CONFIRM = "PENDING_CONFIRM";
|
||||
|
||||
/** 已退库 */
|
||||
public static final String RETURNED = "RETURNED";
|
||||
|
||||
private ReturnOrderStatus()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.ruoyi.asset.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.asset.domain.AmsAsset;
|
||||
import com.ruoyi.asset.domain.AmsReturnAssetCandidate;
|
||||
import com.ruoyi.asset.domain.AmsReturnOrder;
|
||||
|
||||
/**
|
||||
* 退库管理Service接口
|
||||
*
|
||||
* @author Yangk
|
||||
*/
|
||||
public interface IAmsReturnOrderService
|
||||
{
|
||||
public AmsReturnOrder selectAmsReturnOrderByOrderId(Long orderId);
|
||||
|
||||
public List<AmsReturnOrder> selectAmsReturnOrderList(AmsReturnOrder amsReturnOrder);
|
||||
|
||||
/** 查询未被其他有效退库单占用的在用资产。 */
|
||||
public List<AmsReturnAssetCandidate> selectAvailableReturnAssetList(AmsAsset amsAsset, Long currentOrderId,
|
||||
Long originWarehouseId, Boolean missingOriginOnly);
|
||||
|
||||
public int insertAmsReturnOrder(AmsReturnOrder amsReturnOrder, Long applicantId,
|
||||
String applicantName, Long applyDeptId);
|
||||
|
||||
public int updateAmsReturnOrder(AmsReturnOrder amsReturnOrder);
|
||||
|
||||
/** 提交退库单(草稿 → 待确认)。 */
|
||||
public int submitReturn(Long orderId, String operateLoginName);
|
||||
|
||||
/** 确认退库(待确认 → 已退库)。 */
|
||||
public int confirmReturn(Long orderId, Long operateUserId, String operateUserName,
|
||||
String operateLoginName);
|
||||
|
||||
public int deleteAmsReturnOrderByOrderIds(String orderIds);
|
||||
|
||||
public int deleteAmsReturnOrderByOrderId(Long orderId);
|
||||
}
|
||||
@ -0,0 +1,389 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.asset.mapper.AmsReturnOrderMapper">
|
||||
|
||||
<resultMap type="AmsReturnOrder" id="AmsReturnOrderResult">
|
||||
<result property="orderId" column="order_id" />
|
||||
<result property="returnNo" column="return_no" />
|
||||
<result property="applicantId" column="applicant_id" />
|
||||
<result property="applicantName" column="applicant_name" />
|
||||
<result property="applyDeptId" column="apply_dept_id" />
|
||||
<result property="applyDeptName" column="apply_dept_name" />
|
||||
<result property="receiveWarehouseId" column="receive_warehouse_id" />
|
||||
<result property="receiveWarehouseCode" column="receive_warehouse_code" />
|
||||
<result property="receiveWarehouseName" column="receive_warehouse_name" />
|
||||
<result property="confirmUserId" column="confirm_user_id" />
|
||||
<result property="confirmUserName" column="confirm_user_name" />
|
||||
<result property="confirmTime" column="confirm_time" />
|
||||
<result property="orderStatus" column="order_status" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="AmsReturnOrderItemCollectionResult" type="AmsReturnOrder" extends="AmsReturnOrderResult">
|
||||
<collection property="amsReturnOrderItemList" ofType="AmsReturnOrderItem"
|
||||
column="order_id" select="selectAmsReturnOrderItemList" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="AmsReturnOrderItem" id="AmsReturnOrderItemResult">
|
||||
<result property="itemId" column="item_id" />
|
||||
<result property="orderId" column="order_id" />
|
||||
<result property="returnNo" column="return_no" />
|
||||
<result property="assetId" column="asset_id" />
|
||||
<result property="assetCode" column="asset_code" />
|
||||
<result property="assetName" column="asset_name" />
|
||||
<result property="categoryId" column="category_id" />
|
||||
<result property="categoryCode" column="category_code" />
|
||||
<result property="categoryName" column="category_name" />
|
||||
<result property="specModel" column="spec_model" />
|
||||
<result property="brand" column="brand" />
|
||||
<result property="assetStatus" column="asset_status" />
|
||||
<result property="beforeUserId" column="before_user_id" />
|
||||
<result property="beforeUserName" column="before_user_name" />
|
||||
<result property="beforeDeptId" column="before_dept_id" />
|
||||
<result property="beforeDeptName" column="before_dept_name" />
|
||||
<result property="afterWarehouseId" column="after_warehouse_id" />
|
||||
<result property="afterWarehouseCode" column="after_warehouse_code" />
|
||||
<result property="afterWarehouseName" column="after_warehouse_name" />
|
||||
<result property="afterLocationId" column="after_location_id" />
|
||||
<result property="afterLocationCode" column="after_location_code" />
|
||||
<result property="afterLocationName" column="after_location_name" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="AmsReturnAssetCandidate" id="AvailableReturnAssetResult">
|
||||
<result property="assetId" column="asset_id" />
|
||||
<result property="assetCode" column="asset_code" />
|
||||
<result property="assetName" column="asset_name" />
|
||||
<result property="categoryId" column="category_id" />
|
||||
<result property="categoryCode" column="category_code" />
|
||||
<result property="categoryName" column="category_name" />
|
||||
<result property="specModel" column="spec_model" />
|
||||
<result property="brand" column="brand" />
|
||||
<result property="assetStatus" column="asset_status" />
|
||||
<result property="warehouseId" column="warehouse_id" />
|
||||
<result property="warehouseCode" column="warehouse_code" />
|
||||
<result property="warehouseName" column="warehouse_name" />
|
||||
<result property="locationId" column="location_id" />
|
||||
<result property="locationCode" column="location_code" />
|
||||
<result property="locationName" column="location_name" />
|
||||
<result property="useDeptId" column="use_dept_id" />
|
||||
<result property="useDeptName" column="use_dept_name" />
|
||||
<result property="useUserId" column="use_user_id" />
|
||||
<result property="useUserName" column="use_user_name" />
|
||||
<result property="tagCode" column="tag_code" />
|
||||
<result property="sourceReceiveOrderId" column="source_receive_order_id" />
|
||||
<result property="sourceReceiveItemId" column="source_receive_item_id" />
|
||||
<result property="originWarehouseId" column="origin_warehouse_id" />
|
||||
<result property="originWarehouseCode" column="origin_warehouse_code" />
|
||||
<result property="originWarehouseName" column="origin_warehouse_name" />
|
||||
<result property="originWarehouseEnabled" column="origin_warehouse_enabled" />
|
||||
<result property="originLocationId" column="origin_location_id" />
|
||||
<result property="originLocationCode" column="origin_location_code" />
|
||||
<result property="originLocationName" column="origin_location_name" />
|
||||
<result property="originLocationEnabled" column="origin_location_enabled" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectAmsReturnOrderVo">
|
||||
select order_id, return_no, applicant_id, applicant_name, apply_dept_id, apply_dept_name,
|
||||
receive_warehouse_id, receive_warehouse_code, receive_warehouse_name,
|
||||
confirm_user_id, confirm_user_name, confirm_time, order_status,
|
||||
create_by, create_time, update_by, update_time, remark, del_flag
|
||||
from ams_return_order
|
||||
</sql>
|
||||
|
||||
<select id="selectAmsReturnOrderList" parameterType="AmsReturnOrder" resultMap="AmsReturnOrderResult">
|
||||
<include refid="selectAmsReturnOrderVo"/>
|
||||
<where>
|
||||
del_flag = '0'
|
||||
<if test="returnNo != null and returnNo != ''">
|
||||
and return_no like concat(#{returnNo}, '%')
|
||||
</if>
|
||||
<if test="applicantName != null and applicantName != ''">
|
||||
and applicant_name like concat('%', #{applicantName}, '%')
|
||||
</if>
|
||||
<if test="applyDeptId != null">
|
||||
and apply_dept_id = #{applyDeptId}
|
||||
</if>
|
||||
<if test="receiveWarehouseId != null">
|
||||
and receive_warehouse_id = #{receiveWarehouseId}
|
||||
</if>
|
||||
<if test="params.beginConfirmTime != null and params.beginConfirmTime != ''">
|
||||
and confirm_time >= #{params.beginConfirmTime}
|
||||
</if>
|
||||
<if test="params.endConfirmTime != null and params.endConfirmTime != ''">
|
||||
and confirm_time < date_add(#{params.endConfirmTime}, interval 1 day)
|
||||
</if>
|
||||
<if test="orderStatus != null and orderStatus != ''">
|
||||
and order_status = #{orderStatus}
|
||||
</if>
|
||||
<if test="params.assetCode != null and params.assetCode != ''">
|
||||
and exists (
|
||||
select 1 from ams_return_order_item item
|
||||
where item.order_id = ams_return_order.order_id
|
||||
and item.del_flag = '0'
|
||||
and item.asset_code like concat(#{params.assetCode}, '%')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc, order_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectAmsReturnOrderByOrderId" parameterType="Long"
|
||||
resultMap="AmsReturnOrderItemCollectionResult">
|
||||
<include refid="selectAmsReturnOrderVo"/>
|
||||
where order_id = #{orderId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectAmsReturnOrderByOrderIdForUpdate" parameterType="Long"
|
||||
resultMap="AmsReturnOrderItemCollectionResult">
|
||||
<include refid="selectAmsReturnOrderVo"/>
|
||||
where order_id = #{orderId} and del_flag = '0'
|
||||
for update
|
||||
</select>
|
||||
|
||||
<select id="selectAvailableReturnAssetList" resultMap="AvailableReturnAssetResult">
|
||||
select asset.asset_id, asset.asset_code, asset.asset_name, asset.category_id,
|
||||
asset.category_code, asset.category_name, asset.spec_model, asset.brand,
|
||||
asset.asset_status, asset.warehouse_id, asset.warehouse_code, asset.warehouse_name,
|
||||
asset.location_id, asset.location_code, asset.location_name, asset.use_dept_id,
|
||||
asset.use_dept_name, asset.use_user_id, asset.use_user_name, asset.tag_code,
|
||||
origin_order.order_id as source_receive_order_id,
|
||||
origin_item.item_id as source_receive_item_id,
|
||||
origin_item.before_warehouse_id as origin_warehouse_id,
|
||||
origin_item.before_warehouse_code as origin_warehouse_code,
|
||||
origin_item.before_warehouse_name as origin_warehouse_name,
|
||||
origin_warehouse.enabled as origin_warehouse_enabled,
|
||||
origin_item.before_location_id as origin_location_id,
|
||||
origin_item.before_location_code as origin_location_code,
|
||||
origin_item.before_location_name as origin_location_name,
|
||||
origin_location.enabled as origin_location_enabled
|
||||
from ams_asset asset
|
||||
left join ams_receive_order_item origin_item on origin_item.item_id = (
|
||||
select candidate_item.item_id
|
||||
from ams_receive_order_item candidate_item
|
||||
inner join ams_receive_order candidate_order on candidate_order.order_id = candidate_item.order_id
|
||||
and candidate_order.del_flag = '0'
|
||||
and candidate_order.order_status = 'COMPLETED'
|
||||
where candidate_item.asset_id = asset.asset_id
|
||||
and candidate_item.del_flag = '0'
|
||||
order by candidate_order.confirm_time desc, candidate_order.order_id desc, candidate_item.item_id desc
|
||||
limit 1
|
||||
)
|
||||
left join ams_receive_order origin_order on origin_order.order_id = origin_item.order_id
|
||||
left join ams_warehouse origin_warehouse on origin_warehouse.warehouse_id = origin_item.before_warehouse_id
|
||||
and origin_warehouse.del_flag = '0'
|
||||
left join ams_asset_location origin_location on origin_location.location_id = origin_item.before_location_id
|
||||
and origin_location.del_flag = '0'
|
||||
where asset.del_flag = '0'
|
||||
and asset.asset_status = #{inUseStatus}
|
||||
and not exists (
|
||||
select 1
|
||||
from ams_return_order_item item
|
||||
inner join ams_return_order return_order on return_order.order_id = item.order_id
|
||||
and return_order.del_flag = '0'
|
||||
and return_order.order_status in (#{draftStatus}, #{pendingStatus})
|
||||
where item.asset_id = asset.asset_id
|
||||
and item.del_flag = '0'
|
||||
<if test="currentOrderId != null">
|
||||
and return_order.order_id != #{currentOrderId}
|
||||
</if>
|
||||
)
|
||||
<if test="asset.assetCode != null and asset.assetCode != ''">
|
||||
and asset.asset_code like concat('%', #{asset.assetCode}, '%')
|
||||
</if>
|
||||
<if test="asset.assetName != null and asset.assetName != ''">
|
||||
and asset.asset_name like concat('%', #{asset.assetName}, '%')
|
||||
</if>
|
||||
<if test="asset.categoryName != null and asset.categoryName != ''">
|
||||
and asset.category_name like concat('%', #{asset.categoryName}, '%')
|
||||
</if>
|
||||
<if test="asset.useDeptId != null">
|
||||
and asset.use_dept_id = #{asset.useDeptId}
|
||||
</if>
|
||||
<if test="asset.useUserName != null and asset.useUserName != ''">
|
||||
and asset.use_user_name like concat('%', #{asset.useUserName}, '%')
|
||||
</if>
|
||||
<if test="originWarehouseId != null">
|
||||
and origin_item.before_warehouse_id = #{originWarehouseId}
|
||||
and origin_item.before_location_id is not null
|
||||
</if>
|
||||
<if test="missingOriginOnly != null and missingOriginOnly">
|
||||
and (origin_item.before_warehouse_id is null or origin_item.before_location_id is null)
|
||||
</if>
|
||||
order by asset.asset_id
|
||||
</select>
|
||||
|
||||
<select id="selectReturnAssetCandidateByAssetId" parameterType="Long" resultMap="AvailableReturnAssetResult">
|
||||
select asset.asset_id, asset.asset_code, asset.asset_name, asset.category_id,
|
||||
asset.category_code, asset.category_name, asset.spec_model, asset.brand,
|
||||
asset.asset_status, asset.warehouse_id, asset.warehouse_code, asset.warehouse_name,
|
||||
asset.location_id, asset.location_code, asset.location_name, asset.use_dept_id,
|
||||
asset.use_dept_name, asset.use_user_id, asset.use_user_name, asset.tag_code,
|
||||
origin_order.order_id as source_receive_order_id,
|
||||
origin_item.item_id as source_receive_item_id,
|
||||
origin_item.before_warehouse_id as origin_warehouse_id,
|
||||
origin_item.before_warehouse_code as origin_warehouse_code,
|
||||
origin_item.before_warehouse_name as origin_warehouse_name,
|
||||
origin_warehouse.enabled as origin_warehouse_enabled,
|
||||
origin_item.before_location_id as origin_location_id,
|
||||
origin_item.before_location_code as origin_location_code,
|
||||
origin_item.before_location_name as origin_location_name,
|
||||
origin_location.enabled as origin_location_enabled
|
||||
from ams_asset asset
|
||||
left join ams_receive_order_item origin_item on origin_item.item_id = (
|
||||
select candidate_item.item_id
|
||||
from ams_receive_order_item candidate_item
|
||||
inner join ams_receive_order candidate_order on candidate_order.order_id = candidate_item.order_id
|
||||
and candidate_order.del_flag = '0'
|
||||
and candidate_order.order_status = 'COMPLETED'
|
||||
where candidate_item.asset_id = asset.asset_id
|
||||
and candidate_item.del_flag = '0'
|
||||
order by candidate_order.confirm_time desc, candidate_order.order_id desc, candidate_item.item_id desc
|
||||
limit 1
|
||||
)
|
||||
left join ams_receive_order origin_order on origin_order.order_id = origin_item.order_id
|
||||
left join ams_warehouse origin_warehouse on origin_warehouse.warehouse_id = origin_item.before_warehouse_id
|
||||
and origin_warehouse.del_flag = '0'
|
||||
left join ams_asset_location origin_location on origin_location.location_id = origin_item.before_location_id
|
||||
and origin_location.del_flag = '0'
|
||||
where asset.asset_id = #{assetId}
|
||||
and asset.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="countOtherActiveReturnOrderByAssetId" resultType="int">
|
||||
select count(1)
|
||||
from ams_return_order_item item
|
||||
inner join ams_return_order return_order on return_order.order_id = item.order_id
|
||||
and return_order.del_flag = '0'
|
||||
and return_order.order_status in (#{draftStatus}, #{pendingStatus})
|
||||
where item.asset_id = #{assetId}
|
||||
and item.del_flag = '0'
|
||||
<if test="currentOrderId != null">
|
||||
and return_order.order_id != #{currentOrderId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectAmsReturnOrderItemList" resultMap="AmsReturnOrderItemResult">
|
||||
select item.item_id, item.order_id, item.return_no, item.asset_id, item.asset_code,
|
||||
item.asset_name, item.category_id, item.category_code, item.category_name,
|
||||
item.spec_model, item.brand, asset.asset_status, item.before_user_id,
|
||||
item.before_user_name, item.before_dept_id, item.before_dept_name,
|
||||
item.after_warehouse_id, item.after_warehouse_code, item.after_warehouse_name,
|
||||
item.after_location_id, item.after_location_code, item.after_location_name,
|
||||
item.create_by, item.create_time, item.update_by, item.update_time,
|
||||
item.remark, item.del_flag
|
||||
from ams_return_order_item item
|
||||
left join ams_asset asset on asset.asset_id = item.asset_id and asset.del_flag = '0'
|
||||
where item.order_id = #{order_id} and item.del_flag = '0'
|
||||
order by item.item_id
|
||||
</select>
|
||||
|
||||
<insert id="insertAmsReturnOrder" parameterType="AmsReturnOrder"
|
||||
useGeneratedKeys="true" keyProperty="orderId">
|
||||
insert into ams_return_order (
|
||||
return_no, applicant_id, applicant_name, apply_dept_id, apply_dept_name,
|
||||
receive_warehouse_id, receive_warehouse_code, receive_warehouse_name,
|
||||
order_status, create_by, create_time, remark, del_flag
|
||||
) values (
|
||||
#{returnNo}, #{applicantId}, #{applicantName}, #{applyDeptId}, #{applyDeptName},
|
||||
#{receiveWarehouseId}, #{receiveWarehouseCode}, #{receiveWarehouseName},
|
||||
#{orderStatus}, #{createBy}, #{createTime}, #{remark}, #{delFlag}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateAmsReturnOrder" parameterType="AmsReturnOrder">
|
||||
update ams_return_order
|
||||
set receive_warehouse_id = #{receiveWarehouseId},
|
||||
receive_warehouse_code = #{receiveWarehouseCode},
|
||||
receive_warehouse_name = #{receiveWarehouseName},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime},
|
||||
remark = #{remark}
|
||||
where order_id = #{orderId} and del_flag = '0' and order_status = 'DRAFT'
|
||||
</update>
|
||||
|
||||
<update id="submitAmsReturnOrder" parameterType="AmsReturnOrder">
|
||||
update ams_return_order
|
||||
set order_status = #{orderStatus},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where order_id = #{orderId} and del_flag = '0' and order_status = 'DRAFT'
|
||||
</update>
|
||||
|
||||
<update id="confirmAmsReturnOrder" parameterType="AmsReturnOrder">
|
||||
update ams_return_order
|
||||
set order_status = #{orderStatus},
|
||||
confirm_user_id = #{confirmUserId},
|
||||
confirm_user_name = #{confirmUserName},
|
||||
confirm_time = #{confirmTime},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where order_id = #{orderId} and del_flag = '0' and order_status = 'PENDING_CONFIRM'
|
||||
</update>
|
||||
|
||||
<update id="deleteAmsReturnOrderByOrderId" parameterType="Long">
|
||||
update ams_return_order
|
||||
set del_flag = '1'
|
||||
where order_id = #{orderId} and del_flag = '0' and order_status = 'DRAFT'
|
||||
</update>
|
||||
|
||||
<update id="deleteAmsReturnOrderByOrderIds" parameterType="String">
|
||||
update ams_return_order
|
||||
set del_flag = '1'
|
||||
where order_id in
|
||||
<foreach item="orderId" collection="array" open="(" separator="," close=")">
|
||||
#{orderId}
|
||||
</foreach>
|
||||
and del_flag = '0' and order_status = 'DRAFT'
|
||||
</update>
|
||||
|
||||
<update id="deleteAmsReturnOrderItemByOrderIds" parameterType="String">
|
||||
update ams_return_order_item
|
||||
set del_flag = '1'
|
||||
where order_id in
|
||||
<foreach item="orderId" collection="array" open="(" separator="," close=")">
|
||||
#{orderId}
|
||||
</foreach>
|
||||
and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="deleteAmsReturnOrderItemByOrderId" parameterType="Long">
|
||||
update ams_return_order_item
|
||||
set del_flag = '1'
|
||||
where order_id = #{orderId} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<insert id="batchAmsReturnOrderItem">
|
||||
insert into ams_return_order_item (
|
||||
order_id, return_no, asset_id, asset_code, asset_name, category_id, category_code,
|
||||
category_name, spec_model, brand, before_user_id, before_user_name, before_dept_id,
|
||||
before_dept_name, after_warehouse_id, after_warehouse_code, after_warehouse_name,
|
||||
after_location_id, after_location_code, after_location_name,
|
||||
create_by, create_time, remark, del_flag
|
||||
) values
|
||||
<foreach item="item" collection="list" separator=",">
|
||||
(
|
||||
#{item.orderId}, #{item.returnNo}, #{item.assetId}, #{item.assetCode},
|
||||
#{item.assetName}, #{item.categoryId}, #{item.categoryCode}, #{item.categoryName},
|
||||
#{item.specModel}, #{item.brand}, #{item.beforeUserId}, #{item.beforeUserName},
|
||||
#{item.beforeDeptId}, #{item.beforeDeptName}, #{item.afterWarehouseId},
|
||||
#{item.afterWarehouseCode}, #{item.afterWarehouseName}, #{item.afterLocationId},
|
||||
#{item.afterLocationCode}, #{item.afterLocationName}, #{item.createBy},
|
||||
#{item.createTime}, #{item.remark}, #{item.delFlag}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,229 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:include="include :: header('新增退库单')" />
|
||||
<style type="text/css">
|
||||
table label.error { position: inherit; }
|
||||
select + label.error { z-index: 1; right: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="white-bg">
|
||||
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
|
||||
<form class="form-horizontal m" id="form-return-add">
|
||||
<h4 class="form-header h4">基本信息</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label is-required">接收仓库:</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="receiveWarehouseId" class="form-control" required
|
||||
onchange="changeReceiveWarehouse()">
|
||||
<option value="">请选择接收仓库</option>
|
||||
<option th:each="warehouse : ${warehouseList}" th:value="${warehouse.warehouseId}"
|
||||
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">备注:</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea name="remark" maxlength="500" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="form-header h4">退库明细</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-white btn-sm" onclick="selectAssets()"><i class="fa fa-plus"> 选择资产</i></button>
|
||||
<button type="button" class="btn btn-white btn-sm" onclick="removeSelectedAssets()"><i class="fa fa-minus"> 删除</i></button>
|
||||
<div class="col-sm-12 select-table table-striped"><table id="bootstrap-table"></table></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<th:block th:include="include :: footer" />
|
||||
<script th:inline="javascript">
|
||||
var prefix = ctx + "asset/return";
|
||||
var locationList = [[${locationList}]];
|
||||
$("#form-return-add").validate({ focusCleanup: true });
|
||||
|
||||
function submitHandler() {
|
||||
if ($("#bootstrap-table").bootstrapTable("getData").length === 0) {
|
||||
$.modal.alertWarning("请至少添加一条退库明细");
|
||||
return;
|
||||
}
|
||||
if ($.validate.form()) {
|
||||
$.operate.save(prefix + "/add", $("#form-return-add").serialize());
|
||||
}
|
||||
}
|
||||
|
||||
$(function() { initDetailTable([]); });
|
||||
|
||||
function initDetailTable(data) {
|
||||
$.table.init({
|
||||
data: data,
|
||||
pagination: false,
|
||||
showSearch: false,
|
||||
showRefresh: false,
|
||||
showToggle: false,
|
||||
showColumns: false,
|
||||
sidePagination: "client",
|
||||
columns: [
|
||||
{ checkbox: true },
|
||||
{ field: "assetId", title: "资产", formatter: function(value, row, index) { return buildAssetCell(row, index); } },
|
||||
{ field: "categoryName", title: "资产类别" },
|
||||
{ field: "originWarehouseName", title: "领用前仓位", formatter: function(value, row) { return buildOriginText(row); } },
|
||||
{ field: "beforeDeptName", title: "退库前部门" },
|
||||
{ field: "beforeUserName", title: "退库前使用人" },
|
||||
{ field: "afterLocationId", title: "退库位置", formatter: function(value, row, index) { return buildLocationSelect(value, index); } },
|
||||
{ field: "remark", title: "明细备注", formatter: function(value, row, index) { return buildInput("amsReturnOrderItemList[" + index + "].remark", value, 500); } },
|
||||
{ title: "操作", align: "center", formatter: function(value, row) {
|
||||
return '<a class="btn btn-danger btn-xs" href="javascript:void(0)" onclick="removeAsset(\'' + row.assetId + '\')"><i class="fa fa-remove"></i>删除</a>';
|
||||
}}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function selectAssets() {
|
||||
syncDetailRows();
|
||||
$.modal.openOptions({ title: "选择可退库资产", url: buildSelectorUrl(), width: "1200", height: "680", callBack: addSelectedAssets });
|
||||
}
|
||||
|
||||
function addSelectedAssets(index, layero) {
|
||||
var selectedAssets = layero.find("iframe")[0].contentWindow.getSelectedAssets();
|
||||
if (!selectedAssets || selectedAssets.length === 0) {
|
||||
$.modal.alertWarning("请至少选择一条资产记录");
|
||||
return;
|
||||
}
|
||||
syncDetailRows();
|
||||
var currentRows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
if (!isCompatibleOriginGroup(currentRows.concat(selectedAssets))) {
|
||||
$.modal.alertWarning("一张退库单只能选择同一原领用仓库的资产,缺少领用来源的资产不能与可追溯资产混选");
|
||||
return;
|
||||
}
|
||||
if (currentRows.length === 0 && isOriginEnabled(selectedAssets[0])) {
|
||||
$("[name='receiveWarehouseId']").val(selectedAssets[0].originWarehouseId);
|
||||
}
|
||||
var existing = {};
|
||||
$.each(currentRows, function(i, row) { existing[String(row.assetId)] = true; });
|
||||
$.each(selectedAssets, function(i, asset) {
|
||||
if (!existing[String(asset.assetId)]) {
|
||||
$("#bootstrap-table").bootstrapTable("insertRow", { index: $("#bootstrap-table").bootstrapTable("getData").length, row: buildReturnItem(asset) });
|
||||
existing[String(asset.assetId)] = true;
|
||||
}
|
||||
});
|
||||
$.modal.close(index);
|
||||
}
|
||||
|
||||
function buildReturnItem(asset) {
|
||||
return {
|
||||
assetId: asset.assetId, assetCode: asset.assetCode, assetName: asset.assetName,
|
||||
categoryName: asset.categoryName, beforeDeptId: asset.useDeptId,
|
||||
beforeDeptName: asset.useDeptName, beforeUserId: asset.useUserId,
|
||||
beforeUserName: asset.useUserName, originWarehouseId: asset.originWarehouseId,
|
||||
originWarehouseCode: asset.originWarehouseCode, originWarehouseName: asset.originWarehouseName,
|
||||
originWarehouseEnabled: asset.originWarehouseEnabled, originLocationId: asset.originLocationId,
|
||||
originLocationCode: asset.originLocationCode, originLocationName: asset.originLocationName,
|
||||
originLocationEnabled: asset.originLocationEnabled,
|
||||
afterLocationId: isOriginEnabled(asset)
|
||||
&& String(asset.originWarehouseId) === String($("[name='receiveWarehouseId']").val())
|
||||
? asset.originLocationId : "", remark: ""
|
||||
};
|
||||
}
|
||||
|
||||
function buildSelectorUrl() {
|
||||
var url = prefix + "/selectAsset";
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
if (rows.length === 0) { return url; }
|
||||
if (isTraceable(rows[0])) {
|
||||
return url + "?originWarehouseId=" + rows[0].originWarehouseId;
|
||||
}
|
||||
return url + "?missingOriginOnly=true";
|
||||
}
|
||||
|
||||
function isCompatibleOriginGroup(rows) {
|
||||
if (!rows || rows.length === 0) { return true; }
|
||||
var traceable = isTraceable(rows[0]);
|
||||
var warehouseId = traceable ? String(rows[0].originWarehouseId) : "";
|
||||
for (var i = 1; i < rows.length; i++) {
|
||||
if (isTraceable(rows[i]) !== traceable) { return false; }
|
||||
if (traceable && String(rows[i].originWarehouseId) !== warehouseId) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTraceable(row) {
|
||||
return row.originWarehouseId != null && row.originLocationId != null;
|
||||
}
|
||||
|
||||
function isOriginEnabled(row) {
|
||||
return isTraceable(row) && row.originWarehouseEnabled === "Y" && row.originLocationEnabled === "Y";
|
||||
}
|
||||
|
||||
function buildOriginText(row) {
|
||||
if (!isTraceable(row)) { return "无可追溯领用前仓位"; }
|
||||
var text = (row.originWarehouseName || "-") + " / " + (row.originLocationName || "-");
|
||||
return text + (isOriginEnabled(row) ? "" : "(原仓位已停用)");
|
||||
}
|
||||
|
||||
function changeReceiveWarehouse() {
|
||||
syncDetailRows();
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
$.each(rows, function(index, row) { row.afterLocationId = ""; });
|
||||
$("#bootstrap-table").bootstrapTable("load", rows);
|
||||
}
|
||||
|
||||
function removeSelectedAssets() {
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getSelections");
|
||||
if (rows.length === 0) {
|
||||
$.modal.alertWarning("请至少选择一条记录");
|
||||
return;
|
||||
}
|
||||
syncDetailRows();
|
||||
$("#bootstrap-table").bootstrapTable("remove", { field: "assetId", values: $.map(rows, function(row) { return row.assetId; }) });
|
||||
}
|
||||
|
||||
function removeAsset(assetId) {
|
||||
syncDetailRows();
|
||||
$("#bootstrap-table").bootstrapTable("remove", { field: "assetId", values: [assetId] });
|
||||
}
|
||||
|
||||
function syncDetailRows() {
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
$.each(rows, function(index, row) {
|
||||
var tr = $("#bootstrap-table tbody tr[data-index='" + index + "']");
|
||||
row.afterLocationId = tr.find("[name$='.afterLocationId']").val() || "";
|
||||
row.remark = tr.find("[name$='.remark']").val() || "";
|
||||
});
|
||||
}
|
||||
|
||||
function buildAssetCell(row, index) {
|
||||
var hidden = $("<input>").attr({ type: "hidden", name: "amsReturnOrderItemList[" + index + "].assetId", value: row.assetId }).prop("outerHTML");
|
||||
return hidden + $("<span>").text(row.assetCode + " - " + row.assetName).prop("outerHTML");
|
||||
}
|
||||
|
||||
function buildLocationSelect(value, index) {
|
||||
var warehouseId = $("[name='receiveWarehouseId']").val();
|
||||
var select = $("<select>").addClass("form-control").attr({ name: "amsReturnOrderItemList[" + index + "].afterLocationId", required: true });
|
||||
select.append($("<option>").val("").text("请选择退库位置"));
|
||||
$.each(locationList, function(i, location) {
|
||||
if (String(location.warehouseId) === String(warehouseId)) {
|
||||
var option = $("<option>").val(location.locationId).text(location.locationCode + " - " + location.locationName);
|
||||
if (String(location.locationId) === String(value)) { option.attr("selected", "selected"); }
|
||||
select.append(option);
|
||||
}
|
||||
});
|
||||
return select.prop("outerHTML");
|
||||
}
|
||||
|
||||
function buildInput(name, value, maxLength) {
|
||||
return $("<input>").addClass("form-control").attr({ type: "text", name: name, maxlength: maxLength }).val(value || "").prop("outerHTML");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,244 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:include="include :: header('修改退库单')" />
|
||||
<style type="text/css">
|
||||
table label.error { position: inherit; }
|
||||
select + label.error { z-index: 1; right: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="white-bg">
|
||||
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
|
||||
<form class="form-horizontal m" id="form-return-edit" th:object="${amsReturnOrder}">
|
||||
<input name="orderId" th:field="*{orderId}" type="hidden">
|
||||
<h4 class="form-header h4">基本信息</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">退库单号:</label>
|
||||
<div class="col-sm-8"><input th:value="*{returnNo}" class="form-control" type="text" readonly></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">申请人/部门:</label>
|
||||
<div class="col-sm-8"><input th:value="${amsReturnOrder.applicantName + ' / ' + amsReturnOrder.applyDeptName}" class="form-control" type="text" readonly></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label is-required">接收仓库:</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="receiveWarehouseId" th:field="*{receiveWarehouseId}" class="form-control" required
|
||||
onchange="changeReceiveWarehouse()">
|
||||
<option value="">请选择接收仓库</option>
|
||||
<option th:each="warehouse : ${warehouseList}" th:value="${warehouse.warehouseId}"
|
||||
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">备注:</label>
|
||||
<div class="col-sm-10"><textarea name="remark" maxlength="500" class="form-control" rows="3">[[*{remark}]]</textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="form-header h4">退库明细</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-white btn-sm" onclick="selectAssets()"><i class="fa fa-plus"> 选择资产</i></button>
|
||||
<button type="button" class="btn btn-white btn-sm" onclick="removeSelectedAssets()"><i class="fa fa-minus"> 删除</i></button>
|
||||
<div class="col-sm-12 select-table table-striped"><table id="bootstrap-table"></table></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<th:block th:include="include :: footer" />
|
||||
<script th:inline="javascript">
|
||||
var prefix = ctx + "asset/return";
|
||||
var orderId = [[${amsReturnOrder.orderId}]];
|
||||
var detailList = [[${amsReturnOrder.amsReturnOrderItemList}]];
|
||||
var locationList = [[${locationList}]];
|
||||
$("#form-return-edit").validate({ focusCleanup: true });
|
||||
|
||||
function submitHandler() {
|
||||
if ($("#bootstrap-table").bootstrapTable("getData").length === 0) {
|
||||
$.modal.alertWarning("请至少保留一条退库明细");
|
||||
return;
|
||||
}
|
||||
if ($.validate.form()) {
|
||||
$.operate.save(prefix + "/edit", $("#form-return-edit").serialize());
|
||||
}
|
||||
}
|
||||
|
||||
$(function() { initDetailTable(detailList); });
|
||||
|
||||
function initDetailTable(data) {
|
||||
$.table.init({
|
||||
data: data,
|
||||
pagination: false,
|
||||
showSearch: false,
|
||||
showRefresh: false,
|
||||
showToggle: false,
|
||||
showColumns: false,
|
||||
sidePagination: "client",
|
||||
columns: [
|
||||
{ checkbox: true },
|
||||
{ field: "assetId", title: "资产", formatter: function(value, row, index) { return buildAssetCell(row, index); } },
|
||||
{ field: "categoryName", title: "资产类别" },
|
||||
{ field: "originWarehouseName", title: "领用前仓位", formatter: function(value, row) { return buildOriginText(row); } },
|
||||
{ field: "beforeDeptName", title: "退库前部门" },
|
||||
{ field: "beforeUserName", title: "退库前使用人" },
|
||||
{ field: "afterLocationId", title: "退库位置", formatter: function(value, row, index) { return buildLocationSelect(value, index); } },
|
||||
{ field: "remark", title: "明细备注", formatter: function(value, row, index) { return buildInput("amsReturnOrderItemList[" + index + "].remark", value, 500); } },
|
||||
{ title: "操作", align: "center", formatter: function(value, row) {
|
||||
return '<a class="btn btn-danger btn-xs" href="javascript:void(0)" onclick="removeAsset(\'' + row.assetId + '\')"><i class="fa fa-remove"></i>删除</a>';
|
||||
}}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function selectAssets() {
|
||||
syncDetailRows();
|
||||
$.modal.openOptions({ title: "选择可退库资产", url: buildSelectorUrl(), width: "1200", height: "680", callBack: addSelectedAssets });
|
||||
}
|
||||
|
||||
function addSelectedAssets(index, layero) {
|
||||
var selectedAssets = layero.find("iframe")[0].contentWindow.getSelectedAssets();
|
||||
if (!selectedAssets || selectedAssets.length === 0) {
|
||||
$.modal.alertWarning("请至少选择一条资产记录");
|
||||
return;
|
||||
}
|
||||
syncDetailRows();
|
||||
var currentRows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
if (!isCompatibleOriginGroup(currentRows.concat(selectedAssets))) {
|
||||
$.modal.alertWarning("一张退库单只能选择同一原领用仓库的资产,缺少领用来源的资产不能与可追溯资产混选");
|
||||
return;
|
||||
}
|
||||
if (currentRows.length === 0 && isOriginEnabled(selectedAssets[0])) {
|
||||
$("[name='receiveWarehouseId']").val(selectedAssets[0].originWarehouseId);
|
||||
}
|
||||
var existing = {};
|
||||
$.each(currentRows, function(i, row) { existing[String(row.assetId)] = true; });
|
||||
$.each(selectedAssets, function(i, asset) {
|
||||
if (!existing[String(asset.assetId)]) {
|
||||
$("#bootstrap-table").bootstrapTable("insertRow", { index: $("#bootstrap-table").bootstrapTable("getData").length, row: buildReturnItem(asset) });
|
||||
existing[String(asset.assetId)] = true;
|
||||
}
|
||||
});
|
||||
$.modal.close(index);
|
||||
}
|
||||
|
||||
function buildReturnItem(asset) {
|
||||
return {
|
||||
assetId: asset.assetId, assetCode: asset.assetCode, assetName: asset.assetName,
|
||||
categoryName: asset.categoryName, beforeDeptId: asset.useDeptId,
|
||||
beforeDeptName: asset.useDeptName, beforeUserId: asset.useUserId,
|
||||
beforeUserName: asset.useUserName, originWarehouseId: asset.originWarehouseId,
|
||||
originWarehouseCode: asset.originWarehouseCode, originWarehouseName: asset.originWarehouseName,
|
||||
originWarehouseEnabled: asset.originWarehouseEnabled, originLocationId: asset.originLocationId,
|
||||
originLocationCode: asset.originLocationCode, originLocationName: asset.originLocationName,
|
||||
originLocationEnabled: asset.originLocationEnabled,
|
||||
afterLocationId: isOriginEnabled(asset)
|
||||
&& String(asset.originWarehouseId) === String($("[name='receiveWarehouseId']").val())
|
||||
? asset.originLocationId : "", remark: ""
|
||||
};
|
||||
}
|
||||
|
||||
function buildSelectorUrl() {
|
||||
var url = prefix + "/selectAsset?orderId=" + orderId;
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
if (rows.length === 0) { return url; }
|
||||
if (isTraceable(rows[0])) {
|
||||
return url + "&originWarehouseId=" + rows[0].originWarehouseId;
|
||||
}
|
||||
return url + "&missingOriginOnly=true";
|
||||
}
|
||||
|
||||
function isCompatibleOriginGroup(rows) {
|
||||
if (!rows || rows.length === 0) { return true; }
|
||||
var traceable = isTraceable(rows[0]);
|
||||
var warehouseId = traceable ? String(rows[0].originWarehouseId) : "";
|
||||
for (var i = 1; i < rows.length; i++) {
|
||||
if (isTraceable(rows[i]) !== traceable) { return false; }
|
||||
if (traceable && String(rows[i].originWarehouseId) !== warehouseId) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTraceable(row) {
|
||||
return row.originWarehouseId != null && row.originLocationId != null;
|
||||
}
|
||||
|
||||
function isOriginEnabled(row) {
|
||||
return isTraceable(row) && row.originWarehouseEnabled === "Y" && row.originLocationEnabled === "Y";
|
||||
}
|
||||
|
||||
function buildOriginText(row) {
|
||||
if (!isTraceable(row)) { return "无可追溯领用前仓位"; }
|
||||
var text = (row.originWarehouseName || "-") + " / " + (row.originLocationName || "-");
|
||||
return text + (isOriginEnabled(row) ? "" : "(原仓位已停用)");
|
||||
}
|
||||
|
||||
function changeReceiveWarehouse() {
|
||||
syncDetailRows();
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
$.each(rows, function(index, row) { row.afterLocationId = ""; });
|
||||
$("#bootstrap-table").bootstrapTable("load", rows);
|
||||
}
|
||||
|
||||
function removeSelectedAssets() {
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getSelections");
|
||||
if (rows.length === 0) {
|
||||
$.modal.alertWarning("请至少选择一条记录");
|
||||
return;
|
||||
}
|
||||
syncDetailRows();
|
||||
$("#bootstrap-table").bootstrapTable("remove", { field: "assetId", values: $.map(rows, function(row) { return row.assetId; }) });
|
||||
}
|
||||
|
||||
function removeAsset(assetId) {
|
||||
syncDetailRows();
|
||||
$("#bootstrap-table").bootstrapTable("remove", { field: "assetId", values: [assetId] });
|
||||
}
|
||||
|
||||
function syncDetailRows() {
|
||||
var rows = $("#bootstrap-table").bootstrapTable("getData");
|
||||
$.each(rows, function(index, row) {
|
||||
var tr = $("#bootstrap-table tbody tr[data-index='" + index + "']");
|
||||
row.afterLocationId = tr.find("[name$='.afterLocationId']").val() || "";
|
||||
row.remark = tr.find("[name$='.remark']").val() || "";
|
||||
});
|
||||
}
|
||||
|
||||
function buildAssetCell(row, index) {
|
||||
var hidden = $("<input>").attr({ type: "hidden", name: "amsReturnOrderItemList[" + index + "].assetId", value: row.assetId }).prop("outerHTML");
|
||||
return hidden + $("<span>").text(row.assetCode + " - " + row.assetName).prop("outerHTML");
|
||||
}
|
||||
|
||||
function buildLocationSelect(value, index) {
|
||||
var warehouseId = $("[name='receiveWarehouseId']").val();
|
||||
var select = $("<select>").addClass("form-control").attr({ name: "amsReturnOrderItemList[" + index + "].afterLocationId", required: true });
|
||||
select.append($("<option>").val("").text("请选择退库位置"));
|
||||
$.each(locationList, function(i, location) {
|
||||
if (String(location.warehouseId) === String(warehouseId)) {
|
||||
var option = $("<option>").val(location.locationId).text(location.locationCode + " - " + location.locationName);
|
||||
if (String(location.locationId) === String(value)) { option.attr("selected", "selected"); }
|
||||
select.append(option);
|
||||
}
|
||||
});
|
||||
return select.prop("outerHTML");
|
||||
}
|
||||
|
||||
function buildInput(name, value, maxLength) {
|
||||
return $("<input>").addClass("form-control").attr({ type: "text", name: name, maxlength: maxLength }).val(value || "").prop("outerHTML");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
|
||||
<head>
|
||||
<th:block th:include="include :: header('资产退库列表')" />
|
||||
<th:block th:include="include :: datetimepicker-css" />
|
||||
</head>
|
||||
<body class="gray-bg">
|
||||
<div class="container-div">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 search-collapse">
|
||||
<form id="formId">
|
||||
<div class="select-list"><ul>
|
||||
<li><label>退库单号:</label><input type="text" name="returnNo"/></li>
|
||||
<li><label>申请人:</label><input type="text" name="applicantName"/></li>
|
||||
<li>
|
||||
<label>申请部门:</label>
|
||||
<select name="applyDeptId">
|
||||
<option value="">所有</option>
|
||||
<option th:each="dept : ${deptList}" th:value="${dept.deptId}" th:text="${dept.deptName}"></option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label>接收仓库:</label>
|
||||
<select name="receiveWarehouseId">
|
||||
<option value="">所有</option>
|
||||
<option th:each="warehouse : ${warehouseList}" th:value="${warehouse.warehouseId}"
|
||||
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></option>
|
||||
</select>
|
||||
</li>
|
||||
<li class="select-time">
|
||||
<label>确认时间:</label>
|
||||
<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginConfirmTime]"/>
|
||||
<span>-</span>
|
||||
<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endConfirmTime]"/>
|
||||
</li>
|
||||
<li>
|
||||
<label>单据状态:</label>
|
||||
<select name="orderStatus" th:with="type=${@dict.getType('ams_return_status')}">
|
||||
<option value="">所有</option>
|
||||
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
|
||||
</select>
|
||||
</li>
|
||||
<li><label>资产编码:</label><input type="text" name="params[assetCode]"/></li>
|
||||
<li>
|
||||
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a>
|
||||
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
|
||||
</li>
|
||||
</ul></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="btn-group-sm" id="toolbar" role="group">
|
||||
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="asset:return:add"><i class="fa fa-plus"></i> 添加</a>
|
||||
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="asset:return:export"><i class="fa fa-download"></i> 导出</a>
|
||||
</div>
|
||||
<div class="col-sm-12 select-table table-striped"><table id="bootstrap-table"></table></div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:include="include :: footer" />
|
||||
<th:block th:include="include :: datetimepicker-js" />
|
||||
<script th:inline="javascript">
|
||||
var editFlag = [[${@permission.hasPermi('asset:return:edit')}]];
|
||||
var removeFlag = [[${@permission.hasPermi('asset:return:remove')}]];
|
||||
var submitFlag = [[${@permission.hasPermi('asset:return:submit')}]];
|
||||
var confirmFlag = [[${@permission.hasPermi('asset:return:confirm')}]];
|
||||
var orderStatusDatas = [[${@dict.getType('ams_return_status')}]];
|
||||
var prefix = ctx + "asset/return";
|
||||
|
||||
$(function() {
|
||||
$.table.init({
|
||||
url: prefix + "/list",
|
||||
viewUrl: prefix + "/view/{id}",
|
||||
createUrl: prefix + "/add",
|
||||
updateUrl: prefix + "/edit/{id}",
|
||||
removeUrl: prefix + "/remove",
|
||||
exportUrl: prefix + "/export",
|
||||
modalName: "退库单",
|
||||
columns: [
|
||||
{ field: "orderId", title: "单据ID", visible: false },
|
||||
{ field: "returnNo", title: "退库单号" },
|
||||
{ field: "applicantName", title: "申请人" },
|
||||
{ field: "applyDeptName", title: "申请部门" },
|
||||
{ field: "receiveWarehouseName", title: "接收仓库" },
|
||||
{ field: "confirmUserName", title: "确认人" },
|
||||
{ field: "confirmTime", title: "确认时间" },
|
||||
{ field: "orderStatus", title: "单据状态", formatter: function(value) { return $.table.selectDictLabel(orderStatusDatas, value); } },
|
||||
{ title: "操作", align: "center", formatter: function(value, row) {
|
||||
var actions = [];
|
||||
actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.orderId + '\')"><i class="fa fa-eye"></i>查看</a> ');
|
||||
if (row.orderStatus === "DRAFT") {
|
||||
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.orderId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
|
||||
actions.push('<a class="btn btn-primary btn-xs ' + submitFlag + '" href="javascript:void(0)" onclick="submitReturn(\'' + row.orderId + '\')"><i class="fa fa-upload"></i>提交</a> ');
|
||||
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.orderId + '\')"><i class="fa fa-remove"></i>删除</a>');
|
||||
} else if (row.orderStatus === "PENDING_CONFIRM") {
|
||||
actions.push('<a class="btn btn-primary btn-xs ' + confirmFlag + '" href="javascript:void(0)" onclick="confirmReturn(\'' + row.orderId + '\')"><i class="fa fa-check"></i>确认退库</a>');
|
||||
}
|
||||
return actions.join("");
|
||||
}}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function submitReturn(orderId) {
|
||||
$.modal.confirm("提交后退库单不可再修改或删除,是否继续?", function() {
|
||||
$.operate.post(prefix + "/submit/" + orderId, {});
|
||||
});
|
||||
}
|
||||
|
||||
function confirmReturn(orderId) {
|
||||
$.modal.confirm("确认后资产将变为在库并清空使用归属,是否继续?", function() {
|
||||
$.operate.post(prefix + "/confirm/" + orderId, {});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:include="include :: header('选择可退库资产')" />
|
||||
</head>
|
||||
<body class="gray-bg">
|
||||
<div class="container-div">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 search-collapse">
|
||||
<form id="formId">
|
||||
<div class="select-list"><ul>
|
||||
<li><label>资产编码:</label><input type="text" name="assetCode"></li>
|
||||
<li><label>资产名称:</label><input type="text" name="assetName"></li>
|
||||
<li><label>资产类别:</label><input type="text" name="categoryName"></li>
|
||||
<li>
|
||||
<label>使用部门:</label>
|
||||
<select name="useDeptId">
|
||||
<option value="">所有</option>
|
||||
<option th:each="dept : ${deptList}" th:value="${dept.deptId}" th:text="${dept.deptName}"></option>
|
||||
</select>
|
||||
</li>
|
||||
<li><label>使用人:</label><input type="text" name="useUserName"></li>
|
||||
<li>
|
||||
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a>
|
||||
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
|
||||
</li>
|
||||
</ul></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-12 select-table table-striped"><table id="bootstrap-table"></table></div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:include="include :: footer" />
|
||||
<script th:inline="javascript">
|
||||
var prefix = ctx + "asset/return";
|
||||
var orderId = [[${orderId}]];
|
||||
var originWarehouseId = [[${originWarehouseId}]];
|
||||
var missingOriginOnly = [[${missingOriginOnly}]];
|
||||
$(function() {
|
||||
var url = prefix + "/availableAssetList";
|
||||
var params = [];
|
||||
if (orderId) { params.push("orderId=" + orderId); }
|
||||
if (originWarehouseId) { params.push("originWarehouseId=" + originWarehouseId); }
|
||||
if (missingOriginOnly) { params.push("missingOriginOnly=true"); }
|
||||
if (params.length > 0) { url += "?" + params.join("&"); }
|
||||
$.table.init({
|
||||
url: url,
|
||||
showSearch: false,
|
||||
showRefresh: true,
|
||||
showToggle: false,
|
||||
showColumns: false,
|
||||
modalName: "可退库资产",
|
||||
columns: [
|
||||
{ checkbox: true },
|
||||
{ field: "assetCode", title: "资产编码" },
|
||||
{ field: "assetName", title: "资产名称" },
|
||||
{ field: "categoryName", title: "资产类别" },
|
||||
{ field: "originWarehouseName", title: "领用前仓库", formatter: function(value, row) { return originText(value, row.originWarehouseEnabled); } },
|
||||
{ field: "originLocationName", title: "领用前位置", formatter: function(value, row) { return originText(value, row.originLocationEnabled); } },
|
||||
{ field: "useDeptName", title: "当前使用部门" },
|
||||
{ field: "useUserName", title: "当前使用人" }
|
||||
]
|
||||
});
|
||||
});
|
||||
function getSelectedAssets() { return $("#bootstrap-table").bootstrapTable("getSelections"); }
|
||||
function originText(value, enabled) {
|
||||
if (!value) { return "无可追溯来源"; }
|
||||
return value + (enabled === "Y" ? "" : "(已停用)");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:include="include :: header('退库单详情')" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-content">
|
||||
<form class="form-horizontal" th:object="${amsReturnOrder}">
|
||||
<h4 class="form-header h4">基本信息</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">退库单号:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{returnNo}"></p></div></div></div>
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">申请人:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{applicantName}"></p></div></div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">申请部门:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{applyDeptName}"></p></div></div></div>
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">接收仓库:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{receiveWarehouseName}"></p></div></div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">单据状态:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{@dict.getLabel('ams_return_status', orderStatus)}"></p></div></div></div>
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">确认人:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{confirmUserName}"></p></div></div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">确认时间:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{confirmTime == null ? '' : #dates.format(confirmTime, 'yyyy-MM-dd HH:mm:ss')}"></p></div></div></div>
|
||||
<div class="col-sm-6"><div class="form-group"><label class="col-sm-4 control-label">备注:</label><div class="col-sm-8"><p class="form-control-plaintext" th:text="*{remark}"></p></div></div></div>
|
||||
</div>
|
||||
<h4 class="form-header h4">退库明细</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 select-table table-striped">
|
||||
<table class="table table-bordered">
|
||||
<thead><tr><th>序号</th><th>资产编码</th><th>资产名称</th><th>资产类别</th><th>退库前部门/使用人</th><th>退库后仓库/位置</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="item, stat : *{amsReturnOrderItemList}">
|
||||
<td th:text="${stat.count}"></td>
|
||||
<td th:text="${item.assetCode}"></td>
|
||||
<td th:text="${item.assetName}"></td>
|
||||
<td th:text="${item.categoryName}"></td>
|
||||
<td th:text="${(item.beforeDeptName ?: '-') + ' / ' + (item.beforeUserName ?: '-')}"></td>
|
||||
<td th:text="${(item.afterWarehouseName ?: '-') + ' / ' + (item.afterLocationName ?: '-')}"></td>
|
||||
<td th:text="${item.remark}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<th:block th:include="include :: footer" />
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,376 @@
|
||||
package com.ruoyi.asset.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.ruoyi.asset.constant.AssetStatus;
|
||||
import com.ruoyi.asset.constant.ReturnOrderStatus;
|
||||
import com.ruoyi.asset.domain.AmsAsset;
|
||||
import com.ruoyi.asset.domain.AmsAssetLocation;
|
||||
import com.ruoyi.asset.domain.AmsReturnAssetCandidate;
|
||||
import com.ruoyi.asset.domain.AmsReturnOrder;
|
||||
import com.ruoyi.asset.domain.AmsReturnOrderItem;
|
||||
import com.ruoyi.asset.domain.AmsWarehouse;
|
||||
import com.ruoyi.asset.domain.AssetTransitionContext;
|
||||
import com.ruoyi.asset.mapper.AmsAssetMapper;
|
||||
import com.ruoyi.asset.mapper.AmsReturnOrderMapper;
|
||||
import com.ruoyi.asset.service.IAmsAssetLocationService;
|
||||
import com.ruoyi.asset.service.IAmsWarehouseService;
|
||||
import com.ruoyi.asset.service.IAssetStatusTransitionService;
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.system.service.ISysCodeRuleService;
|
||||
import com.ruoyi.system.service.ISysDeptService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AmsReturnOrderServiceImplTest
|
||||
{
|
||||
@Mock
|
||||
private AmsReturnOrderMapper amsReturnOrderMapper;
|
||||
|
||||
@Mock
|
||||
private AmsAssetMapper amsAssetMapper;
|
||||
|
||||
@Mock
|
||||
private ISysCodeRuleService sysCodeRuleService;
|
||||
|
||||
@Mock
|
||||
private IAmsWarehouseService amsWarehouseService;
|
||||
|
||||
@Mock
|
||||
private IAmsAssetLocationService amsAssetLocationService;
|
||||
|
||||
@Mock
|
||||
private ISysDeptService sysDeptService;
|
||||
|
||||
@Mock
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Mock
|
||||
private IAssetStatusTransitionService assetStatusTransitionService;
|
||||
|
||||
@InjectMocks
|
||||
private AmsReturnOrderServiceImpl service;
|
||||
|
||||
/** 新增退库草稿应生成单号,并使用台账及主数据回填快照。 */
|
||||
@Test
|
||||
void insertShouldGenerateCodeAndFillSnapshots()
|
||||
{
|
||||
AmsReturnOrder order = buildRequest();
|
||||
stubApplicant();
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouseLocation();
|
||||
when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
|
||||
doAnswer(invocation -> {
|
||||
AmsReturnOrder inserted = invocation.getArgument(0);
|
||||
inserted.setOrderId(100L);
|
||||
return 1;
|
||||
}).when(amsReturnOrderMapper).insertAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
when(amsReturnOrderMapper.batchAmsReturnOrderItem(anyList()))
|
||||
.thenAnswer(invocation -> ((List<?>) invocation.getArgument(0)).size());
|
||||
|
||||
assertEquals(1, service.insertAmsReturnOrder(order, 1L, "管理员", 103L));
|
||||
|
||||
AmsReturnOrderItem item = order.getAmsReturnOrderItemList().get(0);
|
||||
assertEquals("TK202606150001", order.getReturnNo());
|
||||
assertEquals(ReturnOrderStatus.DRAFT, order.getOrderStatus());
|
||||
assertEquals("研发部门", order.getApplyDeptName());
|
||||
assertEquals("二号仓", order.getReceiveWarehouseName());
|
||||
assertEquals("测试部门", item.getBeforeDeptName());
|
||||
assertEquals("测试用户", item.getBeforeUserName());
|
||||
assertEquals("二号仓A区", item.getAfterLocationName());
|
||||
assertEquals(100L, item.getOrderId());
|
||||
assertNotNull(item.getCreateTime());
|
||||
}
|
||||
|
||||
/** 退库位置必须属于主表所选接收仓库。 */
|
||||
@Test
|
||||
void insertShouldRejectLocationOutsideWarehouse()
|
||||
{
|
||||
AmsReturnOrder order = buildRequest();
|
||||
stubApplicant();
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouseLocation();
|
||||
when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
|
||||
AmsAssetLocation location = new AmsAssetLocation();
|
||||
location.setLocationId(20L);
|
||||
location.setWarehouseId(99L);
|
||||
location.setEnabled("Y");
|
||||
when(amsAssetLocationService.selectAmsAssetLocationByLocationId(20L)).thenReturn(location);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.insertAmsReturnOrder(order, 1L, "管理员", 103L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("不属于接收仓库"));
|
||||
verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
}
|
||||
|
||||
/** 被其他草稿或待确认退库单占用的资产不得再次加入退库单。 */
|
||||
@Test
|
||||
void insertShouldRejectAssetOccupiedByOtherActiveReturn()
|
||||
{
|
||||
AmsReturnOrder order = buildRequest();
|
||||
stubApplicant();
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouse();
|
||||
when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
|
||||
when(amsReturnOrderMapper.countOtherActiveReturnOrderByAssetId(1L, null,
|
||||
ReturnOrderStatus.DRAFT, ReturnOrderStatus.PENDING_CONFIRM)).thenReturn(1);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.insertAmsReturnOrder(order, 1L, "管理员", 103L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("其他有效退库单"));
|
||||
verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
}
|
||||
|
||||
/** 同一退库单中的可追溯资产必须来自同一领用前仓库。 */
|
||||
@Test
|
||||
void insertShouldRejectAssetsFromDifferentOriginWarehouses()
|
||||
{
|
||||
AmsReturnOrder order = buildRequestWithAssets(1L, 2L);
|
||||
stubApplicant();
|
||||
stubInUseAsset(1L);
|
||||
stubInUseAsset(2L);
|
||||
stubTargetWarehouseLocation();
|
||||
when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
|
||||
when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(1L))
|
||||
.thenReturn(buildCandidate(1L, 10L));
|
||||
when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(2L))
|
||||
.thenReturn(buildCandidate(2L, 20L));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.insertAmsReturnOrder(order, 1L, "管理员", 103L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("原领用仓库"));
|
||||
verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
}
|
||||
|
||||
/** 缺少领用来源的资产不能与可追溯资产混在同一退库单。 */
|
||||
@Test
|
||||
void insertShouldRejectMixedTraceableAndMissingOrigin()
|
||||
{
|
||||
AmsReturnOrder order = buildRequestWithAssets(1L, 2L);
|
||||
stubApplicant();
|
||||
stubInUseAsset(1L);
|
||||
stubInUseAsset(2L);
|
||||
stubTargetWarehouseLocation();
|
||||
when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
|
||||
when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(1L))
|
||||
.thenReturn(buildCandidate(1L, 10L));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.insertAmsReturnOrder(order, 1L, "管理员", 103L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("来源类型不一致"));
|
||||
verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
}
|
||||
|
||||
/** 草稿提交前校验资产仍在用且使用归属未变化。 */
|
||||
@Test
|
||||
void submitShouldMoveToPendingConfirm()
|
||||
{
|
||||
AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.DRAFT);
|
||||
when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouseLocation();
|
||||
when(amsReturnOrderMapper.submitAmsReturnOrder(any(AmsReturnOrder.class))).thenReturn(1);
|
||||
|
||||
assertEquals(1, service.submitReturn(100L, "admin"));
|
||||
|
||||
assertEquals(ReturnOrderStatus.PENDING_CONFIRM, order.getOrderStatus());
|
||||
verify(amsReturnOrderMapper).submitAmsReturnOrder(order);
|
||||
}
|
||||
|
||||
/** 确认退库应委托公共流转服务,并记录确认信息。 */
|
||||
@Test
|
||||
void confirmShouldDelegateTransitionAndCompleteOrder()
|
||||
{
|
||||
AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.PENDING_CONFIRM);
|
||||
when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouseLocation();
|
||||
when(amsReturnOrderMapper.confirmAmsReturnOrder(any(AmsReturnOrder.class))).thenReturn(1);
|
||||
|
||||
assertEquals(1, service.confirmReturn(100L, 1L, "管理员", "admin"));
|
||||
|
||||
ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
|
||||
verify(assetStatusTransitionService).confirmReturn(
|
||||
org.mockito.ArgumentMatchers.eq(1L),
|
||||
org.mockito.ArgumentMatchers.eq(2L),
|
||||
org.mockito.ArgumentMatchers.eq(20L),
|
||||
contextCaptor.capture());
|
||||
assertEquals(100L, contextCaptor.getValue().getSourceOrderId());
|
||||
assertEquals(101L, contextCaptor.getValue().getSourceItemId());
|
||||
assertEquals(ReturnOrderStatus.RETURNED, order.getOrderStatus());
|
||||
assertEquals("管理员", order.getConfirmUserName());
|
||||
assertNotNull(order.getConfirmTime());
|
||||
}
|
||||
|
||||
/** 草稿保存后使用人发生变化时,提交必须阻断。 */
|
||||
@Test
|
||||
void submitShouldRejectStaleUseOwnership()
|
||||
{
|
||||
AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.DRAFT);
|
||||
order.getAmsReturnOrderItemList().get(0).setBeforeUserId(99L);
|
||||
when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
|
||||
stubInUseAsset();
|
||||
stubTargetWarehouse();
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.submitReturn(100L, "admin"));
|
||||
|
||||
assertTrue(exception.getMessage().contains("当前使用归属已变化"));
|
||||
verify(amsReturnOrderMapper, never()).submitAmsReturnOrder(any(AmsReturnOrder.class));
|
||||
}
|
||||
|
||||
/** 非草稿退库单不得删除。 */
|
||||
@Test
|
||||
void deleteShouldRejectNonDraftOrder()
|
||||
{
|
||||
AmsReturnOrder order = new AmsReturnOrder();
|
||||
order.setOrderId(100L);
|
||||
order.setOrderStatus(ReturnOrderStatus.PENDING_CONFIRM);
|
||||
when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.deleteAmsReturnOrderByOrderId(100L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("仅草稿"));
|
||||
verify(amsReturnOrderMapper, never()).deleteAmsReturnOrderByOrderId(100L);
|
||||
}
|
||||
|
||||
private AmsReturnOrder buildRequest()
|
||||
{
|
||||
AmsReturnOrder order = new AmsReturnOrder();
|
||||
order.setCreateBy("admin");
|
||||
order.setReceiveWarehouseId(2L);
|
||||
AmsReturnOrderItem item = new AmsReturnOrderItem();
|
||||
item.setAssetId(1L);
|
||||
item.setAfterLocationId(20L);
|
||||
order.setAmsReturnOrderItemList(Collections.singletonList(item));
|
||||
return order;
|
||||
}
|
||||
|
||||
private AmsReturnOrder buildRequestWithAssets(Long... assetIds)
|
||||
{
|
||||
AmsReturnOrder order = new AmsReturnOrder();
|
||||
order.setCreateBy("admin");
|
||||
order.setReceiveWarehouseId(2L);
|
||||
order.setAmsReturnOrderItemList(Arrays.stream(assetIds).map(assetId -> {
|
||||
AmsReturnOrderItem item = new AmsReturnOrderItem();
|
||||
item.setAssetId(assetId);
|
||||
item.setAfterLocationId(20L);
|
||||
return item;
|
||||
}).toList());
|
||||
return order;
|
||||
}
|
||||
|
||||
private AmsReturnOrder buildPersistedOrder(String status)
|
||||
{
|
||||
AmsReturnOrder order = buildRequest();
|
||||
order.setOrderId(100L);
|
||||
order.setReturnNo("TK202606150001");
|
||||
order.setOrderStatus(status);
|
||||
AmsReturnOrderItem item = order.getAmsReturnOrderItemList().get(0);
|
||||
item.setItemId(101L);
|
||||
item.setBeforeDeptId(105L);
|
||||
item.setBeforeUserId(2L);
|
||||
item.setAfterWarehouseId(2L);
|
||||
return order;
|
||||
}
|
||||
|
||||
private void stubApplicant()
|
||||
{
|
||||
SysDept dept = new SysDept();
|
||||
dept.setDeptId(103L);
|
||||
dept.setDeptName("研发部门");
|
||||
dept.setStatus("0");
|
||||
dept.setDelFlag("0");
|
||||
when(sysDeptService.selectDeptById(103L)).thenReturn(dept);
|
||||
|
||||
SysUser user = new SysUser();
|
||||
user.setUserId(1L);
|
||||
user.setUserName("管理员");
|
||||
user.setDeptId(103L);
|
||||
user.setStatus("0");
|
||||
user.setDelFlag("0");
|
||||
when(sysUserService.selectUserById(1L)).thenReturn(user);
|
||||
}
|
||||
|
||||
private void stubInUseAsset()
|
||||
{
|
||||
stubInUseAsset(1L);
|
||||
}
|
||||
|
||||
private void stubInUseAsset(Long assetId)
|
||||
{
|
||||
AmsAsset asset = new AmsAsset();
|
||||
asset.setAssetId(assetId);
|
||||
asset.setAssetCode("ASSET-" + assetId);
|
||||
asset.setAssetName("测试资产");
|
||||
asset.setCategoryId(3L);
|
||||
asset.setCategoryCode("CAT-003");
|
||||
asset.setCategoryName("测试类别");
|
||||
asset.setAssetStatus(AssetStatus.IN_USE);
|
||||
asset.setUseDeptId(105L);
|
||||
asset.setUseDeptName("测试部门");
|
||||
asset.setUseUserId(2L);
|
||||
asset.setUseUserName("测试用户");
|
||||
when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(assetId)).thenReturn(asset);
|
||||
}
|
||||
|
||||
private AmsReturnAssetCandidate buildCandidate(Long warehouseId, Long locationId)
|
||||
{
|
||||
AmsReturnAssetCandidate candidate = new AmsReturnAssetCandidate();
|
||||
candidate.setOriginWarehouseId(warehouseId);
|
||||
candidate.setOriginWarehouseName("原仓库" + warehouseId);
|
||||
candidate.setOriginWarehouseEnabled("Y");
|
||||
candidate.setOriginLocationId(locationId);
|
||||
candidate.setOriginLocationName("原位置" + locationId);
|
||||
candidate.setOriginLocationEnabled("Y");
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private void stubTargetWarehouseLocation()
|
||||
{
|
||||
stubTargetWarehouse();
|
||||
|
||||
AmsAssetLocation location = new AmsAssetLocation();
|
||||
location.setLocationId(20L);
|
||||
location.setLocationCode("LOC-020");
|
||||
location.setLocationName("二号仓A区");
|
||||
location.setWarehouseId(2L);
|
||||
location.setEnabled("Y");
|
||||
when(amsAssetLocationService.selectAmsAssetLocationByLocationId(20L)).thenReturn(location);
|
||||
}
|
||||
|
||||
private void stubTargetWarehouse()
|
||||
{
|
||||
AmsWarehouse warehouse = new AmsWarehouse();
|
||||
warehouse.setWarehouseId(2L);
|
||||
warehouse.setWarehouseCode("WH-002");
|
||||
warehouse.setWarehouseName("二号仓");
|
||||
warehouse.setEnabled("Y");
|
||||
when(amsWarehouseService.selectAmsWarehouseByWarehouseId(2L)).thenReturn(warehouse);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue