feat: Add bank account details to admin profile

- Add accountHolder, bankName, iban, bic fields to AppUser domain
- Update UserOption, UserRow, UserMutation records in CatalogService
- Update all AppUser constructor calls to include new fields
- Add bank fields to frontend UserOption and UserRow types
- Add bank account form section to AdminProfilePage
This commit is contained in:
2026-03-18 09:31:32 +01:00
parent 8adc817428
commit 60e2f95637
4 changed files with 150 additions and 13 deletions

View File

@@ -19,6 +19,10 @@ public record AppUser(
String city, String city,
String email, String email,
String phoneNumber, String phoneNumber,
String accountHolder,
String bankName,
String iban,
String bic,
String passwordHash, String passwordHash,
boolean active, boolean active,
UserRole role, UserRole role,

View File

@@ -436,6 +436,10 @@ public class CatalogService {
adminManaged ? blankToNull(mutation.city()) : null, adminManaged ? blankToNull(mutation.city()) : null,
normalizeEmail(mutation.email()), normalizeEmail(mutation.email()),
adminManaged ? blankToNull(mutation.phoneNumber()) : null, adminManaged ? blankToNull(mutation.phoneNumber()) : null,
adminManaged ? blankToNull(mutation.accountHolder()) : null,
adminManaged ? blankToNull(mutation.bankName()) : null,
adminManaged ? blankToNull(mutation.iban()) : null,
adminManaged ? blankToNull(mutation.bic()) : null,
encodeIfPresent(mutation.password()), encodeIfPresent(mutation.password()),
mutation.active(), mutation.active(),
adminManaged ? normalizeManagedRole(mutation.role()) : UserRole.CUSTOMER, adminManaged ? normalizeManagedRole(mutation.role()) : UserRole.CUSTOMER,
@@ -467,6 +471,10 @@ public class CatalogService {
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.city()) : existing.city(), isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.city()) : existing.city(),
normalizeEmail(mutation.email()), normalizeEmail(mutation.email()),
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.phoneNumber()) : existing.phoneNumber(), isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.phoneNumber()) : existing.phoneNumber(),
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.accountHolder()) : existing.accountHolder(),
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.bankName()) : existing.bankName(),
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.iban()) : existing.iban(),
isPrimaryUser(existing) || actor.role() == UserRole.ADMIN ? blankToNull(mutation.bic()) : existing.bic(),
isBlank(mutation.password()) ? existing.passwordHash() : passwordEncoder.encode(mutation.password()), isBlank(mutation.password()) ? existing.passwordHash() : passwordEncoder.encode(mutation.password()),
mutation.active(), mutation.active(),
actor.role() == UserRole.ADMIN actor.role() == UserRole.ADMIN
@@ -516,6 +524,10 @@ public class CatalogService {
existing.city(), existing.city(),
existing.email(), existing.email(),
existing.phoneNumber(), existing.phoneNumber(),
existing.accountHolder(),
existing.bankName(),
existing.iban(),
existing.bic(),
passwordEncoder.encode(newPassword), passwordEncoder.encode(newPassword),
existing.active(), existing.active(),
existing.role(), existing.role(),
@@ -580,6 +592,10 @@ public class CatalogService {
city, city,
normalizedEmail, normalizedEmail,
phoneNumber, phoneNumber,
null,
null,
null,
null,
passwordEncoder.encode(mutation.password()), passwordEncoder.encode(mutation.password()),
false, false,
UserRole.CUSTOMER, UserRole.CUSTOMER,
@@ -600,6 +616,10 @@ public class CatalogService {
created.city(), created.city(),
created.email(), created.email(),
created.phoneNumber(), created.phoneNumber(),
created.accountHolder(),
created.bankName(),
created.iban(),
created.bic(),
created.passwordHash(), created.passwordHash(),
false, false,
created.role(), created.role(),
@@ -708,6 +728,10 @@ public class CatalogService {
user.city(), user.city(),
user.email(), user.email(),
user.phoneNumber(), user.phoneNumber(),
user.accountHolder(),
user.bankName(),
user.iban(),
user.bic(),
user.active(), user.active(),
normalizeStoredRole(user.role()), normalizeStoredRole(user.role()),
user.updatedAt() user.updatedAt()
@@ -743,6 +767,10 @@ public class CatalogService {
user.city(), user.city(),
user.email(), user.email(),
user.phoneNumber(), user.phoneNumber(),
user.accountHolder(),
user.bankName(),
user.iban(),
user.bic(),
normalizeStoredRole(user.role()) normalizeStoredRole(user.role())
); );
} }
@@ -835,6 +863,10 @@ public class CatalogService {
user.city(), user.city(),
user.email(), user.email(),
user.phoneNumber(), user.phoneNumber(),
user.accountHolder(),
user.bankName(),
user.iban(),
user.bic(),
user.passwordHash(), user.passwordHash(),
user.active(), user.active(),
normalizeStoredRole(user.role()), normalizeStoredRole(user.role()),
@@ -870,6 +902,10 @@ public class CatalogService {
null, null,
email, email,
null, null,
null,
null,
null,
null,
passwordEncoder.encode(rawPassword), passwordEncoder.encode(rawPassword),
true, true,
role, role,
@@ -997,6 +1033,10 @@ public class CatalogService {
String city, String city,
String email, String email,
String phoneNumber, String phoneNumber,
String accountHolder,
String bankName,
String iban,
String bic,
UserRole role UserRole role
) { ) {
} }
@@ -1066,6 +1106,10 @@ public class CatalogService {
String city, String city,
String email, String email,
String phoneNumber, String phoneNumber,
String accountHolder,
String bankName,
String iban,
String bic,
boolean active, boolean active,
UserRole role, UserRole role,
LocalDateTime updatedAt LocalDateTime updatedAt
@@ -1083,6 +1127,10 @@ public class CatalogService {
String city, String city,
String email, String email,
String phoneNumber, String phoneNumber,
String accountHolder,
String bankName,
String iban,
String bic,
String password, String password,
boolean active, boolean active,
UserRole role UserRole role

View File

@@ -62,6 +62,10 @@ export interface UserOption {
city: string | null; city: string | null;
email: string | null; email: string | null;
phoneNumber: string | null; phoneNumber: string | null;
accountHolder: string | null;
bankName: string | null;
iban: string | null;
bic: string | null;
role: UserRole; role: UserRole;
} }

View File

@@ -18,6 +18,10 @@ export default function AdminProfilePage() {
city: "", city: "",
email: "", email: "",
phoneNumber: "", phoneNumber: "",
accountHolder: "",
bankName: "",
iban: "",
bic: "",
}); });
// Load current user data // Load current user data
@@ -36,6 +40,10 @@ export default function AdminProfilePage() {
city: currentUser.city || "", city: currentUser.city || "",
email: currentUser.email || "", email: currentUser.email || "",
phoneNumber: currentUser.phoneNumber || "", phoneNumber: currentUser.phoneNumber || "",
accountHolder: currentUser.accountHolder || "",
bankName: currentUser.bankName || "",
iban: currentUser.iban || "",
bic: currentUser.bic || "",
}); });
} }
} catch (error) { } catch (error) {
@@ -77,6 +85,10 @@ export default function AdminProfilePage() {
city: formData.city.trim() || null, city: formData.city.trim() || null,
email: formData.email.trim(), email: formData.email.trim(),
phoneNumber: formData.phoneNumber.trim() || null, phoneNumber: formData.phoneNumber.trim() || null,
accountHolder: formData.accountHolder.trim() || null,
bankName: formData.bankName.trim() || null,
iban: formData.iban.trim() || null,
bic: formData.bic.trim() || null,
active: true, active: true,
}); });
@@ -92,6 +104,10 @@ export default function AdminProfilePage() {
city: response.city, city: response.city,
email: response.email, email: response.email,
phoneNumber: response.phoneNumber, phoneNumber: response.phoneNumber,
accountHolder: response.accountHolder,
bankName: response.bankName,
iban: response.iban,
bic: response.bic,
}); });
} }
@@ -122,7 +138,7 @@ export default function AdminProfilePage() {
<p className="eyebrow">Stammdaten</p> <p className="eyebrow">Stammdaten</p>
<h3>Meine Stammdaten</h3> <h3>Meine Stammdaten</h3>
<p className="muted-text"> <p className="muted-text">
Verwalten Sie hier Ihre persönlichen und Unternehmensdaten. Verwalten Sie hier Ihre persönlichen, Unternehmens- und Bankdaten.
</p> </p>
</div> </div>
</section> </section>
@@ -140,12 +156,12 @@ export default function AdminProfilePage() {
</div> </div>
)} )}
{/* Stammdaten-Formular */} {/* Persönliche Daten */}
<section className="section-card"> <section className="section-card">
<div className="section-card__header"> <div className="section-card__header">
<div> <div>
<p className="eyebrow">Profil</p> <p className="eyebrow">Profil</p>
<h3>Stammdaten bearbeiten</h3> <h3>Persönliche Daten</h3>
</div> </div>
</div> </div>
@@ -255,17 +271,82 @@ export default function AdminProfilePage() {
disabled={saving} disabled={saving}
/> />
</label> </label>
</form>
</section>
<div className="field" style={{ gridColumn: "1 / -1" }}> {/* Bankverbindung */}
<section className="section-card">
<div className="section-card__header">
<div>
<p className="eyebrow">Zahlung</p>
<h3>Bankverbindung</h3>
</div>
</div>
<div className="field-grid field-grid--2col">
<label className="field">
<span>Kontoinhaber</span>
<input
type="text"
value={formData.accountHolder}
onChange={(e) =>
setFormData({ ...formData, accountHolder: e.target.value })
}
placeholder="Name des Kontoinhabers"
disabled={saving}
/>
</label>
<label className="field">
<span>Bankname</span>
<input
type="text"
value={formData.bankName}
onChange={(e) =>
setFormData({ ...formData, bankName: e.target.value })
}
placeholder="Name der Bank"
disabled={saving}
/>
</label>
<label className="field">
<span>IBAN</span>
<input
type="text"
value={formData.iban}
onChange={(e) =>
setFormData({ ...formData, iban: e.target.value })
}
placeholder="DE12 3456 7890 1234 5678 90"
disabled={saving}
/>
</label>
<label className="field">
<span>BIC</span>
<input
type="text"
value={formData.bic}
onChange={(e) =>
setFormData({ ...formData, bic: e.target.value })
}
placeholder="ABCDEFGHXXX"
disabled={saving}
/>
</label>
</div>
<div className="field" style={{ marginTop: "1rem" }}>
<button <button
type="submit" type="button"
className="accent-button" className="accent-button"
onClick={handleSubmit}
disabled={saving} disabled={saving}
> >
{saving ? "Wird gespeichert..." : "Stammdaten speichern"} {saving ? "Wird gespeichert..." : "Stammdaten speichern"}
</button> </button>
</div> </div>
</form>
</section> </section>
{/* Info-Box */} {/* Info-Box */}