add OrderFulfillmentRevoke.vue
This commit is contained in:
@@ -95,7 +95,7 @@
|
|||||||
v-if="isVerified && canRevoke"
|
v-if="isVerified && canRevoke"
|
||||||
theme="danger"
|
theme="danger"
|
||||||
content="履约冲正"
|
content="履约冲正"
|
||||||
@click="openRevoke"
|
@click="revokeVisible = true"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -110,42 +110,16 @@
|
|||||||
>
|
>
|
||||||
<t-textarea v-model.trim="reason" borderless autosize placeholder="请输入原因" />
|
<t-textarea v-model.trim="reason" borderless autosize placeholder="请输入原因" />
|
||||||
</t-dialog>
|
</t-dialog>
|
||||||
<bottom-popup
|
<order-fulfillment-revoke
|
||||||
v-model="revokeVisible"
|
v-if="fulfillment"
|
||||||
title="履约冲正"
|
v-model:visible="revokeVisible"
|
||||||
left-button-text="取消"
|
:fulfillment="fulfillment"
|
||||||
right-button-text="提交"
|
@success="loadData"
|
||||||
@left-button-click="revokeVisible = false"
|
/>
|
||||||
@right-button-click="submitRevoke"
|
|
||||||
>
|
|
||||||
<t-form class="revoke-form">
|
|
||||||
<t-form-item label="冲正原因" required-mark>
|
|
||||||
<t-textarea
|
|
||||||
v-model="revokeReason"
|
|
||||||
borderless
|
|
||||||
autosize
|
|
||||||
/>
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="库存退回门店" content-align="right">
|
|
||||||
<t-switch v-model="returnedToStore" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label-width="0">
|
|
||||||
<media-selector
|
|
||||||
:model-value="photoUrlList"
|
|
||||||
v-model:photo-items="photoItems"
|
|
||||||
:upload="uploadPhotoList"
|
|
||||||
:edit-mode="true"
|
|
||||||
required
|
|
||||||
title="冲正凭证照片"
|
|
||||||
/>
|
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</bottom-popup>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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";
|
||||||
@@ -154,19 +128,18 @@ import SalesOrderAPI from "@/api/SalesOrderAPI";
|
|||||||
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
|
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
|
||||||
import ProductSkuTags from "@/components/ProductSkuTags.vue";
|
import ProductSkuTags from "@/components/ProductSkuTags.vue";
|
||||||
import ProductStockPreview from "@/components/ProductStockPreview.vue";
|
import ProductStockPreview from "@/components/ProductStockPreview.vue";
|
||||||
|
import OrderFulfillmentRevoke from "@/pages/fulfillment/components/OrderFulfillmentRevoke.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { OrderFulfillment, OrderFulfillmentItem } from "@/types/Fulfillment";
|
import type { OrderFulfillment, OrderFulfillmentItem } from "@/types/Fulfillment";
|
||||||
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
|
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 { addPackageQuantity, getPackageQuantityText } from "@/utils/package";
|
||||||
import { addPackageQuantity, createPackageRequestNo, getPackageQuantityText } 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, getSalesOrderItemTypeText, 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 type { ProductStockPreviewItem } from "@/utils/productStock";
|
import type { ProductStockPreviewItem } from "@/utils/productStock";
|
||||||
import { buildProductStockPreview, mergeProductStockRequirements } from "@/utils/productStock";
|
import { buildProductStockPreview, mergeProductStockRequirements } from "@/utils/productStock";
|
||||||
|
|
||||||
@@ -184,13 +157,8 @@ const reasonVisible = ref(false);
|
|||||||
const reasonAction = ref<ReasonAction>("reject");
|
const reasonAction = ref<ReasonAction>("reject");
|
||||||
const reason = ref("");
|
const reason = ref("");
|
||||||
const revokeVisible = ref(false);
|
const revokeVisible = ref(false);
|
||||||
const revokeReason = ref("");
|
|
||||||
const returnedToStore = ref(true);
|
|
||||||
const revokeRequestNo = ref("");
|
|
||||||
const photoItems = ref<PhotoItem[]>([]);
|
|
||||||
const stockPreviewList = ref<ProductStockPreviewItem[]>([]);
|
const stockPreviewList = ref<ProductStockPreviewItem[]>([]);
|
||||||
const stockPreviewLoading = ref(false);
|
const stockPreviewLoading = ref(false);
|
||||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
|
||||||
const fulfillmentPhotoItems = computed<PhotoItem[]>(() => (fulfillment.value?.attachmentList || [])
|
const fulfillmentPhotoItems = computed<PhotoItem[]>(() => (fulfillment.value?.attachmentList || [])
|
||||||
.filter(item => !!item.id)
|
.filter(item => !!item.id)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
@@ -363,29 +331,6 @@ async function submitReason(): Promise<void> {
|
|||||||
} catch (error) { Message.error(getErrorMessage(error, "履约状态操作失败")); }
|
} catch (error) { Message.error(getErrorMessage(error, "履约状态操作失败")); }
|
||||||
finally { operating.value = false; }
|
finally { operating.value = false; }
|
||||||
}
|
}
|
||||||
function openRevoke(): void { revokeReason.value = ""; returnedToStore.value = true; revokeRequestNo.value = ""; photoItems.value = []; revokeVisible.value = true; }
|
|
||||||
function buildAttachments(): Partial<Attachment>[] {
|
|
||||||
return photoItems.value.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: "PHOTO" }] : []);
|
|
||||||
}
|
|
||||||
async function submitRevoke(): Promise<void> {
|
|
||||||
if (!fulfillment.value?.id || !revokeReason.value || operating.value) return;
|
|
||||||
if (fulfillment.value.stockRecordList?.length && !buildAttachments().length) { Message.warning("实物履约冲正必须上传退回照片"); return; }
|
|
||||||
if (!revokeRequestNo.value) revokeRequestNo.value = createPackageRequestNo();
|
|
||||||
operating.value = true;
|
|
||||||
try {
|
|
||||||
fulfillment.value = await FulfillmentAPI.revoke({
|
|
||||||
fulfillmentId: fulfillment.value.id,
|
|
||||||
requestNo: revokeRequestNo.value,
|
|
||||||
reason: revokeReason.value.trim(),
|
|
||||||
returnedToStore: returnedToStore.value,
|
|
||||||
attachmentList: buildAttachments()
|
|
||||||
});
|
|
||||||
revokeVisible.value = false;
|
|
||||||
Message.success("履约单已冲正");
|
|
||||||
await loadData();
|
|
||||||
} catch (error) { Message.error(getErrorMessage(error, "履约冲正失败,重试会复用同一请求号")); }
|
|
||||||
finally { operating.value = false; }
|
|
||||||
}
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
onUnmounted(() => navBarStore.clearRight());
|
onUnmounted(() => navBarStore.clearRight());
|
||||||
</script>
|
</script>
|
||||||
@@ -467,7 +412,4 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.revoke-form {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<template>
|
||||||
|
<bottom-popup
|
||||||
|
v-model="visible"
|
||||||
|
title="履约冲正"
|
||||||
|
left-button-text="取消"
|
||||||
|
:right-button-text="operating ? '提交中' : '提交'"
|
||||||
|
:z-index="revokePopupZIndex"
|
||||||
|
:overlay-props="{ zIndex: revokePopupZIndex - 1 }"
|
||||||
|
@left-button-click="visible = false"
|
||||||
|
@right-button-click="submit"
|
||||||
|
>
|
||||||
|
<t-form class="revoke-form" label-width="7rem">
|
||||||
|
<t-form-item label="冲正原因" required-mark>
|
||||||
|
<div class="reason">
|
||||||
|
<t-textarea
|
||||||
|
v-model.trim="reason"
|
||||||
|
borderless
|
||||||
|
autosize
|
||||||
|
maxlength="500"
|
||||||
|
placeholder="请输入或选择冲正原因"
|
||||||
|
/>
|
||||||
|
<t-button
|
||||||
|
size="extra-small"
|
||||||
|
theme="primary"
|
||||||
|
variant="outline"
|
||||||
|
@click="openReasonPicker"
|
||||||
|
>选择</t-button>
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="库存退回门店" content-align="right">
|
||||||
|
<t-switch v-model="returnedToStore" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label-width="0">
|
||||||
|
<media-selector
|
||||||
|
:model-value="photoUrlList"
|
||||||
|
v-model:photo-items="photoItems"
|
||||||
|
:upload="uploadPhotoList"
|
||||||
|
:edit-mode="true"
|
||||||
|
required
|
||||||
|
title="冲正凭证照片"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</bottom-popup>
|
||||||
|
<t-popup
|
||||||
|
v-model="reasonPickerVisible"
|
||||||
|
placement="bottom"
|
||||||
|
:z-index="reasonPickerZIndex"
|
||||||
|
:overlay-props="{ zIndex: reasonPickerZIndex - 1 }"
|
||||||
|
>
|
||||||
|
<t-picker
|
||||||
|
v-model="reasonPickerDraft"
|
||||||
|
:columns="reasonPickerColumns"
|
||||||
|
title="选择冲正原因"
|
||||||
|
@cancel="reasonPickerVisible = false"
|
||||||
|
@confirm="confirmReason"
|
||||||
|
/>
|
||||||
|
</t-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { BottomPopup } from "timi-tdesign-mobile";
|
||||||
|
import type { Attachment } from "timi-web-api";
|
||||||
|
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||||
|
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
|
||||||
|
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||||
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
|
import { getErrorMessage } from "@/utils/error";
|
||||||
|
import { createPackageRequestNo } from "@/utils/package";
|
||||||
|
import { Message } from "@/utils/message";
|
||||||
|
import { uploadPhotoList } from "@/utils/photo";
|
||||||
|
|
||||||
|
defineOptions({ name: "OrderFulfillmentRevoke" });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fulfillment: OrderFulfillment;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
const visible = defineModel<boolean>("visible", { default: false });
|
||||||
|
|
||||||
|
const revokePopupZIndex = 12000;
|
||||||
|
const reasonPickerZIndex = revokePopupZIndex + 1;
|
||||||
|
const operating = ref(false);
|
||||||
|
const reason = ref("");
|
||||||
|
const returnedToStore = ref(true);
|
||||||
|
const photoItems = ref<PhotoItem[]>([]);
|
||||||
|
const reasonPickerVisible = ref(false);
|
||||||
|
const reasonPickerDraft = ref<string[]>([]);
|
||||||
|
const requestNo = ref("");
|
||||||
|
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||||
|
const reasonOptions = [
|
||||||
|
"客户申请撤销履约",
|
||||||
|
"履约数量填写有误",
|
||||||
|
"商品或服务与实际不符",
|
||||||
|
"重复履约",
|
||||||
|
"履约操作有误",
|
||||||
|
"库存扣减有误"
|
||||||
|
];
|
||||||
|
const reasonPickerColumns = reasonOptions.map(item => ({ label: item, value: item }));
|
||||||
|
|
||||||
|
function resetForm(): void {
|
||||||
|
reason.value = "";
|
||||||
|
returnedToStore.value = true;
|
||||||
|
photoItems.value = [];
|
||||||
|
reasonPickerVisible.value = false;
|
||||||
|
reasonPickerDraft.value = [];
|
||||||
|
requestNo.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openReasonPicker(): void {
|
||||||
|
const currentReason = reasonOptions.includes(reason.value) ? reason.value : reasonOptions[0];
|
||||||
|
reasonPickerDraft.value = [currentReason];
|
||||||
|
reasonPickerVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmReason(value: Array<string | number>): void {
|
||||||
|
reason.value = String(value[0] || reasonPickerDraft.value[0] || "");
|
||||||
|
reasonPickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAttachments(): Partial<Attachment>[] {
|
||||||
|
return photoItems.value.flatMap(item => item.tempFileId ? [{
|
||||||
|
tempFileId: item.tempFileId,
|
||||||
|
attachType: "PHOTO"
|
||||||
|
}] : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit(): Promise<void> {
|
||||||
|
if (operating.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!reason.value.trim()) {
|
||||||
|
Message.warning("冲正原因不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const attachmentList = buildAttachments();
|
||||||
|
if (props.fulfillment.stockRecordList?.length && !attachmentList.length) {
|
||||||
|
Message.warning("实物履约冲正必须上传退回照片");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!props.fulfillment.id) {
|
||||||
|
Message.error("履约单编号缺失,无法冲正");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!requestNo.value) {
|
||||||
|
requestNo.value = createPackageRequestNo();
|
||||||
|
}
|
||||||
|
operating.value = true;
|
||||||
|
try {
|
||||||
|
await FulfillmentAPI.revoke({
|
||||||
|
fulfillmentId: props.fulfillment.id,
|
||||||
|
requestNo: requestNo.value,
|
||||||
|
reason: reason.value.trim(),
|
||||||
|
returnedToStore: returnedToStore.value,
|
||||||
|
attachmentList
|
||||||
|
});
|
||||||
|
visible.value = false;
|
||||||
|
Message.success("履约单已冲正");
|
||||||
|
emit("success");
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(getErrorMessage(error, "履约冲正失败,重试会复用同一请求号"));
|
||||||
|
} finally {
|
||||||
|
operating.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(visible, value => {
|
||||||
|
if (value) {
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
reasonPickerVisible.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.revoke-form {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.reason {
|
||||||
|
gap: .5rem;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.t-textarea {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user