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