@mohasinac/appkit 4.41.11 โ 4.41.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/features/admin/components/AdminOrdersView.js +29 -3
- package/dist/features/seller/components/SellerOrdersView.js +28 -4
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +2 -2
- package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +15 -15
- package/dist/seed/orders-seed-data.js +44 -1
- package/package.json +1 -1
|
@@ -141,9 +141,35 @@ export function AdminOrdersView({ children, ...props }) {
|
|
|
141
141
|
? `${title}${extraCount > 0 ? ` +${extraCount} more` : ""}`
|
|
142
142
|
: `Order ${orderId}`,
|
|
143
143
|
secondary: [
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
/*
|
|
145
|
+
* ๐ `userName` and `totalPrice` FIRST โ they are the document's
|
|
146
|
+
* real field names (OrderDocument, and the live response of
|
|
147
|
+
* GET /api/admin/orders).
|
|
148
|
+
*
|
|
149
|
+
* This read `buyerName ?? customerName` and
|
|
150
|
+
* `totalAmount ?? total ?? amount`: five names, not one of which
|
|
151
|
+
* the order document has ever carried. So every row rendered
|
|
152
|
+
* "Unknown buyer ยท -", on all 25 rows, for as long as the view has
|
|
153
|
+
* existed โ and the `??` chains made it look like considered
|
|
154
|
+
* defensive coding rather than a guess. A fallback spelled three
|
|
155
|
+
* ways is the tell that nobody checked which one was real.
|
|
156
|
+
*
|
|
157
|
+
* The aliases are GONE rather than demoted to fallbacks: nothing
|
|
158
|
+
* declares them (the client `Order` type has no such fields) and
|
|
159
|
+
* nothing emits them (no adapter renames to them). Keeping a
|
|
160
|
+
* fallback with no producer only preserves the ambiguity that
|
|
161
|
+
* hid the bug.
|
|
162
|
+
*/
|
|
163
|
+
toStringValue(item.userName, "Unknown buyer"),
|
|
164
|
+
toCurrency(item.totalPrice),
|
|
165
|
+
/*
|
|
166
|
+
* Not `.slice(0, 14)` โ that cut mid-date, so
|
|
167
|
+
* `order-1-20260822-aucwon` and `order-1-20260821-prizedr` both
|
|
168
|
+
* rendered as "order-1-202608". A truncation that destroys
|
|
169
|
+
* uniqueness is worse than a long string, because the id is the
|
|
170
|
+
* one thing on the row an admin searches by.
|
|
171
|
+
*/
|
|
172
|
+
orderId,
|
|
147
173
|
paymentReviewLabel(item),
|
|
148
174
|
]
|
|
149
175
|
.filter(Boolean)
|
|
@@ -366,13 +366,37 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
|
|
|
366
366
|
const loc = item.physicalLocation;
|
|
367
367
|
return {
|
|
368
368
|
id: toStringValue(item.id, `order-${index}`),
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
/*
|
|
370
|
+
* ๐ The row says WHAT WAS SOLD, not the id repeated.
|
|
371
|
+
*
|
|
372
|
+
* This was `Order ${id.slice(0, 14)}` โ a raw id, truncated
|
|
373
|
+
* mid-date, so two different orders rendered identically. The title
|
|
374
|
+
* was available the whole time: `items[0].productTitle` is
|
|
375
|
+
* denormalised onto every order precisely so a list never needs a
|
|
376
|
+
* second fetch (Root Cause #52), and it is already read three lines
|
|
377
|
+
* below as `itemTitle`.
|
|
378
|
+
*/
|
|
379
|
+
primary: (() => {
|
|
380
|
+
const title = typeof firstItem.productTitle === "string" ? firstItem.productTitle : "";
|
|
381
|
+
if (!title)
|
|
382
|
+
return `Order ${toStringValue(item.id, "-")}`;
|
|
383
|
+
const extra = itemsArr.length - 1;
|
|
384
|
+
return extra > 0 ? `${title} +${extra} more` : title;
|
|
385
|
+
})(),
|
|
386
|
+
/*
|
|
387
|
+
* `userName` / `totalPrice` are the document's real field names.
|
|
388
|
+
* `buyerName`/`buyerDisplayName`/`totalAmount`/`total` are four
|
|
389
|
+
* spellings the order document has never carried, so every seller's
|
|
390
|
+
* order list showed "Unknown buyer" and โน0 โ see AdminOrdersView for
|
|
391
|
+
* the full writeup. They are removed rather than demoted: nothing
|
|
392
|
+
* declares them and nothing emits them.
|
|
393
|
+
*/
|
|
394
|
+
secondary: toStringValue(item.userName, "Unknown buyer"),
|
|
371
395
|
status: toStringValue(item.status, "PENDING"),
|
|
372
396
|
updatedAt: toRelativeDate(item.updatedAt ?? item.orderDate ?? item.createdAt),
|
|
373
397
|
itemCount: itemsArr.length,
|
|
374
|
-
totalAmount: Number(item.
|
|
375
|
-
buyerName: toStringValue(item.
|
|
398
|
+
totalAmount: Number(item.totalPrice ?? 0),
|
|
399
|
+
buyerName: toStringValue(item.userName, "Unknown buyer"),
|
|
376
400
|
itemImage: typeof firstItem.image === "string" ? firstItem.image : undefined,
|
|
377
401
|
itemTitle: typeof firstItem.productTitle === "string" ? firstItem.productTitle : undefined,
|
|
378
402
|
physicalLocation: loc && typeof loc.zone === "string"
|
|
@@ -117,12 +117,12 @@ export const authored = {
|
|
|
117
117
|
startPage: "/admin/orders",
|
|
118
118
|
steps: [
|
|
119
119
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
120
|
-
"Open /admin/orders and open the row drawer for order-1-
|
|
120
|
+
"Open /admin/orders and open the row drawer for order-1-20251107-11joon.",
|
|
121
121
|
"Write down the item titles, the shipping address, the payment method and the status.",
|
|
122
122
|
"Close the drawer and use that row's Open full page action.",
|
|
123
123
|
"Read the same four things on the full page.",
|
|
124
124
|
],
|
|
125
|
-
inputs: { orderId: "order-1-
|
|
125
|
+
inputs: { orderId: "order-1-20251107-11joon" },
|
|
126
126
|
expectedBehaviour: "The drawer and the full page are two renderings of one record, so they must agree. Where they differ, one of them is reading a field the other is not โ and the full page is the bookmarkable one an admin will send to somebody else.",
|
|
127
127
|
expectedUiState: "Item titles, address, payment method and status are identical in both, and the full page is at a real bookmarkable URL rather than a modal over the list.",
|
|
128
128
|
expectedData: { drawerAndPageAgree: true },
|
|
@@ -42,12 +42,12 @@ export const authored = {
|
|
|
42
42
|
startPage: "/user/orders",
|
|
43
43
|
steps: [
|
|
44
44
|
"Sign in as karthik.new@gmail.com / TempPass123!, a seeded buyer who owns no seeded orders.",
|
|
45
|
-
"Navigate directly to /api/user/orders/order-1-
|
|
45
|
+
"Navigate directly to /api/user/orders/order-1-20251107-11joon/invoice โ a seeded order belonging to a different persona.",
|
|
46
46
|
"Read the response.",
|
|
47
|
-
"Now navigate to /user/orders/order-1-
|
|
47
|
+
"Now navigate to /user/orders/order-1-20251107-11joon.",
|
|
48
48
|
"Read that page.",
|
|
49
49
|
],
|
|
50
|
-
inputs: { foreignOrderId: "order-1-
|
|
50
|
+
inputs: { foreignOrderId: "order-1-20251107-11joon" },
|
|
51
51
|
expectedBehaviour: "An order id is short and guessable, so ownership is checked on every hit rather than relied upon to be secret. Answering 403 instead of 404 would confirm that an order with that id exists, which is a membership oracle over the id space.",
|
|
52
52
|
expectedUiState: "Neither the invoice nor the order page shows any of that order's contents. No item titles, no address, no amounts.",
|
|
53
53
|
expectedData: { foreignInvoiceServed: false },
|
|
@@ -74,14 +74,14 @@ export const authored = {
|
|
|
74
74
|
startPage: "/user/orders",
|
|
75
75
|
steps: [
|
|
76
76
|
"Sign in as vivaan.kapoor@gmail.com / TempPass123! โ this persona owns the seeded DELIVERED orders, and a return can only be requested on one.",
|
|
77
|
-
"Open /user/orders and open order-1-
|
|
77
|
+
"Open /user/orders and open order-1-20251107-11joon, which is delivered.",
|
|
78
78
|
"Use the Return action.",
|
|
79
79
|
"Give the reason: item not as described.",
|
|
80
80
|
"Submit the request.",
|
|
81
81
|
"Reload the order.",
|
|
82
82
|
"Open /user/returns and look for it.",
|
|
83
83
|
],
|
|
84
|
-
inputs: { orderId: "order-1-
|
|
84
|
+
inputs: { orderId: "order-1-20251107-11joon", reason: "item not as described" },
|
|
85
85
|
expectedBehaviour: "A return request moves the order into Return Requested and appears on the returns surface. That status is deliberately an ACTIVE one โ it is the seller's next action item, not a closed state โ so the order must not disappear from the active list.",
|
|
86
86
|
expectedUiState: "The order's status reads Return Requested, and the same order is listed under /user/returns.",
|
|
87
87
|
expectedData: { statusAfterReturn: "return_requested" },
|
|
@@ -92,12 +92,12 @@ export const authored = {
|
|
|
92
92
|
startPage: "/user/orders",
|
|
93
93
|
steps: [
|
|
94
94
|
"Sign in as rehan.sheikh@gmail.com / TempPass123! โ this persona owns the seeded SHIPPED order.",
|
|
95
|
-
"Open /user/orders and open order-1-
|
|
95
|
+
"Open /user/orders and open order-1-20251104-aevnlw.",
|
|
96
96
|
"Use the Track action.",
|
|
97
97
|
"Read every step on the timeline and note which carry a date and which do not.",
|
|
98
98
|
"Compare the dates shown against the order's own placed and shipped dates.",
|
|
99
99
|
],
|
|
100
|
-
inputs: { orderId: "order-1-
|
|
100
|
+
inputs: { orderId: "order-1-20251104-aevnlw" },
|
|
101
101
|
expectedBehaviour: "Each timeline step shows the date actually recorded for it. A step with no recorded date renders an em-dash rather than a guess โ an invented or estimated date is worse than a blank, because it cannot be told apart from a real one.",
|
|
102
102
|
expectedUiState: "The placed and shipped steps carry dates matching the order. Steps the order has not reached show no date rather than today's.",
|
|
103
103
|
expectedData: { fabricatedDatesShown: false },
|
|
@@ -108,14 +108,14 @@ export const authored = {
|
|
|
108
108
|
startPage: "/user/orders",
|
|
109
109
|
steps: [
|
|
110
110
|
"Sign in as karthik.new@gmail.com / TempPass123!, a seeded buyer who owns no seeded orders.",
|
|
111
|
-
"Navigate to /user/orders/order-1-
|
|
112
|
-
"Navigate to /user/orders/order-1-
|
|
113
|
-
"Navigate to /user/orders/order-1-
|
|
114
|
-
"Navigate to /user/orders/order-1-
|
|
115
|
-
"Navigate to /user/orders/order-1-
|
|
111
|
+
"Navigate to /user/orders/order-1-20251107-11joon/track.",
|
|
112
|
+
"Navigate to /user/orders/order-1-20251107-11joon/invoice.",
|
|
113
|
+
"Navigate to /user/orders/order-1-20251107-11joon/return.",
|
|
114
|
+
"Navigate to /user/orders/order-1-20251107-11joon/cancel.",
|
|
115
|
+
"Navigate to /user/orders/order-1-20251107-11joon/payment.",
|
|
116
116
|
"Read each page in turn.",
|
|
117
117
|
],
|
|
118
|
-
inputs: { foreignOrderId: "order-1-
|
|
118
|
+
inputs: { foreignOrderId: "order-1-20251107-11joon" },
|
|
119
119
|
expectedBehaviour: "Ownership is enforced on EVERY subroute, not only on the order page that links to them. A subroute that checks nothing is reachable by typing a URL, and these five carry the address, the amounts and the payment details.",
|
|
120
120
|
expectedUiState: "All five show a not-found page. None shows an item title, an address, an amount or a tracking number belonging to that order.",
|
|
121
121
|
expectedData: { subroutesLeakingData: 0 },
|
|
@@ -126,11 +126,11 @@ export const authored = {
|
|
|
126
126
|
startPage: "/user/orders",
|
|
127
127
|
steps: [
|
|
128
128
|
"Open a private window with no session.",
|
|
129
|
-
"Navigate to /user/orders/order-1-
|
|
129
|
+
"Navigate to /user/orders/order-1-20251107-11joon/track.",
|
|
130
130
|
"Read the page.",
|
|
131
131
|
"Navigate to /user/orders and read that page too.",
|
|
132
132
|
],
|
|
133
|
-
inputs: { foreignOrderId: "order-1-
|
|
133
|
+
inputs: { foreignOrderId: "order-1-20251107-11joon" },
|
|
134
134
|
expectedBehaviour: "A signed-out visitor is sent to sign in. Being blocked IS the expected result here โ the failure to look for is the page rendering its content for a moment before redirecting, which is enough to read and enough for a crawler to capture.",
|
|
135
135
|
expectedUiState: "The sign-in page, or the orders page in its signed-out state. At no point does any order content appear, not even briefly.",
|
|
136
136
|
expectedData: { orderContentVisibleToGuest: false },
|
|
@@ -448,11 +448,54 @@ const cashOrderVerified = {
|
|
|
448
448
|
createdAt: daysAgo(4),
|
|
449
449
|
updatedAt: daysAgo(2),
|
|
450
450
|
};
|
|
451
|
+
/*
|
|
452
|
+
* ๐ DERIVES `items[]` FROM THE FLAT FIELDS WHEN A FIXTURE OMITS IT.
|
|
453
|
+
*
|
|
454
|
+
* `items[]` is the canonical shape and every orders surface reads
|
|
455
|
+
* `items[0].productTitle` for its row label (Root Cause #52). The generator
|
|
456
|
+
* below was taught to emit it โ and that fix was NARROWER THAN THE DEFECT:
|
|
457
|
+
* measured after it shipped, 14 of the 50 seeded orders still had none,
|
|
458
|
+
* because the hand-written fixtures never go through the generator.
|
|
459
|
+
*
|
|
460
|
+
* That is Root Cause #84's shape โ a fix scoped to the population you happened
|
|
461
|
+
* to be looking at. Patching those 14 by hand would leave the 15th to whoever
|
|
462
|
+
* writes it next, so the derivation lives here instead, on the path EVERY
|
|
463
|
+
* fixture already takes.
|
|
464
|
+
*
|
|
465
|
+
* It only ever fills a gap: a fixture that declares its own `items[]` (the
|
|
466
|
+
* multi-line ones, where the flat fields cannot express two products) is
|
|
467
|
+
* passed through untouched.
|
|
468
|
+
*/
|
|
469
|
+
function withOrderItems(order) {
|
|
470
|
+
if (Array.isArray(order.items) && order.items.length > 0)
|
|
471
|
+
return order;
|
|
472
|
+
if (!order.productId)
|
|
473
|
+
return order;
|
|
474
|
+
const quantity = order.quantity ?? 1;
|
|
475
|
+
const unitPrice = order.unitPrice ?? 0;
|
|
476
|
+
return {
|
|
477
|
+
...order,
|
|
478
|
+
items: [
|
|
479
|
+
{
|
|
480
|
+
productId: order.productId,
|
|
481
|
+
productTitle: order.productTitle ?? order.productId,
|
|
482
|
+
listingType: "standard",
|
|
483
|
+
quantity,
|
|
484
|
+
unitPrice,
|
|
485
|
+
// Prefer the order's own total: on a single-line order it is the
|
|
486
|
+
// authoritative figure, and re-deriving it would silently drop any
|
|
487
|
+
// fixture that deliberately encodes a locked or negotiated price.
|
|
488
|
+
totalPrice: order.totalPrice ?? unitPrice * quantity,
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
};
|
|
492
|
+
}
|
|
451
493
|
// Backfills image/imageUrls (see orderItemImage() above) on every fixture
|
|
452
494
|
// regardless of which shape it uses (multi-item `items[]` vs the legacy
|
|
453
495
|
// single-item top-level fields), so no seeded order silently renders without
|
|
454
496
|
// a thumbnail the way real pre-2026-08-20 orders do.
|
|
455
|
-
function withOrderImages(
|
|
497
|
+
function withOrderImages(input) {
|
|
498
|
+
const order = withOrderItems(input);
|
|
456
499
|
return {
|
|
457
500
|
...order,
|
|
458
501
|
imageUrls: order.imageUrls ?? (order.productId ? [orderItemImage(order.productId)] : undefined),
|