diff --git a/client/src/components/reservation/ReservationV4PaymentAttachmentPreview.vue b/client/src/components/reservation/ReservationV4PaymentAttachmentPreview.vue
index 1ef3c57..ec33fa1 100644
--- a/client/src/components/reservation/ReservationV4PaymentAttachmentPreview.vue
+++ b/client/src/components/reservation/ReservationV4PaymentAttachmentPreview.vue
@@ -386,7 +386,6 @@ function formatFileSize(size: number | null): string {
.payment-attachments {
display: grid;
gap: 12px;
- margin: 0 20px 18px;
border: 1px solid var(--th-color-slate-200);
border-radius: var(--th-radius-sm);
background: var(--th-color-slate-50);
diff --git a/client/src/components/reservation/ReservationV4RoomingListCard.vue b/client/src/components/reservation/ReservationV4RoomingListCard.vue
index 82b58da..be3f81d 100644
--- a/client/src/components/reservation/ReservationV4RoomingListCard.vue
+++ b/client/src/components/reservation/ReservationV4RoomingListCard.vue
@@ -58,7 +58,6 @@ function readableValue(value?: string | null): string {
.rooming-list-card {
display: grid;
gap: 12px;
- margin: 0 20px 18px;
border: 1px solid var(--th-color-slate-200);
border-radius: var(--th-radius-sm);
background: var(--th-color-slate-50);
diff --git a/client/src/components/reservation/ReservationV4TraceCard.vue b/client/src/components/reservation/ReservationV4TraceCard.vue
index eb88e3c..3a21522 100644
--- a/client/src/components/reservation/ReservationV4TraceCard.vue
+++ b/client/src/components/reservation/ReservationV4TraceCard.vue
@@ -415,7 +415,6 @@ function isRecord(value: unknown): value is ReservationRecord {
.trace-card {
display: grid;
gap: 14px;
- margin: 0 20px 18px;
}
.trace-item {
diff --git a/client/src/i18n/locales/en-US.ts b/client/src/i18n/locales/en-US.ts
index 1e73e35..e9a82cc 100644
--- a/client/src/i18n/locales/en-US.ts
+++ b/client/src/i18n/locales/en-US.ts
@@ -581,6 +581,7 @@ export default {
totalCards: 'Total',
pendingCards: 'Pending',
reviewCards: 'Review',
+ readonlyCards: 'View only',
confirmedCards: 'Confirmed',
orderTaskId: 'Item ID',
orderReference: 'Order reference',
diff --git a/client/src/i18n/locales/th-TH.ts b/client/src/i18n/locales/th-TH.ts
index 6480d8b..4adeb48 100644
--- a/client/src/i18n/locales/th-TH.ts
+++ b/client/src/i18n/locales/th-TH.ts
@@ -581,6 +581,7 @@ export default {
totalCards: 'ทั้งหมด',
pendingCards: 'รอยืนยัน',
reviewCards: 'รอตรวจสอบ',
+ readonlyCards: 'ดูอย่างเดียว',
confirmedCards: 'ยืนยันแล้ว',
orderTaskId: 'รหัสรายการ',
orderReference: 'อ้างอิงออเดอร์',
diff --git a/client/src/i18n/locales/zh-CN.ts b/client/src/i18n/locales/zh-CN.ts
index 8f77d15..70510bc 100644
--- a/client/src/i18n/locales/zh-CN.ts
+++ b/client/src/i18n/locales/zh-CN.ts
@@ -581,6 +581,7 @@ export default {
totalCards: '全部',
pendingCards: '待确认',
reviewCards: '待复核',
+ readonlyCards: '仅查看',
confirmedCards: '已确认',
orderTaskId: '事项编号',
orderReference: '订单引用',
diff --git a/client/src/tests/reservationV4Views.spec.ts b/client/src/tests/reservationV4Views.spec.ts
index ecb0d87..0c5df38 100644
--- a/client/src/tests/reservationV4Views.spec.ts
+++ b/client/src/tests/reservationV4Views.spec.ts
@@ -7,6 +7,9 @@ import { createI18n } from 'vue-i18n'
import { createMemoryHistory, createRouter } from 'vue-router'
import taskCardSectionSource from '@/components/reservation/ReservationV4TaskCardSection.vue?raw'
+import paymentAttachmentPreviewSource from '@/components/reservation/ReservationV4PaymentAttachmentPreview.vue?raw'
+import roomingListCardSource from '@/components/reservation/ReservationV4RoomingListCard.vue?raw'
+import traceCardSource from '@/components/reservation/ReservationV4TraceCard.vue?raw'
import zhCN from '@/i18n/locales/zh-CN'
import { useAuthStore } from '@/stores/authStore'
import type {
@@ -164,7 +167,11 @@ describe('reservation V4 pages', () => {
expect(summary.text()).toContain('3')
expect(summary.text()).toContain('2')
expect(summary.text()).toContain(zhCN.taskV4.pendingCards)
+ expect(summary.text()).toContain(zhCN.taskV4.readonlyCards)
expect(summary.text()).toContain(zhCN.taskV4.confirmedCards)
+ const readonlyProgress = summary.find('.handling-progress__item--readonly')
+ expect(readonlyProgress.exists()).toBe(true)
+ expect(readonlyProgress.text()).toContain('1')
expect(summary.text()).not.toContain('9001')
expect(summary.text()).not.toContain(zhCN.taskV4.orderTaskId)
})
@@ -201,6 +208,25 @@ describe('reservation V4 pages', () => {
expect(wrapper.find('.card-actions .primary-button').exists()).toBe(false)
})
+ it('keeps bound order links in non-rooming card footers', async () => {
+ const detail = createOrderTaskDetail()
+ useBoundOrder(detail)
+ vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
+
+ const wrapper = await mountWithPlugins(
+ ReservationV4OrderTaskDetailView,
+ '/reservation/order-tasks/9001',
+ )
+ await flushPromises()
+
+ const firstCard = wrapper.find('.task-card-section')
+ const footerLink = firstCard.find('.task-card-section__footer .card-order-link')
+ expect(footerLink.exists()).toBe(true)
+ expect(footerLink.text()).toContain(zhCN.task.viewOrder)
+ expect(footerLink.attributes('data-to')).toBe('/reservation/orders/order-2001')
+ expect(firstCard.find('.task-card-section__body .card-order-link').exists()).toBe(false)
+ })
+
it('does not render direct attachment URLs from V4 source message payload', async () => {
const detail = createOrderTaskDetail({
sourceDisplayPayload: {
@@ -1408,6 +1434,12 @@ describe('reservation V4 pages', () => {
expect(taskCardSectionSource).toContain('.task-card-section__footer {\n align-items: flex-end;\n }')
})
+ it('lets specialized V4 card bodies align with the shared card shell padding', () => {
+ expect(paymentAttachmentPreviewSource).not.toContain('margin: 0 20px 18px')
+ expect(roomingListCardSource).not.toContain('margin: 0 20px 18px')
+ expect(traceCardSource).not.toContain('margin: 0 20px 18px')
+ })
+
it('requires confirmed order id before resolving an unresolved V4 review card', async () => {
const detail = createOrderTaskDetail({
businessCardStatus: 'REVIEW_REQUIRED',
@@ -1525,7 +1557,8 @@ async function mountWithPlugins(component: object, initialPath: string) {
plugins: [pinia, i18n, router, PrimeVue],
stubs: {
RouterLink: {
- template: '