feat(cashbox): book a catalog penalty directly as a transaction
Adds a catalog picker to the member-booking form in the cashbox (prefills amount/note/type, stays editable) and a "Buchen" button on each penalty catalog entry that jumps to the cashbox with that entry preselected via a penaltyId query param. No backend changes — reuses the existing POST /transactions flow, the catalog only supplies starting values. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,9 @@ import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, convertToParamMap } from '@angular/router';
|
||||
import { AuthStore } from '../../../core/auth/auth-store';
|
||||
import { PenaltyApi } from '../../../core/team/penalty-api';
|
||||
import { TeamStore } from '../../../core/team/team-store';
|
||||
import { TransactionsApi } from '../../../core/team/transactions-api';
|
||||
import { Cashbox } from './cashbox';
|
||||
@@ -54,11 +56,17 @@ describe('Cashbox', () => {
|
||||
},
|
||||
];
|
||||
|
||||
async function setup(roleId = 2, confirm = true) {
|
||||
const penalties = [
|
||||
{ id: 101, description: 'Zu spät zum Training', amount: 5 },
|
||||
{ id: 102, description: 'Handy vergessen', amount: 2.5 },
|
||||
];
|
||||
|
||||
async function setup(roleId = 2, confirm = true, penaltyIdParam: string | null = null) {
|
||||
const createPlayerTransactions = vi.fn(() => of([]));
|
||||
const createTeamWalletTransaction = vi.fn(() => of(activities[0]));
|
||||
const reverseTransaction = vi.fn(() => of(activities[0]));
|
||||
const loadTeamTransactions = vi.fn(() => of(activities));
|
||||
const loadPenalties = vi.fn(() => of(penalties));
|
||||
const refreshTeam = vi.fn();
|
||||
const dialog = {
|
||||
open: vi.fn(() => ({ afterClosed: () => of(confirm) })),
|
||||
@@ -92,6 +100,17 @@ describe('Cashbox', () => {
|
||||
reverseTransaction,
|
||||
},
|
||||
},
|
||||
{ provide: PenaltyApi, useValue: { loadPenalties } },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: {
|
||||
queryParamMap: convertToParamMap(
|
||||
penaltyIdParam ? { penaltyId: penaltyIdParam } : {},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ provide: MatDialog, useValue: dialog },
|
||||
],
|
||||
}).compileComponents();
|
||||
@@ -166,4 +185,33 @@ describe('Cashbox', () => {
|
||||
expect(fixture.nativeElement.querySelector('[data-testid="player-booking"]')).toBeNull();
|
||||
expect(fixture.nativeElement.querySelector('[data-testid="reverse-booking"]')).toBeNull();
|
||||
});
|
||||
|
||||
it('prefills amount, note and type from a manually selected catalog entry, and stays editable', async () => {
|
||||
const { component } = await setup();
|
||||
|
||||
component['onPenaltySelect'](102);
|
||||
|
||||
expect(component['playerForm'].getRawValue()).toEqual(
|
||||
expect.objectContaining({ amount: 2.5, note: 'Handy vergessen', type: 11 }),
|
||||
);
|
||||
|
||||
component['playerForm'].patchValue({ amount: 7 });
|
||||
expect(component['playerForm'].getRawValue().amount).toBe(7);
|
||||
});
|
||||
|
||||
it('prefills the booking form automatically from a penaltyId query param', async () => {
|
||||
const { component } = await setup(2, true, '101');
|
||||
|
||||
expect(component['playerForm'].getRawValue()).toEqual(
|
||||
expect.objectContaining({ amount: 5, note: 'Zu spät zum Training', type: 11 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores an unknown penaltyId query param without error', async () => {
|
||||
const { component } = await setup(2, true, '999');
|
||||
|
||||
expect(component['playerForm'].getRawValue()).toEqual(
|
||||
expect.objectContaining({ amount: 0, note: '', type: 11 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user