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
@@ -6,6 +6,7 @@ import { TableService } from '../admin-table/table.service';
|
||||
import type { TableQueryDto } from '../admin-table/table-query.dto';
|
||||
import { faToEn } from '../customer-auth/digits';
|
||||
import { OTP_ONLY_PASSWORD_HASH } from '../auth/password-hash';
|
||||
import { agentSalesWhere, customerSalesWhere } from '../sales-agent/agent-orders';
|
||||
import { SmsService } from '../sms/sms.service';
|
||||
import { AccountingHooksService } from '../accounting-sync/accounting-hooks.service';
|
||||
|
||||
@@ -318,7 +319,8 @@ export class AdminCustomersService {
|
||||
// legacy kept these as two INDEPENDENT switches — see toggleSts/toggleActive
|
||||
active: c.isActive ? 1 : -1,
|
||||
sts: c.canPlaceOrders ? 1 : -1,
|
||||
sts_str: c.canPlaceOrders ? 'مجاز' : 'غیر مجاز',
|
||||
// Legacy Customer::$STS labels (Customer.php:94-97).
|
||||
sts_str: c.canPlaceOrders ? 'فعال' : 'عدم سفارش',
|
||||
type: kindToLegacyType(c.kind),
|
||||
type_str: CUSTOMER_TYPE_STR[c.kind],
|
||||
is_archive: c.isArchived ? 1 : 0,
|
||||
@@ -450,7 +452,7 @@ export class AdminCustomersService {
|
||||
const mapped = await Promise.all(
|
||||
rows.map(async (r) => {
|
||||
const agg = await this.prisma.order.aggregate({
|
||||
where: { customer: { referredByAgentId: r.id } },
|
||||
where: agentSalesWhere(r.id),
|
||||
_sum: { totalGoldWeightGrams: true },
|
||||
});
|
||||
return {
|
||||
@@ -472,7 +474,7 @@ export class AdminCustomersService {
|
||||
child: r._count.referredCustomers,
|
||||
active: r.isActive ? 1 : -1,
|
||||
sts: r.isActive ? 1 : -1,
|
||||
sts_str: r.isActive ? 'مجاز' : 'غیر مجاز',
|
||||
sts_str: r.isActive ? 'فعال' : 'عدم سفارش',
|
||||
caj: r.createdAt,
|
||||
uaj: r.createdAt,
|
||||
branch: r.branch ? { id: r.branch.id, name: r.branch.name } : null,
|
||||
@@ -514,14 +516,19 @@ export class AdminCustomersService {
|
||||
});
|
||||
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: { customer: { referredByAgentId: id } },
|
||||
// The orders credited to this expert (Order.salesAgentId) plus their own —
|
||||
// the same set «میزان فروش» counts, so the list and the figure agree.
|
||||
where: agentSalesWhere(id),
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100,
|
||||
include: { customer: true },
|
||||
});
|
||||
|
||||
const gramsSoldAgg = await this.prisma.order.aggregate({
|
||||
where: { customer: { referredByAgentId: id }, ...AdminCustomersService.SOLD },
|
||||
// Same figure as the experts list and legacy grams_sell: no status filter
|
||||
// (CustomerController::updateGramSell). This screen used to count only
|
||||
// delivered orders, so the same expert showed two different numbers.
|
||||
where: agentSalesWhere(id),
|
||||
_sum: { totalGoldWeightGrams: true },
|
||||
});
|
||||
|
||||
@@ -555,11 +562,6 @@ export class AdminCustomersService {
|
||||
};
|
||||
}
|
||||
|
||||
/** Orders that reached debt-recognition, reused by reagentDetail(). */
|
||||
private static readonly SOLD: Prisma.OrderWhereInput = {
|
||||
status: { in: ['DELIVERED_TO_AGENT', 'DELIVERED_TO_CUSTOMER'] },
|
||||
};
|
||||
|
||||
/**
|
||||
* Legacy `POST single-customer` — the retail ("تک") customer list.
|
||||
*
|
||||
@@ -1032,12 +1034,14 @@ export class AdminCustomersService {
|
||||
const c = await this.prisma.customer.findUnique({ where: { id } });
|
||||
if (!c) throw new NotFoundException('مشتری یافت نشد');
|
||||
|
||||
const [orderCount, openOrders, ledgerCount] = await Promise.all([
|
||||
const [orderCount, openOrders, ledgerCount, sold] = await Promise.all([
|
||||
this.prisma.order.count({ where: { customerId: id } }),
|
||||
this.prisma.order.count({
|
||||
where: { customerId: id, deliveredAt: null, status: { not: 'CANCELLED' } },
|
||||
}),
|
||||
this.prisma.ledgerEntry.count({ where: { customerId: id } }),
|
||||
// Legacy ViewOmdeCustomer «میزان فروش» (customers.grams_sell).
|
||||
this.prisma.order.aggregate({ where: customerSalesWhere(id), _sum: { totalGoldWeightGrams: true } }),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -1047,6 +1051,7 @@ export class AdminCustomersService {
|
||||
gram_level: c.goldCreditLimitGrams,
|
||||
remain: c.goldDebtGrams,
|
||||
rial_remain: c.fiatDebtRial,
|
||||
grams_sell: sold._sum.totalGoldWeightGrams ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user