feat(aftersale): per-line refund/return flow with ledger-backed completion (add-aftersale-refunds)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { InvoiceKind, Order, Shipment } from "@vmall/shared";
|
||||
import type { Aftersale, InvoiceKind, Order, Shipment } from "@vmall/shared";
|
||||
import { t as pick } from "@vmall/shared";
|
||||
|
||||
definePageMeta({ middleware: "auth" });
|
||||
@@ -10,6 +10,7 @@ const route = useRoute();
|
||||
|
||||
const order = ref<Order | null>(null);
|
||||
const shipments = ref<Shipment[]>([]);
|
||||
const aftersales = ref<Aftersale[]>([]);
|
||||
const loading = ref(true);
|
||||
const confirmingShipmentId = ref("");
|
||||
const invoiceWorking = ref(false);
|
||||
@@ -23,13 +24,23 @@ const invoiceKind = ref<InvoiceKind>("personal");
|
||||
const orderId = computed(() => String(route.params.id ?? ""));
|
||||
|
||||
const orderShipments = computed(() => shipments.value.filter((shipment) => shipment.order_id === order.value?.id));
|
||||
const activeAftersaleItemIds = computed(() => new Set(
|
||||
aftersales.value
|
||||
.filter((row) => ["pending", "approved", "buyer_shipping", "merchant_confirmed"].includes(row.status))
|
||||
.map((row) => row.order_item_id),
|
||||
));
|
||||
|
||||
function canApply(itemId: string): boolean {
|
||||
return Boolean(order.value && ["paid", "fulfilling", "shipped", "completed"].includes(order.value.status) && !activeAftersaleItemIds.value.has(itemId));
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [o, allShipments] = await Promise.all([$api.getOrder(orderId.value), $api.listMyShipments()]);
|
||||
const [o, allShipments, mine] = await Promise.all([$api.getOrder(orderId.value), $api.listMyShipments(), $api.listMyAftersales()]);
|
||||
order.value = o;
|
||||
shipments.value = allShipments;
|
||||
aftersales.value = mine;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -92,13 +103,14 @@ onMounted(() => {
|
||||
<VCard :padded="false" class="p-5">
|
||||
<h2 class="mb-3.5 text-[15px] font-semibold text-text">{{ t("user.orderItems") }}</h2>
|
||||
<VTable>
|
||||
<thead><tr><th>{{ t("user.product") }}</th><th>{{ t("common.price") }}</th><th>{{ t("common.qty") }}</th><th>{{ t("common.total") }}</th></tr></thead>
|
||||
<thead><tr><th>{{ t("user.product") }}</th><th>{{ t("common.price") }}</th><th>{{ t("common.qty") }}</th><th>{{ t("common.total") }}</th><th /></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="item in order.items" :key="item.id">
|
||||
<td><div class="flex items-center gap-2.5"><img class="h-11 w-11 border border-border object-contain" :src="item.image || '/mock/product-1.svg'" :alt="pick(item.product_name, locale)" /><span>{{ pick(item.product_name, locale) }}</span><VBadge v-if="item.flash_sale_item_id" tone="red">{{ t("marketing.flashTag") }}</VBadge></div></td>
|
||||
<td><PriceText :amount-minor="item.unit_price_minor" :currency="order.currency" /></td>
|
||||
<td>{{ item.qty }}</td>
|
||||
<td><PriceText :amount-minor="item.unit_price_minor * item.qty" :currency="order.currency" /></td>
|
||||
<td><NuxtLink v-if="canApply(item.id)" class="text-primary no-underline" :to="{ path: '/user/aftersales/apply', query: { order_id: order.id, item_id: item.id } }">{{ t("user.applyAftersaleForItem") }}</NuxtLink></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
|
||||
Reference in New Issue
Block a user