mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 15:30:56 +00:00
contrat nad booking modification
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import type {
|
||||
ContractDocumentArticle,
|
||||
ContractDocumentSnapshot,
|
||||
} from './entities/contract.entity';
|
||||
|
||||
/**
|
||||
* One recorded change between two document snapshots. Granularity is per
|
||||
* article: a body edit is reported as "the body changed", not as a text diff.
|
||||
*/
|
||||
export type ContractDocumentChange =
|
||||
| { kind: 'ARTICLE_ADDED'; articleId: string; title: string }
|
||||
| { kind: 'ARTICLE_REMOVED'; articleId: string; title: string }
|
||||
| {
|
||||
kind: 'ARTICLE_RENAMED';
|
||||
articleId: string;
|
||||
title: string;
|
||||
fromTitle: string;
|
||||
}
|
||||
| { kind: 'ARTICLE_BODY_CHANGED'; articleId: string; title: string }
|
||||
| {
|
||||
kind: 'ARTICLE_REORDERED';
|
||||
articleId: string;
|
||||
title: string;
|
||||
fromOrder: number;
|
||||
toOrder: number;
|
||||
}
|
||||
| { kind: 'DOCUMENT_TITLE_CHANGED'; title: string; fromTitle: string | null }
|
||||
| { kind: 'WHEREAS_CHANGED'; added: number; removed: number };
|
||||
|
||||
type SnapshotLike = Pick<
|
||||
ContractDocumentSnapshot,
|
||||
'documentTitle' | 'whereasClauses' | 'articles'
|
||||
> | null;
|
||||
|
||||
/** Match on id when present, else on normalized title (editors may omit ids). */
|
||||
function articleKey(article: ContractDocumentArticle): string {
|
||||
return article.id || `title:${article.title.trim().toLowerCase()}`;
|
||||
}
|
||||
|
||||
function indexArticles(
|
||||
articles: ContractDocumentArticle[] | undefined,
|
||||
): Map<string, ContractDocumentArticle> {
|
||||
const map = new Map<string, ContractDocumentArticle>();
|
||||
for (const article of articles ?? []) {
|
||||
map.set(articleKey(article), article);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two document snapshots and describe what changed, article by article.
|
||||
* Returns an empty array when the snapshots are equivalent, so callers can skip
|
||||
* recording a no-op revision.
|
||||
*/
|
||||
export function diffSnapshots(
|
||||
before: SnapshotLike,
|
||||
after: SnapshotLike,
|
||||
): ContractDocumentChange[] {
|
||||
const changes: ContractDocumentChange[] = [];
|
||||
|
||||
const beforeTitle = before?.documentTitle ?? null;
|
||||
const afterTitle = after?.documentTitle ?? null;
|
||||
if (beforeTitle !== afterTitle && afterTitle !== null) {
|
||||
changes.push({
|
||||
kind: 'DOCUMENT_TITLE_CHANGED',
|
||||
title: afterTitle,
|
||||
fromTitle: beforeTitle,
|
||||
});
|
||||
}
|
||||
|
||||
const beforeWhereas = before?.whereasClauses ?? [];
|
||||
const afterWhereas = after?.whereasClauses ?? [];
|
||||
const beforeWhereasSet = new Set(beforeWhereas);
|
||||
const afterWhereasSet = new Set(afterWhereas);
|
||||
const whereasAdded = afterWhereas.filter((c) => !beforeWhereasSet.has(c)).length;
|
||||
const whereasRemoved = beforeWhereas.filter((c) => !afterWhereasSet.has(c)).length;
|
||||
if (whereasAdded > 0 || whereasRemoved > 0) {
|
||||
changes.push({
|
||||
kind: 'WHEREAS_CHANGED',
|
||||
added: whereasAdded,
|
||||
removed: whereasRemoved,
|
||||
});
|
||||
}
|
||||
|
||||
const beforeArticles = indexArticles(before?.articles);
|
||||
const afterArticles = indexArticles(after?.articles);
|
||||
|
||||
for (const [key, article] of afterArticles) {
|
||||
const previous = beforeArticles.get(key);
|
||||
if (!previous) {
|
||||
changes.push({
|
||||
kind: 'ARTICLE_ADDED',
|
||||
articleId: article.id,
|
||||
title: article.title,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previous.title !== article.title) {
|
||||
changes.push({
|
||||
kind: 'ARTICLE_RENAMED',
|
||||
articleId: article.id,
|
||||
title: article.title,
|
||||
fromTitle: previous.title,
|
||||
});
|
||||
}
|
||||
if (previous.body !== article.body) {
|
||||
changes.push({
|
||||
kind: 'ARTICLE_BODY_CHANGED',
|
||||
articleId: article.id,
|
||||
title: article.title,
|
||||
});
|
||||
}
|
||||
if (previous.order !== article.order) {
|
||||
changes.push({
|
||||
kind: 'ARTICLE_REORDERED',
|
||||
articleId: article.id,
|
||||
title: article.title,
|
||||
fromOrder: previous.order,
|
||||
toOrder: article.order,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, article] of beforeArticles) {
|
||||
if (afterArticles.has(key)) continue;
|
||||
changes.push({
|
||||
kind: 'ARTICLE_REMOVED',
|
||||
articleId: article.id,
|
||||
title: article.title,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/** Short human summary of a change set, e.g. "2 articles edited, 1 article added". */
|
||||
export function summarizeChanges(changes: ContractDocumentChange[]): string {
|
||||
if (changes.length === 0) return 'No changes';
|
||||
|
||||
const articleVerbs: Record<string, string> = {
|
||||
ARTICLE_ADDED: 'added',
|
||||
ARTICLE_REMOVED: 'removed',
|
||||
ARTICLE_RENAMED: 'renamed',
|
||||
ARTICLE_BODY_CHANGED: 'edited',
|
||||
ARTICLE_REORDERED: 'reordered',
|
||||
};
|
||||
|
||||
const counts = new Map<string, number>();
|
||||
const parts: string[] = [];
|
||||
|
||||
for (const change of changes) {
|
||||
const verb = articleVerbs[change.kind];
|
||||
if (verb) {
|
||||
counts.set(verb, (counts.get(verb) ?? 0) + 1);
|
||||
} else if (change.kind === 'DOCUMENT_TITLE_CHANGED') {
|
||||
parts.push('document title changed');
|
||||
} else if (change.kind === 'WHEREAS_CHANGED') {
|
||||
parts.push('recitals changed');
|
||||
}
|
||||
}
|
||||
|
||||
const articleParts = [...counts.entries()].map(
|
||||
([verb, count]) => `${count} article${count === 1 ? '' : 's'} ${verb}`,
|
||||
);
|
||||
|
||||
return [...articleParts, ...parts].join(', ');
|
||||
}
|
||||
Reference in New Issue
Block a user