From f336420415ea6bced405e8e18986a2ccd4ddabd6 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Thu, 13 Aug 2026 07:27:10 +0400 Subject: [PATCH] feat: add actor to order timeline audit trail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AdminOrderTimelineEntry had no actor field at all - order status changes and refund requests were unattributed. Added actor: string, populated from the signed-in admin's displayName (same pattern as Users/Transactions), surfaced in the order detail timeline UI. Real backend-issued orders still need a server-side audit trail; this only covers the local mock gateway pending backend work (BACKEND-API-REFERENCE.md ยง12). Co-Authored-By: Claude Sonnet 5 --- .../order-timeline/order-timeline.component.html | 3 +++ .../order-timeline/order-timeline.component.scss | 7 +++++++ .../order-timeline/order-timeline.component.ts | 1 + .../admin/orders/models/admin-order.model.ts | 1 + .../pages/admin-order-detail-page.component.ts | 1 + .../services/admin-orders-local.gateway.ts | 16 +++++++++++----- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.html b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.html index a98ae89..62904c2 100644 --- a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.html +++ b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.html @@ -13,6 +13,9 @@ }

{{ entry.note }}

+ @if (entry.actor) { + {{ entry.actor }} + } } diff --git a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.scss b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.scss index 248c40a..4a123bc 100644 --- a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.scss +++ b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.scss @@ -55,3 +55,10 @@ font-size: var(--font-size-sm, 0.8125rem); color: var(--text-secondary); } + +.order-timeline__actor { + display: block; + margin-top: 2px; + font-size: var(--font-size-xs, 0.75rem); + color: var(--text-light); +} diff --git a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.ts b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.ts index 54ea8f9..30a3075 100644 --- a/src/app/features/admin/orders/components/order-timeline/order-timeline.component.ts +++ b/src/app/features/admin/orders/components/order-timeline/order-timeline.component.ts @@ -8,6 +8,7 @@ export interface OrderTimelineEntry { /** Pre-resolved, already-translated display text for this event. */ note: string; orderNumber?: string; + actor?: string; } /** Reusable vertical event timeline - used on both the order detail page and the customer activity tab. */ diff --git a/src/app/features/admin/orders/models/admin-order.model.ts b/src/app/features/admin/orders/models/admin-order.model.ts index 7dc06d9..dbb0550 100644 --- a/src/app/features/admin/orders/models/admin-order.model.ts +++ b/src/app/features/admin/orders/models/admin-order.model.ts @@ -33,6 +33,7 @@ export interface AdminOrderTimelineEntry { status: AdminOrderStatus; timestamp: string; eventKey: AdminOrderTimelineEventKey; + actor: string; } export interface AdminOrder { diff --git a/src/app/features/admin/orders/pages/admin-order-detail-page.component.ts b/src/app/features/admin/orders/pages/admin-order-detail-page.component.ts index 7c74dc2..7435150 100644 --- a/src/app/features/admin/orders/pages/admin-order-detail-page.component.ts +++ b/src/app/features/admin/orders/pages/admin-order-detail-page.component.ts @@ -54,6 +54,7 @@ export class AdminOrderDetailPageComponent { note: this.translate.t('adminOrders.timelineEvent.' + entry.eventKey, { status: this.translate.t('adminOrders.status.' + entry.status), }), + actor: entry.actor, })); }); diff --git a/src/app/features/admin/orders/services/admin-orders-local.gateway.ts b/src/app/features/admin/orders/services/admin-orders-local.gateway.ts index 6420a19..b3f7969 100644 --- a/src/app/features/admin/orders/services/admin-orders-local.gateway.ts +++ b/src/app/features/admin/orders/services/admin-orders-local.gateway.ts @@ -1,8 +1,9 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; import { AdminOrder, AdminOrderListFilters, AdminOrdersListResult, AdminOrderStatus } from '../models/admin-order.model'; import { AdminOrdersGateway } from './admin-orders-gateway.interface'; +import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service'; const STATUSES: AdminOrderStatus[] = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded']; const CUSTOMER_NAMES = ['Anna Petrova', 'Karen Sargsyan', 'Ivan Ivanov', 'Mariam Grigoryan', 'Sergey Volkov', 'Lilit Hakobyan']; @@ -10,8 +11,13 @@ const SEED_COUNT = 24; @Injectable({ providedIn: 'root' }) export class AdminOrdersLocalGateway implements AdminOrdersGateway { + private readonly adminAuth = inject(AdminAuthService); private cache: AdminOrder[] | null = null; + private get currentActor(): string { + return this.adminAuth.displayName() ?? 'admin'; + } + loadOrders(filters: AdminOrderListFilters): Observable { const all = this.ensureData(); const filtered = all @@ -36,7 +42,7 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway { ...order, status, updatedAt: new Date().toISOString(), - timeline: [...order.timeline, { status, timestamp: new Date().toISOString(), eventKey: 'statusChanged' as const }], + timeline: [...order.timeline, { status, timestamp: new Date().toISOString(), eventKey: 'statusChanged' as const, actor: this.currentActor }], })); } @@ -45,7 +51,7 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway { ...order, payment: { ...order.payment, status: 'refund_requested' }, updatedAt: new Date().toISOString(), - timeline: [...order.timeline, { status: order.status, timestamp: new Date().toISOString(), eventKey: 'refundRequested' as const }], + timeline: [...order.timeline, { status: order.status, timestamp: new Date().toISOString(), eventKey: 'refundRequested' as const, actor: this.currentActor }], })); } @@ -122,8 +128,8 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway { notes: '', internalNotes: '', timeline: [ - { status: 'pending', timestamp: createdAt, eventKey: 'created' }, - ...(status !== 'pending' ? [{ status, timestamp: createdAt, eventKey: 'statusChanged' as const }] : []), + { status: 'pending', timestamp: createdAt, eventKey: 'created', actor: 'system' }, + ...(status !== 'pending' ? [{ status, timestamp: createdAt, eventKey: 'statusChanged' as const, actor: 'system' }] : []), ], archived: false, createdAt,