build: rebuild release bundles for orders pagination and dashboard hero

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-17 13:21:55 +03:30
co-authored by Cursor
parent afb068c643
commit 3223c90983
303 changed files with 468 additions and 322 deletions
+156 -59
View File
@@ -80418,16 +80418,13 @@ var require_accounting_sync_service = __commonJS({
select: { accountingGoldBalance: true, accountingRialBalance: true, accountingBalanceAt: true }
});
const stale = !c?.accountingBalanceAt || Date.now() - c.accountingBalanceAt.getTime() > BALANCE_FRESH_MS;
if (stale) {
if (c?.accountingBalanceAt && stale) {
void this.refreshStoredBalance(a, customerId, link.externalCode).catch((err) => {
this.logger.warn(`Background balance refresh for ${customerId} failed: ${err.message}`);
});
} else if (stale) {
try {
const [b] = await a.provider.getBalances({ ...a.settings, timeoutMs: Math.min(a.settings.timeoutMs, BALANCE_ON_DEMAND_TIMEOUT_MS) }, [link.externalCode]);
if (b) {
c = await this.prisma.customer.update({
where: { id: customerId },
data: { accountingGoldBalance: b.goldGrams, accountingRialBalance: b.rial, accountingBalanceAt: /* @__PURE__ */ new Date() },
select: { accountingGoldBalance: true, accountingRialBalance: true, accountingBalanceAt: true }
});
}
c = await this.refreshStoredBalance(a, customerId, link.externalCode) ?? c;
} catch (err) {
this.logger.warn(`On-demand balance for ${customerId} failed, serving the stored one: ${err.message}`);
}
@@ -80441,6 +80438,25 @@ var require_accounting_sync_service = __commonJS({
provider: a.provider.label
};
}
/** Pull one linked customer's balance from the provider and persist it. */
async refreshStoredBalance(a, customerId, externalCode) {
const [b] = await a.provider.getBalances({ ...a.settings, timeoutMs: Math.min(a.settings.timeoutMs, BALANCE_ON_DEMAND_TIMEOUT_MS) }, [externalCode]);
if (!b)
return null;
return this.prisma.customer.update({
where: { id: customerId },
data: {
accountingGoldBalance: b.goldGrams,
accountingRialBalance: b.rial,
accountingBalanceAt: /* @__PURE__ */ new Date()
},
select: {
accountingGoldBalance: true,
accountingRialBalance: true,
accountingBalanceAt: true
}
});
}
// ════════════════════════════════════════════════════════════════════
// Real-time: change poll + live reads
// ════════════════════════════════════════════════════════════════════
@@ -81778,8 +81794,13 @@ var require_catalog_service = __commonJS({
}
if (query.categoryId)
where.categoryId = query.categoryId;
if (query.brandId)
where.brandId = query.brandId;
if (query.brandId) {
const brandIds = query.brandId.split(",").map((s) => s.trim()).filter(Boolean);
if (brandIds.length === 1)
where.brandId = brandIds[0];
else if (brandIds.length > 1)
where.brandId = { in: brandIds };
}
if (query.collectionId)
where.collections = { some: { collectionId: query.collectionId } };
if (query.minGram !== void 0 || query.maxGram !== void 0) {
@@ -83215,11 +83236,16 @@ var require_customers_service = __commonJS({
var __metadata3 = exports2 && exports2.__metadata || function(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var CustomersService_1;
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.CustomersService = void 0;
var common_12 = require_common();
var client_1 = require("@prisma/client");
var prisma_service_1 = require_prisma_service();
var accounting_sync_service_1 = require_accounting_sync_service();
var ORDERS_PAGE_LIMIT = 20;
var ORDERS_PAGE_LIMIT_MAX = 50;
var ORDER_STATUSES = new Set(Object.values(client_1.OrderStatus));
var PUBLIC_CUSTOMER_SELECT = {
id: true,
fullName: true,
@@ -83239,9 +83265,10 @@ var require_customers_service = __commonJS({
createdAt: true,
updatedAt: true
};
var CustomersService = class CustomersService {
var CustomersService = CustomersService_1 = class CustomersService {
prisma;
accounting;
logger = new common_12.Logger(CustomersService_1.name);
constructor(prisma, accounting) {
this.prisma = prisma;
this.accounting = accounting;
@@ -83253,51 +83280,91 @@ var require_customers_service = __commonJS({
});
if (!customer)
throw new common_12.NotFoundException(`Customer ${customerId} not found`);
const accounting = await this.accounting.customerBalance(customerId);
if (accounting) {
return {
...customer,
goldDebtGrams: accounting.gold,
fiatDebtRial: accounting.rial,
balanceSource: "accounting",
balanceAt: accounting.at
};
try {
const accounting = await this.accounting.customerBalance(customerId);
if (accounting) {
return {
...customer,
goldDebtGrams: accounting.gold,
fiatDebtRial: accounting.rial,
balanceSource: "accounting",
balanceAt: accounting.at
};
}
} catch (err) {
this.logger.warn(`Balance overlay for ${customerId} failed; serving app ledger: ${err.message}`);
}
return { ...customer, balanceSource: "app", balanceAt: null };
}
getOrders(customerId) {
return this.prisma.order.findMany({
where: { customerId },
include: {
items: {
// Each item carried only `productVariantId`, so the apps looked the
// title up by loading the ENTIRE catalogue and joining in the browser
// (composables/useOrderItemProduct.ts). With ~16k products that is
// the wrong trade for a few row labels, and it also failed for an
// order whose product has since left the storefront — the catalogue
// hides it, so the row rendered blank.
//
// A nested read here is one join and always answers, including for
// products that are no longer on sale.
include: {
productVariant: {
select: {
id: true,
title: true,
product: {
select: {
id: true,
title: true,
images: { orderBy: { sortOrder: "asc" }, take: 1, select: { url: true } }
}
/**
* Paginated order history for the signed-in customer.
*
* Shape matches the report endpoints (`result` / `count` / `page` / `limit`)
* so the apps can infinite-scroll the same way. Status and search are applied
* server-side — client-side filtering after a page slice would miss matches
* that sit on later pages.
*/
async getOrders(customerId, query = {}) {
const page = Math.max(1, Math.floor(query.page ?? 1) || 1);
const limit = Math.min(ORDERS_PAGE_LIMIT_MAX, Math.max(1, Math.floor(query.limit ?? ORDERS_PAGE_LIMIT) || ORDERS_PAGE_LIMIT));
const where = { customerId };
if (query.status && query.status !== "ALL" && ORDER_STATUSES.has(query.status)) {
where.status = query.status;
}
const q = query.q?.trim();
if (q) {
where.orderNumber = { contains: q, mode: "insensitive" };
}
const include = {
items: {
// Each item carried only `productVariantId`, so the apps looked the
// title up by loading the ENTIRE catalogue and joining in the browser
// (composables/useOrderItemProduct.ts). With ~16k products that is
// the wrong trade for a few row labels, and it also failed for an
// order whose product has since left the storefront — the catalogue
// hides it, so the row rendered blank.
//
// A nested read here is one join and always answers, including for
// products that are no longer on sale.
include: {
productVariant: {
select: {
id: true,
title: true,
product: {
select: {
id: true,
title: true,
images: { orderBy: { sortOrder: "asc" }, take: 1, select: { url: true } }
}
}
}
}
}
},
orderBy: { createdAt: "desc" }
});
// Chronological status changes for the order-detail timeline in the app.
statusEvents: {
orderBy: { createdAt: "asc" },
select: {
id: true,
fromStatus: true,
toStatus: true,
note: true,
createdAt: true
}
}
};
const [result, count] = await Promise.all([
this.prisma.order.findMany({
where,
include,
orderBy: { createdAt: "desc" },
skip: (page - 1) * limit,
take: limit
}),
this.prisma.order.count({ where })
]);
return { result, count, page, limit };
}
getLedgerEntries(customerId) {
return this.prisma.ledgerEntry.findMany({
@@ -83307,7 +83374,7 @@ var require_customers_service = __commonJS({
}
};
exports2.CustomersService = CustomersService;
exports2.CustomersService = CustomersService = __decorate4([
exports2.CustomersService = CustomersService = CustomersService_1 = __decorate4([
(0, common_12.Injectable)(),
__metadata3("design:paramtypes", [
prisma_service_1.PrismaService,
@@ -88612,7 +88679,7 @@ var require_brand = __commonJS({
exports2.brandSiteUrl = brandSiteUrl;
var env_12 = require_env();
function brandName() {
return (0, env_12.envOr)("BRAND_NAME", "\u0632\u0631\u062A\u0627\u062C");
return (0, env_12.envOr)("BRAND_NAME", "\u062A\u0647\u0631\u0627\u0646 \u062A\u0627\u062C");
}
function brandSiteUrl() {
return (0, env_12.envOr)("BRAND_SITE_URL", "https://bnk.montzar.ir");
@@ -89617,8 +89684,13 @@ var require_customer_auth_controller = __commonJS({
me(me) {
return this.customers.getOrThrow(me.id);
}
myOrders(me) {
return this.customers.getOrders(me.id);
myOrders(me, page, limit, status, q) {
return this.customers.getOrders(me.id, {
page: Number(page) || 1,
limit: limit !== void 0 && limit !== "" ? Number(limit) : void 0,
status,
q
});
}
myLedgerEntries(me) {
return this.customers.getLedgerEntries(me.id);
@@ -89705,8 +89777,12 @@ var require_customer_auth_controller = __commonJS({
(0, customer_guards_1.CustomerAuth)(),
(0, common_12.Get)("me/orders"),
__param3(0, (0, customer_guards_1.CurrentCustomer)()),
__param3(1, (0, common_12.Query)("page")),
__param3(2, (0, common_12.Query)("limit")),
__param3(3, (0, common_12.Query)("status")),
__param3(4, (0, common_12.Query)("q")),
__metadata3("design:type", Function),
__metadata3("design:paramtypes", [Object]),
__metadata3("design:paramtypes", [Object, String, String, String, String]),
__metadata3("design:returntype", void 0)
], CustomerAuthController.prototype, "myOrders", null);
__decorate4([
@@ -92010,13 +92086,17 @@ var require_orders_service = __commonJS({
const flatRialCharge = computedItems.reduce((sum, item) => sum.add(item.flatRialCharge.mul(item.quantity)), new client_1.Prisma.Decimal(0));
const isSuspendedOverCreditLimit = totalGoldWeightGrams.add(customer.goldDebtGrams).gt(customer.goldCreditLimitGrams);
const initialStatus = isSuspendedOverCreditLimit ? client_1.OrderStatus.SUSPENDED : client_1.OrderStatus.PLACED;
const channel = opts.placedBy === "customer" ? client_1.OrderChannel.WHOLESALE : dto.channel ?? client_1.OrderChannel.RETAIL;
if (opts.placedBy === "customer" && dto.channel && dto.channel !== client_1.OrderChannel.WHOLESALE) {
this.logger.warn(`Order body carried channel=${dto.channel} from the app; forcing WHOLESALE.`);
}
const placed = await this.prisma.$transaction(async (tx) => {
const order = await tx.order.create({
data: {
orderNumber: this.generateOrderNumber(),
orderNumber: await this.nextOrderNumber(tx),
customerId,
branchId: dto.branchId,
channel: dto.channel ?? client_1.OrderChannel.RETAIL,
channel,
status: initialStatus,
totalGoldWeightGrams,
wageWeightGrams,
@@ -92181,9 +92261,22 @@ var require_orders_service = __commonJS({
throw new common_12.NotFoundException("\u0633\u0641\u0627\u0631\u0634 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
return order;
}
generateOrderNumber() {
const random = Math.random().toString(36).slice(2, 8).toUpperCase();
return `ORD-${Date.now()}-${random}`;
/**
* Short global sequential number for the customer, SMS and search
* (`100`, `101`, …). Allocated from Postgres `order_number_seq` inside the
* placement transaction so concurrent creates cannot collide. Legacy used
* the auto-increment order id the same way; the old `ORD-{ts}-{rand}` form
* was too long for SMS and the list UI.
*/
async nextOrderNumber(tx) {
const rows = await tx.$queryRaw`
SELECT nextval('order_number_seq') AS n
`;
const n = rows[0]?.n;
if (n === void 0 || n === null) {
throw new Error("order_number_seq returned no value");
}
return String(n);
}
};
exports2.OrdersService = OrdersService;
@@ -92269,7 +92362,11 @@ var require_create_order_dto = __commonJS({
/**
* Which sales channel placed the order. Legacy had no such field because the
* two channels lived in separate tables (orders vs orders_orders); the one
* Order model carries the distinction here. Defaults to RETAIL.
* Order model carries the distinction here.
*
* On the customer (app) path the server forces WHOLESALE and ignores this
* field. Operators may set it; admin MakeOrder always sends WHOLESALE.
* Default when an operator omits it remains RETAIL (retail list builder).
*/
channel;
items;
@@ -0,0 +1,14 @@
-- App (B2B) orders were stored as RETAIL because createOrder defaulted
-- channel to RETAIL and the apps never sent one. Legacy's mobile setOrder
-- wrote to the wholesale `orders` table.
--
-- Flip mis-filed rows: any RETAIL order whose customer is NOT a تک
-- (CustomerKind.RETAIL) belongs on the omde list. Genuine retail-list
-- orders for CustomerKind.RETAIL stay RETAIL.
UPDATE "Order" AS o
SET channel = 'WHOLESALE'
FROM "Customer" AS c
WHERE o."customerId" = c.id
AND o.channel = 'RETAIL'
AND c.kind <> 'RETAIL';
@@ -0,0 +1,31 @@
-- Short global customer-facing order numbers (SMS / app), replacing
-- ORD-{timestamp}-{random}. Pure digits, starting at 100.
-- Existing ORD-* / MOCK-ORD-* rows stay as-is; the sequence only advances
-- past any already-numeric orderNumber so new allocations never collide.
DROP SEQUENCE IF EXISTS order_number_seq;
CREATE SEQUENCE order_number_seq
AS BIGINT
INCREMENT BY 1
MINVALUE 1
START WITH 100
NO CYCLE;
DO $$
DECLARE
max_numeric bigint;
BEGIN
SELECT MAX("orderNumber"::bigint)
INTO max_numeric
FROM "Order"
WHERE "orderNumber" ~ '^[0-9]+$';
IF max_numeric IS NULL THEN
-- next nextval() returns 100
PERFORM setval('order_number_seq', 100, false);
ELSE
-- next nextval() returns max_numeric + 1
PERFORM setval('order_number_seq', max_numeric, true);
END IF;
END $$;
+2 -2
View File
@@ -2086,8 +2086,8 @@ async function main() {
await prisma.brand.updateMany({ where: { id: "brand-henza" }, data: { id: "brand-montzar" } });
const brandMain = await prisma.brand.upsert({
where: { id: "brand-montzar" },
update: { title: "\u0632\u0631\u062A\u0627\u062C \u0637\u0644\u0627" },
create: { id: "brand-montzar", title: "\u0632\u0631\u062A\u0627\u062C \u0637\u0644\u0627" }
update: { title: "\u062A\u0647\u0631\u0627\u0646 \u062A\u0627\u062C \u0637\u0644\u0627" },
create: { id: "brand-montzar", title: "\u062A\u0647\u0631\u0627\u0646 \u062A\u0627\u062C \u0637\u0644\u0627" }
});
const brandArtisan = await prisma.brand.upsert({
where: { id: "brand-artisan" },