fix: keep birthdays and show Shamsi dates
Imported customers keep their birthday, and assistant text plus remaining calendars use the Jalali date. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,9 @@ import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
type OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
@@ -16,6 +18,7 @@ import {
|
||||
MatchPersonDto,
|
||||
SearchTahesabDto,
|
||||
} from './dto';
|
||||
import { parseBirthDate } from '../../common/excel';
|
||||
import { normalizeIranMobile, SanadBuilders, type TahesabApi } from './tahesab-methods';
|
||||
|
||||
/** اندازهٔ هر صفحهٔ «همه» (بازهٔ کد) — امن برای پروکسی/nginx */
|
||||
@@ -57,8 +60,21 @@ function pickTel(row: Record<string, unknown>): string {
|
||||
return String(row.Tel ?? row.Phone ?? row.Mobile ?? row.tel ?? '');
|
||||
}
|
||||
|
||||
function pickBirthDate(row: Record<string, unknown>): Date | null {
|
||||
const raw =
|
||||
row.BDate ??
|
||||
row.bDate ??
|
||||
row.Tarikh_Tavalod ??
|
||||
row.tarikh_tavalod ??
|
||||
row.BirthDate;
|
||||
if (raw == null) return null;
|
||||
return parseBirthDate(String(raw));
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TahesabPersonLinkService {
|
||||
private readonly logger = new Logger(TahesabPersonLinkService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly connections: TahesabConnectionsService,
|
||||
@@ -522,6 +538,7 @@ export class TahesabPersonLinkService {
|
||||
...carryPreviousSource(existing, 'tahesab'),
|
||||
importSource: 'tahesab',
|
||||
sourceRef,
|
||||
...(!existing.birthDate && pickBirthDate(row) ? { birthDate: pickBirthDate(row) } : {}),
|
||||
...(mobile
|
||||
? {
|
||||
mobile: existing.mobile || mobile,
|
||||
@@ -558,6 +575,7 @@ export class TahesabPersonLinkService {
|
||||
...carryPreviousSource(byMobile, 'tahesab'),
|
||||
importSource: 'tahesab',
|
||||
sourceRef,
|
||||
...(!byMobile.birthDate && pickBirthDate(row) ? { birthDate: pickBirthDate(row) } : {}),
|
||||
},
|
||||
});
|
||||
personId = byMobile.id;
|
||||
@@ -572,6 +590,7 @@ export class TahesabPersonLinkService {
|
||||
notes: `واردشده از تهحساب (کد ${dto.moshtariCode})`,
|
||||
importSource: 'tahesab',
|
||||
sourceRef,
|
||||
birthDate: pickBirthDate(row),
|
||||
},
|
||||
});
|
||||
personId = created.id;
|
||||
@@ -702,6 +721,13 @@ export class TahesabPersonLinkService {
|
||||
try {
|
||||
const linkedPersonId = linkedByCode.get(moshtariCode);
|
||||
if (linkedPersonId) {
|
||||
const birthDate = pickBirthDate(rowsByCode.get(moshtariCode) ?? {});
|
||||
if (birthDate) {
|
||||
await this.prisma.person.updateMany({
|
||||
where: { id: linkedPersonId, birthDate: null },
|
||||
data: { birthDate },
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
moshtariCode,
|
||||
ok: true,
|
||||
@@ -740,4 +766,84 @@ export class TahesabPersonLinkService {
|
||||
|
||||
return { imported, skipped, failed, items };
|
||||
}
|
||||
|
||||
/** مشتریهای قبلاً لینکشده که تاریخ تولد ندارند را از فهرست تهحساب پر میکند. */
|
||||
async fillMissingBirthDates() {
|
||||
const connections = await this.prisma.accountingConnection.findMany({
|
||||
where: { enabled: true },
|
||||
select: { id: true, organizationId: true },
|
||||
});
|
||||
let filled = 0;
|
||||
for (const connection of connections) {
|
||||
try {
|
||||
const links = await this.prisma.personAccountingLink.findMany({
|
||||
where: { connectionId: connection.id, person: { birthDate: null } },
|
||||
select: { personId: true, moshtariCode: true },
|
||||
});
|
||||
if (!links.length) continue;
|
||||
const recent = await this.prisma.accountingSyncJob.findFirst({
|
||||
where: {
|
||||
connectionId: connection.id,
|
||||
kind: 'birthdate',
|
||||
createdAt: { gte: new Date(Date.now() - 12 * 60 * 60 * 1000) },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (recent) continue;
|
||||
await this.prisma.accountingSyncJob.create({
|
||||
data: {
|
||||
organizationId: connection.organizationId,
|
||||
connectionId: connection.id,
|
||||
kind: 'birthdate',
|
||||
status: 'success',
|
||||
},
|
||||
});
|
||||
const { api } = await this.connections.getApi(connection.organizationId, connection.id);
|
||||
const codes = [...new Set(links.map((link) => link.moshtariCode))].sort((a, b) => a - b);
|
||||
const personByCode = new Map(links.map((link) => [link.moshtariCode, link.personId]));
|
||||
let index = 0;
|
||||
while (index < codes.length) {
|
||||
const from = codes[index]!;
|
||||
let end = index;
|
||||
while (
|
||||
end + 1 < codes.length &&
|
||||
codes[end + 1]! - from < 2000 &&
|
||||
end - index < 400
|
||||
) {
|
||||
end += 1;
|
||||
}
|
||||
const to = codes[end]!;
|
||||
const raw = await api.doListMoshtariByCode(from, to);
|
||||
for (const row of asMoshtariList(raw)) {
|
||||
const code = pickCode(row);
|
||||
const personId = code == null ? undefined : personByCode.get(code);
|
||||
const birthDate = pickBirthDate(row);
|
||||
if (!personId || !birthDate) continue;
|
||||
const updated = await this.prisma.person.updateMany({
|
||||
where: { id: personId, birthDate: null },
|
||||
data: { birthDate },
|
||||
});
|
||||
filled += updated.count;
|
||||
}
|
||||
index = end + 1;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`تاریخ تولد تهحساب برای اتصال ${connection.id} خوانده نشد: ${
|
||||
err instanceof Error ? err.message : err
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (filled) this.logger.log(`تاریخ تولد ${filled} مشتری از تهحساب پر شد`);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TahesabBirthDateBackfill implements OnModuleInit {
|
||||
constructor(private readonly links: TahesabPersonLinkService) {}
|
||||
|
||||
onModuleInit() {
|
||||
void this.links.fillMissingBirthDates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { AuthModule } from '../../auth/auth.module';
|
||||
import { TahesabCryptoService } from './tahesab-crypto.service';
|
||||
import { TahesabHttpClient } from './tahesab-http.client';
|
||||
import { TahesabConnectionsService } from './connections.service';
|
||||
import { TahesabPersonLinkService } from './person-link.service';
|
||||
import {
|
||||
TahesabBirthDateBackfill,
|
||||
TahesabPersonLinkService,
|
||||
} from './person-link.service';
|
||||
import { TahesabProductLinkService } from './product-link.service';
|
||||
import { TahesabBalanceService } from './balance.service';
|
||||
import { TahesabDocumentsService } from './documents.service';
|
||||
@@ -17,6 +20,7 @@ import { TahesabController } from './tahesab.controller';
|
||||
TahesabHttpClient,
|
||||
TahesabConnectionsService,
|
||||
TahesabPersonLinkService,
|
||||
TahesabBirthDateBackfill,
|
||||
TahesabProductLinkService,
|
||||
TahesabBalanceService,
|
||||
TahesabDocumentsService,
|
||||
|
||||
Reference in New Issue
Block a user