Commit 3bbd5f32 by qinjianhui

Merge branch 'dev_product_scan' into 'dev'

Dev product scan

See merge request !211
parents 20b5f95f 086a2552
......@@ -54,7 +54,6 @@ declare module 'vue' {
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
......
......@@ -535,6 +535,14 @@ export function completeDeliveryApi(ids: (number | string)[]) {
)
}
// 生产扫码
export function productionScanApi(id: number | string, status: string) {
return axios.get<never, BaseRespData<never>>(
'factory/podOrderOperation/scan-produce',
{ params: { id, status } },
)
}
export function applyForProductByIdApi(id: string, url: string) {
return axios.get<never, BaseRespData<operateOrderListData[]>>(url, {
params: { podProductId: id },
......@@ -550,6 +558,13 @@ export function getByOperationNoLogApi(operationNo: string) {
)
}
// 校验库存
export function checkInventoryApi(id: number | string, status: string) {
return axios.get<never, BaseRespData<never>>(
'factory/podOrderOperation/check-inventory',
{ params: { id, status } },
)
}
export function listByNoPodOrderApi(params: {
type: string
no: string
......
......@@ -45,7 +45,11 @@
</div>
<div class="right">
<div
v-if="['fastProduction', 'fastReceipt'].includes(fastKey)"
v-if="
['fastProduction', 'fastReceipt', 'productionScan'].includes(
fastKey,
)
"
class="input"
>
<el-input
......@@ -70,10 +74,7 @@
{{ detail?.userMark }}
</div>
</div>
<div
:title="String(detail?.factoryOrderNumber)"
class="div-item"
>
<div :title="String(detail?.factoryOrderNumber)" class="div-item">
<span class="div-item-label" style="font-size: 18px"
>订单号:</span
>
......@@ -145,8 +146,9 @@
>
<span class="div-item-label">挂起前状态:</span>
<div class="div-item-value">
<ElTag v-if="detail.statusName" type="primary">{{ detail.statusName || '' }}</ElTag
>
<ElTag v-if="detail.statusName" type="primary">{{
detail.statusName || ''
}}</ElTag>
</div>
</div>
<div
......@@ -237,10 +239,13 @@
<div class="btn">
<div
:style="{
visibility:
fastKey === 'fastProduction' || fastKey === 'fastReceipt'
? 'visible'
: 'hidden',
visibility: [
'fastProduction',
'fastReceipt',
'productionScan',
].includes(fastKey)
? 'visible'
: 'hidden',
}"
class="btn-sure"
>
......@@ -250,10 +255,19 @@
type="success"
@click="changeStatus"
>
{{ fastKey === 'fastProduction' ? '生产完成' : '快捷入库' }}
{{
['fastProduction', 'productionScan'].includes(fastKey)
? '生产完成'
: '快捷入库'
}}
</el-button>
<div class="check">
<el-checkbox v-model="isAutoSure"> 自动完成上一单 </el-checkbox>
<el-checkbox
v-if="fastKey !== 'productionScan'"
v-model="isAutoSure"
>
自动完成上一单
</el-checkbox>
</div>
</div>
<div v-if="fastKey !== 'fastReceipt'" class="btn-down">
......@@ -311,6 +325,7 @@ import {
downloadMaterialApi,
} from '@/api/podCnOrder'
import { cardImages, PodOrderRes } from '@/types/api/podCnOrder'
import { checkInventoryApi } from '@/api/factoryOrderNew'
import { showConfirm } from '@/utils/ui'
import { filePath } from '@/api/axios'
import { computed, ref, watch } from 'vue'
......@@ -320,7 +335,7 @@ interface HistoryDataItem {
finished: boolean
}
const title = computed(() => {
if (props.fastKey === 'fastProduction') {
if (['fastProduction', 'productionScan'].includes(props.fastKey)) {
return '快捷生产'
} else if (props.fastKey === 'fastReceipt') {
return props.dialogTitle
......@@ -334,6 +349,7 @@ export type FastProductionDetail = PodOrderRes & {
podOrderNo?: string | null
podOrderProductId?: number | null
statusName?: string | null
productMark?: string | null
}
const trackingNumberRef = ref()
const historyData = ref<HistoryDataItem[]>([])
......@@ -384,6 +400,7 @@ const props = withDefaults(
completeApi?: (
ids: number[],
detail: Record<string, unknown>,
trigger?: 'manual' | 'auto',
) => Promise<unknown>
downloadApi?: (
ids: number[],
......@@ -394,6 +411,10 @@ const props = withDefaults(
pendingOrderLabel?: string
pleaseScanTip?: string
searchInputAudioTip?: string
/** 是否限制扫码 */
isLimitScan?: boolean
/** 限制扫码的提示 */
limitScanMessage?: string
dialogTitle?: string
}>(),
{
......@@ -410,7 +431,8 @@ const props = withDefaults(
notFoundMessage: '生产单不存在',
pendingOrderLabel: '生产单号',
pleaseScanTip: '请扫码生产单号',
searchInputAudioTip: '请录入生产单号',
searchInputAudioTip: '请录入生产单号???',
isLimitScan: false,
queryApi: (orderNumber: string) => getSubOrderBySubOrderNumber(orderNumber),
completeApi: (ids: number[], detailData: Record<string, unknown>) =>
productionQueryApi(ids[0], Number(detailData.podJomallOrderCnId || -1)),
......@@ -505,7 +527,9 @@ watch(
const len = historyData.value
if (
len.length > 0 &&
(props.fastKey === 'fastProduction' || props.fastKey === 'fastReceipt')
['fastProduction', 'fastReceipt', 'productionScan'].includes(
props.fastKey,
)
) {
confirmQuery(len, 0)
}
......@@ -537,7 +561,9 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => {
const el = len[i]
showConfirm(
`${props.pendingOrderLabel} ${el.orderNumber} ${
props.fastKey === 'fastProduction' ? '未生产' : '未入库'
['fastProduction', 'productionScan'].includes(props.fastKey)
? '未生产'
: '未入库'
}完成,取消则不提醒?`,
{
confirmButtonText: '确定',
......@@ -549,7 +575,13 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => {
TrackingNumber.value = el.orderNumber
await trackCodeInput()
await setData(el.orderNumber)
ElMessage.success(`${props.fastKey === 'fastProduction' ? '生产完成' : '入库完成'}`)
ElMessage.success(
`${
['fastProduction', 'productionScan'].includes(props.fastKey)
? '生产完成'
: '入库完成'
}`,
)
if (len[i + 1]) {
confirmQuery(len, i + 1)
}
......@@ -576,21 +608,32 @@ const changeStatus = async () => {
if (!detail.value || Object.keys(detail.value).length <= 1) {
return ElMessage.warning(props.pleaseScanTip)
}
showConfirm(`确定${props.fastKey === 'fastProduction' ? '生产完成' : '入库完成'}?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
showConfirm(
`确定${
['fastProduction', 'productionScan'].includes(props.fastKey)
? '生产完成'
: '入库完成'
}?`,
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
).then(() => {
setData(historyKeyFromDetail(detail.value))
})
}
const setData = async (orderNumber: string) => {
const setData = async (
orderNumber: string,
trigger: 'manual' | 'auto' = 'manual',
) => {
if (!detail.value || detail.value?.id === -1) return
try {
const id = detail.value.id
const res = (await props.completeApi(
[id],
detail.value as unknown as Record<string, unknown>,
trigger,
)) as unknown as BaseRespData<
{
factoryOrderNumber?: number
......@@ -614,16 +657,43 @@ const setData = async (orderNumber: string) => {
}
emit('onSuccess', res.data)
playAudio('weight_success')
detail.value = {
id: -1,
podJomallOrderCnId: -1,
imgList: [] as cardImages[],
if (trigger === 'auto') {
// new 快捷生产:扫码后直接将订单流转至生产中
localStorage.setItem(props.historyStorageKey, JSON.stringify([]))
} else {
detail.value = {
id: -1,
podJomallOrderCnId: -1,
imgList: [] as cardImages[],
}
}
TrackingNumber.value = ''
isDownloadImage.value = false
trackingNumberRef.value && trackingNumberRef.value.focus()
} catch (e) {
console.error(e)
const err = e as BaseRespData<{
inventory: number
producingQuantity: number
availableInventory: number
thirdSkuCode?: string
}>
if (err?.code === 301) {
await ElMessageBox.alert(
`${err.data?.thirdSkuCode}库存不足,无法进入生产!请联系仓库管理人员核对库存。<br/>
库存数量:${err.data?.inventory},生产中的数量:${err.data?.producingQuantity}
<span style="color: red">可调配库存:${err.data?.availableInventory}</span>`,
'提示',
{
type: 'warning',
dangerouslyUseHTMLString: true,
},
).then(() => {
localStorage.setItem(props.historyStorageKey, JSON.stringify([]))
})
}
detail.value = {
id: -1,
podJomallOrderCnId: -1,
......@@ -725,9 +795,10 @@ const trackCodeInput = async () => {
const orderNumber = TrackingNumber.value
if (isAutoSure.value) {
if (!['productionScan'].includes(props.fastKey) && isAutoSure.value) {
await setData(
historyData.value[historyData.value.length - 1].orderNumber || '',
'auto',
)
}
......@@ -738,9 +809,67 @@ const trackCodeInput = async () => {
}
detail.value = normalizeDetail(res.data)
// 扫码后限制
if (props.isLimitScan) {
if (
detail.value.productMark &&
['custom_normal', 'normal'].includes(detail.value.productMark)
) {
detail.value = { id: -1, podJomallOrderCnId: -1, imgList: [] }
localStorage.setItem(props.historyStorageKey, JSON.stringify([]))
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
return showConfirm(props.limitScanMessage || '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
}
}
if (isDownloadImage.value) {
download()
}
// 工厂订单new 校验库存 生产扫码
if (props.fastKey === 'productionScan') {
try {
await checkInventoryApi(
detail.value.id,
String(detail.value.status ?? ''),
)
} catch (inventoryErr) {
const err = inventoryErr as BaseRespData<{
inventory: number
producingQuantity: number
availableInventory: number
thirdSkuCode?: string
}>
if (err?.code === 301) {
historyData.value = []
localStorage.setItem(props.historyStorageKey, JSON.stringify([]))
detail.value = emptyDetail()
playAudio('weight_search_error', props.searchInputAudioTip)
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
await ElMessageBox.alert(
`${err.data?.thirdSkuCode}库存不足,无法进入生产!请联系仓库管理人员核对库存。<br/>
库存数量:${err.data?.inventory},生产中的数量:${err.data?.producingQuantity}
<span style="color: red">可调配库存:${err.data?.availableInventory}</span>`,
'提示',
{
type: 'warning',
dangerouslyUseHTMLString: true,
},
)
return
}
throw inventoryErr
}
await setData(detail.value.id.toString(), 'auto')
}
playAudio('weight_search_success')
trackingNumberRef.value && trackingNumberRef.value.focus()
......
<template>
<div class="page card h-100 flex-gap-10 overflow-hidden flex">
<div class="order-status">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="['ALL']"
:highlight-current="true"
node-key="status"
:data="statusTree"
:props="{ children: 'children', label: 'statusName' }"
class="status-tree"
@node-click="handleStatusNodeClick"
>
<template #default="{ node, data }">
<div class="tree-node">
<div class="tree-node-label-wrapper">
<span class="tree-node-label">{{ data.statusName }}</span>
<div
class="order-status-wrapper"
:class="{ 'is-collapsed': statusSidebarCollapsed }"
>
<div class="order-status">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="['ALL']"
:highlight-current="true"
node-key="status"
:data="statusTree"
:props="{ children: 'children', label: 'statusName' }"
class="status-tree"
@node-click="handleStatusNodeClick"
>
<template #default="{ node, data }">
<div class="tree-node">
<div class="tree-node-label-wrapper">
<span class="tree-node-label">{{ data.statusName }}</span>
<span
v-if="
data.status !== 'ALL' &&
(data.quantity || data.quantity === 0)
"
class="tree-node-count"
>
{{ `(${data.quantity})` }}
</span>
</div>
<span
v-if="
data.status !== 'ALL' &&
(data.quantity || data.quantity === 0)
"
class="tree-node-count"
v-if="data.children && data.children.length"
class="tree-node-expand"
@click.stop="toggleExpand(node)"
>
{{ `(${data.quantity})` }}
<el-icon
><ArrowUp v-if="node.expanded" /><ArrowDown v-else
/></el-icon>
</span>
</div>
<span
v-if="data.children && data.children.length"
class="tree-node-expand"
@click.stop="toggleExpand(node)"
>
<el-icon
><ArrowUp v-if="node.expanded" /><ArrowDown v-else
/></el-icon>
</span>
</div>
</template>
</ElTree>
</template>
</ElTree>
</div>
<div
class="sidebar-toggle"
title="收起/展开"
@click="toggleStatusSidebar"
>
<span
class="sidebar-toggle-icon"
:class="{ 'is-collapsed': statusSidebarCollapsed }"
/>
</div>
</div>
<div class="order-content flex-1 flex-column overflow-hidden">
<div v-if="!isSpecialLayout" class="header">
......@@ -551,6 +566,7 @@
>拣胚完成</ElButton
>
</span>
<span v-if="status === 'PENDING_PICK'" class="item">
<ElButton type="danger" @click="handlePickFail">拣胚失败</ElButton>
</span>
......@@ -566,7 +582,7 @@
</span>
<span v-if="status === 'PENDING_PACKING'" class="item">
<ElButton type="success" @click="handleSeedingWall"
>播种墙配货</ElButton
>配货打单</ElButton
>
</span>
<span v-if="status === 'PENDING_PACKING'" class="item">
......@@ -583,8 +599,8 @@
>申请补胚</ElButton
>
</span>
<span v-if="status === 'IN_PRODUCTION'" class="item">
<ElButton type="success" @click="handleQuickProduction"
<span v-if="['IN_PRODUCTION'].includes(status)" class="item">
<ElButton type="success" @click="handleQuickProduction()"
>快捷生产</ElButton
>
</span>
......@@ -992,12 +1008,14 @@
:fast-key="fastKey"
:default-auto-sure="true"
:show-operation-no-row="true"
:is-limit-scan="isLimitScan"
limit-scan-message="G和CB类的操作单无效操作"
not-found-message="操作单不存在"
pending-order-label="操作单号"
please-scan-tip="请扫码操作单号"
search-input-audio-tip="请录入操作单号"
history-storage-key="historyFactoryOrderNewData"
tracking-placeholder="扫描枪输入操作单号,录入下一单本单自动生产完成,最后一单扫两次完成生产"
tracking-placeholder="扫描枪输入操作单号"
:query-api="getOperationByNo"
:complete-api="completeOperationById"
:download-api="downloadOperationById"
......@@ -1177,6 +1195,7 @@ import {
updateReceiverAddressApi,
updateRemarkApi,
completeDeliveryApi,
productionScanApi,
getByOperationNoLogApi,
listByNoPodOrderApi,
orderWeighingPodOrderApi,
......@@ -1228,6 +1247,11 @@ import { ElTag } from 'element-plus'
const resultInfo = ref<ResultInfoDataItem[]>([])
const resultRefs = ref()
const statusSidebarCollapsed = ref(false)
const toggleStatusSidebar = () => {
statusSidebarCollapsed.value = !statusSidebarCollapsed.value
}
const batchManageRef = ref<InstanceType<typeof BatchManageTable>>()
const waitingRestockRef = ref<InstanceType<typeof WaitingRestockTable>>()
const cardLayoutRef = ref<InstanceType<typeof CardLayout>>()
......@@ -2857,10 +2881,25 @@ const handleReplenishFail = () => {
}
const getOperationByNo = (operationNo: string) =>
getByOperationNoLogApi(operationNo) as Promise<{ data?: unknown }>
const completeOperationById = (ids: number[]) => completeDeliveryApi(ids)
const completeOperationById = (
ids: number[],
detailData?: Record<string, unknown>,
trigger: 'manual' | 'auto' = 'manual',
) => {
if (trigger === 'auto') {
const detailStatus = String(detailData?.status ?? '')
return productionScanApi(ids[0], detailStatus)
}
return completeDeliveryApi(ids)
}
const downloadOperationById = (ids: number[]) =>
downloadOperationMaterialApi(ids)
const handleFastProductionSuccess = (data: ResultInfoDataItem[]) => {
if (fastKey.value === 'productionScan') {
ElMessage.success('操作成功')
return
}
const result = data[0]
if (!result.status) {
ElMessageBox.alert(
......@@ -2871,6 +2910,8 @@ const handleFastProductionSuccess = (data: ResultInfoDataItem[]) => {
},
)
return
} else {
ElMessage.success('操作成功')
}
}
const createLogisticsSuccess = (data: ResultInfoDataItem[]) => {
......@@ -2889,6 +2930,7 @@ const fastClose = () => {
refreshCurrentView({ isRefreshTree: true })
}
const handleViewDetail = async (item: operateOrderListData) => {
isLimitScan.value = false
const loading = ElLoading.service({
fullscreen: true,
text: '加载中...',
......@@ -2906,9 +2948,11 @@ const handleViewDetail = async (item: operateOrderListData) => {
loading.close()
}
}
const isLimitScan = ref(false)
const handleQuickProduction = () => {
detailVisible.value = true
fastKey.value = 'fastProduction'
fastKey.value = 'productionScan'
isLimitScan.value = true
detailData.value = {}
}
const handleSinglePrint = () => {
......@@ -3033,8 +3077,140 @@ onMounted(() => {
flex-direction: column;
gap: 2px;
}
.order-status-wrapper {
position: relative;
flex-shrink: 0;
width: 180px;
transition: width 0.3s ease;
&.is-collapsed {
width: 0;
.order-status {
opacity: 0;
visibility: hidden;
}
}
}
.sidebar-toggle {
position: absolute;
top: 50%;
right: -12px;
transform: translateY(-50%);
z-index: 10;
width: 12px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid #dcdfe6;
border-left: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
box-shadow: 1px 0 4px rgba(0, 0, 0, 0.06);
user-select: none;
&:hover {
background: #f5f7fa;
.sidebar-toggle-icon {
border-right-color: #409eff;
&.is-collapsed {
border-right-color: transparent;
border-left-color: #409eff;
}
}
}
}
.sidebar-toggle-icon {
display: block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-right: 6px solid #909399;
transition: border-color 0.2s;
&.is-collapsed {
border-right: none;
border-left: 6px solid #909399;
}
}
.order-status-wrapper {
position: relative;
flex-shrink: 0;
width: 180px;
transition: width 0.3s ease;
&.is-collapsed {
width: 0;
.order-status {
opacity: 0;
visibility: hidden;
}
}
}
.sidebar-toggle {
position: absolute;
top: 50%;
right: -12px;
transform: translateY(-50%);
z-index: 10;
width: 12px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid #dcdfe6;
border-left: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
box-shadow: 1px 0 4px rgba(0, 0, 0, 0.06);
user-select: none;
&:hover {
background: #f5f7fa;
.sidebar-toggle-icon {
border-right-color: #409eff;
&.is-collapsed {
border-right-color: transparent;
border-left-color: #409eff;
}
}
}
}
.sidebar-toggle-icon {
display: block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-right: 6px solid #909399;
transition: border-color 0.2s;
&.is-collapsed {
border-right: none;
border-left: 6px solid #909399;
}
}
.order-status {
width: 180px;
height: 100%;
overflow: auto;
transition: opacity 0.3s ease;
:deep(.el-tree-node__content) {
height: 30px;
......
......@@ -13,7 +13,7 @@
>
<template #header>
<div class="title">
<span>播种墙配货</span>
<span>{{ isNewOrder ? '配货打单' : '播种墙配货' }}</span>
<span v-if="socketConnect === 'online'" class="online">[在线]</span>
<span v-else class="offline">[离线]</span>
<ElButton
......@@ -298,6 +298,7 @@ import socket from '@/utils/websocket'
import { WarehouseListData } from '@/types/index'
import { filePath } from '@/api/axios.ts'
import { ElButton, ElIcon, ElTag } from 'element-plus'
import { BaseRespData } from '@/types/api'
const { getCLodop } = useLodop()
......@@ -803,6 +804,7 @@ const getPackingData = async (code: string) => {
}
}
const res = await getPackingDataApi(url, params)
if (res.code !== 200) {
ElMessage.warning(res.message)
isLock.value = false
......@@ -839,7 +841,24 @@ const getPackingData = async (code: string) => {
// }
} catch (error) {
console.log(error)
const err = error as BaseRespData<{
inventory: number
producingQuantity: number
availableInventory: number
thirdSkuCode?: string
}>
if (props.isNewOrder && err?.code === 301) {
loading.close()
await ElMessageBox.alert(
`${err.data?.thirdSkuCode}库存不足,无法进入生产!请联系仓库管理人员核对库存。<br/>
库存数量:${err.data?.inventory},生产中的数量:${err.data?.producingQuantity}<span style="color: red">可调配库存:${err.data?.availableInventory}</span>`,
'提示',
{
type: 'warning',
dangerouslyUseHTMLString: true,
},
)
}
} finally {
isLock.value = false
productionOrder.value = ''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment