feat: T6 wave 1 – compliance schema, permissions, lock order, backup
- Migrations (additive): veterinary medicines with lots and movement ledger, treatment medicine/lot/dose links, label particulars, authority notices, apiary restrictions, compliance settings and acknowledgements; notification category COMPLIANCE, added to non-empty Telegram lists. - Permissions veterinary-medicines:* and compliance:* with role grants. - LOCKABLE_TABLES: Apiary locks before Colony; T6 tables appended. - Backup exports and restores the new tables in FK order; unsealed files skip them and report dropped medicine links; an open TSK draft in the target wins. - describeNullContracts shared helper; Telegram backlog e2e no longer depends on the time of day. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7a1480138d
commit
8a31c42578
23 files changed
+2028
-84
No files matched your search
@@ -113,6 +113,18 @@ describe('deliveryAvailableAt', () => {
|
||||
expect(deliveryAvailableAt(now, 'SUGGESTION', NO_QUIET)).toEqual(now);
|
||||
});
|
||||
|
||||
it('COMPLIANCE has no time of day: available now, inside quiet hours at their end', () => {
|
||||
const afternoon = at('2026-10-13', '15:00');
|
||||
const night = at('2026-10-13', '23:00');
|
||||
|
||||
expect(deliveryAvailableAt(afternoon, 'COMPLIANCE', DEFAULTS)).toEqual(
|
||||
afternoon,
|
||||
);
|
||||
expect(deliveryAvailableAt(night, 'COMPLIANCE', DEFAULTS)).toEqual(
|
||||
at('2026-10-14', '06:30'),
|
||||
);
|
||||
});
|
||||
|
||||
it('ends quiet hours at 06:30 Berlin time across the clock change', () => {
|
||||
const now = at('2026-10-24', '22:00');
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
import { NotificationCategory } from '../../../generated/prisma';
|
||||
import { UpdateNotificationSettingsDto } from './update-notification-settings.dto';
|
||||
|
||||
async function errorsOf(body: Record<string, unknown>): Promise<string[]> {
|
||||
const errors = await validate(
|
||||
plainToInstance(UpdateNotificationSettingsDto, body),
|
||||
{ whitelist: true, forbidNonWhitelisted: true },
|
||||
);
|
||||
return errors.map((error) => error.property);
|
||||
}
|
||||
|
||||
describe('UpdateNotificationSettingsDto telegramCategories', () => {
|
||||
it('accepts COMPLIANCE like every other category', async () => {
|
||||
await expect(
|
||||
errorsOf({ telegramCategories: ['COMPLIANCE'] }),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('accepts the complete list of categories, COMPLIANCE included', async () => {
|
||||
await expect(
|
||||
errorsOf({ telegramCategories: Object.values(NotificationCategory) }),
|
||||
).resolves.toEqual([]);
|
||||
expect(Object.values(NotificationCategory)).toContain('COMPLIANCE');
|
||||
});
|
||||
|
||||
it('rejects an unknown category and a duplicate', async () => {
|
||||
await expect(
|
||||
errorsOf({ telegramCategories: ['AUTHORITY'] }),
|
||||
).resolves.toEqual(['telegramCategories']);
|
||||
await expect(
|
||||
errorsOf({ telegramCategories: ['COMPLIANCE', 'COMPLIANCE'] }),
|
||||
).resolves.toEqual(['telegramCategories']);
|
||||
});
|
||||
});
|
||||
@@ -15,7 +15,7 @@ const stored = {
|
||||
quietFrom: '21:00' as string | null,
|
||||
quietUntil: '06:30' as string | null,
|
||||
telegramEnabled: false,
|
||||
telegramCategories: ['TASK_DUE', 'DIGEST'],
|
||||
telegramCategories: ['TASK_DUE', 'DIGEST', 'COMPLIANCE'],
|
||||
telegramDetail: true,
|
||||
updatedAt: new Date(0),
|
||||
};
|
||||
@@ -167,7 +167,7 @@ describe('NotificationPreferencesService', () => {
|
||||
digestTime: '07:00',
|
||||
quietFrom: '21:00',
|
||||
quietUntil: '06:30',
|
||||
telegramCategories: ['TASK_DUE', 'DIGEST'],
|
||||
telegramCategories: ['TASK_DUE', 'DIGEST', 'COMPLIANCE'],
|
||||
telegramDetail: false,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user