feat(apps): sales-expert screens in both apps + v2 admin home slider (D21, APP-02)
Both expert screens are built in user-app and user-app-v2, with byte-identical
template and script and a per-app style block only — verified by diffing
everything above <style>:
ChildOrdersView "سفارش مشتریان" — legacy ChildOrders.vue. Keeps the
reference's title, search, infinite paging, and per-card item count, total
weight, customer name and the fixed «تحویل مسئول فروش» chip (the endpoint
only returns that status, ApiMobileController:766).
CustomerChoiceView "لیست مشتریان" — legacy customerChoice.vue. Keeps its
title, per-search re-query and the reference's choice to LIST inactive
customers (its active() scope is commented out at :187).
Reachable from inside the app, not just by URL: routes carry the reference's own
paths and names, the dashboard grows the two entries only for an expert (v1 as
tiles, v2 as its own rows), and a router guard sends an ordinary customer back
to their dashboard. None of that is relied on for safety — the server scopes
both endpoints on the AdminUser id in the token and answers 403 otherwise.
Not ported, both recorded in D21: the act-as api_token minting (:197-201) and
the child-order edit drawer, which recomputed an order's money from client-sent
values. The card opens a read-only summary instead.
APP-02 fixed: user-app-v2 gained getHomeSlider and now prefers admin-configured
slides, keeping its category-derived hero as the fallback and its own design.
Session stores stay byte-identical across the apps.
build 5/5, jest 45/45, expert login 36/36, apps smoke 43/43, typecheck clean.
[ref: henza_pwa/src/views/user/src/components/ChildOrders.vue, customerChoice.vue]
[ref: henza_pwa/src/router/index.ts:171-188]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
69f4ba2b29
commit
f71fd5bfe1
@@ -189,6 +189,45 @@ export interface AdminSlide {
|
||||
link: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* D21 — a sales expert signed into the app. Ordinary customers get
|
||||
* `isSalesExpert: false` and neither expert screen is shown to them.
|
||||
*/
|
||||
export interface ExpertIdentity {
|
||||
isSalesExpert: boolean;
|
||||
type: 'customer' | 'sales-expert';
|
||||
}
|
||||
|
||||
/** One of the expert's referred customers ("انتخاب مشتری"). */
|
||||
export interface ExpertCustomer {
|
||||
id: string;
|
||||
/** Legacy key the reference's card bound to. */
|
||||
name: string;
|
||||
fullName: string;
|
||||
mobile: string;
|
||||
phone: string | null;
|
||||
store_name: string | null;
|
||||
pasazh_name: string | null;
|
||||
gram_level: string;
|
||||
remain: string;
|
||||
rial_remain: string;
|
||||
active: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** One row of "سفارش مشتریان". */
|
||||
export interface ExpertChildOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
status: string;
|
||||
/** Total order weight — the reference's `item.w` (ChildOrders.vue:55). */
|
||||
w: string;
|
||||
totalGoldWeightGrams: string;
|
||||
createdAt: string;
|
||||
items: OrderItemDetail[];
|
||||
customer: { id: string; name: string; mobile: string } | null;
|
||||
}
|
||||
export const api = {
|
||||
listProducts: () => request<ProductSummary[]>('/catalog/products'),
|
||||
|
||||
@@ -226,4 +265,20 @@ export const api = {
|
||||
/** GET /home/slider — public, no auth. Empty array when the admin hasn't
|
||||
* configured any slides (the caller falls back to a derived hero). */
|
||||
getHomeSlider: () => request<AdminSlide[]>('/home/slider'),
|
||||
|
||||
// ── D21: sales-expert screens ────────────────────────────────────────
|
||||
// All three scope themselves on the AdminUser id inside the token; no id
|
||||
// ever travels from the client. An ordinary customer gets 403 on the first
|
||||
// two, and `isSalesExpert: false` from whoami so the menu entries stay hidden.
|
||||
getExpertIdentity: () => request<ExpertIdentity>('/expert/whoami'),
|
||||
getExpertCustomers: (search?: string | null) =>
|
||||
request<ExpertCustomer[]>('/expert/my-customers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ search: search ?? null }),
|
||||
}),
|
||||
getExpertChildOrders: (page = 1, search?: string | null) =>
|
||||
request<{ rows: ExpertChildOrder[]; total: number; page: number; limit: number }>(
|
||||
'/expert/child-orders',
|
||||
{ method: 'POST', body: JSON.stringify({ page, search: search ?? null }) },
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user