179 lines
5.6 KiB
Vue
179 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { reactive, ref, onMounted } from 'vue'
|
|
import { useInventory } from '@/modules/inventory/composables/useInventory'
|
|
import { apiRequest } from '@/shared/api'
|
|
import { addNotification, handleApiError } from '@/shared/notification'
|
|
|
|
const { state, loadInventory, ensureStockBaseData } = useInventory()
|
|
|
|
const showModal = ref(false)
|
|
const editingItem = ref<any>(null)
|
|
const saving = ref(false)
|
|
const form = reactive({
|
|
product_id: null as number | null,
|
|
warehouse_id: null as number | null,
|
|
quantity: 0,
|
|
locked_quantity: 0,
|
|
batch_no: '',
|
|
location: ''
|
|
})
|
|
|
|
function openCreate() {
|
|
ensureStockBaseData()
|
|
editingItem.value = null
|
|
form.product_id = null
|
|
form.warehouse_id = null
|
|
form.quantity = 0
|
|
form.locked_quantity = 0
|
|
form.batch_no = ''
|
|
form.location = ''
|
|
showModal.value = true
|
|
}
|
|
|
|
function openEdit(item: any) {
|
|
ensureStockBaseData()
|
|
editingItem.value = item
|
|
form.product_id = item.product_id ?? null
|
|
form.warehouse_id = item.warehouse_id ?? null
|
|
form.quantity = item.quantity ?? 0
|
|
form.locked_quantity = item.locked_quantity ?? 0
|
|
form.batch_no = item.batch_no ?? ''
|
|
form.location = item.location ?? ''
|
|
showModal.value = true
|
|
}
|
|
|
|
async function save() {
|
|
if (saving.value) return
|
|
if (!form.product_id) {
|
|
addNotification('请选择物料', 'warning')
|
|
return
|
|
}
|
|
if (!form.warehouse_id) {
|
|
addNotification('请选择仓库', 'warning')
|
|
return
|
|
}
|
|
if (form.quantity < 0) {
|
|
addNotification('数量不能为负数', 'warning')
|
|
return
|
|
}
|
|
if (form.locked_quantity > form.quantity) {
|
|
addNotification('锁定数量不能大于库存数量', 'warning')
|
|
return
|
|
}
|
|
saving.value = true
|
|
try {
|
|
const payload = {
|
|
product_id: form.product_id,
|
|
warehouse_id: form.warehouse_id,
|
|
quantity: form.quantity,
|
|
locked_quantity: form.locked_quantity,
|
|
batch_no: form.batch_no || null,
|
|
location: form.location || null
|
|
}
|
|
if (editingItem.value) {
|
|
await apiRequest(`/api/inventory/${editingItem.value.id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(payload)
|
|
})
|
|
addNotification('物料库存更新成功', 'success')
|
|
} else {
|
|
await apiRequest('/api/inventory', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
})
|
|
addNotification('物料库存已创建', 'success')
|
|
}
|
|
showModal.value = false
|
|
loadInventory()
|
|
} catch (e) {
|
|
handleApiError(e, '保存物料库存')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
async function deleteItem(id: number) {
|
|
if (!confirm('确定要删除这个物料库存记录吗?')) return
|
|
try {
|
|
await apiRequest(`/api/inventory/${id}`, { method: 'DELETE' })
|
|
addNotification('物料库存已删除', 'success')
|
|
loadInventory()
|
|
} catch (e) {
|
|
handleApiError(e, '删除物料库存')
|
|
}
|
|
}
|
|
onMounted(() => { loadInventory() })
|
|
</script>
|
|
|
|
<template>
|
|
<div class="inventory-tab">
|
|
<div class="table-header">
|
|
<t-button type="primary" @click="openCreate">+ 新增物料库存</t-button>
|
|
</div>
|
|
<t-table :data="state.inventory" style="width: 100%" :loading="state.loading" empty-text="暂无库存数据">
|
|
<t-table-column prop="product_sku" label="SKU" />
|
|
<t-table-column prop="product_name" label="物料" />
|
|
<t-table-column prop="warehouse_name" label="仓库" />
|
|
<t-table-column prop="quantity" label="数量" />
|
|
<t-table-column prop="available_quantity" label="可用" />
|
|
<t-table-column label="操作" width="180">
|
|
<template #default="{ row }">
|
|
<t-button size="small" @click="openEdit(row)">编辑</t-button>
|
|
<t-button size="small" type="danger" @click="deleteItem(row.id)">删除</t-button>
|
|
</template>
|
|
</t-table-column>
|
|
</t-table>
|
|
|
|
<t-dialog v-model="showModal" :title="editingItem ? '编辑物料库存' : '新增物料库存'" width="520px" :close-on-click-modal="false">
|
|
<t-form :model="form" label-width="80px">
|
|
<t-form-item label="物料" required>
|
|
<t-select v-model="form.product_id" placeholder="请选择物料" style="width: 100%">
|
|
<t-option
|
|
v-for="m in state.materials"
|
|
:key="m.id"
|
|
:label="m.name + (m.sku ? ' (' + m.sku + ')' : '')"
|
|
:value="m.id"
|
|
/>
|
|
</t-select>
|
|
</t-form-item>
|
|
<t-form-item label="仓库" required>
|
|
<t-select v-model="form.warehouse_id" placeholder="请选择仓库" style="width: 100%">
|
|
<t-option
|
|
v-for="w in state.warehouses"
|
|
:key="w.id"
|
|
:label="w.name"
|
|
:value="w.id"
|
|
/>
|
|
</t-select>
|
|
</t-form-item>
|
|
<t-form-item label="数量" required>
|
|
<t-input-number v-model="form.quantity" :min="0" style="width: 100%" />
|
|
</t-form-item>
|
|
<t-form-item label="锁定数量">
|
|
<t-input-number v-model="form.locked_quantity" :min="0" style="width: 100%" />
|
|
</t-form-item>
|
|
<t-form-item label="批次号">
|
|
<t-input v-model="form.batch_no" placeholder="请输入批次号" />
|
|
</t-form-item>
|
|
<t-form-item label="库位">
|
|
<t-input v-model="form.location" placeholder="请输入库位" />
|
|
</t-form-item>
|
|
</t-form>
|
|
<template #footer>
|
|
<t-button @click="showModal = false" :disabled="saving">取消</t-button>
|
|
<t-button type="primary" :loading="saving" :disabled="saving" @click="save">保存</t-button>
|
|
</template>
|
|
</t-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.inventory-tab {
|
|
padding: 0;
|
|
}
|
|
|
|
.table-header {
|
|
margin-bottom: 16px;
|
|
}
|
|
</style>
|