Der Einzel-Import kennt den dritten Ausgang jetzt auch

Nach dem Umbau von heute Nachmittag habe ich nachgesehen, WER
tagAusZelle() sonst noch ruft -- und genau dort die naechste Luecke
gefunden, bevor sie jemanden getroffen hat.

DIE FUNKTION HAT SEIT HEUTE DREI AUSGAENGE (Tag, Zeitraum, Grund).
Gekannt hat den dritten nur der Backstage-Weg. Im Einzel-Import stand
weiterhin `gelesen.tag` -- bei einem Zeitraum `undefined`:

  VORSCHAU:    `undefined > heute` ist false, die Zeile faellt durch
               und landet mit `tag: undefined` in der Liste.
  SCHREIBWEG:  `if (!tag)` greift, die Zeile wird still uebersprungen.

Die Vorschau haette also eine Zeile versprochen, die danach nirgends
steht. Zwei Aussagen ueber dieselbe Datei, die einander widersprechen,
und beide sehen fuer sich plausibel aus -- das ist der Unterschied, den
niemand bemerkt.

Das ist an diesem Tag die FUENFTE Wiederholung derselben Sache: eine
Regel, mehrere Aufrufer, und einer kennt sie nicht. Ein dritter Ausgang
taugt nur, wenn ihn ALLE Aufrufer kennen.

Jetzt legt auch der Einzel-Import einen Zeitraum in leistung_zeitraum
ab -- dieselbe Tabelle, dieselbe Regel, dieselbe Anzeige. Und die
Dauer-Einheit gilt dort ebenfalls; sie fehlte in der Vorschau noch.

DIE MELDUNG SAGT JETZT, WAS ANGEKOMMEN IST: "1 Zeitraum uebernommen"
statt "1 Tage uebernommen". Bei einer Backstage-Ausgabe ueber zwei
Wochen haette man sie sonst in der Tagesliste gesucht und nicht
gefunden.

pruef-backstage-import 106 -> 118. Die Pruefung, auf die es ankommt,
vergleicht VORSCHAU UND ERGEBNIS: Was die Vorschau verspricht, muss
danach dastehen -- genau die Aussage, die vorher falsch gewesen waere.
Dazu, dass in der Vorschau die Spanne steht und kein leerer Tag, und
die Gegenprobe, dass eine echte Tagesdatei weiterhin als Tage durchgeht.

