Let a planned absence be called off
The old refusal read: "Diese Abwesenheit ist noch nicht wirksam. Sie muss über den Vorgang selbst abgebrochen werden." There was no such way. The row sat in the file, the scheduled change kept running toward its date, and nothing could stop either one. That is not hypothetical. One person went absent in July, came back in August, and still has a second return booked for the first of September — recorded while they were already working again. The guard added yesterday stops a third from being written; it does not remove the one that exists. Absences are called off whole, not field by field. For a planned contract change the scheduled payload gets the affected fields lifted out of it and runs on with the rest; an absence has no fields in that map, and half an absence is not a thing anyone means. So the whole scheduled change is cancelled, and what it had already noted on the person goes with it: the date they were to be away from, the date they were to come back on. Left behind, the profile would show an absence with no event behind it. If the absence is still running, the return date planned when it began applies again. The link between the row and the scheduled change had to exist first — start_karenz and record_karenz_return now record it. Existing rows get it backfilled, but only where one running change of that kind falls on that person and that day. Where two would match, the row keeps refusing: guessing which process to cancel is worse than refusing to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -303,3 +303,37 @@ describe("HistorieTab — Filter", () => {
|
||||
expect(screen.queryByRole("button", { name: /löschen/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// Eine geplante Abwesenheit oder Rückkehr hat keine einzelnen Felder, die
|
||||
// sich herausnehmen liessen — sie fällt als Ganzes. Der Dialog muss das sagen,
|
||||
// sonst steht unter „Wird nicht mehr passieren" eine leere Liste.
|
||||
describe("HistorieTab — geplanten Vorgang abbrechen", () => {
|
||||
const GEPLANT = eintrag({
|
||||
event_type: "Rückkehr",
|
||||
event_date: "2099-09-01",
|
||||
description: "Rückkehr aus Elternkarenz am 2099-09-01",
|
||||
changes: null,
|
||||
pending_id: "p1",
|
||||
});
|
||||
|
||||
it("bietet das Abbrechen an", () => {
|
||||
render(<HistorieTab history={[GEPLANT]} employeeId="e1" />);
|
||||
expect(screen.getByRole("button", { name: /Rückkehr vom 01\.09\.2099 löschen/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("nennt den Knopf beim richtigen Namen und sagt, was entfällt", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<HistorieTab history={[GEPLANT]} employeeId="e1" />);
|
||||
await user.click(screen.getByRole("button", { name: /löschen/ }));
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getByText(/Der Vorgang entfällt ganz/)).toBeInTheDocument();
|
||||
expect(within(dialog).getByRole("button", { name: "Vorgang abbrechen" })).toBeInTheDocument();
|
||||
expect(within(dialog).queryByRole("button", { name: "Löschen und zurücksetzen" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("bietet es ohne hinterlegten Vorgang nicht an", () => {
|
||||
render(<HistorieTab history={[eintrag({ ...GEPLANT, pending_id: null })]} employeeId="e1" />);
|
||||
expect(screen.queryByRole("button", { name: /löschen/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -149,10 +149,36 @@ describe("Reihenfolge von Abwesenheit und Rückkehr", () => {
|
||||
expect(darfKorrigiertWerden(karenz, HEUTE, [frueher, karenz]).erlaubt).toBe(true);
|
||||
});
|
||||
|
||||
it("lässt eine noch nicht wirksame Abwesenheit in Ruhe", () => {
|
||||
const geplant = eintrag({ event_type: "Karenz", event_date: "2099-01-01", pending_id: "p1" });
|
||||
it("bricht eine noch nicht wirksame Abwesenheit ab, wenn ihr Vorgang bekannt ist", () => {
|
||||
// Vorher hiess es hier „Sie muss über den Vorgang selbst abgebrochen
|
||||
// werden" — nur gab es diesen Weg nirgends. Die Zeile stand in der Akte,
|
||||
// der Vorgang lief weiter, und niemand konnte beides aufhalten.
|
||||
const geplant = eintrag({ event_type: "Karenz", event_date: "2099-01-01", pending_id: "p1", changes: null });
|
||||
expect(darfKorrigiertWerden(geplant, HEUTE, [geplant]).erlaubt).toBe(true);
|
||||
});
|
||||
|
||||
it("bleibt bei einer geplanten Abwesenheit ohne hinterlegten Vorgang stehen", () => {
|
||||
// Ohne Verweis liesse sich nur über Person und Datum raten, welcher
|
||||
// Vorgang gemeint ist — und geraten wird beim Abbrechen nicht.
|
||||
const geplant = eintrag({ event_type: "Karenz", event_date: "2099-01-01", pending_id: null, changes: null });
|
||||
const u = darfKorrigiertWerden(geplant, HEUTE, [geplant]);
|
||||
expect(u.erlaubt).toBe(false);
|
||||
expect(u.erlaubt === false && u.grund).toMatch(/noch nicht wirksam/);
|
||||
expect(u.erlaubt === false && u.grund).toMatch(/kein Vorgang hinterlegt/);
|
||||
});
|
||||
|
||||
it("verlangt bei einer bereits wirksamen Abwesenheit weiterhin Feldwerte", () => {
|
||||
// Rückwärts geht es nur, wenn irgendwo steht, was vorher galt.
|
||||
const alt = eintrag({ event_type: "Karenz", event_date: "2026-07-15", changes: null });
|
||||
const u = darfKorrigiertWerden(alt, HEUTE, [alt]);
|
||||
expect(u.erlaubt).toBe(false);
|
||||
expect(u.erlaubt === false && u.grund).toMatch(/keine Feldwerte/);
|
||||
});
|
||||
|
||||
it("hält die Reihenfolge auch bei geplanten Einträgen ein", () => {
|
||||
const geplanteKarenz = eintrag({ id: "k2", event_type: "Karenz", event_date: "2099-01-01", pending_id: "p1", changes: null });
|
||||
const geplanteRueckkehr = eintrag({ id: "r2", event_type: "Rückkehr", event_date: "2099-06-01", pending_id: "p2", changes: null });
|
||||
const u = darfKorrigiertWerden(geplanteKarenz, HEUTE, [geplanteKarenz, geplanteRueckkehr]);
|
||||
expect(u.erlaubt).toBe(false);
|
||||
expect(u.erlaubt === false && u.grund).toMatch(/zuerst gelöscht/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user