Restructure generated contract as a full-format rental agreement
Rewrites contractDocumentGenerator.ts from a short summary into a proper Wohnraummietvertrag layout: numbered §§ for Vertragsparteien, Mieträume, Mietzeit, Miete/Nebenkosten, Kaution, Instandhaltung/ Schönheitsreparaturen, Hausordnung, Tierhaltung/Untervermietung, Mietanpassung, sonstige Vereinbarungen, salvatorische Klausel, plus signature lines for both landlord and tenant (with the drawn signature embedded once signed) and a page-footer disclaimer instead of a top-of-document warning box. Adds Contract.landlordName/landlordAddress/propertyAddress so the document can show a real letterhead — addresses default to a "please fill in" placeholder rather than a fabricated address, since this is a real WG's data. Landlord fills them once via new fields in the contract generator form; persisted per contract like the other template options.
This commit is contained in:
parent
bba4fc318b
commit
410db93c30
@ -98,6 +98,9 @@ const CONTRACT_DOCUMENTS_SELECT = {
|
|||||||
wifiIncluded: true,
|
wifiIncluded: true,
|
||||||
furnished: true,
|
furnished: true,
|
||||||
utilityBillingModel: true,
|
utilityBillingModel: true,
|
||||||
|
landlordName: true,
|
||||||
|
landlordAddress: true,
|
||||||
|
propertyAddress: true,
|
||||||
signedAt: true,
|
signedAt: true,
|
||||||
user: { select: { id: true, fullName: true } },
|
user: { select: { id: true, fullName: true } },
|
||||||
room: { select: { id: true, roomNumber: true, sizeSqm: true } },
|
room: { select: { id: true, roomNumber: true, sizeSqm: true } },
|
||||||
@ -193,6 +196,9 @@ contractsRouter.post(
|
|||||||
depositAmount,
|
depositAmount,
|
||||||
rentAdjustmentType,
|
rentAdjustmentType,
|
||||||
rentAdjustmentClause,
|
rentAdjustmentClause,
|
||||||
|
landlordName,
|
||||||
|
landlordAddress,
|
||||||
|
propertyAddress,
|
||||||
} = req.body || {};
|
} = req.body || {};
|
||||||
|
|
||||||
if (utilityBillingModel !== undefined && !UTILITY_MODELS.includes(utilityBillingModel)) {
|
if (utilityBillingModel !== undefined && !UTILITY_MODELS.includes(utilityBillingModel)) {
|
||||||
@ -212,6 +218,9 @@ contractsRouter.post(
|
|||||||
depositAmount: Number.isFinite(Number(depositAmount)) ? Number(depositAmount) : undefined,
|
depositAmount: Number.isFinite(Number(depositAmount)) ? Number(depositAmount) : undefined,
|
||||||
rentAdjustmentType: rentAdjustmentType || undefined,
|
rentAdjustmentType: rentAdjustmentType || undefined,
|
||||||
rentAdjustmentClause: typeof rentAdjustmentClause === 'string' ? rentAdjustmentClause.trim() || null : undefined,
|
rentAdjustmentClause: typeof rentAdjustmentClause === 'string' ? rentAdjustmentClause.trim() || null : undefined,
|
||||||
|
landlordName: typeof landlordName === 'string' && landlordName.trim() ? landlordName.trim() : undefined,
|
||||||
|
landlordAddress: typeof landlordAddress === 'string' ? landlordAddress.trim() || null : undefined,
|
||||||
|
propertyAddress: typeof propertyAddress === 'string' ? propertyAddress.trim() || null : undefined,
|
||||||
},
|
},
|
||||||
include: { room: true, user: true },
|
include: { room: true, user: true },
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|||||||
@ -3,15 +3,24 @@ import { Contract, Room, User, UtilityBillingModel, RentAdjustmentType } from '@
|
|||||||
|
|
||||||
type ContractForPdf = Contract & { room: Room; user: User };
|
type ContractForPdf = Contract & { room: Room; user: User };
|
||||||
|
|
||||||
const ADJUSTMENT_LABEL: Record<RentAdjustmentType, string> = {
|
const UTILITY_LABEL: Record<UtilityBillingModel, string> = {
|
||||||
NONE: 'Keine Mietanpassungsklausel.',
|
PAUSCHALE:
|
||||||
INDEX_MIETE: 'Indexmietklausel: Die Miete kann sich gemäß der Entwicklung des Verbraucherpreisindexes ändern.',
|
'Die Nebenkosten werden als Pauschale erhoben und sind in der in § 4 genannten Warmmiete enthalten. ' +
|
||||||
STAFFEL_MIETE: 'Staffelmietklausel: Die Miete steigt zu vorab vereinbarten Zeitpunkten in vereinbarten Schritten.',
|
'Eine gesonderte Abrechnung findet nicht statt, auch nicht bei Nachzahlung oder Guthaben.',
|
||||||
|
ABRECHNUNG:
|
||||||
|
'Die Nebenkosten werden als Vorauszahlung erhoben und jährlich auf Basis der tatsächlich angefallenen ' +
|
||||||
|
'Kosten anteilig nach Zimmergröße (§ 556a BGB) abgerechnet. Nachzahlungen und Guthaben werden im ' +
|
||||||
|
'Rahmen der jeweiligen Abrechnung ausgeglichen.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const UTILITY_LABEL: Record<UtilityBillingModel, string> = {
|
const ADJUSTMENT_LABEL: Record<RentAdjustmentType, string> = {
|
||||||
PAUSCHALE: 'Die Nebenkosten sind als Pauschale in der Warmmiete enthalten und werden nicht separat abgerechnet.',
|
NONE: 'Es ist keine Mietanpassungsklausel vereinbart. Mieterhöhungen richten sich nach den gesetzlichen Vorschriften (§§ 557 ff. BGB).',
|
||||||
ABRECHNUNG: 'Die Nebenkosten werden jährlich auf Basis der tatsächlichen Kosten anteilig nach Zimmergröße abgerechnet.',
|
INDEX_MIETE:
|
||||||
|
'Es wird eine Indexmiete gemäß § 557b BGB vereinbart. Die Miete verändert sich entsprechend der ' +
|
||||||
|
'Entwicklung des vom Statistischen Bundesamt ermittelten Verbraucherpreisindexes für Deutschland.',
|
||||||
|
STAFFEL_MIETE:
|
||||||
|
'Es wird eine Staffelmiete gemäß § 557a BGB vereinbart. Die Miete erhöht sich zu den nachfolgend ' +
|
||||||
|
'genannten Zeitpunkten um die jeweils vereinbarten Beträge.',
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatEuro(value: unknown): string {
|
function formatEuro(value: unknown): string {
|
||||||
@ -19,82 +28,199 @@ function formatEuro(value: unknown): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(date: Date | null | undefined): string {
|
function formatDate(date: Date | null | undefined): string {
|
||||||
return date ? date.toLocaleDateString('de-DE') : 'unbefristet';
|
return date ? date.toLocaleDateString('de-DE') : null as unknown as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addressOrPlaceholder(value: string | null | undefined): string {
|
||||||
|
return value && value.trim() ? value.trim() : '[Anschrift bitte im Vertragsformular ergänzen]';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt aus den Vertrags-Baukasten-Feldern (siehe Contract-Model:
|
* Erzeugt aus den Vertrags-Baukasten-Feldern ein vollständig strukturiertes
|
||||||
* wifiIncluded, furnished, utilityBillingModel, noticePeriodMonths,
|
* PDF im Format eines gewöhnlichen Wohnraum-/WG-Zimmer-Mietvertrags
|
||||||
* rentAdjustmentType, depositAmount, ...) ein PDF-Dokument als Buffer.
|
* (Vertragsparteien, Mieträume, Mietzeit, Miete/Nebenkosten, Kaution,
|
||||||
|
* Instandhaltung, Hausordnung, Tierhaltung, Untervermietung, Kündigung,
|
||||||
|
* Mietanpassung, salvatorische Klausel, Unterschriften).
|
||||||
*
|
*
|
||||||
* Ausdrücklich eine Vorlage/Dokumentation auf Basis der App-Daten, keine
|
* Ausdrücklich eine aus den App-Daten generierte Vorlage, keine
|
||||||
* rechtsverbindliche oder anwaltlich geprüfte Vertragsvorlage.
|
* anwaltlich geprüfte oder rechtsverbindlich garantierte Vertragsurkunde
|
||||||
|
* — siehe Hinweis am Dokumentende.
|
||||||
*/
|
*/
|
||||||
export function generateContractPdf(contract: ContractForPdf): Promise<Buffer> {
|
export function generateContractPdf(contract: ContractForPdf): Promise<Buffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const doc = new PDFDocument({ margin: 56 });
|
const doc = new PDFDocument({ margin: 56, bufferPages: true, size: 'A4' });
|
||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
doc.on('data', (chunk) => chunks.push(chunk));
|
doc.on('data', (chunk) => chunks.push(chunk));
|
||||||
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
doc.on('error', reject);
|
doc.on('error', reject);
|
||||||
|
|
||||||
doc.fontSize(18).text('Mietvertrag über Wohnraum (Vorlage)', { underline: true });
|
const H1 = () => doc.font('Helvetica-Bold').fontSize(11);
|
||||||
doc.moveDown(0.3);
|
const BODY = () => doc.font('Helvetica').fontSize(10.5);
|
||||||
doc.fontSize(9).fillColor('#666666').text(
|
const section = (title: string, bodyFn: () => void) => {
|
||||||
'Automatisch erstellte Vorlage auf Basis der Angaben in der WG-Verwaltungs-App. ' +
|
doc.moveDown(0.9);
|
||||||
'Keine Rechtsberatung, keine Gewähr auf rechtliche Vollständigkeit.',
|
H1().text(title);
|
||||||
|
doc.moveDown(0.25);
|
||||||
|
BODY();
|
||||||
|
bodyFn();
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Titel ---
|
||||||
|
doc.font('Helvetica-Bold').fontSize(17).text('Mietvertrag über Wohnraum', { align: 'center' });
|
||||||
|
doc.moveDown(0.15);
|
||||||
|
doc.font('Helvetica').fontSize(10).fillColor('#555555').text(
|
||||||
|
`Zimmer ${contract.room.roomNumber} · ${addressOrPlaceholder(contract.propertyAddress)}`,
|
||||||
|
{ align: 'center' },
|
||||||
);
|
);
|
||||||
doc.fillColor('#000000');
|
doc.fillColor('#000000');
|
||||||
doc.moveDown(1);
|
doc.moveDown(1.2);
|
||||||
|
|
||||||
doc.fontSize(13).text('§ 1 Mietparteien und Mietobjekt', { underline: true });
|
// --- § 1 Vertragsparteien ---
|
||||||
doc.fontSize(11).moveDown(0.3);
|
section('§ 1 Vertragsparteien', () => {
|
||||||
doc.text(`Vermieter: Familie Lombardo, WG Nackenheim`);
|
doc.text(`Vermieter: ${contract.landlordName}`);
|
||||||
doc.text(`Mieter: ${contract.user.fullName}`);
|
doc.text(`Anschrift des Vermieters: ${addressOrPlaceholder(contract.landlordAddress)}`);
|
||||||
doc.text(`Mietobjekt: ${contract.room.roomNumber}, ${Number(contract.room.sizeSqm)} qm, WG Nackenheim`);
|
doc.moveDown(0.4);
|
||||||
doc.moveDown(1);
|
doc.text(`Mieter: ${contract.user.fullName}`);
|
||||||
|
doc.text(`E-Mail: ${contract.user.email}`);
|
||||||
|
doc.moveDown(0.4);
|
||||||
|
doc.text(
|
||||||
|
'Vermieter und Mieter schließen den nachfolgenden Mietvertrag über den in § 2 bezeichneten Wohnraum ' +
|
||||||
|
'innerhalb einer gemeinschaftlich bewohnten Wohnung (Wohngemeinschaft).',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
doc.fontSize(13).text('§ 2 Mietzeit', { underline: true });
|
// --- § 2 Mieträume ---
|
||||||
doc.fontSize(11).moveDown(0.3);
|
section('§ 2 Mieträume', () => {
|
||||||
doc.text(`Mietbeginn: ${formatDate(contract.startDate)}`);
|
doc.text(`Anschrift der Wohnung: ${addressOrPlaceholder(contract.propertyAddress)}`);
|
||||||
doc.text(`Mietende: ${formatDate(contract.endDate)}`);
|
doc.text(`Vermietetes Zimmer: ${contract.room.roomNumber}, ca. ${Number(contract.room.sizeSqm)} m².`);
|
||||||
doc.text(`Kündigungsfrist: ${contract.noticePeriodMonths} Monate zum Monatsende.`);
|
doc.text(
|
||||||
doc.moveDown(1);
|
'Mitbenutzung: Küche, Bad/WC sowie ggf. weitere Gemeinschaftsflächen der Wohnung stehen dem Mieter ' +
|
||||||
|
'gemeinsam mit den übrigen Bewohnern zur Mitbenutzung zur Verfügung.',
|
||||||
|
);
|
||||||
|
doc.text(`Das Zimmer wird ${contract.furnished ? 'möbliert' : 'unmöbliert'} übergeben.`);
|
||||||
|
doc.text(`Internet/WLAN: ${contract.wifiIncluded ? 'im Mietpreis enthalten.' : 'nicht im Mietpreis enthalten.'}`);
|
||||||
|
});
|
||||||
|
|
||||||
doc.fontSize(13).text('§ 3 Miete und Nebenkosten', { underline: true });
|
// --- § 3 Mietzeit ---
|
||||||
doc.fontSize(11).moveDown(0.3);
|
section('§ 3 Mietzeit', () => {
|
||||||
doc.text(`Warmmiete gesamt: ${formatEuro(contract.totalWarmRent)} pro Monat, fällig am ${contract.paymentDueDay}. Werktag.`);
|
doc.text(`Das Mietverhältnis beginnt am ${formatDate(contract.startDate)}.`);
|
||||||
doc.text(UTILITY_LABEL[contract.utilityBillingModel]);
|
doc.text(
|
||||||
doc.text(`Kaution: ${formatEuro(contract.depositAmount)}.`);
|
contract.endDate
|
||||||
doc.moveDown(1);
|
? `Es endet, ohne dass es einer Kündigung bedarf, am ${formatDate(contract.endDate)}.`
|
||||||
|
: 'Es läuft auf unbestimmte Zeit.',
|
||||||
|
);
|
||||||
|
doc.text(
|
||||||
|
`Die ordentliche Kündigungsfrist beträgt ${contract.noticePeriodMonths} Monate zum Ende eines ` +
|
||||||
|
'Kalendermonats. Die Kündigung bedarf der Schriftform. Das Recht zur außerordentlichen ' +
|
||||||
|
'Kündigung aus wichtigem Grund bleibt unberührt.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
doc.fontSize(13).text('§ 4 Ausstattung', { underline: true });
|
// --- § 4 Miete und Nebenkosten ---
|
||||||
doc.fontSize(11).moveDown(0.3);
|
section('§ 4 Miete und Nebenkosten', () => {
|
||||||
doc.text(`WLAN: ${contract.wifiIncluded ? 'im Mietpreis inbegriffen.' : 'nicht im Mietpreis enthalten.'}`);
|
doc.text(`Die Warmmiete beträgt ${formatEuro(contract.totalWarmRent)} monatlich.`);
|
||||||
doc.text(`Zimmer: ${contract.furnished ? 'möbliert übergeben.' : 'unmöbliert übergeben.'}`);
|
doc.text(
|
||||||
doc.moveDown(1);
|
`Sie ist im Voraus, spätestens am ${contract.paymentDueDay}. Werktag eines jeden Monats, auf das ` +
|
||||||
|
'vom Vermieter benannte Konto zu entrichten.',
|
||||||
|
);
|
||||||
|
doc.text(UTILITY_LABEL[contract.utilityBillingModel]);
|
||||||
|
});
|
||||||
|
|
||||||
doc.fontSize(13).text('§ 5 Mietanpassung', { underline: true });
|
// --- § 5 Kaution ---
|
||||||
doc.fontSize(11).moveDown(0.3);
|
section('§ 5 Kaution', () => {
|
||||||
doc.text(ADJUSTMENT_LABEL[contract.rentAdjustmentType]);
|
doc.text(
|
||||||
if (contract.rentAdjustmentClause) doc.text(contract.rentAdjustmentClause);
|
`Der Mieter leistet eine Mietsicherheit (Kaution) in Höhe von ${formatEuro(contract.depositAmount)}. ` +
|
||||||
doc.moveDown(2);
|
'Die Kaution wird getrennt vom Vermögen des Vermieters angelegt und nach Beendigung des ' +
|
||||||
|
'Mietverhältnisses und ordnungsgemäßer Rückgabe des Mietobjekts abzüglich etwaiger berechtigter ' +
|
||||||
|
'Forderungen des Vermieters zurückerstattet.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- § 6 Instandhaltung und Schönheitsreparaturen ---
|
||||||
|
section('§ 6 Instandhaltung und Schönheitsreparaturen', () => {
|
||||||
|
doc.text(
|
||||||
|
'Der Mieter hat das Mietobjekt pfleglich zu behandeln und Schäden, die über die vertragsgemäße ' +
|
||||||
|
'Abnutzung hinausgehen, dem Vermieter unverzüglich anzuzeigen. Schäden, die durch den Mieter, ' +
|
||||||
|
'seine Besucher oder von ihm veranlasste Dritte schuldhaft verursacht wurden, sind vom Mieter zu ' +
|
||||||
|
'ersetzen. Für die Ausführung von Schönheitsreparaturen gelten die gesetzlichen Regelungen, ' +
|
||||||
|
'soweit hierzu keine gesonderte, wirksame Vereinbarung getroffen wurde.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- § 7 Hausordnung und Gemeinschaftsleben ---
|
||||||
|
section('§ 7 Hausordnung und Gemeinschaftsleben', () => {
|
||||||
|
doc.text(
|
||||||
|
'Der Mieter verpflichtet sich, die geltende Hausordnung sowie die innerhalb der Wohngemeinschaft ' +
|
||||||
|
'einvernehmlich abgestimmten Regelungen (u. a. Putzplan) einzuhalten und auf die übrigen ' +
|
||||||
|
'Bewohner Rücksicht zu nehmen. Die aktuelle Hausordnung ist im Dokumenten-Safe der ' +
|
||||||
|
'Verwaltungs-App hinterlegt.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- § 8 Tierhaltung und Untervermietung ---
|
||||||
|
section('§ 8 Tierhaltung und Untervermietung', () => {
|
||||||
|
doc.text(
|
||||||
|
'Die Haltung von Haustieren sowie die vollständige oder teilweise Untervermietung/Gebrauchsüberlassung ' +
|
||||||
|
'des Mietobjekts an Dritte bedürfen der vorherigen schriftlichen Zustimmung des Vermieters. Ein ' +
|
||||||
|
'gesetzlicher Anspruch auf Erteilung der Erlaubnis bleibt hiervon unberührt.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- § 9 Mietanpassung ---
|
||||||
|
section('§ 9 Mietanpassung', () => {
|
||||||
|
doc.text(ADJUSTMENT_LABEL[contract.rentAdjustmentType]);
|
||||||
|
if (contract.rentAdjustmentClause) doc.text(contract.rentAdjustmentClause);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- § 10 Sonstige Vereinbarungen ---
|
||||||
|
section('§ 10 Sonstige Vereinbarungen', () => {
|
||||||
|
doc.text(
|
||||||
|
'Änderungen und Ergänzungen dieses Vertrags bedürfen der Schriftform. Dies gilt auch für die ' +
|
||||||
|
'Aufhebung des Schriftformerfordernisses selbst. Sollte eine Bestimmung dieses Vertrags unwirksam ' +
|
||||||
|
'sein oder werden, bleibt die Wirksamkeit der übrigen Bestimmungen hiervon unberührt.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Unterschriften ---
|
||||||
|
doc.moveDown(1.4);
|
||||||
|
H1().text('Ort, Datum');
|
||||||
|
BODY().moveDown(0.2).text('Nackenheim, ' + formatDate(contract.signedAt || new Date()));
|
||||||
|
doc.moveDown(1.2);
|
||||||
|
|
||||||
|
const sigTop = doc.y;
|
||||||
|
doc.moveTo(56, sigTop + 40).lineTo(260, sigTop + 40).stroke();
|
||||||
|
doc.moveTo(320, sigTop + 40).lineTo(524, sigTop + 40).stroke();
|
||||||
|
doc.fontSize(9).text('Vermieter', 56, sigTop + 44, { width: 204 });
|
||||||
|
doc.text('Mieter', 320, sigTop + 44, { width: 204 });
|
||||||
|
|
||||||
if (contract.signedAt && contract.tenantSignatureUrl) {
|
if (contract.signedAt && contract.tenantSignatureUrl) {
|
||||||
doc.fontSize(13).text('§ 6 Unterschrift', { underline: true });
|
|
||||||
doc.fontSize(11).moveDown(0.3);
|
|
||||||
doc.text(`Digital unterschrieben von ${contract.user.fullName} am ${formatDate(contract.signedAt)}.`);
|
|
||||||
doc.moveDown(0.5);
|
|
||||||
const match = /^data:image\/(png|jpeg);base64,(.+)$/.exec(contract.tenantSignatureUrl);
|
const match = /^data:image\/(png|jpeg);base64,(.+)$/.exec(contract.tenantSignatureUrl);
|
||||||
if (match) {
|
if (match) {
|
||||||
try {
|
try {
|
||||||
doc.image(Buffer.from(match[2], 'base64'), { width: 200 });
|
doc.image(Buffer.from(match[2], 'base64'), 320, sigTop - 36, { width: 180 });
|
||||||
} catch {
|
} catch {
|
||||||
doc.text('(Unterschrift-Bild konnte nicht eingebettet werden)');
|
/* Unterschrift-Bild ungültig — Zeile bleibt leer, Signatur-Vermerk unten reicht als Beleg. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
doc.fontSize(9).fillColor('#555555').text(
|
||||||
doc.fontSize(11).text('Noch nicht unterschrieben.');
|
`Digital unterschrieben von ${contract.user.fullName} am ${formatDate(contract.signedAt)}.`,
|
||||||
|
56,
|
||||||
|
sigTop + 60,
|
||||||
|
);
|
||||||
|
doc.fillColor('#000000');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Fußzeile mit Hinweis auf allen Seiten ---
|
||||||
|
const pageCount = doc.bufferedPageRange().count;
|
||||||
|
for (let i = 0; i < pageCount; i++) {
|
||||||
|
doc.switchToPage(i);
|
||||||
|
doc.fontSize(7.5).fillColor('#888888').text(
|
||||||
|
'Automatisiert aus den Angaben in der WG-Verwaltungs-App erstellte Vertragsvorlage — keine Rechtsberatung, ' +
|
||||||
|
`keine Gewähr für rechtliche Vollständigkeit. Seite ${i + 1} von ${pageCount}.`,
|
||||||
|
56,
|
||||||
|
doc.page.height - 40,
|
||||||
|
{ width: doc.page.width - 112, align: 'center' },
|
||||||
|
);
|
||||||
|
doc.fillColor('#000000');
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.end();
|
doc.end();
|
||||||
|
|||||||
@ -281,6 +281,9 @@ model Contract {
|
|||||||
wifiIncluded Boolean @default(false) @map("wifi_included")
|
wifiIncluded Boolean @default(false) @map("wifi_included")
|
||||||
furnished Boolean @default(false)
|
furnished Boolean @default(false)
|
||||||
utilityBillingModel UtilityBillingModel @default(PAUSCHALE) @map("utility_billing_model")
|
utilityBillingModel UtilityBillingModel @default(PAUSCHALE) @map("utility_billing_model")
|
||||||
|
landlordName String @default("Familie Lombardo") @map("landlord_name")
|
||||||
|
landlordAddress String? @map("landlord_address") // vom Vermieter selbst einzutragen, keine erfundene Anschrift
|
||||||
|
propertyAddress String? @map("property_address") // Anschrift der Wohnung/des Hauses
|
||||||
|
|
||||||
// Digitale Unterschrift des Mieters zum generierten Vertragsdokument
|
// Digitale Unterschrift des Mieters zum generierten Vertragsdokument
|
||||||
tenantSignatureUrl String? @map("tenant_signature_url") // Data-URL des gezeichneten Unterschrift-Bilds
|
tenantSignatureUrl String? @map("tenant_signature_url") // Data-URL des gezeichneten Unterschrift-Bilds
|
||||||
|
|||||||
@ -2849,6 +2849,35 @@
|
|||||||
function buildContractTemplateForm(c) {
|
function buildContractTemplateForm(c) {
|
||||||
const form = el('div', 'contract-template-form');
|
const form = el('div', 'contract-template-form');
|
||||||
|
|
||||||
|
const landlordNameField = el('div', 'field');
|
||||||
|
landlordNameField.style.minWidth = '180px';
|
||||||
|
landlordNameField.appendChild(el('label', null, 'Vermieter (Name)'));
|
||||||
|
const landlordNameInput = document.createElement('input');
|
||||||
|
landlordNameInput.type = 'text';
|
||||||
|
landlordNameInput.value = c.landlordName || '';
|
||||||
|
landlordNameField.appendChild(landlordNameInput);
|
||||||
|
form.appendChild(landlordNameField);
|
||||||
|
|
||||||
|
const landlordAddressField = el('div', 'field');
|
||||||
|
landlordAddressField.style.minWidth = '220px';
|
||||||
|
landlordAddressField.appendChild(el('label', null, 'Anschrift des Vermieters'));
|
||||||
|
const landlordAddressInput = document.createElement('input');
|
||||||
|
landlordAddressInput.type = 'text';
|
||||||
|
landlordAddressInput.placeholder = 'Straße Hausnr., PLZ Ort';
|
||||||
|
landlordAddressInput.value = c.landlordAddress || '';
|
||||||
|
landlordAddressField.appendChild(landlordAddressInput);
|
||||||
|
form.appendChild(landlordAddressField);
|
||||||
|
|
||||||
|
const propertyAddressField = el('div', 'field');
|
||||||
|
propertyAddressField.style.minWidth = '220px';
|
||||||
|
propertyAddressField.appendChild(el('label', null, 'Anschrift der Wohnung'));
|
||||||
|
const propertyAddressInput = document.createElement('input');
|
||||||
|
propertyAddressInput.type = 'text';
|
||||||
|
propertyAddressInput.placeholder = 'Straße Hausnr., PLZ Nackenheim';
|
||||||
|
propertyAddressInput.value = c.propertyAddress || '';
|
||||||
|
propertyAddressField.appendChild(propertyAddressInput);
|
||||||
|
form.appendChild(propertyAddressField);
|
||||||
|
|
||||||
const wifiField = el('label', 'field-checkbox');
|
const wifiField = el('label', 'field-checkbox');
|
||||||
const wifiCheckbox = document.createElement('input');
|
const wifiCheckbox = document.createElement('input');
|
||||||
wifiCheckbox.type = 'checkbox';
|
wifiCheckbox.type = 'checkbox';
|
||||||
@ -2926,6 +2955,9 @@
|
|||||||
rentAdjustmentType: adjustmentSelect.value,
|
rentAdjustmentType: adjustmentSelect.value,
|
||||||
noticePeriodMonths: noticeInput.value,
|
noticePeriodMonths: noticeInput.value,
|
||||||
depositAmount: depositInput.value,
|
depositAmount: depositInput.value,
|
||||||
|
landlordName: landlordNameInput.value,
|
||||||
|
landlordAddress: landlordAddressInput.value,
|
||||||
|
propertyAddress: propertyAddressInput.value,
|
||||||
}, generateBtn);
|
}, generateBtn);
|
||||||
form.appendChild(generateBtn);
|
form.appendChild(generateBtn);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user