进销存系统重构
This commit is contained in:
+515
-102
@@ -1487,6 +1487,9 @@ const InventoryView = {
|
||||
supplierProductStatement: [],
|
||||
products: [],
|
||||
materials: [],
|
||||
purchaseOrders: [],
|
||||
purchaseWarehouseId: null,
|
||||
purchaseReceiveItems: [],
|
||||
productionOrders: [],
|
||||
productionPlan: null,
|
||||
productionWarehouseId: null,
|
||||
@@ -1503,22 +1506,6 @@ const InventoryView = {
|
||||
form: {}
|
||||
});
|
||||
|
||||
const inboundMovementOptions = [
|
||||
{ value: 'purchase_in', label: '采购入库' },
|
||||
{ value: 'return_from_production', label: '生产退料入库' },
|
||||
{ value: 'outsource_return', label: '外协回库' },
|
||||
{ value: 'finish_in', label: '完工入库' },
|
||||
{ value: 'in', label: '其他入库' }
|
||||
];
|
||||
|
||||
const outboundMovementOptions = [
|
||||
{ value: 'issue_to_production', label: '生产领料出库' },
|
||||
{ value: 'outsource_send', label: '外协发料出库' },
|
||||
{ value: 'shipment_out', label: '销售出库' },
|
||||
{ value: 'scrap_out', label: '报废出库' },
|
||||
{ value: 'out', label: '其他出库' }
|
||||
];
|
||||
|
||||
const getMovementTypeLabel = (movementType) => {
|
||||
const movementLabelMap = {
|
||||
in: '其他入库',
|
||||
@@ -1643,6 +1630,25 @@ const InventoryView = {
|
||||
}
|
||||
};
|
||||
|
||||
const loadPurchaseOrders = async () => {
|
||||
state.loading = true;
|
||||
try {
|
||||
const [orders, warehouses] = await Promise.all([
|
||||
apiRequest('/api/purchase-orders?limit=100'),
|
||||
apiRequest('/api/warehouses')
|
||||
]);
|
||||
state.purchaseOrders = orders || [];
|
||||
state.warehouses = warehouses || [];
|
||||
if (!state.purchaseWarehouseId) {
|
||||
state.purchaseWarehouseId = state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null;
|
||||
}
|
||||
} catch (e) {
|
||||
handleApiError(e, '加载采购订单');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadCustomers = async () => {
|
||||
state.loading = true;
|
||||
try {
|
||||
@@ -1734,6 +1740,7 @@ const InventoryView = {
|
||||
case 'customers': loadCustomers(); break;
|
||||
case 'inventory': loadInventory(); break;
|
||||
case 'movements': loadMovements(); break;
|
||||
case 'purchases': loadPurchaseOrders(); break;
|
||||
case 'production': loadProductionOrders(); break;
|
||||
case 'finance': loadFinance(); break;
|
||||
}
|
||||
@@ -1773,16 +1780,65 @@ const InventoryView = {
|
||||
state.modalType = type;
|
||||
state.editingItem = item;
|
||||
if (item) {
|
||||
state.form = { ...item };
|
||||
if (type === 'salesOrder') {
|
||||
await loadCustomers();
|
||||
await loadProducts();
|
||||
const detail = await apiRequest(`/api/sales-orders/${item.id}`);
|
||||
state.form = {
|
||||
customer_id: detail.customer_id,
|
||||
delivery_date: detail.delivery_date ? new Date(detail.delivery_date).toISOString().slice(0, 16) : '',
|
||||
remark: detail.remark || '',
|
||||
items: (detail.items || []).map(line => ({
|
||||
product_id: line.product_id,
|
||||
quantity: line.quantity,
|
||||
unit_price: line.unit_price,
|
||||
remark: line.remark || ''
|
||||
}))
|
||||
};
|
||||
} else if (type === 'purchaseOrder') {
|
||||
await loadSuppliers();
|
||||
await loadMaterials();
|
||||
const detail = await apiRequest(`/api/purchase-orders/${item.id}`);
|
||||
state.form = {
|
||||
supplier_id: detail.supplier_id,
|
||||
expected_date: detail.expected_date ? new Date(detail.expected_date).toISOString().slice(0, 16) : '',
|
||||
remark: detail.remark || '',
|
||||
items: (detail.items || []).map(line => ({
|
||||
product_id: line.product_id,
|
||||
quantity: line.quantity,
|
||||
unit_price: line.unit_price,
|
||||
remark: line.remark || ''
|
||||
}))
|
||||
};
|
||||
} else if (type === 'purchaseReceive') {
|
||||
await loadWarehouses();
|
||||
const detail = await apiRequest(`/api/purchase-orders/${item.id}`);
|
||||
state.purchaseReceiveItems = (detail.items || [])
|
||||
.map(line => ({
|
||||
item_id: line.id,
|
||||
material_label: `${line.product_sku || line.product_id} - ${line.product_name || ''}`.trim(),
|
||||
remaining_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0),
|
||||
receive_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0)
|
||||
}))
|
||||
.filter(line => line.remaining_quantity > 0);
|
||||
state.form = {
|
||||
warehouse_id: state.purchaseWarehouseId || state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
|
||||
remark: ''
|
||||
};
|
||||
} else {
|
||||
state.form = { ...item };
|
||||
}
|
||||
} else {
|
||||
state.form = {};
|
||||
if (type === 'stockIn' || type === 'stockOut') {
|
||||
if (type === 'inventoryItem') {
|
||||
await ensureStockBaseData();
|
||||
state.form = {
|
||||
product_id: state.materials[0]?.id || null,
|
||||
warehouse_id: state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
|
||||
quantity: 1,
|
||||
movement_type: type === 'stockIn' ? 'purchase_in' : 'issue_to_production'
|
||||
quantity: 0,
|
||||
locked_quantity: 0,
|
||||
batch_number: '',
|
||||
location: ''
|
||||
};
|
||||
}
|
||||
if (type === 'product') {
|
||||
@@ -1795,6 +1851,36 @@ const InventoryView = {
|
||||
sale_price: 0
|
||||
};
|
||||
}
|
||||
if (type === 'salesOrder') {
|
||||
await loadCustomers();
|
||||
await loadProducts();
|
||||
state.form = {
|
||||
customer_id: state.customers[0]?.id || null,
|
||||
delivery_date: '',
|
||||
remark: '',
|
||||
items: [{
|
||||
product_id: state.products.find(p => p.item_type === 'finished')?.id || null,
|
||||
quantity: 1,
|
||||
unit_price: 0,
|
||||
remark: ''
|
||||
}]
|
||||
};
|
||||
}
|
||||
if (type === 'purchaseOrder') {
|
||||
await loadSuppliers();
|
||||
await loadMaterials();
|
||||
state.form = {
|
||||
supplier_id: state.suppliers[0]?.id || null,
|
||||
expected_date: '',
|
||||
remark: '',
|
||||
items: [{
|
||||
product_id: state.materials[0]?.id || null,
|
||||
quantity: 1,
|
||||
unit_price: 0,
|
||||
remark: ''
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
state.showModal = true;
|
||||
};
|
||||
@@ -1804,6 +1890,7 @@ const InventoryView = {
|
||||
state.modalType = '';
|
||||
state.editingItem = null;
|
||||
state.productBomItems = [];
|
||||
state.purchaseReceiveItems = [];
|
||||
state.form = {};
|
||||
};
|
||||
|
||||
@@ -1951,33 +2038,193 @@ const InventoryView = {
|
||||
}
|
||||
};
|
||||
|
||||
const stockIn = async () => {
|
||||
const saveInventoryItem = async () => {
|
||||
try {
|
||||
await apiRequest('/api/stock-movements', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...state.form, movement_type: state.form.movement_type || 'purchase_in' })
|
||||
});
|
||||
addNotification('入库成功', 'success');
|
||||
if (state.editingItem) {
|
||||
await apiRequest(`/api/inventory/${state.editingItem.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
quantity: state.form.quantity,
|
||||
locked_quantity: state.form.locked_quantity,
|
||||
batch_number: state.form.batch_number,
|
||||
location: state.form.location
|
||||
})
|
||||
});
|
||||
addNotification('物料库存更新成功', 'success');
|
||||
} else {
|
||||
await apiRequest('/api/inventory', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(state.form)
|
||||
});
|
||||
addNotification('物料库存创建成功', 'success');
|
||||
}
|
||||
closeModal();
|
||||
loadInventory();
|
||||
loadMovements();
|
||||
} catch (e) {
|
||||
handleApiError(e, '入库操作');
|
||||
handleApiError(e, '保存物料库存');
|
||||
}
|
||||
};
|
||||
|
||||
const stockOut = async () => {
|
||||
const deleteInventoryItem = async (id) => {
|
||||
if (!confirm('确定要删除这个物料库存记录吗?')) return;
|
||||
try {
|
||||
await apiRequest('/api/stock-movements', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...state.form, movement_type: state.form.movement_type || 'issue_to_production' })
|
||||
});
|
||||
addNotification('出库成功', 'success');
|
||||
await apiRequest(`/api/inventory/${id}`, { method: 'DELETE' });
|
||||
addNotification('物料库存已删除', 'success');
|
||||
loadInventory();
|
||||
} catch (e) {
|
||||
handleApiError(e, '删除物料库存');
|
||||
}
|
||||
};
|
||||
|
||||
const addSalesOrderItem = () => {
|
||||
state.form.items = state.form.items || [];
|
||||
state.form.items.push({
|
||||
product_id: state.products.find(p => p.item_type === 'finished')?.id || null,
|
||||
quantity: 1,
|
||||
unit_price: 0,
|
||||
remark: ''
|
||||
});
|
||||
};
|
||||
|
||||
const removeSalesOrderItem = (index) => {
|
||||
state.form.items.splice(index, 1);
|
||||
};
|
||||
|
||||
const saveSalesOrder = async () => {
|
||||
try {
|
||||
if (!state.form.items || !state.form.items.length) {
|
||||
addNotification('请至少添加一个成品明细', 'warning');
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
customer_id: state.form.customer_id,
|
||||
delivery_date: state.form.delivery_date ? new Date(state.form.delivery_date).toISOString() : null,
|
||||
remark: state.form.remark,
|
||||
items: state.form.items
|
||||
};
|
||||
if (state.editingItem) {
|
||||
await apiRequest(`/api/sales-orders/${state.editingItem.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
addNotification('销售订单更新成功', 'success');
|
||||
} else {
|
||||
await apiRequest('/api/sales-orders', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
addNotification('销售订单创建成功并已自动扣减物料', 'success');
|
||||
}
|
||||
closeModal();
|
||||
loadProductionOrders();
|
||||
loadInventory();
|
||||
loadMovements();
|
||||
} catch (e) {
|
||||
handleApiError(e, '出库操作');
|
||||
handleApiError(e, '保存销售订单');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSalesOrder = async (orderId) => {
|
||||
if (!confirm('确定删除这个销售订单吗?系统会自动回补已扣减物料。')) return;
|
||||
try {
|
||||
await apiRequest(`/api/sales-orders/${orderId}`, { method: 'DELETE' });
|
||||
addNotification('销售订单已删除并回补物料', 'success');
|
||||
if (state.productionPlan?.sales_order_id === orderId) {
|
||||
state.productionPlan = null;
|
||||
}
|
||||
loadProductionOrders();
|
||||
loadInventory();
|
||||
loadMovements();
|
||||
} catch (e) {
|
||||
handleApiError(e, '删除销售订单');
|
||||
}
|
||||
};
|
||||
|
||||
const addPurchaseOrderItem = () => {
|
||||
state.form.items = state.form.items || [];
|
||||
state.form.items.push({
|
||||
product_id: state.materials[0]?.id || null,
|
||||
quantity: 1,
|
||||
unit_price: 0,
|
||||
remark: ''
|
||||
});
|
||||
};
|
||||
|
||||
const removePurchaseOrderItem = (index) => {
|
||||
state.form.items.splice(index, 1);
|
||||
};
|
||||
|
||||
const savePurchaseOrder = async () => {
|
||||
try {
|
||||
if (!state.form.items || !state.form.items.length) {
|
||||
addNotification('请至少添加一个物料明细', 'warning');
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
supplier_id: state.form.supplier_id,
|
||||
expected_date: state.form.expected_date ? new Date(state.form.expected_date).toISOString() : null,
|
||||
remark: state.form.remark,
|
||||
items: state.form.items
|
||||
};
|
||||
if (state.editingItem) {
|
||||
await apiRequest(`/api/purchase-orders/${state.editingItem.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
addNotification('采购订单更新成功', 'success');
|
||||
} else {
|
||||
await apiRequest('/api/purchase-orders', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
addNotification('采购订单创建成功', 'success');
|
||||
}
|
||||
closeModal();
|
||||
loadPurchaseOrders();
|
||||
} catch (e) {
|
||||
handleApiError(e, '保存采购订单');
|
||||
}
|
||||
};
|
||||
|
||||
const deletePurchaseOrder = async (orderId) => {
|
||||
if (!confirm('确定删除这个采购订单吗?')) return;
|
||||
try {
|
||||
await apiRequest(`/api/purchase-orders/${orderId}`, { method: 'DELETE' });
|
||||
addNotification('采购订单已删除', 'success');
|
||||
loadPurchaseOrders();
|
||||
} catch (e) {
|
||||
handleApiError(e, '删除采购订单');
|
||||
}
|
||||
};
|
||||
|
||||
const receivePurchaseOrder = async () => {
|
||||
try {
|
||||
if (!state.editingItem?.id) return;
|
||||
const items = (state.purchaseReceiveItems || [])
|
||||
.filter(line => Number(line.receive_quantity) > 0)
|
||||
.map(line => ({
|
||||
item_id: line.item_id,
|
||||
receive_quantity: Number(line.receive_quantity)
|
||||
}));
|
||||
if (!items.length) {
|
||||
addNotification('请填写本次入库数量', 'warning');
|
||||
return;
|
||||
}
|
||||
await apiRequest(`/api/purchase-orders/${state.editingItem.id}/receive`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
warehouse_id: state.form.warehouse_id || state.purchaseWarehouseId,
|
||||
items,
|
||||
remark: state.form.remark || ''
|
||||
})
|
||||
});
|
||||
addNotification('采购到货入库成功', 'success');
|
||||
closeModal();
|
||||
loadPurchaseOrders();
|
||||
loadInventory();
|
||||
loadMovements();
|
||||
} catch (e) {
|
||||
handleApiError(e, '采购到货入库');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2007,14 +2254,22 @@ const InventoryView = {
|
||||
deleteSupplier,
|
||||
saveCustomer,
|
||||
deleteCustomer,
|
||||
stockIn,
|
||||
stockOut,
|
||||
saveInventoryItem,
|
||||
deleteInventoryItem,
|
||||
addSalesOrderItem,
|
||||
removeSalesOrderItem,
|
||||
saveSalesOrder,
|
||||
deleteSalesOrder,
|
||||
addPurchaseOrderItem,
|
||||
removePurchaseOrderItem,
|
||||
savePurchaseOrder,
|
||||
deletePurchaseOrder,
|
||||
receivePurchaseOrder,
|
||||
loadPurchaseOrders,
|
||||
loadProductionOrders,
|
||||
loadOrderProductionPlan,
|
||||
issueOrderMaterials,
|
||||
refreshFinanceByPeriod,
|
||||
inboundMovementOptions,
|
||||
outboundMovementOptions,
|
||||
getMovementTypeLabel,
|
||||
getMovementBadgeClass
|
||||
};
|
||||
@@ -2030,6 +2285,7 @@ const InventoryView = {
|
||||
<button :class="['tab', { active: state.activeTab === 'dashboard' }]" @click="switchTab('dashboard')">仪表盘</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'products' }]" @click="switchTab('products')">产品</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'inventory' }]" @click="switchTab('inventory')">库存</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'purchases' }]" @click="switchTab('purchases')">采购</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'suppliers' }]" @click="switchTab('suppliers')">供应商</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'customers' }]" @click="switchTab('customers')">客户</button>
|
||||
<button :class="['tab', { active: state.activeTab === 'movements' }]" @click="switchTab('movements')">变动记录</button>
|
||||
@@ -2132,18 +2388,18 @@ const InventoryView = {
|
||||
|
||||
<div v-else-if="state.activeTab === 'inventory'">
|
||||
<div class="table-header">
|
||||
<button class="btn btn-primary" @click="openModal('stockIn')">📥 采购/完工入库</button>
|
||||
<button class="btn btn-secondary" @click="openModal('stockOut')" style="margin-left: 8px;">📤 领料/销售出库</button>
|
||||
<button class="btn btn-primary" @click="openModal('inventoryItem')">+ 新增物料库存</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>产品</th>
|
||||
<th>物料</th>
|
||||
<th>仓库</th>
|
||||
<th>数量</th>
|
||||
<th>可用</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -2153,6 +2409,59 @@ const InventoryView = {
|
||||
<td>{{ item.warehouse_name }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.available_quantity }}</td>
|
||||
<td>
|
||||
<div class="action-btns">
|
||||
<button class="btn btn-sm btn-secondary" @click="openModal('inventoryItem', item)">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" @click="deleteInventoryItem(item.id)">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="state.activeTab === 'purchases'">
|
||||
<div class="table-header" style="margin-bottom: 12px;">
|
||||
<button class="btn btn-primary" @click="openModal('purchaseOrder')">+ 新增采购订单</button>
|
||||
</div>
|
||||
<div class="table-container" style="margin-bottom: 16px;">
|
||||
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
||||
<label>到货仓库</label>
|
||||
<select v-model.number="state.purchaseWarehouseId" class="form-input" style="width:260px;">
|
||||
<option v-for="warehouse in state.warehouses" :key="'purchase-warehouse-' + warehouse.id" :value="warehouse.id">
|
||||
{{ warehouse.name }}{{ warehouse.is_default ? ' [默认]' : '' }}
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" @click="loadPurchaseOrders">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>采购单</th>
|
||||
<th>供应商</th>
|
||||
<th>状态</th>
|
||||
<th>总金额</th>
|
||||
<th>已付款</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in state.purchaseOrders" :key="'purchase-order-' + order.id">
|
||||
<td>{{ order.order_no }}</td>
|
||||
<td>{{ order.supplier_name }}</td>
|
||||
<td>{{ order.status }}</td>
|
||||
<td>{{ formatCurrency(order.total_amount || 0) }}</td>
|
||||
<td>{{ formatCurrency(order.paid_amount || 0) }}</td>
|
||||
<td>
|
||||
<div class="action-btns">
|
||||
<button class="btn btn-sm btn-secondary" @click="openModal('purchaseOrder', order)">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" @click="deletePurchaseOrder(order.id)">删除</button>
|
||||
<button class="btn btn-sm btn-primary" @click="openModal('purchaseReceive', order)">到货入库</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -2230,6 +2539,9 @@ const InventoryView = {
|
||||
</div>
|
||||
|
||||
<div v-else-if="state.activeTab === 'production'">
|
||||
<div class="table-header" style="margin-bottom: 12px;">
|
||||
<button class="btn btn-primary" @click="openModal('salesOrder')">+ 新增销售订单</button>
|
||||
</div>
|
||||
<div class="table-container" style="margin-bottom: 16px;">
|
||||
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
||||
<label>领料仓库</label>
|
||||
@@ -2264,8 +2576,9 @@ const InventoryView = {
|
||||
<td>{{ formatCurrency(order.actual_material_cost || 0) }}</td>
|
||||
<td>
|
||||
<div class="action-btns">
|
||||
<button class="btn btn-sm btn-secondary" @click="openModal('salesOrder', order)">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" @click="deleteSalesOrder(order.id)">删除</button>
|
||||
<button class="btn btn-sm btn-secondary" @click="loadOrderProductionPlan(order.id)">领料建议</button>
|
||||
<button class="btn btn-sm btn-primary" @click="issueOrderMaterials(order)">执行领料</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2532,7 +2845,7 @@ const InventoryView = {
|
||||
<div v-if="state.showModal" class="modal-overlay" @click.self="closeModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>{{ state.editingItem ? '编辑' : '新增' }}{{ state.modalType === 'product' ? '产品/物料' : state.modalType === 'productBom' ? '产品BOM' : state.modalType === 'supplier' ? '供应商' : state.modalType === 'customer' ? '客户' : state.modalType === 'stockIn' ? '入库' : '出库' }}</h3>
|
||||
<h3>{{ state.editingItem ? '编辑' : '新增' }}{{ state.modalType === 'product' ? '产品/物料' : state.modalType === 'productBom' ? '产品BOM' : state.modalType === 'inventoryItem' ? '物料库存' : state.modalType === 'salesOrder' ? '销售订单' : state.modalType === 'purchaseOrder' ? '采购订单' : state.modalType === 'purchaseReceive' ? '采购到货入库' : state.modalType === 'supplier' ? '供应商' : '客户' }}</h3>
|
||||
<button class="modal-close" @click="closeModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -2583,6 +2896,153 @@ const InventoryView = {
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form v-else-if="state.modalType === 'salesOrder'" @submit.prevent="saveSalesOrder">
|
||||
<div class="form-group">
|
||||
<label class="form-label">客户 *</label>
|
||||
<select v-model.number="state.form.customer_id" class="form-input" required>
|
||||
<option v-for="customer in state.customers" :key="'order-customer-' + customer.id" :value="customer.id">
|
||||
{{ customer.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">交付日期</label>
|
||||
<input v-model="state.form.delivery_date" type="datetime-local" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">备注</label>
|
||||
<input v-model="state.form.remark" class="form-input" placeholder="订单备注" />
|
||||
</div>
|
||||
<div class="table-header" style="margin-bottom: 8px;">
|
||||
<button type="button" class="btn btn-secondary" @click="addSalesOrderItem">+ 添加成品</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>成品</th>
|
||||
<th>数量</th>
|
||||
<th>单价</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(line, index) in state.form.items" :key="'sales-order-line-' + index">
|
||||
<td>
|
||||
<select v-model.number="line.product_id" class="form-input" required>
|
||||
<option v-for="product in state.products.filter(p => p.item_type === 'finished')" :key="'sales-order-product-' + product.id" :value="product.id">
|
||||
{{ product.sku }} - {{ product.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input v-model.number="line.quantity" type="number" min="1" class="form-input" required /></td>
|
||||
<td><input v-model.number="line.unit_price" type="number" min="0" step="0.01" class="form-input" required /></td>
|
||||
<td><input v-model="line.remark" class="form-input" placeholder="明细备注" /></td>
|
||||
<td><button type="button" class="btn btn-sm btn-danger" @click="removeSalesOrderItem(index)">删除</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">保存订单</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form v-else-if="state.modalType === 'purchaseOrder'" @submit.prevent="savePurchaseOrder">
|
||||
<div class="form-group">
|
||||
<label class="form-label">供应商 *</label>
|
||||
<select v-model.number="state.form.supplier_id" class="form-input" required>
|
||||
<option v-for="supplier in state.suppliers" :key="'purchase-supplier-' + supplier.id" :value="supplier.id">
|
||||
{{ supplier.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">预计到货</label>
|
||||
<input v-model="state.form.expected_date" type="datetime-local" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">备注</label>
|
||||
<input v-model="state.form.remark" class="form-input" placeholder="采购单备注" />
|
||||
</div>
|
||||
<div class="table-header" style="margin-bottom: 8px;">
|
||||
<button type="button" class="btn btn-secondary" @click="addPurchaseOrderItem">+ 添加物料</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>物料</th>
|
||||
<th>数量</th>
|
||||
<th>单价</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(line, index) in state.form.items" :key="'purchase-order-line-' + index">
|
||||
<td>
|
||||
<select v-model.number="line.product_id" class="form-input" required>
|
||||
<option v-for="material in state.materials" :key="'purchase-order-material-' + material.id" :value="material.id">
|
||||
{{ material.sku }} - {{ material.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input v-model.number="line.quantity" type="number" min="1" class="form-input" required /></td>
|
||||
<td><input v-model.number="line.unit_price" type="number" min="0" step="0.01" class="form-input" required /></td>
|
||||
<td><input v-model="line.remark" class="form-input" placeholder="明细备注" /></td>
|
||||
<td><button type="button" class="btn btn-sm btn-danger" @click="removePurchaseOrderItem(index)">删除</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">保存采购单</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form v-else-if="state.modalType === 'purchaseReceive'" @submit.prevent="receivePurchaseOrder">
|
||||
<div class="form-group">
|
||||
<label class="form-label">入库仓库 *</label>
|
||||
<select v-model.number="state.form.warehouse_id" class="form-input" required>
|
||||
<option v-for="warehouse in state.warehouses" :key="'purchase-receive-warehouse-' + warehouse.id" :value="warehouse.id">
|
||||
{{ warehouse.name }}{{ warehouse.is_default ? ' [默认]' : '' }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">备注</label>
|
||||
<input v-model="state.form.remark" class="form-input" placeholder="到货说明" />
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>物料</th>
|
||||
<th>明细ID</th>
|
||||
<th>剩余待入库</th>
|
||||
<th>本次入库</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="line in state.purchaseReceiveItems" :key="'purchase-receive-line-' + line.item_id">
|
||||
<td>{{ line.material_label }}</td>
|
||||
<td>{{ line.item_id }}</td>
|
||||
<td>{{ line.remaining_quantity }}</td>
|
||||
<td><input v-model.number="line.receive_quantity" type="number" min="0" :max="line.remaining_quantity" class="form-input" required /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">确认入库</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form v-else-if="state.modalType === 'productBom'" @submit.prevent="saveProductBom">
|
||||
<div class="table-header" style="margin-bottom: 12px;">
|
||||
<button type="button" class="btn btn-secondary" @click="addBomItem">+ 添加物料</button>
|
||||
@@ -2675,8 +3135,7 @@ const InventoryView = {
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 入库表单 -->
|
||||
<form v-else-if="state.modalType === 'stockIn'" @submit.prevent="stockIn">
|
||||
<form v-else-if="state.modalType === 'inventoryItem'" @submit.prevent="saveInventoryItem">
|
||||
<div class="form-group">
|
||||
<label class="form-label">物料 *</label>
|
||||
<select v-model.number="state.form.product_id" class="form-input" required>
|
||||
@@ -2695,71 +3154,25 @@ const InventoryView = {
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">业务类型 *</label>
|
||||
<select v-model="state.form.movement_type" class="form-input" required>
|
||||
<option v-for="item in inboundMovementOptions" :key="'stockin-type-' + item.value" :value="item.value">{{ item.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">数量 *</label>
|
||||
<input v-model.number="state.form.quantity" type="number" class="form-input" required placeholder="入库数量" />
|
||||
<input v-model.number="state.form.quantity" type="number" class="form-input" required placeholder="库存数量" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">单价</label>
|
||||
<input v-model.number="state.form.unit_price" type="number" step="0.01" class="form-input" placeholder="采购单价" />
|
||||
<label class="form-label">锁定数量</label>
|
||||
<input v-model.number="state.form.locked_quantity" type="number" class="form-input" placeholder="锁定库存" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">备注</label>
|
||||
<input v-model="state.form.remark" class="form-input" placeholder="备注信息" />
|
||||
<label class="form-label">批次号</label>
|
||||
<input v-model="state.form.batch_number" class="form-input" placeholder="批次号(可选)" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">库位</label>
|
||||
<input v-model="state.form.location" class="form-input" placeholder="库位(可选)" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="!state.form.product_id || !state.form.warehouse_id">确认入库</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 出库表单 -->
|
||||
<form v-else-if="state.modalType === 'stockOut'" @submit.prevent="stockOut">
|
||||
<div class="form-group">
|
||||
<label class="form-label">物料 *</label>
|
||||
<select v-model.number="state.form.product_id" class="form-input" required>
|
||||
<option v-if="!state.materials.length" :value="null" disabled>暂无物料,请先新增物料</option>
|
||||
<option v-for="product in state.materials" :key="'stockout-product-' + product.id" :value="product.id">
|
||||
{{ product.sku }} - {{ product.name }}(ID: {{ product.id }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">仓库 *</label>
|
||||
<select v-model.number="state.form.warehouse_id" class="form-input" required>
|
||||
<option v-if="!state.warehouses.length" :value="null" disabled>暂无仓库,系统将自动创建默认仓库</option>
|
||||
<option v-for="warehouse in state.warehouses" :key="'stockout-warehouse-' + warehouse.id" :value="warehouse.id">
|
||||
{{ warehouse.name }}{{ warehouse.code ? ' (' + warehouse.code + ')' : '' }}{{ warehouse.is_default ? ' [默认]' : '' }}(ID: {{ warehouse.id }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">业务类型 *</label>
|
||||
<select v-model="state.form.movement_type" class="form-input" required>
|
||||
<option v-for="item in outboundMovementOptions" :key="'stockout-type-' + item.value" :value="item.value">{{ item.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">数量 *</label>
|
||||
<input v-model.number="state.form.quantity" type="number" class="form-input" required placeholder="出库数量" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">单价</label>
|
||||
<input v-model.number="state.form.unit_price" type="number" step="0.01" class="form-input" placeholder="销售单价" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">备注</label>
|
||||
<input v-model="state.form.remark" class="form-input" placeholder="备注信息" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="!state.form.product_id || !state.form.warehouse_id">确认出库</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="!state.form.product_id || !state.form.warehouse_id">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user