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:
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user