Rechtliches: Fristen ohne Monatsende-Drift, Indizes und Warten auf Attribute im Setup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8c1d84794
commit
c24b3d43c8
4 files changed
+65
-5
No files matched your search
+22
-3
@@ -34,6 +34,7 @@ export const COLLECTIONS = [
|
||||
{ key: "liveUrl", type: "string", size: 500 },
|
||||
{ key: "sort", type: "integer", min: 0, max: 1000 },
|
||||
],
|
||||
indexes: [{ key: "idx_sort", type: "key", attributes: ["sort"] }],
|
||||
},
|
||||
{
|
||||
id: "legalVersions",
|
||||
@@ -49,7 +50,10 @@ export const COLLECTIONS = [
|
||||
{ key: "fileIds", type: "string[]", size: 64 },
|
||||
{ key: "source", type: "string", size: 300 },
|
||||
],
|
||||
indexes: [{ key: "idx_documentKey", type: "key", attributes: ["documentKey"] }],
|
||||
indexes: [
|
||||
{ key: "idx_documentKey", type: "key", attributes: ["documentKey"] },
|
||||
{ key: "idx_source", type: "key", attributes: ["source"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "providerContracts",
|
||||
@@ -70,6 +74,7 @@ export const COLLECTIONS = [
|
||||
{ key: "note", type: "string", size: 5000 },
|
||||
{ key: "fileIds", type: "string[]", size: 64 },
|
||||
],
|
||||
indexes: [{ key: "idx_provider", type: "key", attributes: ["provider"] }],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -92,6 +97,21 @@ export const DOCUMENTS = [
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/** Wartet, bis Appwrite alle Attribute einer Sammlung fertig angelegt hat. */
|
||||
export async function waitForAttributes(db, collectionId, { intervalMs = 1000, timeoutMs = 120000 } = {}) {
|
||||
const until = Date.now() + timeoutMs;
|
||||
for (;;) {
|
||||
const { attributes } = await db.listAttributes(databaseId, collectionId);
|
||||
const failed = attributes.filter((a) => a.status === "failed" || a.status === "stuck");
|
||||
if (failed.length) {
|
||||
throw new Error(`Attribut fehlgeschlagen in ${collectionId}: ${failed.map((a) => `${a.key} (${a.error || a.status})`).join(", ")}`);
|
||||
}
|
||||
if (attributes.every((a) => a.status === "available")) return;
|
||||
if (Date.now() > until) throw new Error(`Attribute in ${collectionId} nach ${timeoutMs} ms nicht verfuegbar`);
|
||||
await sleep(intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
async function createAttribute(db, collectionId, a) {
|
||||
const req = Boolean(a.required);
|
||||
switch (a.type) {
|
||||
@@ -143,10 +163,10 @@ async function run() {
|
||||
console.log(" Attribut", a.key);
|
||||
await sleep(400);
|
||||
}
|
||||
await waitForAttributes(db, c.id);
|
||||
const indexes = new Set((await db.listIndexes(databaseId, c.id)).indexes.map((i) => i.key));
|
||||
for (const idx of c.indexes || []) {
|
||||
if (indexes.has(idx.key)) continue;
|
||||
await sleep(1500);
|
||||
await db.createIndex(databaseId, c.id, idx.key, idx.type, idx.attributes);
|
||||
console.log(" Index", idx.key);
|
||||
}
|
||||
@@ -161,7 +181,6 @@ async function run() {
|
||||
console.log("Bucket angelegt:", BUCKET.id);
|
||||
}
|
||||
|
||||
await sleep(2000);
|
||||
for (const d of DOCUMENTS) {
|
||||
try {
|
||||
await db.getDocument(databaseId, "legalDocuments", d.key);
|
||||
|
||||
+4
-2
@@ -64,8 +64,10 @@ export function nextCancellationDate(contract, today) {
|
||||
if (!isSet(contract.noticeValue) || !isSet(contract.noticeUnit)) {
|
||||
return { state: 'incomplete', periodEnd: null, noticeBy: null }
|
||||
}
|
||||
while (noticeDate(periodEnd, contract) < today) {
|
||||
periodEnd = addMonthsClamped(periodEnd, renewal)
|
||||
// Jede Periode vom Beginn aus rechnen: vom schon geklemmten Ende weiterzuzaehlen
|
||||
// wuerde nach einem Februar auf dem 28. haengen bleiben.
|
||||
for (let k = 1; noticeDate(periodEnd, contract) < today; k++) {
|
||||
periodEnd = addMonthsClamped(contract.startDate, term + k * renewal)
|
||||
}
|
||||
return { state: 'active', periodEnd, noticeBy: noticeDate(periodEnd, contract) }
|
||||
}
|
||||
|
||||
@@ -136,3 +136,12 @@ test('Abloesung am selben Tag: validTo nie vor validFrom', () => {
|
||||
const r = planNewVersion([v5], { documentKey: 'agb', version: 'v6', status: 'current', validFrom: '2026-05-01' })
|
||||
assert.deepEqual(r.updates, [{ id: 'v5', patch: { status: 'superseded', validTo: '2026-05-01' } }])
|
||||
})
|
||||
|
||||
test('Verlaengerungen ab dem 31. driften nicht auf den 28.', () => {
|
||||
const r = nextCancellationDate(
|
||||
{ startDate: '2025-01-31', termMonths: 1, renewalMonths: 1, noticeValue: 1, noticeUnit: 'days' },
|
||||
'2025-04-05'
|
||||
)
|
||||
assert.equal(r.periodEnd, '2025-04-30')
|
||||
assert.equal(r.noticeBy, '2025-04-29')
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { COLLECTIONS, waitForAttributes } from '../scripts/setup-legal.mjs'
|
||||
|
||||
const indexOn = (id, attr) =>
|
||||
COLLECTIONS.find((c) => c.id === id).indexes?.some((i) => i.attributes.includes(attr))
|
||||
|
||||
test('Indizes fuer alle Abfragen (equal/orderAsc)', () => {
|
||||
assert.ok(indexOn('legalVersions', 'documentKey'))
|
||||
assert.ok(indexOn('legalVersions', 'source'))
|
||||
assert.ok(indexOn('legalDocuments', 'sort'))
|
||||
assert.ok(indexOn('providerContracts', 'provider'))
|
||||
})
|
||||
|
||||
test('waitForAttributes wartet, bis alle Attribute verfuegbar sind', async () => {
|
||||
let calls = 0
|
||||
const db = {
|
||||
listAttributes: async () => {
|
||||
calls++
|
||||
return { attributes: [{ key: 'a', status: 'available' }, { key: 'b', status: calls < 3 ? 'processing' : 'available' }] }
|
||||
},
|
||||
}
|
||||
await waitForAttributes(db, 'x', { intervalMs: 1, timeoutMs: 1000 })
|
||||
assert.equal(calls, 3)
|
||||
})
|
||||
|
||||
test('waitForAttributes bricht bei fehlgeschlagenem Attribut ab', async () => {
|
||||
const db = { listAttributes: async () => ({ attributes: [{ key: 'b', status: 'failed', error: 'kaputt' }] }) }
|
||||
await assert.rejects(waitForAttributes(db, 'x', { intervalMs: 1, timeoutMs: 1000 }), /b/)
|
||||
})
|
||||
Reference in new issue
Block a user