update customer package

This commit is contained in:
Timi
2026-09-06 15:49:20 +08:00
parent b8b3ecbf5d
commit 4c6920be85
19 changed files with 383 additions and 285 deletions
@@ -84,30 +84,28 @@
</template>
<script setup lang="ts">
import PackageAPI from "@/api/PackageAPI";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import FulfillmentAPI from "@/api/FulfillmentAPI";
import PhotoScroller from "@/components/PhotoScroller.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { CustomerPackageItem, GaoCustomerPackage } from "@/types/Package";
import type { OrderFulfillmentItemRequest } from "@/types/Fulfillment";
import type { SalesOrder, SalesOrderItem } from "@/types/SalesOrder";
import { useUserStore } from "timi-web-api";
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
import type { GaoSalesOrderItem, SalesOrder } from "@/types/SalesOrder";
import type { PhotoItem } from "@/types/Photo";
import type { Attachment } from "timi-web-api";
import { createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity } from "@/utils/package";
import { comparePackageQuantity, createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity } from "@/utils/package";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission";
import { uploadPhotoList } from "@/utils/photo";
import { getProductPhotoURL } from "@/utils/product";
type SourceItem = SalesOrderItem | CustomerPackageItem;
defineOptions({ name: "OrderFulfillmentCreate" });
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const order = ref<SalesOrder>();
const customerPackage = ref<GaoCustomerPackage>();
const quantityMap = reactive<Record<string, string>>({});
const deliveredAtText = ref("");
const draftTime = ref("");
@@ -117,12 +115,14 @@ const requestNo = ref("");
const isSaving = ref(false);
const photoItems = ref<PhotoItem[]>([]);
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
const isPackage = computed(() => !!customerPackage.value);
const sourceItems = computed<SourceItem[]>(() => customerPackage.value?.itemList || order.value?.itemList || []);
const isPackage = computed(() => order.value?.orderType === "PACKAGE");
const expectedItemType = computed(() => isPackage.value ? "PACKAGE_ENTITLEMENT" : "DIRECT_PRODUCT");
const sourceItems = computed<GaoSalesOrderItem[]>(() => (order.value?.itemList || []).filter(item => item.itemType === expectedItemType.value));
const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.productStock.create));
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
async function loadData(): Promise<void> {
const salesOrderId = String(route.query.salesOrderId || "");
const customerPackageId = String(route.query.customerPackageId || "");
if (!salesOrderId) {
Message.warning("请从订单详情或客户套餐详情进入履约创建");
await router.replace("/management/order-fulfillments");
@@ -130,26 +130,20 @@ async function loadData(): Promise<void> {
}
try {
order.value = await SalesOrderAPI.detail(salesOrderId);
if (customerPackageId) customerPackage.value = await PackageAPI.customerPackageDetail(customerPackageId);
} catch (error) {
Message.error(getErrorMessage(error, "履约来源加载失败"));
}
}
function getAvailableQuantity(item: SourceItem): string {
if (isPackage.value) {
return getPackageQuantityText((item as CustomerPackageItem).availableQuantity);
}
const orderItem = item as SalesOrderItem;
const availableQuantity = Math.max(0, Number(orderItem.quantity || 0) - Number(orderItem.reservedQuantity || 0) - Number(orderItem.fulfilledQuantity || 0));
return getPackageQuantityText(availableQuantity.toFixed(3));
function getAvailableQuantity(item: GaoSalesOrderItem): string {
return getPackageQuantityText(item.availableQuantity);
}
function getItemDescription(item: SourceItem): string {
function getItemDescription(item: GaoSalesOrderItem): string {
return ` · 剩余 ${getAvailableQuantity(item)}`;
}
function fillAllQuantity(item: SourceItem): void {
function fillAllQuantity(item: GaoSalesOrderItem): void {
if (item.id) {
quantityMap[item.id] = getAvailableQuantity(item);
}
@@ -178,6 +172,14 @@ function validate(): boolean {
Message.warning("请从订单详情或客户套餐详情进入履约创建");
return false;
}
if (order.value.status !== "ACTIVE") {
Message.warning("当前订单不是进行中状态,不能创建履约");
return false;
}
if (!canFulfillByPayment.value) {
Message.warning("订单尚未满足履约收款条件");
return false;
}
if (!buildAttachments().length) {
Message.warning("履约必须上传至少一张照片");
return false;
@@ -188,10 +190,15 @@ function validate(): boolean {
return false;
}
for (const item of selected) {
if (!normalizePackageQuantity(quantityMap[item.id || ""])) {
const quantity = normalizePackageQuantity(quantityMap[item.id || ""]);
if (!quantity) {
Message.warning("履约数量必须是正数,最多三位小数");
return false;
}
if (0 < comparePackageQuantity(quantity, item.availableQuantity ?? "0")) {
Message.warning(`${item.productNameSnapshot || "产品"} 的可履约数量不足`);
return false;
}
}
return true;
}
@@ -201,10 +208,10 @@ async function save(): Promise<void> {
if (!requestNo.value) requestNo.value = createPackageRequestNo();
isSaving.value = true;
try {
const itemList: OrderFulfillmentItemRequest[] = sourceItems.value
const itemList: GaoOrderFulfillmentItemRequest[] = sourceItems.value
.filter(item => !!item.id && quantityMap[item.id])
.map(item => ({
...(isPackage.value ? { sourceCustomerPackageItemId: item.id } : { sourceOrderItemId: item.id }),
sourceOrderItemId: item.id!,
quantity: normalizePackageQuantity(quantityMap[item.id || ""])!
}));
const result = await FulfillmentAPI.create({