feat new update from MR S.Esmaieli

This commit is contained in:
Montech
2026-10-11 14:43:17 +03:30
parent 574fa1b4aa
commit 2e21758c2c
282 changed files with 502 additions and 336 deletions
+223 -46
View File
@@ -77270,9 +77270,40 @@ var require_zarrin_provider = __commonJS({
var STATEMENT_PAGE = 250;
var CHANGE_WINDOW = 50;
var MAX_STATEMENT_PAGES = 20;
var ALL_CODES = { az: 1, ta: 999999 };
var GROUP_SCAN_MAX = 300;
var GROUP_SCAN_CONCURRENCY = 10;
function endpoint(s) {
return s.baseUrl.trim();
}
var ZarrinHttpError = class extends accounting_provider_1.AccountingProviderError {
status;
constructor(message, body, status) {
super(message, false, body, status === 404);
this.status = status;
}
};
function isMissingKind(err) {
return err instanceof ZarrinHttpError && err.status === 400;
}
function errorBodyText(body, token) {
let text = body;
try {
const j = (0, accounting_http_1.parseLooseJson)(body);
const o = typeof j === "string" && /^\s*[{[]/.test(j) ? (0, accounting_http_1.parseLooseJson)(j) : j;
if (o && typeof o === "object")
text = (0, accounting_provider_1.str)(o.msg) ?? (0, accounting_provider_1.str)(o.detail) ?? (0, accounting_provider_1.str)(o.message) ?? (0, accounting_provider_1.str)(o.Message) ?? (0, accounting_provider_1.str)(o.title) ?? JSON.stringify(o);
else if (typeof o === "string")
text = o;
} catch {
text = body.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]+>/g, " ");
}
if (token)
for (const t of /* @__PURE__ */ new Set([token, encodeURIComponent(token)]))
text = text.split(t).join("***");
text = text.replace(/\s+/g, " ").trim();
return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
}
async function call(s, kind, params = {}) {
let url;
try {
@@ -77296,7 +77327,8 @@ var require_zarrin_provider = __commonJS({
throw err;
}
if (res.status < 200 || res.status >= 300) {
throw new accounting_provider_1.AccountingProviderError(`\u0632\u0631\u06CC\u0646 (${kind}): HTTP ${res.status}`, false, res.body.slice(0, 500), res.status === 404);
const reason = errorBodyText(res.body, s.token);
throw new ZarrinHttpError(`\u0632\u0631\u06CC\u0646 (${kind}): HTTP ${res.status}${reason ? ` \u2014 ${reason}` : ""}`, res.body.slice(0, 500), res.status);
}
let json;
try {
@@ -77347,6 +77379,18 @@ var require_zarrin_provider = __commonJS({
return s;
return `14${m[1]}/${m[2].padStart(2, "0")}/${m[3].padStart(2, "0")}`;
}
async function allAccounts(s) {
return listOf(await call(s, "listhesabha", ALL_CODES)).filter((r) => r.id !== void 0).map((r) => ({
code: String(r.id),
name: (0, accounting_provider_1.str)(r.nam) ?? String(r.id),
mobile: (0, accounting_provider_1.normalizeMobile)(r.mobile),
phone: (0, accounting_provider_1.str)(r.tell),
nationalCode: (0, accounting_provider_1.str)(r.codeMeli),
address: (0, accounting_provider_1.str)(r.address),
groupId: null,
groupName: (0, accounting_provider_1.str)(r.gorooh)
}));
}
function signed(s, v) {
return (s.balanceOwesWhen ?? "negative") === "negative" ? v.negated() : v;
}
@@ -77365,33 +77409,8 @@ var require_zarrin_provider = __commonJS({
name: (0, accounting_provider_1.str)(g.name) ?? String(g.id)
}));
},
async listCustomers(s, cursor) {
const from = Number(cursor ?? 0) || 0;
const page = listOf(await call(s, "Get100Account", { id: from }));
if (page.length === 0)
return { items: [], next: null };
const codes = page.map((r) => Number(r.code)).filter(Number.isFinite);
const lo = Math.min(...codes);
const hi = Math.max(...codes);
const details = /* @__PURE__ */ new Map();
for (const r of listOf(await call(s, "listhesabha", { az: lo, ta: hi })))
details.set(String(r.id), r);
const items = page.map((r) => {
const code = String(r.code);
const d = details.get(code) ?? {};
return {
code,
name: (0, accounting_provider_1.str)(d.nam) ?? joinName(r.name, r.namKhanevadegi),
mobile: (0, accounting_provider_1.normalizeMobile)(d.mobile ?? r.mobile),
phone: (0, accounting_provider_1.str)(d.tell),
nationalCode: (0, accounting_provider_1.str)(d.codeMeli),
address: (0, accounting_provider_1.str)(d.address),
groupId: null,
groupName: (0, accounting_provider_1.str)(d.gorooh)
};
});
const next = page.length < 100 ? null : String(hi + 1);
return { items, next };
async listCustomers(s) {
return { items: await allAccounts(s), next: null };
},
async findCustomersByMobile(s, mobile) {
const target = (0, accounting_provider_1.normalizeMobile)(mobile);
@@ -77399,19 +77418,21 @@ var require_zarrin_provider = __commonJS({
return [];
let rows;
try {
rows = listOf(await call(s, "getinfoByPhone", { number: target }));
rows = listOf(await call(s, "searchAccount", { search: target }));
} catch (err) {
if (err instanceof accounting_provider_1.AccountingProviderError && err.notFound)
return [];
throw err;
if (!isMissingKind(err))
throw err;
return (await allAccounts(s)).filter((c) => c.mobile === target);
}
return rows.filter((r) => r.code !== void 0 && (0, accounting_provider_1.normalizeMobile)(r.mobile) === target).map((r) => ({
code: String(r.code),
name: joinName(r.nam, r.namKhanevadegi),
name: joinName(r.name, r.namKhanevadegi),
mobile: target,
phone: (0, accounting_provider_1.str)(r.tel),
phone: null,
nationalCode: null,
address: (0, accounting_provider_1.str)(r.address),
address: null,
groupId: null,
groupName: null
}));
@@ -77530,7 +77551,29 @@ var require_zarrin_provider = __commonJS({
});
},
async listKarSakhteGroups(s) {
return listOf(await call(s, "goroohSakhte")).map((g) => ({ id: String(g.id), name: (0, accounting_provider_1.str)(g.name) ?? String(g.id) }));
try {
return listOf(await call(s, "goroohSakhte")).map((g) => ({ id: String(g.id), name: (0, accounting_provider_1.str)(g.name) ?? String(g.id) }));
} catch (err) {
if (!isMissingKind(err))
throw err;
}
const found = [];
let next = 1;
const worker = async () => {
while (next <= GROUP_SCAN_MAX) {
const id = next++;
try {
for (const g of listOf(await call(s, "infogorooh", { id }))) {
const name = (0, accounting_provider_1.str)(g.nam);
if (name)
found.push({ id: String(g.code ?? id), name });
}
} catch {
}
}
};
await Promise.all(Array.from({ length: GROUP_SCAN_CONCURRENCY }, worker));
return found.sort((a, b) => Number(a.id) - Number(b.id));
},
async listKarSakhteTypes(s) {
const types = /* @__PURE__ */ new Map();
@@ -77769,6 +77812,87 @@ var require_secret_box = __commonJS({
}
});
// dist/modules/catalog/catalog-cache.service.js
var require_catalog_cache_service = __commonJS({
"dist/modules/catalog/catalog-cache.service.js"(exports2) {
"use strict";
var __decorate4 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata3 = exports2 && exports2.__metadata || function(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.CatalogCacheService = exports2.CATALOG_CACHE_MS = void 0;
var common_12 = require_common();
var prisma_service_1 = require_prisma_service();
exports2.CATALOG_CACHE_MS = 3e4;
var CATALOG_MODELS = /* @__PURE__ */ new Set(["Product", "ProductVariant", "ProductImage", "Category", "Brand", "Collection", "CollectionProduct"]);
var WRITE_ACTIONS = /* @__PURE__ */ new Set([
"create",
"createMany",
"createManyAndReturn",
"update",
"updateMany",
"upsert",
"delete",
"deleteMany"
]);
var TX_SETTLE_MS = 2e3;
var CatalogCacheService = class CatalogCacheService {
prisma;
value = null;
inflight = null;
/** Bumped by invalidate(), so a build started before it is never stored. */
generation = 0;
constructor(prisma) {
this.prisma = prisma;
}
onModuleInit() {
this.prisma.$use(async (params, next) => {
const result = await next(params);
if (params.model && CATALOG_MODELS.has(params.model) && WRITE_ACTIONS.has(params.action)) {
this.invalidate();
if (params.runInTransaction)
setTimeout(() => this.invalidate(), TX_SETTLE_MS).unref?.();
}
return result;
});
}
async get(build) {
if (this.value && Date.now() - this.value.at < exports2.CATALOG_CACHE_MS)
return this.value.data;
if (this.inflight)
return this.inflight;
const gen = this.generation;
const p = build().then((data) => {
if (gen === this.generation)
this.value = { at: Date.now(), data };
return data;
}).finally(() => {
if (this.inflight === p)
this.inflight = null;
});
this.inflight = p;
return p;
}
invalidate() {
this.generation++;
this.value = null;
this.inflight = null;
}
};
exports2.CatalogCacheService = CatalogCacheService;
exports2.CatalogCacheService = CatalogCacheService = __decorate4([
(0, common_12.Injectable)(),
__metadata3("design:paramtypes", [prisma_service_1.PrismaService])
], CatalogCacheService);
}
});
// dist/modules/accounting-sync/accounting-config.service.js
var require_accounting_config_service = __commonJS({
"dist/modules/accounting-sync/accounting-config.service.js"(exports2) {
@@ -77790,6 +77914,7 @@ var require_accounting_config_service = __commonJS({
var providers_1 = require_providers();
var secret_box_1 = require_secret_box();
var accounting_http_1 = require_accounting_http();
var catalog_cache_service_1 = require_catalog_cache_service();
function describeCertificate(pem) {
const [c] = (0, accounting_http_1.parsePemCertificates)(pem);
if (!c)
@@ -77805,10 +77930,12 @@ var require_accounting_config_service = __commonJS({
var CACHE_MS = 5e3;
var AccountingConfigService = class AccountingConfigService {
prisma;
catalogCache;
logger = new common_12.Logger("Accounting");
cache = null;
constructor(prisma) {
constructor(prisma, catalogCache) {
this.prisma = prisma;
this.catalogCache = catalogCache;
}
onModuleInit() {
if ((0, secret_box_1.secretKeySource)() !== "ACCOUNTING_SECRET_KEY") {
@@ -77944,6 +78071,7 @@ var require_accounting_config_service = __commonJS({
create: { key: accounting_config_1.ACCOUNTING_CONFIG_KEY, value: next }
});
this.cache = null;
this.catalogCache.invalidate();
return { providerChanged: current.provider !== next.provider };
}
/** Decrypted settings for one provider; null when it is not usable. */
@@ -78041,7 +78169,10 @@ var require_accounting_config_service = __commonJS({
exports2.AccountingConfigService = AccountingConfigService;
exports2.AccountingConfigService = AccountingConfigService = __decorate4([
(0, common_12.Injectable)(),
__metadata3("design:paramtypes", [prisma_service_1.PrismaService])
__metadata3("design:paramtypes", [
prisma_service_1.PrismaService,
catalog_cache_service_1.CatalogCacheService
])
], AccountingConfigService);
}
});
@@ -81276,13 +81407,21 @@ var require_accounting_hooks_service = __commonJS({
* not linked under the active provider are cleared by every product pull.
*/
async stockBlocked(product) {
if (!product.accountingStockAt)
return false;
return (await this.stockBlocker())(product);
}
/**
* The same rule as stockBlocked, with the configuration read once — for
* checking a whole list without an await per product.
*/
async stockBlocker() {
const c = await this.config.get();
if (c.provider === "none" || !c.features.products)
return false;
const stock = product.accountingStockWeight ?? product.accountingStockCount;
return stock !== null && new client_1.Prisma.Decimal(stock).lte(0);
const active = c.provider !== "none" && c.features.products;
return (product) => {
if (!active || !product.accountingStockAt)
return false;
const stock = product.accountingStockWeight ?? product.accountingStockCount;
return stock !== null && new client_1.Prisma.Decimal(stock).lte(0);
};
}
};
exports2.AccountingHooksService = AccountingHooksService;
@@ -81316,14 +81455,21 @@ var require_catalog_service = __commonJS({
var common_12 = require_common();
var prisma_service_1 = require_prisma_service();
var accounting_hooks_service_1 = require_accounting_hooks_service();
var catalog_cache_service_1 = require_catalog_cache_service();
var CatalogService = class CatalogService {
prisma;
accounting;
constructor(prisma, accounting) {
catalogCache;
constructor(prisma, accounting, catalogCache) {
this.prisma = prisma;
this.accounting = accounting;
this.catalogCache = catalogCache;
}
async listProducts() {
/** Served from CatalogCacheService; see there for what invalidates it. */
listProducts() {
return this.catalogCache.get(() => this.buildProductList());
}
async buildProductList() {
const products = await this.prisma.product.findMany({
// D27 — a product with no image is never shown in the app. Products now
// also arrive from the accounting software, which carries no pictures;
@@ -81347,10 +81493,11 @@ var require_catalog_service = __commonJS({
collections: { include: { collection: true } }
}
});
const stockBlocked = await this.accounting.stockBlocker();
const out = [];
for (const p of products) {
const { accountingStockWeight, accountingStockCount, accountingStockAt, ...product } = p;
const blocked = await this.accounting.stockBlocked({ accountingStockWeight, accountingStockCount, accountingStockAt });
const blocked = stockBlocked({ accountingStockWeight, accountingStockCount, accountingStockAt });
out.push(blocked ? { ...product, variants: product.variants.map((v) => v.isPreOrder ? v : { ...v, qty: 0 }) } : product);
}
return out;
@@ -81388,7 +81535,8 @@ var require_catalog_service = __commonJS({
(0, common_12.Injectable)(),
__metadata3("design:paramtypes", [
prisma_service_1.PrismaService,
accounting_hooks_service_1.AccountingHooksService
accounting_hooks_service_1.AccountingHooksService,
catalog_cache_service_1.CatalogCacheService
])
], CatalogService);
}
@@ -82589,6 +82737,33 @@ var require_catalog_module = __commonJS({
}
});
// dist/modules/catalog/catalog-cache.module.js
var require_catalog_cache_module = __commonJS({
"dist/modules/catalog/catalog-cache.module.js"(exports2) {
"use strict";
var __decorate4 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.CatalogCacheModule = void 0;
var common_12 = require_common();
var catalog_cache_service_1 = require_catalog_cache_service();
var CatalogCacheModule = class CatalogCacheModule {
};
exports2.CatalogCacheModule = CatalogCacheModule;
exports2.CatalogCacheModule = CatalogCacheModule = __decorate4([
(0, common_12.Global)(),
(0, common_12.Module)({
providers: [catalog_cache_service_1.CatalogCacheService],
exports: [catalog_cache_service_1.CatalogCacheService]
})
], CatalogCacheModule);
}
});
// dist/modules/customers/customers.service.js
var require_customers_service = __commonJS({
"dist/modules/customers/customers.service.js"(exports2) {
@@ -162315,6 +162490,7 @@ var require_app_module = __commonJS({
var common_12 = require_common();
var prisma_module_1 = require_prisma_module();
var catalog_module_1 = require_catalog_module();
var catalog_cache_module_1 = require_catalog_cache_module();
var customers_module_1 = require_customers_module();
var customer_auth_module_1 = require_customer_auth_module();
var sales_expert_app_module_1 = require_sales_expert_app_module();
@@ -162358,6 +162534,7 @@ var require_app_module = __commonJS({
iam_module_1.IamModule,
admin_session_module_1.AdminSessionModule,
prisma_module_1.PrismaModule,
catalog_cache_module_1.CatalogCacheModule,
auth_module_1.AuthModule,
admin_table_module_1.AdminTableModule,
admin_orders_module_1.AdminOrdersModule,
-9
View File
@@ -1,9 +0,0 @@
class PrismaClient {
constructor() {
throw new Error(
'@prisma/client/deno/edge did not initialize yet. Please run "prisma generate" and try to import it again.',
)
}
}
export { PrismaClient }
+3 -4
View File
@@ -800,7 +800,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "E:\\gold-bank\\node_modules\\.pnpm\\@prisma+client@5.22.0_prisma@5.22.0\\node_modules\\@prisma\\client",
"value": "D:\\APP\\abshode_ah\\montzar_hamkar\\gold-bank\\node_modules\\.pnpm\\@prisma+client@5.22.0_prisma@5.22.0\\node_modules\\@prisma\\client",
"fromEnvVar": null
},
"config": {
@@ -818,11 +818,10 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "E:\\gold-bank\\apps\\api\\prisma\\schema.prisma"
"sourceFilePath": "D:\\APP\\abshode_ah\\montzar_hamkar\\gold-bank\\apps\\api\\prisma\\schema.prisma"
},
"relativeEnvPaths": {
"rootEnvPath": null,
"schemaEnvPath": "../../../../../../apps/api/.env"
"rootEnvPath": null
},
"relativePath": "../../../../../../apps/api/prisma",
"clientVersion": "5.22.0",
+3 -4
View File
@@ -801,7 +801,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "E:\\gold-bank\\node_modules\\.pnpm\\@prisma+client@5.22.0_prisma@5.22.0\\node_modules\\@prisma\\client",
"value": "D:\\APP\\abshode_ah\\montzar_hamkar\\gold-bank\\node_modules\\.pnpm\\@prisma+client@5.22.0_prisma@5.22.0\\node_modules\\@prisma\\client",
"fromEnvVar": null
},
"config": {
@@ -819,11 +819,10 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "E:\\gold-bank\\apps\\api\\prisma\\schema.prisma"
"sourceFilePath": "D:\\APP\\abshode_ah\\montzar_hamkar\\gold-bank\\apps\\api\\prisma\\schema.prisma"
},
"relativeEnvPaths": {
"rootEnvPath": null,
"schemaEnvPath": "../../../../../../apps/api/.env"
"rootEnvPath": null
},
"relativePath": "../../../../../../apps/api/prisma",
"clientVersion": "5.22.0",
+2 -2
View File
@@ -473,14 +473,14 @@ model Customer {
///
/// Legacy recorded NO reason: none of its 49 `customers` columns is a
/// reason/note field, and on rejection it only sent a fixed SMS. So the 14
/// rows migrated from henza carry the sentinel
/// rows migrated from the legacy system carry the sentinel
/// «رد شده در سیستم قدیم — فاقد شرح علت ثبت‌شده» instead of NULL, so support
/// knows why it is blank (owner ruling 2026-10-07). The migration tool
/// writes it only WHERE this IS NULL, so a re-run cannot overwrite a real
/// reason entered in the new system.
verificationRejectionReason String?
/// NULL for rows migrated from henza — legacy recorded no rejection time.
/// NULL for rows migrated from the legacy system — it recorded no rejection time.
verificationRejectedAt DateTime?
/// Legacy customers.birth_date, populated on 917 of 1,595 rows. Kept for the