skyline преди 3 седмици
родител
ревизия
03874e2859
променени са 29 файла, в които са добавени 2637 реда и са изтрити 0 реда
  1. 58 0
      haha-admin-web/src/api/replenishmentOrder.ts
  2. 34 0
      haha-admin-web/src/router/modules/replenishmentOrder.ts
  3. 265 0
      haha-admin-web/src/views/replenishment-order/components/CreateDialog.vue
  4. 166 0
      haha-admin-web/src/views/replenishment-order/detail.vue
  5. 215 0
      haha-admin-web/src/views/replenishment-order/index.vue
  6. 240 0
      haha-admin-web/src/views/replenishment-order/utils/hook.tsx
  7. 50 0
      haha-admin-web/src/views/replenishment-order/utils/types.ts
  8. 45 0
      haha-admin/src/main/java/com/haha/admin/config/QdbSdkConfig.java
  9. 163 0
      haha-admin/src/main/java/com/haha/admin/controller/ReplenishmentOrderController.java
  10. 6 0
      haha-admin/src/main/resources/application.yml
  11. 118 0
      haha-admin/src/main/resources/sql/distribution.sql
  12. 20 0
      haha-admin/src/main/resources/sql/layer_template.sql
  13. 45 0
      haha-admin/src/main/resources/sql/layer_template_init.sql
  14. 49 0
      haha-admin/src/main/resources/sql/replenishment_order.sql
  15. 53 0
      haha-common/src/main/java/com/haha/common/enums/ReplenishmentOrderStatusEnum.java
  16. 138 0
      haha-entity/src/main/java/com/haha/entity/ReplenishmentOrder.java
  17. 97 0
      haha-entity/src/main/java/com/haha/entity/ReplenishmentOrderItem.java
  18. 101 0
      haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderCreateDTO.java
  19. 47 0
      haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderQueryDTO.java
  20. 47 0
      haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderUpdateDTO.java
  21. 28 0
      haha-mapper/src/main/java/com/haha/mapper/ReplenishmentOrderItemMapper.java
  22. 19 0
      haha-mapper/src/main/java/com/haha/mapper/ReplenishmentOrderMapper.java
  23. 7 0
      haha-service/pom.xml
  24. 67 0
      haha-service/src/main/java/com/haha/service/ReplenishmentOrderService.java
  25. 418 0
      haha-service/src/main/java/com/haha/service/impl/ReplenishmentOrderServiceImpl.java
  26. 5 0
      qdb-sdk/src/main/java/com/qdb/sdk/QdbClient.java
  27. 30 0
      qdb-sdk/src/main/java/com/qdb/sdk/api/PurchaseApi.java
  28. 58 0
      qdb-sdk/src/main/java/com/qdb/sdk/model/request/PurchaseOrderAddRequest.java
  29. 48 0
      qdb-sdk/src/main/java/com/qdb/sdk/model/request/PurchaseOrderGoods.java

+ 58 - 0
haha-admin-web/src/api/replenishmentOrder.ts

@@ -0,0 +1,58 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+/** 分页查询补货单列表 */
+export const getOrderList = (params: object) =>
+  http.request<ResultTable>("get", "/replenishment-orders/list", { params });
+
+/** 获取补货单详情 */
+export const getOrderDetail = (id: string) =>
+  http.request<Result>("get", `/replenishment-orders/${id}`);
+
+/** 获取补货单明细列表 */
+export const getOrderItems = (id: string) =>
+  http.request<Result>("get", `/replenishment-orders/${id}/items`);
+
+/** 创建补货单 */
+export const createOrder = (data: object) =>
+  http.request<Result>("post", "/replenishment-orders", { data });
+
+/** 更新补货单 */
+export const updateOrder = (data: object) =>
+  http.request<Result>("put", "/replenishment-orders", { data });
+
+/** 删除补货单 */
+export const deleteOrder = (id: string) =>
+  http.request<Result>("delete", `/replenishment-orders/${id}`);
+
+/** 提交补货单 */
+export const submitOrder = (id: string) =>
+  http.request<Result>("post", `/replenishment-orders/${id}/submit`);
+
+/** 同步到ERP */
+export const syncToErp = (id: string) =>
+  http.request<Result>("post", `/replenishment-orders/${id}/sync-erp`);
+
+/** 完成补货单 */
+export const completeOrder = (id: string) =>
+  http.request<Result>("post", `/replenishment-orders/${id}/complete`);
+
+/** 取消补货单 */
+export const cancelOrder = (id: string) =>
+  http.request<Result>("post", `/replenishment-orders/${id}/cancel`);

+ 34 - 0
haha-admin-web/src/router/modules/replenishmentOrder.ts

@@ -0,0 +1,34 @@
+import type { RouteConfigsTable } from "/@/router/types";
+
+export default {
+  path: "/replenishment-order",
+  redirect: "/replenishment-order/list",
+  meta: {
+    icon: "ep:shopping-cart",
+    title: "设备补货单",
+    rank: 9,
+    roles: ["admin"]
+  },
+  children: [
+    {
+      path: "/replenishment-order/list",
+      name: "ReplenishmentOrderList",
+      component: () =>
+        import("@/views/replenishment-order/index.vue"),
+      meta: {
+        icon: "ep:list",
+        title: "补货单列表"
+      }
+    },
+    {
+      path: "/replenishment-order/detail",
+      name: "ReplenishmentOrderDetail",
+      component: () =>
+        import("@/views/replenishment-order/detail.vue"),
+      meta: {
+        title: "补货单详情",
+        showLink: false
+      }
+    }
+  ]
+} satisfies RouteConfigsTable;

+ 265 - 0
haha-admin-web/src/views/replenishment-order/components/CreateDialog.vue

@@ -0,0 +1,265 @@
+<script setup lang="ts">
+import { ref, reactive } from "vue";
+import { message } from "@/utils/message";
+import { createOrder } from "@/api/replenishmentOrder";
+import type { FormInstance, FormRules } from "element-plus";
+import type { ReplenishmentOrderItem } from "../utils/types";
+
+const emit = defineEmits<{
+  (e: "success"): void;
+}>();
+
+const visible = ref(false);
+const formRef = ref<FormInstance>();
+const loading = ref(false);
+
+const form = reactive({
+  deviceId: "",
+  shopId: undefined as string | undefined,
+  supplierName: "",
+  warehouseName: "",
+  expectedArrivalTime: "",
+  remark: ""
+});
+
+const items = ref<ReplenishmentOrderItem[]>([
+  {
+    productId: "",
+    productCode: "",
+    productName: "",
+    plannedQuantity: 1,
+    unitPrice: undefined,
+    shelfNum: undefined,
+    position: ""
+  }
+]);
+
+const rules: FormRules = {
+  deviceId: [
+    { required: true, message: "请输入设备SN号", trigger: "blur" }
+  ]
+};
+
+function open() {
+  resetForm();
+  visible.value = true;
+}
+
+function resetForm() {
+  form.deviceId = "";
+  form.shopId = undefined;
+  form.supplierName = "";
+  form.warehouseName = "";
+  form.expectedArrivalTime = "";
+  form.remark = "";
+  items.value = [
+    {
+      productId: "",
+      productCode: "",
+      productName: "",
+      plannedQuantity: 1,
+      unitPrice: undefined,
+      shelfNum: undefined,
+      position: ""
+    }
+  ];
+}
+
+function addItem() {
+  items.value.push({
+    productId: "",
+    productCode: "",
+    productName: "",
+    plannedQuantity: 1,
+    unitPrice: undefined,
+    shelfNum: undefined,
+    position: ""
+  });
+}
+
+function removeItem(index: number) {
+  if (items.value.length <= 1) return;
+  items.value.splice(index, 1);
+}
+
+async function handleSubmit(formEl: FormInstance | undefined) {
+  if (!formEl) return;
+  const valid = await formEl.validate().catch(() => false);
+  if (!valid) return;
+
+  // Validate items
+  const emptyItem = items.value.find(
+    item => !item.productId && !item.productCode
+  );
+  if (emptyItem) {
+    message("请填写商品信息", { type: "warning" });
+    return;
+  }
+
+  loading.value = true;
+  try {
+    const { code } = await createOrder({
+      deviceId: form.deviceId,
+      shopId: form.shopId ? Number(form.shopId) : undefined,
+      supplierName: form.supplierName || undefined,
+      warehouseName: form.warehouseName || undefined,
+      expectedArrivalTime: form.expectedArrivalTime
+        ? form.expectedArrivalTime.replace("T", " ") + ":00"
+        : undefined,
+      remark: form.remark || undefined,
+      items: items.value.map(item => ({
+        productId: Number(item.productId),
+        productCode: item.productCode || undefined,
+        productName: item.productName || undefined,
+        plannedQuantity: item.plannedQuantity,
+        unitPrice: item.unitPrice,
+        shelfNum: item.shelfNum,
+        position: item.position || undefined
+      }))
+    });
+
+    if (code === 200) {
+      message("创建补货单成功", { type: "success" });
+      visible.value = false;
+      emit("success");
+    }
+  } finally {
+    loading.value = false;
+  }
+}
+
+defineExpose({ open });
+</script>
+
+<template>
+  <el-dialog
+    v-model="visible"
+    title="新建补货单"
+    width="800px"
+    :close-on-click-modal="false"
+    destroy-on-close
+  >
+    <el-form
+      ref="formRef"
+      :model="form"
+      :rules="rules"
+      label-width="110px"
+    >
+      <el-form-item label="设备SN号" prop="deviceId">
+        <el-input v-model="form.deviceId" placeholder="请输入设备SN号" />
+      </el-form-item>
+
+      <el-row :gutter="16">
+        <el-col :span="12">
+          <el-form-item label="供应商">
+            <el-input v-model="form.supplierName" placeholder="ERP供应商名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="仓库">
+            <el-input v-model="form.warehouseName" placeholder="ERP仓库名称" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="16">
+        <el-col :span="12">
+          <el-form-item label="预计到货时间">
+            <el-date-picker
+              v-model="form.expectedArrivalTime"
+              type="datetime"
+              placeholder="选择日期时间"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              class="w-full!"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="备注">
+            <el-input v-model="form.remark" placeholder="备注信息" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-divider content-position="left">补货商品</el-divider>
+
+      <div v-for="(item, index) in items" :key="index" class="mb-2">
+        <el-row :gutter="8" align="middle">
+          <el-col :span="5">
+            <el-input
+              v-model="item.productCode"
+              placeholder="商品编码"
+              size="small"
+            />
+          </el-col>
+          <el-col :span="5">
+            <el-input
+              v-model="item.productName"
+              placeholder="商品名称"
+              size="small"
+            />
+          </el-col>
+          <el-col :span="3">
+            <el-input-number
+              v-model="item.plannedQuantity"
+              :min="1"
+              :max="99999"
+              size="small"
+              controls-position="right"
+              class="w-full!"
+            />
+          </el-col>
+          <el-col :span="3">
+            <el-input-number
+              v-model="item.unitPrice"
+              :min="0"
+              :precision="2"
+              placeholder="单价"
+              size="small"
+              controls-position="right"
+              class="w-full!"
+            />
+          </el-col>
+          <el-col :span="2">
+            <el-input-number
+              v-model="item.shelfNum"
+              :min="1"
+              placeholder="层"
+              size="small"
+              controls-position="right"
+              class="w-full!"
+            />
+          </el-col>
+          <el-col :span="3">
+            <el-input
+              v-model="item.position"
+              placeholder="货道位置"
+              size="small"
+            />
+          </el-col>
+          <el-col :span="2">
+            <el-button
+              type="danger"
+              size="small"
+              :icon="'ep:delete'"
+              circle
+              :disabled="items.length <= 1"
+              @click="removeItem(index)"
+            />
+          </el-col>
+        </el-row>
+      </div>
+
+      <el-button type="primary" link :icon="'ep:plus'" @click="addItem">
+        添加商品
+      </el-button>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="visible = false">取消</el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit(formRef)">
+        确认创建
+      </el-button>
+    </template>
+  </el-dialog>
+</template>

