|
|
@@ -1,7 +1,10 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
-import { getOrderDetail, getOrderItems } from "@/api/replenishmentOrder";
|
|
|
+import { getOrderDetail, getOrderItems, updateOrder, submitOrder, syncToErp } from "@/api/replenishmentOrder";
|
|
|
+import { message } from "@/utils/message";
|
|
|
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
|
|
+import DeleteIcon from "~icons/ep/delete";
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
defineOptions({
|
|
|
@@ -11,6 +14,8 @@ defineOptions({
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
const loading = ref(true);
|
|
|
+const saving = ref(false);
|
|
|
+const submitting = ref(false);
|
|
|
const order = ref<any>({});
|
|
|
const items = ref<any[]>([]);
|
|
|
|
|
|
@@ -37,17 +42,95 @@ async function loadData() {
|
|
|
order.value = orderRes.data;
|
|
|
}
|
|
|
if (itemsRes.data) {
|
|
|
- items.value = itemsRes.data;
|
|
|
+ // 深拷贝以便编辑,plannedQuantity 绑定到 v-model
|
|
|
+ items.value = (itemsRes.data || []).map((item: any) => ({ ...item }));
|
|
|
}
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function addItem() {
|
|
|
+ items.value.push({
|
|
|
+ orderId: order.value.id,
|
|
|
+ deviceId: order.value.deviceId,
|
|
|
+ productId: null,
|
|
|
+ productCode: "",
|
|
|
+ productName: "",
|
|
|
+ plannedQuantity: 1,
|
|
|
+ unitPrice: undefined,
|
|
|
+ shelfNum: undefined,
|
|
|
+ position: "",
|
|
|
+ beforeStock: undefined,
|
|
|
+ afterStock: undefined
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function removeItem(index: number) {
|
|
|
+ if (items.value.length <= 1) return;
|
|
|
+ items.value.splice(index, 1);
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSave() {
|
|
|
+ saving.value = true;
|
|
|
+ try {
|
|
|
+ const payload = {
|
|
|
+ id: Number(order.value.id),
|
|
|
+ supplierName: order.value.supplierName || undefined,
|
|
|
+ warehouseName: order.value.warehouseName || undefined,
|
|
|
+ expectedArrivalTime: order.value.expectedArrivalTime,
|
|
|
+ remark: order.value.remark || undefined,
|
|
|
+ items: items.value.map((item: any) => ({
|
|
|
+ productId: item.productId ? Number(item.productId) : undefined,
|
|
|
+ productCode: item.productCode || undefined,
|
|
|
+ productName: item.productName || undefined,
|
|
|
+ plannedQuantity: item.plannedQuantity,
|
|
|
+ unitPrice: item.unitPrice,
|
|
|
+ shelfNum: item.shelfNum,
|
|
|
+ position: item.position || undefined
|
|
|
+ }))
|
|
|
+ };
|
|
|
+ await updateOrder(payload);
|
|
|
+ message("保存成功", { type: "success" });
|
|
|
+ await loadData();
|
|
|
+ } catch (e) {
|
|
|
+ message("保存失败", { type: "error" });
|
|
|
+ } finally {
|
|
|
+ saving.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSubmitToErp() {
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ // 先提交
|
|
|
+ await submitOrder(order.value.id);
|
|
|
+ // 再同步 ERP
|
|
|
+ try {
|
|
|
+ await syncToErp(order.value.id);
|
|
|
+ message("已提交并同步ERP", { type: "success" });
|
|
|
+ } catch {
|
|
|
+ message("已提交,但ERP同步失败", { type: "warning" });
|
|
|
+ }
|
|
|
+ await loadData();
|
|
|
+ } catch (e) {
|
|
|
+ message("提交失败", { type: "error" });
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function goBack() {
|
|
|
router.push({ path: "/inventory/replenishment-order/list" });
|
|
|
}
|
|
|
|
|
|
+const isDraft = () => order.value.status === 0;
|
|
|
+const canEdit = () => isDraft();
|
|
|
+
|
|
|
+function itemSubtotal(item: any) {
|
|
|
+ return ((item.plannedQuantity || 0) * (item.unitPrice || 0)).toFixed(2);
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
loadData();
|
|
|
});
|
|
|
@@ -55,9 +138,19 @@ onMounted(() => {
|
|
|
|
|
|
<template>
|
|
|
<div class="main p-6">
|
|
|
- <div class="mb-4 flex items-center gap-4">
|
|
|
- <el-button :icon="'ep:arrow-left'" @click="goBack">返回列表</el-button>
|
|
|
- <h2 class="text-lg font-bold">补货单详情</h2>
|
|
|
+ <div class="mb-4 flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <el-button :icon="'ep:arrow-left'" @click="goBack">返回列表</el-button>
|
|
|
+ <h2 class="text-lg font-bold">补货单详情</h2>
|
|
|
+ </div>
|
|
|
+ <div v-if="!loading && isDraft()" class="flex gap-2">
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleSubmitToErp">
|
|
|
+ 提交ERP
|
|
|
+ </el-button>
|
|
|
+ <el-button type="success" :loading="saving" @click="handleSave">
|
|
|
+ 保存修改
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<el-card v-loading="loading" class="mb-4">
|
|
|
@@ -80,37 +173,43 @@ onMounted(() => {
|
|
|
<el-descriptions-item label="设备ID">
|
|
|
{{ order.deviceId }}
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="门店ID">
|
|
|
- {{ order.shopId || "-" }}
|
|
|
+ <el-descriptions-item label="门店">
|
|
|
+ {{ order.shopName || order.shopId || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="ERP采购单号">
|
|
|
- {{ order.erpOrderNo || "-" }}
|
|
|
+ <el-descriptions-item v-if="canEdit()" label="供应商">
|
|
|
+ <el-input v-model="order.supplierName" placeholder="供应商" size="small" style="width:160px;" />
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="供应商">
|
|
|
+ <el-descriptions-item v-else label="供应商">
|
|
|
{{ order.supplierName || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="仓库">
|
|
|
+ <el-descriptions-item v-if="canEdit()" label="仓库">
|
|
|
+ <el-input v-model="order.warehouseName" placeholder="仓库" size="small" style="width:160px;" />
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item v-else label="仓库">
|
|
|
{{ order.warehouseName || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="ERP采购单号">
|
|
|
+ {{ order.erpOrderNo || "-" }}
|
|
|
+ </el-descriptions-item>
|
|
|
<el-descriptions-item label="总数量">
|
|
|
- {{ order.totalQuantity }}
|
|
|
+ {{ items.reduce((s: number, i: any) => s + (i.plannedQuantity || 0), 0) }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="总金额">
|
|
|
- ¥{{ order.totalAmount != null ? Number(order.totalAmount).toFixed(2) : "0.00" }}
|
|
|
+ ¥{{ items.reduce((s: number, i: any) => s + (i.plannedQuantity || 0) * (i.unitPrice || 0), 0).toFixed(2) }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="预计到货时间">
|
|
|
{{ order.expectedArrivalTime || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="ERP同步时间">
|
|
|
- {{ order.erpSyncTime || "-" }}
|
|
|
- </el-descriptions-item>
|
|
|
<el-descriptions-item label="创建人">
|
|
|
- {{ order.creatorName }}
|
|
|
+ {{ order.creatorName || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间">
|
|
|
{{ order.createTime ? dayjs(order.createTime).format("YYYY-MM-DD HH:mm:ss") : "-" }}
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="备注" :span="2">
|
|
|
+ <el-descriptions-item v-if="canEdit()" label="备注" :span="2">
|
|
|
+ <el-input v-model="order.remark" placeholder="备注" size="small" />
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item v-else label="备注" :span="2">
|
|
|
{{ order.remark || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
@@ -118,46 +217,98 @@ onMounted(() => {
|
|
|
|
|
|
<el-card v-loading="loading">
|
|
|
<template #header>
|
|
|
- <span class="font-bold">补货明细</span>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <span class="font-bold">补货明细</span>
|
|
|
+ <el-button v-if="canEdit()" type="primary" size="small" :icon="'ep:plus'" @click="addItem">
|
|
|
+ 添加商品
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<el-table :data="items" border stripe>
|
|
|
- <el-table-column prop="productCode" label="商品编码" min-width="120" />
|
|
|
- <el-table-column prop="productName" label="商品名称" min-width="140" />
|
|
|
- <el-table-column prop="plannedQuantity" label="计划数量" width="100" />
|
|
|
- <el-table-column prop="actualQuantity" label="实际数量" width="100">
|
|
|
+ <el-table-column label="图片" width="70">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <img
|
|
|
+ v-if="row.productImage"
|
|
|
+ :src="row.productImage"
|
|
|
+ style="width:40px;height:40px;object-fit:cover;border-radius:4px;"
|
|
|
+ @error="$event.target.style.display='none'"
|
|
|
+ />
|
|
|
+ <span v-else style="color:#ccc;">-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="商品编码" min-width="120">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.actualQuantity != null ? row.actualQuantity : "-" }}
|
|
|
+ <el-input v-if="canEdit()" v-model="row.productCode" placeholder="商品编码" size="small" />
|
|
|
+ <span v-else>{{ row.productCode || "-" }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="补货前库存" width="110">
|
|
|
+ <el-table-column label="商品名称" min-width="140">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.beforeStock != null ? row.beforeStock : "-" }}
|
|
|
+ <el-input v-if="canEdit()" v-model="row.productName" placeholder="商品名称" size="small" />
|
|
|
+ <span v-else>{{ row.productName || "-" }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="补货后库存" width="110">
|
|
|
+ <el-table-column label="计划数量" width="110">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.afterStock != null ? row.afterStock : "-" }}
|
|
|
+ <el-input-number
|
|
|
+ v-if="canEdit()"
|
|
|
+ v-model="row.plannedQuantity"
|
|
|
+ :min="1"
|
|
|
+ :max="99999"
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ <span v-else>{{ row.plannedQuantity }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="单价" width="100">
|
|
|
+ <el-table-column label="单价" width="110">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.unitPrice != null ? `¥${Number(row.unitPrice).toFixed(2)}` : "-" }}
|
|
|
+ <el-input-number
|
|
|
+ v-if="canEdit()"
|
|
|
+ v-model="row.unitPrice"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ placeholder="单价"
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ <span v-else>{{ row.unitPrice != null ? `¥${Number(row.unitPrice).toFixed(2)}` : "-" }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="小计" width="100">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.totalPrice != null ? `¥${Number(row.totalPrice).toFixed(2)}` : "-" }}
|
|
|
+ <span class="font-medium">¥{{ itemSubtotal(row) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="shelfNum" label="货架层" width="80">
|
|
|
+ <el-table-column label="层号" width="70">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.shelfNum != null ? row.shelfNum : "-" }}
|
|
|
+ <el-input-number
|
|
|
+ v-if="canEdit()"
|
|
|
+ v-model="row.shelfNum"
|
|
|
+ :min="1"
|
|
|
+ placeholder="层"
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ <span v-else>{{ row.shelfNum != null ? row.shelfNum : "-" }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="position" label="货道位置" width="100">
|
|
|
+ <el-table-column label="货道" width="90">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.position || "-" }}
|
|
|
+ <el-input v-if="canEdit()" v-model="row.position" placeholder="位置" size="small" />
|
|
|
+ <span v-else>{{ row.position || "-" }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column v-if="canEdit()" label="操作" width="70" fixed="right">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ :icon="useRenderIcon(DeleteIcon)"
|
|
|
+ :disabled="items.length <= 1"
|
|
|
+ @click="removeItem($index)"
|
|
|
+ />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|