diff --git a/client/src/components/Budget/CostsPanel.tsx b/client/src/components/Budget/CostsPanel.tsx index 0d4fa44b2..d36bd2564 100644 --- a/client/src/components/Budget/CostsPanel.tsx +++ b/client/src/components/Budget/CostsPanel.tsx @@ -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 }) diff --git a/client/src/components/Budget/useExpenseForm.test.ts b/client/src/components/Budget/useExpenseForm.test.ts index 0e15a65c0..7e8f984af 100644 --- a/client/src/components/Budget/useExpenseForm.test.ts +++ b/client/src/components/Budget/useExpenseForm.test.ts @@ -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; 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, + }); + }); }); diff --git a/client/src/components/Budget/useExpenseForm.ts b/client/src/components/Budget/useExpenseForm.ts index 4e6920364..2813c9561 100644 --- a/client/src/components/Budget/useExpenseForm.ts +++ b/client/src/components/Budget/useExpenseForm.ts @@ -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) => ({