+ 166 - 0
haha-admin-web/src/views/replenishment-order/detail.vue

@@ -0,0 +1,166 @@
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { getOrderDetail, getOrderItems } from "@/api/replenishmentOrder";
+import dayjs from "dayjs";
+
+defineOptions({
+  name: "ReplenishmentOrderDetail"
+});
+
+const route = useRoute();
+const router = useRouter();
+const loading = ref(true);
+const order = ref<any>({});
+const items = ref<any[]>([]);
+
+const statusMap: Record<number, { text: string; type: string }> = {
+  0: { text: "草稿", type: "info" },
+  1: { text: "已提交", type: "warning" },
+  2: { text: "已同步ERP", type: "primary" },
+  3: { text: "已完成", type: "success" },
+  4: { text: "已取消", type: "danger" }
+};
+
+async function loadData() {
+  loading.value = true;
+  try {
+    const id = route.query.id as string;
+    if (!id) return;
+
+    const [orderRes, itemsRes] = await Promise.all([
+      getOrderDetail(id),
+      getOrderItems(id)
+    ]);
+
+    if (orderRes.data) {
+      order.value = orderRes.data;
+    }
+    if (itemsRes.data) {
+      items.value = itemsRes.data;
+    }
+  } finally {
+    loading.value = false;
+  }
+}
+
+function goBack() {
+  router.push({ path: "/replenishment-order/list" });
+}
+
+onMounted(() => {
+  loadData();
+});
+</script>
+
+<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>
+
+    <el-card v-loading="loading" class="mb-4">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span class="font-bold">基本信息</span>
+          <el-tag
+            v-if="order.status != null"
+            :type="statusMap[order.status]?.type || 'info'"
+          >
+            {{ statusMap[order.status]?.text || "未知" }}
+          </el-tag>
+        </div>
+      </template>
+
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="补货单号">
+          {{ order.orderNo }}
+        </el-descriptions-item>
+        <el-descriptions-item label="设备ID">
+          {{ order.deviceId }}
+        </el-descriptions-item>
+        <el-descriptions-item label="门店ID">
+          {{ order.shopId || "-" }}
+        </el-descriptions-item>
+        <el-descriptions-item label="ERP采购单号">
+          {{ order.erpOrderNo || "-" }}
+        </el-descriptions-item>
+        <el-descriptions-item label="供应商">
+          {{ order.supplierName || "-" }}
+        </el-descriptions-item>
+        <el-descriptions-item label="仓库">
+          {{ order.warehouseName || "-" }}
+        </el-descriptions-item>
+        <el-descriptions-item label="总数量">
+          {{ order.totalQuantity }}
+        </el-descriptions-item>
+        <el-descriptions-item label="总金额">
+          ¥{{ order.totalAmount != null ? Number(order.totalAmount).toFixed(2) : "0.00" }}
+        </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 }}
+        </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">
+          {{ order.remark || "-" }}
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <el-card v-loading="loading">
+      <template #header>
+        <span class="font-bold">补货明细</span>
+      </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">
+          <template #default="{ row }">
+            {{ row.actualQuantity != null ? row.actualQuantity : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="补货前库存" width="110">
+          <template #default="{ row }">
+            {{ row.beforeStock != null ? row.beforeStock : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="补货后库存" width="110">
+          <template #default="{ row }">
+            {{ row.afterStock != null ? row.afterStock : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="单价" width="100">
+          <template #default="{ row }">
+            {{ row.unitPrice != null ? `¥${Number(row.unitPrice).toFixed(2)}` : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="小计" width="100">
+          <template #default="{ row }">
+            {{ row.totalPrice != null ? `¥${Number(row.totalPrice).toFixed(2)}` : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="shelfNum" label="货架层" width="80">
+          <template #default="{ row }">
+            {{ row.shelfNum != null ? row.shelfNum : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="position" label="货道位置" width="100">
+          <template #default="{ row }">
+            {{ row.position || "-" }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>

+ 215 - 0
haha-admin-web/src/views/replenishment-order/index.vue

@@ -0,0 +1,215 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { useRouter } from "vue-router";
+import { useReplenishmentOrder } from "./utils/hook";
+import { PureTableBar } from "@/components/RePureTableBar";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import Refresh from "~icons/ep/refresh";
+import Search from "~icons/ep/search";
+import Delete from "~icons/ep/delete";
+import Upload from "~icons/ep/upload";
+import Check from "~icons/ep/check";
+import Close from "~icons/ep/close";
+import View from "~icons/ep/view";
+import CreateDialog from "./components/CreateDialog.vue";
+
+defineOptions({
+  name: "ReplenishmentOrderList"
+});
+
+const router = useRouter();
+const formRef = ref();
+const tableRef = ref();
+const createDialogRef = ref();
+
+const {
+  form,
+  loading,
+  columns,
+  dataList,
+  pagination,
+  onSearch,
+  resetForm,
+  handleDelete,
+  handleSubmit,
+  handleSyncErp,
+  handleComplete,
+  handleCancel,
+  handleSizeChange,
+  handleCurrentChange
+} = useReplenishmentOrder();
+
+function handleCreate() {
+  createDialogRef.value?.open();
+}
+
+function handleViewDetail(row) {
+  router.push({
+    path: "/replenishment-order/detail",
+    query: { id: row.id }
+  });
+}
+
+function onCreated() {
+  onSearch();
+}
+</script>
+
+<template>
+  <div class="main">
+    <el-form
+      ref="formRef"
+      :inline="true"
+      :model="form"
+      class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
+    >
+      <el-form-item label="补货单号:" prop="orderNo">
+        <el-input
+          v-model="form.orderNo"
+          placeholder="请输入补货单号"
+          clearable
+          class="w-[180px]!"
+        />
+      </el-form-item>
+      <el-form-item label="设备ID:" prop="deviceId">
+        <el-input
+          v-model="form.deviceId"
+          placeholder="请输入设备ID"
+          clearable
+          class="w-[180px]!"
+        />
+      </el-form-item>
+      <el-form-item label="状态:" prop="status">
+        <el-select
+          v-model="form.status"
+          placeholder="请选择"
+          clearable
+          class="w-[160px]!"
+        >
+          <el-option label="草稿" :value="0" />
+          <el-option label="已提交" :value="1" />
+          <el-option label="已同步ERP" :value="2" />
+          <el-option label="已完成" :value="3" />
+          <el-option label="已取消" :value="4" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button
+          type="primary"
+          :icon="useRenderIcon(Search)"
+          :loading="loading"
+          @click="onSearch"
+        >
+          搜索
+        </el-button>
+        <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
+          重置
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <PureTableBar
+      title="设备补货单"
+      :columns="columns"
+      @refresh="onSearch"
+    >
+      <template #buttons>
+        <el-button
+          type="primary"
+          :icon="useRenderIcon('ep:plus')"
+          @click="handleCreate"
+        >
+          新建补货单
+        </el-button>
+      </template>
+
+      <template v-slot="{ size, dynamicColumns }">
+        <pure-table
+          ref="tableRef"
+          border
+          adaptive
+          align-whole="center"
+          row-key="id"
+          show-overflow-tooltip
+          table-layout="auto"
+          :loading="loading"
+          :size="size"
+          :data="dataList"
+          :columns="dynamicColumns"
+          :pagination="pagination"
+          :paginationSmall="size === 'small'"
+          :header-cell-style="{
+            background: 'var(--el-fill-color-light)',
+            color: 'var(--el-text-color-primary)'
+          }"
+          @page-size-change="handleSizeChange"
+          @page-current-change="handleCurrentChange"
+        >
+          <template #operation="{ row }">
+            <el-button
+              link
+              type="primary"
+              :size="size"
+              :icon="useRenderIcon(View)"
+              @click="handleViewDetail(row)"
+            >
+              详情
+            </el-button>
+            <el-button
+              v-if="row.status === 0"
+              link
+              type="warning"
+              :size="size"
+              :icon="useRenderIcon(Upload)"
+              @click="handleSubmit(row)"
+            >
+              提交
+            </el-button>
+            <el-button
+              v-if="row.status === 1"
+              link
+              type="primary"
+              :size="size"
+              :icon="useRenderIcon('ep:connection')"
+              @click="handleSyncErp(row)"
+            >
+              同步ERP
+            </el-button>
+            <el-button
+              v-if="row.status === 2"
+              link
+              type="success"
+              :size="size"
+              :icon="useRenderIcon(Check)"
+              @click="handleComplete(row)"
+            >
+              完成
+            </el-button>
+            <el-button
+              v-if="row.status === 0 || row.status === 1"
+              link
+              type="danger"
+              :size="size"
+              :icon="useRenderIcon(Close)"
+              @click="handleCancel(row)"
+            >
+              取消
+            </el-button>
+            <el-button
+              v-if="row.status === 0"
+              link
+              type="danger"
+              :size="size"
+              :icon="useRenderIcon(Delete)"
+              @click="handleDelete(row)"
+            >
+              删除
+            </el-button>
+          </template>
+        </pure-table>
+      </template>
+    </PureTableBar>
+
+    <CreateDialog ref="createDialogRef" @success="onCreated" />
+  </div>
+</template>

+ 240 - 0
haha-admin-web/src/views/replenishment-order/utils/hook.tsx

@@ -0,0 +1,240 @@
+import dayjs from "dayjs";
+import { message } from "@/utils/message";
+import {
+  getOrderList,
+  deleteOrder,
+  submitOrder,
+  syncToErp,
+  completeOrder,
+  cancelOrder
+} from "@/api/replenishmentOrder";
+import type { PaginationProps } from "@pureadmin/table";
+import { onMounted, reactive, ref } from "vue";
+
+export function useReplenishmentOrder() {
+  const form = reactive<{
+    orderNo?: string;
+    deviceId?: string;
+    shopId?: string;
+    status?: number | "";
+    startTime?: string;
+    endTime?: string;
+  }>({
+    orderNo: "",
+    deviceId: "",
+    shopId: undefined,
+    status: "",
+    startTime: "",
+    endTime: ""
+  });
+  const loading = ref(true);
+  const dataList = ref([]);
+  const pagination = reactive<PaginationProps>({
+    total: 0,
+    pageSize: 10,
+    currentPage: 1,
+    background: true
+  });
+
+  const statusMap: Record<number, { text: string; type: string }> = {
+    0: { text: "草稿", type: "info" },
+    1: { text: "已提交", type: "warning" },
+    2: { text: "已同步ERP", type: "primary" },
+    3: { text: "已完成", type: "success" },
+    4: { text: "已取消", type: "danger" }
+  };
+
+  const columns: TableColumnList = [
+    {
+      label: "补货单号",
+      prop: "orderNo",
+      minWidth: 160
+    },
+    {
+      label: "设备ID",
+      prop: "deviceId",
+      minWidth: 120
+    },
+    {
+      label: "状态",
+      prop: "status",
+      minWidth: 110,
+      cellRenderer: ({ row }) => {
+        const item = statusMap[row.status] || { text: "未知", type: "info" };
+        return <el-tag type={item.type}>{item.text}</el-tag>;
+      }
+    },
+    {
+      label: "总数量",
+      prop: "totalQuantity",
+      width: 90
+    },
+    {
+      label: "总金额",
+      prop: "totalAmount",
+      minWidth: 100,
+      formatter: ({ totalAmount }) =>
+        totalAmount != null ? `¥${Number(totalAmount).toFixed(2)}` : "-"
+    },
+    {
+      label: "供应商",
+      prop: "supplierName",
+      minWidth: 110,
+      formatter: ({ supplierName }) => supplierName || "-"
+    },
+    {
+      label: "仓库",
+      prop: "warehouseName",
+      minWidth: 110,
+      formatter: ({ warehouseName }) => warehouseName || "-"
+    },
+    {
+      label: "ERP单号",
+      prop: "erpOrderNo",
+      minWidth: 140,
+      formatter: ({ erpOrderNo }) => erpOrderNo || "-"
+    },
+    {
+      label: "创建人",
+      prop: "creatorName",
+      minWidth: 100
+    },
+    {
+      label: "创建时间",
+      prop: "createTime",
+      minWidth: 160,
+      formatter: ({ createTime }) =>
+        createTime ? dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") : ""
+    },
+    {
+      label: "操作",
+      fixed: "right",
+      width: 280,
+      slot: "operation"
+    }
+  ];
+
+  async function onSearch() {
+    loading.value = true;
+    try {
+      const params: any = {
+        page: pagination.currentPage,
+        pageSize: pagination.pageSize
+      };
+      if (form.orderNo) params.orderNo = form.orderNo;
+      if (form.deviceId) params.deviceId = form.deviceId;
+      if (form.shopId) params.shopId = form.shopId;
+      if (form.status !== "" && form.status !== undefined)
+        params.status = form.status;
+      if (form.startTime) params.startTime = form.startTime;
+      if (form.endTime) params.endTime = form.endTime;
+
+      const { data } = await getOrderList(params);
+      if (data) {
+        dataList.value = data.list || [];
+        pagination.total = Number(data.total) || 0;
+      }
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  function resetForm(formEl) {
+    if (!formEl) return;
+    formEl.resetFields();
+    pagination.currentPage = 1;
+    onSearch();
+  }
+
+  async function handleDelete(row) {
+    try {
+      const { code } = await deleteOrder(row.id);
+      if (code === 200) {
+        message("删除成功", { type: "success" });
+        onSearch();
+      }
+    } catch (e) {
+      // error handled by interceptor
+    }
+  }
+
+  async function handleSubmit(row) {
+    try {
+      const { code } = await submitOrder(row.id);
+      if (code === 200) {
+        message("提交成功", { type: "success" });
+        onSearch();
+      }
+    } catch (e) {
+      // error handled by interceptor
+    }
+  }
+
+  async function handleSyncErp(row) {
+    try {
+      const { code } = await syncToErp(row.id);
+      if (code === 200) {
+        message("同步ERP成功", { type: "success" });
+        onSearch();
+      }
+    } catch (e) {
+      // error handled by interceptor
+    }
+  }
+
+  async function handleComplete(row) {
+    try {
+      const { code } = await completeOrder(row.id);
+      if (code === 200) {
+        message("完成补货单", { type: "success" });
+        onSearch();
+      }
+    } catch (e) {
+      // error handled by interceptor
+    }
+  }
+
+  async function handleCancel(row) {
+    try {
+      const { code } = await cancelOrder(row.id);
+      if (code === 200) {
+        message("取消成功", { type: "success" });
+        onSearch();
+      }
+    } catch (e) {
+      // error handled by interceptor
+    }
+  }
+
+  function handleSizeChange(val: number) {
+    pagination.pageSize = val;
+    onSearch();
+  }
+
+  function handleCurrentChange(val: number) {
+    pagination.currentPage = val;
+    onSearch();
+  }
+
+  onMounted(() => {
+    onSearch();
+  });
+
+  return {
+    form,
+    loading,
+    columns,
+    dataList,
+    pagination,
+    statusMap,
+    onSearch,
+    resetForm,
+    handleDelete,
+    handleSubmit,
+    handleSyncErp,
+    handleComplete,
+    handleCancel,
+    handleSizeChange,
+    handleCurrentChange
+  };
+}

+ 50 - 0
haha-admin-web/src/views/replenishment-order/utils/types.ts

@@ -0,0 +1,50 @@
+export interface ReplenishmentOrderItem {
+  id?: string;
+  orderId?: string;
+  productId: string;
+  productCode?: string;
+  productName?: string;
+  plannedQuantity: number;
+  actualQuantity?: number;
+  beforeStock?: number;
+  afterStock?: number;
+  unitPrice?: number;
+  totalPrice?: number;
+  shelfNum?: number;
+  position?: string;
+  createTime?: string;
+}
+
+export interface ReplenishmentOrder {
+  id: string;
+  orderNo: string;
+  deviceId: string;
+  shopId?: string;
+  status: number;
+  statusLabel?: string;
+  statusColor?: string;
+  erpOrderNo?: string;
+  erpSyncTime?: string;
+  supplierName?: string;
+  warehouseName?: string;
+  expectedArrivalTime?: string;
+  totalQuantity: number;
+  totalAmount: number;
+  remark?: string;
+  creatorId?: string;
+  creatorName?: string;
+  shopName?: string;
+  deviceName?: string;
+  itemCount?: number;
+  createTime: string;
+  updateTime?: string;
+}
+
+export interface SearchFormProps {
+  orderNo?: string;
+  deviceId?: string;
+  shopId?: string;
+  status?: number | "";
+  startTime?: string;
+  endTime?: string;
+}

+ 45 - 0
haha-admin/src/main/java/com/haha/admin/config/QdbSdkConfig.java

@@ -0,0 +1,45 @@
+package com.haha.admin.config;
+
+import com.qdb.sdk.QdbClient;
+import com.qdb.sdk.QdbConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 企得宝 ERP SDK 配置类
+ */
+@Slf4j
+@Configuration
+public class QdbSdkConfig {
+
+    @Value("${qdb.client-id:}")
+    private String clientId;
+
+    @Value("${qdb.client-secret:}")
+    private String clientSecret;
+
+    @Value("${qdb.api-url:https://api.7debao.com/router/api}")
+    private String apiUrl;
+
+    /**
+     * 创建企得宝ERP SDK客户端Bean
+     * 仅在配置了 client-id 和 client-secret 时才创建
+     */
+    @Bean
+    @ConditionalOnProperty(prefix = "qdb", name = {"client-id", "client-secret"})
+    public QdbClient qdbClient() {
+        QdbConfig config = QdbConfig.builder()
+                .clientId(clientId)
+                .clientSecret(clientSecret)
+                .apiUrl(apiUrl)
+                .build();
+
+        config.validate();
+
+        log.info("企得宝ERP SDK 初始化成功, clientId: {}", clientId);
+        return new QdbClient(config);
+    }
+}

+ 163 - 0
haha-admin/src/main/java/com/haha/admin/controller/ReplenishmentOrderController.java

@@ -0,0 +1,163 @@
+package com.haha.admin.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.haha.admin.annotation.RequirePermission;
+import com.haha.common.annotation.Log;
+import com.haha.common.enums.OperationType;
+import com.haha.common.vo.PageResult;
+import com.haha.common.vo.Result;
+import com.haha.entity.ReplenishmentOrder;
+import com.haha.entity.ReplenishmentOrderItem;
+import com.haha.entity.dto.ReplenishmentOrderCreateDTO;
+import com.haha.entity.dto.ReplenishmentOrderQueryDTO;
+import com.haha.entity.dto.ReplenishmentOrderUpdateDTO;
+import com.haha.service.ReplenishmentOrderService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 设备补货单管理控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/replenishment-orders")
+@RequiredArgsConstructor
+public class ReplenishmentOrderController {
+
+    private final ReplenishmentOrderService orderService;
+
+    /**
+     * 分页查询补货单列表
+     */
+    @RequirePermission("replenishment:order:read")
+    @GetMapping("/list")
+    public Result<PageResult<ReplenishmentOrder>> list(ReplenishmentOrderQueryDTO queryDTO) {
+        IPage<ReplenishmentOrder> page = orderService.getPage(queryDTO);
+        return Result.success("查询成功", PageResult.of(page));
+    }
+
+    /**
+     * 获取补货单详情(含明细)
+     */
+    @RequirePermission("replenishment:order:read")
+    @GetMapping("/{id}")
+    public Result<ReplenishmentOrder> getById(@PathVariable Long id) {
+        ReplenishmentOrder order = orderService.getDetailWithItems(id);
+        List<ReplenishmentOrderItem> items = orderService.getItems(id);
+        order.setItemCount(items != null ? items.size() : 0);
+        return Result.success("查询成功", order);
+    }
+
+    /**
+     * 获取补货单明细列表
+     */
+    @RequirePermission("replenishment:order:read")
+    @GetMapping("/{id}/items")
+    public Result<List<ReplenishmentOrderItem>> getItems(@PathVariable Long id) {
+        List<ReplenishmentOrderItem> items = orderService.getItems(id);
+        return Result.success("查询成功", items);
+    }
+
+    /**
+     * 创建补货单
+     */
+    @RequirePermission("replenishment:order:create")
+    @Log(module = "补货单管理", operation = OperationType.INSERT, summary = "创建补货单")
+    @PostMapping
+    public Result<ReplenishmentOrder> create(@Valid @RequestBody ReplenishmentOrderCreateDTO dto) {
+        Long creatorId = getCurrentUserId();
+        String creatorName = getCurrentUserName();
+        ReplenishmentOrder order = orderService.createOrder(dto, creatorId, creatorName);
+        return Result.success("创建成功", order);
+    }
+
+    /**
+     * 更新补货单
+     */
+    @RequirePermission("replenishment:order:update")
+    @Log(module = "补货单管理", operation = OperationType.UPDATE, summary = "更新补货单")
+    @PutMapping
+    public Result<ReplenishmentOrder> update(@Valid @RequestBody ReplenishmentOrderUpdateDTO dto) {
+        ReplenishmentOrder order = orderService.updateOrder(dto);
+        return Result.success("更新成功", order);
+    }
+
+    /**
+     * 删除补货单
+     */
+    @RequirePermission("replenishment:order:delete")
+    @Log(module = "补货单管理", operation = OperationType.DELETE, summary = "删除补货单")
+    @DeleteMapping("/{id}")
+    public Result<Void> delete(@PathVariable Long id) {
+        orderService.deleteOrder(id);
+        return Result.success("删除成功");
+    }
+
+    /**
+     * 提交补货单
+     */
+    @RequirePermission("replenishment:order:submit")
+    @Log(module = "补货单管理", operation = OperationType.UPDATE, summary = "提交补货单")
+    @PostMapping("/{id}/submit")
+    public Result<ReplenishmentOrder> submit(@PathVariable Long id) {
+        ReplenishmentOrder order = orderService.submitOrder(id);
+        return Result.success("提交成功", order);
+    }
+
+    /**
+     * 同步到ERP
+     */
+    @RequirePermission("replenishment:order:sync")
+    @Log(module = "补货单管理", operation = OperationType.OTHER, summary = "同步补货单到ERP")
+    @PostMapping("/{id}/sync-erp")
+    public Result<ReplenishmentOrder> syncToErp(@PathVariable Long id) {
+        ReplenishmentOrder order = orderService.syncToErp(id);
+        return Result.success("同步ERP成功", order);
+    }
+
+    /**
+     * 完成补货单
+     */
+    @RequirePermission("replenishment:order:complete")
+    @Log(module = "补货单管理", operation = OperationType.UPDATE, summary = "完成补货单")
+    @PostMapping("/{id}/complete")
+    public Result<ReplenishmentOrder> complete(@PathVariable Long id) {
+        ReplenishmentOrder order = orderService.completeOrder(id);
+        return Result.success("完成补货单", order);
+    }
+
+    /**
+     * 取消补货单
+     */
+    @RequirePermission("replenishment:order:update")
+    @Log(module = "补货单管理", operation = OperationType.UPDATE, summary = "取消补货单")
+    @PostMapping("/{id}/cancel")
+    public Result<ReplenishmentOrder> cancel(@PathVariable Long id) {
+        ReplenishmentOrder order = orderService.cancelOrder(id);
+        return Result.success("取消成功", order);
+    }
+
+    private Long getCurrentUserId() {
+        try {
+            return cn.dev33.satoken.stp.StpUtil.getLoginIdAsLong();
+        } catch (Exception e) {
+            return 1L;
+        }
+    }
+
+    private String getCurrentUserName() {
+        try {
+            Object name = cn.dev33.satoken.stp.StpUtil.getSession().get("nickname");
+            if (name != null) {
+                return name.toString();
+            }
+            return cn.dev33.satoken.stp.StpUtil.getLoginIdAsString();
+        } catch (Exception e) {
+            return "系统";
+        }
+    }
+}

+ 6 - 0
haha-admin/src/main/resources/application.yml

@@ -107,6 +107,12 @@ haha:
     # 是否启用请求日志
     enable-request-log: true
 
+# 企得宝 ERP SDK 配置
+qdb:
+  client-id:
+  client-secret:
+  api-url: https://api.7debao.com/router/api
+
 # 设备离线预警配置
 device:
   alert:

+ 118 - 0
haha-admin/src/main/resources/sql/distribution.sql

@@ -0,0 +1,118 @@
+CREATE TABLE IF NOT EXISTS `t_distributor` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `code` varchar(20) NOT NULL COMMENT '分销编码,格式DS+6位数字',
+  `name` varchar(50) NOT NULL COMMENT '姓名',
+  `phone` varchar(20) NOT NULL COMMENT '手机号',
+  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-待激活 1-已激活 2-已停用',
+  `qrcode_content` varchar(500) DEFAULT NULL COMMENT '地推二维码文本',
+  `total_referrals` int NOT NULL DEFAULT 0 COMMENT '总推荐人数',
+  `valid_referrals` int NOT NULL DEFAULT 0 COMMENT '有效推荐人数',
+  `commission_balance` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '佣金可提现余额',
+  `frozen_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '冻结金额(提现审核中)',
+  `total_commission` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '累计佣金总额',
+  `total_withdrawn` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '累计已提现金额',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `creator_id` bigint DEFAULT NULL COMMENT '创建人ID',
+  `creator_name` varchar(50) DEFAULT NULL COMMENT '创建人姓名',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_code` (`code`),
+  UNIQUE KEY `uk_phone` (`phone`),
+  KEY `idx_status` (`status`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分销员表';
+
+CREATE TABLE IF NOT EXISTS `t_distributor_referral` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `distributor_id` bigint NOT NULL COMMENT '分销员ID',
+  `user_id` bigint NOT NULL COMMENT '被推荐用户ID',
+  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-待生效 1-已生效 2-已失效',
+  `first_order_id` bigint DEFAULT NULL COMMENT '首单ID',
+  `first_order_amount` decimal(10,2) DEFAULT NULL COMMENT '首单金额',
+  `first_order_time` datetime DEFAULT NULL COMMENT '首单时间',
+  `effective_time` datetime DEFAULT NULL COMMENT '生效时间',
+  `expire_time` datetime DEFAULT NULL COMMENT '过期时间(根据有效时间窗口计算)',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_distributor_user` (`distributor_id`, `user_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分销推荐关系表';
+
+CREATE TABLE IF NOT EXISTS `t_distributor_commission` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `distributor_id` bigint NOT NULL COMMENT '分销员ID',
+  `referral_id` bigint DEFAULT NULL COMMENT '关联推荐记录ID',
+  `user_id` bigint NOT NULL COMMENT '被推荐用户ID',
+  `commission_type` tinyint NOT NULL COMMENT '佣金类型:0-拉新奖励(固定金额) 1-消费提成(首单比例)',
+  `amount` decimal(10,2) NOT NULL COMMENT '佣金金额',
+  `order_amount` decimal(10,2) DEFAULT NULL COMMENT '关联订单金额(比例佣金时使用)',
+  `rate` decimal(5,2) DEFAULT NULL COMMENT '佣金比例(百分比,如5.00表示5%)',
+  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-待结算 1-已结算 2-已冻结',
+  `settle_time` datetime DEFAULT NULL COMMENT '结算时间',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_distributor_id` (`distributor_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_commission_type` (`commission_type`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='佣金记录表';
+
+CREATE TABLE IF NOT EXISTS `t_distributor_withdrawal` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `distributor_id` bigint NOT NULL COMMENT '分销员ID',
+  `amount` decimal(10,2) NOT NULL COMMENT '提现金额',
+  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-待审核 1-已通过 2-已拒绝 3-已到账',
+  `reviewer_id` bigint DEFAULT NULL COMMENT '审核人ID',
+  `reviewer_name` varchar(50) DEFAULT NULL COMMENT '审核人姓名',
+  `review_time` datetime DEFAULT NULL COMMENT '审核时间',
+  `review_remark` varchar(500) DEFAULT NULL COMMENT '审核备注/拒绝原因',
+  `transfer_time` datetime DEFAULT NULL COMMENT '到账时间',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_distributor_id` (`distributor_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='提现申请表';
+
+CREATE TABLE IF NOT EXISTS `t_distributor_monthly_report` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `distributor_id` bigint NOT NULL COMMENT '分销员ID',
+  `year_month` varchar(7) NOT NULL COMMENT '年月,格式yyyy-MM',
+  `total_referrals` int NOT NULL DEFAULT 0 COMMENT '总推荐人数',
+  `valid_referrals` int NOT NULL DEFAULT 0 COMMENT '有效推荐人数',
+  `pending_referrals` int NOT NULL DEFAULT 0 COMMENT '待生效人数',
+  `expired_referrals` int NOT NULL DEFAULT 0 COMMENT '已失效人数',
+  `total_commission` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '佣金总额',
+  `settled_commission` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '已结算佣金',
+  `pending_commission` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '待结算佣金',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_distributor_month` (`distributor_id`, `year_month`),
+  KEY `idx_year_month` (`year_month`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='月度统计报表表';
+
+CREATE TABLE IF NOT EXISTS `t_distributor_config` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `config_key` varchar(100) NOT NULL COMMENT '配置键',
+  `config_value` varchar(500) NOT NULL COMMENT '配置值',
+  `config_desc` varchar(200) DEFAULT NULL COMMENT '配置描述',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_config_key` (`config_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分销配置表';
+
+INSERT INTO `t_distributor_config` (`config_key`, `config_value`, `config_desc`) VALUES
+('fixed_commission_amount', '20.00', '固定佣金金额(元/人)'),
+('first_order_commission_rate', '5.00', '首单佣金比例(百分比)'),
+('valid_user_rule', 'FIRST_ORDER', '有效用户判定规则:FIRST_ORDER-首单完成 MIN_AMOUNT-最低消费金额 BOTH-首单且最低金额'),
+('min_valid_order_amount', '0.00', '有效用户最低消费金额(元),0表示不限'),
+('valid_time_window_days', '0', '有效时间窗口(天),0表示不限'),
+('min_withdrawal_amount', '100.00', '最低提现金额(元)');

+ 20 - 0
haha-admin/src/main/resources/sql/layer_template.sql

@@ -0,0 +1,20 @@
+CREATE TABLE IF NOT EXISTS `t_layer_template` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `template_id` VARCHAR(64) DEFAULT NULL COMMENT '模板ID(哈哈平台返回)',
+  `device_id` VARCHAR(64) NOT NULL COMMENT '设备ID(SN号)',
+  `template_name` VARCHAR(256) DEFAULT NULL COMMENT '模板名称',
+  `device_type` INT DEFAULT NULL COMMENT '设备类型',
+  `shelf_num` INT DEFAULT NULL COMMENT '机柜层数',
+  `left_floors` JSON DEFAULT NULL COMMENT '左门层数据(JSON格式)',
+  `right_floors` JSON DEFAULT NULL COMMENT '右门层数据(JSON格式)',
+  `goods_rule` TEXT DEFAULT NULL COMMENT '商品规则',
+  `sync_status` INT DEFAULT 0 COMMENT '同步状态:0-未同步,1-同步中,2-已同步,3-同步失败',
+  `sync_time` DATETIME DEFAULT NULL COMMENT '最后同步时间',
+  `is_deleted` INT DEFAULT 0 COMMENT '是否删除:0-正常,1-已删除',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_device_id` (`device_id`),
+  KEY `idx_sync_status` (`sync_status`),
+  KEY `idx_template_id` (`template_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备层模版表';

+ 45 - 0
haha-admin/src/main/resources/sql/layer_template_init.sql

@@ -0,0 +1,45 @@
+-- =====================================================
+-- 设备层模版管理功能 - 数据库初始化脚本
+-- 创建时间: 2026-04-12
+-- 说明: 用于初始化设备层模版管理功能所需的数据库表和权限
+-- =====================================================
+
+-- 1. 创建层模版表
+CREATE TABLE IF NOT EXISTS `t_layer_template` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `template_id` VARCHAR(64) DEFAULT NULL COMMENT '模板ID(哈哈平台返回)',
+  `device_id` VARCHAR(64) NOT NULL COMMENT '设备ID(SN号)',
+  `template_name` VARCHAR(256) DEFAULT NULL COMMENT '模板名称',
+  `device_type` INT DEFAULT NULL COMMENT '设备类型',
+  `shelf_num` INT DEFAULT NULL COMMENT '机柜层数',
+  `left_floors` JSON DEFAULT NULL COMMENT '左门层数据(JSON格式)',
+  `right_floors` JSON DEFAULT NULL COMMENT '右门层数据(JSON格式)',
+  `goods_rule` TEXT DEFAULT NULL COMMENT '商品规则',
+  `sync_status` INT DEFAULT 0 COMMENT '同步状态:0-未同步,1-同步中,2-已同步,3-同步失败',
+  `sync_time` DATETIME DEFAULT NULL COMMENT '最后同步时间',
+  `is_deleted` INT DEFAULT 0 COMMENT '是否删除:0-正常,1-已删除',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_device_id` (`device_id`),
+  KEY `idx_sync_status` (`sync_status`),
+  KEY `idx_template_id` (`template_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备层模版表';
+
+-- 2. 添加设备层模版管理模块的权限
+-- 2. 添加设备层模版管理模块的权限
+INSERT INTO t_data_permission (id,permission_code, permission_name, module_code, module_name, operation_type, description, sort_order) VALUES
+(68,'layer-template:create', '创建层模版', 'layer-template', '设备层模版管理', 'create', '添加新层模版', 151),
+(69,'layer-template:read', '查询层模版', 'layer-template', '设备层模版管理', 'read', '查看层模版列表和详情', 152),
+(70,'layer-template:update', '修改层模版', 'layer-template', '设备层模版管理', 'update', '编辑层模版信息和同步操作', 153),
+(71,'layer-template:delete', '删除层模版', 'layer-template', '设备层模版管理', 'delete', '删除层模版', 154);
+-- 3. 为超级管理员角色(role.id=1)分配设备层模版管理权限
+INSERT IGNORE INTO t_role_permission (role_id, permission_id)
+SELECT 1, id FROM t_data_permission WHERE module_code = 'layer-template' AND status = 1;
+
+-- =====================================================
+-- 执行完成提示
+-- =====================================================
+SELECT '✅ 设备层模版管理功能数据库初始化完成!' AS message;
+SELECT COUNT(*) AS table_exists FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 't_layer_template';
+SELECT COUNT(*) AS permission_count FROM t_data_permission WHERE module_code = 'layer-template';

+ 49 - 0
haha-admin/src/main/resources/sql/replenishment_order.sql

@@ -0,0 +1,49 @@
+-- 设备补货单表
+CREATE TABLE IF NOT EXISTS `t_replenishment_order` (
+    `id` BIGINT NOT NULL COMMENT '主键ID(雪花算法)',
+    `order_no` VARCHAR(32) NOT NULL COMMENT '补货单号(RO+日期+序号)',
+    `device_id` VARCHAR(50) NOT NULL COMMENT '设备SN号',
+    `shop_id` BIGINT DEFAULT NULL COMMENT '门店ID(冗余)',
+    `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态: 0-草稿, 1-已提交, 2-已同步ERP, 3-已完成, 4-已取消',
+    `erp_order_no` VARCHAR(64) DEFAULT NULL COMMENT 'ERP采购单号',
+    `erp_sync_time` DATETIME DEFAULT NULL COMMENT 'ERP同步时间',
+    `supplier_name` VARCHAR(100) DEFAULT NULL COMMENT '供应商名称',
+    `warehouse_name` VARCHAR(100) DEFAULT NULL COMMENT 'ERP仓库名称',
+    `expected_arrival_time` DATETIME DEFAULT NULL COMMENT '预计到货时间',
+    `total_quantity` INT NOT NULL DEFAULT 0 COMMENT '补货总数量',
+    `total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '补货总金额',
+    `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
+    `creator_id` BIGINT DEFAULT NULL COMMENT '创建人ID',
+    `creator_name` VARCHAR(64) DEFAULT NULL COMMENT '创建人姓名',
+    `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_order_no` (`order_no`),
+    INDEX `idx_device_id` (`device_id`),
+    INDEX `idx_shop_id` (`shop_id`),
+    INDEX `idx_status` (`status`),
+    INDEX `idx_erp_order_no` (`erp_order_no`),
+    INDEX `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备补货单';
+
+-- 设备补货单明细表
+CREATE TABLE IF NOT EXISTS `t_replenishment_order_item` (
+    `id` BIGINT NOT NULL COMMENT '主键ID(雪花算法)',
+    `order_id` BIGINT NOT NULL COMMENT '补货单ID',
+    `device_id` VARCHAR(50) NOT NULL COMMENT '设备SN号',
+    `product_id` BIGINT NOT NULL COMMENT '商品ID',
+    `product_code` VARCHAR(64) DEFAULT NULL COMMENT '商品编码',
+    `product_name` VARCHAR(128) DEFAULT NULL COMMENT '商品名称',
+    `planned_quantity` INT NOT NULL DEFAULT 0 COMMENT '计划补货数量',
+    `actual_quantity` INT DEFAULT NULL COMMENT '实际补货数量',
+    `before_stock` INT DEFAULT NULL COMMENT '补货前库存',
+    `after_stock` INT DEFAULT NULL COMMENT '补货后库存',
+    `unit_price` DECIMAL(10,2) DEFAULT NULL COMMENT '单价',
+    `total_price` DECIMAL(12,2) DEFAULT NULL COMMENT '小计金额',
+    `shelf_num` INT DEFAULT NULL COMMENT '货架层号',
+    `position` VARCHAR(16) DEFAULT NULL COMMENT '货道位置',
+    `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    INDEX `idx_order_id` (`order_id`),
+    INDEX `idx_product_id` (`product_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备补货单明细';

+ 53 - 0
haha-common/src/main/java/com/haha/common/enums/ReplenishmentOrderStatusEnum.java

@@ -0,0 +1,53 @@
+package com.haha.common.enums;
+
+import com.haha.common.vo.StatusLabel;
+
+/**
+ * 补货单状态枚举
+ */
+public enum ReplenishmentOrderStatusEnum {
+
+    DRAFT(0, "草稿", "info"),
+    SUBMITTED(1, "已提交", "warning"),
+    SYNCED_TO_ERP(2, "已同步ERP", "primary"),
+    COMPLETED(3, "已完成", "success"),
+    CANCELLED(4, "已取消", "danger");
+
+    private final int code;
+    private final String label;
+    private final String color;
+
+    ReplenishmentOrderStatusEnum(int code, String label, String color) {
+        this.code = code;
+        this.label = label;
+        this.color = color;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public String getColor() {
+        return color;
+    }
+
+    public StatusLabel getStatusLabel() {
+        return new StatusLabel(label, color);
+    }
+
+    public static StatusLabel getLabelByCode(Integer code) {
+        if (code == null) {
+            return StatusLabel.info("未知");
+        }
+        for (ReplenishmentOrderStatusEnum status : values()) {
+            if (status.code == code) {
+                return status.getStatusLabel();
+            }
+        }
+        return StatusLabel.info("未知");
+    }
+}

+ 138 - 0
haha-entity/src/main/java/com/haha/entity/ReplenishmentOrder.java

@@ -0,0 +1,138 @@
+package com.haha.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 设备补货单实体
+ */
+@Data
+@TableName("t_replenishment_order")
+public class ReplenishmentOrder implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 补货单号
+     */
+    private String orderNo;
+
+    /**
+     * 设备SN号
+     */
+    private String deviceId;
+
+    /**
+     * 门店ID(冗余)
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long shopId;
+
+    /**
+     * 状态: 0=草稿, 1=已提交, 2=已同步ERP, 3=已完成, 4=已取消
+     */
+    private Integer status;
+
+    /**
+     * ERP采购单号
+     */
+    private String erpOrderNo;
+
+    /**
+     * ERP同步时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime erpSyncTime;
+
+    /**
+     * 供应商名称
+     */
+    private String supplierName;
+
+    /**
+     * ERP仓库名称
+     */
+    private String warehouseName;
+
+    /**
+     * 预计到货时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime expectedArrivalTime;
+
+    /**
+     * 补货总数量
+     */
+    private Integer totalQuantity;
+
+    /**
+     * 补货总金额
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建人ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long creatorId;
+
+    /**
+     * 创建人姓名
+     */
+    private String creatorName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime updateTime;
+
+    /**
+     * 状态标签(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String statusLabel;
+
+    /**
+     * 状态颜色(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String statusColor;
+
+    /**
+     * 门店名称(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String shopName;
+
+    /**
+     * 设备名称(非数据库字段)
+     */
+    @TableField(exist = false)
+    private String deviceName;
+
+    /**
+     * 明细数量(非数据库字段)
+     */
+    @TableField(exist = false)
+    private Integer itemCount;
+}

+ 97 - 0
haha-entity/src/main/java/com/haha/entity/ReplenishmentOrderItem.java

@@ -0,0 +1,97 @@
+package com.haha.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 设备补货单明细实体
+ */
+@Data
+@TableName("t_replenishment_order_item")
+public class ReplenishmentOrderItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 补货单ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long orderId;
+
+    /**
+     * 设备SN号
+     */
+    private String deviceId;
+
+    /**
+     * 商品ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long productId;
+
+    /**
+     * 商品编码
+     */
+    private String productCode;
+
+    /**
+     * 商品名称
+     */
+    private String productName;
+
+    /**
+     * 计划补货数量
+     */
+    private Integer plannedQuantity;
+
+    /**
+     * 实际补货数量
+     */
+    private Integer actualQuantity;
+
+    /**
+     * 补货前库存
+     */
+    private Integer beforeStock;
+
+    /**
+     * 补货后库存
+     */
+    private Integer afterStock;
+
+    /**
+     * 单价
+     */
+    private BigDecimal unitPrice;
+
+    /**
+     * 小计金额
+     */
+    private BigDecimal totalPrice;
+
+    /**
+     * 货架层号
+     */
+    private Integer shelfNum;
+
+    /**
+     * 货道位置
+     */
+    private String position;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime createTime;
+}

+ 101 - 0
haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderCreateDTO.java

@@ -0,0 +1,101 @@
+package com.haha.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 补货单创建DTO
+ */
+@Data
+public class ReplenishmentOrderCreateDTO {
+
+    /**
+     * 设备SN号
+     */
+    @NotBlank(message = "设备ID不能为空")
+    private String deviceId;
+
+    /**
+     * 门店ID
+     */
+    private Long shopId;
+
+    /**
+     * 供应商名称
+     */
+    private String supplierName;
+
+    /**
+     * ERP仓库名称
+     */
+    private String warehouseName;
+
+    /**
+     * 预计到货时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime expectedArrivalTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 补货商品列表
+     */
+    @NotEmpty(message = "补货商品列表不能为空")
+    @Valid
+    private List<ReplenishmentOrderItemDTO> items;
+
+    @Data
+    public static class ReplenishmentOrderItemDTO {
+
+        /**
+         * 商品ID
+         */
+        @NotNull(message = "商品ID不能为空")
+        private Long productId;
+
+        /**
+         * 商品编码
+         */
+        private String productCode;
+
+        /**
+         * 商品名称
+         */
+        private String productName;
+
+        /**
+         * 计划补货数量
+         */
+        @NotNull(message = "计划补货数量不能为空")
+        @Min(value = 1, message = "计划补货数量至少为1")
+        private Integer plannedQuantity;
+
+        /**
+         * 单价
+         */
+        private BigDecimal unitPrice;
+
+        /**
+         * 货架层号
+         */
+        private Integer shelfNum;
+
+        /**
+         * 货道位置
+         */
+        private String position;
+    }
+}

+ 47 - 0
haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderQueryDTO.java

@@ -0,0 +1,47 @@
+package com.haha.entity.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 补货单查询DTO
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ReplenishmentOrderQueryDTO extends PageQueryDTO {
+
+    /**
+     * 补货单号(模糊搜索)
+     */
+    private String orderNo;
+
+    /**
+     * 设备SN号
+     */
+    private String deviceId;
+
+    /**
+     * 门店ID
+     */
+    private Long shopId;
+
+    /**
+     * 状态
+     */
+    private Integer status;
+
+    /**
+     * 开始时间(yyyy-MM-dd)
+     */
+    private String startTime;
+
+    /**
+     * 结束时间(yyyy-MM-dd)
+     */
+    private String endTime;
+
+    /**
+     * 创建人ID
+     */
+    private Long creatorId;
+}

+ 47 - 0
haha-entity/src/main/java/com/haha/entity/dto/ReplenishmentOrderUpdateDTO.java

@@ -0,0 +1,47 @@
+package com.haha.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 补货单更新DTO
+ */
+@Data
+public class ReplenishmentOrderUpdateDTO {
+
+    @NotNull(message = "补货单ID不能为空")
+    private Long id;
+
+    /**
+     * 供应商名称
+     */
+    private String supplierName;
+
+    /**
+     * ERP仓库名称
+     */
+    private String warehouseName;
+
+    /**
+     * 预计到货时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private LocalDateTime expectedArrivalTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 更新商品列表(会替换原明细)
+     */
+    @Valid
+    private List<ReplenishmentOrderCreateDTO.ReplenishmentOrderItemDTO> items;
+}

+ 28 - 0
haha-mapper/src/main/java/com/haha/mapper/ReplenishmentOrderItemMapper.java

@@ -0,0 +1,28 @@
+package com.haha.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.haha.entity.ReplenishmentOrderItem;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 补货单明细Mapper
+ */
+@Mapper
+public interface ReplenishmentOrderItemMapper extends BaseMapper<ReplenishmentOrderItem> {
+
+    /**
+     * 根据补货单ID查询明细列表
+     */
+    @Select("SELECT * FROM t_replenishment_order_item WHERE order_id = #{orderId}")
+    List<ReplenishmentOrderItem> selectByOrderId(@Param("orderId") Long orderId);
+
+    /**
+     * 根据补货单ID删除明细
+     */
+    @Select("DELETE FROM t_replenishment_order_item WHERE order_id = #{orderId}")
+    void deleteByOrderId(@Param("orderId") Long orderId);
+}

+ 19 - 0
haha-mapper/src/main/java/com/haha/mapper/ReplenishmentOrderMapper.java

@@ -0,0 +1,19 @@
+package com.haha.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.haha.entity.ReplenishmentOrder;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 补货单Mapper
+ */
+@Mapper
+public interface ReplenishmentOrderMapper extends BaseMapper<ReplenishmentOrder> {
+
+    /**
+     * 查询当天最大单号(用于生成新单号)
+     */
+    @Select("SELECT MAX(order_no) FROM t_replenishment_order WHERE order_no LIKE CONCAT('RO', DATE_FORMAT(NOW(), '%Y%m%d'), '%')")
+    String selectMaxOrderNoToday();
+}

+ 7 - 0
haha-service/pom.xml

@@ -34,6 +34,13 @@
             <artifactId>haha-sdk</artifactId>
         </dependency>
 
+        <!-- 企得宝 ERP SDK -->
+        <dependency>
+            <groupId>com.qdb</groupId>
+            <artifactId>qdb-sdk</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
         <!-- Spring Boot Web - provided scope -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 67 - 0
haha-service/src/main/java/com/haha/service/ReplenishmentOrderService.java

@@ -0,0 +1,67 @@
+package com.haha.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.haha.entity.ReplenishmentOrder;
+import com.haha.entity.ReplenishmentOrderItem;
+import com.haha.entity.dto.ReplenishmentOrderCreateDTO;
+import com.haha.entity.dto.ReplenishmentOrderQueryDTO;
+import com.haha.entity.dto.ReplenishmentOrderUpdateDTO;
+
+import java.util.List;
+
+/**
+ * 补货单服务接口
+ */
+public interface ReplenishmentOrderService extends IService<ReplenishmentOrder> {
+
+    /**
+     * 分页查询补货单
+     */
+    IPage<ReplenishmentOrder> getPage(ReplenishmentOrderQueryDTO queryDTO);
+
+    /**
+     * 获取补货单详情(含明细)
+     */
+    ReplenishmentOrder getDetailWithItems(Long id);
+
+    /**
+     * 获取补货单的明细列表
+     */
+    List<ReplenishmentOrderItem> getItems(Long orderId);
+
+    /**
+     * 创建补货单
+     */
+    ReplenishmentOrder createOrder(ReplenishmentOrderCreateDTO dto, Long creatorId, String creatorName);
+
+    /**
+     * 更新补货单(仅草稿状态可更新)
+     */
+    ReplenishmentOrder updateOrder(ReplenishmentOrderUpdateDTO dto);
+
+    /**
+     * 删除补货单(仅草稿状态可删除)
+     */
+    void deleteOrder(Long id);
+
+    /**
+     * 提交补货单(草稿→已提交)
+     */
+    ReplenishmentOrder submitOrder(Long id);
+
+    /**
+     * 同步到ERP(已提交→已同步ERP)
+     */
+    ReplenishmentOrder syncToErp(Long id);
+
+    /**
+     * 完成补货单(已同步ERP→已完成)
+     */
+    ReplenishmentOrder completeOrder(Long id);
+
+    /**
+     * 取消补货单(草稿/已提交→已取消)
+     */
+    ReplenishmentOrder cancelOrder(Long id);
+}

+ 418 - 0
haha-service/src/main/java/com/haha/service/impl/ReplenishmentOrderServiceImpl.java

@@ -0,0 +1,418 @@
+package com.haha.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.haha.common.enums.ReplenishmentOrderStatusEnum;
+import com.haha.common.exception.BusinessException;
+import com.haha.entity.ReplenishmentOrder;
+import com.haha.entity.ReplenishmentOrderItem;
+import com.haha.entity.dto.ReplenishmentOrderCreateDTO;
+import com.haha.entity.dto.ReplenishmentOrderQueryDTO;
+import com.haha.entity.dto.ReplenishmentOrderUpdateDTO;
+import com.haha.mapper.ReplenishmentOrderItemMapper;
+import com.haha.mapper.ReplenishmentOrderMapper;
+import com.haha.service.ReplenishmentOrderService;
+import com.qdb.sdk.QdbClient;
+import com.qdb.sdk.model.request.OrderGoods;
+import com.qdb.sdk.model.request.OrderSaveRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 补货单服务实现
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ReplenishmentOrderServiceImpl extends ServiceImpl<ReplenishmentOrderMapper, ReplenishmentOrder>
+        implements ReplenishmentOrderService {
+
+    private final ReplenishmentOrderMapper orderMapper;
+    private final ReplenishmentOrderItemMapper orderItemMapper;
+
+    @Autowired(required = false)
+    private QdbClient qdbClient;
+
+    @Override
+    public IPage<ReplenishmentOrder> getPage(ReplenishmentOrderQueryDTO queryDTO) {
+        queryDTO.validate();
+
+        LambdaQueryWrapper<ReplenishmentOrder> wrapper = new LambdaQueryWrapper<>();
+
+        if (StringUtils.hasText(queryDTO.getOrderNo())) {
+            wrapper.like(ReplenishmentOrder::getOrderNo, queryDTO.getOrderNo());
+        }
+        if (StringUtils.hasText(queryDTO.getDeviceId())) {
+            wrapper.eq(ReplenishmentOrder::getDeviceId, queryDTO.getDeviceId());
+        }
+        if (queryDTO.getShopId() != null) {
+            wrapper.eq(ReplenishmentOrder::getShopId, queryDTO.getShopId());
+        }
+        if (queryDTO.getStatus() != null) {
+            wrapper.eq(ReplenishmentOrder::getStatus, queryDTO.getStatus());
+        }
+        if (queryDTO.getCreatorId() != null) {
+            wrapper.eq(ReplenishmentOrder::getCreatorId, queryDTO.getCreatorId());
+        }
+        if (StringUtils.hasText(queryDTO.getStartTime())) {
+            wrapper.ge(ReplenishmentOrder::getCreateTime,
+                    LocalDate.parse(queryDTO.getStartTime()).atStartOfDay());
+        }
+        if (StringUtils.hasText(queryDTO.getEndTime())) {
+            wrapper.le(ReplenishmentOrder::getCreateTime,
+                    LocalDate.parse(queryDTO.getEndTime()).atTime(23, 59, 59));
+        }
+
+        wrapper.orderByDesc(ReplenishmentOrder::getCreateTime);
+
+        Page<ReplenishmentOrder> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
+        IPage<ReplenishmentOrder> result = page(page, wrapper);
+
+        for (ReplenishmentOrder order : result.getRecords()) {
+            fillStatusLabel(order);
+            int count = orderItemMapper.selectByOrderId(order.getId()).size();
+            order.setItemCount(count);
+        }
+
+        return result;
+    }
+
+    @Override
+    public ReplenishmentOrder getDetailWithItems(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        fillStatusLabel(order);
+        return order;
+    }
+
+    @Override
+    public List<ReplenishmentOrderItem> getItems(Long orderId) {
+        return orderItemMapper.selectByOrderId(orderId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder createOrder(ReplenishmentOrderCreateDTO dto, Long creatorId, String creatorName) {
+        ReplenishmentOrder order = new ReplenishmentOrder();
+        order.setOrderNo(generateOrderNo());
+        order.setDeviceId(dto.getDeviceId());
+        order.setShopId(dto.getShopId());
+        order.setStatus(ReplenishmentOrderStatusEnum.DRAFT.getCode());
+        order.setSupplierName(dto.getSupplierName());
+        order.setWarehouseName(dto.getWarehouseName());
+        order.setExpectedArrivalTime(dto.getExpectedArrivalTime());
+        order.setRemark(dto.getRemark());
+        order.setCreatorId(creatorId);
+        order.setCreatorName(creatorName);
+        order.setCreateTime(LocalDateTime.now());
+        order.setUpdateTime(LocalDateTime.now());
+
+        int totalQuantity = 0;
+        BigDecimal totalAmount = BigDecimal.ZERO;
+        List<ReplenishmentOrderItem> items = new ArrayList<>();
+
+        for (ReplenishmentOrderCreateDTO.ReplenishmentOrderItemDTO itemDto : dto.getItems()) {
+            ReplenishmentOrderItem item = new ReplenishmentOrderItem();
+            item.setDeviceId(dto.getDeviceId());
+            item.setProductId(itemDto.getProductId());
+            item.setProductCode(itemDto.getProductCode());
+            item.setProductName(itemDto.getProductName());
+            item.setPlannedQuantity(itemDto.getPlannedQuantity());
+            item.setUnitPrice(itemDto.getUnitPrice());
+            item.setShelfNum(itemDto.getShelfNum());
+            item.setPosition(itemDto.getPosition());
+
+            if (itemDto.getUnitPrice() != null) {
+                BigDecimal itemTotal = itemDto.getUnitPrice()
+                        .multiply(BigDecimal.valueOf(itemDto.getPlannedQuantity()));
+                item.setTotalPrice(itemTotal);
+                totalAmount = totalAmount.add(itemTotal);
+            }
+            totalQuantity += itemDto.getPlannedQuantity();
+            items.add(item);
+        }
+
+        order.setTotalQuantity(totalQuantity);
+        order.setTotalAmount(totalAmount);
+
+        save(order);
+
+        for (ReplenishmentOrderItem item : items) {
+            item.setOrderId(order.getId());
+            item.setCreateTime(LocalDateTime.now());
+            orderItemMapper.insert(item);
+        }
+
+        log.info("创建补货单: orderNo={}, deviceId={}, items={}", order.getOrderNo(), dto.getDeviceId(), items.size());
+
+        fillStatusLabel(order);
+        order.setItemCount(items.size());
+        return order;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder updateOrder(ReplenishmentOrderUpdateDTO dto) {
+        ReplenishmentOrder order = getById(dto.getId());
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        if (order.getStatus() != ReplenishmentOrderStatusEnum.DRAFT.getCode()) {
+            throw new BusinessException(400, "仅草稿状态的补货单可编辑");
+        }
+
+        if (dto.getSupplierName() != null) {
+            order.setSupplierName(dto.getSupplierName());
+        }
+        if (dto.getWarehouseName() != null) {
+            order.setWarehouseName(dto.getWarehouseName());
+        }
+        if (dto.getExpectedArrivalTime() != null) {
+            order.setExpectedArrivalTime(dto.getExpectedArrivalTime());
+        }
+        if (dto.getRemark() != null) {
+            order.setRemark(dto.getRemark());
+        }
+        order.setUpdateTime(LocalDateTime.now());
+
+        if (dto.getItems() != null && !dto.getItems().isEmpty()) {
+            orderItemMapper.deleteByOrderId(dto.getId());
+
+            int totalQuantity = 0;
+            BigDecimal totalAmount = BigDecimal.ZERO;
+
+            for (ReplenishmentOrderCreateDTO.ReplenishmentOrderItemDTO itemDto : dto.getItems()) {
+                ReplenishmentOrderItem item = new ReplenishmentOrderItem();
+                item.setOrderId(dto.getId());
+                item.setDeviceId(order.getDeviceId());
+                item.setProductId(itemDto.getProductId());
+                item.setProductCode(itemDto.getProductCode());
+                item.setProductName(itemDto.getProductName());
+                item.setPlannedQuantity(itemDto.getPlannedQuantity());
+                item.setUnitPrice(itemDto.getUnitPrice());
+                item.setShelfNum(itemDto.getShelfNum());
+                item.setPosition(itemDto.getPosition());
+
+                if (itemDto.getUnitPrice() != null) {
+                    BigDecimal itemTotal = itemDto.getUnitPrice()
+                            .multiply(BigDecimal.valueOf(itemDto.getPlannedQuantity()));
+                    item.setTotalPrice(itemTotal);
+                    totalAmount = totalAmount.add(itemTotal);
+                }
+                totalQuantity += itemDto.getPlannedQuantity();
+                item.setCreateTime(LocalDateTime.now());
+                orderItemMapper.insert(item);
+            }
+
+            order.setTotalQuantity(totalQuantity);
+            order.setTotalAmount(totalAmount);
+        }
+
+        updateById(order);
+
+        log.info("更新补货单: id={}, orderNo={}", dto.getId(), order.getOrderNo());
+
+        fillStatusLabel(order);
+        return order;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteOrder(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        if (order.getStatus() != ReplenishmentOrderStatusEnum.DRAFT.getCode()) {
+            throw new BusinessException(400, "仅草稿状态的补货单可删除");
+        }
+
+        orderItemMapper.deleteByOrderId(id);
+        removeById(id);
+
+        log.info("删除补货单: id={}, orderNo={}", id, order.getOrderNo());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder submitOrder(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        if (order.getStatus() != ReplenishmentOrderStatusEnum.DRAFT.getCode()) {
+            throw new BusinessException(400, "仅草稿状态的补货单可提交");
+        }
+
+        order.setStatus(ReplenishmentOrderStatusEnum.SUBMITTED.getCode());
+        order.setUpdateTime(LocalDateTime.now());
+        updateById(order);
+
+        log.info("提交补货单: id={}, orderNo={}", id, order.getOrderNo());
+
+        fillStatusLabel(order);
+        return order;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder syncToErp(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        if (order.getStatus() != ReplenishmentOrderStatusEnum.SUBMITTED.getCode()) {
+            throw new BusinessException(400, "仅已提交状态的补货单可同步到ERP");
+        }
+
+        if (qdbClient == null) {
+            throw new BusinessException(500, "ERP客户端未配置,无法同步");
+        }
+
+        List<ReplenishmentOrderItem> items = orderItemMapper.selectByOrderId(id);
+        if (items.isEmpty()) {
+            throw new BusinessException(400, "补货单明细为空,无法同步");
+        }
+
+        // 构建订单商品列表
+        List<OrderGoods> goodsList = items.stream().map(item -> {
+            BigDecimal price = item.getUnitPrice() != null ? item.getUnitPrice() : BigDecimal.ZERO;
+            BigDecimal qty = BigDecimal.valueOf(item.getPlannedQuantity());
+            return OrderGoods.builder()
+                    .platformGoodsCode(item.getProductCode() != null ? item.getProductCode() : "")
+                    .platformGoodsName(item.getProductName() != null ? item.getProductName() : "")
+                    .quantity(qty)
+                    .price(price)
+                    .realPrice(price)
+                    .build();
+        }).collect(Collectors.toList());
+
+        // 构建订单上传请求
+        // TODO: shopId 需要映射为 ERP 系统中的店铺ID,当前暂用设备关联的门店ID
+        // TODO: receiver* 收件人信息需从设备/门店地址中获取,当前暂不填充
+        // TODO: warehouseName 需确认与 ERP 仓库名称一致
+        OrderSaveRequest request = OrderSaveRequest.builder()
+                .shopId(order.getShopId())
+                .tradeNo(order.getOrderNo())
+                .platformTradeStatus("02")
+                .platformRefundStatus("0")
+                .orderTypeCode("3")
+                .postAmount(BigDecimal.ZERO)
+                .payAmount(order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO)
+                // TODO: 从设备/门店信息中获取收件人地址
+                // .receiverProvince(null)
+                // .receiverCity(null)
+                // .receiverDistrict(null)
+                // .receiverAddress(null)
+                // .receiverName(null)
+                // .receiverMobile(null)
+                .ordersGoods(goodsList)
+                .buyerMessage(order.getRemark())
+                .sellerMessage("设备补货单: " + order.getDeviceId())
+                // TODO: 确认仓库名称与 ERP 一致
+                .warehouseName(order.getWarehouseName())
+                .build();
+
+        try {
+            qdbClient.getOrderApi().saveOrder(request);
+
+            order.setStatus(ReplenishmentOrderStatusEnum.SYNCED_TO_ERP.getCode());
+            order.setErpSyncTime(LocalDateTime.now());
+            order.setUpdateTime(LocalDateTime.now());
+            updateById(order);
+
+            log.info("补货单同步ERP成功: id={}, orderNo={}", id, order.getOrderNo());
+        } catch (Exception e) {
+            log.error("补货单同步ERP失败: id={}, orderNo={}, error={}", id, order.getOrderNo(), e.getMessage());
+            throw new BusinessException(500, "ERP同步失败: " + e.getMessage());
+        }
+
+        fillStatusLabel(order);
+        return order;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder completeOrder(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        if (order.getStatus() != ReplenishmentOrderStatusEnum.SYNCED_TO_ERP.getCode()) {
+            throw new BusinessException(400, "仅已同步ERP的补货单可完成");
+        }
+
+        order.setStatus(ReplenishmentOrderStatusEnum.COMPLETED.getCode());
+        order.setUpdateTime(LocalDateTime.now());
+        updateById(order);
+
+        log.info("完成补货单: id={}, orderNo={}", id, order.getOrderNo());
+
+        fillStatusLabel(order);
+        return order;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ReplenishmentOrder cancelOrder(Long id) {
+        ReplenishmentOrder order = getById(id);
+        if (order == null) {
+            throw new BusinessException(404, "补货单不存在");
+        }
+        int status = order.getStatus();
+        if (status != ReplenishmentOrderStatusEnum.DRAFT.getCode()
+                && status != ReplenishmentOrderStatusEnum.SUBMITTED.getCode()) {
+            throw new BusinessException(400, "仅草稿或已提交状态的补货单可取消");
+        }
+
+        order.setStatus(ReplenishmentOrderStatusEnum.CANCELLED.getCode());
+        order.setUpdateTime(LocalDateTime.now());
+        updateById(order);
+
+        log.info("取消补货单: id={}, orderNo={}", id, order.getOrderNo());
+
+        fillStatusLabel(order);
+        return order;
+    }
+
+    /**
+     * 生成补货单号:RO + yyyyMMdd + 4位序号
+     */
+    private String generateOrderNo() {
+        String datePrefix = "RO" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        String maxNo = orderMapper.selectMaxOrderNoToday();
+        int seq = 1;
+        if (maxNo != null && maxNo.startsWith(datePrefix)) {
+            try {
+                seq = Integer.parseInt(maxNo.substring(maxNo.length() - 4)) + 1;
+            } catch (NumberFormatException e) {
+                log.warn("解析补货单号失败: {}", maxNo);
+            }
+        }
+        return datePrefix + String.format("%04d", seq);
+    }
+
+    private void fillStatusLabel(ReplenishmentOrder order) {
+        if (order.getStatus() != null) {
+            var statusEnum = ReplenishmentOrderStatusEnum.getLabelByCode(order.getStatus());
+            order.setStatusLabel(statusEnum.getLabel());
+            order.setStatusColor(statusEnum.getColor());
+        }
+    }
+}

+ 5 - 0
qdb-sdk/src/main/java/com/qdb/sdk/QdbClient.java

@@ -1,6 +1,7 @@
 package com.qdb.sdk;
 
 import com.qdb.sdk.api.OrderApi;
+import com.qdb.sdk.api.PurchaseApi;
 import com.qdb.sdk.util.HttpUtil;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
@@ -62,6 +63,9 @@ public class QdbClient {
     /** 订单管理 API */
     private final OrderApi orderApi;
 
+    /** 采购管理 API */
+    private final PurchaseApi purchaseApi;
+
     /**
      * 构造方法
      *
@@ -76,6 +80,7 @@ public class QdbClient {
 
         // 初始化各业务 API 模块
         this.orderApi = new OrderApi(this);
+        this.purchaseApi = new PurchaseApi(this);
 
         log.info("企得宝ERP SDK 初始化成功, clientId: {}", config.getClientId());
     }

+ 30 - 0
qdb-sdk/src/main/java/com/qdb/sdk/api/PurchaseApi.java

@@ -0,0 +1,30 @@
+package com.qdb.sdk.api;
+
+import com.qdb.sdk.QdbClient;
+import com.qdb.sdk.QdbException;
+import com.qdb.sdk.model.request.PurchaseOrderAddRequest;
+import com.qdb.sdk.model.response.QdbResponse;
+
+/**
+ * 采购管理 API
+ * 提供采购订单创建、供应商查询等接口
+ */
+public class PurchaseApi extends BaseQdbApi {
+
+    private static final String METHOD_ADD_PURCHASE_ORDER = "foonsu.erp.purchaseOrders.addPurchaseOrder";
+
+    public PurchaseApi(QdbClient client) {
+        super(client);
+    }
+
+    /**
+     * 创建采购订单
+     *
+     * @param request 采购订单请求参数
+     * @return 响应(data为null表示成功)
+     * @throws QdbException 调用失败时抛出
+     */
+    public QdbResponse<Void> addPurchaseOrder(PurchaseOrderAddRequest request) throws QdbException {
+        return execute(METHOD_ADD_PURCHASE_ORDER, request);
+    }
+}

+ 58 - 0
qdb-sdk/src/main/java/com/qdb/sdk/model/request/PurchaseOrderAddRequest.java

@@ -0,0 +1,58 @@
+package com.qdb.sdk.model.request;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 采购订单创建请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PurchaseOrderAddRequest {
+
+    /**
+     * 仓库名称(必填)
+     */
+    private String warehouseName;
+
+    /**
+     * 采购类型:1-标准采购, 2-新品采购, 3-补货采购(必填)
+     */
+    private String purchaseType;
+
+    /**
+     * 供应商名称(必填)
+     */
+    private String provideName;
+
+    /**
+     * 预计到货时间(必填)
+     */
+    private String expectedArrivalTime;
+
+    /**
+     * 实际支付总金额(未输入默认取商品总金额)
+     */
+    private String theTotalAmountActuallyPaid;
+
+    /**
+     * 备注
+     */
+    private String reamrk;
+
+    /**
+     * 外部唯一标识(长度不超过32)
+     */
+    private String externalUniqueCode;
+
+    /**
+     * 商品信息列表(必填)
+     */
+    private List<PurchaseOrderGoods> purchaseOrdersGoodsInfoList;
+}

+ 48 - 0
qdb-sdk/src/main/java/com/qdb/sdk/model/request/PurchaseOrderGoods.java

@@ -0,0 +1,48 @@
+package com.qdb.sdk.model.request;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 采购订单商品
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PurchaseOrderGoods {
+
+    /**
+     * 商品类型:0-普通商品, 1-批次商品, 2-唯一码商品(必填)
+     */
+    private String goodsType;
+
+    /**
+     * 商品编码(必填)
+     */
+    private String goodsCode;
+
+    /**
+     * 商品名称(必填)
+     */
+    private String goodsName;
+
+    /**
+     * 商品数量(必填)
+     */
+    private BigDecimal quantity;
+
+    /**
+     * 单价(必填)
+     */
+    private BigDecimal price;
+
+    /**
+     * 商品总价(必填)
+     */
+    private BigDecimal totalAmount;
+}