mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
fix(budget): a phone ticket split keeps the share of everyone a receipt line names (#1382)
On the phone, a ticket split where a receipt line named someone who was not ticked as a participant saved only the ticked people as members. Their share of the line was dropped, the member amounts no longer added up to total_price, and the settlement kept a difference nobody could clear. The desktop editor had been fixed for this in #1382. Cause: useExpenseForm only added the people named on ticket lines when the caller passed ticketShareMembers, and only the desktop CostsPanel did. The phone MCostSheet kept the old behaviour. Fix: the hook always sends everyone a ticket line names, and the ticketShareMembers option is gone, so both editors save the same members. Red test: src/components/Budget/useExpenseForm.test.ts FE-BUDGET-EXPFORM-021 failed on the unfixed code with "AssertionError: expected { name: 'Lunch', ... } to match object { ... }": member_ids was [1] and members held only user 1, without user 2's 6. Changed assertions: useExpenseForm.test.ts FE-BUDGET-EXPFORM-017 used to expect the phone run (ticketShareMembers false) to send members [{ user_id: 1, amount: 6 }] and member_ids [1]; that phone half is removed and the test now expects every editor to send members for users 1 and 2 with 6 each and member_ids [1, 2].
This commit is contained in:
@@ -1178,7 +1178,7 @@ export function ExpenseModal({ tripId, base, people, me, editing, prefill, onClo
|
||||
setCat, setCurrency, setCustomAmounts, setDay, setName, setNote, setPayerId,
|
||||
setPreviewReceipts: setModalPreviewReceipts, setSplitMode, splitMode, splitShortfall, splitSum, sym, ticketInfo,
|
||||
ticketItems, toggleParticipant, togglePayer, total, totalNum, tripCur, uploadingReceipt, valid,
|
||||
} = useExpenseForm({ tripId, base, people, me, editing, prefill, onSaved, ticketShareMembers: true })
|
||||
} = useExpenseForm({ tripId, base, people, me, editing, prefill, onSaved })
|
||||
|
||||
// The custom split typed as percentages instead of amounts (#1709).
|
||||
const pct = usePercentSplit({ total: totalNum, participants, customAmounts, setCustomAmounts, currency })
|
||||
|
||||
@@ -21,7 +21,7 @@ vi.mock('./receiptUploads', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
// FE-BUDGET-EXPFORM-001 to FE-BUDGET-EXPFORM-020
|
||||
// FE-BUDGET-EXPFORM-001 to FE-BUDGET-EXPFORM-021
|
||||
|
||||
const PEOPLE: TripMember[] = [
|
||||
{ id: 1, username: 'alice', avatar_url: null },
|
||||
@@ -36,7 +36,6 @@ let addToast: ReturnType<typeof vi.fn>;
|
||||
interface Options {
|
||||
editing?: BudgetItem | null;
|
||||
prefill?: ExpensePrefill;
|
||||
ticketShareMembers?: boolean;
|
||||
oneSaveAtATime?: boolean;
|
||||
keepSavingOnSuccess?: boolean;
|
||||
}
|
||||
@@ -52,7 +51,6 @@ function setup(options: Options = {}) {
|
||||
editing: options.editing ?? null,
|
||||
prefill: options.prefill,
|
||||
onSaved,
|
||||
ticketShareMembers: options.ticketShareMembers,
|
||||
oneSaveAtATime: options.oneSaveAtATime,
|
||||
keepSavingOnSuccess: options.keepSavingOnSuccess,
|
||||
})
|
||||
@@ -464,26 +462,22 @@ describe('useExpenseForm: saving', () => {
|
||||
expect(result.current.pendingReceiptFiles).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-BUDGET-EXPFORM-017: a ticket split sends everyone a line names on the desktop, only the ticked on the phone (#1382)', async () => {
|
||||
const run = async (ticketShareMembers: boolean) => {
|
||||
const { result } = setup({ ticketShareMembers });
|
||||
act(() => {
|
||||
result.current.setName('Lunch');
|
||||
result.current.setSplitMode('ticket');
|
||||
result.current.handleAddEmptyItem();
|
||||
});
|
||||
const id = result.current.ticketItems[0].id;
|
||||
act(() => {
|
||||
result.current.handleUpdateItemName(id, 'Pizza');
|
||||
result.current.handleUpdateItemPrice(id, '12');
|
||||
// Bob eats from the line but is not ticked for the expense.
|
||||
result.current.toggleParticipant(2);
|
||||
});
|
||||
await save(result);
|
||||
return addBudgetItem.mock.calls[addBudgetItem.mock.calls.length - 1][1];
|
||||
};
|
||||
const desktop = await run(true);
|
||||
expect(desktop).toMatchObject({
|
||||
it('FE-BUDGET-EXPFORM-017: a ticket split sends everyone a line names, ticked or not (#1382)', async () => {
|
||||
const { result } = setup();
|
||||
act(() => {
|
||||
result.current.setName('Lunch');
|
||||
result.current.setSplitMode('ticket');
|
||||
result.current.handleAddEmptyItem();
|
||||
});
|
||||
const id = result.current.ticketItems[0].id;
|
||||
act(() => {
|
||||
result.current.handleUpdateItemName(id, 'Pizza');
|
||||
result.current.handleUpdateItemPrice(id, '12');
|
||||
// Bob eats from the line but is not ticked for the expense.
|
||||
result.current.toggleParticipant(2);
|
||||
});
|
||||
await save(result);
|
||||
expect(addBudgetItem.mock.calls[0][1]).toMatchObject({
|
||||
members: [
|
||||
{ user_id: 1, amount: 6 },
|
||||
{ user_id: 2, amount: 6 },
|
||||
@@ -492,8 +486,6 @@ describe('useExpenseForm: saving', () => {
|
||||
total_price: 12,
|
||||
ticket_json: JSON.stringify({ items: [{ name: 'Pizza', price: '12', parts: [1, 2] }] }),
|
||||
});
|
||||
const phone = await run(false);
|
||||
expect(phone).toMatchObject({ members: [{ user_id: 1, amount: 6 }], member_ids: [1] });
|
||||
});
|
||||
|
||||
it('FE-BUDGET-EXPFORM-018: a failed save says why; a receipt it could not take back is counted', async () => {
|
||||
@@ -549,4 +541,29 @@ describe('useExpenseForm: saving', () => {
|
||||
await save(invalid.result);
|
||||
expect(saveWithReceipts).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('FE-BUDGET-EXPFORM-021: the phone sends everyone a ticket line names too, so the shares add up to the total (#1382)', async () => {
|
||||
const { result } = setup({ oneSaveAtATime: true, keepSavingOnSuccess: true });
|
||||
act(() => {
|
||||
result.current.setName('Lunch');
|
||||
result.current.setSplitMode('ticket');
|
||||
result.current.handleAddEmptyItem();
|
||||
});
|
||||
const id = result.current.ticketItems[0].id;
|
||||
act(() => {
|
||||
result.current.handleUpdateItemName(id, 'Pizza');
|
||||
result.current.handleUpdateItemPrice(id, '12');
|
||||
// Bob eats from the line but is not ticked for the expense.
|
||||
result.current.toggleParticipant(2);
|
||||
});
|
||||
await save(result);
|
||||
expect(addBudgetItem.mock.calls[0][1]).toMatchObject({
|
||||
members: [
|
||||
{ user_id: 1, amount: 6 },
|
||||
{ user_id: 2, amount: 6 },
|
||||
],
|
||||
member_ids: [1, 2],
|
||||
total_price: 12,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,6 @@ let ticketItemSeq = 0;
|
||||
* through the trip store. Both surfaces lay it out their own way.
|
||||
*
|
||||
* Where the two save differently today, the surface says which way:
|
||||
* `ticketShareMembers` (desktop) also sends everyone a ticket line names (#1382);
|
||||
* `oneSaveAtATime` (phone) ignores a save while one is out; `keepSavingOnSuccess`
|
||||
* (phone) leaves the form busy after a save that went through, as the sheet closes.
|
||||
*/
|
||||
@@ -50,7 +49,6 @@ export function useExpenseForm({
|
||||
editing,
|
||||
prefill,
|
||||
onSaved,
|
||||
ticketShareMembers = false,
|
||||
oneSaveAtATime = false,
|
||||
keepSavingOnSuccess = false,
|
||||
}: {
|
||||
@@ -61,7 +59,6 @@ export function useExpenseForm({
|
||||
editing: BudgetItem | null;
|
||||
prefill?: ExpensePrefill;
|
||||
onSaved: () => void;
|
||||
ticketShareMembers?: boolean;
|
||||
oneSaveAtATime?: boolean;
|
||||
keepSavingOnSuccess?: boolean;
|
||||
}) {
|
||||
@@ -349,7 +346,7 @@ export function useExpenseForm({
|
||||
// only the ticked set would drop their share, leaving the member sum short of
|
||||
// total_price and handing the settlement a difference it can never clear (#1382).
|
||||
const memberIds =
|
||||
splitMode === 'ticket' && ticketShareMembers
|
||||
splitMode === 'ticket'
|
||||
? [...new Set([...participants, ...Object.keys(ticketInfo.shares).map(Number)])].sort((a, b) => a - b)
|
||||
: [...participants];
|
||||
const memberList = memberIds.map((id) => ({
|
||||
|
||||
Reference in New Issue
Block a user