|
|
|
|
@ -16,7 +16,7 @@
|
|
|
|
|
<view class="card-info-right">
|
|
|
|
|
<view class="info-item"><text class="info-label">备件名称:</text><text class="info-name">{{ textValue(sparepart.name) }}</text></view>
|
|
|
|
|
<view class="info-item"><text class="info-label">规格:</text><text class="info-value">{{ textValue(sparepart.standard || sparepart.deviceSpec) }}</text></view>
|
|
|
|
|
<view class="info-item"><text class="info-label">当前库存:</text><text class="info-value stock-highlight">{{ sparepart.count != null ? sparepart.count : 0 }}{{ textUnit(sparepart.unitName || sparepart.minStockUnitName || '个') }}</text></view>
|
|
|
|
|
<view class="info-item"><text class="info-label">当前总库存:</text><text class="info-value stock-highlight">{{ sparepart.count != null ? sparepart.count : 0 }}{{ textUnit(sparepart.unitName || sparepart.minStockUnitName || '个') }}</text></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="card-bottom">
|
|
|
|
|
@ -94,43 +94,6 @@
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 出库数量 -->
|
|
|
|
|
<view class="section-title-bar" style="padding-top: 24rpx;">
|
|
|
|
|
<view class="section-bar-line"></view><text class="section-title">出库数量</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="qty-input-card">
|
|
|
|
|
<view class="form-field">
|
|
|
|
|
<text class="form-label">出库数量</text>
|
|
|
|
|
<input v-model="outboundQty" class="form-input" placeholder="请输入" confirm-type="done" />
|
|
|
|
|
<text class="form-suffix-text">单位:{{ textValue(sparepart.purchaseUnitName) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="convert-row">
|
|
|
|
|
<text class="convert-label-inline">出库后剩余库存:</text>
|
|
|
|
|
<text :class="['convert-value-inline', { 'text-danger': stockExceeded }]">{{ remainingStock }}</text>
|
|
|
|
|
<text class="convert-unit-inline">{{ stockUnitLabel(sparepart) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="convert-helper">{{ currentStockText }}</view>
|
|
|
|
|
<view class="convert-formula-inline">{{ outboundQty || 0 }}{{ textValue(sparepart.purchaseUnitName) }} × {{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(sparepart) }}</view>
|
|
|
|
|
<view v-if="stockExceeded" class="stock-warning">
|
|
|
|
|
<text class="warning-icon">⚠</text>
|
|
|
|
|
<view class="warning-text">
|
|
|
|
|
<text class="warning-line">当前库存不足,不能出库</text>
|
|
|
|
|
<text class="warning-line">当前库存:{{ stockCount }}{{ stockUnitLabel(sparepart) }}({{ stockPackText }})</text>
|
|
|
|
|
<text class="warning-line">本次出库:{{ calculatedStock }}{{ stockUnitLabel(sparepart) }}({{ outboundPackText }})</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 供应商 -->
|
|
|
|
|
<view class="section-title-bar" style="padding-top: 24rpx;">
|
|
|
|
|
<view class="section-bar-line"></view><text class="section-title">供应商</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="select-row-card">
|
|
|
|
|
<view class="full-dropdown">
|
|
|
|
|
<text :class="{ placeholder: !defaultSupplierName }">{{ defaultSupplierName || '未配置默认供应商' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 仓库/库区 -->
|
|
|
|
|
<view class="section-title-bar" style="padding-top: 24rpx;">
|
|
|
|
|
<view class="section-bar-line"></view><text class="section-title">仓库/库区</text>
|
|
|
|
|
@ -173,6 +136,44 @@
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 出库数量 -->
|
|
|
|
|
<view class="section-title-bar" style="padding-top: 24rpx;">
|
|
|
|
|
<view class="section-bar-line"></view><text class="section-title">出库数量</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="qty-input-card">
|
|
|
|
|
<view class="form-field">
|
|
|
|
|
<text class="form-label">出库数量</text>
|
|
|
|
|
<input v-model="outboundQty" class="form-input" placeholder="请输入" confirm-type="done" />
|
|
|
|
|
<text class="form-suffix-text">单位:{{ textValue(sparepart.purchaseUnitName) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="!hasWarehouseArea" class="warehouse-hint">请先选择仓库与库区,选择后可查看当前库存</view>
|
|
|
|
|
<view v-if="hasWarehouseArea" class="convert-row">
|
|
|
|
|
<text class="convert-label-inline">出库后当前仓库/库区的剩余库存:</text>
|
|
|
|
|
<text :class="['convert-value-inline', { 'text-danger': stockExceeded }]">{{ remainingStock }}</text>
|
|
|
|
|
<text class="convert-unit-inline">{{ stockUnitLabel(sparepart) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="hasWarehouseArea" class="convert-helper">{{ currentStockText }}</view>
|
|
|
|
|
<view class="convert-formula-inline">{{ outboundQty || 0 }}{{ textValue(sparepart.purchaseUnitName) }} × {{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(sparepart) }}</view>
|
|
|
|
|
<view v-if="stockExceeded" class="stock-warning">
|
|
|
|
|
<text class="warning-icon">⚠</text>
|
|
|
|
|
<view class="warning-text">
|
|
|
|
|
<text class="warning-line">当前仓库/库区的库存不足,不能出库</text>
|
|
|
|
|
<text class="warning-line">当前仓库/库区的库存:{{ stockCount }}{{ stockUnitLabel(sparepart) }}({{ stockPackText }})</text>
|
|
|
|
|
<text class="warning-line">本次出库:{{ calculatedStock }}{{ stockUnitLabel(sparepart) }}({{ outboundPackText }})</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 供应商 -->
|
|
|
|
|
<view class="section-title-bar" style="padding-top: 24rpx;">
|
|
|
|
|
<view class="section-bar-line"></view><text class="section-title">供应商</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="select-row-card">
|
|
|
|
|
<view class="full-dropdown">
|
|
|
|
|
<text :class="{ placeholder: !defaultSupplierName }">{{ defaultSupplierName || '未配置默认供应商' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="bottom-actions">
|
|
|
|
|
@ -190,12 +191,14 @@ import { getDeviceLedgerList } from '@/api/mes/moldoperate'
|
|
|
|
|
import { getWarehouseSimpleList, getWarehouseAreaSimpleList } from '@/api/mes/moldget'
|
|
|
|
|
import { getEquipmentRepairListByDeviceId, getMaintenanceTicketPage } from '@/api/mes/equipment'
|
|
|
|
|
import { getDvRepairPage } from '@/api/mes/dvrepair'
|
|
|
|
|
import { getSparepartDetail, getSparepartStockCount } from '@/api/mes/sparepart'
|
|
|
|
|
import { getSparepartDetail, getSparepartStock, getSparepartStockCount } from '@/api/mes/sparepart'
|
|
|
|
|
|
|
|
|
|
const sparepart = ref({})
|
|
|
|
|
const outboundQty = ref(null)
|
|
|
|
|
const selectedPurpose = ref('repair')
|
|
|
|
|
|
|
|
|
|
const hasWarehouseArea = computed(() => selectedWarehouse.value && selectedArea.value)
|
|
|
|
|
|
|
|
|
|
function setPurpose(value) {
|
|
|
|
|
selectedPurpose.value = value
|
|
|
|
|
selectedDevice.value = null
|
|
|
|
|
@ -237,17 +240,24 @@ const sparepartImage = computed(() => {
|
|
|
|
|
if (Array.isArray(images)) return String(images[0] || '')
|
|
|
|
|
return String(images).split(',')[0]?.trim() || ''
|
|
|
|
|
})
|
|
|
|
|
const stockCount = computed(() => sparepart.value.count ?? 0)
|
|
|
|
|
// 选中仓库/库区的库存量,未选则为 0
|
|
|
|
|
const warehouseAreaStockCount = ref(0)
|
|
|
|
|
const stockCount = computed(() => warehouseAreaStockCount.value)
|
|
|
|
|
const calculatedStock = computed(() => {
|
|
|
|
|
const qty = Number(outboundQty.value) || 0
|
|
|
|
|
const ratio = Number(sparepart.value.purchaseUnitConvertQuantity) || 1
|
|
|
|
|
return qty * ratio
|
|
|
|
|
const result = qty * ratio
|
|
|
|
|
return Number.isFinite(result) ? result : 0
|
|
|
|
|
})
|
|
|
|
|
const remainingStock = computed(() => {
|
|
|
|
|
return Math.max(stockCount.value - calculatedStock.value, 0)
|
|
|
|
|
const sc = Number.isFinite(stockCount.value) ? stockCount.value : 0
|
|
|
|
|
const cs = Number.isFinite(calculatedStock.value) ? calculatedStock.value : 0
|
|
|
|
|
return Math.max(sc - cs, 0)
|
|
|
|
|
})
|
|
|
|
|
const stockExceeded = computed(() => {
|
|
|
|
|
return outboundQty.value > 0 && calculatedStock.value > stockCount.value
|
|
|
|
|
const sc = Number.isFinite(stockCount.value) ? stockCount.value : 0
|
|
|
|
|
const cs = Number.isFinite(calculatedStock.value) ? calculatedStock.value : 0
|
|
|
|
|
return outboundQty.value > 0 && cs > sc
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function formatStockPack(count) {
|
|
|
|
|
@ -263,7 +273,7 @@ function formatStockPack(count) {
|
|
|
|
|
|
|
|
|
|
const currentStockText = computed(() => {
|
|
|
|
|
const text = formatStockPack(stockCount.value)
|
|
|
|
|
return text ? `当前库存:${text}` : ''
|
|
|
|
|
return text ? `当前仓库/库区的库存:${text}` : ''
|
|
|
|
|
})
|
|
|
|
|
const stockPackText = computed(() => {
|
|
|
|
|
return formatStockPack(stockCount.value) || `${stockCount.value}${stockUnitLabel(sparepart)}`
|
|
|
|
|
@ -298,7 +308,7 @@ function handleConfirm() {
|
|
|
|
|
if (stockExceeded.value) {
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: '库存不足',
|
|
|
|
|
content: `当前库存:${stockCount.value}${stockUnitLabel(sparepart)}(${stockPackText.value})\n本次出库:${calculatedStock.value}${stockUnitLabel(sparepart)}(${outboundPackText.value})`,
|
|
|
|
|
content: `当前仓库/库区的库存:${stockCount.value}${stockUnitLabel(sparepart)}(${stockPackText.value})\n本次出库:${calculatedStock.value}${stockUnitLabel(sparepart)}(${outboundPackText.value})`,
|
|
|
|
|
showCancel: false,
|
|
|
|
|
confirmText: '知道了'
|
|
|
|
|
})
|
|
|
|
|
@ -449,19 +459,17 @@ async function loadRepairOrdersById(deviceId) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleWarehouseDropdown() { showWarehouseDropdown.value = !showWarehouseDropdown.value }
|
|
|
|
|
function handleSelectWarehouse(item) { selectedWarehouse.value = item; showWarehouseDropdown.value = false; selectedArea.value = null; areaOptions.value = []; loadAreas(item.value) }
|
|
|
|
|
function handleSelectWarehouse(item) { selectedWarehouse.value = item; showWarehouseDropdown.value = false; selectedArea.value = null; areaOptions.value = []; warehouseAreaStockCount.value = 0; loadAreas(item.value) }
|
|
|
|
|
async function loadWarehouses() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getWarehouseSimpleList()
|
|
|
|
|
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
|
|
|
|
|
warehouseOptions.value = data.map(w => ({ value: w.id, label: w.name || String(w.id || '') }))
|
|
|
|
|
const defaultWh = warehouseOptions.value.find(w => (w.label || '').includes('备件仓'))
|
|
|
|
|
if (defaultWh) { selectedWarehouse.value = defaultWh; loadAreas(defaultWh.value) }
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleAreaDropdown() { if (!selectedWarehouse.value) { uni.showToast({ title: '请先选择仓库', icon: 'none' }); return }; showAreaDropdown.value = !showAreaDropdown.value }
|
|
|
|
|
function handleSelectArea(item) { selectedArea.value = item; showAreaDropdown.value = false }
|
|
|
|
|
function handleSelectArea(item) { selectedArea.value = item; showAreaDropdown.value = false; loadStockCount() }
|
|
|
|
|
async function loadAreas(warehouseId) {
|
|
|
|
|
if (!warehouseId) return
|
|
|
|
|
loadingAreas.value = true
|
|
|
|
|
@ -470,8 +478,6 @@ async function loadAreas(warehouseId) {
|
|
|
|
|
const res = await getWarehouseAreaSimpleList(warehouseId)
|
|
|
|
|
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
|
|
|
|
|
areaOptions.value = data.map(a => ({ value: a.id, label: a.name || a.areaName || String(a.id || '') }))
|
|
|
|
|
const defaultArea = areaOptions.value.find(a => (a.label || '').includes('备件库'))
|
|
|
|
|
if (defaultArea) selectedArea.value = defaultArea
|
|
|
|
|
} catch (e) {} finally { loadingAreas.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -485,7 +491,7 @@ onShow(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getSparepartDetail(selectResult.id)
|
|
|
|
|
const detail = res?.data || res
|
|
|
|
|
if (detail) { sparepart.value = { ...sparepart.value, ...detail }; loadStockCount() }
|
|
|
|
|
if (detail) { sparepart.value = { ...sparepart.value, ...detail }; loadTotalStockCount() }
|
|
|
|
|
} catch (e) { console.error('获取备件详情失败:', e) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -493,11 +499,32 @@ onShow(async () => {
|
|
|
|
|
loadWarehouses()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 加载总库存(已选备件卡片显示,get-count 返回纯数字)
|
|
|
|
|
async function loadTotalStockCount() {
|
|
|
|
|
const id = sparepart.value.id
|
|
|
|
|
if (!id) return
|
|
|
|
|
try {
|
|
|
|
|
const res = await getSparepartStockCount(id)
|
|
|
|
|
sparepart.value.count = (res?.data ?? res) != null ? Number(res?.data ?? res) : 0
|
|
|
|
|
} catch (e) { sparepart.value.count = 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载选中仓库库区的库存(出库数量区块用)
|
|
|
|
|
async function loadStockCount() {
|
|
|
|
|
const id = sparepart.value.id
|
|
|
|
|
if (!id) return
|
|
|
|
|
try { const res = await getSparepartStockCount(id); sparepart.value.count = (res?.data ?? res) != null ? Number(res?.data ?? res) : 0 }
|
|
|
|
|
catch (e) { sparepart.value.count = 0 }
|
|
|
|
|
const whId = selectedWarehouse.value?.value
|
|
|
|
|
const arId = selectedArea.value?.value
|
|
|
|
|
if (!whId || !arId) {
|
|
|
|
|
warehouseAreaStockCount.value = 0
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res = await getSparepartStock(id, whId, arId)
|
|
|
|
|
const detail = res?.data || res
|
|
|
|
|
const count = (detail != null && typeof detail === 'object') ? Number(detail.count || 0) : Number(detail || 0)
|
|
|
|
|
warehouseAreaStockCount.value = Number.isFinite(count) ? count : 0
|
|
|
|
|
} catch (e) { warehouseAreaStockCount.value = 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false })
|
|
|
|
|
@ -519,7 +546,8 @@ onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false
|
|
|
|
|
.info-item { display: flex; align-items: center; margin-bottom: 8rpx; }
|
|
|
|
|
.info-label { font-size: 26rpx; color: #6b7280; flex-shrink: 0; }
|
|
|
|
|
.info-name { font-size: 30rpx; font-weight: 700; color: #0f172a; }
|
|
|
|
|
.info-value { font-size: 26rpx; color: #374151; &.stock-highlight { color: #2563eb; font-weight: 500; } }
|
|
|
|
|
.info-value { font-size: 26rpx; color: #374151; }
|
|
|
|
|
.stock-highlight { font-size: 30rpx; font-weight: 700; color: #1f2937; }
|
|
|
|
|
.card-bottom { margin-top: 20rpx; padding-top: 20rpx; border-top: 1rpx solid #f0f0f0; }
|
|
|
|
|
.detail-row { display: flex; align-items: center; margin-bottom: 14rpx; &:last-child { margin-bottom: 0; } &.two-col { justify-content: space-between; } }
|
|
|
|
|
.detail-col { display: flex; align-items: center; flex: 1; }
|
|
|
|
|
@ -541,6 +569,7 @@ onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false
|
|
|
|
|
.convert-label-inline { font-size: 26rpx; color: #6b7280; }
|
|
|
|
|
.convert-value-inline { font-size: 36rpx; font-weight: 700; color: #1f2937; margin-left: auto; }
|
|
|
|
|
.convert-unit-inline { font-size: 24rpx; color: #64748b; margin-left: 8rpx; }
|
|
|
|
|
.warehouse-hint { margin-top: 16rpx; padding: 16rpx 20rpx; background: #fefce8; border: 1rpx solid #fef08a; border-radius: 10rpx; font-size: 24rpx; color: #a16207; }
|
|
|
|
|
.convert-formula-inline { margin-top: 10rpx; font-size: 22rpx; color: #9ca3af; }
|
|
|
|
|
.convert-helper { margin-top: 8rpx; font-size: 24rpx; color: #9ca3af; }
|
|
|
|
|
.text-danger { color: #dc2626 !important; }
|
|
|
|
|
|