refactor(api): cutoff-based prior-archive where helpers

Replace didar/live-deal filters with createdAt cutoff and promotedFromPriorAt; add unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-17 16:33:32 +03:30
co-authored by Cursor
parent a04743c6df
commit 02841a5b8c
2 changed files with 55 additions and 155 deletions
+32 -85
View File
@@ -1,117 +1,64 @@
import type { Prisma } from '@prisma/client';
import type { Actor } from './record-scope';
export const PRIOR_IMPORT_SOURCE = 'didar';
export type PriorLens = 'off' | 'current' | 'archive';
export function actorPriorLens(actor: Pick<Actor, 'priorLens'> | undefined): PriorLens {
return actor?.priorLens ?? 'off';
}
function liveDeal(now: Date): Prisma.DealWhereInput {
return {
importSource: PRIOR_IMPORT_SOURCE,
status: 'open',
expectedCloseAt: { gt: now },
};
function hasCutoff(cutoff?: Date | null): cutoff is Date {
return cutoff instanceof Date && !Number.isNaN(cutoff.getTime());
}
function liveActivity(now: Date): Prisma.ActivityWhereInput {
return {
importSource: PRIOR_IMPORT_SOURCE,
status: 'open',
dueAt: { gt: now },
};
}
/** فعالیت باز با موعد آینده، حتی اگر قبلی باشد، در کار جاری می‌ماند. */
function stillOpenAhead(now: Date): Prisma.ActivityWhereInput {
return { status: 'open', dueAt: { gt: now } };
}
/**
* فعالیت قبلی است اگر منبعش didar باشد،
* یا قبل از روشن شدن ماژول ساخته شده و به شخص/شرکت/معاملهٔ قبلی وصل باشد.
* معاملهٔ غیرقبلی مانع نمی‌شود؛ شخص قبلی (حتی اگر بعداً منبعش عوض شده) کافی است.
* فعالیت بدون شخص و شرکت و معامله هم اگر موعدش قبل از همان لحظه باشد قبلی است.
*/
function historicalActivity(cutoff: Date): Prisma.ActivityWhereInput {
return {
OR: [
{ importSource: PRIOR_IMPORT_SOURCE },
{
createdAt: { lt: cutoff },
OR: [
{
person: {
OR: [
{ importSource: PRIOR_IMPORT_SOURCE },
{ previousImportSource: PRIOR_IMPORT_SOURCE },
],
},
},
{ company: { importSource: PRIOR_IMPORT_SOURCE } },
{ deal: { importSource: PRIOR_IMPORT_SOURCE } },
{
personId: null,
companyId: null,
dealId: null,
dueAt: { lt: cutoff },
},
],
},
],
};
}
export function personPriorWhere(lens: PriorLens | undefined): Prisma.PersonWhereInput {
if (lens === 'archive') return { importSource: PRIOR_IMPORT_SOURCE };
if (lens === 'current') return { importSource: { not: PRIOR_IMPORT_SOURCE } };
/** شخص/شرکت دیگر با این لنز فیلتر نمی‌شوند. */
export function personPriorWhere(_lens?: PriorLens): Prisma.PersonWhereInput {
return {};
}
export function companyPriorWhere(lens: PriorLens | undefined): Prisma.CompanyWhereInput {
if (lens === 'archive') return { importSource: PRIOR_IMPORT_SOURCE };
if (lens === 'current') return { importSource: { not: PRIOR_IMPORT_SOURCE } };
export function companyPriorWhere(_lens?: PriorLens): Prisma.CompanyWhereInput {
return {};
}
export function dealPriorWhere(lens: PriorLens | undefined, now = new Date()): Prisma.DealWhereInput {
export function dealPriorWhere(
lens: PriorLens | undefined,
cutoff?: Date | null,
): Prisma.DealWhereInput {
if (lens !== 'current' && lens !== 'archive') return {};
if (!hasCutoff(cutoff)) return {};
if (lens === 'current') {
return { OR: [{ importSource: { not: PRIOR_IMPORT_SOURCE } }, liveDeal(now)] };
return {
OR: [
{ createdAt: { gte: cutoff } },
{ promotedFromPriorAt: { not: null } },
],
};
}
if (lens === 'archive') {
return { importSource: PRIOR_IMPORT_SOURCE, NOT: liveDeal(now) };
}
return {};
return { createdAt: { lt: cutoff }, promotedFromPriorAt: null };
}
export function activityPriorWhere(
lens: PriorLens | undefined,
now = new Date(),
cutoff?: Date | null,
): Prisma.ActivityWhereInput {
if (lens !== 'current' && lens !== 'archive') return {};
if (!cutoff) {
if (lens === 'current') {
return { OR: [{ importSource: { not: PRIOR_IMPORT_SOURCE } }, liveActivity(now)] };
}
return { importSource: PRIOR_IMPORT_SOURCE, NOT: liveActivity(now) };
if (!hasCutoff(cutoff)) return {};
if (lens === 'current') {
return {
OR: [
{ createdAt: { gte: cutoff } },
{ promotedFromPriorAt: { not: null } },
],
};
}
const prior = historicalActivity(cutoff);
const live = stillOpenAhead(now);
if (lens === 'current') return { OR: [{ NOT: prior }, live] };
return { AND: [prior, { NOT: live }] };
return { createdAt: { lt: cutoff }, promotedFromPriorAt: null };
}
/** فهرست شخص/شرکت: در کار جاری، جستجو و فیلتر صریح منبع، قبلی‌ها را هم پیدا می‌کند. */
/** فهرست شخص/شرکت: همیشه بدون فیلتر لنز (سازگاری با call-siteهای قدیمی). */
export function listPriorLens(
lens: PriorLens | undefined,
q: string | undefined,
explicitSource: boolean,
_lens: PriorLens | undefined,
_q: string | undefined,
_explicitSource: boolean,
): PriorLens {
if (explicitSource) return 'off';
if (lens === 'current' && q?.trim()) return 'off';
return lens ?? 'off';
return 'off';
}
+23 -70
View File
@@ -3,85 +3,38 @@ import assert from 'node:assert/strict';
import {
activityPriorWhere,
dealPriorWhere,
listPriorLens,
personPriorWhere,
} from '../src/common/prior-archive';
const now = new Date('2026-10-15T08:00:00.000Z');
const cutoff = new Date('2026-10-01T00:00:00.000Z');
describe('بایگانی اطلاعات قبل', () => {
it('بدون مجوز فیلتر نمی‌گذارد', () => {
assert.deepEqual(personPriorWhere('off'), {});
assert.deepEqual(dealPriorWhere(undefined, now), {});
describe('بایگانی اطلاعات قبل — تاریخ قطع', () => {
it('بدون لنز یا بدون تاریخ فیلتر نمی‌گذارد', () => {
assert.deepEqual(dealPriorWhere('off', cutoff), {});
assert.deepEqual(dealPriorWhere('current', null), {});
assert.deepEqual(activityPriorWhere('archive', undefined), {});
assert.deepEqual(personPriorWhere('current'), {});
});
it('کار جاری شخص قبلی را کنار می‌گذارد و معاملهٔ بازِ موعددار را نگه می‌دارد', () => {
assert.deepEqual(personPriorWhere('current'), { importSource: { not: 'didar' } });
const where = dealPriorWhere('current', now);
assert.equal(where.OR?.length, 2);
assert.deepEqual(where.OR?.[1], {
importSource: 'didar',
status: 'open',
expectedCloseAt: { gt: now },
});
});
it('بایگانی فقط قبلی‌ها را می‌آورد و استثنای باز را برمی‌دارد', () => {
const where = activityPriorWhere('archive', now);
assert.equal(where.importSource, 'didar');
assert.deepEqual(where.NOT, {
importSource: 'didar',
status: 'open',
dueAt: { gt: now },
});
});
it('فعالیت قدیمی روی شخص قبلی از کار جاری کنار می‌رود', () => {
const cutoff = new Date('2026-10-01T00:00:00.000Z');
const current = JSON.parse(JSON.stringify(activityPriorWhere('current', now, cutoff))) as {
it('کار جاری: بعد از قطع یا منتقل‌شده', () => {
assert.deepEqual(dealPriorWhere('current', cutoff), {
OR: [
{
NOT: {
OR: [
{ importSource: string },
{
createdAt: { lt: string };
OR: Array<{ person?: { OR: Array<Record<string, string>> } }>;
},
];
};
},
{ status: string; dueAt: { gt: string } },
];
};
const linked = current.OR[0].NOT.OR[1];
assert.equal(linked.createdAt.lt, cutoff.toISOString());
assert.deepEqual(linked.OR[0], {
person: {
OR: [{ importSource: 'didar' }, { previousImportSource: 'didar' }],
},
});
assert.deepEqual(linked.OR[3], {
personId: null,
companyId: null,
dealId: null,
dueAt: { lt: cutoff.toISOString() },
});
assert.deepEqual(current.OR[1], { status: 'open', dueAt: { gt: now.toISOString() } });
const archive = JSON.parse(JSON.stringify(activityPriorWhere('archive', now, cutoff))) as {
AND: [unknown, { NOT: { status: string; dueAt: { gt: string } } }];
};
assert.deepEqual(archive.AND[0], current.OR[0].NOT);
assert.deepEqual(archive.AND[1], {
NOT: { status: 'open', dueAt: { gt: now.toISOString() } },
{ createdAt: { gte: cutoff } },
{ promotedFromPriorAt: { not: null } },
],
});
});
it('جستجوی کار جاری شخص قبلی را پیدا می‌کند', () => {
assert.equal(listPriorLens('current', 'علی', false), 'off');
assert.equal(listPriorLens('current', ' ', false), 'current');
assert.equal(listPriorLens('archive', 'علی', false), 'archive');
assert.equal(listPriorLens('current', '', true), 'off');
it('بایگانی: قبل از قطع و بدون برچسب انتقال', () => {
assert.deepEqual(activityPriorWhere('archive', cutoff), {
createdAt: { lt: cutoff },
promotedFromPriorAt: null,
});
});
it('استثنای موعد آینده دیگر نیست', () => {
const w = JSON.stringify(dealPriorWhere('current', cutoff));
assert.equal(w.includes('expectedCloseAt'), false);
assert.equal(w.includes('didar'), false);
});
});