feat(orders): credit each order to the expert who placed it, as legacy did
Legacy fixed `orders.reagent_id` once, when the order was placed, and its «میزان فروش» summed `customer_id = X OR reagent_id = X`. The app credited orders to the customer's CURRENT referrer and left out an expert's own orders, so 9 of 49 imported experts showed a different figure. - Order.salesAgentId (+ referrerCustomerId for 17 legacy orders whose referrer was not an expert); additive migration, recent app orders backfilled from the customer's referrer - createOrder records the expert at placement time - one shared filter (agent-orders.ts) for the experts list, expert detail, dashboard and the expert app; child orders follow salesAgentId - customer page gets its «میزان فروش» back - import carries orders.reagent_id as is; ex-experts kept as admins keep their profile so their own orders stay linked - reconcile-sales: 1,557 of 1,558 match legacy's own formula; balances unchanged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8c5d971d1f
commit
29084904e9
@@ -0,0 +1,29 @@
|
||||
import type { Prisma } from '@prisma/client';
|
||||
|
||||
/**
|
||||
* The orders that count as a sales expert's sales («میزان فروش»).
|
||||
*
|
||||
* Legacy `CustomerController::updateGramSell`:
|
||||
* SUM(orders.input) WHERE customer_id = X OR reagent_id = X
|
||||
* (soft-deleted orders excluded by the model, no status filter).
|
||||
*
|
||||
* - `reagent_id = X` is Order.salesAgentId: the expert recorded on the order
|
||||
* when it was placed — NOT the customer's current referrer, so a customer
|
||||
* moving to another expert never moves past sales (owner ruling 2026-10-08).
|
||||
* - `customer_id = X` is the expert's own orders, which live on their trading
|
||||
* account (SalesExpertProfile.tradingCustomerId, D21).
|
||||
*/
|
||||
export function agentSalesWhere(agentId: string): Prisma.OrderWhereInput {
|
||||
return {
|
||||
OR: [{ salesAgentId: agentId }, { customer: { salesExpertTradingFor: { adminUserId: agentId } } }],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The same figure for a CUSTOMER's own page (legacy ViewOmdeCustomer «میزان
|
||||
* فروش» — the same `grams_sell` column): their own orders plus any legacy
|
||||
* order that named them as its referrer (Order.referrerCustomerId).
|
||||
*/
|
||||
export function customerSalesWhere(customerId: string): Prisma.OrderWhereInput {
|
||||
return { OR: [{ customerId }, { referrerCustomerId: customerId }] };
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/commo
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import type { AuthenticatedAdmin } from '../auth/jwt.strategy';
|
||||
import { agentSalesWhere } from './agent-orders';
|
||||
|
||||
/**
|
||||
* Backs the sales expert's ("کارشناس فروش") own screens in the customer apps.
|
||||
@@ -45,12 +46,12 @@ export class SalesAgentService {
|
||||
* Legacy kept this denormalised in `customers.grams_sell`, recomputed by
|
||||
* `CustomerController::updateGramSell` as
|
||||
* SUM(orders.input) WHERE customer_id = X OR reagent_id = X
|
||||
* with NO order-status filter. Computed live here instead of denormalised.
|
||||
* with NO order-status filter. Computed live here instead of denormalised;
|
||||
* both arms are kept — see agent-orders.ts.
|
||||
*
|
||||
* Two deliberate fidelity notes:
|
||||
* - The `OR customer_id = X` arm is structurally empty now: an agent is an
|
||||
* AdminUser and cannot own an Order, so only the referred-customer arm can
|
||||
* ever contribute. Kept as a comment rather than silently dropped.
|
||||
* - The `OR customer_id = X` arm is the expert's trading account (D21): an
|
||||
* AdminUser cannot own an Order, so their own orders sit on that Customer.
|
||||
* - No status filter, matching legacy. The admin panel disagrees with itself
|
||||
* here — `AdminCustomersService.listReagents` aggregates unfiltered while
|
||||
* `reagentDetail` filters on the SOLD statuses — so this matches
|
||||
@@ -59,7 +60,7 @@ export class SalesAgentService {
|
||||
*/
|
||||
private async gramsSoldFor(agentId: string) {
|
||||
const agg = await this.prisma.order.aggregate({
|
||||
where: { customer: { referredByAgentId: agentId } },
|
||||
where: agentSalesWhere(agentId),
|
||||
_sum: { totalGoldWeightGrams: true },
|
||||
});
|
||||
return agg._sum.totalGoldWeightGrams ?? new Prisma.Decimal(0);
|
||||
|
||||
Reference in New Issue
Block a user