Leeren Bestellschein aus Materialschein anlegen (bei Neuanlage und im bestehenden Schein)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
8dd61d8889
commit
1a4559589a
@@ -1,4 +1,5 @@
|
||||
import type { ScheinTyp } from './types';
|
||||
import { api } from './api';
|
||||
import type { Projekt, ScheinTyp } from './types';
|
||||
|
||||
// Materialschein und Bestellschein verhalten sich identisch – nur Beschriftung, Routen
|
||||
// und Dateiname des Exports unterscheiden sich.
|
||||
@@ -17,3 +18,27 @@ export function scheinInfo(typ: ScheinTyp): ScheinInfo {
|
||||
? { name: 'Bestellschein', plural: 'Bestellscheine', liste: 'Bestellliste', druckTitel: 'Bestellschein', betreff: 'Bestellung', listBase: '/bestellungen', detailBase: '/bestellungen' }
|
||||
: { name: 'Materialschein', plural: 'Materialscheine', liste: 'Materialliste', druckTitel: 'Materialschein', betreff: 'Materialverbrauch', listBase: '/', detailBase: '/projekte' };
|
||||
}
|
||||
|
||||
// Legt zu einem Materialschein einen leeren Bestellschein mit denselben Kopfdaten an
|
||||
// (Projekt/Baustelle, Kunde, Auftrags-Nr., Datum, Monteur, Notiz). Beide Scheine sind danach
|
||||
// unabhängig voneinander – Löschen geht nur im jeweiligen Reiter.
|
||||
export interface ScheinKopf {
|
||||
titel: string;
|
||||
auftraggeber: string;
|
||||
auftragsNr: string;
|
||||
datum: string;
|
||||
monteur: string;
|
||||
notiz: string;
|
||||
}
|
||||
|
||||
export const normName = (s: string) => s.trim().toLowerCase().replace(/\s+/g, ' ');
|
||||
|
||||
export async function bestellscheinFuerKopf(kopf: ScheinKopf): Promise<Projekt> {
|
||||
return api.projekte.create({ ...kopf, typ: 'Bestell' });
|
||||
}
|
||||
|
||||
// Gibt es zu Projekt + Kunde schon einen Bestellschein? (Schutz vor versehentlichen Doppelten)
|
||||
export async function findeBestellschein(kopf: Pick<ScheinKopf, 'titel' | 'auftraggeber'>): Promise<Projekt | undefined> {
|
||||
const alle = await api.projekte.list('Bestell');
|
||||
return alle.find(p => normName(p.titel) === normName(kopf.titel) && normName(p.auftraggeber ?? '') === normName(kopf.auftraggeber));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Projekt, ProjektStatus, ScheinTyp } from '../lib/types';
|
||||
import { useFavoriten } from '../lib/favoriten';
|
||||
import MonteurField from '../components/MonteurField';
|
||||
import FavStar from '../components/FavStar';
|
||||
import { scheinInfo } from '../lib/schein';
|
||||
import { bestellscheinFuerKopf, scheinInfo } from '../lib/schein';
|
||||
|
||||
function dd(s: string | null) {
|
||||
if (!s) return '–';
|
||||
@@ -23,6 +23,7 @@ export default function Dashboard({ typ }: { typ: ScheinTyp }) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [mitBestellschein, setMitBestellschein] = useState(false);
|
||||
const [err, setErr] = useState('');
|
||||
const [form, setForm] = useState({ titel: '', auftraggeber: '', auftragsNr: '', datum: new Date().toISOString().slice(0, 10), monteur: '', notiz: '' });
|
||||
|
||||
@@ -59,7 +60,10 @@ export default function Dashboard({ typ }: { typ: ScheinTyp }) {
|
||||
setErr('');
|
||||
try {
|
||||
const projekt = await api.projekte.create({ ...form, typ });
|
||||
// Optional zusätzlich ein leerer Bestellschein mit denselben Kopfdaten (Kunde etc.).
|
||||
if (typ === 'Material' && mitBestellschein) await bestellscheinFuerKopf(form);
|
||||
setShowForm(false);
|
||||
setMitBestellschein(false);
|
||||
navigate(`${info.detailBase}/${projekt.id}`);
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : String(e));
|
||||
@@ -121,6 +125,12 @@ export default function Dashboard({ typ }: { typ: ScheinTyp }) {
|
||||
<textarea value={form.notiz} onChange={e => setForm({ ...form, notiz: e.target.value })} placeholder="Stockwerk, Bereich, Bemerkung …" />
|
||||
</label>
|
||||
</div>
|
||||
{typ === 'Material' && (
|
||||
<label className="check-line">
|
||||
<input type="checkbox" checked={mitBestellschein} onChange={e => setMitBestellschein(e.target.checked)} />
|
||||
Zusätzlich einen leeren Bestellschein mit diesem Kunden anlegen
|
||||
</label>
|
||||
)}
|
||||
<div style={{ display: 'flex', gap: 10, marginTop: 16 }}>
|
||||
<button className="btn btn-cyan" disabled={busy} onClick={createProjekt}>✓ Anlegen & öffnen</button>
|
||||
<button className="btn btn-ghost" onClick={() => setShowForm(false)}>Abbrechen</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { MaterialItem, Projekt, ScheinTyp } from '../lib/types';
|
||||
import { kabelInfoForBez } from '../data/kabelData';
|
||||
import KabelSelect from '../components/KabelSelect';
|
||||
import FavStar from '../components/FavStar';
|
||||
import { scheinInfo } from '../lib/schein';
|
||||
import { bestellscheinFuerKopf, findeBestellschein, scheinInfo } from '../lib/schein';
|
||||
import MonteurField from '../components/MonteurField';
|
||||
|
||||
const fmt1 = (v: number | string) => (Math.round((parseFloat(String(v)) || 0) * 10) / 10).toFixed(1);
|
||||
@@ -21,6 +21,7 @@ export default function ProjektDetail({ typ }: { typ: ScheinTyp }) {
|
||||
const [matList, setMatList] = useState<MaterialItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleteBusy, setDeleteBusy] = useState(false);
|
||||
const [bestellBusy, setBestellBusy] = useState(false);
|
||||
|
||||
// Baustelle-Formular
|
||||
const [titel, setTitel] = useState('');
|
||||
@@ -84,6 +85,26 @@ export default function ProjektDetail({ typ }: { typ: ScheinTyp }) {
|
||||
setProjekt(updated);
|
||||
}
|
||||
|
||||
// Leeren Bestellschein mit denselben Kopfdaten (v. a. Kunde) zu diesem Materialschein anlegen.
|
||||
async function createBestellschein() {
|
||||
if (!projekt) return;
|
||||
const kopf = { titel, auftraggeber, auftragsNr, datum, monteur, notiz };
|
||||
setBestellBusy(true);
|
||||
try {
|
||||
const vorhanden = await findeBestellschein(kopf);
|
||||
if (vorhanden && !confirm(`Es gibt bereits einen Bestellschein "${vorhanden.titel}"${vorhanden.auftraggeber ? ' (' + vorhanden.auftraggeber + ')' : ''}. Trotzdem einen weiteren anlegen?`)) {
|
||||
navigate(`/bestellungen/${vorhanden.id}`);
|
||||
return;
|
||||
}
|
||||
const neu = await bestellscheinFuerKopf(kopf);
|
||||
navigate(`/bestellungen/${neu.id}`);
|
||||
} catch (e) {
|
||||
alert('Fehler beim Anlegen: ' + (e instanceof Error ? e.message : e));
|
||||
} finally {
|
||||
setBestellBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProjekt() {
|
||||
if (!id || !projekt) return;
|
||||
if (!confirm(`"${projekt.titel}" endgültig löschen? Alle ${matList.length} Material-Positionen werden mitgelöscht. Das kann nicht rückgängig gemacht werden.`)) return;
|
||||
@@ -292,6 +313,11 @@ export default function ProjektDetail({ typ }: { typ: ScheinTyp }) {
|
||||
<button className="btn btn-outline" style={{ height: 36, fontSize: 11, padding: '0 14px' }} onClick={toggleStatus}>
|
||||
{projekt.status === 'Abgeschlossen' ? '↺ Wieder aktivieren' : '✓ Abschließen'}
|
||||
</button>
|
||||
{typ === 'Material' && (
|
||||
<button className="btn btn-outline" style={{ height: 36, fontSize: 11, padding: '0 14px' }} disabled={bestellBusy} onClick={createBestellschein}>
|
||||
🛒 Bestellschein erstellen
|
||||
</button>
|
||||
)}
|
||||
{projekt.status === 'Abgeschlossen' && (
|
||||
<button className="btn btn-danger" style={{ height: 36, fontSize: 11, padding: '0 14px' }} disabled={deleteBusy} onClick={deleteProjekt}>
|
||||
🗑 Löschen
|
||||
|
||||
@@ -347,3 +347,5 @@ table.pinfo td:last-child{text-align:right;font-weight:600}
|
||||
.kab-search-info{font-size:11px;color:var(--muted)}
|
||||
.reftab td .fav-star{font-size:18px;padding:0 4px}
|
||||
@media print{.fav-star,.kab-select .kab-search-row{display:none!important}}
|
||||
.check-line{display:flex;align-items:center;gap:10px;margin-top:14px;font-size:13px;color:var(--text);cursor:pointer}
|
||||
.check-line input{width:18px;height:18px;accent-color:var(--cyan)}
|
||||
|
||||
Reference in New Issue
Block a user