mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-07-09 15:05:59 +00:00
feat(costs): Splitwise-like cost splitting
Add per-payer and per-member custom split amounts with Equally, Custom and Ticket split modes on top of the existing equal split, keep legacy "paid by" expenses working, and document the modes in the Budget Tracking wiki page.
This commit is contained in:
@@ -24,6 +24,7 @@ export const budgetItemMemberSchema = z.object({
|
||||
avatar_url: z.string().nullable().optional(),
|
||||
avatar: z.string().nullable().optional(),
|
||||
budget_item_id: z.number().optional(),
|
||||
amount: z.number().nullable().optional(),
|
||||
});
|
||||
export type BudgetItemMember = z.infer<typeof budgetItemMemberSchema>;
|
||||
|
||||
@@ -126,6 +127,11 @@ const payerInputSchema = z.object({
|
||||
amount: z.number(),
|
||||
});
|
||||
|
||||
const memberInputSchema = z.object({
|
||||
user_id: z.number(),
|
||||
amount: z.number().nullable().optional(),
|
||||
});
|
||||
|
||||
export const budgetCreateItemRequestSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
category: z.string().optional(),
|
||||
@@ -137,6 +143,7 @@ export const budgetCreateItemRequestSchema = z.object({
|
||||
payers: z.array(payerInputSchema).optional(),
|
||||
// Equal-split participants. When omitted, the item has no split (planning-only).
|
||||
member_ids: z.array(z.number()).optional(),
|
||||
members: z.array(memberInputSchema).optional(),
|
||||
persons: z.number().nullable().optional(),
|
||||
days: z.number().nullable().optional(),
|
||||
note: z.string().nullable().optional(),
|
||||
@@ -156,6 +163,7 @@ export const budgetUpdateItemRequestSchema = z.object({
|
||||
exchange_rate: z.number().optional(),
|
||||
payers: z.array(payerInputSchema).optional(),
|
||||
member_ids: z.array(z.number()).optional(),
|
||||
members: z.array(memberInputSchema).optional(),
|
||||
persons: z.number().nullable().optional(),
|
||||
days: z.number().nullable().optional(),
|
||||
note: z.string().nullable().optional(),
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'في الإجمالي فقط — لم تتم التسوية بعد',
|
||||
'costs.tapToInclude': 'اضغط للتضمين',
|
||||
'costs.amount': 'المبلغ',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Apenas no total — ainda não acertado',
|
||||
'costs.tapToInclude': 'Toque para incluir',
|
||||
'costs.amount': 'Valor',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Jen v součtu — zatím nevyrovnáno',
|
||||
'costs.tapToInclude': 'Klepnutím zahrnout',
|
||||
'costs.amount': 'Částka',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Nur Gesamtsumme — noch nicht abgerechnet',
|
||||
'costs.tapToInclude': 'Zum Einbeziehen tippen',
|
||||
'costs.amount': 'Betrag',
|
||||
'costs.split': "Aufteilen",
|
||||
'costs.splitEqually': "Gleichmäßig",
|
||||
'costs.splitCustom': "Individuell",
|
||||
'costs.splitTicket': "Beleg",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -91,6 +91,10 @@ const budget: TranslationStrings = {
|
||||
'costs.category': 'Category',
|
||||
'costs.whoPaid': 'Who paid?',
|
||||
'costs.splitBetween': 'Split equally between',
|
||||
'costs.split': 'Split',
|
||||
'costs.splitEqually': 'Equally',
|
||||
'costs.splitCustom': 'Custom',
|
||||
'costs.splitTicket': 'Ticket',
|
||||
'costs.pickSomeone': 'Pick at least one person to split with.',
|
||||
'costs.splitSummary': 'Split {count} ways · {amount} each',
|
||||
'costs.cat.accommodation': 'Accommodation',
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Solo en el total — aún sin saldar',
|
||||
'costs.tapToInclude': 'Toca para incluir',
|
||||
'costs.amount': 'Importe',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Total seulement — pas encore réglé',
|
||||
'costs.tapToInclude': 'Toucher pour inclure',
|
||||
'costs.amount': 'Montant',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -117,6 +117,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Μόνο στο σύνολο — δεν έχει διακανονιστεί',
|
||||
'costs.tapToInclude': 'Πατήστε για προσθήκη',
|
||||
'costs.amount': 'Ποσό',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Csak az összegben — még nincs rendezve',
|
||||
'costs.tapToInclude': 'Koppintson a hozzáadáshoz',
|
||||
'costs.amount': 'Összeg',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Hanya total — belum diselesaikan',
|
||||
'costs.tapToInclude': 'Ketuk untuk menyertakan',
|
||||
'costs.amount': 'Jumlah',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Solo nel totale — non ancora saldato',
|
||||
'costs.tapToInclude': 'Tocca per includere',
|
||||
'costs.amount': 'Importo',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': '合計のみ — 未精算',
|
||||
'costs.tapToInclude': 'タップして追加',
|
||||
'costs.amount': '金額',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': '합계에만 반영 — 아직 미정산',
|
||||
'costs.tapToInclude': '탭하여 포함',
|
||||
'costs.amount': '금액',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Alleen in totaal — nog niet verrekend',
|
||||
'costs.tapToInclude': 'Tik om toe te voegen',
|
||||
'costs.amount': 'Bedrag',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Tylko w sumie — jeszcze nierozliczone',
|
||||
'costs.tapToInclude': 'Dotknij, aby dodać',
|
||||
'costs.amount': 'Kwota',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Только в сумме — ещё не урегулировано',
|
||||
'costs.tapToInclude': 'Нажмите, чтобы добавить',
|
||||
'costs.amount': 'Сумма',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Endast totalt – ännu inte reglerat',
|
||||
'costs.tapToInclude': 'Tryck för att inkludera',
|
||||
'costs.amount': 'Summa',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Yalnızca toplamda — henüz ödenmedi',
|
||||
'costs.tapToInclude': 'Eklemek için dokun',
|
||||
'costs.amount': 'Tutar',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -116,6 +116,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': 'Лише в сумі — ще не врегульовано',
|
||||
'costs.tapToInclude': 'Натисніть, щоб додати',
|
||||
'costs.amount': 'Сума',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -115,6 +115,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': '僅計入總額 — 尚未結算',
|
||||
'costs.tapToInclude': '點按以加入',
|
||||
'costs.amount': '金額',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
@@ -115,6 +115,9 @@ const budget: TranslationStrings = {
|
||||
'costs.unfinishedHint': '仅计入总额 — 尚未结算',
|
||||
'costs.tapToInclude': '点按以加入',
|
||||
'costs.amount': '金额',
|
||||
'costs.split': "Split",
|
||||
'costs.splitEqually': "Equally",
|
||||
'costs.splitCustom': "Custom",
|
||||
'costs.splitTicket': "Ticket",
|
||||
};
|
||||
|
||||
export default budget;
|
||||
|
||||
Reference in New Issue
Block a user