Abrechnung Generierung

This commit is contained in:
Moritz Utcke
2025-08-04 17:23:04 -04:00
parent 3096f05f67
commit f56684a6e8
21 changed files with 459 additions and 115 deletions

View File

@@ -12,25 +12,25 @@ export const createCaller = createCallerFactory({
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
"geg-nachweis-gewerbe/[id]": await import("../src/pages/api/geg-nachweis-gewerbe/[id].ts"),
"geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"),
"geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"),
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),

View File

@@ -1,13 +1,14 @@
<script lang="ts">
import { Aufnahme, BedarfsausweisWohnen, Enums, Objekt, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import { Aufnahme, BedarfsausweisWohnen, Enums, Objekt, Provisionen, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment";
import { DatePicker } from "@svelte-plugins/datepicker"
export let bestellungen: (Rechnung & {
ausweis: (VerbrauchsausweisWohnen | BedarfsausweisWohnen | VerbrauchsausweisGewerbe) & { aufnahme: Aufnahme & { objekt: Objekt }}
})[];
export let provisionen: Record<Enums.Ausweisart, number>;
export let partnerCodeErstesMal: Date;
export let provisionen: Provisionen[];
export let email: string;
export let startdatum: Date;
export let enddatum: Date;
const bestellungenNachMonat: Record<string, (typeof bestellungen)> = {};
for (const bestellung of bestellungen) {
@@ -26,11 +27,9 @@
"09": "September", "10": "Oktober", "11": "November", "12": "Dezember"
};
function getMonthlyPeriods(minTime?: Date): moment.Moment[] {
const min = minTime ? moment(minTime) : moment();
const start = min.clone().startOf('month');
const end = moment().add(1, 'month').startOf('month');
function getMonthlyPeriods(from: Date, to: Date): moment.Moment[] {
const start = moment(from).startOf('month');
const end = moment(to).endOf('month');
const monthsArray: moment.Moment[] = [];
const current = start.clone();
@@ -43,14 +42,12 @@
return monthsArray.reverse(); // Most recent month first
}
const periods = getMonthlyPeriods(partnerCodeErstesMal)
const periods = getMonthlyPeriods(startdatum, enddatum)
let isOpen = false;
export let startDate = moment(partnerCodeErstesMal).startOf('month').toDate();
export let endDate = moment().endOf('month').toDate();
$: formattedStartDate = moment(startDate).format("DD.MM.YYYY");
$: formattedEndDate = moment(endDate).format("DD.MM.YYYY");
$: formatiertesStartDatum = moment(startdatum).format("DD.MM.YYYY");
$: formatiertesEndDatum = moment(enddatum).format("DD.MM.YYYY");
function toggleDatePicker() {
isOpen = !isOpen;
}
@@ -62,14 +59,14 @@
<div class="fixed top-0 left-0 right-0 bg-white p-4 shadow z-10">
<div class="flex justify-between items-center">
<DatePicker bind:isOpen bind:startDate bind:endDate isRange={true} onDateChange={onChange} isMultipane={true}>
<input type="text" class="w-min" readonly value={`${formattedStartDate} - ${formattedEndDate}`} on:click={toggleDatePicker} />
<DatePicker bind:isOpen bind:startDate={startdatum} bind:endDate={enddatum} isRange={true} onDateChange={onChange} isMultipane={true}>
<input type="text" class="w-min" readonly value={`${formatiertesStartDatum} - ${formatiertesEndDatum}`} on:click={toggleDatePicker} />
</DatePicker>
<p>Abrechnungsübersicht für <strong>{email}</strong></p>
</div>
</div>
<main class="my-24 flex justify-center max-w-6xl mx-auto px-4">
<main class="my-24 flex flex-col justify-center max-w-6xl mx-auto px-4">
{#if !bestellungen || bestellungen.length === 0}
<p class="text-center text-gray-500">Keine Bestellungen gefunden.</p>
{/if}
@@ -78,44 +75,60 @@
{#if jahrMonat in bestellungenNachMonat && bestellungenNachMonat[jahrMonat].length > 0}
<!-- Echo dropdown foreach month. -->
{@const provisionMonat = bestellungenNachMonat[jahrMonat].reduce((acc, bestellung) => {
return acc + provisionen[bestellung.ausweis.ausweisart] || 0;
return acc + (provisionen.find((p) => p.ausweisart === bestellung.ausweis.ausweisart)?.provision_betrag || 0);
}, 0) * 1.19}
<!-- <div onclick="$(this).nextUntil('.dropdown_month').filter('table').toggle(); $('#betrag_gesamt').html('Abrechnungsbetrag $month_name: <b>$provision_month €</b>')" class='dropdown_month'>
<p>$month_name $year_name - Klicke, um Tabelle anzuzeigen</p>
<a target='_blank' rel='noreferrer noopener' href='/user/abrechnung/pdf.php?month={dt.format("m")}&year={dt.format("Y")}'>PDF Ansehen</a>
</div> -->
<table class="w-full mb-4 border-collapse border border-gray-300">
<thead>
<tr class="bg-primary text-white">
<td class="text-center font-bold">ID</td>
<td class="text-center font-bold">DATUM</td>
<td class="text-center font-bold">GEBÄUDEADRESSE </td>
<td class="text-center font-bold">PLZ </td>
<td class="text-center font-bold">ORT </td>
<td class="text-center font-bold">AUSWEIS</td>
<td class="text-center font-bold w-48">BETRAG NETTO</td>
</tr>
</thead>
<tbody class="text-sm">
<tr class="bg-secondary text-white">
<td class="text-center font-bold" colspan="6">{months[dt.format("MM")]} {dt.format("YYYY")}</td>
<td class="text-right font-bold w-48" style="font-family: monospace;">{provisionMonat.toFixed(2)}</td>
</tr>
</tbody>
{#each bestellungenNachMonat[jahrMonat] as bestellung}
{@const provisionBestellung = provisionen[bestellung.ausweis.ausweisart] || 0}
<tr>
<td class="text-center px-4 w-24" style="font-family: monospace;">{bestellung.id}</td>
<td class="text-center font-bold w-32">{moment(bestellung.created_at).format("DD.MM.YYYY")}</td>
<td class="text-left w-64">{bestellung.ausweis.aufnahme.objekt.adresse}</td>
<td class="text-center w-16">{bestellung.ausweis.aufnahme.objekt.plz}</td>
<td class="text-left w-64">{bestellung.ausweis.aufnahme.objekt.ort}</td>
<td class="text-center w-32">{bestellung.ausweis.ausweisart}</td>
<td class="text-right w-48" style="font-family: monospace;">{provisionBestellung.toFixed(2)}</td>
</tr>
{/each}
</table>
<details class="group" open>
<summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
<div class="flex flex-row gap-4 items-center">
<span class="text-gray-500">{provisionMonat.toFixed(2)}</span>
<svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</summary>
<div class="p-4">
<table class="w-full mb-4 border-collapse border border-gray-300">
<thead>
<tr class="bg-primary text-white w-full">
<td class="text-center p-2 font-bold">ID</td>
<td class="text-center p-2 font-bold">Datum</td>
<td class="text-center p-2 font-bold">Ausweis</td>
<td class="text-center p-2 font-bold">Provision in %</td>
<td class="text-center p-2 font-bold">Betrag Netto</td>
</tr>
</thead>
<tbody class="text-sm">
{#each bestellungenNachMonat[jahrMonat] as bestellung}
{@const provisionBestellung = provisionen.find((p) => p.ausweisart === bestellung.ausweis.ausweisart)}
<tr class="border-b border-gray-300 hover:bg-gray-100">
<td class="text-center py-2 px-4 w-24" style="font-family: monospace;">{bestellung.ausweis.id}</td>
<td class="text-center py-2 font-bold w-32">{moment(bestellung.created_at).format("DD.MM.YYYY HH:mm")}</td>
<td class="text-center py-2 w-32">{bestellung.ausweis.ausweisart}</td>
<td class="text-center py-2 w-32">{provisionBestellung?.provision_prozent || 0} %</td>
<td class="text-right py-2 w-24" style="font-family: monospace;">{provisionBestellung?.provision_betrag.toFixed(2)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</details>
{:else if !bestellungenNachMonat[jahrMonat] || bestellungenNachMonat[jahrMonat].length === 0}
<details class="group">
<summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
<div class="flex flex-row gap-4 items-center">
<span class="text-gray-500">Keine Bestellungen gefunden</span>
<svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</summary>
<div class="p-4 border-t border-gray-200">
<p class="text-gray-500">Für diesen Monat liegen uns keine Bestellungen über ihren Resellercode vor.</p>
</div>
</details>
{/if}
{/each}
</main>

View File

@@ -5,7 +5,7 @@
CaretDown,
MagnifyingGlass,
} from "radix-svelte-icons";
import { Benutzer } from "#lib/server/prisma.js";
import { Benutzer, Enums } from "#lib/server/prisma.js";
export let lightTheme: boolean;
export let benutzer: Benutzer;
@@ -70,7 +70,9 @@
</div>
</div>
{/if}
<!-- <a href="/dashboard/abrechnung" class="button ">Monatliche Abrechnung</a> -->
{#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<a href="/dashboard/abrechnung" class="button ">Monatliche Abrechnung</a>
{/if}
</div>
<hr class="border-gray-600" />

View File

@@ -12,7 +12,7 @@
email: email,
metadata: {
category: category,
phone: phone,
telefon: telefon,
},
titel: title,
})
@@ -28,7 +28,7 @@
let title = "";
let description = "";
let email = "";
let phone = "";
let telefon = "";
</script>
<form class="max-w-lg" on:submit={createTicket}>
@@ -89,9 +89,9 @@
<input
class="input input-bordered"
type="tel"
placeholder="Ihre Telefonnumer"
name="phone"
bind:value={phone}
placeholder="Ihre Telefonnummer"
name="telefon"
bind:value={telefon}
required
/>
</div>

View File

@@ -19,6 +19,7 @@ export type Lueftungskonzept = (typeof Lueftungskonzept)[keyof typeof Lueftungsk
export const BenutzerRolle = {
USER: "USER",
ADMIN: "ADMIN",
RESELLER: "RESELLER",
} as const;
export type BenutzerRolle = (typeof BenutzerRolle)[keyof typeof BenutzerRolle];

View File

@@ -38,19 +38,19 @@ export const BedarfsausweisWohnenSchema = z.object({
volumen: z.number().nullish(),
dicht: z.boolean().nullish(),
fenster_flaeche_1: z.number().nullish(),
fenster_art_1: z.string().nullish(),
fenster_art_1: z.number().nullish(),
fenster_flaeche_2: z.number().nullish(),
fenster_art_2: z.string().nullish(),
fenster_art_2: z.number().nullish(),
dachfenster_flaeche: z.number().nullish(),
dachfenster_art: z.string().nullish(),
dachfenster_art: z.number().nullish(),
haustuer_flaeche: z.number().nullish(),
haustuer_art: z.string().nullish(),
haustuer_art: z.number().nullish(),
dach_bauart: z.string().nullish(),
decke_bauart: z.string().nullish(),
dach_daemmung: z.string().nullish(),
decke_daemmung: z.string().nullish(),
aussenwand_daemmung: z.string().nullish(),
boden_daemmung: z.string().nullish(),
dach_daemmung: z.number().nullish(),
decke_daemmung: z.number().nullish(),
aussenwand_daemmung: z.number().nullish(),
boden_daemmung: z.number().nullish(),
aussenwand_bauart: z.string().nullish(),
boden_bauart: z.string().nullish(),
warmwasser_verteilung: z.string().nullish(),

View File

@@ -12,6 +12,7 @@ export * from "./gegnachweiswohnen"
export * from "./klimafaktoren"
export * from "./objekt"
export * from "./postleitzahlen"
export * from "./provisionen"
export * from "./rechnung"
export * from "./refreshtokens"
export * from "./tickets"

View File

@@ -0,0 +1,12 @@
import * as z from "zod"
import { Ausweisart } from "@prisma/client"
export const ProvisionenSchema = z.object({
id: z.number().int(),
ausweisart: z.nativeEnum(Ausweisart),
provision_prozent: z.number(),
provision_betrag: z.number(),
benutzer_id: z.string().nullish(),
created_at: z.date(),
updated_at: z.date(),
})

View File

@@ -50,15 +50,15 @@ export const PUT = defineApiRoute({
const url = new URL("https://api.trello.com/1/cards")
url.searchParams.append("name", input.titel)
url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nCategory: ${category}`)
url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nKategorie: ${category}\n\nTelefon: ${(input.metadata as Record<string, string>).telefon || "Nicht angegeben"}`)
url.searchParams.append("pos", "top")
url.searchParams.append("idLabels",
(category &&
labels[category as keyof typeof labels]) ||
"650e909fdc09629a4d6d495d")
url.searchParams.append("key", "e057eb39018368ea96e456c753ac41b4")
url.searchParams.append("idList", "650303186e721b4bef0c3980")
url.searchParams.append("token", "ATTA8b65b3587ab627167038cc32a3460650973eb181cde01dabb208ca1e90ed5467AC06A4F2")
url.searchParams.append("idList", "67d75ca7403fd22c49bc7447")
url.searchParams.append("token", "ATTA6f1774d98472db1897a2373ee7b55ab15f218c2445b6609dfef3071fe5203a90DB15678A")
// Wir laden das Ticket zu Trello hoch.
const result = await fetch(url, {

View File

@@ -18,12 +18,6 @@ if (!benutzer) {
return Astro.redirect("/404");
}
const provisionen = {
[Enums.Ausweisart.VerbrauchsausweisWohnen]: 10,
[Enums.Ausweisart.BedarfsausweisWohnen]: 10,
[Enums.Ausweisart.VerbrauchsausweisGewerbe]: 10,
};
// $kommission = db()->one("SELECT abr_va, abr_ba, abr_vanw FROM users WHERE resellercode = :resellercode", ["resellercode" => $resellercode]);
// Select every entry from database where user was involved.
let bestellungen;
@@ -152,12 +146,13 @@ if (start.isValid() && end.isValid()) {
}
// Wann wurde der partner_code zum ersten mal benutzt?
const partnerCodeErstesMal = (
await prisma.rechnung.findFirst({
select: {
created_at: true,
},
where: {
if (!startdatum) {
startdatum = (
await prisma.rechnung.findFirst({
select: {
created_at: true,
},
where: {
partner_code: "immowelt",
OR: [
{
@@ -184,22 +179,30 @@ const partnerCodeErstesMal = (
created_at: "asc",
},
})
)?.created_at;
)?.created_at || moment().startOf("month").toDate();
}
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
let provision = 0;
const ausweisarten: string[] = [];
for (const bestellung of bestellungen) {
if (bestellung.verbrauchsausweis_wohnen) {
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisWohnen);
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisWohnen];
provision += provisionen.find((p) => p.ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen)?.provision_betrag || 0;
}
if (bestellung.bedarfsausweis_wohnen) {
ausweisarten.push(Enums.Ausweisart.BedarfsausweisWohnen);
provision += provisionen[Enums.Ausweisart.BedarfsausweisWohnen];
provision += provisionen.find((p) => p.ausweisart === Enums.Ausweisart.BedarfsausweisWohnen)?.provision_betrag || 0;
}
if (bestellung.verbrauchsausweis_gewerbe) {
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisGewerbe);
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisGewerbe];
provision += provisionen.find((p) => p.ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe)?.provision_betrag || 0;
}
}
---
@@ -210,9 +213,8 @@ for (const bestellung of bestellungen) {
extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung)
)}
{provisionen}
{partnerCodeErstesMal}
startDate={startdatum}
endDate={enddatum}
startdatum={startdatum}
enddatum={enddatum}
email={benutzer.email}
client:load
/>
@@ -220,15 +222,15 @@ for (const bestellung of bestellungen) {
<div class="fixed bottom-0 left-0 right-0 bg-white p-4 shadow">
<div class="flex justify-between items-center">
<div>
<p id="betrag_gesamt">
Abrechnungsbetrag gesamt: <b>{provision} €</b>
<p>
Abrechnungsbetrag gesamt: <b>{provision.toFixed(2)} €</b>
</p>
</div>
<a
target="_blank"
rel="noreferrer noopener"
class="bg-secondary text-white px-4 py-2 rounded-lg hover:bg-secondary-focus"
href=`/user/abrechnung/pdf.php?month=${moment().subtract(1, "month").get("month")}&year=${moment().subtract(1, "month").get("year")}`
href=`/dashboard/abrechnung/monatlich.pdf?d=${moment().subtract(1, "month").format("YYYY-MM")}`
>PDF für letzten Monat generieren.</a
>
</div>

View File

@@ -0,0 +1,136 @@
---
import abrechnungTemplateHTML from "../../../templates/pdf/abrechnung.handlebars?raw";
import puppeteer from "puppeteer";
import Handlebars from "handlebars";
import moment from "moment";
import { getCurrentUser } from "#lib/server/user";
import { prisma } from "#lib/server/prisma";
import { extrahiereAusweisAusFeldMitMehrerenAusweisen } from "#lib/server/ausweis";
const datum = moment(Astro.url.searchParams.get("d"));
const benutzer = await getCurrentUser(Astro);
if (!benutzer) {
return Astro.redirect("/404");
}
let bestellungen = await prisma.rechnung.findMany({
where: {
partner_code: "immowelt",
OR: [
{
verbrauchsausweis_gewerbe: {
ausgestellt: true,
},
},
{
bedarfsausweis_wohnen: {
ausgestellt: true,
},
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true,
},
},
],
AND: [
{
created_at: {
gte: datum.startOf("month").toDate(),
},
},
{
created_at: {
lte: datum.endOf("month").toDate(),
},
},
],
},
orderBy: {
created_at: "desc",
},
include: {
bedarfsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_gewerbe: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
},
});
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
const ausweisBestellungen = bestellungen.map(bestellung => extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung));
console.log(ausweisBestellungen);
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
// Wir splitten die Daten in Blöcke auf, der erste Block hat 11 Einträge, die folgenden Blöcke haben 15 Einträge.
const blocks = [];
const firstBlock = ausweisBestellungen.slice(0, 16);
const remainingBlocks = ausweisBestellungen.slice(16);
blocks.push(firstBlock);
for (let i = 0; i < remainingBlocks.length; i += 20) {
blocks.push(remainingBlocks.slice(i, i + 20));
}
Handlebars.registerHelper("get-provision-prozent", function (ausweisart) {
const provisionEintrag = provisionen.find((p) => p.ausweisart === ausweisart);
return provisionEintrag ? provisionEintrag.provision_prozent : 0;
});
Handlebars.registerHelper("get-provision-betrag", function (ausweisart) {
const provisionEintrag = provisionen.find((p) => p.ausweisart === ausweisart);
return provisionEintrag ? provisionEintrag.provision_betrag.toFixed(2) : "0.00";
});
const template = Handlebars.compile(abrechnungTemplateHTML);
const html = template({ monat: datum.format("MMMM YYYY"), bestellungen: blocks });
await page.goto(`data:text/html;charset=UTF-8,${encodeURIComponent(html)}`, {
waitUntil: "networkidle0",
});
const pdf = await page.pdf({ path: "abrechnung.pdf", format: "A4" });
await browser.close();
return new Response(pdf, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=abrechnung.pdf",
},
});
---

View File

@@ -23,7 +23,7 @@ const totalPageCount = await prisma.aufnahme.count({
: {},
});
if (page < 1 || page > totalPageCount) {
if (page < 1 || page > totalPageCount && totalPageCount > 0) {
return Astro.redirect("/dashboard/objekte?p=1");
}

View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abrechnung</title>
<link href="https://unpkg.com/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
{{#each bestellungen}}
<header class="p-12 flex flex-row items-center justify-between" style="border-bottom: 12px #f37e3c solid;">
<img src="https://online-energieausweis.org/images/header/logo-big.png" alt="" class="h-24">
<div class="flex flex-col">
<p>fon 040 · 209339850</p>
<p>fax 040 · 209339859</p>
<p class="font-semibold">online-energieausweis.org</p>
</div>
</header>
<article class="py-8 px-16">
{{#if @first}}
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<p class="text-sm">IB Cornelsen · Katendeich 5A · 21035 Hamburg</p>
</div>
<div class="flex flex-col">
<p>Immowelt GmbH</p>
<p>Nordostpark 3-5</p>
<p>90411 Nürnberg</p>
</div>
</div>
{{/if}}
<div class="flex flex-col gap-4 mt-12">
{{#if @first}}
<div class="flex flex-row justify-between items-center">
<p class="font-semibold">Erzielte Conversions {{ @root.monat }}</p>
<p>Erstellt am 16.11.23</p>
</div>
{{/if}}
<table class="table border-collapse border border-black">
<thead>
<tr class="h-16">
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">ID - Datum</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produkt</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produktpreis</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Provision in %</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Betrag Netto</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
{{#with ausweis}}
<td class="border-black border p-1">
<p class="text-sm">{{ id }}</p>
<p class="text-sm">{{ createdAt }}</p>
</td>
<td class=" border-black border p-1 text-sm text-center">
{{ ausweisart }}
</td>
{{/with}}
<td class=" border-black border p-1 font-semibold text-sm text-center">
{{ betrag }}
</td>
{{#with ausweis}}
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-prozent ausweisart}} %
</td>
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-betrag ausweisart}}
</td>
{{/with}}
</tr>
{{/each}}
</tbody>
</table>
</div>
</article>
<footer class="px-16 py-6 flex flex-row justify-between items-center fixed bottom-0 left-0 w-full" style="border-top: 12px #f37e3c solid;">
<p class="font-semibold">Copyright © 2018 · IB Cornelsen</p>
<p class="font-semibold">info@online-energieausweis.org</p>
</footer>
{{/each}}
</body>
</html>