diff --git a/.gitignore b/.gitignore index 1612285..0e72f66 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ vite.config.ts.timestamp-* # generated files pdfgen/* +!pdfgen/template*.tex + +documents/* diff --git a/bun.lockb b/bun.lockb index 9d988b5..800ba34 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index baa9555..2285bc0 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,16 @@ "name": "stundenaufzeichnung", "version": "0.0.1", "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/adapter-node": "^5.2.11", - "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/adapter-auto": "^3.3.1", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.16.1", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@types/sqlite3": "^3.1.11", - "svelte": "^5.0.0", + "svelte": "^5.19.6", "svelte-adapter-bun": "^0.5.2", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "vite": "^6.0.0" + "svelte-check": "^4.1.4", + "typescript": "^5.7.3", + "vite": "^6.0.11" }, "private": true, "scripts": { diff --git a/pdfgen/template-est.tex b/pdfgen/template-est.tex new file mode 100644 index 0000000..230d298 --- /dev/null +++ b/pdfgen/template-est.tex @@ -0,0 +1,101 @@ +\documentclass[a4paper,11pt,oneside]{article} +\usepackage[left=2.5cm,top=3cm,right=2.5cm,bottom=3cm]{geometry} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} + +\usepackage[style=austrian]{csquotes} +\usepackage{csvsimple} +\usepackage{etoolbox} + +%\newcommand{\Anrede}{Herrn} +%\newcommand{\Name}{Patrick Maschek} +%\newcommand{\Addresse}{2231 Strasshof, Ganghoferstra{\ss}e 16} + +\newcommand{\csvext}{.csv} +%\newcommand{\datafile}{\jobname\csvext} +\newcommand{\datafile}{estimate.csv} + +\newcommand{\listitemend}{,} +\newcommand{\listend}{.} + +\setlength\parindent{0pt} + +\begin{document} +\pagestyle{empty} + +\begin{center} + \Large\bfseries + \underline{\underline{VEREINBARUNG ARBEITSBEDARF}}\\[\baselineskip] + \underline{\underline{FÜR KALENDERVIERTELJAHR}}\\[\baselineskip] +\end{center} + +\bigskip + +zwischen + +\bigskip + +Tanzschule Elmayer Vestenbrugg-GmbH\newline +1010 Wien, Bräunerstraße 13 + +\bigskip + +im Folgenden \enquote{Arbeitgeberin} + +\bigskip\bigskip + +und + +\bigskip + +\csvreader[% + separator=semicolon, + no head, + filter test=\ifnumless{\thecsvinputline}{2}] + {\datafile}{1=\Name,2=\Addresse,3=\Arbeitnehmer,4=\Mitarbeiterrolle}{% + + \Name + + \bigskip + + wohnhaft in \Addresse + + \bigskip + + im Folgenden \enquote{\Arbeitnehmer} (\Mitarbeiterrolle) +} + +\bigskip + +wird vereinbart: + +\bigskip + +\csvreader[% + separator=semicolon, + no head, + late after line=\listitemend\\, + late after last line=\listend\\, + filter test=\ifnumgreater{\thecsvinputline}{1}] + {\datafile}{1=\Monat,2=\Schaetzung}{% + Für den Monat {\Monat} werden \textbf{\Schaetzung} vereinbart% +} + +\csvreader[% + separator=semicolon, + no head, + filter test=\ifnumless{\thecsvinputline}{2}] + {\datafile}{3=\Arbeitnehmer,5=\Datum}{% + + Datum: \Datum + + \vspace{2cm} + + \noindent\begin{tabular}{p{0.5\linewidth} p{0.5\linewidth}} + (Arbeitgeberin) & (\Arbeitnehmer) + \end{tabular} +} + + + +\end{document} diff --git a/pdfgen/template.tex b/pdfgen/template-rec.tex similarity index 93% rename from pdfgen/template.tex rename to pdfgen/template-rec.tex index d2d7b1b..8ce2c32 100644 --- a/pdfgen/template.tex +++ b/pdfgen/template-rec.tex @@ -1,12 +1,15 @@ \documentclass[a4paper,oneside]{article} \usepackage[left=2.5cm,top=3cm,right=2.5cm,bottom=3cm]{geometry} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} \usepackage{tabularx} \usepackage{array} \usepackage{csvsimple} \usepackage{etoolbox} -\newcommand{\datafile}{\jobname} +\newcommand{\csvext}{.csv} +\newcommand{\datafile}{\jobname\csvext} \begin{document} \pagestyle{empty} @@ -36,7 +39,7 @@ \noindent \begin{tabularx}{\textwidth}{ |>{\raggedleft\arraybackslash}p{2cm}% - |>{\raggedleft\arraybackslash}p{2cm}% + |>{\raggedright\arraybackslash}p{2cm}% |>{\raggedleft\arraybackslash}p{1.25cm}% |>{\raggedleft\arraybackslash}p{1.25cm}% |>{\raggedleft\arraybackslash}p{1.25cm}% diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 5e4d66b..052ffd8 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -28,6 +28,7 @@ process.on('SIGINT', (reason) => { process.exit(0); }) +import { getAllFiles } from "$lib/server/docstore" export async function handle({ event, resolve }) { diff --git a/src/lib/db_types.ts b/src/lib/db_types.ts index 821c0a4..53560bc 100644 --- a/src/lib/db_types.ts +++ b/src/lib/db_types.ts @@ -3,7 +3,9 @@ import { Database } from 'bun:sqlite'; export interface UserEntry { id: number; + gender: string; name: string; + address: string; initials: string; created: string; } diff --git a/src/lib/server/PDFGen.ts b/src/lib/server/PDFGen.ts index 853e5a3..6360341 100644 --- a/src/lib/server/PDFGen.ts +++ b/src/lib/server/PDFGen.ts @@ -1,10 +1,26 @@ import b from "bun"; import type { RecordEntry } from "$lib/db_types"; -import { toInt, padInt, parseDate, calculateDuration, month_of, weekday_of } from "$lib/util" +import { MONTHS, toInt, padInt, parseDate, calculateDuration, isoToLocalDate, month_of, weekday_of } from "$lib/util" import { User } from "$lib/server/database" +const SALUTATION = { + m: "Herrn", + w: "Frau" +} + +const GENDER_END = { + m: "", + w: "in", +} + +const TEMPLATE_LISTE = "template-liste.tex"; +const TEMPLATE_LISTE_BASE = "pdfgen/" + TEMPLATE_LISTE; + +const TEMPLATE_EST = "template-schaetzung.tex"; +const TEMPLATE_EST_BASE = "pdfgen/" + TEMPLATE_EST; + export async function generateRecordPDF(user: User, date: Date) { const year = date.getFullYear(); @@ -12,26 +28,37 @@ export async function generateRecordPDF(user: User, date: Date) { const dir = "pdfgen/user-" + user.id + "/"; const file_pref = "Stundenliste-" + year + "-" + padInt(month, 2, 0); + const template_path = dir + TEMPLATE_LISTE; const records = user.get_entries_by_month(year, month); - const estimate = user.get_estimate(year, month); + const estimate = user.get_estimate_by_month(year, month); const hr_sum = (() => { let s = 0; records.forEach((r) => { s += calculateDuration(r.start, r.end) }); return s; })() - const { exitCode } = await b.$`mkdir -p ${dir}`.nothrow().quiet(); + if (records.length == 0 || estimate == null || isNaN(estimate)) { + return; + } + + let exitCode = 0; + ({ exitCode: exitCode } = await b.$`mkdir -p ${dir}`.nothrow().quiet()); if (exitCode != 0) { return; } + + if (!await Bun.file(template_path).exists()) { + let { exitCode } = await b.$`cp ${TEMPLATE_LISTE_BASE} ${template_path}`.nothrow().quiet(); + if (exitCode != 0) { + console.warn(`Failed to copy pdf template liste to ${dir} (exit code: ${exitCode})`) + } + } // TODO: escape semicolon in comment - console.log(dir + file_pref + ".csv") + /*console.log(dir + file_pref + ".csv") console.log(`${user.name};${month_of(date)} ${year};${hr_sum.toFixed(2)};${estimate.toFixed(2)};${padInt(hr_sum - estimate, 2, 2, ' ')}`); records.forEach((record) => { console.log(`${record.date};${weekday_of(parseDate(record.date))};${record.start};${record.end};${calculateDuration(record.start, record.end).toFixed(2)};${record.comment}`) - }) - - return; + })*/ const csvfile = Bun.file(dir + file_pref + ".csv") const csvfilewriter = csvfile.writer() @@ -44,9 +71,43 @@ export async function generateRecordPDF(user: User, date: Date) { csvfilewriter.end(); - await b.$`pdflatex -jobname=${dir + file_pref} -output-format=pdf template.tex`; + ({ exitCode: exitCode } = await b.$`pdflatex -interaction=nonstopmode -halt-on-error -jobname=${file_pref} -output-format=pdf template-liste.tex`.cwd(dir).quiet().nothrow()); + } -function generateTable(path: string) { + +export async function generateEstimatePDF(user: User, year: number, quarter: number) { + + const dir = "pdfgen/user-" + user.id + "/"; + const file_pref = "Stundenschätzung-" + year + "-Q" + number; + const template_path = dir + TEMPLATE_EST; + + const estimates = user.get_estimate(year, quarter); + + let exitCode = 0; + ({ exitCode: exitCode } = await b.$`mkdir -p ${dir}`.nothrow().quiet()); + + if (exitCode != 0) { + return; + } + if (!await Bun.file(template_path).exists()) { + let { exitCode } = await b.$`cp ${TEMPLATE_EST_BASE} ${template_path}`.nothrow().quiet(); + if (exitCode != 0) { + console.warn(`Failed to copy pdf template estimate to ${dir} (exit code: ${exitCode})`) + } + } + + const csvfile = Bun.file(dir + file_pref + ".csv") + const csvfilewriter = csvfile.writer() + + csvfilewriter.write(`${SALUTATION[user.gender]} ${user.name};${user.address};Arbeitnehmer${GENDER_END[user.gender]};Teilzeitmitarbeiter${GENDER_END[user.gender]};${isoToLocalDate(new Date().toISOString())}\n`) + + for (let i = 0; i < 3; ++i) { + csvfilewriter.write(`${MONTHS[(quarter - 1) * 3 + i]} ${year};${estimates[`estimate_${i}`]}\n`); + } + + csvfilewriter.end(); + + ({ exitCode: exitCode } = await b.$`pdflatex -interaction=nonstopmode -halt-on-error -jobname=${file_pref} -output-format=pdf template-liste.tex`.cwd(dir).quiet().nothrow()); } diff --git a/src/lib/server/database.ts b/src/lib/server/database.ts index 172c8b2..5414120 100644 --- a/src/lib/server/database.ts +++ b/src/lib/server/database.ts @@ -1,18 +1,25 @@ import { mkdir } from "node:fs/promises"; import { Database, SQLiteError } from "bun:sqlite"; -import { UserEntry, RecordEntry } from "$lib/db_types"; -import { calculateDuration, parseDate, isTimeValidHHMM } from "$lib/util"; +import { UserEntry, RecordEntry, EstimatesEntry } from "$lib/db_types"; +import { calculateDuration, parseDate, toInt, isTimeValidHHMM } from "$lib/util"; -const DATABASES_PATH: string = ""; +const DATABASES_PATH: string = "./databases/"; const USER_DATABASE_PATH: string = DATABASES_PATH + "users.sqlite"; const CHECK_QUERY: string = "SELECT * FROM sqlite_master;"; const USER_DATABASE_SETUP: string[] = [ - "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT, initials TEXT, created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL);", -] + `CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + gender TEXT, + name TEXT, + address TEXT, + initials TEXT, + created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL + );`, +]; const USER_DATABASE_ADD_USER: string = "INSERT INTO users (name, initials) VALUES ($name, $initials);"; @@ -122,7 +129,7 @@ const ESTIMATES_DATABASE_GET_ALL: string = const ESTIMATES_DATABASE_GET_QUARTERS: string = "SELECT year, quarter FROM estimates;" -const ESTIMATES_DATABASE_GET_MONTH: string = +const ESTIMATES_DATABASE_GET_QUART: string = "SELECT estimate_0, estimate_1, estimate_2 FROM estimates WHERE year = $year AND quarter = $quarter;" const ESTIMATES_DATABASE_INSERT: string = @@ -130,13 +137,20 @@ const ESTIMATES_DATABASE_INSERT: string = export class User { id: number; + gender: string; name: string; + address: string; + initials: string; created: string; + private database: Database; constructor(user: UserEntry, db: Database) { this.id = user.id; + this.gender = user.gender; this.name = user.name; + this.address = user.address; + this.initials = user.initials; this.created = user.created; this._database = db; } @@ -145,7 +159,9 @@ export class User { const query = this._database.query(ENTRY_DATABASE_GET_MONTHS); const res = query.all(); - return res; + const ret = res.map((v) => { return { year: toInt(v.year), month: toInt(v.month) }}) + + return ret; } get_quarters(): { year: number, quarter: number }[] { @@ -218,9 +234,16 @@ export class User { return res; } + + get_estimate(year: number, quarter: number): EstimatesEntry { + const query = this._database.query(ESTIMATES_DATABASE_GET_QUART); + const res = query.get({ year: year, quarter: quarter }); - get_estimate(year: number, month: number): number { - const query = this._database.query(ESTIMATES_DATABASE_GET_MONTH); + return res; + } + + get_estimate_by_month(year: number, month: number): number { + const query = this._database.query(ESTIMATES_DATABASE_GET_QUART); const res = query.all({ year: year, quarter: Math.floor(month / 4 + 1) }); return res[0]?.[`estimate_${month % 3}`] ?? NaN; @@ -264,7 +287,7 @@ function get_user_db_name(user: UserEntry) { } function setup_db(db: Database, setup_queries: string[]) { - setup_queries.forEach((q) => { console.log(q); db.query(q).run(); }); + setup_queries.forEach((q) => { /*console.log(q);*/ db.query(q).run(); }); } export function init_db() { @@ -306,11 +329,6 @@ function _get_user(): UserEntry | null { const statement = user_database.prepare(USER_DATABASE_GET_USER); const result: UserEntry = statement.get(); - if (result == null) { - create_user("Patrick Maschek", "PM"); - return _get_user(); - } - return result; } catch (e) { diff --git a/src/lib/server/docstore.ts b/src/lib/server/docstore.ts new file mode 100644 index 0000000..63a9754 --- /dev/null +++ b/src/lib/server/docstore.ts @@ -0,0 +1,238 @@ +import b from "bun"; +import fs from "node:fs/promises" + +import type { RecordEntry } from "$lib/db_types"; +import { User } from "$lib/server/database"; +import { MONTHS, toInt, padInt, parseDate, calculateDuration, isoToLocalDate, month_of, weekday_of } from "$lib/util" + + +export class UserBusyException extends Error { + constructor(message, userid) { + super(`user (id: ${userid}) is busy: ${message}`); + this.name = this.constructor.name; + this.userid = userid; + } +} + +export class LatexException extends Error { + constructor(message, userid, errno, stdout) { + super(message + ` (userid: ${userid}, errno: ${errno})\n${stdout}`); + this.name = this.constructor.name; + this.errno = errno; + this.stdout = stdout; + } +} + +export class FileExistsException extends Error { + constructor(path) { + super("File already exists: " + path); + this.name = this.constructor.name; + this.path = path; + } +} + + +const DOCUMENTS_PATH: string = "./documents" +const ESTIMATES_FOLD: string = "estimates" +const RECORDS_FOLD: string = "records" + +const GENERATION_PATH: string = "./pdfgen"; + +const TEMPLATE_REC = "template-rec.tex"; +const TEMPLATE_REC_PATH = `${GENERATION_PATH}/${TEMPLATE_REC}`; + +const TEMPLATE_EST = "template-est.tex"; +const TEMPLATE_EST_PATH = `${GENERATION_PATH}/${TEMPLATE_EST}`; + +const SALUTATION = { + m: "Herrn", + w: "Frau" +} + +const GENDER_END = { + m: "", + w: "in", +} + + +// this may be a race condition??? +// is a mutex needed in JS? +let locked_users: number[] = new Set(); + +export async function getAllFiles(user: User) { + const path = `${DOCUMENTS_PATH}/user-${user.id}`; + + let file_names = (await fs.readdir(path, { recursive: true }).catch((err) => { + return []; + })).filter((v) => { + return !(v == ESTIMATES_FOLD || v == RECORDS_FOLD); + }); + + let files = (await Promise.all(file_names.map(async (v) => { + return { + path: v, + name: v.split("/").pop(), + timestamp: (await fs.stat(`${path}/${v}`)).mtime + } + }))).sort((a, b) => { + return a.timestamp - b.timestamp + }); + + return files; +} + +export async function getFile(user: User, filename: string) { + const path = `${DOCUMENTS_PATH}/user-${user.id}`; + + let file = Bun.file(`${path}/${filename}`) + + if (!await file.exists()) { + return null; + } + + return await file.bytes(); +} + +export async function generateEstimatePDF(user: User, year: number, quarter: number) { + return await _runProtocted(_generateEstimatePDF, user, year, quarter); +} +export async function generateRecordPDF(user: User, year: number, quarter: number) { + return await _runProtocted(_generateRecordPDF, user, year, quarter); +} + +async function _runProtocted(f, user: User, ...args) { + + if (locked_users.has(user.id)) { + throw UserBusyException(`Cannot generate pdf (type: ${type})` ,user.id); + } + + locked_users.add(user.id); + + try { + await f(user, ...args); + } catch (e) { + throw e; + } finally { + locked_users.delete(user.id); + } +} + + +async function _generateRecordPDF(user: User, year: number, month: number, override: boolean = false) { + const path = `${DOCUMENTS_PATH}/user-${user.id}/${RECORDS_FOLD}`; + const file_pref = "Stundenliste-" + year + "-" + padInt(month, 2, 0); + + if (!override && await Bun.file(`${path}/${file_pref}.pdf`).exists()) { + throw new FileExistsException(`${path}/${file_pref}.pdf`); + } + + fs.mkdir(path, { recursive: true }); + + let gen_path = await _genLatexRec(user, file_pref, year, month); + + let doc_path = `${path}/${gen_path.split('/').pop()}`; + + await fs.rename(gen_path, doc_path); + + return doc_path; +} + +async function _generateEstimatePDF(user: User, year: number, quarter: number, override: boolean = false) { + const path = `${DOCUMENTS_PATH}/user-${user.id}/${ESTIMATES_FOLD}`; + const file_pref = `Vereinbarung_Arbeitsbedarf_${user.name.replace(" ", "_")}_${quarter}.Quartal_${year}` + + if (!override && await Bun.file(`${path}/${file_pref}.pdf`).exists()) { + throw new FileExistsException(`${path}/${file_pref}.pdf`); + } + + fs.mkdir(path, { recursive: true }); + + let gen_path = await _genLatexEst(user, file_pref, year, quarter); + + let doc_path = `${path}/${gen_path.split('/').pop()}`; + + await fs.rename(gen_path, doc_path); + + return doc_path; +} + +async function _genLatexRec(user: User, file_pref: string, year: number, month: number) { + + const dir = `${GENERATION_PATH}/user-${user.id}`; + const template_path = `${dir}/${TEMPLATE_REC}`; + + fs.mkdir(dir, { recursive: true }); + + if (!await Bun.file(template_path).exists()) { + // may fail and throw error + await b.$`cp ${TEMPLATE_REC_PATH} ${template_path}`.quiet(); + } + + const records = user.get_entries_by_month(year, month); + const estimate = user.get_estimate_by_month(year, month); + const hr_sum = (() => { let s = 0; records.forEach((r) => { s += calculateDuration(r.start, r.end) }); return s; })() + + if (estimate == null || isNaN(estimate)) { + throw new Error("No estimate found") + } + + // TODO: escape semicolon in comment + + const csvfile = Bun.file(`${dir}/${file_pref}.csv`) + const csvfilewriter = csvfile.writer() + + csvfilewriter.write(`${user.name};${MONTHS[month - 1]} ${year};${hr_sum.toFixed(2)};${estimate.toFixed(2)};${padInt(hr_sum - estimate, 2, 2, ' ')}\n`) + + records.forEach((record) => { + csvfilewriter.write(`${record.date};${weekday_of(parseDate(record.date))};${record.start};${record.end};${calculateDuration(record.start, record.end).toFixed(2)};${record.comment}\n`) + }) + + csvfilewriter.end(); + + const { stdout, exitCode } = await b.$`pdflatex -interaction=nonstopmode -halt-on-error -jobname=${file_pref} -output-format=pdf ${TEMPLATE_REC}`.cwd(dir).quiet().nothrow(); + + if (exitCode != 0) { + throw new LatexException("Failed to create record PDF", user.id, exitCode, stdout); + } + + return `${dir}/${file_pref}.pdf`; + +} + + +async function _genLatexEst(user: User, file_pref: string, year: number, quarter: number) { + + const dir = `${GENERATION_PATH}/user-${user.id}`; + const template_path = `${dir}/${TEMPLATE_EST}`; + + fs.mkdir(dir, { recursive: true }); + + if (!await Bun.file(template_path).exists()) { + // may fail and throw error + await b.$`cp ${TEMPLATE_EST_PATH} ${template_path}`.quiet(); + } + + const estimates = user.get_estimate(year, quarter); + if (estimates == null) { + throw Error("No estimate for quarter"); + } + + const csvfile = Bun.file(`${dir}/estimate.csv`) + const csvfilewriter = csvfile.writer() + + csvfilewriter.write(`${SALUTATION[user.gender]} ${user.name};${user.address};Arbeitnehmer${GENDER_END[user.gender]};Teilzeitmitarbeiter${GENDER_END[user.gender]};${isoToLocalDate(new Date().toISOString())}\n`) + + for (let i = 0; i < 3; ++i) { + csvfilewriter.write(`${MONTHS[(quarter - 1) * 3 + i]} ${year};${estimates[`estimate_${i}`]}\n`); + } + + csvfilewriter.end(); + + const { stdout, exitCode } = await b.$`pdflatex -interaction=nonstopmode -halt-on-error -jobname=${file_pref} -output-format=pdf ${TEMPLATE_EST}`.cwd(dir).quiet().nothrow(); + + if (exitCode != 0) { + throw new LatexException("Failed to create estimate PDF", user.id, exitCode, stdout); + } + + return `${dir}/${file_pref}.pdf`; +} diff --git a/src/lib/util.ts b/src/lib/util.ts index 78194f6..c27d2c1 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -1,6 +1,6 @@ -const MONTHS = [ "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" ]; -const WEEKDAYS: string[] = [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ]; +export const MONTHS: string[] = [ "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" ]; +export const WEEKDAYS: string[] = [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ]; export function toInt(str: string): number { if (str.length === 0) { @@ -54,8 +54,8 @@ export function toFloat(str: string): number { return value; } -export function padInt(num: number, upper: number, float: number, pad_char: string = '0') { - if (num > 0) { +export function padInt(num: number, upper: number, float: number = 0, pad_char: string = '0') { + if (num >= 0) { if (float != 0) { return num.toFixed(float).padStart(upper + 1 + float, pad_char) } else { @@ -128,11 +128,8 @@ export function isoToLocalDate(str: string): string | undefined { if (!date) { return undefined; } - - console.log(str); - console.log(date); - return date.getDate() + "." + date.getMonth() + "." + date.getFullYear(); + return date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear(); } export function isTimeValidHHMM(str: string): boolean { diff --git a/src/routes/dokumente/+page.server.ts b/src/routes/dokumente/+page.server.ts index 6391ce4..311edcb 100644 --- a/src/routes/dokumente/+page.server.ts +++ b/src/routes/dokumente/+page.server.ts @@ -1,27 +1,54 @@ +import type { PageServerLoad, Actions } from "./$types"; +import { fail } from "@sveltejs/kit" + import { toInt } from "$lib/util" -import { generateRecordPDF } from "$lib/server/PDFGen"; +import { getAllFiles, generateEstimatePDF, generateRecordPDF } from "$lib/server/docstore"; -export async function load({ locals }) { +export const load: PageServerLoad = async ({ locals }) => { return { - availableMonths: locals.user.get_months(), - availableQuarters: locals.user.get_quarters() + documents: await getAllFiles(locals.user) } } export const actions = { - create_pdf: async ({ locals, request }) => { + create_estimate: async ({ locals, request }) => { const data = await request.formData(); + + const quarter = toInt(data.get("quarter") ?? ""); + const year = toInt(data.get("year") ?? ""); - let month = toInt(data.get("month") ?? ""); - let year = toInt(data.get("year") ?? ""); + if (isNaN(year) || isNaN(quarter) || quarter < 1 || quarter > 4) { + return fail(400, { success: false, message: "Invalid parameter", year: year, quarter: quarter }); + } - if (isNaN(year) || isNaN(month) || month > 12) { - return fail(400, {}); + try { + await generateEstimatePDF(locals.user, year, quarter); + } catch (e) { + console.log(e); + return fail(403, { success: false, message: e.toString(), year: year, quarter: quarter }); } - generateRecordPDF(locals.user, new Date(year, month - 1)); + return { success: true }; + }, + create_record: async ({ locals, request }) => { + const data = await request.formData(); + + const month = toInt(data.get("month") ?? ""); + const year = toInt(data.get("year") ?? ""); - return { success: true } + if (isNaN(year) || isNaN(month) || month < 1 || month > 12) { + return fail(400, { success: false, message: "Invalid parameter", year: year, month: month }); + } + + try { + await generateRecordPDF(locals.user, year, month); + } catch (e) { + console.log(e); + return fail(403, { success: false, message: e.toString(), year: year, month: month }); + } + + return { success: true }; } -} + +} satisfies Actions; diff --git a/src/routes/dokumente/+page.svelte b/src/routes/dokumente/+page.svelte index 36df376..b03f370 100644 --- a/src/routes/dokumente/+page.svelte +++ b/src/routes/dokumente/+page.svelte @@ -1,92 +1,111 @@ +
+

Dokumente

+ +
+
- + + + + + + + + + + + + + +
DokumenteDokument erstellen
Stundenliste + - + + + +
Stundenschätzung + . Quartal + + + +
+ +
{!(form?.success) ? form?.message : "Dokument erstellt."}
+ +
+ + + + - + + - {#each documents as doc} - {#if doc.month != null} - - - - {:else} - - - - - {/if} + {#each data.documents as doc} + + + + + {/each} -{#if data.availableMonths === undefined || data.availableMonths.length === 0} - - - - - -{/if} + {#if data.documents.length === 0} + + + + + + {/if}
Erstellte Dokumente
MonatDateinameErstellt Aktion
Stundenliste {doc.month}-{doc.year} -
- - - -
-
Stundenschätzung {doc.quarter}. Quartal {doc.year} -
-
{doc.name}{`${isoToLocalDate(doc.timestamp.toISOString())} um ${padInt(doc.timestamp.getHours(), 2)}:${padInt(doc.timestamp.getMinutes(), 2)}:${padInt(doc.timestamp.getSeconds(), 2)}`} +
+ +
+
No records
Noch keine Dokumente
+
+