add expenses, on day closing
1. Neue Methode CreateAbsentMemberExpensesAsync() (Zeile 322-421)
- Prüft Club.ExpenseCalculation (None → skip)
- Berechnet Average/Maximum aller Tages-Expenses
- Findet Absent-Expense via ExpenseTriggerType.Absent
- Erstellt PersonExpense für alle abwesenden Members
2. Aufruf in AdvanceStatusAsync() (Zeile 312-316)
- Wird aufgerufen wenn Status → Closed wechselt
- Vor SaveChangesAsync() → eine Transaktion
This commit is contained in:
parent
8fd8c37952
commit
4666056e24
|
|
@ -309,10 +309,114 @@ public class DayService : IDayService
|
||||||
_ => throw new InvalidOperationException($"Unknown day status: {day.Status}")
|
_ => throw new InvalidOperationException($"Unknown day status: {day.Status}")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create absent member expenses when closing
|
||||||
|
if (day.Status == DayStatus.Closed)
|
||||||
|
{
|
||||||
|
await CreateAbsentMemberExpensesAsync(context, day.Id, ct);
|
||||||
|
}
|
||||||
|
|
||||||
day.ModifiedAt = DateTime.UtcNow;
|
day.ModifiedAt = DateTime.UtcNow;
|
||||||
await context.SaveChangesAsync(ct);
|
await context.SaveChangesAsync(ct);
|
||||||
|
|
||||||
return await GetByIdAsync(day.Id, ct)
|
return await GetByIdAsync(day.Id, ct)
|
||||||
?? throw new InvalidOperationException("Failed to advance status.");
|
?? throw new InvalidOperationException("Failed to advance status.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates PersonExpense for absent members when day closes.
|
||||||
|
/// </summary>
|
||||||
|
private async Task CreateAbsentMemberExpensesAsync(
|
||||||
|
AppDbContext context,
|
||||||
|
Guid dayId,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
// 1. Load day with club and participants
|
||||||
|
var day = await context.Days
|
||||||
|
.Include(d => d.Club)
|
||||||
|
.Include(d => d.DayPersons)
|
||||||
|
.FirstAsync(d => d.Id == dayId, ct);
|
||||||
|
|
||||||
|
var club = day.Club;
|
||||||
|
|
||||||
|
// 2. Skip if ExpenseCalculation is None
|
||||||
|
if (club.ExpenseCalculation == ExpenseCalculation.None)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 3. Get all PersonExpense prices for this day
|
||||||
|
var dayExpensePrices = await context.PersonExpenses
|
||||||
|
.Where(pe => pe.DayId == dayId && !pe.IsDeleted)
|
||||||
|
.Select(pe => pe.Price)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
// 4. Edge case: No expenses - skip
|
||||||
|
if (dayExpensePrices.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 5. Calculate price
|
||||||
|
decimal calculatedPrice = club.ExpenseCalculation switch
|
||||||
|
{
|
||||||
|
ExpenseCalculation.Average => Math.Round(dayExpensePrices.Average(), 2),
|
||||||
|
ExpenseCalculation.Maximum => dayExpensePrices.Max(),
|
||||||
|
_ => 0m
|
||||||
|
};
|
||||||
|
|
||||||
|
if (calculatedPrice <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 6. Get Expense linked to Absent trigger
|
||||||
|
var absentExpense = await context.ExpenseTriggers
|
||||||
|
.Where(et => et.ClubId == club.Id
|
||||||
|
&& et.Trigger.ExpenseTriggerType == ExpenseTriggerType.Absent
|
||||||
|
&& !et.Expense.IsDeleted)
|
||||||
|
.Select(et => et.Expense)
|
||||||
|
.FirstOrDefaultAsync(ct);
|
||||||
|
|
||||||
|
// 7. No Absent expense configured - skip
|
||||||
|
if (absentExpense is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 8. Get all club members
|
||||||
|
var allMemberIds = await context.Persons
|
||||||
|
.Where(p => p.ClubId == club.Id
|
||||||
|
&& p.PersonStatus == PersonStatus.Member
|
||||||
|
&& !p.IsDeleted)
|
||||||
|
.Select(p => p.Id)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
// 9. Get participant IDs
|
||||||
|
var participantIds = day.DayPersons
|
||||||
|
.Select(dp => dp.PersonId)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
// 10. Absent members = members NOT in DayPersons
|
||||||
|
var absentMemberIds = allMemberIds
|
||||||
|
.Where(id => !participantIds.Contains(id))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (absentMemberIds.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 11. Create PersonExpense for each absent member
|
||||||
|
foreach (var memberId in absentMemberIds)
|
||||||
|
{
|
||||||
|
var personExpense = new PersonExpense
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
PersonId = memberId,
|
||||||
|
ExpenseId = absentExpense.Id,
|
||||||
|
DayId = dayId,
|
||||||
|
GameId = null,
|
||||||
|
ClubId = club.Id,
|
||||||
|
Name = absentExpense.Name,
|
||||||
|
Price = calculatedPrice,
|
||||||
|
ExpenseType = absentExpense.ExpenseType,
|
||||||
|
PersonExpenseStatus = PersonExpenseStatus.Open,
|
||||||
|
AssignedById = Guid.Empty,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
IsDeleted = false
|
||||||
|
};
|
||||||
|
context.PersonExpenses.Add(personExpense);
|
||||||
|
}
|
||||||
|
// SaveChanges called by AdvanceStatusAsync
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue