refactor: remove obsolete member management API module
This commit is contained in:
@@ -17,66 +17,140 @@ export const SepaMandateStatusEnum = z.enum([
|
||||
'expired',
|
||||
]);
|
||||
|
||||
export const CreateMemberSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
memberNumber: z.string().optional(),
|
||||
firstName: z.string().min(1).max(128),
|
||||
lastName: z.string().min(1).max(128),
|
||||
dateOfBirth: z.string().optional(),
|
||||
gender: z.enum(['male', 'female', 'diverse']).optional(),
|
||||
title: z.string().max(32).optional(),
|
||||
email: z.string().email().optional().or(z.literal('')),
|
||||
phone: z.string().max(32).optional(),
|
||||
mobile: z.string().max(32).optional(),
|
||||
street: z.string().max(256).optional(),
|
||||
houseNumber: z.string().max(16).optional(),
|
||||
postalCode: z.string().max(10).optional(),
|
||||
city: z.string().max(128).optional(),
|
||||
country: z.string().max(2).default('DE'),
|
||||
status: MembershipStatusEnum.default('active'),
|
||||
entryDate: z.string().default(() => new Date().toISOString().split('T')[0]!),
|
||||
duesCategoryId: z.string().uuid().optional(),
|
||||
iban: z.string().max(34).optional(),
|
||||
bic: z.string().max(11).optional(),
|
||||
accountHolder: z.string().max(128).optional(),
|
||||
gdprConsent: z.boolean().default(false),
|
||||
notes: z.string().optional(),
|
||||
// New optional fields
|
||||
salutation: z.string().optional(),
|
||||
street2: z.string().optional(),
|
||||
phone2: z.string().optional(),
|
||||
fax: z.string().optional(),
|
||||
birthplace: z.string().optional(),
|
||||
birthCountry: z.string().default('DE'),
|
||||
isHonorary: z.boolean().default(false),
|
||||
isFoundingMember: z.boolean().default(false),
|
||||
isYouth: z.boolean().default(false),
|
||||
isRetiree: z.boolean().default(false),
|
||||
isProbationary: z.boolean().default(false),
|
||||
isTransferred: z.boolean().default(false),
|
||||
exitDate: z.string().optional(),
|
||||
exitReason: z.string().optional(),
|
||||
guardianName: z.string().optional(),
|
||||
guardianPhone: z.string().optional(),
|
||||
guardianEmail: z.string().optional(),
|
||||
duesYear: z.number().int().optional(),
|
||||
duesPaid: z.boolean().default(false),
|
||||
additionalFees: z.number().default(0),
|
||||
exemptionType: z.string().optional(),
|
||||
exemptionReason: z.string().optional(),
|
||||
exemptionAmount: z.number().optional(),
|
||||
gdprNewsletter: z.boolean().default(false),
|
||||
gdprInternet: z.boolean().default(false),
|
||||
gdprPrint: z.boolean().default(false),
|
||||
gdprBirthdayInfo: z.boolean().default(false),
|
||||
sepaMandateReference: z.string().optional(),
|
||||
});
|
||||
// --- Shared validators ---
|
||||
|
||||
/** IBAN validation with mod-97 checksum (ISO 13616) */
|
||||
export function validateIban(iban: string): boolean {
|
||||
const cleaned = iban.replace(/\s/g, '').toUpperCase();
|
||||
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/.test(cleaned)) return false;
|
||||
|
||||
const rearranged = cleaned.slice(4) + cleaned.slice(0, 4);
|
||||
let numStr = '';
|
||||
for (const char of rearranged) {
|
||||
const code = char.charCodeAt(0);
|
||||
numStr += code >= 65 && code <= 90 ? (code - 55).toString() : char;
|
||||
}
|
||||
|
||||
let remainder = 0;
|
||||
for (const digit of numStr) {
|
||||
remainder = (remainder * 10 + parseInt(digit, 10)) % 97;
|
||||
}
|
||||
return remainder === 1;
|
||||
}
|
||||
|
||||
const ibanSchema = z
|
||||
.string()
|
||||
.max(34)
|
||||
.optional()
|
||||
.refine((v) => !v || v.trim() === '' || validateIban(v), {
|
||||
message: 'Ungültige IBAN (Prüfsumme fehlerhaft)',
|
||||
});
|
||||
|
||||
const dateNotFutureSchema = (fieldName: string) =>
|
||||
z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((v) => !v || new Date(v) <= new Date(), {
|
||||
message: `${fieldName} darf nicht in der Zukunft liegen`,
|
||||
});
|
||||
|
||||
// --- Main schemas ---
|
||||
|
||||
export const CreateMemberSchema = z
|
||||
.object({
|
||||
accountId: z.string().uuid(),
|
||||
memberNumber: z.string().optional(),
|
||||
firstName: z.string().min(1).max(128),
|
||||
lastName: z.string().min(1).max(128),
|
||||
dateOfBirth: dateNotFutureSchema('Geburtsdatum'),
|
||||
gender: z.enum(['male', 'female', 'diverse']).optional(),
|
||||
title: z.string().max(32).optional(),
|
||||
email: z.string().email().optional().or(z.literal('')),
|
||||
phone: z.string().max(32).optional(),
|
||||
mobile: z.string().max(32).optional(),
|
||||
street: z.string().max(256).optional(),
|
||||
houseNumber: z.string().max(16).optional(),
|
||||
postalCode: z.string().max(10).optional(),
|
||||
city: z.string().max(128).optional(),
|
||||
country: z.string().max(2).default('DE'),
|
||||
status: MembershipStatusEnum.default('active'),
|
||||
entryDate: z
|
||||
.string()
|
||||
.default(() => new Date().toISOString().split('T')[0]!),
|
||||
duesCategoryId: z.string().uuid().optional(),
|
||||
iban: ibanSchema,
|
||||
bic: z.string().max(11).optional(),
|
||||
accountHolder: z.string().max(128).optional(),
|
||||
gdprConsent: z.boolean().default(false),
|
||||
notes: z.string().optional(),
|
||||
salutation: z.string().optional(),
|
||||
street2: z.string().optional(),
|
||||
phone2: z.string().optional(),
|
||||
fax: z.string().optional(),
|
||||
birthplace: z.string().optional(),
|
||||
birthCountry: z.string().default('DE'),
|
||||
isHonorary: z.boolean().default(false),
|
||||
isFoundingMember: z.boolean().default(false),
|
||||
isYouth: z.boolean().default(false),
|
||||
isRetiree: z.boolean().default(false),
|
||||
isProbationary: z.boolean().default(false),
|
||||
isTransferred: z.boolean().default(false),
|
||||
exitDate: z.string().optional(),
|
||||
exitReason: z.string().optional(),
|
||||
guardianName: z.string().optional(),
|
||||
guardianPhone: z.string().optional(),
|
||||
guardianEmail: z.string().optional(),
|
||||
duesYear: z.number().int().optional(),
|
||||
duesPaid: z.boolean().default(false),
|
||||
additionalFees: z.number().default(0),
|
||||
exemptionType: z.string().optional(),
|
||||
exemptionReason: z.string().optional(),
|
||||
exemptionAmount: z.number().optional(),
|
||||
gdprNewsletter: z.boolean().default(false),
|
||||
gdprInternet: z.boolean().default(false),
|
||||
gdprPrint: z.boolean().default(false),
|
||||
gdprBirthdayInfo: z.boolean().default(false),
|
||||
sepaMandateReference: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
// Cross-field: exit_date must be after entry_date
|
||||
if (data.exitDate && data.entryDate && data.exitDate < data.entryDate) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Austrittsdatum muss nach dem Eintrittsdatum liegen',
|
||||
path: ['exitDate'],
|
||||
});
|
||||
}
|
||||
|
||||
// Cross-field: entry_date must be after date_of_birth
|
||||
if (
|
||||
data.dateOfBirth &&
|
||||
data.entryDate &&
|
||||
data.entryDate < data.dateOfBirth
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Eintrittsdatum muss nach dem Geburtsdatum liegen',
|
||||
path: ['entryDate'],
|
||||
});
|
||||
}
|
||||
|
||||
// Cross-field: youth members should have guardian info
|
||||
if (data.isYouth && !data.guardianName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Jugendmitglieder benötigen einen Erziehungsberechtigten',
|
||||
path: ['guardianName'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type CreateMemberInput = z.infer<typeof CreateMemberSchema>;
|
||||
|
||||
export const UpdateMemberSchema = CreateMemberSchema.partial().extend({
|
||||
memberId: z.string().uuid(),
|
||||
isArchived: z.boolean().optional(),
|
||||
version: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export type UpdateMemberInput = z.infer<typeof UpdateMemberSchema>;
|
||||
@@ -128,7 +202,13 @@ export const CreateSepaMandateSchema = z.object({
|
||||
memberId: z.string().uuid(),
|
||||
accountId: z.string().uuid(),
|
||||
mandateReference: z.string().min(1),
|
||||
iban: z.string().min(15).max(34),
|
||||
iban: z
|
||||
.string()
|
||||
.min(15)
|
||||
.max(34)
|
||||
.refine((v) => validateIban(v), {
|
||||
message: 'Ungültige IBAN (Prüfsumme fehlerhaft)',
|
||||
}),
|
||||
bic: z.string().optional(),
|
||||
accountHolder: z.string().min(1),
|
||||
mandateDate: z.string(),
|
||||
@@ -149,7 +229,14 @@ export type UpdateDuesCategoryInput = z.infer<typeof UpdateDuesCategorySchema>;
|
||||
|
||||
export const UpdateMandateSchema = z.object({
|
||||
mandateId: z.string().uuid(),
|
||||
iban: z.string().min(15).max(34).optional(),
|
||||
iban: z
|
||||
.string()
|
||||
.min(15)
|
||||
.max(34)
|
||||
.refine((v) => validateIban(v), {
|
||||
message: 'Ungültige IBAN (Prüfsumme fehlerhaft)',
|
||||
})
|
||||
.optional(),
|
||||
bic: z.string().optional(),
|
||||
accountHolder: z.string().optional(),
|
||||
sequence: z.enum(['FRST', 'RCUR', 'FNAL', 'OOFF']).optional(),
|
||||
@@ -191,6 +278,7 @@ export const MemberSearchFiltersSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
status: z.array(MembershipStatusEnum).optional(),
|
||||
departmentIds: z.array(z.string().uuid()).optional(),
|
||||
tagIds: z.array(z.string().uuid()).optional(),
|
||||
duesCategoryId: z.string().uuid().optional(),
|
||||
flags: z
|
||||
.array(
|
||||
|
||||
Reference in New Issue
Block a user