From 4650cb84c30a205f3099b2f1412cbaa9d80efcd4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Feb 2025 21:41:16 +0100 Subject: [PATCH] It works --- .gitignore | 3 + bun.lockb | Bin 47411 -> 59776 bytes package.json | 16 +- pdfgen/template-est.tex | 101 +++++++++ pdfgen/{template.tex => template-rec.tex} | 7 +- src/hooks.server.ts | 1 + src/lib/db_types.ts | 2 + src/lib/server/PDFGen.ts | 79 ++++++- src/lib/server/database.ts | 48 +++-- src/lib/server/docstore.ts | 238 ++++++++++++++++++++++ src/lib/util.ts | 13 +- src/routes/dokumente/+page.server.ts | 51 +++-- src/routes/dokumente/+page.svelte | 125 +++++++----- src/routes/dokumente/[...file]/+server.ts | 13 ++ src/routes/schaetzung/+page.server.ts | 1 - 15 files changed, 590 insertions(+), 108 deletions(-) create mode 100644 pdfgen/template-est.tex rename pdfgen/{template.tex => template-rec.tex} (93%) create mode 100644 src/lib/server/docstore.ts create mode 100644 src/routes/dokumente/[...file]/+server.ts 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 9d988b5b0a78eec2a5043f46e3a6e4b4e8299a33..800ba349da1f9127d9bcbec405ded5d17f12c520 100755 GIT binary patch delta 13969 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 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
+
+