ZWEI EIGENE FEHLER DABEI: Ich habe `.length` auf eine Zahl angewendet
(`fehlerhaft` ist eine Anzahl, keine Liste) -- immer `undefined`, immer
rot. Und zum zweiten Mal heute den Absolutwert gemessen, wo die
Veraenderung gehoert: Lumi hatte aus einem frueheren Teil der Pruefung
schon eine Tageszeile in derselben Spanne.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-14 16:34:42 +02:00
co-authored by Claude Opus 5
parent e82bf45f0d
commit 0cce22c08c
32 changed files with 507 additions and 350 deletions
+67 -4
View File
@@ -1244,15 +1244,52 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import/vorschau", gleich
const fehlerhaft = [];
for (let i = 1; i < zeilen.length; i++) {
const z = zeilen[i];
/* DER DRITTE AUSGANG MUSS HIER GENAUSO ANKOMMEN wie im
Backstage-Weg (14.09.2026).
Als tagAusZelle() den Zeitraum bekam, stand hier weiterhin
nur `gelesen.tag`. Bei einem Zeitraum war der `undefined`,
der Vergleich mit heute wurde still falsch, und die Zeile
landete mit `tag: undefined` in der Vorschau -- waehrend der
Schreibweg daneben sie kommentarlos uebersprang. Vorschau
und Ergebnis haetten sich widersprochen, und das ist der
Unterschied, den niemand bemerkt.
Ein dritter Ausgang taugt nur, wenn ihn ALLE Aufrufer
kennen. Das ist an diesem Tag die fuenfte Wiederholung
derselben Sache -- gefunden, bevor sie jemanden getroffen
hat, weil ich nach dem Umbau nachgesehen habe, wer die
Funktion sonst noch ruft. */
const gelesen = tagAusZelle(z[zu.tag]);
if (gelesen.fehler) { fehlerhaft.push({ zeile: i + 1, grund: gelesen.fehler }); continue; }
if (gelesen.zeitraum) {
const { von, bis } = gelesen.zeitraum;
if (bis > tagVor(0)) {
fehlerhaft.push({ zeile: i + 1, grund: `${bis} liegt in der Zukunft` });
continue;
}
const wertez = { von, bis, tage: tageImZeitraum(von, bis) };
for (const [feld, spalte] of Object.entries(zu)) {
if (feld === "tag" || spalte === undefined) continue;
wertez[feld] = feld === "dauer_min" ? dauerLesen(z[spalte]) : zahlLesen(z[spalte]);
}
const daz = db().prepare(
"SELECT 1 FROM leistung_zeitraum WHERE creator_id = ? AND von = ? AND bis = ?")
.get(creatorId, von, bis);
(daz ? ersetzt : neu).push(wertez);
continue;
}
const tag = gelesen.tag;
if (tag > tagVor(0)) { fehlerhaft.push({ zeile: i + 1, grund: `${tag} liegt in der Zukunft` }); continue; }
const werte = { tag };
for (const [feld, spalte] of Object.entries(zu)) {
if (feld === "tag" || spalte === undefined) continue;
werte[feld] = zahlLesen(z[spalte]);
/* Die Dauer hat eine Einheit, die uebrigen Felder nicht --
dieselbe Unterscheidung wie im Backstage-Weg. */
werte[feld] = feld === "dauer_min" ? dauerLesen(z[spalte]) : zahlLesen(z[spalte]);
}
const vorhanden = db().prepare(
"SELECT 1 FROM leistung WHERE creator_id = ? AND tag = ?").get(creatorId, tag);
@@ -1301,15 +1338,41 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft
(SELECT notiz FROM leistung WHERE creator_id = ? AND tag = ?),
?,?, 'import')`);
let geschrieben = 0, uebersprungen = 0;
/* Der zweite Weg, wie im Backstage-Import: Ein Zeitraum geht in
seine eigene Tabelle. */
const spanneSchreiben = d.prepare(`
INSERT OR REPLACE INTO leistung_zeitraum
(creator_id, von, bis, tage, diamanten, dauer_min, gueltige_tage,
zuschauer_avg, zuschauer_max, verweildauer_s, schenker, follower_neu,
erfasst, erfasst_von)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
let geschrieben = 0, uebersprungen = 0, spannen = 0;
d.exec("BEGIN");
try {
for (let i = 1; i < zeilen.length; i++) {
const z = zeilen[i];
/* Dieselbe Regel wie in der Vorschau -- sonst zeigt die
Vorschau etwas anderes an, als geschrieben wird. Genau das
ist der Unterschied, den niemand bemerkt. */
ist der Unterschied, den niemand bemerkt.
UND DERSELBE DRITTE AUSGANG: Ohne diesen Zweig wuerde ein
Zeitraum hier still uebersprungen, waehrend die Vorschau
ihn zaehlt. */
const gelesen = tagAusZelle(z[zu.tag]);
const wz = (feld) => (zu[feld] === undefined ? null
: (feld === "dauer_min" ? dauerLesen(z[zu[feld]]) : zahlLesen(z[zu[feld]])));
if (gelesen.zeitraum) {
const { von, bis } = gelesen.zeitraum;
if (bis > tagVor(0)) { uebersprungen++; continue; }
spanneSchreiben.run(creatorId, von, bis, tageImZeitraum(von, bis),
wz("diamanten"), wz("dauer_min"), wz("gueltige_tage"),
wz("zuschauer_avg"), wz("zuschauer_max"), wz("verweildauer_s"),
wz("schenker"), wz("follower_neu"),
jetzt(), req.person.id);
spannen++;
continue;
}
const tag = gelesen.tag;
if (!tag || tag > tagVor(0)) { uebersprungen++; continue; }
const w = (feld) => (zu[feld] === undefined ? null : zahlLesen(z[zu[feld]]));
@@ -1338,7 +1401,7 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `Creator #${creatorId}: ${geschrieben} Tage`,
});
res.json({ ok: true, geschrieben, uebersprungen });
res.json({ ok: true, geschrieben, zeitraeume: spannen, uebersprungen });
} catch (fehler) {
console.error("[leistung] Import:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });