feat: 完善盘点出入库流程

This commit is contained in:
zgw
2026-06-25 18:31:17 +08:00
parent 4b8bc03343
commit c7c07c01cf
21 changed files with 1974 additions and 111 deletions

View File

@@ -0,0 +1,28 @@
import request from '@/utils/request'
// 获取盘点待处理任务列表
export function getInventoryTaskList(query) {
return request({
url: '/pda/inventoryTaskList',
method: 'get',
params: query
})
}
// 扫码查询盘点任务详情
export function getInventoryDetail(query) {
return request({
url: '/pda/inventoryDetail',
method: 'get',
params: query
})
}
// 确认盘点
export function confirmInventory(data) {
return request({
url: '/pda/inventoryConfirm',
method: 'post',
data: data
})
}

View File

@@ -1,61 +1,237 @@
<template>
<view class="page-container">
<view class="menu-section">
<view class="menu-card" @click="scanInventory">
<view class="card-left">
<view class="card-icon green">
<uni-icons type="scan" size="24" color="#fff"></uni-icons>
</view>
<view class="card-info">
<text class="card-title">扫码盘点</text>
<text class="card-desc">扫描托盘码进行库存盘点</text>
</view>
<view class="scan-section">
<view class="scan-header">
<view>
<text class="scan-title">盘点扫码</text>
<text class="scan-subtitle">WCS 出库到位后扫描托盘核对物料</text>
</view>
<view class="scan-btn" @click="startScan">
<uni-icons type="scan" size="18" color="#1f8f4d"></uni-icons>
<text class="scan-btn-text">扫码</text>
</view>
<uni-icons type="right" size="18" color="#ccc"></uni-icons>
</view>
<view class="menu-card reserve-card" @click="showReservedTip">
<view class="card-left">
<view class="card-icon gray">
<uni-icons type="compose" size="24" color="#fff"></uni-icons>
<view class="input-row">
<text class="input-label">任务号</text>
<input class="scan-input" v-model="taskNo" placeholder="选填" @confirm="handleSearch" />
</view>
<view class="input-row">
<text class="input-label">托盘码</text>
<input class="scan-input" v-model="barcode" placeholder="请输入或扫描托盘码" @confirm="handleSearch" />
</view>
<button class="query-btn" @click="handleSearch">查询盘点任务</button>
</view>
<view class="task-section">
<view class="section-header">
<text class="section-title">待盘点任务</text>
<text class="section-action" @click="loadTasks">刷新</text>
</view>
<view v-if="taskList.length === 0" class="empty-state">
<text class="empty-text">暂无待盘点任务</text>
</view>
<view v-else>
<view class="task-card" v-for="item in taskList" :key="item.jobId" @click="selectTask(item)">
<view class="task-header">
<text class="task-type">{{ item.typeName }}</text>
<text class="task-no">{{ item.taskNo }}</text>
</view>
<view class="card-info">
<text class="card-title">预留界面</text>
<text class="card-desc">后续盘点流程启用时接入</text>
<view class="task-body">
<view class="task-line">
<text class="line-label">托盘</text>
<text class="line-value">{{ item.trayCode || '-' }}</text>
</view>
<view class="task-line">
<text class="line-label">物料</text>
<text class="line-value">{{ item.materialCode || '-' }}</text>
</view>
<view class="task-line">
<text class="line-label">数量</text>
<text class="line-value highlight">{{ item.quantity || 0 }}</text>
</view>
</view>
<view class="task-footer">
<text class="task-time">{{ item.createTime }}</text>
<text class="task-status" :class="item.statusClass">{{ item.statusName }}</text>
</view>
</view>
<uni-icons type="right" size="18" color="#ddd"></uni-icons>
</view>
</view>
<view v-if="scanCode" class="result-card">
<view class="result-header">
<text class="result-title">最近扫描</text>
<text class="result-code">{{ scanCode }}</text>
<view class="form-section" v-if="inventoryInfo">
<text class="form-title">盘点信息</text>
<view class="info-row">
<text class="info-label">任务号</text>
<text class="info-value">{{ inventoryInfo.taskNo }}</text>
</view>
<text class="result-desc">扫码盘点明细待业务流程确认后接入。</text>
<view class="info-row">
<text class="info-label">托盘号</text>
<text class="info-value">{{ inventoryInfo.trayCode }}</text>
</view>
<view class="info-row">
<text class="info-label">原货位</text>
<text class="info-value">{{ inventoryInfo.locationCode || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">盘点总数</text>
<text class="info-value highlight">{{ calculateActualTotal() }}</text>
</view>
<view class="phase-tip" :class="{ ready: inventoryInfo.executionPhase === '5' }">
{{ inventoryInfo.executionPhase === '5' ? '已到达出口,可确认盘点' : '等待 WCS 出库完成后再确认' }}
</view>
</view>
<view class="form-section" v-if="inventoryInfo">
<text class="form-title">物料核对</text>
<view class="material-card" v-for="(item, index) in inventoryInfo.materials" :key="item.jobDetailId || index">
<view class="material-header">
<text class="material-code">{{ item.materialCode }}</text>
<text class="diff-badge" :class="{ changed: isChanged(item) }">
{{ isChanged(item) ? '有差异' : '一致' }}
</text>
</view>
<text class="material-name">{{ item.materialName || '-' }}</text>
<view class="material-meta">
<text>批次:{{ item.lotNo || '-' }}</text>
<text>账面:{{ item.quantity || 0 }}</text>
<text>库存:{{ item.stockQuantity || 0 }}</text>
</view>
<view class="material-confirm">
<text class="material-confirm-label">实盘数量</text>
<input class="material-input" v-model="item.actualQuantity" type="number" :placeholder="'账面 ' + item.quantity" />
</view>
</view>
</view>
<view class="btn-section" v-if="inventoryInfo">
<button class="btn btn-primary" @click="submitInventory">提交盘点</button>
<button class="btn btn-default" @click="resetForm">重置</button>
</view>
</view>
</template>
<script>
import { getInventoryTaskList, getInventoryDetail, confirmInventory } from '@/api/warehouse/inventory'
export default {
data() {
return {
scanCode: ''
taskNo: '',
barcode: '',
taskList: [],
inventoryInfo: null
}
},
onLoad(options) {
if (options.taskNo) {
this.taskNo = options.taskNo
}
this.loadTasks()
},
onShow() {
this.loadTasks()
},
methods: {
scanInventory() {
loadTasks() {
var that = this
getInventoryTaskList().then(function(res) {
that.taskList = res.data || []
}).catch(function() {
that.taskList = []
})
},
startScan() {
var that = this
uni.scanCode({
success: res => {
this.scanCode = res.result || ''
this.$modal.showToast('扫码成功')
onlyFromCamera: false,
scanType: ['barCode', 'qrCode'],
success: function(res) {
that.barcode = res.result || ''
that.handleSearch()
}
})
},
showReservedTip() {
this.$modal.showToast('功能预留')
selectTask(item) {
this.taskNo = item.taskNo || ''
this.barcode = item.trayCode || ''
this.handleSearch()
},
handleSearch() {
if (!this.taskNo && !this.barcode) {
this.$modal.msg('请输入任务号或托盘码')
return
}
var that = this
getInventoryDetail({
taskNo: this.taskNo,
barcode: this.barcode
}).then(function(res) {
var data = res.data || {}
var materials = (data.materials || []).map(function(item) {
return Object.assign({}, item, {
actualQuantity: String(item.quantity != null ? item.quantity : 0)
})
})
that.inventoryInfo = Object.assign({}, data, { materials: materials })
that.taskNo = data.taskNo || that.taskNo
that.barcode = data.trayCode || that.barcode
that.$modal.msgSuccess('盘点任务获取成功')
}).catch(function() {
that.inventoryInfo = null
})
},
submitInventory() {
if (!this.inventoryInfo || !this.inventoryInfo.materials || this.inventoryInfo.materials.length === 0) {
this.$modal.msg('未获取到盘点物料明细')
return
}
if (this.inventoryInfo.executionPhase !== '5') {
this.$modal.msg('WCS 出库完成后才能确认盘点')
return
}
var invalid = this.inventoryInfo.materials.find(function(item) {
return item.actualQuantity === '' || item.actualQuantity === null || Number(item.actualQuantity) < 0
})
if (invalid) {
this.$modal.msg('请输入每个物料的实盘数量')
return
}
var data = {
taskNo: this.inventoryInfo.taskNo || this.taskNo,
barcode: this.inventoryInfo.trayCode || this.barcode,
trayCode: this.inventoryInfo.trayCode || this.barcode,
materials: this.inventoryInfo.materials.map(function(item) {
return {
jobDetailId: item.jobDetailId,
stockDetailId: item.stockDetailId,
stockpdDetailId: item.stockpdDetailId,
quantity: item.actualQuantity
}
})
}
var that = this
confirmInventory(data).then(function() {
that.$modal.msgSuccess('盘点确认成功,已生成回库作业')
that.resetForm()
that.loadTasks()
})
},
resetForm() {
this.taskNo = ''
this.barcode = ''
this.inventoryInfo = null
},
calculateActualTotal() {
if (!this.inventoryInfo || !this.inventoryInfo.materials) {
return '0'
}
return String(this.inventoryInfo.materials.reduce(function(sum, item) {
return sum + Number(item.actualQuantity || 0)
}, 0))
},
isChanged(item) {
return Number(item.actualQuantity || 0) !== Number(item.quantity || 0)
}
}
}
@@ -63,101 +239,338 @@
<style lang="scss" scoped>
page {
background-color: #f5f7fa;
background-color: #f4f6f8;
min-height: 100%;
}
.page-container {
padding: 24rpx;
padding-bottom: 60rpx;
}
.menu-section {
margin-bottom: 32rpx;
}
.menu-card {
.scan-section,
.task-card,
.form-section {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(24, 38, 55, 0.05);
}
.scan-section {
padding: 28rpx;
margin-bottom: 28rpx;
}
.scan-header {
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
align-items: flex-start;
margin-bottom: 22rpx;
}
.reserve-card {
opacity: 0.88;
.scan-title {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #243126;
}
.card-left {
.scan-subtitle {
display: block;
margin-top: 6rpx;
font-size: 23rpx;
color: #7c8a80;
}
.scan-btn {
display: flex;
align-items: center;
flex: 1;
padding: 8rpx 20rpx;
border: 1rpx solid #1f8f4d;
border-radius: 8rpx;
}
.card-icon {
width: 72rpx;
height: 72rpx;
.scan-btn-text {
font-size: 24rpx;
color: #1f8f4d;
margin-left: 8rpx;
}
.input-row {
display: flex;
align-items: center;
border: 1rpx solid #e5e9ec;
border-radius: 10rpx;
margin-bottom: 16rpx;
padding: 0 18rpx;
}
.input-label {
width: 120rpx;
flex-shrink: 0;
font-size: 26rpx;
color: #657069;
}
.scan-input {
flex: 1;
min-width: 0;
height: 76rpx;
font-size: 28rpx;
color: #202724;
}
.query-btn {
margin-top: 6rpx;
height: 78rpx;
line-height: 78rpx;
border-radius: 12rpx;
background: #1f8f4d;
color: #fff;
font-size: 29rpx;
}
.task-section {
margin-bottom: 28rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #25302a;
}
.section-action {
font-size: 26rpx;
color: #1f8f4d;
}
.empty-state {
background: #fff;
border-radius: 16rpx;
padding: 72rpx 0;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
}
.card-icon.green {
background: linear-gradient(135deg, #52c41a, #73d13d);
.empty-text {
font-size: 27rpx;
color: #a9b2ad;
}
.card-icon.gray {
background: linear-gradient(135deg, #8c8c8c, #bfbfbf);
.task-card {
padding: 24rpx;
margin-bottom: 18rpx;
}
.card-info {
display: flex;
flex-direction: column;
}
.card-title {
font-size: 30rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
}
.card-desc {
font-size: 24rpx;
color: #999;
}
.result-card {
background: #fff;
border-radius: 16rpx;
padding: 28rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.result-header {
.task-header,
.task-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14rpx;
}
.result-title {
.task-header {
margin-bottom: 16rpx;
}
.task-type {
font-size: 28rpx;
font-weight: 500;
color: #333;
font-weight: 600;
color: #1f8f4d;
}
.result-code {
.task-no,
.task-time {
font-size: 23rpx;
color: #8b9690;
}
.task-line {
display: flex;
justify-content: space-between;
padding: 8rpx 0;
}
.line-label,
.info-label {
font-size: 26rpx;
color: #52c41a;
color: #8b9690;
}
.result-desc {
.line-value,
.info-value {
font-size: 26rpx;
color: #26302b;
max-width: 460rpx;
text-align: right;
word-break: break-all;
}
.highlight {
color: #1f8f4d;
font-weight: 600;
}
.task-footer {
margin-top: 14rpx;
padding-top: 14rpx;
border-top: 1rpx solid #eef1ef;
}
.task-status {
font-size: 22rpx;
padding: 5rpx 16rpx;
border-radius: 8rpx;
}
.task-status.pending {
color: #9b6b00;
background: #fff6dc;
}
.task-status.processing {
color: #1c6a9e;
background: #e8f4ff;
}
.form-section {
padding: 28rpx;
margin-bottom: 24rpx;
}
.form-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #25302a;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #eef1ef;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 14rpx 0;
}
.phase-tip {
margin-top: 16rpx;
padding: 14rpx 18rpx;
border-radius: 10rpx;
background: #fff6dc;
color: #9b6b00;
font-size: 24rpx;
color: #999;
}
.phase-tip.ready {
background: #e8f6ee;
color: #1f8f4d;
}
.material-card {
padding: 20rpx 0;
border-bottom: 1rpx solid #eef1ef;
}
.material-card:last-child {
border-bottom: 0;
}
.material-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.material-code {
font-size: 28rpx;
font-weight: 600;
color: #25302a;
}
.diff-badge {
font-size: 22rpx;
color: #1f8f4d;
background: #e8f6ee;
border-radius: 8rpx;
padding: 4rpx 14rpx;
}
.diff-badge.changed {
color: #b54416;
background: #fff0e8;
}
.material-name {
display: block;
font-size: 26rpx;
color: #58625d;
margin-bottom: 10rpx;
word-break: break-all;
}
.material-meta {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
font-size: 24rpx;
color: #8b9690;
}
.material-confirm {
display: flex;
align-items: center;
margin-top: 16rpx;
}
.material-confirm-label {
width: 150rpx;
flex-shrink: 0;
font-size: 26rpx;
color: #657069;
}
.material-input {
flex: 1;
min-width: 0;
height: 70rpx;
box-sizing: border-box;
border: 1rpx solid #dfe5e1;
border-radius: 10rpx;
padding: 0 18rpx;
font-size: 27rpx;
color: #202724;
}
.btn-section {
padding: 8rpx 0 24rpx;
}
.btn {
font-size: 30rpx;
border-radius: 12rpx;
margin-bottom: 18rpx;
}
.btn-primary {
background: #1f8f4d;
color: #fff;
}
.btn-default {
background: #fff;
color: #657069;
border: 1rpx solid #dfe5e1;
}
</style>

View File

@@ -20,6 +20,7 @@ import com.ruoyi.in.jobdetail.mapper.MydoJobDetailMapper;
import com.ruoyi.in.stockin.mapper.InStockinMapper;
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.in.job.mapper.MydoJobMapper;
@@ -67,6 +68,9 @@ public class MydoJobServiceImpl implements IMydoJobService
@Autowired
private WcsClient wcsClient;
@Autowired
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
/**
* 查询作业信息
*
@@ -377,6 +381,9 @@ public class MydoJobServiceImpl implements IMydoJobService
if (finishResult.isError()) {
return finishResult;
}
if (isInventoryInboundJob(job)) {
pdDifferenceRecordMapper.markInboundDifferencesPendingReviewByJobId(jobId);
}
return AjaxResult.success();
}
return AjaxResult.error(Convert.toStr(map.get("v_resultmsg")));
@@ -421,6 +428,11 @@ public class MydoJobServiceImpl implements IMydoJobService
return job != null && job.getInOrOut() != null && job.getInOrOut() >= 20;
}
private boolean isInventoryInboundJob(MydoJob job)
{
return job != null && (Long.valueOf(10L).equals(job.getInOrOut()) || Long.valueOf(11L).equals(job.getInOrOut()));
}
/**
* WCS 回调取货无货异常。
*/

View File

@@ -43,6 +43,30 @@ public interface PdDifferenceRecordMapper
*/
public int updatePdDifferenceRecord(PdDifferenceRecord pdDifferenceRecord);
/**
* 审核通过后应用盘点差异到库存明细。
*
* @param pdDifferenceRecord 盘点差异
* @return 结果
*/
public int applyApprovedDifference(PdDifferenceRecord pdDifferenceRecord);
/**
* 审核通过后刷新库存主表汇总数量。
*
* @param pdDifferenceRecord 盘点差异
* @return 结果
*/
public int refreshApprovedDifferenceStockSummary(PdDifferenceRecord pdDifferenceRecord);
/**
* 盘点入库完成后,将对应差异从未入库改为待审核。
*
* @param jobId 盘点入库作业ID
* @return 结果
*/
public int markInboundDifferencesPendingReviewByJobId(String jobId);
/**
* 删除盘点差异
*

View File

@@ -4,6 +4,7 @@ import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
import com.ruoyi.pd.diff.domain.PdDifferenceRecord;
import com.ruoyi.pd.diff.service.IPdDifferenceRecordService;
@@ -97,10 +98,19 @@ public class PdDifferenceRecordServiceImpl implements IPdDifferenceRecordService
* 通过差异记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int approvePdDifferenceRecord(Long id)
{
PdDifferenceRecord record = pdDifferenceRecordMapper.selectPdDifferenceRecordById(id);
record.setStatus("1");
if (record == null || !"1".equals(record.getStatus())) {
return 0;
}
int applyRows = pdDifferenceRecordMapper.applyApprovedDifference(record);
if (applyRows <= 0) {
throw new RuntimeException("盘点差异未匹配到可更新库存");
}
pdDifferenceRecordMapper.refreshApprovedDifferenceStockSummary(record);
record.setStatus("2");
return pdDifferenceRecordMapper.updatePdDifferenceRecord(record);
}
@@ -111,7 +121,10 @@ public class PdDifferenceRecordServiceImpl implements IPdDifferenceRecordService
public int rejectPdDifferenceRecord(Long id)
{
PdDifferenceRecord record = pdDifferenceRecordMapper.selectPdDifferenceRecordById(id);
record.setStatus("2");
if (record == null || !"1".equals(record.getStatus())) {
return 0;
}
record.setStatus("3");
return pdDifferenceRecordMapper.updatePdDifferenceRecord(record);
}
}

View File

@@ -3,14 +3,26 @@ package com.ruoyi.pd.stockpd.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Date;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.bas.id.job.service.JobIdService;
import com.ruoyi.bas.id.taskno.service.TaskNoService;
import com.ruoyi.bas.stock.domain.BasStockDetail;
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
import com.ruoyi.common.PdStockpdStatusEnum;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.in.job.domain.MydoJob;
import com.ruoyi.in.job.service.IMydoJobService;
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.pd.stockpd.mapper.PdStockpdMapper;
import com.ruoyi.pd.stockpd.domain.PdStockpd;
@@ -18,6 +30,7 @@ import com.ruoyi.pd.stockpd.domain.PdStockpdSaveDTO;
import com.ruoyi.pd.stockpd.service.IPdStockpdService;
import com.ruoyi.pd.stockpddetail.mapper.PdStockpdDetailMapper;
import com.ruoyi.pd.stockpddetail.domain.PdStockpdDetail;
import com.ruoyi.pda.mapper.PdaMapper;
/**
* 盘点单据主信息Service业务层处理
@@ -28,6 +41,10 @@ import com.ruoyi.pd.stockpddetail.domain.PdStockpdDetail;
@Service
public class PdStockpdServiceImpl implements IPdStockpdService
{
private static final long MATERIAL_INVENTORY_OUT_TYPE = 30L;
private static final long LOCATION_INVENTORY_OUT_TYPE = 31L;
private static final String INVENTORY_OUT_TASK_NO_TYPE = "22";
@Autowired
private PdStockpdMapper pdStockpdMapper;
@@ -40,6 +57,24 @@ public class PdStockpdServiceImpl implements IPdStockpdService
@Autowired
private JobIdService jobIdService;
@Autowired
private TaskNoService taskNoService;
@Autowired
private BasStockDetailMapper basStockDetailMapper;
@Autowired
private IBasStationPointService basStationPointService;
@Autowired
private IMydoJobService mydoJobService;
@Autowired
private IMydoJobDetailService mydoJobDetailService;
@Autowired
private PdaMapper pdaMapper;
/**
* 查询盘点单据主信息
*
@@ -179,22 +214,73 @@ public class PdStockpdServiceImpl implements IPdStockpdService
* 自动分配盘点单据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AjaxResult autoAssignPdStockpd(String stockpdId)
{
StringBuilder str = new StringBuilder();
PdStockpd stockpd = pdStockpdMapper.selectPdStockpdByStockpdId(stockpdId);
if (stockpd == null) {
return AjaxResult.error("盘点单据不存在");
}
if (!PdStockpdStatusEnum.AUDIT.getCode().equals(stockpd.getStatus())
&& !PdStockpdStatusEnum.RUNNING.getCode().equals(stockpd.getStatus())) {
return AjaxResult.error("只有已审核或执行中的盘点单据允许生成盘点出库作业");
}
PdStockpdDetail query = new PdStockpdDetail();
query.setStockpdId(stockpdId);
List<PdStockpdDetail> details = pdStockpdDetailMapper.selectPdStockpdDetailList(query);
for (PdStockpdDetail detail : details) {
HashMap<String, Object> map = new HashMap<>();
map.put("v_stockpd_detail_id", detail.getStockpdDetailId());
pdStockpdMapper.autoAssignPdStockpdByProc(map);
if (Convert.toStr(map.get("v_result")).equals("10")){
str.append(detail.getStockpdDetailId()).append(Convert.toStr(map.get("v_result_msg"))).append("|");
}else {
str.append(detail.getStockpdDetailId()).append(Convert.toStr(map.get("v_result_msg"))).append("|");
}
if (details == null || details.isEmpty()) {
return AjaxResult.error("盘点单据没有明细");
}
BasStationPoint exitPoint = selectDefaultOutboundStationPoint(stockpd.getWarehouseId());
StringBuilder str = new StringBuilder("盘点出库作业生成成功:");
for (PdStockpdDetail detail : details) {
BasStockDetail stockDetail = resolveInventoryStockDetail(stockpd, detail);
BigDecimal quantity = resolveInventoryQuantity(detail, stockDetail);
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("盘点明细数量必须大于0:" + detail.getStockpdDetailId());
}
MydoJob job = buildInventoryOutboundJob(stockpd, stockDetail, exitPoint);
if (mydoJobService.insertMydoJob(job) <= 0) {
throw new RuntimeException("盘点出库作业生成失败:" + detail.getStockpdDetailId());
}
MydoJobDetail jobDetail = buildInventoryOutboundDetail(job, stockpd, detail, stockDetail, quantity);
if (mydoJobDetailService.insertMydoJobDetail(jobDetail) <= 0) {
throw new RuntimeException("盘点出库作业明细生成失败:" + detail.getStockpdDetailId());
}
HashMap<String, Object> reserveMap = new HashMap<>();
reserveMap.put("stockDetailId", stockDetail.getDetailId());
reserveMap.put("stockId", stockDetail.getStockId());
reserveMap.put("jobDetailId", jobDetail.getJobDetailId());
reserveMap.put("quantity", quantity);
if (pdaMapper.reserveInventoryStockDetail(reserveMap) <= 0) {
throw new RuntimeException("盘点库存已被占用或可用库存不足:" + detail.getStockpdDetailId());
}
HashMap<String, Object> cargoMap = new HashMap<>();
cargoMap.put("stockId", stockDetail.getStockId());
cargoMap.put("cargoSpaceId", stockDetail.getCargoSpaceId());
if (pdaMapper.lockInventoryOutboundCargoSpace(cargoMap) <= 0) {
throw new RuntimeException("盘点出库货位锁定失败:" + detail.getStockpdDetailId());
}
AjaxResult pushResult = mydoJobService.pushOutJobToWcs(job.getJobId());
if (pushResult == null || pushResult.isError()) {
throw new RuntimeException(pushResult == null ? "WCS盘点出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
}
updateAssignedInventoryDetail(detail, stockDetail, quantity);
str.append(job.getJobId()).append("|");
}
PdStockpd update = new PdStockpd();
update.setStockpdId(stockpdId);
update.setStatus(PdStockpdStatusEnum.RUNNING.getCode());
update.setStartDate(new Date());
pdStockpdMapper.updatePdStockpd(update);
return AjaxResult.success(str);
}
@@ -237,10 +323,143 @@ public class PdStockpdServiceImpl implements IPdStockpdService
detail.setAllotNum(0L);
detail.setAssignNum(0L);
detail.setStockpdId(saveDTO.getStockpdId());
if (!StringUtils.hasText(detail.getWarehouseId())) {
detail.setWarehouseId(saveDTO.getWarehouseId());
}
if (!StringUtils.hasText(detail.getWarehouseName())) {
detail.setWarehouseName(saveDTO.getWarehouseName());
}
pdStockpdDetailMapper.insertPdStockpdDetail(detail);
}
}
return saveDTO.getStockpdId();
}
private BasStockDetail resolveInventoryStockDetail(PdStockpd stockpd, PdStockpdDetail detail) {
BasStockDetail stockDetail = null;
if (StringUtils.hasText(detail.getStockDetailId())) {
stockDetail = basStockDetailMapper.selectBasStockDetailByDetailId(detail.getStockDetailId());
}
if (stockDetail == null) {
BasStockDetail query = new BasStockDetail();
query.setStockId(detail.getStockId());
query.setTrayCode(detail.getTrayCode());
query.setWarehouseId(StringUtils.hasText(detail.getWarehouseId()) ? detail.getWarehouseId() : stockpd.getWarehouseId());
query.setCargoSpaceId(detail.getCargoSpaceId());
query.setProductId(detail.getProductId());
List<BasStockDetail> stockDetails = basStockDetailMapper.selectBasStockDetailList(query);
if (stockDetails != null && !stockDetails.isEmpty()) {
stockDetail = stockDetails.get(0);
}
}
if (stockDetail == null) {
throw new RuntimeException("未找到盘点库存明细:" + detail.getStockpdDetailId());
}
return stockDetail;
}
private BigDecimal resolveInventoryQuantity(PdStockpdDetail detail, BasStockDetail stockDetail) {
BigDecimal quantity = detail.getQuantity() == null ? null : BigDecimal.valueOf(detail.getQuantity());
if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
quantity = stockDetail.getAvailableQuantity();
}
if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
quantity = stockDetail.getQuantity();
}
BigDecimal availableQuantity = stockDetail.getAvailableQuantity();
if (availableQuantity != null && availableQuantity.compareTo(BigDecimal.ZERO) > 0
&& quantity.compareTo(availableQuantity) > 0) {
throw new RuntimeException("盘点数量不能大于可用库存:" + detail.getStockpdDetailId());
}
return quantity == null ? BigDecimal.ZERO : quantity;
}
private BasStationPoint selectDefaultOutboundStationPoint(String warehouseId) {
BasStationPoint query = new BasStationPoint();
query.setWarehouseId(warehouseId);
query.setStatus("0");
query.setPointStatus("0");
List<BasStationPoint> pointList = basStationPointService.selectBasStationPointList(query);
if (pointList != null) {
for (BasStationPoint point : pointList) {
if (point != null && ("2".equals(point.getPointType()) || "3".equals(point.getPointType()))) {
return point;
}
}
}
throw new RuntimeException("当前仓库未配置可用出库点位");
}
private MydoJob buildInventoryOutboundJob(PdStockpd stockpd, BasStockDetail stockDetail, BasStationPoint exitPoint) {
String jobId = jobIdService.getId("PC");
String taskNo = taskNoService.getTaskNo(INVENTORY_OUT_TASK_NO_TYPE);
MydoJob job = new MydoJob();
job.setJobId(jobId);
job.setTaskNo(StringUtils.hasText(taskNo) ? taskNo : jobId);
job.setInOrOut(resolveInventoryOutType(stockpd));
job.setStatus("1");
job.setExecutionPhase("0");
job.setTrayCode(stockDetail.getTrayCode());
job.setWarehouseId(StringUtils.hasText(stockDetail.getWarehouseId()) ? stockDetail.getWarehouseId() : stockpd.getWarehouseId());
job.setWarehouseName(StringUtils.hasText(stockDetail.getWarehouseName()) ? stockDetail.getWarehouseName() : stockpd.getWarehouseName());
job.setCargoSpaceId(stockDetail.getCargoSpaceId());
job.setCargoSpaceName(stockDetail.getCargoSpaceName());
job.setExitId(exitPoint.getPointId());
job.setPriority(10L);
job.setHeight(BigDecimal.ZERO);
job.setWeight(BigDecimal.ZERO);
copyLoginUser(job);
return job;
}
private Long resolveInventoryOutType(PdStockpd stockpd) {
return "2".equals(stockpd.getPdType()) ? LOCATION_INVENTORY_OUT_TYPE : MATERIAL_INVENTORY_OUT_TYPE;
}
private MydoJobDetail buildInventoryOutboundDetail(MydoJob job, PdStockpd stockpd, PdStockpdDetail detail,
BasStockDetail stockDetail, BigDecimal quantity) {
MydoJobDetail jobDetail = new MydoJobDetail();
jobDetail.setJobDetailId(job.getJobId() + "-1");
jobDetail.setJobId(job.getJobId());
jobDetail.setStockId(stockDetail.getStockId());
jobDetail.setLotNo(stockDetail.getLotNo());
jobDetail.setInDate(DateUtils.getTime());
jobDetail.setInOrOutId(stockpd.getStockpdId());
jobDetail.setInOrOutDId(detail.getStockpdDetailId());
jobDetail.setProductId(stockDetail.getProductId());
jobDetail.setProductName(stockDetail.getProductName());
jobDetail.setStockQuantity(stockDetail.getQuantity());
jobDetail.setOutQuantity(quantity);
jobDetail.setStockinDetailId(stockDetail.getDetailId());
return jobDetail;
}
private void updateAssignedInventoryDetail(PdStockpdDetail detail, BasStockDetail stockDetail, BigDecimal quantity) {
PdStockpdDetail update = new PdStockpdDetail();
update.setStockpdDetailId(detail.getStockpdDetailId());
update.setStockId(stockDetail.getStockId());
update.setStockDetailId(stockDetail.getDetailId());
update.setTrayCode(stockDetail.getTrayCode());
update.setCargoSpaceId(stockDetail.getCargoSpaceId());
update.setCargoSpaceName(stockDetail.getCargoSpaceName());
update.setWarehouseId(stockDetail.getWarehouseId());
update.setWarehouseName(stockDetail.getWarehouseName());
update.setProductId(stockDetail.getProductId());
update.setProductName(stockDetail.getProductName());
update.setQuantity(quantity.longValue());
update.setAllotNum(quantity.longValue());
update.setAssignNum(0L);
pdStockpdDetailMapper.updatePdStockpdDetail(update);
}
private void copyLoginUser(MydoJob job) {
try {
job.setUserId(SecurityUtils.getUserId());
job.setDeptId(SecurityUtils.getDeptId());
job.setUserName(SecurityUtils.getUsername());
} catch (RuntimeException ignored) {
// Unit tests and external callbacks may run without a Spring Security context.
}
}
}

View File

@@ -26,6 +26,30 @@ public class PdStockpdDetail extends BaseEntity
@Excel(name = "托盘码")
private String trayCode;
/** 库存ID */
@Excel(name = "库存ID")
private String stockId;
/** 库存明细ID */
@Excel(name = "库存明细ID")
private String stockDetailId;
/** 货位ID */
@Excel(name = "货位ID")
private String cargoSpaceId;
/** 货位名称 */
@Excel(name = "货位名称")
private String cargoSpaceName;
/** 仓库ID */
@Excel(name = "仓库ID")
private String warehouseId;
/** 仓库名称 */
@Excel(name = "仓库名称")
private String warehouseName;
/** 物料编码 */
@Excel(name = "物料编码")
private String productId;
@@ -86,6 +110,66 @@ public class PdStockpdDetail extends BaseEntity
return trayCode;
}
public void setStockId(String stockId)
{
this.stockId = stockId;
}
public String getStockId()
{
return stockId;
}
public void setStockDetailId(String stockDetailId)
{
this.stockDetailId = stockDetailId;
}
public String getStockDetailId()
{
return stockDetailId;
}
public void setCargoSpaceId(String cargoSpaceId)
{
this.cargoSpaceId = cargoSpaceId;
}
public String getCargoSpaceId()
{
return cargoSpaceId;
}
public void setCargoSpaceName(String cargoSpaceName)
{
this.cargoSpaceName = cargoSpaceName;
}
public String getCargoSpaceName()
{
return cargoSpaceName;
}
public void setWarehouseId(String warehouseId)
{
this.warehouseId = warehouseId;
}
public String getWarehouseId()
{
return warehouseId;
}
public void setWarehouseName(String warehouseName)
{
this.warehouseName = warehouseName;
}
public String getWarehouseName()
{
return warehouseName;
}
public void setProductId(String productId)
{
this.productId = productId;
@@ -142,6 +226,12 @@ public class PdStockpdDetail extends BaseEntity
.append("stockpdDetailId", getStockpdDetailId())
.append("stockpdId", getStockpdId())
.append("trayCode", getTrayCode())
.append("stockId", getStockId())
.append("stockDetailId", getStockDetailId())
.append("cargoSpaceId", getCargoSpaceId())
.append("cargoSpaceName", getCargoSpaceName())
.append("warehouseId", getWarehouseId())
.append("warehouseName", getWarehouseName())
.append("productId", getProductId())
.append("productName", getProductName())
.append("quantity", getQuantity())

View File

@@ -192,6 +192,15 @@ public class PdaController extends BaseController {
return AjaxResult.success(pdaService.outboundTaskList(query));
}
/**
* PDA 盘点待确认任务列表。
*/
@GetMapping("/inventoryTaskList")
@PreAuthorize("@ss.hasPermi('pd:stockpd:list')")
public AjaxResult inventoryTaskList(@RequestParam Map<String, Object> query) {
return AjaxResult.success(pdaService.inventoryTaskList(query));
}
/**
* PDA 查询当前仓库库内空托按条码头分组的数量。
*/
@@ -246,6 +255,19 @@ public class PdaController extends BaseController {
}
}
/**
* PDA 扫码查询盘点任务明细。
*/
@GetMapping("/inventoryDetail")
@PreAuthorize("@ss.hasPermi('pd:stockpd:list')")
public AjaxResult inventoryDetail(@RequestParam Map<String, Object> query) {
try {
return AjaxResult.success(pdaService.inventoryDetail(query));
} catch (RuntimeException e) {
return AjaxResult.error(e.getMessage());
}
}
/**
* PDA 确认出库。
*/
@@ -261,4 +283,19 @@ public class PdaController extends BaseController {
}
}
/**
* PDA 确认盘点。
*/
@PostMapping("/inventoryConfirm")
@PreAuthorize("@ss.hasPermi('pd:stockpd:edit')")
@Log(title = "PDA盘点确认", businessType = BusinessType.UPDATE)
public AjaxResult inventoryConfirm(@RequestBody Map<String, Object> data) {
try {
pdaService.confirmInventory(data);
return AjaxResult.success();
} catch (RuntimeException e) {
return AjaxResult.error(e.getMessage());
}
}
}

View File

@@ -55,16 +55,36 @@ public interface PdaMapper {
*/
List<HashMap<String, Object>> outboundTaskList(HashMap<String, Object> map);
/**
* PDA 盘点待确认任务列表。
*/
List<HashMap<String, Object>> inventoryTaskList(HashMap<String, Object> map);
/**
* PDA 扫码查询出库任务明细。
*/
HashMap<String, Object> selectOutboundDetail(HashMap<String, Object> map);
/**
* PDA 扫码查询盘点出库任务明细。
*/
HashMap<String, Object> selectInventoryDetail(HashMap<String, Object> map);
/**
* 查询出库托盘上的作业物料明细。
*/
List<HashMap<String, Object>> selectOutboundTrayMaterials(HashMap<String, Object> map);
/**
* 查询盘点托盘上的作业物料明细。
*/
List<HashMap<String, Object>> selectInventoryTrayMaterials(HashMap<String, Object> map);
/**
* 查询盘点出库对应的盘点入库作业,防止 PDA 重复确认重复生成回库作业。
*/
HashMap<String, Object> selectInventoryInboundJobByOutboundJobId(@Param("outboundJobId") String outboundJobId);
/**
* 查询出库确认后托盘上仍有库存的物料。
*/
@@ -95,8 +115,18 @@ public interface PdaMapper {
*/
int reserveEmptyTrayStockDetail(HashMap<String, Object> map);
/**
* 预占盘点库存明细,避免重复生成盘点出库作业。
*/
int reserveInventoryStockDetail(HashMap<String, Object> map);
/**
* 将空托所在货位标记为出库锁定。
*/
int lockEmptyTrayCargoSpace(HashMap<String, Object> map);
/**
* 将盘点托盘所在货位标记为出库锁定。
*/
int lockInventoryOutboundCargoSpace(HashMap<String, Object> map);
}

View File

@@ -131,6 +131,14 @@ public interface PdaService {
*/
List<Map<String, Object>> outboundTaskList(Map<String, Object> query);
/**
* PDA 盘点待确认任务列表。
*
* @param query 查询条件
* @return 盘点任务列表
*/
List<Map<String, Object>> inventoryTaskList(Map<String, Object> query);
/**
* PDA 扫码查询出库任务明细。
*
@@ -139,6 +147,14 @@ public interface PdaService {
*/
Map<String, Object> outboundDetail(Map<String, Object> query);
/**
* PDA 扫码查询盘点任务明细。
*
* @param query 查询条件,包含任务号和托盘码
* @return 盘点任务明细
*/
Map<String, Object> inventoryDetail(Map<String, Object> query);
/**
* PDA 确认出库。
*
@@ -146,4 +162,11 @@ public interface PdaService {
*/
void confirmOutbound(Map<String, Object> data);
/**
* PDA 确认盘点。
*
* @param data 确认数据
*/
void confirmInventory(Map<String, Object> data);
}

View File

@@ -20,6 +20,12 @@ import com.ruoyi.in.stockin.domain.InStockin;
import com.ruoyi.in.stockin.mapper.InStockinMapper;
import com.ruoyi.in.sotckindetail.domain.InStockinDetail;
import com.ruoyi.in.sotckindetail.mapper.InStockinDetailMapper;
import com.ruoyi.pd.commit.domain.PdCommitRecord;
import com.ruoyi.pd.commit.mapper.PdCommitRecordMapper;
import com.ruoyi.pd.diff.domain.PdDifferenceRecord;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
import com.ruoyi.pd.stockpddetail.domain.PdStockpdDetail;
import com.ruoyi.pd.stockpddetail.mapper.PdStockpdDetailMapper;
import com.ruoyi.pda.domain.AddInJobRequest;
import com.ruoyi.pda.domain.BindTrayRequest;
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
@@ -51,6 +57,8 @@ public class PdaServiceImpl implements PdaService {
private static final String EMPTY_TRAY_IN_TASK_NO_TYPE = "6";
private static final String EMPTY_TRAY_OUT_TASK_NO_TYPE = "20";
private static final BigDecimal ONE_EMPTY_TRAY = BigDecimal.ONE;
private static final long MATERIAL_INVENTORY_OUT_TYPE = 30L;
private static final long LOCATION_INVENTORY_OUT_TYPE = 31L;
@Autowired
private PdaMapper pdaMapper;
@@ -82,6 +90,15 @@ public class PdaServiceImpl implements PdaService {
@Autowired
private TaskNoService taskNoService;
@Autowired
private PdCommitRecordMapper pdCommitRecordMapper;
@Autowired
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
@Autowired
private PdStockpdDetailMapper pdStockpdDetailMapper;
// ─────────────────────────────────────────────
// 入库待处理任务列表
// ─────────────────────────────────────────────
@@ -653,6 +670,18 @@ public class PdaServiceImpl implements PdaService {
return result;
}
@Override
public List<Map<String, Object>> inventoryTaskList(Map<String, Object> query) {
HashMap<String, Object> map = new HashMap<>();
if (query != null) {
map.putAll(query);
}
List<HashMap<String, Object>> rows = pdaMapper.inventoryTaskList(map);
List<Map<String, Object>> result = new ArrayList<>();
result.addAll(rows);
return result;
}
@Override
public Map<String, Object> outboundDetail(Map<String, Object> query) {
HashMap<String, Object> map = new HashMap<>();
@@ -670,6 +699,23 @@ public class PdaServiceImpl implements PdaService {
return detail;
}
@Override
public Map<String, Object> inventoryDetail(Map<String, Object> query) {
HashMap<String, Object> map = new HashMap<>();
if (query != null) {
map.putAll(query);
}
HashMap<String, Object> detail = pdaMapper.selectInventoryDetail(map);
if (detail == null || detail.isEmpty()) {
throw new RuntimeException("未找到对应盘点任务");
}
HashMap<String, Object> materialQuery = new HashMap<>();
materialQuery.put("jobId", detail.get("jobId"));
List<HashMap<String, Object>> materials = pdaMapper.selectInventoryTrayMaterials(materialQuery);
detail.put("materials", materials == null ? new ArrayList<>() : materials);
return detail;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void confirmOutbound(Map<String, Object> data) {
@@ -691,6 +737,224 @@ public class PdaServiceImpl implements PdaService {
createReturnInboundJobIfNeeded(outboundJob);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void confirmInventory(Map<String, Object> data) {
Map<String, Object> detail = inventoryDetail(data);
if (!"5".equals(Convert.toStr(detail.get("executionPhase")))) {
throw new RuntimeException("盘点出库未到达出口,不能扫码确认");
}
@SuppressWarnings("unchecked")
List<HashMap<String, Object>> materials = detail.get("materials") instanceof List<?>
? (List<HashMap<String, Object>>) detail.get("materials")
: new ArrayList<>();
if (materials.isEmpty()) {
throw new RuntimeException("盘点作业无物料明细");
}
List<Map<String, Object>> requestMaterials = normalizeInventoryRequestMaterials(data, materials);
for (HashMap<String, Object> material : materials) {
Map<String, Object> requestMaterial = findInventoryRequestMaterial(requestMaterials, material);
BigDecimal oldQuantity = toBigDecimal(firstNotBlank(mapValue(material, "quantity"), mapValue(material, "stockQuantity")));
BigDecimal actualQuantity = toBigDecimal(firstNotBlank(
mapValue(requestMaterial, "actualQuantity"),
mapValue(requestMaterial, "quantity")));
if (actualQuantity.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("盘点数量不能小于0");
}
insertInventoryCommitRecord(detail, material, oldQuantity, actualQuantity);
if (oldQuantity.compareTo(actualQuantity) != 0) {
insertInventoryDifferenceRecord(detail, material, oldQuantity, actualQuantity);
}
updateInventoryStockpdDetail(detail, material, oldQuantity, actualQuantity);
}
String jobId = Convert.toStr(detail.get("jobId"));
MydoJob outboundJob = mydoJobService.selectMydoJobByJobId(jobId);
AjaxResult result = mydoJobService.completeJob(jobId, "2");
if (result == null || result.isError()) {
throw new RuntimeException(result == null ? "盘点出库确认失败" : Convert.toStr(result.get(AjaxResult.MSG_TAG)));
}
createInventoryInboundJobIfNeeded(outboundJob, materials);
}
private List<Map<String, Object>> normalizeInventoryRequestMaterials(Map<String, Object> data, List<HashMap<String, Object>> materials) {
Object payload = data == null ? null : data.get("materials");
List<Map<String, Object>> result = new ArrayList<>();
if (payload instanceof List<?> list && !list.isEmpty()) {
for (Object item : list) {
if (item instanceof Map<?, ?>) {
@SuppressWarnings("unchecked")
Map<String, Object> material = (Map<String, Object>) item;
result.add(material);
}
}
}
return result;
}
private Map<String, Object> findInventoryRequestMaterial(List<Map<String, Object>> requestMaterials, Map<String, Object> material) {
String jobDetailId = mapString(material, "jobDetailId");
String stockDetailId = mapString(material, "stockDetailId");
String stockpdDetailId = mapString(material, "stockpdDetailId");
for (Map<String, Object> requestMaterial : requestMaterials) {
if (StringUtils.hasText(jobDetailId) && jobDetailId.equals(mapString(requestMaterial, "jobDetailId"))) {
return requestMaterial;
}
if (StringUtils.hasText(stockDetailId) && stockDetailId.equals(mapString(requestMaterial, "stockDetailId"))) {
return requestMaterial;
}
if (StringUtils.hasText(stockpdDetailId) && stockpdDetailId.equals(mapString(requestMaterial, "stockpdDetailId"))) {
return requestMaterial;
}
}
return material;
}
private void insertInventoryCommitRecord(Map<String, Object> detail, Map<String, Object> material,
BigDecimal oldQuantity, BigDecimal actualQuantity) {
PdCommitRecord record = new PdCommitRecord();
record.setWarehouseId(Convert.toStr(detail.get("warehouseId")));
record.setTrayCode(Convert.toStr(detail.get("trayCode")));
record.setStockId(mapString(material, "stockId"));
record.setProductId(mapProductId(material));
record.setOldQuantity(oldQuantity.longValue());
record.setPdQuantity(actualQuantity.longValue());
record.setUserId(SecurityUtils.getUserId());
record.setDepeId(SecurityUtils.getDeptId());
record.setStockpdDetailId(mapString(material, "stockpdDetailId"));
record.setCreateTime(DateUtils.getNowDate());
pdCommitRecordMapper.insertPdCommitRecord(record);
}
private void insertInventoryDifferenceRecord(Map<String, Object> detail, Map<String, Object> material,
BigDecimal oldQuantity, BigDecimal actualQuantity) {
PdDifferenceRecord record = new PdDifferenceRecord();
record.setWarehouseId(Convert.toStr(detail.get("warehouseId")));
record.setWarehouseName(Convert.toStr(detail.get("warehouseName")));
record.setTrayCode(Convert.toStr(detail.get("trayCode")));
record.setProductId(mapProductId(material));
record.setProductName(mapProductName(material));
record.setOldQuantity(oldQuantity.longValue());
record.setPdQuantity(actualQuantity.longValue());
record.setUserId(SecurityUtils.getUserId());
record.setUserName(SecurityUtils.getUsername());
record.setDepeId(SecurityUtils.getDeptId());
record.setDepeName(null);
record.setStockpdDetailId(mapString(material, "stockpdDetailId"));
record.setStatus("0");
record.setRecordType(resolveDifferenceRecordType(oldQuantity, actualQuantity));
record.setCreateTime(DateUtils.getNowDate());
pdDifferenceRecordMapper.insertPdDifferenceRecord(record);
}
private String resolveDifferenceRecordType(BigDecimal oldQuantity, BigDecimal actualQuantity) {
if (oldQuantity.compareTo(actualQuantity) < 0) {
return "1";
}
if (actualQuantity.compareTo(BigDecimal.ZERO) == 0) {
return "3";
}
return "2";
}
private void updateInventoryStockpdDetail(Map<String, Object> detail, Map<String, Object> material,
BigDecimal oldQuantity, BigDecimal actualQuantity) {
PdStockpdDetail update = new PdStockpdDetail();
update.setStockpdDetailId(mapString(material, "stockpdDetailId"));
update.setStockId(mapString(material, "stockId"));
update.setStockDetailId(mapString(material, "stockDetailId"));
update.setTrayCode(Convert.toStr(detail.get("trayCode")));
update.setWarehouseId(Convert.toStr(detail.get("warehouseId")));
update.setWarehouseName(Convert.toStr(detail.get("warehouseName")));
update.setCargoSpaceId(Convert.toStr(detail.get("locationCode")));
update.setCargoSpaceName(Convert.toStr(detail.get("locationName")));
update.setProductId(mapProductId(material));
update.setProductName(mapProductName(material));
update.setQuantity(oldQuantity.longValue());
update.setAllotNum(actualQuantity.longValue());
update.setAssignNum(actualQuantity.longValue());
pdStockpdDetailMapper.updatePdStockpdDetail(update);
}
private void createInventoryInboundJobIfNeeded(MydoJob outboundJob, List<HashMap<String, Object>> materials) {
if (outboundJob == null || !StringUtils.hasText(outboundJob.getTrayCode()) || !StringUtils.hasText(outboundJob.getWarehouseId())) {
return;
}
HashMap<String, Object> exists = pdaMapper.selectInventoryInboundJobByOutboundJobId(outboundJob.getJobId());
if (exists != null && !exists.isEmpty()) {
return;
}
String returnJobId = jobIdService.getId("PR");
String returnTaskNo = taskNoService.getTaskNo("2");
MydoJob returnJob = new MydoJob();
returnJob.setJobId(returnJobId);
returnJob.setTaskNo(StringUtils.hasText(returnTaskNo) ? returnTaskNo : returnJobId);
returnJob.setInOrOut(resolveInventoryInboundType(outboundJob));
returnJob.setStatus("1");
returnJob.setExecutionPhase("0");
returnJob.setTrayCode(outboundJob.getTrayCode());
returnJob.setWarehouseId(outboundJob.getWarehouseId());
returnJob.setWarehouseName(outboundJob.getWarehouseName());
returnJob.setEntranceId(outboundJob.getExitId());
returnJob.setPriority(10L);
returnJob.setHeight(BigDecimal.ZERO);
returnJob.setWeight(BigDecimal.ZERO);
copyLoginUser(returnJob);
if (StringUtils.hasText(outboundJob.getExitId())) {
BasStationPoint exitPoint = basStationPointService.selectBasStationPointByPointId(outboundJob.getExitId());
if (exitPoint != null) {
returnJob.setRoadwayId(exitPoint.getRoadwayId());
}
}
if (mydoJobService.insertMydoJob(returnJob) <= 0) {
throw new RuntimeException("盘点入库作业生成失败");
}
int index = 1;
for (HashMap<String, Object> material : materials) {
BigDecimal quantity = toBigDecimal(firstNotBlank(mapValue(material, "quantity"), mapValue(material, "stockQuantity")));
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
MydoJobDetail returnDetail = new MydoJobDetail();
returnDetail.setJobDetailId(returnJobId + "-" + index++);
returnDetail.setJobId(returnJobId);
returnDetail.setStockId(mapString(material, "stockId"));
returnDetail.setLotNo(mapString(material, "lotNo"));
returnDetail.setInDate(DateUtils.getTime());
returnDetail.setInOrOutId(outboundJob.getJobId());
returnDetail.setInOrOutDId(mapString(material, "stockpdDetailId"));
returnDetail.setProductId(mapProductId(material));
returnDetail.setProductName(mapProductName(material));
returnDetail.setInQuantity(quantity);
returnDetail.setStockQuantity(quantity);
returnDetail.setStockinDetailId(mapString(material, "stockDetailId"));
mydoJobDetailService.insertMydoJobDetail(returnDetail);
}
ApiResponse<String> wcsResponse = wcsClient.sendPutawayTask(returnJob);
if (wcsResponse == null || !wcsResponse.isSuccess()) {
String message = wcsResponse == null ? "WCS接口无返回" : wcsResponse.getMessage();
throw new RuntimeException("WCS盘点回库作业下发失败:" + message);
}
markInJobDispatchedToWcs(returnJobId);
}
private Long resolveInventoryInboundType(MydoJob outboundJob) {
return outboundJob.getInOrOut() != null && outboundJob.getInOrOut() == LOCATION_INVENTORY_OUT_TYPE ? 11L : 10L;
}
private String mapProductId(Map<String, Object> material) {
return Convert.toStr(firstNotBlank(mapValue(material, "productId"), mapValue(material, "materialCode")));
}
private String mapProductName(Map<String, Object> material) {
return Convert.toStr(firstNotBlank(mapValue(material, "productName"), mapValue(material, "materialName")));
}
@SuppressWarnings("unchecked")
private BigDecimal applyOutboundMaterialQuantities(Map<String, Object> data, Map<String, Object> detail) {
Object materialPayload = data == null ? null : data.get("materials");

View File

@@ -112,6 +112,88 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where id = #{id}
</update>
<update id="applyApprovedDifference" parameterType="PdDifferenceRecord">
update bsd
set bsd.quantity = #{pdQuantity},
bsd.out_quantity = case
when isnull(bsd.out_quantity, 0) &gt; #{pdQuantity} then #{pdQuantity}
else isnull(bsd.out_quantity, 0)
end,
bsd.update_time = sysdatetime()
from bas_stock_detail bsd
inner join bas_stock bs on bsd.stock_id = bs.stock_id
left join pd_stockpd_detail pdd on pdd.stockpd_detail_id = #{stockpdDetailId}
left join pd_stockpd_detail_finish pddf on pddf.stockpd_detail_id = #{stockpdDetailId}
outer apply (
select coalesce(nullif(pdd.stock_detail_id, ''), nullif(pddf.stock_detail_id, '')) as stock_detail_id
) src
where (
src.stock_detail_id is not null
and (bsd.stockin_detail_id = src.stock_detail_id or bsd.detail_id = src.stock_detail_id)
)
or (
src.stock_detail_id is null
and bs.tray_code = #{trayCode}
and bsd.product_id = #{productId}
<if test="warehouseId != null and warehouseId != ''">
and bs.warehouse_id = #{warehouseId}
</if>
)
</update>
<update id="refreshApprovedDifferenceStockSummary" parameterType="PdDifferenceRecord">
update bs
set bs.quantity = isnull(bsa.quantity, 0),
bs.out_quantity = isnull(bsa.out_quantity, 0)
from bas_stock bs
left join (
select stock_id,
sum(isnull(quantity, 0)) as quantity,
sum(isnull(out_quantity, 0)) as out_quantity
from bas_stock_detail
group by stock_id
) bsa on bsa.stock_id = bs.stock_id
where bs.stock_id in (
select distinct bsd.stock_id
from bas_stock_detail bsd
inner join bas_stock xbs on bsd.stock_id = xbs.stock_id
left join pd_stockpd_detail pdd on pdd.stockpd_detail_id = #{stockpdDetailId}
left join pd_stockpd_detail_finish pddf on pddf.stockpd_detail_id = #{stockpdDetailId}
outer apply (
select coalesce(nullif(pdd.stock_detail_id, ''), nullif(pddf.stock_detail_id, '')) as stock_detail_id
) src
where (
src.stock_detail_id is not null
and (bsd.stockin_detail_id = src.stock_detail_id or bsd.detail_id = src.stock_detail_id)
)
or (
src.stock_detail_id is null
and xbs.tray_code = #{trayCode}
and bsd.product_id = #{productId}
<if test="warehouseId != null and warehouseId != ''">
and xbs.warehouse_id = #{warehouseId}
</if>
)
)
</update>
<update id="markInboundDifferencesPendingReviewByJobId" parameterType="String">
update pd_difference_record
set status = '1'
where status = '0'
and stockpd_detail_id in (
select in_or_out_d_id
from mydo_job_detail
where job_id = #{jobId}
and in_or_out_d_id is not null
union
select in_or_out_d_id
from mydo_job_detail_finish
where job_id = #{jobId}
and in_or_out_d_id is not null
)
</update>
<delete id="deletePdDifferenceRecordById" parameterType="Long">
delete from pd_difference_record where id = #{id}
</delete>
@@ -122,4 +204,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{id}
</foreach>
</delete>
</mapper>
</mapper>

View File

@@ -8,6 +8,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="stockpdDetailId" column="stockpd_detail_id" />
<result property="stockpdId" column="stockpd_id" />
<result property="trayCode" column="tray_code" />
<result property="stockId" column="stock_id" />
<result property="stockDetailId" column="stock_detail_id" />
<result property="cargoSpaceId" column="cargo_space_id" />
<result property="cargoSpaceName" column="cargo_space_name" />
<result property="warehouseId" column="warehouse_id" />
<result property="warehouseName" column="warehouse_name" />
<result property="productId" column="product_id" />
<result property="productName" column="product_name" />
<result property="quantity" column="quantity" />
@@ -17,12 +23,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectPdStockpdDetailVo">
select stockpd_detail_id, stockpd_id, tray_code, product_id, product_name, quantity, allot_num, assign_num, remark from pd_stockpd_detail
select stockpd_detail_id, stockpd_id, tray_code, stock_id, stock_detail_id, cargo_space_id, cargo_space_name,
warehouse_id, warehouse_name, product_id, product_name, quantity, allot_num, assign_num, remark
from pd_stockpd_detail
</sql>
<sql id="selectPdStockpdDetailFinishVo">
select stockpd_detail_id,
stockpd_id,
tray_code,
stock_id,
stock_detail_id,
cargo_space_id,
cargo_space_name,
warehouse_id,
warehouse_name,
product_id,
product_name,
quantity,
@@ -37,6 +51,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<where>
<if test="stockpdId != null and stockpdId != ''"> and stockpd_id = #{stockpdId}</if>
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
<if test="stockId != null and stockId != ''"> and stock_id = #{stockId}</if>
<if test="stockDetailId != null and stockDetailId != ''"> and stock_detail_id = #{stockDetailId}</if>
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
<if test="productName != null and productName != ''"> and product_name like concat('%', #{productName}, '%')</if>
<if test="quantity != null "> and quantity = #{quantity}</if>
@@ -54,6 +72,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<where>
<if test="stockpdId != null and stockpdId != ''"> and stockpd_id = #{stockpdId}</if>
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
<if test="stockId != null and stockId != ''"> and stock_id = #{stockId}</if>
<if test="stockDetailId != null and stockDetailId != ''"> and stock_detail_id = #{stockDetailId}</if>
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
<if test="productName != null and productName != ''"> and product_name like concat('%', #{productName}, '%')</if>
<if test="quantity != null "> and quantity = #{quantity}</if>
@@ -68,6 +90,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stockpdDetailId != null">stockpd_detail_id,</if>
<if test="stockpdId != null and stockpdId != ''">stockpd_id,</if>
<if test="trayCode != null">tray_code,</if>
<if test="stockId != null">stock_id,</if>
<if test="stockDetailId != null">stock_detail_id,</if>
<if test="cargoSpaceId != null">cargo_space_id,</if>
<if test="cargoSpaceName != null">cargo_space_name,</if>
<if test="warehouseId != null">warehouse_id,</if>
<if test="warehouseName != null">warehouse_name,</if>
<if test="productId != null and productId != ''">product_id,</if>
<if test="productName != null">product_name,</if>
<if test="quantity != null">quantity,</if>
@@ -79,6 +107,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stockpdDetailId != null">#{stockpdDetailId},</if>
<if test="stockpdId != null and stockpdId != ''">#{stockpdId},</if>
<if test="trayCode != null">#{trayCode},</if>
<if test="stockId != null">#{stockId},</if>
<if test="stockDetailId != null">#{stockDetailId},</if>
<if test="cargoSpaceId != null">#{cargoSpaceId},</if>
<if test="cargoSpaceName != null">#{cargoSpaceName},</if>
<if test="warehouseId != null">#{warehouseId},</if>
<if test="warehouseName != null">#{warehouseName},</if>
<if test="productId != null and productId != ''">#{productId},</if>
<if test="productName != null">#{productName},</if>
<if test="quantity != null">#{quantity},</if>
@@ -93,6 +127,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="SET" suffixOverrides=",">
<if test="stockpdId != null and stockpdId != ''">stockpd_id = #{stockpdId},</if>
<if test="trayCode != null">tray_code = #{trayCode},</if>
<if test="stockId != null">stock_id = #{stockId},</if>
<if test="stockDetailId != null">stock_detail_id = #{stockDetailId},</if>
<if test="cargoSpaceId != null">cargo_space_id = #{cargoSpaceId},</if>
<if test="cargoSpaceName != null">cargo_space_name = #{cargoSpaceName},</if>
<if test="warehouseId != null">warehouse_id = #{warehouseId},</if>
<if test="warehouseName != null">warehouse_name = #{warehouseName},</if>
<if test="productId != null and productId != ''">product_id = #{productId},</if>
<if test="productName != null">product_name = #{productName},</if>
<if test="quantity != null">quantity = #{quantity},</if>
@@ -113,4 +153,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{stockpdDetailId}
</foreach>
</delete>
</mapper>
</mapper>

View File

@@ -120,12 +120,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from mydo_job j
inner join mydo_job_detail jd on j.job_id = jd.job_id
where j.in_or_out &gt;= 20
and j.in_or_out not in (30, 31)
and j.status in ('1', '2', '3')
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
group by j.job_id, j.task_no, j.exit_id, j.create_time, j.status, j.execution_phase
order by j.create_time desc
</select>
<select id="inventoryTaskList" parameterType="java.util.Map" resultType="java.util.HashMap">
select j.job_id as jobId,
j.task_no as taskNo,
'inventory' as type,
case when j.in_or_out = 31 then '货位盘点' else '物料盘点' end as typeName,
'inventory' as typeClass,
max(jd.product_id) as materialCode,
max(jd.product_name) as materialName,
sum(isnull(jd.out_quantity, 0)) as quantity,
isnull(j.exit_id, '') as portName,
j.tray_code as trayCode,
convert(varchar(19), j.create_time, 120) as createTime,
j.status as status,
j.execution_phase as executionPhase,
case
when j.execution_phase = '5' then '待扫码盘点'
when j.status = '1' then '未下发'
when j.status = '2' then '待执行'
when j.status = '3' then 'WCS执行中'
else '待盘点'
end as statusName,
case
when j.execution_phase = '5' then 'pending'
when j.status = '3' then 'processing'
else 'pending'
end as statusClass
from mydo_job j
inner join mydo_job_detail jd on j.job_id = jd.job_id
where j.in_or_out in (30, 31)
and j.status in ('1', '2', '3')
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
group by j.job_id, j.task_no, j.in_or_out, j.exit_id, j.tray_code, j.create_time, j.status, j.execution_phase
order by j.create_time desc
</select>
<select id="selectOutboundDetail" parameterType="java.util.Map" resultType="java.util.HashMap">
select top 1 j.job_id as jobId,
j.task_no as taskNo,
@@ -142,6 +178,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from mydo_job j
inner join mydo_job_detail jd on j.job_id = jd.job_id
where j.in_or_out &gt;= 20
and j.in_or_out not in (30, 31)
and j.status in ('1', '2', '3')
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
and (#{barcode} is null or #{barcode} = '' or j.tray_code = #{barcode})
@@ -150,6 +187,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by j.create_time desc
</select>
<select id="selectInventoryDetail" parameterType="java.util.Map" resultType="java.util.HashMap">
select top 1 j.job_id as jobId,
j.task_no as taskNo,
j.tray_code as trayCode,
j.cargo_space_id as locationCode,
j.cargo_space_name as locationName,
j.warehouse_id as warehouseId,
j.warehouse_name as warehouseName,
j.execution_phase as executionPhase,
j.in_or_out as inventoryType,
isnull(j.exit_id, '') as portName,
sum(isnull(jd.out_quantity, 0)) as quantity,
count(1) as materialCount,
'' as spec
from mydo_job j
inner join mydo_job_detail jd on j.job_id = jd.job_id
where j.in_or_out in (30, 31)
and j.status in ('1', '2', '3')
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
and (#{barcode} is null or #{barcode} = '' or j.tray_code = #{barcode})
group by j.job_id, j.task_no, j.tray_code, j.cargo_space_id, j.cargo_space_name,
j.warehouse_id, j.warehouse_name, j.execution_phase, j.in_or_out, j.exit_id, j.create_time
order by j.create_time desc
</select>
<select id="selectOutboundTrayMaterials" parameterType="java.util.Map" resultType="java.util.HashMap">
select jd.job_detail_id as jobDetailId,
jd.stock_id as stockId,
@@ -165,6 +227,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by jd.job_detail_id
</select>
<select id="selectInventoryTrayMaterials" parameterType="java.util.Map" resultType="java.util.HashMap">
select jd.job_detail_id as jobDetailId,
jd.stock_id as stockId,
jd.stockin_detail_id as stockDetailId,
jd.in_or_out_d_id as stockpdDetailId,
jd.product_id as materialCode,
jd.product_name as materialName,
jd.lot_no as lotNo,
jd.stock_quantity as stockQuantity,
jd.out_quantity as quantity
from mydo_job_detail jd
where jd.job_id = #{jobId}
order by jd.job_detail_id
</select>
<select id="selectInventoryInboundJobByOutboundJobId" parameterType="String" resultType="java.util.HashMap">
select top 1 jobId, taskNo, status
from (
select j.job_id as jobId,
j.task_no as taskNo,
j.status as status,
j.create_time as createTime
from mydo_job j
inner join mydo_job_detail jd on j.job_id = jd.job_id
where j.in_or_out in (10, 11)
and jd.in_or_out_id = #{outboundJobId}
union all
select jf.job_id as jobId,
jf.task_no as taskNo,
jf.status as status,
jf.create_time as createTime
from mydo_job_finish jf
inner join mydo_job_detail_finish jdf on jf.job_id = jdf.job_id
where jf.in_or_out in (10, 11)
and jdf.in_or_out_id = #{outboundJobId}
) x
order by createTime desc
</select>
<select id="selectResidualTrayMaterials" parameterType="java.util.Map" resultType="java.util.HashMap">
select bsd.detail_id as stockDetailId,
bsd.stock_id as stockId,
@@ -301,6 +402,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and isnull(quantity, 0) - isnull(out_quantity, 0) &gt;= #{quantity}
</update>
<update id="reserveInventoryStockDetail" parameterType="java.util.Map">
update bas_stock_detail
set out_quantity = isnull(out_quantity, 0) + #{quantity},
job_detail_id = #{jobDetailId},
update_time = sysdatetime()
where detail_id = #{stockDetailId}
and stock_id = #{stockId}
and isnull(quantity, 0) - isnull(out_quantity, 0) &gt;= #{quantity}
</update>
<update id="lockEmptyTrayCargoSpace" parameterType="java.util.Map">
update bcs
set status = '4',
@@ -310,4 +421,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.stock_id = #{stockId}
and bs.cargo_space_id = #{cargoSpaceId}
</update>
<update id="lockInventoryOutboundCargoSpace" parameterType="java.util.Map">
update bcs
set status = '4',
tray_code = bs.tray_code
from bas_cargo_space bcs
inner join bas_stock bs on bcs.cargo_space_id = bs.cargo_space_id
where bs.stock_id = #{stockId}
and bs.cargo_space_id = #{cargoSpaceId}
</update>
</mapper>

View File

@@ -16,6 +16,7 @@ import com.ruoyi.in.jobdetail.mapper.MydoJobDetailMapper;
import com.ruoyi.in.stockin.mapper.InStockinMapper;
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -71,6 +72,9 @@ class MydoJobServiceImplTest {
@Mock
private WcsClient wcsClient;
@Mock
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
@InjectMocks
private MydoJobServiceImpl service;
@@ -225,6 +229,26 @@ class MydoJobServiceImplTest {
verify(mydoJobMapper, never()).completeJob(any(java.util.HashMap.class));
}
@Test
void completeInventoryInboundFromWcsMarksDifferencesPendingReviewAfterProcedureSucceeds() {
MydoJob job = new MydoJob();
job.setJobId("PR001");
job.setInOrOut(10L);
when(mydoJobMapper.selectMydoJobByJobId("PR001")).thenReturn(job);
when(mydoJobMapper.selectStockinIdsByJobId("PR001")).thenReturn(Collections.emptyList());
doAnswer(invocation -> {
@SuppressWarnings("unchecked")
java.util.HashMap<String, Object> map = invocation.getArgument(0);
map.put("v_result", "1");
return null;
}).when(mydoJobMapper).completeJob(any(java.util.HashMap.class));
AjaxResult result = service.completeInJobFromWcs("PR001");
assertThat(result.isSuccess()).isTrue();
verify(pdDifferenceRecordMapper).markInboundDifferencesPendingReviewByJobId("PR001");
}
@Test
void completeOutJobFromWcsRejectsInboundJobType() {
MydoJob job = new MydoJob();

View File

@@ -0,0 +1,66 @@
package com.ruoyi.pd.diff.service.impl;
import com.ruoyi.pd.diff.domain.PdDifferenceRecord;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PdDifferenceRecordServiceImplTest {
@Mock
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
@InjectMocks
private PdDifferenceRecordServiceImpl service;
@Test
void approvePendingDifferenceAppliesStockQuantityAndMarksApproved() {
PdDifferenceRecord record = new PdDifferenceRecord();
record.setId(1L);
record.setStatus("1");
record.setRecordType("2");
record.setStockpdDetailId("PDD001-1");
record.setTrayCode("A001");
record.setProductId("MAT001");
record.setOldQuantity(5L);
record.setPdQuantity(4L);
when(pdDifferenceRecordMapper.selectPdDifferenceRecordById(1L)).thenReturn(record);
when(pdDifferenceRecordMapper.applyApprovedDifference(record)).thenReturn(1);
when(pdDifferenceRecordMapper.refreshApprovedDifferenceStockSummary(record)).thenReturn(1);
when(pdDifferenceRecordMapper.updatePdDifferenceRecord(record)).thenReturn(1);
int rows = service.approvePdDifferenceRecord(1L);
assertThat(rows).isEqualTo(1);
verify(pdDifferenceRecordMapper).applyApprovedDifference(record);
verify(pdDifferenceRecordMapper).refreshApprovedDifferenceStockSummary(record);
ArgumentCaptor<PdDifferenceRecord> updateCaptor = ArgumentCaptor.forClass(PdDifferenceRecord.class);
verify(pdDifferenceRecordMapper).updatePdDifferenceRecord(updateCaptor.capture());
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("2");
}
@Test
void rejectPendingDifferenceLeavesStockUntouchedAndMarksRejected() {
PdDifferenceRecord record = new PdDifferenceRecord();
record.setId(2L);
record.setStatus("1");
when(pdDifferenceRecordMapper.selectPdDifferenceRecordById(2L)).thenReturn(record);
when(pdDifferenceRecordMapper.updatePdDifferenceRecord(record)).thenReturn(1);
int rows = service.rejectPdDifferenceRecord(2L);
assertThat(rows).isEqualTo(1);
ArgumentCaptor<PdDifferenceRecord> updateCaptor = ArgumentCaptor.forClass(PdDifferenceRecord.class);
verify(pdDifferenceRecordMapper).updatePdDifferenceRecord(updateCaptor.capture());
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("3");
}
}

View File

@@ -0,0 +1,161 @@
package com.ruoyi.pd.stockpd.service.impl;
import com.ruoyi.bas.id.job.service.JobIdService;
import com.ruoyi.bas.id.taskno.service.TaskNoService;
import com.ruoyi.bas.stock.domain.BasStockDetail;
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.in.job.domain.MydoJob;
import com.ruoyi.in.job.service.IMydoJobService;
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
import com.ruoyi.pd.stockpd.domain.PdStockpd;
import com.ruoyi.pd.stockpd.mapper.PdStockpdMapper;
import com.ruoyi.pd.stockpddetail.domain.PdStockpdDetail;
import com.ruoyi.pd.stockpddetail.mapper.PdStockpdDetailMapper;
import com.ruoyi.pda.mapper.PdaMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
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;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PdStockpdServiceImplTest {
@Mock
private PdStockpdMapper pdStockpdMapper;
@Mock
private PdStockpdDetailMapper pdStockpdDetailMapper;
@Mock
private SysDeptMapper sysDeptMapper;
@Mock
private JobIdService jobIdService;
@Mock
private TaskNoService taskNoService;
@Mock
private BasStockDetailMapper basStockDetailMapper;
@Mock
private IBasStationPointService basStationPointService;
@Mock
private IMydoJobService mydoJobService;
@Mock
private IMydoJobDetailService mydoJobDetailService;
@Mock
private PdaMapper pdaMapper;
@InjectMocks
private PdStockpdServiceImpl service;
@Test
void autoAssignCreatesInventoryOutboundJobFromStockDetailAndPushesWcs() {
PdStockpd stockpd = new PdStockpd();
stockpd.setStockpdId("PDD001");
stockpd.setPdType("1");
stockpd.setStatus("2");
stockpd.setWarehouseId("WH01");
stockpd.setWarehouseName("一号仓");
when(pdStockpdMapper.selectPdStockpdByStockpdId("PDD001")).thenReturn(stockpd);
PdStockpdDetail detail = new PdStockpdDetail();
detail.setStockpdDetailId("PDD001-1");
detail.setStockpdId("PDD001");
detail.setStockId("KC001");
detail.setStockDetailId("SD001");
detail.setTrayCode("A001");
detail.setProductId("MAT001");
detail.setProductName("物料1");
detail.setQuantity(5L);
when(pdStockpdDetailMapper.selectPdStockpdDetailList(any(PdStockpdDetail.class))).thenReturn(List.of(detail));
BasStockDetail stockDetail = new BasStockDetail();
stockDetail.setDetailId("SD001");
stockDetail.setStockId("KC001");
stockDetail.setTrayCode("A001");
stockDetail.setWarehouseId("WH01");
stockDetail.setWarehouseName("一号仓");
stockDetail.setCargoSpaceId("CS001");
stockDetail.setCargoSpaceName("货位1");
stockDetail.setLotNo("LOT1");
stockDetail.setProductId("MAT001");
stockDetail.setProductName("物料1");
stockDetail.setQuantity(new BigDecimal("5"));
stockDetail.setAvailableQuantity(new BigDecimal("5"));
when(basStockDetailMapper.selectBasStockDetailByDetailId("SD001")).thenReturn(stockDetail);
BasStationPoint exitPoint = new BasStationPoint();
exitPoint.setPointId("OUT01");
exitPoint.setWarehouseId("WH01");
exitPoint.setRoadwayId("R01");
exitPoint.setPointType("2");
exitPoint.setStatus("0");
when(basStationPointService.selectBasStationPointList(any(BasStationPoint.class))).thenReturn(List.of(exitPoint));
when(jobIdService.getId("PC")).thenReturn("PC001");
when(taskNoService.getTaskNo("22")).thenReturn("40001");
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
when(pdaMapper.reserveInventoryStockDetail(any(HashMap.class))).thenReturn(1);
when(pdaMapper.lockInventoryOutboundCargoSpace(any(HashMap.class))).thenReturn(1);
when(mydoJobService.pushOutJobToWcs("PC001")).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
when(pdStockpdMapper.updatePdStockpd(any(PdStockpd.class))).thenReturn(1);
when(pdStockpdDetailMapper.updatePdStockpdDetail(any(PdStockpdDetail.class))).thenReturn(1);
AjaxResult result = service.autoAssignPdStockpd("PDD001");
assertThat(result.isSuccess()).isTrue();
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
MydoJob job = jobCaptor.getValue();
assertThat(job.getJobId()).isEqualTo("PC001");
assertThat(job.getTaskNo()).isEqualTo("40001");
assertThat(job.getInOrOut()).isEqualTo(30L);
assertThat(job.getTrayCode()).isEqualTo("A001");
assertThat(job.getWarehouseId()).isEqualTo("WH01");
assertThat(job.getCargoSpaceId()).isEqualTo("CS001");
assertThat(job.getExitId()).isEqualTo("OUT01");
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
verify(mydoJobDetailService).insertMydoJobDetail(detailCaptor.capture());
MydoJobDetail jobDetail = detailCaptor.getValue();
assertThat(jobDetail.getJobDetailId()).isEqualTo("PC001-1");
assertThat(jobDetail.getStockId()).isEqualTo("KC001");
assertThat(jobDetail.getStockinDetailId()).isEqualTo("SD001");
assertThat(jobDetail.getInOrOutId()).isEqualTo("PDD001");
assertThat(jobDetail.getInOrOutDId()).isEqualTo("PDD001-1");
assertThat(jobDetail.getOutQuantity()).isEqualByComparingTo("5");
ArgumentCaptor<HashMap> reserveCaptor = ArgumentCaptor.forClass(HashMap.class);
verify(pdaMapper).reserveInventoryStockDetail(reserveCaptor.capture());
assertThat(reserveCaptor.getValue())
.containsEntry("stockDetailId", "SD001")
.containsEntry("stockId", "KC001")
.containsEntry("jobDetailId", "PC001-1")
.containsEntry("quantity", new BigDecimal("5"));
verify(mydoJobService).pushOutJobToWcs("PC001");
verify(pdStockpdMapper).updatePdStockpd(any(PdStockpd.class));
verify(pdStockpdDetailMapper).updatePdStockpdDetail(any(PdStockpdDetail.class));
}
}

View File

@@ -86,10 +86,26 @@ class PdaMapperSqlTest {
.contains("j.execution_phase as executionPhase")
.contains("when j.execution_phase = '5' then '待扫码确认'")
.contains("and j.status in ('1', '2', '3')")
.contains("and j.in_or_out not in (30, 31)")
.contains("group by j.job_id, j.task_no, j.exit_id, j.create_time, j.status, j.execution_phase");
assertThat(detailSql)
.contains("j.execution_phase as executionPhase")
.contains("and j.status in ('1', '2', '3')");
.contains("and j.status in ('1', '2', '3')")
.contains("and j.in_or_out not in (30, 31)");
}
@Test
void inventoryQueriesUseDedicatedInventoryJobTypes() throws Exception {
String xml = Files.readString(resolveMapperXml());
String detailSql = section(xml, "<select id=\"selectInventoryDetail\"", "</select>");
String materialsSql = section(xml, "<select id=\"selectInventoryTrayMaterials\"", "</select>");
assertThat(detailSql)
.contains("j.in_or_out in (30, 31)")
.contains("j.execution_phase as executionPhase");
assertThat(materialsSql)
.contains("jd.in_or_out_d_id as stockpdDetailId")
.contains("jd.stockin_detail_id as stockDetailId");
}
private Path resolveMapperXml() {

View File

@@ -17,6 +17,9 @@ import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
import com.ruoyi.in.stockin.mapper.InStockinMapper;
import com.ruoyi.in.sotckindetail.mapper.InStockinDetailMapper;
import com.ruoyi.pd.commit.mapper.PdCommitRecordMapper;
import com.ruoyi.pd.diff.mapper.PdDifferenceRecordMapper;
import com.ruoyi.pd.stockpddetail.mapper.PdStockpdDetailMapper;
import com.ruoyi.pda.domain.AddInJobRequest;
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
import com.ruoyi.pda.mapper.PdaMapper;
@@ -79,6 +82,15 @@ class PdaServiceImplTest {
@Mock
private TaskNoService taskNoService;
@Mock
private PdCommitRecordMapper pdCommitRecordMapper;
@Mock
private PdDifferenceRecordMapper pdDifferenceRecordMapper;
@Mock
private PdStockpdDetailMapper pdStockpdDetailMapper;
@InjectMocks
private PdaServiceImpl service;
@@ -407,6 +419,106 @@ class PdaServiceImplTest {
verify(mydoJobService).completeJob("CK002", "2");
}
@Test
void confirmInventoryRecordsDifferenceCompletesOutboundAndCreatesReturnInboundWithOriginalQuantity() {
HashMap<String, Object> inventoryDetail = new HashMap<>();
inventoryDetail.put("jobId", "PC001");
inventoryDetail.put("taskNo", "40001");
inventoryDetail.put("trayCode", "A001");
inventoryDetail.put("warehouseId", "WH01");
inventoryDetail.put("warehouseName", "一号仓");
inventoryDetail.put("quantity", "5");
inventoryDetail.put("executionPhase", "5");
when(pdaMapper.selectInventoryDetail(any(HashMap.class))).thenReturn(inventoryDetail);
HashMap<String, Object> material = new HashMap<>();
material.put("jobDetailId", "PC001-1");
material.put("stockId", "KC001");
material.put("stockDetailId", "SD001");
material.put("stockpdDetailId", "PDD001-1");
material.put("materialCode", "MAT001");
material.put("materialName", "物料1");
material.put("lotNo", "LOT1");
material.put("stockQuantity", "5");
material.put("quantity", "5");
when(pdaMapper.selectInventoryTrayMaterials(any(HashMap.class))).thenReturn(List.of(material));
when(pdaMapper.selectInventoryInboundJobByOutboundJobId("PC001")).thenReturn(null);
MydoJob outboundJob = new MydoJob();
outboundJob.setJobId("PC001");
outboundJob.setTaskNo("40001");
outboundJob.setInOrOut(30L);
outboundJob.setTrayCode("A001");
outboundJob.setWarehouseId("WH01");
outboundJob.setWarehouseName("一号仓");
outboundJob.setExitId("OUT01");
when(mydoJobService.selectMydoJobByJobId("PC001")).thenReturn(outboundJob);
when(mydoJobService.completeJob("PC001", "2")).thenReturn(AjaxResult.success());
BasStationPoint exitPoint = new BasStationPoint();
exitPoint.setPointId("OUT01");
exitPoint.setRoadwayId("R02");
when(basStationPointService.selectBasStationPointByPointId("OUT01")).thenReturn(exitPoint);
when(jobIdService.getId("PR")).thenReturn("PR001");
when(taskNoService.getTaskNo("2")).thenReturn("30001");
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
when(mydoJobService.updateMydoJob(any(MydoJob.class))).thenReturn(1);
when(pdCommitRecordMapper.insertPdCommitRecord(any())).thenReturn(1);
when(pdDifferenceRecordMapper.insertPdDifferenceRecord(any())).thenReturn(1);
when(pdStockpdDetailMapper.updatePdStockpdDetail(any())).thenReturn(1);
when(wcsClient.sendPutawayTask(any(MydoJob.class))).thenReturn(ApiResponse.ok("OK"));
HashMap<String, Object> requestMaterial = new HashMap<>();
requestMaterial.put("jobDetailId", "PC001-1");
requestMaterial.put("stockDetailId", "SD001");
requestMaterial.put("stockpdDetailId", "PDD001-1");
requestMaterial.put("quantity", "4");
HashMap<String, Object> data = new HashMap<>();
data.put("taskNo", "40001");
data.put("barcode", "A001");
data.put("materials", List.of(requestMaterial));
service.confirmInventory(data);
verify(mydoJobService).completeJob("PC001", "2");
ArgumentCaptor<com.ruoyi.pd.commit.domain.PdCommitRecord> commitCaptor =
ArgumentCaptor.forClass(com.ruoyi.pd.commit.domain.PdCommitRecord.class);
verify(pdCommitRecordMapper).insertPdCommitRecord(commitCaptor.capture());
assertThat(commitCaptor.getValue().getStockpdDetailId()).isEqualTo("PDD001-1");
assertThat(commitCaptor.getValue().getOldQuantity()).isEqualTo(5L);
assertThat(commitCaptor.getValue().getPdQuantity()).isEqualTo(4L);
ArgumentCaptor<com.ruoyi.pd.diff.domain.PdDifferenceRecord> diffCaptor =
ArgumentCaptor.forClass(com.ruoyi.pd.diff.domain.PdDifferenceRecord.class);
verify(pdDifferenceRecordMapper).insertPdDifferenceRecord(diffCaptor.capture());
assertThat(diffCaptor.getValue().getStockpdDetailId()).isEqualTo("PDD001-1");
assertThat(diffCaptor.getValue().getStatus()).isEqualTo("0");
assertThat(diffCaptor.getValue().getRecordType()).isEqualTo("2");
assertThat(diffCaptor.getValue().getOldQuantity()).isEqualTo(5L);
assertThat(diffCaptor.getValue().getPdQuantity()).isEqualTo(4L);
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
MydoJob inboundJob = jobCaptor.getValue();
assertThat(inboundJob.getJobId()).isEqualTo("PR001");
assertThat(inboundJob.getTaskNo()).isEqualTo("30001");
assertThat(inboundJob.getInOrOut()).isEqualTo(10L);
assertThat(inboundJob.getEntranceId()).isEqualTo("OUT01");
assertThat(inboundJob.getRoadwayId()).isEqualTo("R02");
verify(wcsClient).sendPutawayTask(inboundJob);
ArgumentCaptor<MydoJobDetail> inboundDetailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
verify(mydoJobDetailService).insertMydoJobDetail(inboundDetailCaptor.capture());
MydoJobDetail inboundDetail = inboundDetailCaptor.getValue();
assertThat(inboundDetail.getJobId()).isEqualTo("PR001");
assertThat(inboundDetail.getInOrOutId()).isEqualTo("PC001");
assertThat(inboundDetail.getInOrOutDId()).isEqualTo("PDD001-1");
assertThat(inboundDetail.getStockinDetailId()).isEqualTo("SD001");
assertThat(inboundDetail.getInQuantity()).isEqualByComparingTo("5");
}
private void stubInboundStationAndRoadway() {
BasStationPoint point = new BasStationPoint();
point.setPointId("IN01");

View File

@@ -377,14 +377,18 @@ export default {
if (!this.detailLines.some(l => l.detailId === s.detailId)) {
this.detailLines.push({
detailId: s.detailId,
stockDetailId: s.detailId,
stockId: s.stockId,
productId: s.productId,
productName: s.productName,
lotNo: s.lotNo || '',
cargoSpaceId: s.cargoSpaceId || '',
cargoSpaceName: s.cargoSpaceName || '',
trayCode: s.trayCode || '',
warehouseId: s.warehouseId || this.mainForm.warehouseId,
warehouseName: s.warehouseName || this.mainForm.warehouseName,
stockQuantity: Number(s.availableQuantity != null ? s.availableQuantity : s.quantity) || 0,
pdQuantity: 1, // 盘点数量默认1
pdQuantity: Number(s.availableQuantity != null ? s.availableQuantity : s.quantity) || 1,
remark: ''
})
}
@@ -424,14 +428,18 @@ export default {
const saveData = {
...this.mainForm,
details: this.detailLines.map(line => ({
stockId: line.stockId,
productId: line.productId,
productName: line.productName,
lotNo: line.lotNo || null,
cargoSpaceId: line.cargoSpaceId || null,
trayCode: line.trayCode || null,
quantity: line.pdQuantity, // 盘点数量
remark: line.remark || null
stockId: line.stockId,
stockDetailId: line.stockDetailId || line.detailId,
productId: line.productId,
productName: line.productName,
lotNo: line.lotNo || null,
cargoSpaceId: line.cargoSpaceId || null,
cargoSpaceName: line.cargoSpaceName || null,
warehouseId: line.warehouseId || this.mainForm.warehouseId,
warehouseName: line.warehouseName || this.mainForm.warehouseName,
trayCode: line.trayCode || null,
quantity: line.pdQuantity,
remark: line.remark || null
}))
}
const res = await saveWithDetails(saveData)

View File

@@ -0,0 +1,80 @@
-- 盘点流程增强:盘点明细关联库存明细,支持盘点出库/PDA盘点/盘点回库/差异审核。
-- 可重复执行;请先在 FTC25009-WMS 的测试库验证后再上生产。
IF COL_LENGTH('dbo.pd_stockpd_detail', 'stock_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD stock_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail', 'stock_detail_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD stock_detail_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail', 'cargo_space_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD cargo_space_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail', 'cargo_space_name') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD cargo_space_name nvarchar(255) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail', 'warehouse_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD warehouse_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail', 'warehouse_name') IS NULL
ALTER TABLE dbo.pd_stockpd_detail ADD warehouse_name nvarchar(255) NULL;
IF OBJECT_ID('dbo.pd_stockpd_detail_finish', 'U') IS NOT NULL
BEGIN
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'stock_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD stock_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'stock_detail_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD stock_detail_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'cargo_space_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD cargo_space_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'cargo_space_name') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD cargo_space_name nvarchar(255) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'warehouse_id') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD warehouse_id nvarchar(80) NULL;
IF COL_LENGTH('dbo.pd_stockpd_detail_finish', 'warehouse_name') IS NULL
ALTER TABLE dbo.pd_stockpd_detail_finish ADD warehouse_name nvarchar(255) NULL;
END;
EXEC(N'
UPDATE pdd
SET pdd.stock_id = bsd.stock_id,
pdd.stock_detail_id = bsd.detail_id,
pdd.cargo_space_id = bs.cargo_space_id,
pdd.cargo_space_name = bcs.cargo_space_name,
pdd.warehouse_id = bs.warehouse_id,
pdd.warehouse_name = ISNULL(NULLIF(bs.warehouse_name, ''''), bw.warehouse_name)
FROM dbo.pd_stockpd_detail pdd
INNER JOIN dbo.bas_stock bs ON bs.tray_code = pdd.tray_code
INNER JOIN dbo.bas_stock_detail bsd ON bsd.stock_id = bs.stock_id AND bsd.product_id = pdd.product_id
LEFT JOIN dbo.bas_cargo_space bcs ON bcs.cargo_space_id = bs.cargo_space_id
LEFT JOIN dbo.bas_warehouse bw ON bw.warehouse_id = bs.warehouse_id
WHERE (pdd.stock_detail_id IS NULL OR pdd.stock_detail_id = '''')
AND ISNULL(bsd.quantity, 0) > 0
');
IF OBJECT_ID('dbo.pd_stockpd_detail_finish', 'U') IS NOT NULL
BEGIN
EXEC(N'
UPDATE pdd
SET pdd.stock_id = bsd.stock_id,
pdd.stock_detail_id = bsd.detail_id,
pdd.cargo_space_id = bs.cargo_space_id,
pdd.cargo_space_name = bcs.cargo_space_name,
pdd.warehouse_id = bs.warehouse_id,
pdd.warehouse_name = ISNULL(NULLIF(bs.warehouse_name, ''''), bw.warehouse_name)
FROM dbo.pd_stockpd_detail_finish pdd
INNER JOIN dbo.bas_stock bs ON bs.tray_code = pdd.tray_code
INNER JOIN dbo.bas_stock_detail bsd ON bsd.stock_id = bs.stock_id AND bsd.product_id = pdd.product_id
LEFT JOIN dbo.bas_cargo_space bcs ON bcs.cargo_space_id = bs.cargo_space_id
LEFT JOIN dbo.bas_warehouse bw ON bw.warehouse_id = bs.warehouse_id
WHERE (pdd.stock_detail_id IS NULL OR pdd.stock_detail_id = '''')
AND ISNULL(bsd.quantity, 0) > 0
');
END;
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_pd_stockpd_detail_stock_detail_id' AND object_id = OBJECT_ID('dbo.pd_stockpd_detail'))
EXEC(N'CREATE INDEX IX_pd_stockpd_detail_stock_detail_id ON dbo.pd_stockpd_detail(stock_detail_id)');
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_pd_stockpd_detail_stock_id' AND object_id = OBJECT_ID('dbo.pd_stockpd_detail'))
EXEC(N'CREATE INDEX IX_pd_stockpd_detail_stock_id ON dbo.pd_stockpd_detail(stock_id)');
IF OBJECT_ID('dbo.pd_stockpd_detail_finish', 'U') IS NOT NULL
BEGIN
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_pd_stockpd_detail_finish_stock_detail_id' AND object_id = OBJECT_ID('dbo.pd_stockpd_detail_finish'))
EXEC(N'CREATE INDEX IX_pd_stockpd_detail_finish_stock_detail_id ON dbo.pd_stockpd_detail_finish(stock_detail_id)');
END;