Paginate Tahesab customer list by code windows past the 5000 cap.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
@@ -155,10 +156,24 @@ export class ImportBulkFromTahesabDto {
|
||||
@Min(1)
|
||||
toCode?: number;
|
||||
|
||||
/** اگر true باشد همهٔ مشتریان با اسکن بازهای خوانده میشوند */
|
||||
/** اگر true باشد یک صفحه از مشتریان (بازهٔ کد) خوانده میشود */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
listAll?: boolean;
|
||||
|
||||
/** صفحهٔ بازهٔ کد هنگام listAll (پیشفرض ۱) */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(100)
|
||||
@Max(2000)
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export class SearchTahesabDto {
|
||||
@@ -191,10 +206,28 @@ export class SearchTahesabDto {
|
||||
@Min(1)
|
||||
toCode?: number;
|
||||
|
||||
/** بازه خالی در UI → همهٔ مشتریان */
|
||||
/** بازه خالی در UI → مرور صفحهای همهٔ مشتریان */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
listAll?: boolean;
|
||||
|
||||
/**
|
||||
* صفحهٔ بازهٔ کد برای listAll (۱ = کد ۱…۲۰۰۰، ۲ = ۲۰۰۱…۴۰۰۰، …).
|
||||
* سقف ۵۰۰۰ دیگر اعمال نمیشود.
|
||||
*/
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
/** اندازهٔ پنجرهٔ کد در هر صفحه (پیشفرض ۲۰۰۰، حداکثر ۲۰۰۰) */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(100)
|
||||
@Max(2000)
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export class SearchTahesabProductsDto {
|
||||
|
||||
@@ -17,10 +17,9 @@ import {
|
||||
} from './dto';
|
||||
import { normalizeIranMobile, SanadBuilders, type TahesabApi } from './tahesab-methods';
|
||||
|
||||
const LIST_ALL_CHUNK = 2000;
|
||||
/** سقف «همه» در یک درخواست ByCode — تکهتکه باعث ۵۰۲ پروکسی میشود */
|
||||
const LIST_ALL_MAX_CODE = 5_000;
|
||||
const LIST_ALL_TIMEOUT_MS = 55_000;
|
||||
/** اندازهٔ هر صفحهٔ «همه» (بازهٔ کد) — امن برای پروکسی/nginx */
|
||||
const LIST_PAGE_SIZE = 2000;
|
||||
const LIST_TIMEOUT_MS = 55_000;
|
||||
|
||||
function asMoshtariList(raw: unknown): Array<Record<string, unknown>> {
|
||||
if (!raw) return [];
|
||||
@@ -318,25 +317,32 @@ export class TahesabPersonLinkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* لیست همهٔ مشتریان در یک درخواست (بازه ۱…LIST_ALL_MAX_CODE).
|
||||
* تکهتکه کردن پشتسرهم معمولاً از مهلت nginx بیشتر میشود و ۵۰۲ میدهد.
|
||||
* یک صفحه از مشتریان بر اساس بازهٔ کد:
|
||||
* صفحه ۱ → ۱…pageSize، صفحه ۲ → pageSize+1…۲×pageSize، …
|
||||
* سقف ۵۰۰۰ حذف شده؛ با پیجینیت میتوان همه را مرور کرد.
|
||||
*/
|
||||
private async listAllMoshtari(api: TahesabApi) {
|
||||
try {
|
||||
return asMoshtariList(
|
||||
await api.doListMoshtariByCode(1, LIST_ALL_MAX_CODE, {
|
||||
timeoutMs: LIST_ALL_TIMEOUT_MS,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
// اگر بازهٔ بزرگ سمت تهحساب رد شد، همان سقف دستی ۲۰۰۰ را یکبار امتحان کن
|
||||
if (LIST_ALL_MAX_CODE <= LIST_ALL_CHUNK) throw err;
|
||||
return asMoshtariList(
|
||||
await api.doListMoshtariByCode(1, LIST_ALL_CHUNK, {
|
||||
timeoutMs: LIST_ALL_TIMEOUT_MS,
|
||||
}),
|
||||
);
|
||||
}
|
||||
private async listMoshtariPage(
|
||||
api: TahesabApi,
|
||||
page: number,
|
||||
pageSize?: number,
|
||||
) {
|
||||
const size = Math.min(
|
||||
LIST_PAGE_SIZE,
|
||||
Math.max(100, Math.floor(pageSize ?? LIST_PAGE_SIZE) || LIST_PAGE_SIZE),
|
||||
);
|
||||
const p = Math.max(1, Math.floor(page) || 1);
|
||||
const fromCode = (p - 1) * size + 1;
|
||||
const toCode = fromCode + size - 1;
|
||||
const raw = await api.doListMoshtariByCode(fromCode, toCode, {
|
||||
timeoutMs: LIST_TIMEOUT_MS,
|
||||
});
|
||||
return {
|
||||
rows: asMoshtariList(raw),
|
||||
fromCode,
|
||||
toCode,
|
||||
page: p,
|
||||
pageSize: size,
|
||||
};
|
||||
}
|
||||
|
||||
async search(organizationId: string, dto: SearchTahesabDto) {
|
||||
@@ -345,8 +351,28 @@ export class TahesabPersonLinkService {
|
||||
dto.connectionId,
|
||||
);
|
||||
let raw: unknown = [];
|
||||
let listMeta:
|
||||
| {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
fromCode: number;
|
||||
toCode: number;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (dto.listAll) {
|
||||
raw = await this.listAllMoshtari(api);
|
||||
const listed = await this.listMoshtariPage(
|
||||
api,
|
||||
dto.page ?? 1,
|
||||
dto.pageSize,
|
||||
);
|
||||
raw = listed.rows;
|
||||
listMeta = {
|
||||
page: listed.page,
|
||||
pageSize: listed.pageSize,
|
||||
fromCode: listed.fromCode,
|
||||
toCode: listed.toCode,
|
||||
};
|
||||
} else if (dto.fromCode != null && dto.toCode != null) {
|
||||
if (dto.toCode < dto.fromCode) {
|
||||
throw new BadRequestException('کد پایان باید بزرگتر یا مساوی کد شروع باشد.');
|
||||
@@ -354,7 +380,9 @@ export class TahesabPersonLinkService {
|
||||
if (dto.toCode - dto.fromCode > 2000) {
|
||||
throw new BadRequestException('بازهٔ کد حداکثر ۲۰۰۰ عدد است (مثلاً ۱ تا ۲۰۰۰). برای همه، بازه را خالی بگذارید.');
|
||||
}
|
||||
raw = await api.doListMoshtariByCode(dto.fromCode, dto.toCode);
|
||||
raw = await api.doListMoshtariByCode(dto.fromCode, dto.toCode, {
|
||||
timeoutMs: LIST_TIMEOUT_MS,
|
||||
});
|
||||
} else if (dto.moshtariCode) {
|
||||
raw = await api.doListMoshtariByCode(dto.moshtariCode, dto.moshtariCode);
|
||||
} else if (dto.phone) {
|
||||
@@ -414,11 +442,23 @@ export class TahesabPersonLinkService {
|
||||
kind: 'search',
|
||||
status: 'success',
|
||||
request: dto as unknown as Prisma.InputJsonValue,
|
||||
result: { count: results.length } as Prisma.InputJsonValue,
|
||||
result: {
|
||||
count: results.length,
|
||||
...(listMeta ?? {}),
|
||||
} as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
|
||||
return { results };
|
||||
return {
|
||||
results,
|
||||
...(listMeta
|
||||
? {
|
||||
...listMeta,
|
||||
hasMore: results.length > 0,
|
||||
note: `صفحه ${listMeta.page}: کد ${listMeta.fromCode} تا ${listMeta.toCode}. برای ادامه صفحه بعد را بزنید.`,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
async importFromTahesab(
|
||||
@@ -516,10 +556,15 @@ export class TahesabPersonLinkService {
|
||||
organizationId,
|
||||
dto.connectionId,
|
||||
);
|
||||
const listed = (await this.listAllMoshtari(api))
|
||||
const listed = await this.listMoshtariPage(
|
||||
api,
|
||||
dto.page ?? 1,
|
||||
dto.pageSize,
|
||||
);
|
||||
const listedCodes = listed.rows
|
||||
.map((row) => pickCode(row))
|
||||
.filter((c): c is number => c != null);
|
||||
codes = [...new Set([...codes, ...listed])];
|
||||
codes = [...new Set([...codes, ...listedCodes])];
|
||||
} else if (dto.fromCode != null && dto.toCode != null) {
|
||||
if (dto.toCode < dto.fromCode) {
|
||||
throw new BadRequestException('کد پایان باید بزرگتر یا مساوی کد شروع باشد.');
|
||||
@@ -545,10 +590,10 @@ export class TahesabPersonLinkService {
|
||||
'کد مشتری یا بازهٔ کد برای وارد کردن مشخص نشده است.',
|
||||
);
|
||||
}
|
||||
const maxImport = dto.listAll ? 2000 : 500;
|
||||
const maxImport = dto.listAll || (dto.fromCode != null && dto.toCode != null) ? 2000 : 500;
|
||||
if (codes.length > maxImport) {
|
||||
throw new BadRequestException(
|
||||
`حداکثر ${maxImport} مشتری در هر بار وارد کنید (الان ${codes.length}). بازه را کوچکتر کنید.`,
|
||||
`حداکثر ${maxImport} مشتری در هر بار وارد کنید (الان ${codes.length}). صفحه یا بازه را کوچکتر کنید.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user