Arbeitszeit: Zeitbuchungen anlegen, aendern und loeschen
createZeitbuchung, updateZeitbuchung und deleteZeitbuchung buchen Taetigkeitsbloecke und Pausen ueber module/arbeitszeit/api/. Die erste Buchung eines Tages legt per nachtragen.php die Schicht an, weitere Buchungen kommen in die vorhandene Schicht, die bei Bedarf verlaengert wird. Die Kostenstelle wird an die Notiz gehaengt. Unbekannte Taetigkeiten werden vorher abgelehnt, weil Agrarmonitor sie ohne Fehler speichert. Ablehnungen kommen als ZeitbuchungError mit Grund. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6db6c5b278
commit
d703e46125
21 files changed
+1920
-16
No files matched your search
@@ -105,4 +105,8 @@ The store can read both the connector format and the older Telefonbuch cookie-ar
|
|||||||
- `getMitarbeiter(suchstring?, mitarbeitergruppe?)` reads `/module/mitarbeiter/livesearch.php` and returns employee ID, number, last name, first name, and active flag.
|
- `getMitarbeiter(suchstring?, mitarbeitergruppe?)` reads `/module/mitarbeiter/livesearch.php` and returns employee ID, number, last name, first name, and active flag.
|
||||||
- `getTaetigkeiten()` reads `/module/arbeitszeit/taetigkeiten-livesearch.php` (active and inactive) and returns activity ID, UUID, number, name, active flag, area (`bereichId`, `bereich`) and the flags `maschineZuordnen`, `abwesenheit`, `notizErzwingen`.
|
- `getTaetigkeiten()` reads `/module/arbeitszeit/taetigkeiten-livesearch.php` (active and inactive) and returns activity ID, UUID, number, name, active flag, area (`bereichId`, `bereich`) and the flags `maschineZuordnen`, `abwesenheit`, `notizErzwingen`.
|
||||||
- Agrarmonitor has no cost center master data (`Kostenstelle` is only a free-text field on machines, employees and articles), so there is no `getKostenstellen()`. See `docs/zeitbuchung-analyse.md`.
|
- Agrarmonitor has no cost center master data (`Kostenstelle` is only a free-text field on machines, employees and articles), so there is no `getKostenstellen()`. See `docs/zeitbuchung-analyse.md`.
|
||||||
|
- `createZeitbuchung(daten)` books an activity block (`taetigkeitId`, optional `maschinenId`, `notiz`, `kostenstelle`) or a break (`art: 'pause'`) for `mitarbeiterId` from `start` to `ende` (minute precision, Europe/Berlin) and returns `{ ref }`. The first booking of a day creates the shift via `nachtragen.php`; later bookings go into the existing shift, which is extended if needed. Breaks need an existing shift. Agrarmonitor has no cost center field, so `kostenstelle` is appended to the note as `Kostenstelle: …`. Pass `datum` (YYYY-MM-DD) for blocks after midnight that belong to the previous workday.
|
||||||
|
- `updateZeitbuchung(ref, daten)` changes times, activity, machine and note of a booking (the old note is replaced); changing the type (activity/break), employee or workday is rejected.
|
||||||
|
- `deleteZeitbuchung(ref)` deletes a booking; deleting the last activity of a shift also deletes the shift in Agrarmonitor.
|
||||||
|
- Refs look like `verteilung:<mitarbeiterId>:<YYYY-MM-DD>:<id>` or `pause:…`. Rejections by Agrarmonitor (HTTP 4xx such as 422 with the reason in the body) and invalid input throw `ZeitbuchungError` (`grund`, `status`); network and 5xx errors are thrown unchanged so callers can retry.
|
||||||
- `saveSession()` explicitly persists the current cookie jar.
|
- `saveSession()` explicitly persists the current cookie jar.
|
||||||
Vendored
+59
-1
@@ -1,9 +1,10 @@
|
|||||||
import { type AxiosInstance } from 'axios';
|
import { type AxiosInstance } from 'axios';
|
||||||
import type { AgrarmonitorApiCustomer, AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, AgrarmonitorDeviceRegistrationOptions, AgrarmonitorDeviceRegistrationResult, AgrarmonitorFetchCustomersOptions, AgrarmonitorFreischaltungStatus, AgrarmonitorKunde2, AgrarmonitorRegistrierungStatus, Artikel, ArtikelEinheit, ArtikelKategorie, EingangsrechnungLivesearchResult, Firma, Maschine, MaschineMitGruppe, MaschinenKategorie, Mitarbeiter, Rechnungsdaten, Taetigkeit } from './types';
|
import type { AgrarmonitorApiCustomer, AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, AgrarmonitorDeviceRegistrationOptions, AgrarmonitorDeviceRegistrationResult, AgrarmonitorFetchCustomersOptions, AgrarmonitorFreischaltungStatus, AgrarmonitorKunde2, AgrarmonitorRegistrierungStatus, Artikel, ArtikelEinheit, ArtikelKategorie, EingangsrechnungLivesearchResult, Firma, Maschine, MaschineMitGruppe, MaschinenKategorie, Mitarbeiter, Rechnungsdaten, Taetigkeit, ZeitbuchungDaten } from './types';
|
||||||
export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
||||||
private readonly options;
|
private readonly options;
|
||||||
http: AxiosInstance;
|
http: AxiosInstance;
|
||||||
private static readonly maxLivesearchSeiten;
|
private static readonly maxLivesearchSeiten;
|
||||||
|
private static readonly berlinFormat;
|
||||||
private static readonly s3DateienBaseUrl;
|
private static readonly s3DateienBaseUrl;
|
||||||
private readonly baseUrl;
|
private readonly baseUrl;
|
||||||
private readonly apiBaseUrl;
|
private readonly apiBaseUrl;
|
||||||
@@ -44,6 +45,63 @@ export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResul
|
|||||||
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
||||||
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
||||||
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
||||||
|
/**
|
||||||
|
* Bucht einen Taetigkeitsblock oder eine Pause. Gibt es fuer Mitarbeiter und
|
||||||
|
* Arbeitstag noch keine Schicht, legt `nachtragen.php` Schicht und Verteilung
|
||||||
|
* an (Pausen brauchen eine vorhandene Schicht). Sonst kommt die Buchung in die
|
||||||
|
* passende Schicht, die bei Bedarf verlaengert wird.
|
||||||
|
*/
|
||||||
|
createZeitbuchung(daten: ZeitbuchungDaten): Promise<{
|
||||||
|
ref: string;
|
||||||
|
}>;
|
||||||
|
updateZeitbuchung(ref: string, daten: ZeitbuchungDaten): Promise<void>;
|
||||||
|
deleteZeitbuchung(ref: string): Promise<void>;
|
||||||
|
private zeitbuchungAufbereiten;
|
||||||
|
/** Rundet auf die volle Minute - Agrarmonitor speichert keine Sekunden. */
|
||||||
|
private zeitpunkt;
|
||||||
|
/** YYYY-MM-DD HH:MM -> dd.mm.yy (HH:MM) */
|
||||||
|
private vergleichZuFormular;
|
||||||
|
private zeitbuchungRef;
|
||||||
|
private parseZeitbuchungRef;
|
||||||
|
private ladeTagesansicht;
|
||||||
|
private ladeSchichten;
|
||||||
|
private ladeSchicht;
|
||||||
|
/** Schicht, die sich mit der Buchung ueberschneidet, sonst die letzte davor, sonst die erste. */
|
||||||
|
private passendeSchicht;
|
||||||
|
/** Neue Verteilungen verlaengern die Schicht in Agrarmonitor nicht von selbst. */
|
||||||
|
private schichtErweitern;
|
||||||
|
private schichtSetzen;
|
||||||
|
/**
|
||||||
|
* Erste Buchung des Tages: nachtragen.php legt Schicht und Verteilung an,
|
||||||
|
* liefert aber keine ID. Die Verteilung wird danach in der Tagesansicht gesucht.
|
||||||
|
*/
|
||||||
|
private taetigkeitNachtragen;
|
||||||
|
/**
|
||||||
|
* editVerteilungForm.php mit shiftID legt sofort eine leere Verteilung an
|
||||||
|
* (das Agrarmonitor-UI loescht sie bei "Abbrechen"). Schlaegt das Speichern
|
||||||
|
* fehl, wird dieser Platzhalter wieder entfernt.
|
||||||
|
*/
|
||||||
|
private verteilungAnlegen;
|
||||||
|
private ladeVerteilungFormular;
|
||||||
|
private verteilungSpeichern;
|
||||||
|
/** Wie bei Verteilungen legt editPauseForm.php mit shiftID sofort eine Pause an. */
|
||||||
|
private pauseAnlegen;
|
||||||
|
private pauseSpeichern;
|
||||||
|
/**
|
||||||
|
* JSON-POST an die Arbeitszeit-API. 4xx (z. B. 422 mit Grund im Body) wird
|
||||||
|
* als ZeitbuchungError geworfen; Netzwerk- und 5xx-Fehler bleiben Axios-Fehler.
|
||||||
|
*/
|
||||||
|
private zeitbuchungPost;
|
||||||
|
private zeitbuchungAntwortPruefen;
|
||||||
|
/** 422-Antworten sehen so aus: {"verteilungForm":"ganzertag must be present"} */
|
||||||
|
private zeitbuchungGrund;
|
||||||
|
/**
|
||||||
|
* Agrarmonitor speichert auch unbekannte oder inaktive Taetigkeits-IDs (dann
|
||||||
|
* ohne Taetigkeit). Deshalb gegen die Auswahl im Buchungsformular pruefen.
|
||||||
|
*/
|
||||||
|
private taetigkeitPruefen;
|
||||||
|
private selectedValue;
|
||||||
|
private normalisiereText;
|
||||||
/**
|
/**
|
||||||
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
||||||
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"AgrarmonitorConnector.d.ts","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"AgrarmonitorConnector.d.ts","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"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"}
|
||||||
Vendored
+439
@@ -8,10 +8,20 @@ const axios_1 = __importDefault(require("axios"));
|
|||||||
const axios_cookiejar_support_1 = require("axios-cookiejar-support");
|
const axios_cookiejar_support_1 = require("axios-cookiejar-support");
|
||||||
const jsdom_1 = require("jsdom");
|
const jsdom_1 = require("jsdom");
|
||||||
const tough_cookie_1 = require("tough-cookie");
|
const tough_cookie_1 = require("tough-cookie");
|
||||||
|
const ZeitbuchungError_1 = require("./ZeitbuchungError");
|
||||||
class AgrarmonitorConnector {
|
class AgrarmonitorConnector {
|
||||||
options;
|
options;
|
||||||
http;
|
http;
|
||||||
static maxLivesearchSeiten = 500;
|
static maxLivesearchSeiten = 500;
|
||||||
|
static berlinFormat = new Intl.DateTimeFormat('de-DE', {
|
||||||
|
timeZone: 'Europe/Berlin',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
});
|
||||||
static s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
static s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
||||||
baseUrl;
|
baseUrl;
|
||||||
apiBaseUrl;
|
apiBaseUrl;
|
||||||
@@ -474,6 +484,435 @@ class AgrarmonitorConnector {
|
|||||||
}
|
}
|
||||||
return Array.from(taetigkeiten.values());
|
return Array.from(taetigkeiten.values());
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Bucht einen Taetigkeitsblock oder eine Pause. Gibt es fuer Mitarbeiter und
|
||||||
|
* Arbeitstag noch keine Schicht, legt `nachtragen.php` Schicht und Verteilung
|
||||||
|
* an (Pausen brauchen eine vorhandene Schicht). Sonst kommt die Buchung in die
|
||||||
|
* passende Schicht, die bei Bedarf verlaengert wird.
|
||||||
|
*/
|
||||||
|
async createZeitbuchung(daten) {
|
||||||
|
const buchung = this.zeitbuchungAufbereiten(daten);
|
||||||
|
const schichten = await this.ladeSchichten(buchung.mitarbeiterId, buchung.datum);
|
||||||
|
if (schichten.length === 0) {
|
||||||
|
if (buchung.art === 'pause') {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`Pause am ${buchung.datum} kann nicht gebucht werden: keine Schicht vorhanden`, 'keine Schicht vorhanden');
|
||||||
|
}
|
||||||
|
const id = await this.taetigkeitNachtragen(buchung);
|
||||||
|
return { ref: this.zeitbuchungRef(buchung, id) };
|
||||||
|
}
|
||||||
|
const schicht = this.passendeSchicht(schichten, buchung);
|
||||||
|
const schichtVorher = { ...schicht };
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
try {
|
||||||
|
const id = buchung.art === 'pause'
|
||||||
|
? await this.pauseAnlegen(buchung, schicht.id)
|
||||||
|
: await this.verteilungAnlegen(buchung, schicht.id);
|
||||||
|
return { ref: this.zeitbuchungRef(buchung, id) };
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (schicht.start !== schichtVorher.start || schicht.ende !== schichtVorher.ende) {
|
||||||
|
await this.schichtSetzen(schichtVorher).catch(restoreError => {
|
||||||
|
this.logger?.warn?.('Schicht konnte nicht zurueckgesetzt werden', {
|
||||||
|
schichtId: schichtVorher.id,
|
||||||
|
fehler: restoreError instanceof Error ? restoreError.message : String(restoreError),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateZeitbuchung(ref, daten) {
|
||||||
|
const zielRef = this.parseZeitbuchungRef(ref);
|
||||||
|
const buchung = this.zeitbuchungAufbereiten(daten);
|
||||||
|
if (buchung.art !== zielRef.art) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Art der Buchung (Taetigkeit/Pause) kann nicht geaendert werden', 'art geaendert');
|
||||||
|
}
|
||||||
|
if (buchung.mitarbeiterId !== zielRef.mitarbeiterId || buchung.datum !== zielRef.datum) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Mitarbeiter oder Arbeitstag einer Buchung kann nicht geaendert werden', 'mitarbeiter oder datum geaendert');
|
||||||
|
}
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(zielRef.mitarbeiterId, zielRef.datum);
|
||||||
|
const zeilenKlasse = zielRef.art === 'pause' ? 'pause_betrieb' : 'betrieb';
|
||||||
|
const schichtId = this.parseNumber(tagesansicht
|
||||||
|
.querySelector(`tr.${zeilenKlasse}[id="${zielRef.id}"]`)
|
||||||
|
?.closest('table.verteilung_table')
|
||||||
|
?.getAttribute('data-schicht'));
|
||||||
|
if (!schichtId) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`Buchung ${ref} nicht gefunden`, 'nicht gefunden');
|
||||||
|
}
|
||||||
|
const schicht = await this.ladeSchicht(schichtId);
|
||||||
|
if (schicht) {
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
}
|
||||||
|
if (buchung.art === 'pause') {
|
||||||
|
await this.pauseSpeichern(buchung, zielRef.id, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: zielRef.mitarbeiterId,
|
||||||
|
date: zielRef.datum,
|
||||||
|
distributionID: String(zielRef.id),
|
||||||
|
});
|
||||||
|
this.taetigkeitPruefen(formular, buchung.taetigkeitId);
|
||||||
|
const notizen = Array.from(formular.querySelectorAll('#notizen_table tbody tr[data-id]'))
|
||||||
|
.map(row => ({
|
||||||
|
id: this.parseNumber(row.getAttribute('data-id')),
|
||||||
|
text: this.normalisiereText(row.querySelectorAll('td')[2]?.textContent ?? ''),
|
||||||
|
}))
|
||||||
|
.filter(notiz => notiz.id > 0);
|
||||||
|
const notizUnveraendert = (notizen.length === 0 && !buchung.notiz) ||
|
||||||
|
(notizen.length === 1 && notizen[0].text === this.normalisiereText(buchung.notiz));
|
||||||
|
await this.verteilungSpeichern(buchung, zielRef.id, {
|
||||||
|
alteTaetigkeitId: this.selectedValue(formular, '#taetigkeit_id') || '0',
|
||||||
|
projektId: this.selectedValue(formular, '#projekt_id') || '0',
|
||||||
|
verfasserId: formular.querySelector('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: notizUnveraendert ? '' : buchung.notiz,
|
||||||
|
});
|
||||||
|
// Alte Notizen erst nach erfolgreichem Speichern entfernen.
|
||||||
|
if (!notizUnveraendert) {
|
||||||
|
for (const notiz of notizen) {
|
||||||
|
await this.zeitbuchungPost('/module/notiz/api/delete.php', { noteID: notiz.id }, 'Notiz loeschen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async deleteZeitbuchung(ref) {
|
||||||
|
const zielRef = this.parseZeitbuchungRef(ref);
|
||||||
|
if (zielRef.art === 'pause') {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/deletePause.php', { pauseID: zielRef.id, employeeID: zielRef.mitarbeiterId, date: zielRef.datum }, 'Pause loeschen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/deleteVerteilung.php', { distributionID: zielRef.id, employeeID: zielRef.mitarbeiterId, date: zielRef.datum }, 'Buchung loeschen');
|
||||||
|
}
|
||||||
|
zeitbuchungAufbereiten(daten) {
|
||||||
|
if (!Number.isInteger(daten.mitarbeiterId) || daten.mitarbeiterId <= 0) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltige Mitarbeiter-ID', 'mitarbeiterId');
|
||||||
|
}
|
||||||
|
if (!(daten.start instanceof Date) || Number.isNaN(daten.start.getTime())) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltiger Start', 'start');
|
||||||
|
}
|
||||||
|
if (!(daten.ende instanceof Date) || Number.isNaN(daten.ende.getTime())) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltiges Ende', 'ende');
|
||||||
|
}
|
||||||
|
if (daten.datum !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(daten.datum)) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltiger Arbeitstag, erwartet YYYY-MM-DD', 'datum');
|
||||||
|
}
|
||||||
|
const start = this.zeitpunkt(daten.start);
|
||||||
|
const ende = this.zeitpunkt(daten.ende);
|
||||||
|
if (ende.vergleich <= start.vergleich) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ende muss mindestens eine Minute nach dem Start liegen', 'dauer');
|
||||||
|
}
|
||||||
|
const basis = {
|
||||||
|
mitarbeiterId: daten.mitarbeiterId,
|
||||||
|
datum: daten.datum ?? start.datum,
|
||||||
|
start,
|
||||||
|
ende,
|
||||||
|
};
|
||||||
|
if (daten.art === 'pause') {
|
||||||
|
return { ...basis, art: 'pause', taetigkeitId: 0, maschinenId: 0, notiz: '' };
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(daten.taetigkeitId) || daten.taetigkeitId <= 0) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltige Taetigkeits-ID', 'taetigkeitId');
|
||||||
|
}
|
||||||
|
const maschinenId = daten.maschinenId ?? 0;
|
||||||
|
if (!Number.isInteger(maschinenId) || maschinenId < 0) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Ungueltige Maschinen-ID', 'maschinenId');
|
||||||
|
}
|
||||||
|
const kostenstelle = daten.kostenstelle?.trim();
|
||||||
|
const notiz = [daten.notiz?.trim(), kostenstelle ? `Kostenstelle: ${kostenstelle}` : '']
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
return { ...basis, art: 'taetigkeit', taetigkeitId: daten.taetigkeitId, maschinenId, notiz };
|
||||||
|
}
|
||||||
|
/** Rundet auf die volle Minute - Agrarmonitor speichert keine Sekunden. */
|
||||||
|
zeitpunkt(date) {
|
||||||
|
const gerundet = new Date(Math.round(date.getTime() / 60000) * 60000);
|
||||||
|
const teile = Object.fromEntries(AgrarmonitorConnector.berlinFormat.formatToParts(gerundet).map(teil => [teil.type, teil.value]));
|
||||||
|
const datum = `${teile.year}-${teile.month}-${teile.day}`;
|
||||||
|
const uhrzeit = `${teile.hour}:${teile.minute}`;
|
||||||
|
return {
|
||||||
|
vergleich: `${datum} ${uhrzeit}`,
|
||||||
|
formular: this.vergleichZuFormular(`${datum} ${uhrzeit}`),
|
||||||
|
datum,
|
||||||
|
uhrzeit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/** YYYY-MM-DD HH:MM -> dd.mm.yy (HH:MM) */
|
||||||
|
vergleichZuFormular(wert) {
|
||||||
|
const [datum, uhrzeit] = wert.split(' ');
|
||||||
|
const [jahr, monat, tag] = datum.split('-');
|
||||||
|
return `${tag}.${monat}.${jahr.slice(2)} (${uhrzeit})`;
|
||||||
|
}
|
||||||
|
zeitbuchungRef(buchung, id) {
|
||||||
|
return `${buchung.art === 'pause' ? 'pause' : 'verteilung'}:${buchung.mitarbeiterId}:${buchung.datum}:${id}`;
|
||||||
|
}
|
||||||
|
parseZeitbuchungRef(ref) {
|
||||||
|
const match = /^(verteilung|pause):(\d+):(\d{4}-\d{2}-\d{2}):(\d+)$/.exec(ref);
|
||||||
|
if (!match) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`Ungueltige Zeitbuchungs-Referenz: ${ref}`, 'ref');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
art: match[1] === 'pause' ? 'pause' : 'taetigkeit',
|
||||||
|
mitarbeiterId: Number(match[2]),
|
||||||
|
datum: match[3],
|
||||||
|
id: Number(match[4]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async ladeTagesansicht(mitarbeiterId, datum) {
|
||||||
|
const response = await this.http.get(`/arbeitszeit/detail/${mitarbeiterId}/${datum}`);
|
||||||
|
await this.saveSession();
|
||||||
|
return this.parseHtmlDocument(response.data);
|
||||||
|
}
|
||||||
|
async ladeSchichten(mitarbeiterId, datum) {
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(mitarbeiterId, datum);
|
||||||
|
const ids = new Set(Array.from(tagesansicht.querySelectorAll('table.verteilung_table[data-schicht]'))
|
||||||
|
.map(table => this.parseNumber(table.getAttribute('data-schicht')))
|
||||||
|
.filter(id => id > 0));
|
||||||
|
const schichten = [];
|
||||||
|
for (const id of ids) {
|
||||||
|
const schicht = await this.ladeSchicht(id);
|
||||||
|
if (schicht) {
|
||||||
|
schichten.push(schicht);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schichten;
|
||||||
|
}
|
||||||
|
async ladeSchicht(schichtId) {
|
||||||
|
const response = await this.http.get('/module/arbeitszeit/api/getSchicht.php', {
|
||||||
|
params: { schicht_id: schichtId },
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
const data = (typeof response.data === 'string' ? JSON.parse(response.data || 'null') : response.data);
|
||||||
|
if (!data?.timestamp_start || Number(data.ist_deleted) === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const start = data.timestamp_start.slice(0, 16);
|
||||||
|
const ende = data.timestamp_ende && !data.timestamp_ende.startsWith('0000') ? data.timestamp_ende.slice(0, 16) : start;
|
||||||
|
return { id: schichtId, start, ende };
|
||||||
|
}
|
||||||
|
/** Schicht, die sich mit der Buchung ueberschneidet, sonst die letzte davor, sonst die erste. */
|
||||||
|
passendeSchicht(schichten, buchung) {
|
||||||
|
return (schichten.find(schicht => schicht.start <= buchung.ende.vergleich && buchung.start.vergleich <= schicht.ende) ??
|
||||||
|
schichten.filter(schicht => schicht.start <= buchung.start.vergleich).at(-1) ??
|
||||||
|
schichten[0]);
|
||||||
|
}
|
||||||
|
/** Neue Verteilungen verlaengern die Schicht in Agrarmonitor nicht von selbst. */
|
||||||
|
async schichtErweitern(schicht, buchung) {
|
||||||
|
const start = buchung.start.vergleich < schicht.start ? buchung.start.vergleich : schicht.start;
|
||||||
|
const ende = buchung.ende.vergleich > schicht.ende ? buchung.ende.vergleich : schicht.ende;
|
||||||
|
if (start === schicht.start && ende === schicht.ende) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.schichtSetzen({ id: schicht.id, start, ende });
|
||||||
|
schicht.start = start;
|
||||||
|
schicht.ende = ende;
|
||||||
|
}
|
||||||
|
async schichtSetzen(schicht) {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/updateSchicht.php', {
|
||||||
|
shiftID: schicht.id,
|
||||||
|
timestampStart: this.vergleichZuFormular(schicht.start),
|
||||||
|
timestampEnd: this.vergleichZuFormular(schicht.ende),
|
||||||
|
}, 'Schicht anpassen');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Erste Buchung des Tages: nachtragen.php legt Schicht und Verteilung an,
|
||||||
|
* liefert aber keine ID. Die Verteilung wird danach in der Tagesansicht gesucht.
|
||||||
|
*/
|
||||||
|
async taetigkeitNachtragen(buchung) {
|
||||||
|
const nachtragenFormular = await this.http.get('/module/arbeitszeit/api/nachtragen-form.php');
|
||||||
|
await this.saveSession();
|
||||||
|
this.taetigkeitPruefen(this.parseHtmlDocument(nachtragenFormular.data), buchung.taetigkeitId);
|
||||||
|
const amArbeitstag = buchung.start.datum === buchung.datum && buchung.ende.datum === buchung.datum;
|
||||||
|
const form = {
|
||||||
|
taetigkeit_id: String(buchung.taetigkeitId),
|
||||||
|
projekt_id: '0',
|
||||||
|
arbeitszeit_start: buchung.start.uhrzeit,
|
||||||
|
// Nachtragen kennt nur Uhrzeiten; ueber Mitternacht wird unten korrigiert.
|
||||||
|
arbeitszeit_ende: amArbeitstag ? buchung.ende.uhrzeit : '23:59',
|
||||||
|
arbeitzeit_sollstunden: '',
|
||||||
|
pause_start: '',
|
||||||
|
pause_ende: '',
|
||||||
|
maschine_id: String(buchung.maschinenId),
|
||||||
|
notiz: buchung.notiz,
|
||||||
|
uebernehmen: '1',
|
||||||
|
mitarbeitertyp: '1',
|
||||||
|
mitarbeiter_id: String(buchung.mitarbeiterId),
|
||||||
|
kategorie_id: '0',
|
||||||
|
};
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
for (const [name, value] of Object.entries(form)) {
|
||||||
|
body.append(`form[${name}]`, value);
|
||||||
|
}
|
||||||
|
body.append('tage[]', buchung.datum);
|
||||||
|
body.append('anzahl_datum', '1');
|
||||||
|
body.append('planungAusMitarbeiterId', '0');
|
||||||
|
const response = await this.http.post('/module/arbeitszeit/api/nachtragen.php', body, {
|
||||||
|
...this.formPostConfig('/arbeitszeit'),
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
this.zeitbuchungAntwortPruefen(response, 'Nachtragen');
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(buchung.mitarbeiterId, buchung.datum);
|
||||||
|
const zeilen = Array.from(tagesansicht.querySelectorAll('table.verteilung_table tr.betrieb[id]'));
|
||||||
|
if (zeilen.length !== 1) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`Nachgetragene Buchung am ${buchung.datum} nicht eindeutig gefunden (${zeilen.length} Buchungen)`, 'nicht eindeutig');
|
||||||
|
}
|
||||||
|
const id = this.parseNumber(zeilen[0].id);
|
||||||
|
if (!amArbeitstag) {
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: buchung.mitarbeiterId,
|
||||||
|
date: buchung.datum,
|
||||||
|
distributionID: String(id),
|
||||||
|
});
|
||||||
|
await this.verteilungSpeichern(buchung, id, {
|
||||||
|
alteTaetigkeitId: String(buchung.taetigkeitId),
|
||||||
|
projektId: '0',
|
||||||
|
verfasserId: formular.querySelector('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: '',
|
||||||
|
});
|
||||||
|
const schicht = await this.ladeSchicht(this.parseNumber(zeilen[0].closest('table.verteilung_table')?.getAttribute('data-schicht')));
|
||||||
|
if (schicht) {
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* editVerteilungForm.php mit shiftID legt sofort eine leere Verteilung an
|
||||||
|
* (das Agrarmonitor-UI loescht sie bei "Abbrechen"). Schlaegt das Speichern
|
||||||
|
* fehl, wird dieser Platzhalter wieder entfernt.
|
||||||
|
*/
|
||||||
|
async verteilungAnlegen(buchung, schichtId) {
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: buchung.mitarbeiterId,
|
||||||
|
date: buchung.datum,
|
||||||
|
shiftID: schichtId,
|
||||||
|
});
|
||||||
|
const id = this.parseNumber(formular.querySelector('#verteilung_form')?.getAttribute('data-id'));
|
||||||
|
if (!id) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Agrarmonitor hat keine neue Buchung angelegt', 'keine verteilung_id');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.taetigkeitPruefen(formular, buchung.taetigkeitId);
|
||||||
|
await this.verteilungSpeichern(buchung, id, {
|
||||||
|
alteTaetigkeitId: '0',
|
||||||
|
projektId: '0',
|
||||||
|
verfasserId: formular.querySelector('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: buchung.notiz,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/deleteVerteilung.php', { distributionID: id, employeeID: buchung.mitarbeiterId, date: buchung.datum }, 'Platzhalter loeschen').catch(() => undefined);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
async ladeVerteilungFormular(body) {
|
||||||
|
const html = await this.zeitbuchungPost('/module/arbeitszeit/api/editVerteilungForm.php', body, 'Buchungsformular laden');
|
||||||
|
const formular = this.parseHtmlDocument(html);
|
||||||
|
if (!formular.querySelector('#verteilung_form')) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Buchungsformular nicht gefunden', 'kein verteilung_form');
|
||||||
|
}
|
||||||
|
return formular;
|
||||||
|
}
|
||||||
|
async verteilungSpeichern(buchung, id, formular) {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/updateVerteilung.php', {
|
||||||
|
mitarbeiterId: buchung.mitarbeiterId,
|
||||||
|
verteilungId: String(id),
|
||||||
|
verteilungForm: {
|
||||||
|
timestamp_start: buchung.start.formular,
|
||||||
|
timestamp_ende: buchung.ende.formular,
|
||||||
|
alte_taetigkeit_id: formular.alteTaetigkeitId,
|
||||||
|
taetigkeit_id: String(buchung.taetigkeitId),
|
||||||
|
ganzertag: '1',
|
||||||
|
maschine_id: String(buchung.maschinenId),
|
||||||
|
projekt_id: formular.projektId,
|
||||||
|
},
|
||||||
|
// Nicht leere notizfeld werden als neue Notiz an die Verteilung gehaengt.
|
||||||
|
notizForm: {
|
||||||
|
notizfeld: formular.notiz,
|
||||||
|
verfasser_id: formular.verfasserId,
|
||||||
|
},
|
||||||
|
}, 'Buchung speichern');
|
||||||
|
}
|
||||||
|
/** Wie bei Verteilungen legt editPauseForm.php mit shiftID sofort eine Pause an. */
|
||||||
|
async pauseAnlegen(buchung, schichtId) {
|
||||||
|
const html = await this.zeitbuchungPost('/module/arbeitszeit/api/editPauseForm.php', { employeeID: buchung.mitarbeiterId, date: buchung.datum, shiftID: schichtId }, 'Pausenformular laden');
|
||||||
|
const id = this.parseNumber(this.parseHtmlDocument(html).querySelector('#pause_form')?.getAttribute('data-id'));
|
||||||
|
if (!id) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError('Agrarmonitor hat keine neue Pause angelegt', 'keine pause_id');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.pauseSpeichern(buchung, id, 1);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/deletePause.php', { pauseID: id, employeeID: buchung.mitarbeiterId, date: buchung.datum }, 'Platzhalter loeschen').catch(() => undefined);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
async pauseSpeichern(buchung, id, neu) {
|
||||||
|
await this.zeitbuchungPost('/module/arbeitszeit/api/updatePause.php', {
|
||||||
|
mitarbeiterId: buchung.mitarbeiterId,
|
||||||
|
pause_id: String(id),
|
||||||
|
pauseForm: {
|
||||||
|
pause_start: buchung.start.formular,
|
||||||
|
pause_ende: buchung.ende.formular,
|
||||||
|
},
|
||||||
|
neu,
|
||||||
|
}, 'Pause speichern');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* JSON-POST an die Arbeitszeit-API. 4xx (z. B. 422 mit Grund im Body) wird
|
||||||
|
* als ZeitbuchungError geworfen; Netzwerk- und 5xx-Fehler bleiben Axios-Fehler.
|
||||||
|
*/
|
||||||
|
async zeitbuchungPost(url, body, aktion) {
|
||||||
|
const response = await this.http.post(url, body, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json, */*',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
Referer: `${this.baseUrl}/arbeitszeit`,
|
||||||
|
Origin: this.baseUrl,
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
this.zeitbuchungAntwortPruefen(response, aktion);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
zeitbuchungAntwortPruefen(response, aktion) {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const grund = this.zeitbuchungGrund(response.data);
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`${aktion} fehlgeschlagen (HTTP ${response.status}): ${grund}`, grund, response.status);
|
||||||
|
}
|
||||||
|
/** 422-Antworten sehen so aus: {"verteilungForm":"ganzertag must be present"} */
|
||||||
|
zeitbuchungGrund(data) {
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
const texte = Object.values(data)
|
||||||
|
.flatMap(wert => (wert && typeof wert === 'object' ? Object.values(wert) : [wert]))
|
||||||
|
.filter((wert) => typeof wert === 'string' && wert.trim() !== '');
|
||||||
|
return texte.length ? texte.join('; ') : JSON.stringify(data).slice(0, 500);
|
||||||
|
}
|
||||||
|
return String(data ?? '').trim().slice(0, 500) || 'keine Antwort';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Agrarmonitor speichert auch unbekannte oder inaktive Taetigkeits-IDs (dann
|
||||||
|
* ohne Taetigkeit). Deshalb gegen die Auswahl im Buchungsformular pruefen.
|
||||||
|
*/
|
||||||
|
taetigkeitPruefen(formular, taetigkeitId) {
|
||||||
|
const buchbar = Array.from(formular.querySelectorAll('select#taetigkeit_id option')).map(option => option.value);
|
||||||
|
if (!buchbar.includes(String(taetigkeitId))) {
|
||||||
|
throw new ZeitbuchungError_1.ZeitbuchungError(`Taetigkeit ${taetigkeitId} ist in Agrarmonitor nicht buchbar (unbekannt oder inaktiv)`, 'taetigkeit nicht buchbar');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedValue(document, selector) {
|
||||||
|
const select = document.querySelector(selector);
|
||||||
|
return select?.querySelector('option[selected]')?.value ?? select?.value ?? '';
|
||||||
|
}
|
||||||
|
normalisiereText(text) {
|
||||||
|
return text.replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
||||||
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"AgrarmonitorConnector.js","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"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 truncated
|
{"version":3,"file":"AgrarmonitorConnector.js","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"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 truncated
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Agrarmonitor hat eine Zeitbuchung fachlich abgelehnt (z. B. HTTP 422 mit
|
||||||
|
* Grund im Body) oder die Buchung ist so nicht moeglich. Ein erneuter Versuch
|
||||||
|
* mit denselben Daten hilft nicht - anders als bei Netzwerk- oder 5xx-Fehlern,
|
||||||
|
* die als normale Fehler durchgereicht werden.
|
||||||
|
*/
|
||||||
|
export declare class ZeitbuchungError extends Error {
|
||||||
|
readonly grund: string;
|
||||||
|
readonly status?: number | undefined;
|
||||||
|
constructor(message: string, grund: string, status?: number | undefined);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=ZeitbuchungError.d.ts.map
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"ZeitbuchungError.d.ts","sourceRoot":"","sources":["../src/ZeitbuchungError.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,KAAK,EAAE,MAAM;aACb,MAAM,CAAC,EAAE,MAAM;gBAF/B,OAAO,EAAE,MAAM,EACC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,YAAA;CAKlC"}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.ZeitbuchungError = void 0;
|
||||||
|
/**
|
||||||
|
* Agrarmonitor hat eine Zeitbuchung fachlich abgelehnt (z. B. HTTP 422 mit
|
||||||
|
* Grund im Body) oder die Buchung ist so nicht moeglich. Ein erneuter Versuch
|
||||||
|
* mit denselben Daten hilft nicht - anders als bei Netzwerk- oder 5xx-Fehlern,
|
||||||
|
* die als normale Fehler durchgereicht werden.
|
||||||
|
*/
|
||||||
|
class ZeitbuchungError extends Error {
|
||||||
|
grund;
|
||||||
|
status;
|
||||||
|
constructor(message, grund, status) {
|
||||||
|
super(message);
|
||||||
|
this.grund = grund;
|
||||||
|
this.status = status;
|
||||||
|
this.name = 'ZeitbuchungError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ZeitbuchungError = ZeitbuchungError;
|
||||||
|
//# sourceMappingURL=ZeitbuchungError.js.map
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"ZeitbuchungError.js","sourceRoot":"","sources":["../src/ZeitbuchungError.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,MAAa,gBAAiB,SAAQ,KAAK;IAGvB;IACA;IAHlB,YACE,OAAe,EACC,KAAa,EACb,MAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,UAAK,GAAL,KAAK,CAAQ;QACb,WAAM,GAAN,MAAM,CAAS;QAG/B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AATD,4CASC"}
|
||||||
Vendored
+2
-1
@@ -3,5 +3,6 @@ export { FileCookieStore } from './cookie-store/FileCookieStore';
|
|||||||
export { MemoryCookieStore } from './cookie-store/MemoryCookieStore';
|
export { MemoryCookieStore } from './cookie-store/MemoryCookieStore';
|
||||||
export { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
export { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
||||||
export { createAgrarmonitorClient } from './createAgrarmonitorClient';
|
export { createAgrarmonitorClient } from './createAgrarmonitorClient';
|
||||||
export type { AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, CookieEncryptor, CookieStore, Logger, } from './types';
|
export { ZeitbuchungError } from './ZeitbuchungError';
|
||||||
|
export type { AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, CookieEncryptor, CookieStore, Logger, MaschineMitGruppe, PausenbuchungDaten, Taetigkeit, TaetigkeitsbuchungDaten, ZeitbuchungDaten, } from './types';
|
||||||
//# sourceMappingURL=index.d.ts.map
|
//# sourceMappingURL=index.d.ts.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,eAAe,EACf,WAAW,EACX,MAAM,GACP,MAAM,SAAS,CAAC"}
|
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,eAAe,EACf,WAAW,EACX,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
|
||||||
Vendored
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.createAgrarmonitorClient = exports.AgrarmonitorConnector = exports.MemoryCookieStore = exports.FileCookieStore = exports.AesGcmCookieEncryptor = void 0;
|
exports.ZeitbuchungError = exports.createAgrarmonitorClient = exports.AgrarmonitorConnector = exports.MemoryCookieStore = exports.FileCookieStore = exports.AesGcmCookieEncryptor = void 0;
|
||||||
var AesGcmCookieEncryptor_1 = require("./crypto/AesGcmCookieEncryptor");
|
var AesGcmCookieEncryptor_1 = require("./crypto/AesGcmCookieEncryptor");
|
||||||
Object.defineProperty(exports, "AesGcmCookieEncryptor", { enumerable: true, get: function () { return AesGcmCookieEncryptor_1.AesGcmCookieEncryptor; } });
|
Object.defineProperty(exports, "AesGcmCookieEncryptor", { enumerable: true, get: function () { return AesGcmCookieEncryptor_1.AesGcmCookieEncryptor; } });
|
||||||
var FileCookieStore_1 = require("./cookie-store/FileCookieStore");
|
var FileCookieStore_1 = require("./cookie-store/FileCookieStore");
|
||||||
@@ -11,4 +11,6 @@ var AgrarmonitorConnector_1 = require("./AgrarmonitorConnector");
|
|||||||
Object.defineProperty(exports, "AgrarmonitorConnector", { enumerable: true, get: function () { return AgrarmonitorConnector_1.AgrarmonitorConnector; } });
|
Object.defineProperty(exports, "AgrarmonitorConnector", { enumerable: true, get: function () { return AgrarmonitorConnector_1.AgrarmonitorConnector; } });
|
||||||
var createAgrarmonitorClient_1 = require("./createAgrarmonitorClient");
|
var createAgrarmonitorClient_1 = require("./createAgrarmonitorClient");
|
||||||
Object.defineProperty(exports, "createAgrarmonitorClient", { enumerable: true, get: function () { return createAgrarmonitorClient_1.createAgrarmonitorClient; } });
|
Object.defineProperty(exports, "createAgrarmonitorClient", { enumerable: true, get: function () { return createAgrarmonitorClient_1.createAgrarmonitorClient; } });
|
||||||
|
var ZeitbuchungError_1 = require("./ZeitbuchungError");
|
||||||
|
Object.defineProperty(exports, "ZeitbuchungError", { enumerable: true, get: function () { return ZeitbuchungError_1.ZeitbuchungError; } });
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,wEAAuE;AAA9D,8HAAA,qBAAqB,OAAA;AAC9B,kEAAiE;AAAxD,kHAAA,eAAe,OAAA;AACxB,sEAAqE;AAA5D,sHAAA,iBAAiB,OAAA;AAC1B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA"}
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,wEAAuE;AAA9D,8HAAA,qBAAqB,OAAA;AAC9B,kEAAiE;AAAxD,kHAAA,eAAe,OAAA;AACxB,sEAAqE;AAA5D,sHAAA,iBAAiB,OAAA;AAC1B,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA"}
|
||||||
Vendored
+31
@@ -57,6 +57,11 @@ export interface AgrarmonitorConnectorResult {
|
|||||||
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
||||||
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
||||||
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
||||||
|
createZeitbuchung(daten: ZeitbuchungDaten): Promise<{
|
||||||
|
ref: string;
|
||||||
|
}>;
|
||||||
|
updateZeitbuchung(ref: string, daten: ZeitbuchungDaten): Promise<void>;
|
||||||
|
deleteZeitbuchung(ref: string): Promise<void>;
|
||||||
}
|
}
|
||||||
export interface AgrarmonitorFreischaltungStatus {
|
export interface AgrarmonitorFreischaltungStatus {
|
||||||
freigeschaltet: boolean;
|
freigeschaltet: boolean;
|
||||||
@@ -200,4 +205,30 @@ export interface Taetigkeit {
|
|||||||
abwesenheit: boolean;
|
abwesenheit: boolean;
|
||||||
notizErzwingen: boolean;
|
notizErzwingen: boolean;
|
||||||
}
|
}
|
||||||
|
interface ZeitbuchungBasis {
|
||||||
|
/** Agrarmonitor-Mitarbeiter-ID */
|
||||||
|
mitarbeiterId: number;
|
||||||
|
start: Date;
|
||||||
|
ende: Date;
|
||||||
|
/**
|
||||||
|
* Arbeitstag (YYYY-MM-DD), zu dessen Schicht die Buchung gehoert.
|
||||||
|
* Standard: Datum von `start` in Europe/Berlin. Fuer Bloecke nach
|
||||||
|
* Mitternacht den Tag des Arbeitsbeginns angeben.
|
||||||
|
*/
|
||||||
|
datum?: string;
|
||||||
|
}
|
||||||
|
export interface TaetigkeitsbuchungDaten extends ZeitbuchungBasis {
|
||||||
|
art?: 'taetigkeit';
|
||||||
|
taetigkeitId: number;
|
||||||
|
maschinenId?: number;
|
||||||
|
/** Agrarmonitor hat kein Kostenstellen-Feld - landet als Zeile in der Notiz */
|
||||||
|
kostenstelle?: string;
|
||||||
|
notiz?: string;
|
||||||
|
}
|
||||||
|
/** Pausen haben in Agrarmonitor keine Notiz. */
|
||||||
|
export interface PausenbuchungDaten extends ZeitbuchungBasis {
|
||||||
|
art: 'pause';
|
||||||
|
}
|
||||||
|
export type ZeitbuchungDaten = TaetigkeitsbuchungDaten | PausenbuchungDaten;
|
||||||
|
export {};
|
||||||
//# sourceMappingURL=types.d.ts.map
|
//# sourceMappingURL=types.d.ts.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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 truncated
|
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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 truncated
|
||||||
@@ -120,12 +120,24 @@ Ablauf `createZeitbuchung`:
|
|||||||
|
|
||||||
`deleteZeitbuchung`: `deleteVerteilung.php`.
|
`deleteZeitbuchung`: `deleteVerteilung.php`.
|
||||||
|
|
||||||
## 4. Offene Fragen
|
## 4. Entscheidungen (14.10.2026)
|
||||||
|
|
||||||
1. **Kostenstelle:** Agrarmonitor hat keine Kostenstellen-Stammdaten und kein Kostenstellen-Feld an der Buchung. Optionen: (a) Kostenstelle in der App streichen, (b) stattdessen **Projekte** (`projekt_id`) verwenden, (c) Kostenstelle nur in die Notiz schreiben. `getKostenstellen()` habe ich deshalb in Schritt 2 **nicht** umgesetzt.
|
- **Kostenstelle:** wird als Zeile `Kostenstelle: …` an die Notiz gehängt.
|
||||||
2. **Pausen:** Die App überträgt keine Pausen. Die Lücke zwischen zwei Blöcken erscheint in Agrarmonitor dann als „Nicht zugeordnet“ innerhalb der Schicht und zählt als Anwesenheit/Arbeitszeit. Sollen Pausen als Agrarmonitor-Pause (`updatePause.php`) übertragen werden, oder soll jede Lücke eine eigene Schicht bekommen?
|
- **Pausen:** werden als Agrarmonitor-Pause übertragen (`editPauseForm.php` mit `shiftID` legt die Pause an, `updatePause.php` mit `neu: 1` speichert sie, `deletePause.php` `{pauseID, employeeID, date}` löscht). Eine Pause braucht eine vorhandene Schicht.
|
||||||
3. **Parallele Erfassung:** Stempeln Mitarbeiter zusätzlich in der Agrarmonitor-App, gibt es am selben Tag schon eine Schicht mit Verteilungen; App-Blöcke würden hineingebucht und können sich überschneiden. Vorher `checkPlausibelInSchicht.php` prüfen und bei Überschneidung ablehnen?
|
- **Überschneidungen:** Das Buchen in Agrarmonitor selbst wird für die Mitarbeiter abgeschaltet; der Connector prüft keine Überschneidungen.
|
||||||
4. **Über Mitternacht:** Verteilungs-Zeitstempel enthalten das Datum, das Nachtragen nur `HH:MM`. Blöcke über Mitternacht sind ungetestet.
|
- **Umsetzung:** `createZeitbuchung`, `updateZeitbuchung`, `deleteZeitbuchung` mit `ref` = `verteilung:<mitarbeiterId>:<datum>:<id>` bzw. `pause:…`.
|
||||||
5. **Maschinenpflicht:** Tätigkeiten mit „Maschinen zuordnen = Ja“ verlangen im UI eine Maschine (clientseitige Prüfung über `data-extra`). Ob der Server das ebenfalls erzwingt, ist ungetestet – die App sollte es vorher prüfen (Feld `maschineZuordnen` in `getTaetigkeiten`).
|
- **Notiz ändern:** Notizen löscht `module/notiz/api/delete.php` `{noteID}` (Antwort `true`). Beim Ändern wird die neue Notiz gespeichert und danach die alte gelöscht.
|
||||||
6. **Tätigkeitsauswahl:** Das Buchungsformular bietet nur aktive Tätigkeiten an; Abwesenheiten (Bereich 900) und „800 Pause“ sollten in der App vermutlich ausgeblendet werden.
|
|
||||||
7. **Verfasser der Notiz:** `verfasser_id` ist der Agrarmonitor-Benutzer des Connectors, nicht der Mitarbeiter.
|
### Echttest (14.10.2026, Mitarbeiter 3309, Tag 11.10.2026)
|
||||||
|
|
||||||
|
Über den Connector (eigenes, freigeschaltetes Gerät) angelegt: Block 08:00–09:00 mit Notiz und Kostenstelle (per Nachtragen), Pause 09:00–09:15, Block 09:15–10:00 mit Maschine 5033; danach Block 1 auf 08:55 und Pause auf 09:10 geändert, Notiz ersetzt; alles wieder gelöscht, der Tag ist danach leer. Ergebnis wie erwartet.
|
||||||
|
|
||||||
|
**Fund:** Agrarmonitor speichert eine unbekannte `taetigkeit_id` (999999) ohne Fehler – es entsteht eine Verteilung ohne Tätigkeit. Der Connector prüft die Tätigkeit deshalb gegen die Auswahl im Buchungsformular (`nachtragen-form.php` bzw. `editVerteilungForm.php`) und wirft sonst `ZeitbuchungError` (`grund: 'taetigkeit nicht buchbar'`). Maschinen-IDs werden nicht geprüft, weil das Formular nur eine Teilmenge der Maschinen anbietet.
|
||||||
|
|
||||||
|
## 5. Offene Fragen
|
||||||
|
|
||||||
|
1. **Pause als erste Buchung des Tages:** Ohne Schicht kann keine Pause gebucht werden (`ZeitbuchungError`). Die App überträgt Blöcke beim Beenden; eine Pause vor dem ersten Tätigkeitsblock landet dann bei `needs_manual`.
|
||||||
|
2. **Über Mitternacht:** Der Connector trägt bis 23:59 nach und korrigiert Verteilung und Schicht danach auf die echten Zeiten – im Mock getestet, im echten System nicht.
|
||||||
|
3. **Maschinenpflicht:** Tätigkeiten mit „Maschinen zuordnen = Ja“ verlangen im UI eine Maschine (clientseitige Prüfung über `data-extra`). Ob der Server das ebenfalls erzwingt, ist ungetestet – die App sollte es vorher prüfen (Feld `maschineZuordnen` in `getTaetigkeiten`).
|
||||||
|
4. **Tätigkeitsauswahl:** Das Buchungsformular bietet nur aktive Tätigkeiten an; Abwesenheiten (Bereich 900) und „800 Pause“ sollten in der App vermutlich ausgeblendet werden.
|
||||||
|
5. **Verfasser der Notiz:** `verfasser_id` ist der Agrarmonitor-Benutzer des Connectors, nicht der Mitarbeiter.
|
||||||
@@ -25,7 +25,9 @@ import type {
|
|||||||
Mitarbeiter,
|
Mitarbeiter,
|
||||||
Rechnungsdaten,
|
Rechnungsdaten,
|
||||||
Taetigkeit,
|
Taetigkeit,
|
||||||
|
ZeitbuchungDaten,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { ZeitbuchungError } from './ZeitbuchungError';
|
||||||
|
|
||||||
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
||||||
_agrarmonitorRetry?: boolean;
|
_agrarmonitorRetry?: boolean;
|
||||||
@@ -48,6 +50,41 @@ type CookieChanges = {
|
|||||||
removed: string[];
|
removed: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Ortszeit Europe/Berlin, minutengenau, in den Formaten, die Agrarmonitor braucht. */
|
||||||
|
type Zeitpunkt = {
|
||||||
|
/** YYYY-MM-DD HH:MM - lexikografisch vergleichbar */
|
||||||
|
vergleich: string;
|
||||||
|
/** dd.mm.yy (HH:MM) - Formularformat */
|
||||||
|
formular: string;
|
||||||
|
datum: string;
|
||||||
|
uhrzeit: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Zeitbuchung = {
|
||||||
|
art: 'taetigkeit' | 'pause';
|
||||||
|
mitarbeiterId: number;
|
||||||
|
datum: string;
|
||||||
|
start: Zeitpunkt;
|
||||||
|
ende: Zeitpunkt;
|
||||||
|
taetigkeitId: number;
|
||||||
|
maschinenId: number;
|
||||||
|
notiz: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ZeitbuchungRef = {
|
||||||
|
art: 'taetigkeit' | 'pause';
|
||||||
|
mitarbeiterId: number;
|
||||||
|
datum: string;
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Start und Ende als YYYY-MM-DD HH:MM (Ortszeit), wie getSchicht.php sie liefert. */
|
||||||
|
type Schicht = {
|
||||||
|
id: number;
|
||||||
|
start: string;
|
||||||
|
ende: string;
|
||||||
|
};
|
||||||
|
|
||||||
type AgrarmonitorCustomerPage = {
|
type AgrarmonitorCustomerPage = {
|
||||||
data?: unknown;
|
data?: unknown;
|
||||||
meta?: {
|
meta?: {
|
||||||
@@ -60,6 +97,15 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
public http!: AxiosInstance;
|
public http!: AxiosInstance;
|
||||||
|
|
||||||
private static readonly maxLivesearchSeiten = 500;
|
private static readonly maxLivesearchSeiten = 500;
|
||||||
|
private static readonly berlinFormat = new Intl.DateTimeFormat('de-DE', {
|
||||||
|
timeZone: 'Europe/Berlin',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
});
|
||||||
private static readonly s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
private static readonly s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
||||||
|
|
||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
@@ -660,6 +706,604 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
return Array.from(taetigkeiten.values());
|
return Array.from(taetigkeiten.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bucht einen Taetigkeitsblock oder eine Pause. Gibt es fuer Mitarbeiter und
|
||||||
|
* Arbeitstag noch keine Schicht, legt `nachtragen.php` Schicht und Verteilung
|
||||||
|
* an (Pausen brauchen eine vorhandene Schicht). Sonst kommt die Buchung in die
|
||||||
|
* passende Schicht, die bei Bedarf verlaengert wird.
|
||||||
|
*/
|
||||||
|
async createZeitbuchung(daten: ZeitbuchungDaten): Promise<{ ref: string }> {
|
||||||
|
const buchung = this.zeitbuchungAufbereiten(daten);
|
||||||
|
const schichten = await this.ladeSchichten(buchung.mitarbeiterId, buchung.datum);
|
||||||
|
|
||||||
|
if (schichten.length === 0) {
|
||||||
|
if (buchung.art === 'pause') {
|
||||||
|
throw new ZeitbuchungError(
|
||||||
|
`Pause am ${buchung.datum} kann nicht gebucht werden: keine Schicht vorhanden`,
|
||||||
|
'keine Schicht vorhanden'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = await this.taetigkeitNachtragen(buchung);
|
||||||
|
return { ref: this.zeitbuchungRef(buchung, id) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const schicht = this.passendeSchicht(schichten, buchung);
|
||||||
|
const schichtVorher = { ...schicht };
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const id =
|
||||||
|
buchung.art === 'pause'
|
||||||
|
? await this.pauseAnlegen(buchung, schicht.id)
|
||||||
|
: await this.verteilungAnlegen(buchung, schicht.id);
|
||||||
|
|
||||||
|
return { ref: this.zeitbuchungRef(buchung, id) };
|
||||||
|
} catch (error) {
|
||||||
|
if (schicht.start !== schichtVorher.start || schicht.ende !== schichtVorher.ende) {
|
||||||
|
await this.schichtSetzen(schichtVorher).catch(restoreError => {
|
||||||
|
this.logger?.warn?.('Schicht konnte nicht zurueckgesetzt werden', {
|
||||||
|
schichtId: schichtVorher.id,
|
||||||
|
fehler: restoreError instanceof Error ? restoreError.message : String(restoreError),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateZeitbuchung(ref: string, daten: ZeitbuchungDaten): Promise<void> {
|
||||||
|
const zielRef = this.parseZeitbuchungRef(ref);
|
||||||
|
const buchung = this.zeitbuchungAufbereiten(daten);
|
||||||
|
|
||||||
|
if (buchung.art !== zielRef.art) {
|
||||||
|
throw new ZeitbuchungError('Art der Buchung (Taetigkeit/Pause) kann nicht geaendert werden', 'art geaendert');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buchung.mitarbeiterId !== zielRef.mitarbeiterId || buchung.datum !== zielRef.datum) {
|
||||||
|
throw new ZeitbuchungError(
|
||||||
|
'Mitarbeiter oder Arbeitstag einer Buchung kann nicht geaendert werden',
|
||||||
|
'mitarbeiter oder datum geaendert'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(zielRef.mitarbeiterId, zielRef.datum);
|
||||||
|
const zeilenKlasse = zielRef.art === 'pause' ? 'pause_betrieb' : 'betrieb';
|
||||||
|
const schichtId = this.parseNumber(
|
||||||
|
tagesansicht
|
||||||
|
.querySelector(`tr.${zeilenKlasse}[id="${zielRef.id}"]`)
|
||||||
|
?.closest('table.verteilung_table')
|
||||||
|
?.getAttribute('data-schicht')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!schichtId) {
|
||||||
|
throw new ZeitbuchungError(`Buchung ${ref} nicht gefunden`, 'nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
const schicht = await this.ladeSchicht(schichtId);
|
||||||
|
|
||||||
|
if (schicht) {
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buchung.art === 'pause') {
|
||||||
|
await this.pauseSpeichern(buchung, zielRef.id, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: zielRef.mitarbeiterId,
|
||||||
|
date: zielRef.datum,
|
||||||
|
distributionID: String(zielRef.id),
|
||||||
|
});
|
||||||
|
this.taetigkeitPruefen(formular, buchung.taetigkeitId);
|
||||||
|
|
||||||
|
const notizen = Array.from(formular.querySelectorAll('#notizen_table tbody tr[data-id]'))
|
||||||
|
.map(row => ({
|
||||||
|
id: this.parseNumber(row.getAttribute('data-id')),
|
||||||
|
text: this.normalisiereText(row.querySelectorAll('td')[2]?.textContent ?? ''),
|
||||||
|
}))
|
||||||
|
.filter(notiz => notiz.id > 0);
|
||||||
|
const notizUnveraendert =
|
||||||
|
(notizen.length === 0 && !buchung.notiz) ||
|
||||||
|
(notizen.length === 1 && notizen[0].text === this.normalisiereText(buchung.notiz));
|
||||||
|
|
||||||
|
await this.verteilungSpeichern(buchung, zielRef.id, {
|
||||||
|
alteTaetigkeitId: this.selectedValue(formular, '#taetigkeit_id') || '0',
|
||||||
|
projektId: this.selectedValue(formular, '#projekt_id') || '0',
|
||||||
|
verfasserId: formular.querySelector<HTMLInputElement>('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: notizUnveraendert ? '' : buchung.notiz,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Alte Notizen erst nach erfolgreichem Speichern entfernen.
|
||||||
|
if (!notizUnveraendert) {
|
||||||
|
for (const notiz of notizen) {
|
||||||
|
await this.zeitbuchungPost('/module/notiz/api/delete.php', { noteID: notiz.id }, 'Notiz loeschen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteZeitbuchung(ref: string): Promise<void> {
|
||||||
|
const zielRef = this.parseZeitbuchungRef(ref);
|
||||||
|
|
||||||
|
if (zielRef.art === 'pause') {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/deletePause.php',
|
||||||
|
{ pauseID: zielRef.id, employeeID: zielRef.mitarbeiterId, date: zielRef.datum },
|
||||||
|
'Pause loeschen'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/deleteVerteilung.php',
|
||||||
|
{ distributionID: zielRef.id, employeeID: zielRef.mitarbeiterId, date: zielRef.datum },
|
||||||
|
'Buchung loeschen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private zeitbuchungAufbereiten(daten: ZeitbuchungDaten): Zeitbuchung {
|
||||||
|
if (!Number.isInteger(daten.mitarbeiterId) || daten.mitarbeiterId <= 0) {
|
||||||
|
throw new ZeitbuchungError('Ungueltige Mitarbeiter-ID', 'mitarbeiterId');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(daten.start instanceof Date) || Number.isNaN(daten.start.getTime())) {
|
||||||
|
throw new ZeitbuchungError('Ungueltiger Start', 'start');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(daten.ende instanceof Date) || Number.isNaN(daten.ende.getTime())) {
|
||||||
|
throw new ZeitbuchungError('Ungueltiges Ende', 'ende');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daten.datum !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(daten.datum)) {
|
||||||
|
throw new ZeitbuchungError('Ungueltiger Arbeitstag, erwartet YYYY-MM-DD', 'datum');
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = this.zeitpunkt(daten.start);
|
||||||
|
const ende = this.zeitpunkt(daten.ende);
|
||||||
|
|
||||||
|
if (ende.vergleich <= start.vergleich) {
|
||||||
|
throw new ZeitbuchungError('Ende muss mindestens eine Minute nach dem Start liegen', 'dauer');
|
||||||
|
}
|
||||||
|
|
||||||
|
const basis = {
|
||||||
|
mitarbeiterId: daten.mitarbeiterId,
|
||||||
|
datum: daten.datum ?? start.datum,
|
||||||
|
start,
|
||||||
|
ende,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (daten.art === 'pause') {
|
||||||
|
return { ...basis, art: 'pause', taetigkeitId: 0, maschinenId: 0, notiz: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(daten.taetigkeitId) || daten.taetigkeitId <= 0) {
|
||||||
|
throw new ZeitbuchungError('Ungueltige Taetigkeits-ID', 'taetigkeitId');
|
||||||
|
}
|
||||||
|
|
||||||
|
const maschinenId = daten.maschinenId ?? 0;
|
||||||
|
|
||||||
|
if (!Number.isInteger(maschinenId) || maschinenId < 0) {
|
||||||
|
throw new ZeitbuchungError('Ungueltige Maschinen-ID', 'maschinenId');
|
||||||
|
}
|
||||||
|
|
||||||
|
const kostenstelle = daten.kostenstelle?.trim();
|
||||||
|
const notiz = [daten.notiz?.trim(), kostenstelle ? `Kostenstelle: ${kostenstelle}` : '']
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return { ...basis, art: 'taetigkeit', taetigkeitId: daten.taetigkeitId, maschinenId, notiz };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rundet auf die volle Minute - Agrarmonitor speichert keine Sekunden. */
|
||||||
|
private zeitpunkt(date: Date): Zeitpunkt {
|
||||||
|
const gerundet = new Date(Math.round(date.getTime() / 60000) * 60000);
|
||||||
|
const teile = Object.fromEntries(
|
||||||
|
AgrarmonitorConnector.berlinFormat.formatToParts(gerundet).map(teil => [teil.type, teil.value])
|
||||||
|
);
|
||||||
|
const datum = `${teile.year}-${teile.month}-${teile.day}`;
|
||||||
|
const uhrzeit = `${teile.hour}:${teile.minute}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
vergleich: `${datum} ${uhrzeit}`,
|
||||||
|
formular: this.vergleichZuFormular(`${datum} ${uhrzeit}`),
|
||||||
|
datum,
|
||||||
|
uhrzeit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** YYYY-MM-DD HH:MM -> dd.mm.yy (HH:MM) */
|
||||||
|
private vergleichZuFormular(wert: string): string {
|
||||||
|
const [datum, uhrzeit] = wert.split(' ');
|
||||||
|
const [jahr, monat, tag] = datum.split('-');
|
||||||
|
return `${tag}.${monat}.${jahr.slice(2)} (${uhrzeit})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private zeitbuchungRef(buchung: Zeitbuchung, id: number): string {
|
||||||
|
return `${buchung.art === 'pause' ? 'pause' : 'verteilung'}:${buchung.mitarbeiterId}:${buchung.datum}:${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseZeitbuchungRef(ref: string): ZeitbuchungRef {
|
||||||
|
const match = /^(verteilung|pause):(\d+):(\d{4}-\d{2}-\d{2}):(\d+)$/.exec(ref);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new ZeitbuchungError(`Ungueltige Zeitbuchungs-Referenz: ${ref}`, 'ref');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
art: match[1] === 'pause' ? 'pause' : 'taetigkeit',
|
||||||
|
mitarbeiterId: Number(match[2]),
|
||||||
|
datum: match[3],
|
||||||
|
id: Number(match[4]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ladeTagesansicht(mitarbeiterId: number, datum: string): Promise<Document> {
|
||||||
|
const response = await this.http.get(`/arbeitszeit/detail/${mitarbeiterId}/${datum}`);
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
return this.parseHtmlDocument(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ladeSchichten(mitarbeiterId: number, datum: string): Promise<Schicht[]> {
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(mitarbeiterId, datum);
|
||||||
|
const ids = new Set(
|
||||||
|
Array.from(tagesansicht.querySelectorAll('table.verteilung_table[data-schicht]'))
|
||||||
|
.map(table => this.parseNumber(table.getAttribute('data-schicht')))
|
||||||
|
.filter(id => id > 0)
|
||||||
|
);
|
||||||
|
const schichten: Schicht[] = [];
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const schicht = await this.ladeSchicht(id);
|
||||||
|
|
||||||
|
if (schicht) {
|
||||||
|
schichten.push(schicht);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schichten;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ladeSchicht(schichtId: number): Promise<Schicht | null> {
|
||||||
|
const response = await this.http.get('/module/arbeitszeit/api/getSchicht.php', {
|
||||||
|
params: { schicht_id: schichtId },
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
const data = (typeof response.data === 'string' ? JSON.parse(response.data || 'null') : response.data) as {
|
||||||
|
timestamp_start?: string;
|
||||||
|
timestamp_ende?: string;
|
||||||
|
ist_deleted?: number | string;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
if (!data?.timestamp_start || Number(data.ist_deleted) === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = data.timestamp_start.slice(0, 16);
|
||||||
|
const ende = data.timestamp_ende && !data.timestamp_ende.startsWith('0000') ? data.timestamp_ende.slice(0, 16) : start;
|
||||||
|
|
||||||
|
return { id: schichtId, start, ende };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Schicht, die sich mit der Buchung ueberschneidet, sonst die letzte davor, sonst die erste. */
|
||||||
|
private passendeSchicht(schichten: Schicht[], buchung: Zeitbuchung): Schicht {
|
||||||
|
return (
|
||||||
|
schichten.find(schicht => schicht.start <= buchung.ende.vergleich && buchung.start.vergleich <= schicht.ende) ??
|
||||||
|
schichten.filter(schicht => schicht.start <= buchung.start.vergleich).at(-1) ??
|
||||||
|
schichten[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Neue Verteilungen verlaengern die Schicht in Agrarmonitor nicht von selbst. */
|
||||||
|
private async schichtErweitern(schicht: Schicht, buchung: Zeitbuchung): Promise<void> {
|
||||||
|
const start = buchung.start.vergleich < schicht.start ? buchung.start.vergleich : schicht.start;
|
||||||
|
const ende = buchung.ende.vergleich > schicht.ende ? buchung.ende.vergleich : schicht.ende;
|
||||||
|
|
||||||
|
if (start === schicht.start && ende === schicht.ende) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.schichtSetzen({ id: schicht.id, start, ende });
|
||||||
|
schicht.start = start;
|
||||||
|
schicht.ende = ende;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async schichtSetzen(schicht: Schicht): Promise<void> {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/updateSchicht.php',
|
||||||
|
{
|
||||||
|
shiftID: schicht.id,
|
||||||
|
timestampStart: this.vergleichZuFormular(schicht.start),
|
||||||
|
timestampEnd: this.vergleichZuFormular(schicht.ende),
|
||||||
|
},
|
||||||
|
'Schicht anpassen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erste Buchung des Tages: nachtragen.php legt Schicht und Verteilung an,
|
||||||
|
* liefert aber keine ID. Die Verteilung wird danach in der Tagesansicht gesucht.
|
||||||
|
*/
|
||||||
|
private async taetigkeitNachtragen(buchung: Zeitbuchung): Promise<number> {
|
||||||
|
const nachtragenFormular = await this.http.get('/module/arbeitszeit/api/nachtragen-form.php');
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
this.taetigkeitPruefen(this.parseHtmlDocument(nachtragenFormular.data), buchung.taetigkeitId);
|
||||||
|
|
||||||
|
const amArbeitstag = buchung.start.datum === buchung.datum && buchung.ende.datum === buchung.datum;
|
||||||
|
const form: Record<string, string> = {
|
||||||
|
taetigkeit_id: String(buchung.taetigkeitId),
|
||||||
|
projekt_id: '0',
|
||||||
|
arbeitszeit_start: buchung.start.uhrzeit,
|
||||||
|
// Nachtragen kennt nur Uhrzeiten; ueber Mitternacht wird unten korrigiert.
|
||||||
|
arbeitszeit_ende: amArbeitstag ? buchung.ende.uhrzeit : '23:59',
|
||||||
|
arbeitzeit_sollstunden: '',
|
||||||
|
pause_start: '',
|
||||||
|
pause_ende: '',
|
||||||
|
maschine_id: String(buchung.maschinenId),
|
||||||
|
notiz: buchung.notiz,
|
||||||
|
uebernehmen: '1',
|
||||||
|
mitarbeitertyp: '1',
|
||||||
|
mitarbeiter_id: String(buchung.mitarbeiterId),
|
||||||
|
kategorie_id: '0',
|
||||||
|
};
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
|
||||||
|
for (const [name, value] of Object.entries(form)) {
|
||||||
|
body.append(`form[${name}]`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append('tage[]', buchung.datum);
|
||||||
|
body.append('anzahl_datum', '1');
|
||||||
|
body.append('planungAusMitarbeiterId', '0');
|
||||||
|
|
||||||
|
const response = await this.http.post('/module/arbeitszeit/api/nachtragen.php', body, {
|
||||||
|
...this.formPostConfig('/arbeitszeit'),
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
this.zeitbuchungAntwortPruefen(response, 'Nachtragen');
|
||||||
|
|
||||||
|
const tagesansicht = await this.ladeTagesansicht(buchung.mitarbeiterId, buchung.datum);
|
||||||
|
const zeilen = Array.from(tagesansicht.querySelectorAll('table.verteilung_table tr.betrieb[id]'));
|
||||||
|
|
||||||
|
if (zeilen.length !== 1) {
|
||||||
|
throw new ZeitbuchungError(
|
||||||
|
`Nachgetragene Buchung am ${buchung.datum} nicht eindeutig gefunden (${zeilen.length} Buchungen)`,
|
||||||
|
'nicht eindeutig'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = this.parseNumber(zeilen[0].id);
|
||||||
|
|
||||||
|
if (!amArbeitstag) {
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: buchung.mitarbeiterId,
|
||||||
|
date: buchung.datum,
|
||||||
|
distributionID: String(id),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.verteilungSpeichern(buchung, id, {
|
||||||
|
alteTaetigkeitId: String(buchung.taetigkeitId),
|
||||||
|
projektId: '0',
|
||||||
|
verfasserId: formular.querySelector<HTMLInputElement>('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const schicht = await this.ladeSchicht(
|
||||||
|
this.parseNumber(zeilen[0].closest('table.verteilung_table')?.getAttribute('data-schicht'))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (schicht) {
|
||||||
|
await this.schichtErweitern(schicht, buchung);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editVerteilungForm.php mit shiftID legt sofort eine leere Verteilung an
|
||||||
|
* (das Agrarmonitor-UI loescht sie bei "Abbrechen"). Schlaegt das Speichern
|
||||||
|
* fehl, wird dieser Platzhalter wieder entfernt.
|
||||||
|
*/
|
||||||
|
private async verteilungAnlegen(buchung: Zeitbuchung, schichtId: number): Promise<number> {
|
||||||
|
const formular = await this.ladeVerteilungFormular({
|
||||||
|
employeeID: buchung.mitarbeiterId,
|
||||||
|
date: buchung.datum,
|
||||||
|
shiftID: schichtId,
|
||||||
|
});
|
||||||
|
const id = this.parseNumber(formular.querySelector('#verteilung_form')?.getAttribute('data-id'));
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new ZeitbuchungError('Agrarmonitor hat keine neue Buchung angelegt', 'keine verteilung_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.taetigkeitPruefen(formular, buchung.taetigkeitId);
|
||||||
|
await this.verteilungSpeichern(buchung, id, {
|
||||||
|
alteTaetigkeitId: '0',
|
||||||
|
projektId: '0',
|
||||||
|
verfasserId: formular.querySelector<HTMLInputElement>('#verfasser_id')?.value ?? '',
|
||||||
|
notiz: buchung.notiz,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/deleteVerteilung.php',
|
||||||
|
{ distributionID: id, employeeID: buchung.mitarbeiterId, date: buchung.datum },
|
||||||
|
'Platzhalter loeschen'
|
||||||
|
).catch(() => undefined);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ladeVerteilungFormular(body: Record<string, string | number>): Promise<Document> {
|
||||||
|
const html = await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/editVerteilungForm.php',
|
||||||
|
body,
|
||||||
|
'Buchungsformular laden'
|
||||||
|
);
|
||||||
|
const formular = this.parseHtmlDocument(html);
|
||||||
|
|
||||||
|
if (!formular.querySelector('#verteilung_form')) {
|
||||||
|
throw new ZeitbuchungError('Buchungsformular nicht gefunden', 'kein verteilung_form');
|
||||||
|
}
|
||||||
|
|
||||||
|
return formular;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async verteilungSpeichern(
|
||||||
|
buchung: Zeitbuchung,
|
||||||
|
id: number,
|
||||||
|
formular: { alteTaetigkeitId: string; projektId: string; verfasserId: string; notiz: string }
|
||||||
|
): Promise<void> {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/updateVerteilung.php',
|
||||||
|
{
|
||||||
|
mitarbeiterId: buchung.mitarbeiterId,
|
||||||
|
verteilungId: String(id),
|
||||||
|
verteilungForm: {
|
||||||
|
timestamp_start: buchung.start.formular,
|
||||||
|
timestamp_ende: buchung.ende.formular,
|
||||||
|
alte_taetigkeit_id: formular.alteTaetigkeitId,
|
||||||
|
taetigkeit_id: String(buchung.taetigkeitId),
|
||||||
|
ganzertag: '1',
|
||||||
|
maschine_id: String(buchung.maschinenId),
|
||||||
|
projekt_id: formular.projektId,
|
||||||
|
},
|
||||||
|
// Nicht leere notizfeld werden als neue Notiz an die Verteilung gehaengt.
|
||||||
|
notizForm: {
|
||||||
|
notizfeld: formular.notiz,
|
||||||
|
verfasser_id: formular.verfasserId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Buchung speichern'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wie bei Verteilungen legt editPauseForm.php mit shiftID sofort eine Pause an. */
|
||||||
|
private async pauseAnlegen(buchung: Zeitbuchung, schichtId: number): Promise<number> {
|
||||||
|
const html = await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/editPauseForm.php',
|
||||||
|
{ employeeID: buchung.mitarbeiterId, date: buchung.datum, shiftID: schichtId },
|
||||||
|
'Pausenformular laden'
|
||||||
|
);
|
||||||
|
const id = this.parseNumber(this.parseHtmlDocument(html).querySelector('#pause_form')?.getAttribute('data-id'));
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new ZeitbuchungError('Agrarmonitor hat keine neue Pause angelegt', 'keine pause_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.pauseSpeichern(buchung, id, 1);
|
||||||
|
} catch (error) {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/deletePause.php',
|
||||||
|
{ pauseID: id, employeeID: buchung.mitarbeiterId, date: buchung.datum },
|
||||||
|
'Platzhalter loeschen'
|
||||||
|
).catch(() => undefined);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pauseSpeichern(buchung: Zeitbuchung, id: number, neu: 0 | 1): Promise<void> {
|
||||||
|
await this.zeitbuchungPost(
|
||||||
|
'/module/arbeitszeit/api/updatePause.php',
|
||||||
|
{
|
||||||
|
mitarbeiterId: buchung.mitarbeiterId,
|
||||||
|
pause_id: String(id),
|
||||||
|
pauseForm: {
|
||||||
|
pause_start: buchung.start.formular,
|
||||||
|
pause_ende: buchung.ende.formular,
|
||||||
|
},
|
||||||
|
neu,
|
||||||
|
},
|
||||||
|
'Pause speichern'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-POST an die Arbeitszeit-API. 4xx (z. B. 422 mit Grund im Body) wird
|
||||||
|
* als ZeitbuchungError geworfen; Netzwerk- und 5xx-Fehler bleiben Axios-Fehler.
|
||||||
|
*/
|
||||||
|
private async zeitbuchungPost(url: string, body: unknown, aktion: string): Promise<unknown> {
|
||||||
|
const response = await this.http.post(url, body, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json, */*',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
Referer: `${this.baseUrl}/arbeitszeit`,
|
||||||
|
Origin: this.baseUrl,
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
this.zeitbuchungAntwortPruefen(response, aktion);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private zeitbuchungAntwortPruefen(response: AxiosResponse, aktion: string): void {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const grund = this.zeitbuchungGrund(response.data);
|
||||||
|
throw new ZeitbuchungError(`${aktion} fehlgeschlagen (HTTP ${response.status}): ${grund}`, grund, response.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 422-Antworten sehen so aus: {"verteilungForm":"ganzertag must be present"} */
|
||||||
|
private zeitbuchungGrund(data: unknown): string {
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
const texte = Object.values(data as Record<string, unknown>)
|
||||||
|
.flatMap(wert => (wert && typeof wert === 'object' ? Object.values(wert as Record<string, unknown>) : [wert]))
|
||||||
|
.filter((wert): wert is string => typeof wert === 'string' && wert.trim() !== '');
|
||||||
|
|
||||||
|
return texte.length ? texte.join('; ') : JSON.stringify(data).slice(0, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(data ?? '').trim().slice(0, 500) || 'keine Antwort';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agrarmonitor speichert auch unbekannte oder inaktive Taetigkeits-IDs (dann
|
||||||
|
* ohne Taetigkeit). Deshalb gegen die Auswahl im Buchungsformular pruefen.
|
||||||
|
*/
|
||||||
|
private taetigkeitPruefen(formular: Document, taetigkeitId: number): void {
|
||||||
|
const buchbar = Array.from(formular.querySelectorAll<HTMLOptionElement>('select#taetigkeit_id option')).map(
|
||||||
|
option => option.value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!buchbar.includes(String(taetigkeitId))) {
|
||||||
|
throw new ZeitbuchungError(
|
||||||
|
`Taetigkeit ${taetigkeitId} ist in Agrarmonitor nicht buchbar (unbekannt oder inaktiv)`,
|
||||||
|
'taetigkeit nicht buchbar'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectedValue(document: Document, selector: string): string {
|
||||||
|
const select = document.querySelector<HTMLSelectElement>(selector);
|
||||||
|
return select?.querySelector<HTMLOptionElement>('option[selected]')?.value ?? select?.value ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalisiereText(text: string): string {
|
||||||
|
return text.replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
|
||||||
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Agrarmonitor hat eine Zeitbuchung fachlich abgelehnt (z. B. HTTP 422 mit
|
||||||
|
* Grund im Body) oder die Buchung ist so nicht moeglich. Ein erneuter Versuch
|
||||||
|
* mit denselben Daten hilft nicht - anders als bei Netzwerk- oder 5xx-Fehlern,
|
||||||
|
* die als normale Fehler durchgereicht werden.
|
||||||
|
*/
|
||||||
|
export class ZeitbuchungError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public readonly grund: string,
|
||||||
|
public readonly status?: number
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ZeitbuchungError';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,16 @@ export { FileCookieStore } from './cookie-store/FileCookieStore';
|
|||||||
export { MemoryCookieStore } from './cookie-store/MemoryCookieStore';
|
export { MemoryCookieStore } from './cookie-store/MemoryCookieStore';
|
||||||
export { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
export { AgrarmonitorConnector } from './AgrarmonitorConnector';
|
||||||
export { createAgrarmonitorClient } from './createAgrarmonitorClient';
|
export { createAgrarmonitorClient } from './createAgrarmonitorClient';
|
||||||
|
export { ZeitbuchungError } from './ZeitbuchungError';
|
||||||
export type {
|
export type {
|
||||||
AgrarmonitorConnectorOptions,
|
AgrarmonitorConnectorOptions,
|
||||||
AgrarmonitorConnectorResult,
|
AgrarmonitorConnectorResult,
|
||||||
CookieEncryptor,
|
CookieEncryptor,
|
||||||
CookieStore,
|
CookieStore,
|
||||||
Logger,
|
Logger,
|
||||||
|
MaschineMitGruppe,
|
||||||
|
PausenbuchungDaten,
|
||||||
|
Taetigkeit,
|
||||||
|
TaetigkeitsbuchungDaten,
|
||||||
|
ZeitbuchungDaten,
|
||||||
} from './types';
|
} from './types';
|
||||||
@@ -63,6 +63,9 @@ export interface AgrarmonitorConnectorResult {
|
|||||||
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
|
||||||
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
|
||||||
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
getTaetigkeiten(): Promise<Taetigkeit[]>;
|
||||||
|
createZeitbuchung(daten: ZeitbuchungDaten): Promise<{ ref: string }>;
|
||||||
|
updateZeitbuchung(ref: string, daten: ZeitbuchungDaten): Promise<void>;
|
||||||
|
deleteZeitbuchung(ref: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgrarmonitorFreischaltungStatus {
|
export interface AgrarmonitorFreischaltungStatus {
|
||||||
@@ -224,3 +227,32 @@ export interface Taetigkeit {
|
|||||||
abwesenheit: boolean;
|
abwesenheit: boolean;
|
||||||
notizErzwingen: boolean;
|
notizErzwingen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ZeitbuchungBasis {
|
||||||
|
/** Agrarmonitor-Mitarbeiter-ID */
|
||||||
|
mitarbeiterId: number;
|
||||||
|
start: Date;
|
||||||
|
ende: Date;
|
||||||
|
/**
|
||||||
|
* Arbeitstag (YYYY-MM-DD), zu dessen Schicht die Buchung gehoert.
|
||||||
|
* Standard: Datum von `start` in Europe/Berlin. Fuer Bloecke nach
|
||||||
|
* Mitternacht den Tag des Arbeitsbeginns angeben.
|
||||||
|
*/
|
||||||
|
datum?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaetigkeitsbuchungDaten extends ZeitbuchungBasis {
|
||||||
|
art?: 'taetigkeit';
|
||||||
|
taetigkeitId: number;
|
||||||
|
maschinenId?: number;
|
||||||
|
/** Agrarmonitor hat kein Kostenstellen-Feld - landet als Zeile in der Notiz */
|
||||||
|
kostenstelle?: string;
|
||||||
|
notiz?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pausen haben in Agrarmonitor keine Notiz. */
|
||||||
|
export interface PausenbuchungDaten extends ZeitbuchungBasis {
|
||||||
|
art: 'pause';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ZeitbuchungDaten = TaetigkeitsbuchungDaten | PausenbuchungDaten;
|
||||||
@@ -0,0 +1,624 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const http = require('node:http');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
const { AgrarmonitorConnector, MemoryCookieStore, ZeitbuchungError } = require('../dist');
|
||||||
|
|
||||||
|
const loginPage = `
|
||||||
|
<html>
|
||||||
|
<head><title>Anmeldung - AGRARMONITOR</title></head>
|
||||||
|
<body><form><input name="nonce" value="test-nonce" /><button>Einloggen</button></form></body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// "dd.mm.yy (HH:MM)" -> "YYYY-MM-DD HH:MM"
|
||||||
|
function formularZuVergleich(wert) {
|
||||||
|
const [, tag, monat, jahr, uhrzeit] = /^(\d{2})\.(\d{2})\.(\d{2}) \((\d{2}:\d{2})\)$/.exec(wert);
|
||||||
|
return `20${jahr}-${monat}-${tag} ${uhrzeit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nachbildung der Arbeitszeit-Endpunkte, so wie sie sich am 14.10.2026 im
|
||||||
|
* echten Agrarmonitor verhalten haben (siehe docs/zeitbuchung-analyse.md).
|
||||||
|
*/
|
||||||
|
function createServer() {
|
||||||
|
const requests = [];
|
||||||
|
const state = { schichten: new Map(), verteilungen: new Map(), pausen: new Map() };
|
||||||
|
let loggedIn = false;
|
||||||
|
let naechsteId = 100;
|
||||||
|
const neueId = () => (naechsteId += 1);
|
||||||
|
|
||||||
|
const json = (res, status, data) => {
|
||||||
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(data === undefined ? '' : JSON.stringify(data));
|
||||||
|
};
|
||||||
|
const html = (res, body) => {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(body);
|
||||||
|
};
|
||||||
|
const schichtAufraeumen = schichtId => {
|
||||||
|
const leer = ![...state.verteilungen.values()].some(v => v.schichtId === schichtId);
|
||||||
|
if (leer) {
|
||||||
|
state.schichten.get(schichtId).deleted = true;
|
||||||
|
}
|
||||||
|
return leer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagesansicht = (mitarbeiterId, datum) =>
|
||||||
|
[...state.schichten.entries()]
|
||||||
|
.filter(([, s]) => !s.deleted && s.mitarbeiterId === mitarbeiterId && s.datum === datum)
|
||||||
|
.map(([schichtId]) => {
|
||||||
|
const zeilen = [
|
||||||
|
...[...state.verteilungen.entries()]
|
||||||
|
.filter(([, v]) => v.schichtId === schichtId)
|
||||||
|
.map(([id, v]) => `<tr id="${id}" class=" betrieb "><td>${v.start.slice(11)} - ${v.ende.slice(11)}</td></tr>`),
|
||||||
|
...[...state.pausen.entries()]
|
||||||
|
.filter(([, p]) => p.schichtId === schichtId)
|
||||||
|
.map(([id, p]) => `<tr id="${id}" class="pause_betrieb text-orange"><td>${p.start.slice(11)} - ${p.ende.slice(11)}</td></tr>`),
|
||||||
|
];
|
||||||
|
return `<table class="alternate verteilung_table table-fixed" id="verteilung_table_${schichtId}" data-schicht="${schichtId}"><tbody>${zeilen.join('')}</tbody></table>`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
// Buchbar sind nur aktive Taetigkeiten; 999 ist unbekannt.
|
||||||
|
const taetigkeitAuswahl = ausgewaehlt => `
|
||||||
|
<select name="taetigkeit_id" id="taetigkeit_id">
|
||||||
|
<option value="870"${ausgewaehlt === '870' ? ' selected="selected"' : ''}>110 Arbeiten</option>
|
||||||
|
<option value="869"${ausgewaehlt === '869' ? ' selected="selected"' : ''}>100 Reparatur</option>
|
||||||
|
</select>`;
|
||||||
|
|
||||||
|
const verteilungFormular = id => {
|
||||||
|
const v = state.verteilungen.get(id);
|
||||||
|
return `
|
||||||
|
<form id="verteilung_form" data-id="${id}">
|
||||||
|
<input type="text" name="timestamp_start" id="timestamp_start" value="">
|
||||||
|
<input type="hidden" name="alte_taetigkeit_id" id="alte_taetigkeit_id" value="${v.taetigkeitId}">
|
||||||
|
${taetigkeitAuswahl(v.taetigkeitId)}
|
||||||
|
<select name="projekt_id" id="projekt_id">
|
||||||
|
<option value="0">Kein Projekt</option>
|
||||||
|
<option value="12"${v.projektId === '12' ? ' selected="selected"' : ''}>Projekt</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
<form id="notiz-form">
|
||||||
|
<textarea name="notizfeld" id="notizfeld"></textarea>
|
||||||
|
<input type="text" name="verfasser_id" id="verfasser_id" value="6">
|
||||||
|
</form>
|
||||||
|
<table id="notizen_table"><tbody>
|
||||||
|
${v.notizen.map(n => `<tr data-id="${n.id}"><td>14.10.26 (11:15)</td><td>Connector</td><td class="notiz">${n.text.replace(/\n/g, '<br />\n')}</td></tr>`).join('')}
|
||||||
|
</tbody></table>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
let body = '';
|
||||||
|
req.on('data', chunk => {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
req.on('end', () => {
|
||||||
|
const url = new URL(req.url, 'http://localhost');
|
||||||
|
const istJson = (req.headers['content-type'] ?? '').includes('application/json');
|
||||||
|
const daten = istJson && body ? JSON.parse(body) : body;
|
||||||
|
requests.push({ method: req.method, path: url.pathname, body: daten });
|
||||||
|
|
||||||
|
if (req.method === 'POST' && url.pathname === '/login/api/login.php') {
|
||||||
|
loggedIn = true;
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': 'agrar_session=test; Path=/' });
|
||||||
|
res.end(JSON.stringify({ success: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loggedIn) {
|
||||||
|
html(res, loginPage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail = /^\/arbeitszeit\/detail\/(\d+)\/(\d{4}-\d{2}-\d{2})$/.exec(url.pathname);
|
||||||
|
|
||||||
|
if (detail) {
|
||||||
|
html(res, `<html><body>${tagesansicht(Number(detail[1]), detail[2])}</body></html>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (url.pathname) {
|
||||||
|
case '/':
|
||||||
|
html(res, '<html><body>Mein AM</body></html>');
|
||||||
|
return;
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/nachtragen-form.php':
|
||||||
|
html(res, `<form id="nachtragen_form">${taetigkeitAuswahl('0')}</form>`);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/getSchicht.php': {
|
||||||
|
const id = Number(url.searchParams.get('schicht_id'));
|
||||||
|
const s = state.schichten.get(id);
|
||||||
|
json(res, 200, {
|
||||||
|
id,
|
||||||
|
mitarbeiter_id: s.mitarbeiterId,
|
||||||
|
timestamp_start: `${s.start}:00`,
|
||||||
|
timestamp_ende: `${s.ende}:00`,
|
||||||
|
ist_deleted: s.deleted ? 1 : 0,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/nachtragen.php': {
|
||||||
|
const params = new URLSearchParams(body);
|
||||||
|
const datum = params.get('tage[]');
|
||||||
|
const schichtId = neueId();
|
||||||
|
const start = `${datum} ${params.get('form[arbeitszeit_start]')}`;
|
||||||
|
const ende = `${datum} ${params.get('form[arbeitszeit_ende]')}`;
|
||||||
|
state.schichten.set(schichtId, { mitarbeiterId: Number(params.get('form[mitarbeiter_id]')), datum, start, ende });
|
||||||
|
const notiz = params.get('form[notiz]');
|
||||||
|
state.verteilungen.set(neueId(), {
|
||||||
|
schichtId,
|
||||||
|
start,
|
||||||
|
ende,
|
||||||
|
taetigkeitId: params.get('form[taetigkeit_id]'),
|
||||||
|
maschinenId: params.get('form[maschine_id]'),
|
||||||
|
projektId: '0',
|
||||||
|
notizen: notiz ? [{ id: neueId(), text: notiz }] : [],
|
||||||
|
});
|
||||||
|
json(res, 200, { action: 'link', target: 'arbeitszeit/plan' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/editVerteilungForm.php': {
|
||||||
|
let id = Number(daten.distributionID);
|
||||||
|
if (daten.shiftID) {
|
||||||
|
// Wie im echten System: Formular fuer "neu" legt sofort einen Platzhalter an.
|
||||||
|
const s = state.schichten.get(daten.shiftID);
|
||||||
|
id = neueId();
|
||||||
|
state.verteilungen.set(id, {
|
||||||
|
schichtId: daten.shiftID,
|
||||||
|
start: s.start,
|
||||||
|
ende: s.start,
|
||||||
|
taetigkeitId: '0',
|
||||||
|
maschinenId: '0',
|
||||||
|
projektId: '0',
|
||||||
|
notizen: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
html(res, state.verteilungen.has(id) ? verteilungFormular(id) : '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/updateVerteilung.php': {
|
||||||
|
const form = daten.verteilungForm;
|
||||||
|
if (form.ganzertag === undefined) {
|
||||||
|
json(res, 422, { verteilungForm: 'ganzertag must be present' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (form.maschine_id === '999') {
|
||||||
|
json(res, 422, { verteilungForm: 'Maschine ungueltig' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const v = state.verteilungen.get(Number(daten.verteilungId));
|
||||||
|
Object.assign(v, {
|
||||||
|
start: formularZuVergleich(form.timestamp_start),
|
||||||
|
ende: formularZuVergleich(form.timestamp_ende),
|
||||||
|
taetigkeitId: form.taetigkeit_id,
|
||||||
|
maschinenId: form.maschine_id,
|
||||||
|
projektId: form.projekt_id,
|
||||||
|
});
|
||||||
|
if (daten.notizForm.notizfeld) {
|
||||||
|
v.notizen.push({ id: neueId(), text: daten.notizForm.notizfeld });
|
||||||
|
}
|
||||||
|
json(res, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/deleteVerteilung.php': {
|
||||||
|
const v = state.verteilungen.get(daten.distributionID);
|
||||||
|
state.verteilungen.delete(daten.distributionID);
|
||||||
|
json(res, 200, schichtAufraeumen(v.schichtId) ? { action: 'link', target: 'arbeitszeit' } : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/editPauseForm.php': {
|
||||||
|
const s = state.schichten.get(daten.shiftID);
|
||||||
|
const id = neueId();
|
||||||
|
state.pausen.set(id, { schichtId: daten.shiftID, start: s.start, ende: s.ende });
|
||||||
|
html(res, `<form id="pause_form" data-id="${id}"><input name="pause_start" id="timestamp_start"></form>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/updatePause.php': {
|
||||||
|
const p = state.pausen.get(Number(daten.pause_id));
|
||||||
|
p.start = formularZuVergleich(daten.pauseForm.pause_start);
|
||||||
|
p.ende = formularZuVergleich(daten.pauseForm.pause_ende);
|
||||||
|
json(res, 200, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/deletePause.php':
|
||||||
|
state.pausen.delete(daten.pauseID);
|
||||||
|
json(res, 200, null);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case '/module/arbeitszeit/api/updateSchicht.php': {
|
||||||
|
const s = state.schichten.get(daten.shiftID);
|
||||||
|
s.start = formularZuVergleich(daten.timestampStart);
|
||||||
|
s.ende = formularZuVergleich(daten.timestampEnd);
|
||||||
|
json(res, 200, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '/module/notiz/api/delete.php':
|
||||||
|
for (const v of state.verteilungen.values()) {
|
||||||
|
v.notizen = v.notizen.filter(n => n.id !== daten.noteID);
|
||||||
|
}
|
||||||
|
json(res, 200, true);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end('not found');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
server,
|
||||||
|
requests,
|
||||||
|
state,
|
||||||
|
neueId,
|
||||||
|
expireSession() {
|
||||||
|
loggedIn = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startConnector(t) {
|
||||||
|
const fake = createServer();
|
||||||
|
|
||||||
|
await new Promise(resolve => fake.server.listen(0, '127.0.0.1', resolve));
|
||||||
|
t.after(() => new Promise(resolve => fake.server.close(resolve)));
|
||||||
|
|
||||||
|
const connector = new AgrarmonitorConnector({
|
||||||
|
baseUrl: `http://127.0.0.1:${fake.server.address().port}`,
|
||||||
|
username: 'demo',
|
||||||
|
password: 'secret',
|
||||||
|
cookieStore: new MemoryCookieStore(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await connector.init();
|
||||||
|
|
||||||
|
return { connector, ...fake };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Legt eine Schicht mit einer Verteilung an, wie nach einem ersten Nachtragen. */
|
||||||
|
function schichtAnlegen(state, neueId, { start = '2026-10-14 08:00', ende = '2026-10-14 09:00' } = {}) {
|
||||||
|
const schichtId = neueId();
|
||||||
|
const verteilungId = neueId();
|
||||||
|
state.schichten.set(schichtId, { mitarbeiterId: 3309, datum: '2026-10-14', start, ende });
|
||||||
|
state.verteilungen.set(verteilungId, {
|
||||||
|
schichtId,
|
||||||
|
start,
|
||||||
|
ende,
|
||||||
|
taetigkeitId: '870',
|
||||||
|
maschinenId: '0',
|
||||||
|
projektId: '12',
|
||||||
|
notizen: [{ id: neueId(), text: 'Alte Notiz' }],
|
||||||
|
});
|
||||||
|
return { schichtId, verteilungId };
|
||||||
|
}
|
||||||
|
|
||||||
|
const posts = (requests, path) => requests.filter(r => r.method === 'POST' && r.path === path);
|
||||||
|
const schreibendePosts = requests =>
|
||||||
|
requests.filter(r => r.method === 'POST' && r.path.startsWith('/module/') && !r.path.endsWith('Form.php'));
|
||||||
|
|
||||||
|
// 14.10.2026 ist Sommerzeit: 06:00Z = 08:00 Uhr in Berlin.
|
||||||
|
const block = (start, ende, extra = {}) => ({
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
start: new Date(start),
|
||||||
|
ende: new Date(ende),
|
||||||
|
taetigkeitId: 870,
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung traegt ohne Schicht Schicht und Taetigkeit nach', async t => {
|
||||||
|
const { connector, requests, state } = await startConnector(t);
|
||||||
|
|
||||||
|
const { ref } = await connector.createZeitbuchung(
|
||||||
|
block('2026-10-14T06:00:00Z', '2026-10-14T07:00:20Z', {
|
||||||
|
maschinenId: 5033,
|
||||||
|
notiz: 'Hydraulik abgedichtet',
|
||||||
|
kostenstelle: '4711',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const [nachtragen] = posts(requests, '/module/arbeitszeit/api/nachtragen.php');
|
||||||
|
const params = new URLSearchParams(nachtragen.body);
|
||||||
|
assert.equal(params.get('form[mitarbeiter_id]'), '3309');
|
||||||
|
assert.equal(params.get('form[mitarbeitertyp]'), '1');
|
||||||
|
assert.equal(params.get('form[taetigkeit_id]'), '870');
|
||||||
|
assert.equal(params.get('form[maschine_id]'), '5033');
|
||||||
|
assert.equal(params.get('form[arbeitszeit_start]'), '08:00');
|
||||||
|
assert.equal(params.get('form[arbeitszeit_ende]'), '09:00', 'Sekunden werden auf die Minute gerundet');
|
||||||
|
assert.equal(params.get('form[pause_start]'), '');
|
||||||
|
assert.equal(params.get('form[uebernehmen]'), '1');
|
||||||
|
assert.equal(params.get('form[notiz]'), 'Hydraulik abgedichtet\nKostenstelle: 4711');
|
||||||
|
assert.deepEqual(params.getAll('tage[]'), ['2026-10-14']);
|
||||||
|
|
||||||
|
const [[verteilungId, verteilung]] = [...state.verteilungen.entries()];
|
||||||
|
assert.equal(ref, `verteilung:3309:2026-10-14:${verteilungId}`);
|
||||||
|
assert.equal(verteilung.start, '2026-10-14 08:00');
|
||||||
|
assert.equal(verteilung.ende, '2026-10-14 09:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung bucht in die vorhandene Schicht und verlaengert sie', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { schichtId } = schichtAnlegen(state, neueId);
|
||||||
|
|
||||||
|
const { ref } = await connector.createZeitbuchung(
|
||||||
|
block('2026-10-14T07:00:00Z', '2026-10-14T08:00:00Z', { taetigkeitId: 869, maschinenId: 5033, notiz: 'Zweiter Block' })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/editVerteilungForm.php')[0].body, {
|
||||||
|
employeeID: 3309,
|
||||||
|
date: '2026-10-14',
|
||||||
|
shiftID: schichtId,
|
||||||
|
});
|
||||||
|
const verteilungId = Number(ref.split(':').at(-1));
|
||||||
|
assert.equal(ref, `verteilung:3309:2026-10-14:${verteilungId}`);
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/updateVerteilung.php')[0].body, {
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
verteilungId: String(verteilungId),
|
||||||
|
verteilungForm: {
|
||||||
|
timestamp_start: '14.10.26 (09:00)',
|
||||||
|
timestamp_ende: '14.10.26 (10:00)',
|
||||||
|
alte_taetigkeit_id: '0',
|
||||||
|
taetigkeit_id: '869',
|
||||||
|
ganzertag: '1',
|
||||||
|
maschine_id: '5033',
|
||||||
|
projekt_id: '0',
|
||||||
|
},
|
||||||
|
notizForm: { notizfeld: 'Zweiter Block', verfasser_id: '6' },
|
||||||
|
});
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/updateSchicht.php').map(r => r.body), [
|
||||||
|
{ shiftID: schichtId, timestampStart: '14.10.26 (08:00)', timestampEnd: '14.10.26 (10:00)' },
|
||||||
|
]);
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/nachtragen.php').length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung bucht eine Pause in die Schicht', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { schichtId } = schichtAnlegen(state, neueId);
|
||||||
|
|
||||||
|
const { ref } = await connector.createZeitbuchung({
|
||||||
|
art: 'pause',
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
start: new Date('2026-10-14T07:00:00Z'),
|
||||||
|
ende: new Date('2026-10-14T07:30:00Z'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [[pauseId, pause]] = [...state.pausen.entries()];
|
||||||
|
assert.equal(ref, `pause:3309:2026-10-14:${pauseId}`);
|
||||||
|
assert.deepEqual(pause, { schichtId, start: '2026-10-14 09:00', ende: '2026-10-14 09:30' });
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/updatePause.php')[0].body, {
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
pause_id: String(pauseId),
|
||||||
|
pauseForm: { pause_start: '14.10.26 (09:00)', pause_ende: '14.10.26 (09:30)' },
|
||||||
|
neu: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung lehnt eine Pause ohne Schicht ab', async t => {
|
||||||
|
const { connector, requests } = await startConnector(t);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
connector.createZeitbuchung({
|
||||||
|
art: 'pause',
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
start: new Date('2026-10-14T07:00:00Z'),
|
||||||
|
ende: new Date('2026-10-14T07:30:00Z'),
|
||||||
|
}),
|
||||||
|
error => error instanceof ZeitbuchungError && /keine Schicht vorhanden/.test(error.message)
|
||||||
|
);
|
||||||
|
assert.equal(schreibendePosts(requests).length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung meldet den Grund einer 422 und raeumt Platzhalter und Schicht auf', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { schichtId, verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.createZeitbuchung(block('2026-10-14T07:00:00Z', '2026-10-14T08:00:00Z', { maschinenId: 999 })),
|
||||||
|
error =>
|
||||||
|
error instanceof ZeitbuchungError &&
|
||||||
|
error.status === 422 &&
|
||||||
|
error.grund === 'Maschine ungueltig' &&
|
||||||
|
/Buchung speichern fehlgeschlagen \(HTTP 422\): Maschine ungueltig/.test(error.message)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual([...state.verteilungen.keys()], [verteilungId], 'Platzhalter wurde wieder geloescht');
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/deleteVerteilung.php').length, 1);
|
||||||
|
const schicht = state.schichten.get(schichtId);
|
||||||
|
assert.deepEqual([schicht.start, schicht.ende], ['2026-10-14 08:00', '2026-10-14 09:00'], 'Schicht zurueckgesetzt');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung ueber Mitternacht korrigiert Verteilung und Schicht nach dem Nachtragen', async t => {
|
||||||
|
const { connector, requests, state } = await startConnector(t);
|
||||||
|
|
||||||
|
await connector.createZeitbuchung(
|
||||||
|
block('2026-10-14T21:30:00Z', '2026-10-14T22:30:00Z', { datum: '2026-10-14' })
|
||||||
|
);
|
||||||
|
|
||||||
|
const params = new URLSearchParams(posts(requests, '/module/arbeitszeit/api/nachtragen.php')[0].body);
|
||||||
|
assert.equal(params.get('form[arbeitszeit_start]'), '23:30');
|
||||||
|
assert.equal(params.get('form[arbeitszeit_ende]'), '23:59');
|
||||||
|
|
||||||
|
const update = posts(requests, '/module/arbeitszeit/api/updateVerteilung.php')[0].body;
|
||||||
|
assert.equal(update.verteilungForm.timestamp_start, '14.10.26 (23:30)');
|
||||||
|
assert.equal(update.verteilungForm.timestamp_ende, '15.10.26 (00:30)');
|
||||||
|
assert.equal(update.notizForm.notizfeld, '', 'Notiz nicht doppelt anlegen');
|
||||||
|
|
||||||
|
const [schicht] = state.schichten.values();
|
||||||
|
assert.deepEqual([schicht.start, schicht.ende], ['2026-10-14 23:30', '2026-10-15 00:30']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Im echten System gespeichert statt abgelehnt (Echttest 14.10.2026) - der Connector muss pruefen.
|
||||||
|
test('createZeitbuchung lehnt eine nicht buchbare Taetigkeit ab', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.createZeitbuchung(block('2026-10-14T06:00:00Z', '2026-10-14T07:00:00Z', { taetigkeitId: 999 })),
|
||||||
|
error => error instanceof ZeitbuchungError && error.grund === 'taetigkeit nicht buchbar'
|
||||||
|
);
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/nachtragen.php').length, 0, 'ohne Schicht nichts nachtragen');
|
||||||
|
|
||||||
|
const { verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.createZeitbuchung(block('2026-10-14T07:00:00Z', '2026-10-14T08:00:00Z', { taetigkeitId: 999 })),
|
||||||
|
error => error instanceof ZeitbuchungError && error.grund === 'taetigkeit nicht buchbar'
|
||||||
|
);
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/updateVerteilung.php').length, 0);
|
||||||
|
assert.deepEqual([...state.verteilungen.keys()], [verteilungId], 'Platzhalter wurde wieder geloescht');
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
connector.updateZeitbuchung(
|
||||||
|
`verteilung:3309:2026-10-14:${verteilungId}`,
|
||||||
|
block('2026-10-14T06:00:00Z', '2026-10-14T07:00:00Z', { taetigkeitId: 999 })
|
||||||
|
),
|
||||||
|
error => error instanceof ZeitbuchungError && error.grund === 'taetigkeit nicht buchbar'
|
||||||
|
);
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/updateVerteilung.php').length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateZeitbuchung aendert die Verteilung und ersetzt die Notiz', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
const alteNotizId = state.verteilungen.get(verteilungId).notizen[0].id;
|
||||||
|
|
||||||
|
await connector.updateZeitbuchung(
|
||||||
|
`verteilung:3309:2026-10-14:${verteilungId}`,
|
||||||
|
block('2026-10-14T06:00:00Z', '2026-10-14T06:45:00Z', { taetigkeitId: 869, maschinenId: 5033, notiz: 'Neue Notiz' })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/updateVerteilung.php')[0].body, {
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
verteilungId: String(verteilungId),
|
||||||
|
verteilungForm: {
|
||||||
|
timestamp_start: '14.10.26 (08:00)',
|
||||||
|
timestamp_ende: '14.10.26 (08:45)',
|
||||||
|
alte_taetigkeit_id: '870',
|
||||||
|
taetigkeit_id: '869',
|
||||||
|
ganzertag: '1',
|
||||||
|
maschine_id: '5033',
|
||||||
|
projekt_id: '12',
|
||||||
|
},
|
||||||
|
notizForm: { notizfeld: 'Neue Notiz', verfasser_id: '6' },
|
||||||
|
});
|
||||||
|
assert.deepEqual(posts(requests, '/module/notiz/api/delete.php').map(r => r.body), [{ noteID: alteNotizId }]);
|
||||||
|
assert.deepEqual(state.verteilungen.get(verteilungId).notizen.map(n => n.text), ['Neue Notiz']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateZeitbuchung laesst eine unveraenderte Notiz stehen', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
|
||||||
|
await connector.updateZeitbuchung(
|
||||||
|
`verteilung:3309:2026-10-14:${verteilungId}`,
|
||||||
|
block('2026-10-14T06:00:00Z', '2026-10-14T06:45:00Z', { notiz: 'Alte Notiz' })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(posts(requests, '/module/arbeitszeit/api/updateVerteilung.php')[0].body.notizForm.notizfeld, '');
|
||||||
|
assert.equal(posts(requests, '/module/notiz/api/delete.php').length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateZeitbuchung aendert eine Pause', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { schichtId } = schichtAnlegen(state, neueId);
|
||||||
|
const pauseId = neueId();
|
||||||
|
state.pausen.set(pauseId, { schichtId, start: '2026-10-14 08:30', ende: '2026-10-14 08:45' });
|
||||||
|
|
||||||
|
await connector.updateZeitbuchung(`pause:3309:2026-10-14:${pauseId}`, {
|
||||||
|
art: 'pause',
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
start: new Date('2026-10-14T06:35:00Z'),
|
||||||
|
ende: new Date('2026-10-14T06:50:00Z'),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/updatePause.php')[0].body, {
|
||||||
|
mitarbeiterId: 3309,
|
||||||
|
pause_id: String(pauseId),
|
||||||
|
pauseForm: { pause_start: '14.10.26 (08:35)', pause_ende: '14.10.26 (08:50)' },
|
||||||
|
neu: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateZeitbuchung lehnt ungueltige Referenzen und Wechsel von Art oder Tag ab', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
const daten = block('2026-10-14T06:00:00Z', '2026-10-14T06:45:00Z');
|
||||||
|
|
||||||
|
await assert.rejects(() => connector.updateZeitbuchung('1180803', daten), ZeitbuchungError);
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.updateZeitbuchung(`pause:3309:2026-10-14:${verteilungId}`, daten),
|
||||||
|
/Art der Buchung/
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.updateZeitbuchung(`verteilung:3309:2026-10-13:${verteilungId}`, daten),
|
||||||
|
/Arbeitstag/
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.updateZeitbuchung(`verteilung:3309:2026-10-14:999999`, daten),
|
||||||
|
/nicht gefunden/
|
||||||
|
);
|
||||||
|
assert.equal(schreibendePosts(requests).length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleteZeitbuchung loescht Verteilung und Pause', async t => {
|
||||||
|
const { connector, requests, state, neueId } = await startConnector(t);
|
||||||
|
const { schichtId, verteilungId } = schichtAnlegen(state, neueId);
|
||||||
|
const pauseId = neueId();
|
||||||
|
state.pausen.set(pauseId, { schichtId, start: '2026-10-14 08:30', ende: '2026-10-14 08:45' });
|
||||||
|
|
||||||
|
await connector.deleteZeitbuchung(`pause:3309:2026-10-14:${pauseId}`);
|
||||||
|
await connector.deleteZeitbuchung(`verteilung:3309:2026-10-14:${verteilungId}`);
|
||||||
|
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/deletePause.php')[0].body, {
|
||||||
|
pauseID: pauseId,
|
||||||
|
employeeID: 3309,
|
||||||
|
date: '2026-10-14',
|
||||||
|
});
|
||||||
|
assert.deepEqual(posts(requests, '/module/arbeitszeit/api/deleteVerteilung.php')[0].body, {
|
||||||
|
distributionID: verteilungId,
|
||||||
|
employeeID: 3309,
|
||||||
|
date: '2026-10-14',
|
||||||
|
});
|
||||||
|
assert.equal(state.verteilungen.size, 0);
|
||||||
|
assert.equal(state.pausen.size, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung prueft die Eingaben vor dem ersten Request', async t => {
|
||||||
|
const { connector, requests } = await startConnector(t);
|
||||||
|
const anzahlVorher = requests.length;
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.createZeitbuchung(block('2026-10-14T06:00:00Z', '2026-10-14T06:00:20Z')),
|
||||||
|
/mindestens eine Minute/
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => connector.createZeitbuchung(block('2026-10-14T06:00:00Z', '2026-10-14T07:00:00Z', { taetigkeitId: 0 })),
|
||||||
|
/Taetigkeits-ID/
|
||||||
|
);
|
||||||
|
assert.equal(requests.length, anzahlVorher);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createZeitbuchung meldet sich bei abgelaufener Session neu an', async t => {
|
||||||
|
const { connector, requests, state, expireSession } = await startConnector(t);
|
||||||
|
const logins = () => posts(requests, '/login/api/login.php').length;
|
||||||
|
|
||||||
|
assert.equal(logins(), 1);
|
||||||
|
expireSession();
|
||||||
|
|
||||||
|
const { ref } = await connector.createZeitbuchung(block('2026-10-14T06:00:00Z', '2026-10-14T07:00:00Z'));
|
||||||
|
|
||||||
|
assert.equal(logins(), 2, 'genau ein erneuter Login');
|
||||||
|
assert.match(ref, /^verteilung:3309:2026-10-14:\d+$/);
|
||||||
|
assert.equal(state.verteilungen.size, 1);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user