update customer package
This commit is contained in:
@@ -61,8 +61,5 @@ async function fundRecordList(page: FundRecordPage): Promise<SalesOrderPageResul
|
||||
async function fundRecordDetail(id: string): Promise<FundRecord> {
|
||||
return await axios.post(`${FUND_URI}/detail`, undefined, { params: { id } });
|
||||
}
|
||||
async function fundRecordRevoke(request: OrderPaymentRevokeRequest): Promise<FundRecord> {
|
||||
return await axios.post(`${FUND_URI}/revoke`, request);
|
||||
}
|
||||
|
||||
export default { list, detail, createDirectProduct, update, cancel, receivePayment, verifyPayment, rejectPayment, cancelPayment, refund, confirmNoRefund, revokeFund, fundRecordList, fundRecordDetail, fundRecordRevoke };
|
||||
export default { list, detail, createDirectProduct, update, cancel, receivePayment, verifyPayment, rejectPayment, cancelPayment, refund, confirmNoRefund, revokeFund, fundRecordList, fundRecordDetail };
|
||||
|
||||
@@ -84,30 +84,28 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PackageAPI from "@/api/PackageAPI";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { CustomerPackageItem, GaoCustomerPackage } from "@/types/Package";
|
||||
import type { OrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
||||
import type { SalesOrder, SalesOrderItem } from "@/types/SalesOrder";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
||||
import type { GaoSalesOrderItem, SalesOrder } from "@/types/SalesOrder";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
import type { Attachment } from "timi-web-api";
|
||||
import { createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity } from "@/utils/package";
|
||||
import { comparePackageQuantity, createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity } from "@/utils/package";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { uploadPhotoList } from "@/utils/photo";
|
||||
import { getProductPhotoURL } from "@/utils/product";
|
||||
|
||||
type SourceItem = SalesOrderItem | CustomerPackageItem;
|
||||
defineOptions({ name: "OrderFulfillmentCreate" });
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const order = ref<SalesOrder>();
|
||||
const customerPackage = ref<GaoCustomerPackage>();
|
||||
const quantityMap = reactive<Record<string, string>>({});
|
||||
const deliveredAtText = ref("");
|
||||
const draftTime = ref("");
|
||||
@@ -117,12 +115,14 @@ const requestNo = ref("");
|
||||
const isSaving = ref(false);
|
||||
const photoItems = ref<PhotoItem[]>([]);
|
||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||
const isPackage = computed(() => !!customerPackage.value);
|
||||
const sourceItems = computed<SourceItem[]>(() => customerPackage.value?.itemList || order.value?.itemList || []);
|
||||
const isPackage = computed(() => order.value?.orderType === "PACKAGE");
|
||||
const expectedItemType = computed(() => isPackage.value ? "PACKAGE_ENTITLEMENT" : "DIRECT_PRODUCT");
|
||||
const sourceItems = computed<GaoSalesOrderItem[]>(() => (order.value?.itemList || []).filter(item => item.itemType === expectedItemType.value));
|
||||
const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.productStock.create));
|
||||
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const salesOrderId = String(route.query.salesOrderId || "");
|
||||
const customerPackageId = String(route.query.customerPackageId || "");
|
||||
if (!salesOrderId) {
|
||||
Message.warning("请从订单详情或客户套餐详情进入履约创建");
|
||||
await router.replace("/management/order-fulfillments");
|
||||
@@ -130,26 +130,20 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
try {
|
||||
order.value = await SalesOrderAPI.detail(salesOrderId);
|
||||
if (customerPackageId) customerPackage.value = await PackageAPI.customerPackageDetail(customerPackageId);
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "履约来源加载失败"));
|
||||
}
|
||||
}
|
||||
|
||||
function getAvailableQuantity(item: SourceItem): string {
|
||||
if (isPackage.value) {
|
||||
return getPackageQuantityText((item as CustomerPackageItem).availableQuantity);
|
||||
}
|
||||
const orderItem = item as SalesOrderItem;
|
||||
const availableQuantity = Math.max(0, Number(orderItem.quantity || 0) - Number(orderItem.reservedQuantity || 0) - Number(orderItem.fulfilledQuantity || 0));
|
||||
return getPackageQuantityText(availableQuantity.toFixed(3));
|
||||
function getAvailableQuantity(item: GaoSalesOrderItem): string {
|
||||
return getPackageQuantityText(item.availableQuantity);
|
||||
}
|
||||
|
||||
function getItemDescription(item: SourceItem): string {
|
||||
function getItemDescription(item: GaoSalesOrderItem): string {
|
||||
return ` · 剩余 ${getAvailableQuantity(item)}`;
|
||||
}
|
||||
|
||||
function fillAllQuantity(item: SourceItem): void {
|
||||
function fillAllQuantity(item: GaoSalesOrderItem): void {
|
||||
if (item.id) {
|
||||
quantityMap[item.id] = getAvailableQuantity(item);
|
||||
}
|
||||
@@ -178,6 +172,14 @@ function validate(): boolean {
|
||||
Message.warning("请从订单详情或客户套餐详情进入履约创建");
|
||||
return false;
|
||||
}
|
||||
if (order.value.status !== "ACTIVE") {
|
||||
Message.warning("当前订单不是进行中状态,不能创建履约");
|
||||
return false;
|
||||
}
|
||||
if (!canFulfillByPayment.value) {
|
||||
Message.warning("订单尚未满足履约收款条件");
|
||||
return false;
|
||||
}
|
||||
if (!buildAttachments().length) {
|
||||
Message.warning("履约必须上传至少一张照片");
|
||||
return false;
|
||||
@@ -188,10 +190,15 @@ function validate(): boolean {
|
||||
return false;
|
||||
}
|
||||
for (const item of selected) {
|
||||
if (!normalizePackageQuantity(quantityMap[item.id || ""])) {
|
||||
const quantity = normalizePackageQuantity(quantityMap[item.id || ""]);
|
||||
if (!quantity) {
|
||||
Message.warning("履约数量必须是正数,最多三位小数");
|
||||
return false;
|
||||
}
|
||||
if (0 < comparePackageQuantity(quantity, item.availableQuantity ?? "0")) {
|
||||
Message.warning(`${item.productNameSnapshot || "产品"} 的可履约数量不足`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -201,10 +208,10 @@ async function save(): Promise<void> {
|
||||
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const itemList: OrderFulfillmentItemRequest[] = sourceItems.value
|
||||
const itemList: GaoOrderFulfillmentItemRequest[] = sourceItems.value
|
||||
.filter(item => !!item.id && quantityMap[item.id])
|
||||
.map(item => ({
|
||||
...(isPackage.value ? { sourceCustomerPackageItemId: item.id } : { sourceOrderItemId: item.id }),
|
||||
sourceOrderItemId: item.id!,
|
||||
quantity: normalizePackageQuantity(quantityMap[item.id || ""])!
|
||||
}));
|
||||
const result = await FulfillmentAPI.create({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template v-if="fulfillment">
|
||||
<t-cell-group title="履约单信息" theme="card" v-entrance-transition>
|
||||
<t-cell title="销售订单" arrow @click="toOrder" />
|
||||
<t-cell title="履约类型" :note="getFulfillmentTypeText(fulfillment.fulfillmentType)" />
|
||||
<t-cell title="订单项类型" :note="getSalesOrderItemTypeText(sourceItemType)" />
|
||||
<t-cell title="状态"><template #note><t-tag :content="getFulfillmentStatusText(fulfillment.status)" variant="light-outline" :theme="getStatusTheme(fulfillment.status)" /></template></t-cell>
|
||||
<t-cell title="履约时间">
|
||||
<template #note>
|
||||
@@ -52,22 +52,22 @@
|
||||
<t-cell v-if="!fulfillment.itemList?.length" title="暂无履约明细" />
|
||||
</t-cell-group>
|
||||
<section
|
||||
v-if="(isWaitingStock && canVerify) || (isPending && (canVerify || canCreate)) || (isVerified && canRevoke)"
|
||||
v-if="(!isOrderCancelled && isWaitingStock && canVerify) || (!isOrderCancelled && isPending && (canVerify || canCreate)) || (isVerified && canRevoke)"
|
||||
class="actions"
|
||||
v-entrance-transition="140"
|
||||
>
|
||||
<t-button
|
||||
v-if="isWaitingStock && canVerify"
|
||||
v-if="!isOrderCancelled && isWaitingStock && canVerify"
|
||||
theme="primary"
|
||||
content="准备库存"
|
||||
@click="prepareStock"
|
||||
/>
|
||||
<div v-if="isPending && canVerify" class="pair">
|
||||
<div v-if="!isOrderCancelled && isPending && canVerify" class="pair">
|
||||
<t-button theme="primary" content="核销履约" @click="verify" />
|
||||
<t-button theme="danger" content="拒绝履约" @click="openReason('reject')" />
|
||||
</div>
|
||||
<t-button
|
||||
v-if="isPending && canCreate"
|
||||
v-if="!isOrderCancelled && isPending && canCreate"
|
||||
theme="default"
|
||||
content="撤销履约"
|
||||
@click="openReason('cancel')"
|
||||
@@ -119,16 +119,18 @@ import { BottomPopup } from "timi-tdesign-mobile";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
import type { Attachment } from "timi-web-api";
|
||||
import { createPackageRequestNo } from "@/utils/package";
|
||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getProductPhotoURL } from "@/utils/product";
|
||||
import { getFulfillmentStatusText, getFulfillmentTypeText, getStatusTheme } from "@/utils/salesOrder";
|
||||
import { getFulfillmentStatusText, getSalesOrderItemTypeText, getStatusTheme } from "@/utils/salesOrder";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { uploadPhotoList } from "@/utils/photo";
|
||||
@@ -140,6 +142,7 @@ const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const fulfillment = ref<OrderFulfillment>();
|
||||
const order = ref<SalesOrder>();
|
||||
const loading = ref(false);
|
||||
const operating = ref(false);
|
||||
const reasonVisible = ref(false);
|
||||
@@ -163,14 +166,25 @@ const fulfillmentPhotoURLList = computed(() => fulfillmentPhotoItems.value.map(i
|
||||
const isWaitingStock = computed(() => fulfillment.value?.status === "WAITING_STOCK");
|
||||
const isPending = computed(() => fulfillment.value?.status === "PENDING");
|
||||
const isVerified = computed(() => fulfillment.value?.status === "VERIFIED");
|
||||
const isOrderCancelled = computed(() => order.value?.status === "CANCELLED");
|
||||
const canVerify = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.verify));
|
||||
const canCreate = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create));
|
||||
const canRevoke = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.revoke));
|
||||
const sourceItemType = computed<GaoSalesOrderItemType | undefined>(() => {
|
||||
const sourceOrderItemId = fulfillment.value?.itemList?.[0]?.sourceOrderItemId;
|
||||
return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType;
|
||||
});
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) return;
|
||||
loading.value = true;
|
||||
try { fulfillment.value = await FulfillmentAPI.detail(id); }
|
||||
try {
|
||||
const result = await FulfillmentAPI.detail(id);
|
||||
fulfillment.value = result;
|
||||
order.value = result.salesOrderId && userStore.hasPermissions(permissionCodes.salesOrder.read)
|
||||
? await SalesOrderAPI.detail(result.salesOrderId)
|
||||
: undefined;
|
||||
}
|
||||
catch (error) { Message.error(getErrorMessage(error, "履约单加载失败")); }
|
||||
finally { loading.value = false; }
|
||||
}
|
||||
@@ -194,9 +208,10 @@ function getCurrentRouteDepth(): number {
|
||||
}
|
||||
async function prepareStock(): Promise<void> {
|
||||
if (!fulfillment.value?.id || operating.value) return;
|
||||
if (isOrderCancelled.value) { Message.warning("已取消订单不能准备库存"); return; }
|
||||
operating.value = true;
|
||||
try {
|
||||
await FulfillmentAPI.prepareStock({ fulfillmentId: fulfillment.value.id });
|
||||
fulfillment.value = await FulfillmentAPI.prepareStock({ fulfillmentId: fulfillment.value.id, reason: "库存已备齐" });
|
||||
Message.success("库存准备已提交");
|
||||
await loadData();
|
||||
} catch (error) { Message.error(getErrorMessage(error, "库存准备失败")); }
|
||||
@@ -204,9 +219,10 @@ async function prepareStock(): Promise<void> {
|
||||
}
|
||||
async function verify(): Promise<void> {
|
||||
if (!fulfillment.value?.id || operating.value) return;
|
||||
if (isOrderCancelled.value) { Message.warning("已取消订单不能核销履约"); return; }
|
||||
operating.value = true;
|
||||
try {
|
||||
await FulfillmentAPI.verify({ fulfillmentId: fulfillment.value.id, reason: "" });
|
||||
fulfillment.value = await FulfillmentAPI.verify({ fulfillmentId: fulfillment.value.id, reason: "" });
|
||||
Message.success("履约已核销");
|
||||
await loadData();
|
||||
} catch (error) { Message.error(getErrorMessage(error, "履约核销失败")); }
|
||||
@@ -218,8 +234,9 @@ async function submitReason(): Promise<void> {
|
||||
operating.value = true;
|
||||
try {
|
||||
const payload = { fulfillmentId: fulfillment.value.id, reason: reason.value.trim() };
|
||||
if (reasonAction.value === "reject") await FulfillmentAPI.reject(payload);
|
||||
else await FulfillmentAPI.cancel(payload);
|
||||
fulfillment.value = reasonAction.value === "reject"
|
||||
? await FulfillmentAPI.reject(payload)
|
||||
: await FulfillmentAPI.cancel(payload);
|
||||
reasonVisible.value = false;
|
||||
Message.success(reasonAction.value === "reject" ? "履约已拒绝" : "履约已撤销");
|
||||
await loadData();
|
||||
@@ -236,7 +253,7 @@ async function submitRevoke(): Promise<void> {
|
||||
if (!revokeRequestNo.value) revokeRequestNo.value = createPackageRequestNo();
|
||||
operating.value = true;
|
||||
try {
|
||||
await FulfillmentAPI.revoke({
|
||||
fulfillment.value = await FulfillmentAPI.revoke({
|
||||
fulfillmentId: fulfillment.value.id,
|
||||
requestNo: revokeRequestNo.value,
|
||||
reason: revokeReason.value.trim(),
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
<div v-can="permissionCodes.orderFulfillment.read" class="fulfillment-list">
|
||||
<div class="filters">
|
||||
<t-dropdown-menu class="menus">
|
||||
<t-dropdown-item
|
||||
v-model="type"
|
||||
label="履约类型"
|
||||
:options="typeOptions"
|
||||
@change="selectType"
|
||||
/>
|
||||
<t-dropdown-item
|
||||
v-model="status"
|
||||
label="履约状态"
|
||||
@@ -47,7 +41,7 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||
import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
||||
import OrderFulfillmentListCell from "@/pages/fulfillment/components/OrderFulfillmentListCell.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { FulfillmentStatus, FulfillmentType, OrderFulfillment } from "@/types/Fulfillment";
|
||||
import type { GaoFulfillmentStatus, OrderFulfillment } from "@/types/Fulfillment";
|
||||
import type { NavBarAction } from "@/types/NavBarAction";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
@@ -76,8 +70,7 @@ const actions = computed<NavBarAction[]>(() => [
|
||||
action: toCustomerPackageList
|
||||
}
|
||||
]);
|
||||
const type = ref<"ALL" | FulfillmentType>("ALL");
|
||||
const status = ref<"ALL" | FulfillmentStatus>(getInitialStatus(route.query.status));
|
||||
const status = ref<"ALL" | GaoFulfillmentStatus>(getInitialStatus(route.query.status));
|
||||
const list = ref<OrderFulfillment[]>([]);
|
||||
const pageIndex = ref(0);
|
||||
const total = ref(0);
|
||||
@@ -85,11 +78,6 @@ const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const noMore = ref(false);
|
||||
const pageSize = 15;
|
||||
const typeOptions = [
|
||||
{ label: "全部类型", value: "ALL" },
|
||||
{ label: "套餐权益", value: "PACKAGE_ENTITLEMENT" },
|
||||
{ label: "直接产品", value: "DIRECT_PRODUCT" }
|
||||
];
|
||||
const statusOptions = [
|
||||
{ label: "全部状态", value: "ALL" },
|
||||
{ label: "等待库存", value: "WAITING_STOCK" },
|
||||
@@ -104,10 +92,10 @@ function getQueryText(value: unknown): string {
|
||||
return Array.isArray(value) ? "" : typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function getInitialStatus(value: unknown): "ALL" | FulfillmentStatus {
|
||||
function getInitialStatus(value: unknown): "ALL" | GaoFulfillmentStatus {
|
||||
const statusValue = getQueryText(value);
|
||||
if (["WAITING_STOCK", "PENDING", "VERIFIED", "REJECTED", "CANCELLED", "REVOKED"].includes(statusValue)) {
|
||||
return statusValue as FulfillmentStatus;
|
||||
return statusValue as GaoFulfillmentStatus;
|
||||
}
|
||||
return "ALL";
|
||||
}
|
||||
@@ -128,7 +116,6 @@ async function fetchList(reset: boolean): Promise<void> {
|
||||
index: pageIndex.value,
|
||||
size: pageSize,
|
||||
salesOrderId: getQueryText(route.query.salesOrderId) || undefined,
|
||||
fulfillmentType: type.value === "ALL" ? undefined : type.value,
|
||||
status: status.value === "ALL" ? undefined : status.value
|
||||
});
|
||||
const rows = result.list || [];
|
||||
@@ -159,16 +146,9 @@ async function loadMore(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function selectType(value: DropdownValue): Promise<void> {
|
||||
if (!Array.isArray(value)) {
|
||||
type.value = String(value) as "ALL" | FulfillmentType;
|
||||
await fetchList(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function selectStatus(value: DropdownValue): Promise<void> {
|
||||
if (!Array.isArray(value)) {
|
||||
status.value = String(value) as "ALL" | FulfillmentStatus;
|
||||
status.value = String(value) as "ALL" | GaoFulfillmentStatus;
|
||||
await fetchList(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,35 +7,36 @@
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<strong v-text="customerPackage.packageNameSnapshot || '未命名套餐'" />
|
||||
<t-tag :content="getCustomerPackageStatusText(customerPackage.status)" variant="light-outline" :theme="getStatusTheme(customerPackage.status)" />
|
||||
<t-tag :content="getSalesOrderStatusText(order?.status)" variant="light-outline" :theme="getStatusTheme(order?.status)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description><span>购买于 </span><pass-time-label :timestamp="order?.purchasedAt" underline /></template>
|
||||
<template #description><span>购买于 </span><pass-time-label :timestamp="order?.purchasedAt || customerPackage.salesOrder?.purchasedAt" underline /></template>
|
||||
</t-cell>
|
||||
<t-cell v-if="customerPackage.salesOrderId && canReadOrder" title="销售订单" :note="order?.orderNo || customerPackage.salesOrderId" arrow @click="toOrder" />
|
||||
<t-cell v-if="canReadPackageConfig && customerPackage.packageConfigId" title="套餐方案详情" arrow @click="toPackageConfig" />
|
||||
</t-cell-group>
|
||||
<card-container title="套餐权益" description="权益数量来自客户套餐快照,履约状态来自统一履约单" v-entrance-transition="210">
|
||||
<card-container title="套餐权益" description="权益数量来自销售订单项,履约状态来自统一履约单" v-entrance-transition="210">
|
||||
<t-cell-group>
|
||||
<t-cell v-for="item in customerPackage.itemList || []" :key="item.id" class="benefit">
|
||||
<t-cell v-for="item in salesOrderItems" :key="item.id" class="benefit">
|
||||
<template #title><div class="title"><strong v-text="item.productNameSnapshot || '未命名产品'" /><t-tag theme="primary" variant="light" :content="item.unitSnapshot || '未设置单位'" /></div></template>
|
||||
<template #description>
|
||||
<div class="stats">
|
||||
<span>总数 <strong v-text="getPackageQuantityText(item.totalQuantity)" /></span>
|
||||
<span>总数 <strong v-text="getPackageQuantityText(item.quantity)" /></span>
|
||||
<span>可履约 <strong v-text="getPackageQuantityText(item.availableQuantity)" /></span>
|
||||
<span>待核销 <strong v-text="getPackageQuantityText(item.pendingQuantity)" /></span>
|
||||
<span>已履约 <strong v-text="getPackageQuantityText(item.fulfilledQuantity)" /></span>
|
||||
<span>剩余 <strong v-text="getPackageQuantityText(item.remainingQuantity)" /></span>
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
<div v-if="!customerPackage.itemList?.length" class="empty" v-text="'暂无产品权益'" />
|
||||
<div v-if="!salesOrderItems.length" class="empty" v-text="'暂无产品权益'" />
|
||||
</t-cell-group>
|
||||
</card-container>
|
||||
<t-cell-group title="统一履约" theme="card" v-entrance-transition="280">
|
||||
<t-cell
|
||||
v-for="item in customerPackage.fulfillmentList || []"
|
||||
:key="item.id"
|
||||
:title="`${getFulfillmentTypeText(item.fulfillmentType)} · ${getFulfillmentStatusText(item.status)}`"
|
||||
:title="`${getSalesOrderItemTypeText(getFulfillmentItemType(item))} · ${getFulfillmentStatusText(item.status)}`"
|
||||
:note="getFulfillmentSummary(item)"
|
||||
arrow
|
||||
@click="toFulfillment(item.id)"
|
||||
@@ -43,6 +44,19 @@
|
||||
<t-cell v-if="!customerPackage.fulfillmentList?.length" title="暂无履约记录" />
|
||||
<t-cell v-if="canCreateFulfillment && hasAvailableItem" title="创建统一履约单" note="选择本次履约的权益数量" arrow @click="toCreateFulfillment" />
|
||||
</t-cell-group>
|
||||
<t-cell-group v-if="order" class="finance-panel" title="订单资金摘要" theme="card" v-entrance-transition="350">
|
||||
<t-cell title="成交金额" :note="formatPackageAmount(order.saleAmount)" />
|
||||
<t-cell title="已生效收款" :note="formatPackageAmount(order.paidAmount)" />
|
||||
<t-cell title="待核销收款" :note="formatPackageAmount(order.pendingPaidAmount)" />
|
||||
<t-cell title="有效退款" :note="formatPackageAmount(order.refundedAmount)" />
|
||||
<t-cell title="收款状态" :note="getSalesPaymentStatusText(order.paymentStatus)" />
|
||||
<t-cell title="退款状态" :note="getSalesRefundStatusText(order.refundStatus)" />
|
||||
</t-cell-group>
|
||||
<t-cell-group v-if="order?.status === 'CANCELLED'" class="cancel-info" title="取消记录" theme="card" v-entrance-transition="420">
|
||||
<t-cell title="取消时间" :note="order.cancelledAt ? Time.toDateTime(order.cancelledAt) : '未知'" />
|
||||
<t-cell title="取消人" :note="order.cancelOperator?.name || customerPackage.cancelOperator?.name || order.cancelOperatorUserId || '未知'" />
|
||||
<t-cell title="取消原因" :note="order.cancelReason || '未填写'" />
|
||||
</t-cell-group>
|
||||
<t-button v-if="canCancel" class="cell-button" theme="danger" @click="openCancel">取消客户套餐</t-button>
|
||||
</template>
|
||||
<t-empty v-else :description="isLoading ? '正在加载客户套餐' : '客户套餐不存在'" />
|
||||
@@ -73,10 +87,10 @@ import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { GaoCustomerPackage } from "@/types/Package";
|
||||
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||
import type { SalesOrder } from "@/types/SalesOrder";
|
||||
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getCustomerPackageStatusText, getPackageQuantityText } from "@/utils/package";
|
||||
import { getFulfillmentStatusText, getFulfillmentTypeText } from "@/utils/salesOrder";
|
||||
import { formatPackageAmount, getPackageQuantityText } from "@/utils/package";
|
||||
import { getFulfillmentStatusText, getSalesOrderItemTypeText, getSalesOrderStatusText, getSalesPaymentStatusText, getSalesRefundStatusText } from "@/utils/salesOrder";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -91,11 +105,15 @@ const isLoading = ref(false);
|
||||
const cancelVisible = ref(false);
|
||||
const cancelReason = ref("");
|
||||
const isCancelling = ref(false);
|
||||
const canCancel = computed(() => customerPackage.value?.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.customerPackage.cancel));
|
||||
const canCreateFulfillment = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create));
|
||||
const hasPendingFulfillment = computed(() => customerPackage.value?.fulfillmentList?.some(item => item.status === "WAITING_STOCK" || item.status === "PENDING") ?? false);
|
||||
const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.productStock.create));
|
||||
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
||||
const canCancel = computed(() => order.value?.status === "ACTIVE" && !hasPendingFulfillment.value && userStore.hasPermissions(permissionCodes.customerPackage.cancel));
|
||||
const canCreateFulfillment = computed(() => order.value?.status === "ACTIVE" && canFulfillByPayment.value && userStore.hasPermissions(permissionCodes.orderFulfillment.create));
|
||||
const canReadOrder = computed(() => userStore.hasPermissions(permissionCodes.salesOrder.read));
|
||||
const canReadPackageConfig = computed(() => userStore.hasPermissions(permissionCodes.packageConfig.read));
|
||||
const hasAvailableItem = computed(() => customerPackage.value?.itemList?.some(item => 0 < Number(item.availableQuantity)) ?? false);
|
||||
const salesOrderItems = computed(() => order.value?.itemList || customerPackage.value?.itemList || []);
|
||||
const hasAvailableItem = computed(() => salesOrderItems.value.some(item => 0 < Number(item.availableQuantity)));
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
@@ -103,9 +121,10 @@ async function loadData(): Promise<void> {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
customerPackage.value = await PackageAPI.customerPackageDetail(id);
|
||||
order.value = customerPackage.value.salesOrderId && canReadOrder.value
|
||||
? await SalesOrderAPI.detail(customerPackage.value.salesOrderId)
|
||||
: undefined;
|
||||
order.value = customerPackage.value.salesOrder;
|
||||
if (customerPackage.value.salesOrderId && canReadOrder.value) {
|
||||
order.value = await SalesOrderAPI.detail(customerPackage.value.salesOrderId);
|
||||
}
|
||||
} catch (error) {
|
||||
customerPackage.value = undefined;
|
||||
Message.error(getErrorMessage(error, "客户套餐加载失败"));
|
||||
@@ -115,6 +134,10 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
function getStatusTheme(status?: string): "success" | "warning" | "danger" | "default" { return status === "ACTIVE" ? "success" : status === "CANCELLED" ? "danger" : "default"; }
|
||||
function getFulfillmentSummary(item: OrderFulfillment): string { return `${item.deliveredAt ? Time.toDateTime(item.deliveredAt) : "时间未知"} · ${(item.itemList || []).length} 项`; }
|
||||
function getFulfillmentItemType(item: OrderFulfillment): GaoSalesOrderItemType | undefined {
|
||||
const sourceOrderItemId = item.itemList?.[0]?.sourceOrderItemId;
|
||||
return salesOrderItems.value.find(orderItem => orderItem.id === sourceOrderItemId)?.itemType;
|
||||
}
|
||||
async function toOrder(): Promise<void> {
|
||||
if (order.value?.id) {
|
||||
await router.push({
|
||||
@@ -126,10 +149,10 @@ async function toOrder(): Promise<void> {
|
||||
async function toPackageConfig(): Promise<void> { if (customerPackage.value?.packageConfigId) await router.push(`/management/package-configs/${customerPackage.value.packageConfigId}`); }
|
||||
async function toFulfillment(id?: string): Promise<void> { if (id) await router.push(`/management/order-fulfillments/${id}`); }
|
||||
async function toCreateFulfillment(): Promise<void> {
|
||||
if (order.value?.id && customerPackage.value?.id) {
|
||||
if (order.value?.id) {
|
||||
await router.push({
|
||||
path: "/management/order-fulfillments/create",
|
||||
query: { salesOrderId: order.value.id, customerPackageId: customerPackage.value.id }
|
||||
query: { salesOrderId: order.value.id }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<span> - </span>
|
||||
<span v-text="item.packageNameSnapshot || '未命名套餐'" />
|
||||
</div>
|
||||
<t-tag :content="getCustomerPackageStatusText(item.status)" variant="light-outline" :theme="getStatusTheme(item.status)" />
|
||||
<t-tag :content="getSalesOrderStatusText(getPackageStatus(item))" variant="light-outline" :theme="getStatusTheme(getPackageStatus(item))" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
@@ -77,10 +77,11 @@ import { type DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Text, Toolkit } from "timi-web";
|
||||
import PackageAPI from "@/api/PackageAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { CustomerPackageStatus, GaoCustomerPackage } from "@/types/Package";
|
||||
import type { GaoCustomerPackage } from "@/types/Package";
|
||||
import type { GaoSalesOrderStatus } from "@/types/SalesOrder";
|
||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getCustomerPackageStatusText } from "@/utils/package";
|
||||
import { getSalesOrderStatusText } from "@/utils/salesOrder";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -114,7 +115,7 @@ async function loadMore(): Promise<void> {
|
||||
index: pageIndex.value,
|
||||
size: 15,
|
||||
keyword: keyword.value || undefined,
|
||||
status: status.value === "ALL" ? undefined : status.value as CustomerPackageStatus
|
||||
status: status.value === "ALL" ? undefined : status.value as GaoSalesOrderStatus
|
||||
});
|
||||
const list = result.list || [];
|
||||
packageList.value = packageList.value.concat(list);
|
||||
@@ -164,7 +165,11 @@ async function toDetail(item: GaoCustomerPackage): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusTheme(statusValue?: CustomerPackageStatus): "success" | "warning" | "default" {
|
||||
function getPackageStatus(item: GaoCustomerPackage): GaoSalesOrderStatus | undefined {
|
||||
return item.salesOrder?.status;
|
||||
}
|
||||
|
||||
function getStatusTheme(statusValue?: GaoSalesOrderStatus): "success" | "warning" | "default" {
|
||||
return statusValue === "ACTIVE" ? "success" : statusValue === "CANCELLED" ? "warning" : "default";
|
||||
}
|
||||
|
||||
|
||||
@@ -409,12 +409,16 @@ async function submitRecord(): Promise<void> {
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const result = await submitPurchase();
|
||||
if (!result?.id) {
|
||||
if (!result?.id || !result.salesOrderId) {
|
||||
Message.error("套餐购买成功但未返回完整订单关联,请联系管理员");
|
||||
return;
|
||||
}
|
||||
await settingStore.playRegisterSuccessSound();
|
||||
Message.success("套餐购买已提交");
|
||||
await router.replace(`/management/customer-packages/${result.id}`);
|
||||
await router.replace({
|
||||
path: `/management/customer-packages/${result.id}`,
|
||||
query: { salesOrderId: result.salesOrderId }
|
||||
});
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "套餐购买提交失败"));
|
||||
} finally {
|
||||
|
||||
@@ -78,13 +78,13 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
||||
saleAmount,
|
||||
purchasedAt: options.buildRegisteredAt(),
|
||||
attachmentList: options.buildAttachments(),
|
||||
itemList: packagePurchaseItemList.value.map((item, index) => ({
|
||||
itemList: packagePurchaseItemList.value.length ? packagePurchaseItemList.value.map((item, index) => ({
|
||||
productId: item.productId,
|
||||
productUnitId: item.productUnitId,
|
||||
totalQuantity: normalizePackageQuantity(item.totalQuantity)!,
|
||||
sort: item.sort ?? index,
|
||||
remark: item.remark ?? null
|
||||
})),
|
||||
})) : undefined,
|
||||
remark: options.note.value.trim() || null
|
||||
});
|
||||
}
|
||||
@@ -110,10 +110,6 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
||||
Message.warning("成交价必须是合法的元金额,最多两位小数");
|
||||
return false;
|
||||
}
|
||||
if (!packagePurchaseItemList.value.length) {
|
||||
Message.warning("购买权益至少需要一项");
|
||||
return false;
|
||||
}
|
||||
for (const item of packagePurchaseItemList.value) {
|
||||
const quantity = normalizePackageQuantity(item.totalQuantity);
|
||||
if (!item.productId || !item.productUnitId || !quantity || quantity === "0") {
|
||||
|
||||
@@ -216,7 +216,7 @@ async function save(): Promise<void> {
|
||||
if (!requestNo.value) {
|
||||
requestNo.value = createPackageRequestNo();
|
||||
}
|
||||
const result = await SalesOrderAPI.fundRecordRevoke({
|
||||
const result = await SalesOrderAPI.revokeFund({
|
||||
requestNo: requestNo.value,
|
||||
fundRecordId: record.value.id,
|
||||
occurredAt: buildOccurredAt(),
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.salesOrder.cancel" class="sales-order-operation">
|
||||
<t-cell-group v-if="order" title="销售订单" theme="card"><t-cell title="订单号" :note="order.orderNo || order.id" /><t-cell title="当前状态" :note="order.status || '未知'" /></t-cell-group>
|
||||
<t-cell-group v-if="order" title="销售订单" theme="card">
|
||||
<t-cell title="订单号" :note="order.orderNo || order.id" />
|
||||
<t-cell title="当前状态" :note="getSalesOrderStatusText(order.status)" />
|
||||
<t-cell v-if="hasPendingFulfillment" title="履约提示" note="存在待处理履约,暂不能取消订单" />
|
||||
</t-cell-group>
|
||||
<div v-if="order" class="revoke-tip">取消订单会立即撤销本单有效消费;受影响的订单积分和签到消费条件积分将由系统自动追加撤销流水。退款仍需按资金流程处理。</div>
|
||||
<t-form v-if="order" class="form" label-align="left" label-width="6rem">
|
||||
<t-form-item label="取消原因" required-mark><t-textarea v-model.trim="reason" borderless autosize maxlength="500" placeholder="请输入取消原因" /></t-form-item>
|
||||
@@ -11,26 +15,45 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { SalesOrder } from "@/types/SalesOrder";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { getSalesOrderStatusText } from "@/utils/salesOrder";
|
||||
|
||||
defineOptions({ name: "SalesOrderCancel" });
|
||||
const route = useRoute(); const router = useRouter(); const navBarStore = useNavBarStore(); const userStore = useUserStore();
|
||||
const order = ref<SalesOrder>(); const loading = ref(false); const saving = ref(false); const reason = ref("");
|
||||
const order = ref<SalesOrder>(); const loading = ref(false); const saving = ref(false); const reason = ref(""); const hasPendingFulfillment = ref(false);
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) return;
|
||||
loading.value = true;
|
||||
try { order.value = await SalesOrderAPI.detail(id); }
|
||||
try {
|
||||
order.value = await SalesOrderAPI.detail(id);
|
||||
if (userStore.hasPermissions(permissionCodes.orderFulfillment.read)) {
|
||||
const [pendingResult, waitingStockResult] = await Promise.all([
|
||||
FulfillmentAPI.list({ salesOrderId: id, status: "PENDING", index: 0, size: 1 }),
|
||||
FulfillmentAPI.list({ salesOrderId: id, status: "WAITING_STOCK", index: 0, size: 1 })
|
||||
]);
|
||||
hasPendingFulfillment.value = !!pendingResult.list?.length || !!waitingStockResult.list?.length;
|
||||
}
|
||||
}
|
||||
catch (error) { Message.error(getErrorMessage(error, "订单加载失败")); }
|
||||
finally { loading.value = false; }
|
||||
}
|
||||
async function save(): Promise<void> {
|
||||
if (!order.value?.id || !reason.value || saving.value) return;
|
||||
if (order.value.status !== "ACTIVE") {
|
||||
Message.warning("只有进行中的订单可以取消");
|
||||
return;
|
||||
}
|
||||
if (hasPendingFulfillment.value) {
|
||||
Message.warning("存在待处理履约,暂不能取消订单");
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await SalesOrderAPI.cancel({ salesOrderId: order.value.id, reason: reason.value.trim() });
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1 class="no" v-text="order.orderNo" />
|
||||
<div class="meta">
|
||||
<pass-time-label class="time" :timestamp="order.purchasedAt" underline />
|
||||
<div class="amount" :class="getPaymentAmountClass(order.paymentStatus)">
|
||||
<div class="amount red text-bold">
|
||||
<span>¥</span>
|
||||
<strong class="value" v-text="formatPackageAmount(order.saleAmount)" />
|
||||
</div>
|
||||
@@ -66,19 +66,18 @@
|
||||
</div>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<t-cell-group
|
||||
<t-form
|
||||
v-if="order.itemList?.length"
|
||||
class="product empty-cell-title-margin"
|
||||
class="form product"
|
||||
title="订单明细"
|
||||
theme="card"
|
||||
label-width="0"
|
||||
v-entrance-transition="140"
|
||||
>
|
||||
<t-cell
|
||||
class="item"
|
||||
v-for="item in order.itemList"
|
||||
:key="item.id"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<t-form-item class="form-item" v-for="item in order.itemList" :key="item.id">
|
||||
<div class="item">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<t-image
|
||||
v-if="getProductPhotoURL(item)"
|
||||
:src="getProductPhotoURL(item)"
|
||||
@@ -91,38 +90,57 @@
|
||||
class="avatar fallback"
|
||||
v-text="(item.productNameSnapshot || '产').slice(0, 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="title">
|
||||
<span v-text="getItemTitle(item)"></span>
|
||||
<span class="name text-bold" v-text="item.productNameSnapshot" />
|
||||
<t-tag
|
||||
:content="getItemFulfillmentStatus(item).text"
|
||||
:theme="getItemFulfillmentStatus(item).theme"
|
||||
variant="light-outline"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<span>
|
||||
<span>¥</span>
|
||||
<strong class="red" v-text="formatPackageAmount(item.lineAmount)" />
|
||||
</span>
|
||||
<span>
|
||||
<span>已履约</span>
|
||||
<strong :class="getFulfilledQuantityClass(item)" v-text="getPackageQuantityText(item.fulfilledQuantity)" />
|
||||
</span>
|
||||
<span>
|
||||
<span>剩余履约</span>
|
||||
<strong :class="getRemainingQuantityClass(item)" v-text="getRemainingQuantityText(item)" />
|
||||
</span>
|
||||
<strong
|
||||
v-if="item.lineAmount !== null && item.lineAmount !== undefined"
|
||||
class="red"
|
||||
v-text="`¥${formatPackageAmount(item.lineAmount)}`"
|
||||
/>
|
||||
<t-tag v-else size="small" content="套餐权益" />
|
||||
<t-tag size="small" theme="primary" variant="light" :content="item.unitSnapshot" />
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="履约记录" note="查看本单所有履约单" arrow @click="toFulfillmentList" />
|
||||
</t-cell-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="metric">
|
||||
<strong v-text="getPackageQuantityText(item.quantity)" />
|
||||
<span>总数</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong :class="getAvailableQuantityClass(item)" v-text="getPackageQuantityText(item.availableQuantity)" />
|
||||
<span>可履约</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong v-text="getPackageQuantityText(item.pendingQuantity)" />
|
||||
<span>待核销</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong :class="getFulfilledQuantityClass(item)" v-text="getPackageQuantityText(item.fulfilledQuantity)" />
|
||||
<span>已履约</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong :class="getRemainingQuantityClass(item)" v-text="getPackageQuantityText(item.remainingQuantity)" />
|
||||
<span>剩余</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-form-item>
|
||||
<t-form-item label="履约记录" arrow @click="toFulfillmentList">
|
||||
查看本单所有履约单
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<t-button
|
||||
v-if="canCreateFulfillment && canFulfillByPayment && order.orderType === 'DIRECT_PRODUCT' && order.status === 'ACTIVE'"
|
||||
v-if="canCreateFulfillment && canFulfillByPayment && order.status === 'ACTIVE'"
|
||||
class="cell-button"
|
||||
theme="primary"
|
||||
content="履约产品"
|
||||
@@ -149,6 +167,11 @@
|
||||
<t-cell title="退款状态" :note="getSalesRefundStatusText(order.refundStatus)" />
|
||||
<t-cell v-if="canReadFund" title="查看全部资金流水" arrow @click="toFundRecords" />
|
||||
</t-cell-group>
|
||||
<t-cell-group v-if="order.status === 'CANCELLED'" class="cancel-info" title="取消记录" theme="card">
|
||||
<t-cell title="取消时间" :note="order.cancelledAt ? Time.toDateTime(order.cancelledAt) : '未知'" />
|
||||
<t-cell title="取消人" :note="order.cancelOperator?.name || order.cancelOperatorUserId || '未知'" />
|
||||
<t-cell title="取消原因" :note="order.cancelReason || '未填写'" />
|
||||
</t-cell-group>
|
||||
<section class="actions" v-entrance-transition="280">
|
||||
<t-button
|
||||
v-if="canReceive"
|
||||
@@ -179,11 +202,12 @@
|
||||
<script setup lang="ts">
|
||||
import { PassTimeLabel, Text, Time } from "timi-web";
|
||||
import { type Attachment, useUserStore } from "timi-web-api";
|
||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { SalesOrder, SalesOrderItem, SalesPaymentStatus } from "@/types/SalesOrder";
|
||||
import type { GaoSalesOrderItem, GaoSalesPaymentStatus, SalesOrder } from "@/types/SalesOrder";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
import { formatPackageAmount, getPackageQuantityText } from "@/utils/package";
|
||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||
@@ -205,6 +229,7 @@ const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const order = ref<SalesOrder>();
|
||||
const hasPendingFulfillment = ref(false);
|
||||
const loading = ref(false);
|
||||
const isEditing = ref(false);
|
||||
const isSaving = ref(false);
|
||||
@@ -217,7 +242,7 @@ const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.
|
||||
const canReceive = computed(() => !!order.value && order.value.status !== "CANCELLED" && order.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
||||
const canRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
||||
const canConfirmNoRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
||||
const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.salesOrder.cancel));
|
||||
const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE" && !hasPendingFulfillment.value && userStore.hasPermissions(permissionCodes.salesOrder.cancel));
|
||||
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
||||
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
||||
|
||||
@@ -246,7 +271,7 @@ function getPaidAtText(paidAt?: number | null): string {
|
||||
return paidAt ? Time.toDateTime(paidAt) : "待后端回填";
|
||||
}
|
||||
|
||||
function getPaymentAmountClass(status?: SalesPaymentStatus): "green" | "orange" | "red" | "gray" {
|
||||
function getPaymentAmountClass(status?: GaoSalesPaymentStatus): "green" | "orange" | "red" | "gray" {
|
||||
if (status === "PAID") return "green";
|
||||
if (status === "PARTIALLY_PAID") return "orange";
|
||||
if (status === "UNPAID") return "red";
|
||||
@@ -263,37 +288,22 @@ function getRemainingAmountClass(amount?: number | null): "green" | "red" | "gra
|
||||
return amount === 0 ? "green" : "red";
|
||||
}
|
||||
|
||||
function getItemTitle(item: SalesOrderItem): string {
|
||||
const productName = item.productNameSnapshot || "未命名产品";
|
||||
const quantity = getPackageQuantityText(item.quantity);
|
||||
const unit = item.unitSnapshot ? ` ${item.unitSnapshot}` : "";
|
||||
return `${productName} ${quantity}${unit}`;
|
||||
function getAvailableQuantityClass(item: GaoSalesOrderItem): "green" | "orange" {
|
||||
return Number(item.availableQuantity || 0) === 0 ? "green" : "orange";
|
||||
}
|
||||
|
||||
function getRemainingQuantity(item: SalesOrderItem): number {
|
||||
const quantity = Number(item.quantity || 0);
|
||||
function getFulfilledQuantityClass(item: GaoSalesOrderItem): "green" | "orange" {
|
||||
return Number(item.remainingQuantity || 0) === 0 ? "green" : "orange";
|
||||
}
|
||||
|
||||
function getRemainingQuantityClass(item: GaoSalesOrderItem): "green" | "orange" {
|
||||
return Number(item.remainingQuantity || 0) === 0 ? "green" : "orange";
|
||||
}
|
||||
|
||||
function getItemFulfillmentStatus(item: GaoSalesOrderItem): { text: string; theme: "success" | "warning" | "danger" } {
|
||||
const fulfilledQuantity = Number(item.fulfilledQuantity || 0);
|
||||
return Math.max(0, quantity - fulfilledQuantity);
|
||||
}
|
||||
|
||||
function getRemainingQuantityText(item: SalesOrderItem): string {
|
||||
return getPackageQuantityText(getRemainingQuantity(item).toFixed(3));
|
||||
}
|
||||
|
||||
function getFulfilledQuantityClass(item: SalesOrderItem): "green" | "orange" {
|
||||
const quantity = Number(item.quantity || 0);
|
||||
const fulfilledQuantity = Number(item.fulfilledQuantity || 0);
|
||||
return fulfilledQuantity < quantity ? "orange" : "green";
|
||||
}
|
||||
|
||||
function getRemainingQuantityClass(item: SalesOrderItem): "green" | "orange" {
|
||||
return getRemainingQuantity(item) === 0 ? "green" : "orange";
|
||||
}
|
||||
|
||||
function getItemFulfillmentStatus(item: SalesOrderItem): { text: string; theme: "success" | "warning" | "danger" } {
|
||||
const quantity = Number(item.quantity || 0);
|
||||
const fulfilledQuantity = Number(item.fulfilledQuantity || 0);
|
||||
if (0 < quantity && (fulfilledQuantity === quantity || quantity < fulfilledQuantity)) {
|
||||
const remainingQuantity = Number(item.remainingQuantity || 0);
|
||||
if (0 < fulfilledQuantity && remainingQuantity === 0) {
|
||||
return { text: "已完成", theme: "success" };
|
||||
}
|
||||
if (0 < fulfilledQuantity) {
|
||||
@@ -308,12 +318,25 @@ async function loadData(): Promise<void> {
|
||||
navBarStore.clearRight();
|
||||
if (!id) {
|
||||
order.value = undefined;
|
||||
hasPendingFulfillment.value = false;
|
||||
photoItems.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
order.value = await SalesOrderAPI.detail(id);
|
||||
hasPendingFulfillment.value = false;
|
||||
if (userStore.hasPermissions(permissionCodes.orderFulfillment.read)) {
|
||||
try {
|
||||
const [pendingResult, waitingStockResult] = await Promise.all([
|
||||
FulfillmentAPI.list({ salesOrderId: id, status: "PENDING", index: 0, size: 1 }),
|
||||
FulfillmentAPI.list({ salesOrderId: id, status: "WAITING_STOCK", index: 0, size: 1 })
|
||||
]);
|
||||
hasPendingFulfillment.value = !!pendingResult.list?.length || !!waitingStockResult.list?.length;
|
||||
} catch {
|
||||
// 取消接口仍会在后端校验待处理履约,查询失败时不阻断订单详情展示。
|
||||
}
|
||||
}
|
||||
editRemark.value = order.value.remark || "";
|
||||
photoItems.value = toPhotoItems(order.value.attachmentList);
|
||||
} catch (error) {
|
||||
@@ -464,7 +487,6 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
margin-top: .6rem;
|
||||
color: var(--td-text-color-secondary);
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -513,27 +535,77 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
|
||||
.product {
|
||||
|
||||
.item {
|
||||
.form-item {
|
||||
padding: 10px;
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-bottom: .5rem;
|
||||
border-bottom: 1px solid rgba(155, 155, 155, .2);
|
||||
|
||||
.left {
|
||||
|
||||
.avatar {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding-left: .5rem;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.description {
|
||||
gap: .75rem;
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
gap: .25rem;
|
||||
.content {
|
||||
display: flex;
|
||||
padding-top: 5px;
|
||||
justify-content: space-around;
|
||||
|
||||
.metric {
|
||||
gap: .25rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&:not(:last-child):after {
|
||||
content: "";
|
||||
top: 50%;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 50%;
|
||||
transform: translateY(calc(-50% + 2.5px));
|
||||
position: absolute;
|
||||
background: rgba(155, 155, 155, .2);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: .65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
||||
import SalesOrderListCell from "@/pages/salesOrder/components/SalesOrderListCell.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { SalesOrder, SalesOrderPage, SalesOrderStatus, SalesOrderType, SalesPaymentStatus, SalesRefundStatus } from "@/types/SalesOrder";
|
||||
import type { GaoSalesOrderStatus, GaoSalesOrderType, GaoSalesPaymentStatus, GaoSalesRefundStatus, SalesOrder, SalesOrderPage } from "@/types/SalesOrder";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
@@ -93,8 +93,8 @@ const actions = computed(() => canCreate.value ? [{
|
||||
}] : []);
|
||||
const keyword = ref("");
|
||||
const orderType = ref("ALL");
|
||||
type PaymentFilter = "ALL" | SalesPaymentStatus | "PENDING_RECEIVE";
|
||||
type RefundFilter = "ALL" | SalesRefundStatus;
|
||||
type PaymentFilter = "ALL" | GaoSalesPaymentStatus | "PENDING_RECEIVE";
|
||||
type RefundFilter = "ALL" | GaoSalesRefundStatus;
|
||||
|
||||
const status = ref("ALL");
|
||||
const paymentStatus = ref<PaymentFilter>("ALL");
|
||||
@@ -135,10 +135,10 @@ function buildPageFilter(): SalesOrderPage {
|
||||
const pendingReceive = paymentStatus.value === "PENDING_RECEIVE";
|
||||
return {
|
||||
keyword: keyword.value || undefined,
|
||||
orderType: orderType.value === "ALL" ? undefined : orderType.value as SalesOrderType,
|
||||
status: status.value === "ALL" ? undefined : status.value as SalesOrderStatus,
|
||||
paymentStatus: pendingReceive || paymentStatus.value === "ALL" ? undefined : paymentStatus.value as SalesPaymentStatus,
|
||||
refundStatus: refundStatus.value === "ALL" ? undefined : refundStatus.value as SalesRefundStatus,
|
||||
orderType: orderType.value === "ALL" ? undefined : orderType.value as GaoSalesOrderType,
|
||||
status: status.value === "ALL" ? undefined : status.value as GaoSalesOrderStatus,
|
||||
paymentStatus: pendingReceive || paymentStatus.value === "ALL" ? undefined : paymentStatus.value as GaoSalesPaymentStatus,
|
||||
refundStatus: refundStatus.value === "ALL" ? undefined : refundStatus.value as GaoSalesRefundStatus,
|
||||
pendingReceive: pendingReceive ? true : undefined,
|
||||
index: pageIndex.value,
|
||||
size: pageSize
|
||||
|
||||
@@ -13,14 +13,13 @@ import { useUserStore } from "timi-web-api";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { SalesOrder } from "@/types/SalesOrder";
|
||||
import { createPackageRequestNo } from "@/utils/package";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({ name: "SalesOrderNoRefund" });
|
||||
const route = useRoute(); const router = useRouter(); const navBarStore = useNavBarStore(); const userStore = useUserStore();
|
||||
const order = ref<SalesOrder>(); const loading = ref(false); const saving = ref(false); const reason = ref(""); const requestNo = ref("");
|
||||
const order = ref<SalesOrder>(); const loading = ref(false); const saving = ref(false); const reason = ref("");
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) return;
|
||||
@@ -31,13 +30,12 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
async function save(): Promise<void> {
|
||||
if (!order.value?.id || !reason.value || saving.value) return;
|
||||
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
||||
saving.value = true;
|
||||
try {
|
||||
await SalesOrderAPI.confirmNoRefund({ salesOrderId: order.value.id, requestNo: requestNo.value, reason: reason.value.trim() });
|
||||
await SalesOrderAPI.confirmNoRefund({ salesOrderId: order.value.id, reason: reason.value.trim() });
|
||||
Message.success("已确认无需退款");
|
||||
await router.replace(`/management/sales-orders/${order.value.id}`);
|
||||
} catch (error) { Message.error(getErrorMessage(error, "确认失败,重试会复用同一请求号")); }
|
||||
} catch (error) { Message.error(getErrorMessage(error, "确认失败,请刷新后重试")); }
|
||||
finally { saving.value = false; }
|
||||
}
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
<div class="customer-status">
|
||||
<strong class="customer" v-text="customerName" />
|
||||
<div class="status">
|
||||
<t-tag
|
||||
:content="getSalesOrderTypeText(props.order.orderType)"
|
||||
theme="primary"
|
||||
size="small"
|
||||
variant="light-outline"
|
||||
/>
|
||||
<t-tag
|
||||
:content="getSalesOrderStatusText(props.order.status)"
|
||||
size="small"
|
||||
@@ -82,10 +88,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import type { SalesOrder, SalesPaymentStatus } from "@/types/SalesOrder";
|
||||
import type { GaoSalesPaymentStatus, SalesOrder } from "@/types/SalesOrder";
|
||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||
import { formatPackageAmount, getPackageQuantityText } from "@/utils/package";
|
||||
import { getSalesOrderStatusText, getSalesPaymentStatusText, getStatusTheme } from "@/utils/salesOrder";
|
||||
import { getSalesOrderStatusText, getSalesOrderTypeText, getSalesPaymentStatusText, getStatusTheme } from "@/utils/salesOrder";
|
||||
|
||||
defineOptions({ name: "SalesOrderListCell" });
|
||||
|
||||
@@ -110,10 +116,10 @@ const fulfilledProductQuantity = computed(() => sumProductQuantity("fulfilledQua
|
||||
const productSummaryText = computed(() => `共 ${itemList.value.length} 项 · 总数量 ${totalProductQuantity.value} · 已履约 ${fulfilledProductQuantity.value}`);
|
||||
const showReceiveAction = computed(() => props.canReceive && props.order.status !== "CANCELLED" && props.order.paymentStatus !== "PAID");
|
||||
const canFulfillByPayment = computed(() => props.order.paymentStatus === "PAID" || (props.order.paymentStatus === "PARTIALLY_PAID" && props.canOperateStock));
|
||||
const showFulfillAction = computed(() => props.canFulfill && canFulfillByPayment.value && props.order.orderType === "DIRECT_PRODUCT" && props.order.status === "ACTIVE");
|
||||
const showFulfillAction = computed(() => props.canFulfill && canFulfillByPayment.value && props.order.status === "ACTIVE");
|
||||
const showActions = computed(() => showReceiveAction.value || showFulfillAction.value);
|
||||
|
||||
function getPaymentStatusTheme(status?: SalesPaymentStatus): "success" | "warning" | "danger" | "default" {
|
||||
function getPaymentStatusTheme(status?: GaoSalesPaymentStatus): "success" | "warning" | "danger" | "default" {
|
||||
if (status === "PAID") {
|
||||
return "success";
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
|
||||
export type FulfillmentType = "PACKAGE_ENTITLEMENT" | "DIRECT_PRODUCT";
|
||||
export type FulfillmentStatus = "WAITING_STOCK" | "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED" | "REVOKED";
|
||||
export type GaoFulfillmentStatus = "WAITING_STOCK" | "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED" | "REVOKED";
|
||||
|
||||
export type OrderFulfillmentItem = {
|
||||
fulfillmentId?: string;
|
||||
@@ -10,7 +9,6 @@ export type OrderFulfillmentItem = {
|
||||
productUnitId?: string;
|
||||
attachmentList?: Attachment[];
|
||||
sourceOrderItemId?: string;
|
||||
sourceCustomerPackageItemId?: string;
|
||||
quantity?: number | string;
|
||||
baseQuantity?: number | string;
|
||||
reservedBefore?: number | string;
|
||||
@@ -26,8 +24,7 @@ export type OrderFulfillment = {
|
||||
salesOrderId?: string;
|
||||
storeId?: string;
|
||||
customerId?: string;
|
||||
fulfillmentType?: FulfillmentType;
|
||||
status?: FulfillmentStatus;
|
||||
status?: GaoFulfillmentStatus;
|
||||
requestNo?: string;
|
||||
operatorUserId?: string;
|
||||
customer?: Customer;
|
||||
@@ -49,8 +46,7 @@ export type FulfillmentPage = {
|
||||
storeId?: string;
|
||||
salesOrderId?: string;
|
||||
customerId?: string;
|
||||
fulfillmentType?: FulfillmentType;
|
||||
status?: FulfillmentStatus;
|
||||
status?: GaoFulfillmentStatus;
|
||||
beginAt?: number;
|
||||
endAt?: number;
|
||||
index: number;
|
||||
@@ -61,14 +57,13 @@ export type OrderFulfillmentCreateRequest = {
|
||||
salesOrderId: string;
|
||||
requestNo: string;
|
||||
deliveredAt?: number;
|
||||
itemList: OrderFulfillmentItemRequest[];
|
||||
itemList: GaoOrderFulfillmentItemRequest[];
|
||||
attachmentList?: Partial<Attachment>[];
|
||||
remark?: string | null;
|
||||
};
|
||||
|
||||
export type OrderFulfillmentItemRequest = {
|
||||
sourceOrderItemId?: string;
|
||||
sourceCustomerPackageItemId?: string;
|
||||
export type GaoOrderFulfillmentItemRequest = {
|
||||
sourceOrderItemId: string;
|
||||
quantity: number | string;
|
||||
};
|
||||
|
||||
|
||||
+6
-23
@@ -2,9 +2,9 @@ import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||
import type { Product } from "@/types/Product";
|
||||
import type { GaoSalesOrderItem, GaoSalesOrderStatus, SalesOrder } from "@/types/SalesOrder";
|
||||
|
||||
export type PackageConfigStatus = "DRAFT" | "ACTIVE" | "INACTIVE";
|
||||
export type CustomerPackageStatus = "ACTIVE" | "FULFILLED" | "CANCELLED";
|
||||
|
||||
export type PackageConfigItem = {
|
||||
packageConfigId?: string;
|
||||
@@ -34,24 +34,6 @@ export type GaoPackageConfig = {
|
||||
|
||||
export type PackageConfigPage = { storeId?: string; keyword?: string; status?: PackageConfigStatus; index: number; size: number };
|
||||
|
||||
export type CustomerPackageItem = {
|
||||
customerPackageId?: string;
|
||||
productId?: string;
|
||||
productUnitId?: string;
|
||||
attachmentList?: Attachment[];
|
||||
productNameSnapshot?: string;
|
||||
unitSnapshot?: string;
|
||||
baseQuantityPerUnitSnapshot?: number | string;
|
||||
totalQuantity: number | string;
|
||||
reservedQuantity: number | string;
|
||||
fulfilledQuantity: number | string;
|
||||
pendingQuantity: number | string;
|
||||
availableQuantity: number | string;
|
||||
remainingQuantity: number | string;
|
||||
sort?: number;
|
||||
remark?: string | null;
|
||||
} & Model;
|
||||
|
||||
export type GaoCustomerPackage = {
|
||||
storeId?: string;
|
||||
salesOrderId?: string;
|
||||
@@ -59,18 +41,19 @@ export type GaoCustomerPackage = {
|
||||
packageConfigId?: string;
|
||||
packageNameSnapshot?: string;
|
||||
standardAmountSnapshot?: number;
|
||||
status?: CustomerPackageStatus;
|
||||
status?: GaoSalesOrderStatus;
|
||||
cancelledAt?: number | null;
|
||||
cancelOperatorUserId?: string;
|
||||
cancelReason?: string | null;
|
||||
remark?: string | null;
|
||||
customer?: Customer;
|
||||
cancelOperator?: Partial<User>;
|
||||
itemList?: CustomerPackageItem[];
|
||||
salesOrder?: SalesOrder;
|
||||
itemList?: GaoSalesOrderItem[];
|
||||
fulfillmentList?: OrderFulfillment[];
|
||||
} & Model;
|
||||
|
||||
export type CustomerPackagePage = { storeId?: string; customerId?: string; keyword?: string; status?: CustomerPackageStatus; beginAt?: number; endAt?: number; index: number; size: number };
|
||||
export type CustomerPackagePage = { storeId?: string; customerId?: string; keyword?: string; status?: GaoSalesOrderStatus; beginAt?: number; endAt?: number; index: number; size: number };
|
||||
|
||||
export type PackagePurchaseItem = {
|
||||
productId: string;
|
||||
@@ -88,7 +71,7 @@ export type PackagePurchaseRequest = {
|
||||
customerId: string;
|
||||
saleAmount: number;
|
||||
purchasedAt?: number;
|
||||
itemList: PackagePurchaseItem[];
|
||||
itemList?: PackagePurchaseItem[];
|
||||
attachmentList?: PackageAttachment[];
|
||||
remark?: string | null;
|
||||
};
|
||||
|
||||
+21
-17
@@ -2,16 +2,18 @@ import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { EventRecord } from "@/types/EventRecord";
|
||||
|
||||
export type SalesOrderType = "PACKAGE" | "DIRECT_PRODUCT";
|
||||
export type SalesOrderStatus = "ACTIVE" | "FULFILLED" | "CANCELLED";
|
||||
export type SalesPaymentStatus = "UNPAID" | "PARTIALLY_PAID" | "PAID";
|
||||
export type SalesRefundStatus = "NOT_REQUIRED" | "PENDING" | "REFUNDED";
|
||||
export type GaoSalesOrderType = "PACKAGE" | "DIRECT_PRODUCT";
|
||||
export type GaoSalesOrderStatus = "ACTIVE" | "FULFILLED" | "CANCELLED";
|
||||
export type GaoSalesOrderItemType = "PACKAGE_ENTITLEMENT" | "DIRECT_PRODUCT";
|
||||
export type GaoSalesPaymentStatus = "UNPAID" | "PARTIALLY_PAID" | "PAID";
|
||||
export type GaoSalesRefundStatus = "NOT_REQUIRED" | "PENDING" | "REFUNDED";
|
||||
export type FundBizType = "SALES_ORDER";
|
||||
export type FundOperationType = "RECEIVE" | "REFUND" | "CONFIRM_NO_REFUND" | "REVOKE";
|
||||
export type FundRecordStatus = "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED";
|
||||
|
||||
export type SalesOrderItem = {
|
||||
export type GaoSalesOrderItem = {
|
||||
salesOrderId?: string;
|
||||
itemType?: GaoSalesOrderItemType;
|
||||
productId?: string;
|
||||
productUnitId?: string;
|
||||
attachmentList?: Attachment[];
|
||||
@@ -21,8 +23,11 @@ export type SalesOrderItem = {
|
||||
quantity?: number | string;
|
||||
reservedQuantity?: number | string;
|
||||
fulfilledQuantity?: number | string;
|
||||
unitPrice?: number;
|
||||
lineAmount?: number;
|
||||
unitPrice?: number | null;
|
||||
lineAmount?: number | null;
|
||||
pendingQuantity?: number | string;
|
||||
availableQuantity?: number | string;
|
||||
remainingQuantity?: number | string;
|
||||
sort?: number;
|
||||
remark?: string | null;
|
||||
} & Model;
|
||||
@@ -31,10 +36,10 @@ export type SalesOrder = {
|
||||
storeId?: string;
|
||||
customerId?: string;
|
||||
orderNo?: string;
|
||||
orderType?: SalesOrderType;
|
||||
status?: SalesOrderStatus;
|
||||
paymentStatus?: SalesPaymentStatus;
|
||||
refundStatus?: SalesRefundStatus;
|
||||
orderType?: GaoSalesOrderType;
|
||||
status?: GaoSalesOrderStatus;
|
||||
paymentStatus?: GaoSalesPaymentStatus;
|
||||
refundStatus?: GaoSalesRefundStatus;
|
||||
saleAmount?: number;
|
||||
pendingPaidAmount?: number;
|
||||
paidAmount?: number;
|
||||
@@ -52,7 +57,7 @@ export type SalesOrder = {
|
||||
customer?: Customer;
|
||||
operator?: Partial<User>;
|
||||
cancelOperator?: Partial<User>;
|
||||
itemList?: SalesOrderItem[];
|
||||
itemList?: GaoSalesOrderItem[];
|
||||
fundRecordList?: FundRecord[];
|
||||
sourceEventRecord?: EventRecord;
|
||||
attachmentList?: Partial<Attachment>[];
|
||||
@@ -61,10 +66,10 @@ export type SalesOrder = {
|
||||
export type SalesOrderPage = {
|
||||
storeId?: string;
|
||||
customerId?: string;
|
||||
orderType?: SalesOrderType;
|
||||
status?: SalesOrderStatus;
|
||||
paymentStatus?: SalesPaymentStatus;
|
||||
refundStatus?: SalesRefundStatus;
|
||||
orderType?: GaoSalesOrderType;
|
||||
status?: GaoSalesOrderStatus;
|
||||
paymentStatus?: GaoSalesPaymentStatus;
|
||||
refundStatus?: GaoSalesRefundStatus;
|
||||
pendingReceive?: boolean;
|
||||
fulfilledButUnpaid?: boolean;
|
||||
keyword?: string;
|
||||
@@ -122,7 +127,6 @@ export type OrderPaymentRefundRequest = {
|
||||
|
||||
export type OrderPaymentNoRefundRequest = {
|
||||
salesOrderId: string;
|
||||
requestNo: string;
|
||||
occurredAt?: number;
|
||||
reason: string;
|
||||
remark?: string | null;
|
||||
|
||||
+1
-13
@@ -1,8 +1,4 @@
|
||||
import type {
|
||||
CustomerPackageItem,
|
||||
CustomerPackageStatus,
|
||||
PackageConfigStatus
|
||||
} from "@/types/Package";
|
||||
import type { PackageConfigStatus } from "@/types/Package";
|
||||
import type { FundOperationType, FundRecordStatus } from "@/types/SalesOrder";
|
||||
|
||||
/** 生成套餐业务幂等请求号。每次新操作必须重新生成。 */
|
||||
@@ -80,10 +76,6 @@ export function getPackageConfigStatusText(status?: PackageConfigStatus): string
|
||||
return ({ DRAFT: "草稿", ACTIVE: "启用", INACTIVE: "停用" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
|
||||
export function getCustomerPackageStatusText(status?: CustomerPackageStatus): string {
|
||||
return ({ ACTIVE: "进行中", FULFILLED: "已履约", CANCELLED: "已取消" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
|
||||
export function getFundOperationText(type?: FundOperationType): string {
|
||||
return ({ RECEIVE: "线下收款", REFUND: "退款", CONFIRM_NO_REFUND: "确认无需退款", REVOKE: "资金冲正" } as Record<string, string>)[type || ""] || "未知";
|
||||
}
|
||||
@@ -92,10 +84,6 @@ export function getFundStatusText(status?: FundRecordStatus): string {
|
||||
return ({ PENDING: "待核销", VERIFIED: "已核销", REJECTED: "已拒绝", CANCELLED: "已撤销" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
|
||||
export function getPackageItemAvailableQuantity(item: CustomerPackageItem): string {
|
||||
return getPackageQuantityText(item.availableQuantity);
|
||||
}
|
||||
|
||||
function toQuantityParts(value: number | string): { value: bigint; scale: number } | undefined {
|
||||
const normalized = normalizePackageQuantity(value);
|
||||
if (!normalized) {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { FulfillmentStatus, FulfillmentType } from "@/types/Fulfillment";
|
||||
import type { FundBizType, FundOperationType, FundRecordStatus, SalesOrderStatus, SalesOrderType, SalesPaymentStatus, SalesRefundStatus } from "@/types/SalesOrder";
|
||||
import type { GaoFulfillmentStatus } from "@/types/Fulfillment";
|
||||
import type { FundBizType, FundOperationType, FundRecordStatus, GaoSalesOrderItemType, GaoSalesOrderStatus, GaoSalesOrderType, GaoSalesPaymentStatus, GaoSalesRefundStatus } from "@/types/SalesOrder";
|
||||
|
||||
export function getFundBizTypeText(type?: FundBizType): string {
|
||||
return type === "SALES_ORDER" ? "销售订单" : "未知";
|
||||
}
|
||||
export function getSalesOrderTypeText(type?: SalesOrderType): string {
|
||||
return type === "PACKAGE" ? "套餐订单" : type === "DIRECT_PRODUCT" ? "直接产品订单" : "未知订单";
|
||||
export function getSalesOrderTypeText(type?: GaoSalesOrderType): string {
|
||||
return type === "PACKAGE" ? "套餐订单" : type === "DIRECT_PRODUCT" ? "产品订单" : "未知订单";
|
||||
}
|
||||
export function getSalesOrderStatusText(status?: SalesOrderStatus): string {
|
||||
export function getSalesOrderStatusText(status?: GaoSalesOrderStatus): string {
|
||||
return ({ ACTIVE: "进行中", FULFILLED: "已履约", CANCELLED: "已取消" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
export function getSalesPaymentStatusText(status?: SalesPaymentStatus): string {
|
||||
export function getSalesPaymentStatusText(status?: GaoSalesPaymentStatus): string {
|
||||
return ({ UNPAID: "未收款", PARTIALLY_PAID: "部分收款", PAID: "已收款" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
export function getSalesRefundStatusText(status?: SalesRefundStatus): string {
|
||||
export function getSalesRefundStatusText(status?: GaoSalesRefundStatus): string {
|
||||
return ({ NOT_REQUIRED: "无需退款", PENDING: "待处理退款", REFUNDED: "已退款" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
export function getFundOperationText(type?: FundOperationType): string {
|
||||
@@ -22,10 +22,10 @@ export function getFundOperationText(type?: FundOperationType): string {
|
||||
export function getFundStatusText(status?: FundRecordStatus): string {
|
||||
return ({ PENDING: "待核销", VERIFIED: "已生效", REJECTED: "已拒绝", CANCELLED: "已撤销" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
export function getFulfillmentTypeText(type?: FulfillmentType): string {
|
||||
export function getSalesOrderItemTypeText(type?: GaoSalesOrderItemType): string {
|
||||
return type === "PACKAGE_ENTITLEMENT" ? "套餐权益" : type === "DIRECT_PRODUCT" ? "直接产品" : "未知来源";
|
||||
}
|
||||
export function getFulfillmentStatusText(status?: FulfillmentStatus): string {
|
||||
export function getFulfillmentStatusText(status?: GaoFulfillmentStatus): string {
|
||||
return ({ WAITING_STOCK: "等待库存", PENDING: "待核销", VERIFIED: "已核销", REJECTED: "已拒绝", CANCELLED: "已撤销", REVOKED: "已冲正" } as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
export function getStatusTheme(status?: string): "success" | "warning" | "danger" | "default" {
|
||||
|
||||
Reference in New Issue
Block a user