Abrechnung Generierung
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
12
src/generated/zod/provisionen.ts
Normal file
12
src/generated/zod/provisionen.ts
Normal 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(),
|
||||
})
|
||||
@@ -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, {
|
||||
|
||||
@@ -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>
|
||||
|
||||
136
src/pages/dashboard/abrechnung/monatlich.pdf.astro
Normal file
136
src/pages/dashboard/abrechnung/monatlich.pdf.astro
Normal 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",
|
||||
},
|
||||
});
|
||||
---
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
84
src/templates/pdf/abrechnung.handlebars
Normal file
84
src/templates/pdf/abrechnung.handlebars
Normal 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>
|
||||
Reference in New Issue
Block a user