Add tenant information fields to LocationEditForm

Added optional tenant fields (first name, last name, email) to billing locations
with a toggle to enable/disable 2D barcode generation for tenants.

Changes:
- Added generateTenantCode, tenantFirstName, tenantLastName, and tenantEmail
  fields to BillingLocation interface
- Updated LocationEditForm with toggle control and conditional tenant fields
- Implemented conditional validation: tenant names required when generateTenantCode is true
- Updated updateOrAddLocation action to persist tenant data across all update operations
- Added localization strings for tenant fields and validation messages (Croatian/English)

The generateTenantCode flag is persisted in the database and controls visibility
of tenant name fields. When enabled, both first and last names become mandatory.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-11-18 08:23:08 +01:00
parent 365386febb
commit 9ae023cc94
5 changed files with 219 additions and 34 deletions

View File

@@ -14,7 +14,11 @@ import { getTranslations, getLocale } from "next-intl/server";
export type State = {
errors?: {
locationName?: string[];
locationNotes?: string[],
locationNotes?: string[];
generateTenantCode?: string[];
tenantFirstName?: string[];
tenantLastName?: string[];
tenantEmail?: string[];
};
message?:string | null;
};
@@ -27,11 +31,34 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
_id: z.string(),
locationName: z.coerce.string().min(1, t("location-name-required")),
locationNotes: z.string(),
generateTenantCode: z.boolean().optional().nullable(),
tenantFirstName: z.string().optional().nullable(),
tenantLastName: z.string().optional().nullable(),
tenantEmail: z.string().optional().nullable(),
addToSubsequentMonths: z.boolean().optional().nullable(),
updateScope: z.enum(["current", "subsequent", "all"]).optional().nullable(),
})
// dont include the _id field in the response
.omit({ _id: true });
.omit({ _id: true })
// Add conditional validation: if generateTenantCode is true, tenant names are required
.refine((data) => {
if (data.generateTenantCode) {
return !!data.tenantFirstName && data.tenantFirstName.trim().length > 0;
}
return true;
}, {
message: t("tenant-first-name-required"),
path: ["tenantFirstName"],
})
.refine((data) => {
if (data.generateTenantCode) {
return !!data.tenantLastName && data.tenantLastName.trim().length > 0;
}
return true;
}, {
message: t("tenant-last-name-required"),
path: ["tenantLastName"],
});
/**
* Server-side action which adds or updates a bill
@@ -49,6 +76,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
const validatedFields = FormSchema(t).safeParse({
locationName: formData.get('locationName'),
locationNotes: formData.get('locationNotes'),
generateTenantCode: formData.get('generateTenantCode') === 'on',
tenantFirstName: formData.get('tenantFirstName') || null,
tenantLastName: formData.get('tenantLastName') || null,
tenantEmail: formData.get('tenantEmail') || null,
addToSubsequentMonths: formData.get('addToSubsequentMonths') === 'on',
updateScope: formData.get('updateScope') as "current" | "subsequent" | "all" | undefined,
});
@@ -57,13 +88,17 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
if(!validatedFields.success) {
return({
errors: validatedFields.error.flatten().fieldErrors,
message: "Missing Fields",
message: t("validation-failed"),
});
}
const {
locationName,
locationNotes,
generateTenantCode,
tenantFirstName,
tenantLastName,
tenantEmail,
addToSubsequentMonths,
updateScope,
} = validatedFields.data;
@@ -97,6 +132,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
$set: {
name: locationName,
notes: locationNotes,
generateTenantCode: generateTenantCode || false,
tenantFirstName: tenantFirstName || null,
tenantLastName: tenantLastName || null,
tenantEmail: tenantEmail || null,
}
}
);
@@ -108,9 +147,9 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
name: currentLocation.name,
$or: [
{ "yearMonth.year": { $gt: currentLocation.yearMonth.year } },
{
"yearMonth.year": currentLocation.yearMonth.year,
"yearMonth.month": { $gte: currentLocation.yearMonth.month }
{
"yearMonth.year": currentLocation.yearMonth.year,
"yearMonth.month": { $gte: currentLocation.yearMonth.month }
}
]
},
@@ -118,6 +157,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
$set: {
name: locationName,
notes: locationNotes,
generateTenantCode: generateTenantCode || false,
tenantFirstName: tenantFirstName || null,
tenantLastName: tenantLastName || null,
tenantEmail: tenantEmail || null,
}
}
);
@@ -132,6 +175,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
$set: {
name: locationName,
notes: locationNotes,
generateTenantCode: generateTenantCode || false,
tenantFirstName: tenantFirstName || null,
tenantLastName: tenantLastName || null,
tenantEmail: tenantEmail || null,
}
}
);
@@ -144,6 +191,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
userEmail,
name: locationName,
notes: locationNotes,
generateTenantCode: generateTenantCode || false,
tenantFirstName: tenantFirstName || null,
tenantLastName: tenantLastName || null,
tenantEmail: tenantEmail || null,
yearMonth: yearMonth,
bills: [],
});
@@ -208,6 +259,10 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
userEmail,
name: locationName,
notes: locationNotes,
generateTenantCode: generateTenantCode || false,
tenantFirstName: tenantFirstName || null,
tenantLastName: tenantLastName || null,
tenantEmail: tenantEmail || null,
yearMonth: { year: monthData.year, month: monthData.month },
bills: [],
});

View File

@@ -43,6 +43,14 @@ export interface BillingLocation {
bills: Bill[];
/** (optional) notes */
notes: string|null;
/** (optional) whether to generate 2D code for tenant */
generateTenantCode?: boolean | null;
/** (optional) tenant first name */
tenantFirstName?: string | null;
/** (optional) tenant last name */
tenantLastName?: string | null;
/** (optional) tenant email */
tenantEmail?: string | null;
};
export enum BilledTo {