Kundenansicht neu P10: Skript setzt die Kundenart der Bestandseinträge auf Unternehmen (E18)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff8adadf00
commit
4a75d7aba0
2 files changed
+184
No files matched your search
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Einmalig (Kundenansicht neu, P10 Umschalten, E18): die Kundenart der Bestandseintraege. Kunden, Leads und verlorene Leads, die vor
|
||||
* der neuen Ansicht angelegt wurden, haben kein Feld customers.kundenart; leer gilt ueberall als privat (kundenartWirksam) und
|
||||
* sperrte ihnen die Funktionen fuer Betriebe. Alle bisherigen Eintraege sind Betriebe: leere Felder werden "unternehmen".
|
||||
*
|
||||
* Geschrieben wird nur dort, wo das Feld leer ist (fehlt, "" oder nur Leerzeichen). Ein gueltiger Wert ("unternehmen", "privat") und
|
||||
* jeder unbekannte Wert bleiben stehen. Geschrieben wird erst mit der Bestaetigungszeile "Kundenart unternehmen setzen: <Anzahl>",
|
||||
* die genau zur Zahl der leeren Eintraege passt (der Probelauf nennt sie). Ausgaben nennen nur IDs und Zahlen, nie Namen oder Adressen.
|
||||
*
|
||||
* node scripts/kundenart-bestand.mjs --pruefen (zaehlt und listet, schreibt nichts)
|
||||
* node scripts/kundenart-bestand.mjs --ausfuehren --bestaetigung="Kundenart unternehmen setzen: <Anzahl>"
|
||||
* Im Container als Datei: docker exec -w /app <container> node scripts/kundenart-bestand.mjs --pruefen
|
||||
* Die Protokolldatei entsteht per tee ausserhalb des Containers. Exit 0 bei Erfolg, 1 bei Schreibfehlern (Skript erneut ausfuehren),
|
||||
* 2 bei falschem Aufruf oder falscher Bestaetigung.
|
||||
* NICHT gegen Produktion ausfuehren, ohne dass der Probelauf geprueft und die Freigabe da ist.
|
||||
*/
|
||||
import 'dotenv/config'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { config } from '../server/config.js'
|
||||
import { getDocument, listAllDocuments, updateDocument } from '../server/services/appwriteAdmin.js'
|
||||
import { KUNDENARTEN } from '../server/services/kundenart.js'
|
||||
|
||||
const NEUE_ART = 'unternehmen'
|
||||
const AUFRUF = 'Aufruf: node scripts/kundenart-bestand.mjs --pruefen | --ausfuehren --bestaetigung="Kundenart unternehmen setzen: <Anzahl>"'
|
||||
|
||||
/** Falscher Aufruf oder falsche Bestaetigung (Exit 2) */
|
||||
class AufrufFehler extends Error {}
|
||||
|
||||
/** Aufrufargumente: genau --pruefen, oder --ausfuehren zusammen mit --bestaetigung=<Zeile>; alles andere wirft. */
|
||||
export function parseArgs(argv = []) {
|
||||
if (argv.length === 1 && argv[0] === '--pruefen') return { modus: 'pruefen' }
|
||||
if (argv.length === 2 && argv.includes('--ausfuehren')) {
|
||||
const bestaetigung = argv.find((a) => a.startsWith('--bestaetigung='))?.slice('--bestaetigung='.length)
|
||||
if (bestaetigung) return { modus: 'ausfuehren', bestaetigung }
|
||||
}
|
||||
throw new AufrufFehler(AUFRUF)
|
||||
}
|
||||
|
||||
export const bestaetigungsZeile = (anzahl) => `Kundenart unternehmen setzen: ${anzahl}`
|
||||
|
||||
/** Leer = fehlt, "" oder nur Leerzeichen. Nur dann wird geschrieben. */
|
||||
const istLeer = (doc) => String(doc?.kundenart ?? '').trim() === ''
|
||||
|
||||
/** Der alte Wert fuer das Protokoll: nie der Rohwert, nur fehlt oder leer (Leerzeichen zaehlen als leer). */
|
||||
const vorherText = (doc) => (doc?.kundenart === undefined || doc?.kundenart === null ? '(fehlt)' : '(leer)')
|
||||
|
||||
/**
|
||||
* Liest alle Kundeneintraege und sortiert sie ein. Schreibt nichts, nennt nur IDs und Zahlen.
|
||||
* @returns {Promise<{ gesamt: number, mitKundenart: { unternehmen: number, privat: number }, ohne: string[],
|
||||
* ohneNachStatus: Record<string, string[]>, unbekannt: string[] }>}
|
||||
*/
|
||||
export async function pruefen({ log = console.log } = {}) {
|
||||
const docs = await listAllDocuments(config.collections.customers)
|
||||
const mitKundenart = Object.fromEntries(KUNDENARTEN.map((art) => [art, 0]))
|
||||
const ohne = []
|
||||
const ohneNachStatus = {}
|
||||
const unbekannt = []
|
||||
for (const doc of docs) {
|
||||
if (istLeer(doc)) {
|
||||
ohne.push(doc.$id)
|
||||
const status = String(doc.customerStatus ?? '').trim() || '(ohne Status)'
|
||||
;(ohneNachStatus[status] ??= []).push(doc.$id)
|
||||
} else if (KUNDENARTEN.includes(doc.kundenart)) mitKundenart[doc.kundenart] += 1
|
||||
else unbekannt.push(doc.$id)
|
||||
}
|
||||
log(`Einträge gesamt: ${docs.length}`)
|
||||
log(`Schon gesetzt: ${KUNDENARTEN.map((art) => `${art}=${mitKundenart[art]}`).join(' ')}`)
|
||||
log(`Ohne Kundenart: ${ohne.length}`)
|
||||
log(`Unbekannte Werte: ${unbekannt.length}`)
|
||||
if (unbekannt.length) log(`Unbekannte Kundenart bei: ${unbekannt.join(', ')}`)
|
||||
for (const [status, ids] of Object.entries(ohneNachStatus)) log(`${status}: ${ids.join(', ')}`)
|
||||
log(bestaetigungsZeile(ohne.length))
|
||||
return { gesamt: docs.length, mitKundenart, ohne, ohneNachStatus, unbekannt }
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt kundenart auf "unternehmen" bei allen leeren Eintraegen, aber nur mit passender Bestaetigungszeile (vor jedem Schreiben geprueft).
|
||||
* Jeder Eintrag wird vor dem Schreiben neu gelesen; ein inzwischen gesetzter Wert bleibt. Ein Schreibfehler stoppt die uebrigen nicht.
|
||||
* @returns {Promise<{ gesetzt: number, uebersprungen: number, fehler: { id: string, grund: string }[] }>}
|
||||
*/
|
||||
export async function ausfuehren({ bestaetigung, log = console.log } = {}) {
|
||||
const { ohne } = await pruefen({ log })
|
||||
const ergebnis = { gesetzt: 0, uebersprungen: 0, fehler: [] }
|
||||
if (ohne.length === 0) {
|
||||
log('Nichts zu setzen.')
|
||||
return ergebnis
|
||||
}
|
||||
const erwartet = bestaetigungsZeile(ohne.length)
|
||||
if (bestaetigung !== erwartet) throw new AufrufFehler(`Bestätigung fehlt oder stimmt nicht. Erwartet: "${erwartet}"`)
|
||||
|
||||
log(`Schreibe ${ohne.length} Einträge …`)
|
||||
for (const id of ohne) {
|
||||
const zeit = new Date().toISOString()
|
||||
try {
|
||||
const aktuell = await getDocument(config.collections.customers, id)
|
||||
if (!istLeer(aktuell)) {
|
||||
ergebnis.uebersprungen += 1
|
||||
log(`${zeit}\t${id}\t(inzwischen gesetzt)\t->\tunverändert`)
|
||||
continue
|
||||
}
|
||||
await updateDocument(config.collections.customers, id, { kundenart: NEUE_ART })
|
||||
ergebnis.gesetzt += 1
|
||||
log(`${zeit}\t${id}\t${vorherText(aktuell)}\t->\t${NEUE_ART}`)
|
||||
} catch (err) {
|
||||
ergebnis.fehler.push({ id, grund: err.message })
|
||||
log(`${zeit}\t${id}\tFEHLER\t${err.message}`)
|
||||
}
|
||||
}
|
||||
log(`Summe gesetzt=${ergebnis.gesetzt} uebersprungen=${ergebnis.uebersprungen} fehler=${ergebnis.fehler.length}`)
|
||||
return ergebnis
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
||||
try {
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const ergebnis = args.modus === 'pruefen' ? await pruefen({}) : await ausfuehren(args)
|
||||
if (ergebnis.fehler?.length) process.exitCode = 1
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err.message)
|
||||
process.exit(err instanceof AufrufFehler ? 2 : 1)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user