Created
February 26, 2024 13:24
-
-
Save ChristopherJohnston/a6e69bd8894f20ecfe127fa4149bd013 to your computer and use it in GitHub Desktop.
Split import from splitwise
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getPrisma } from '@/lib/prisma' | |
import { Prisma } from '@prisma/client' | |
import { randomId, getCategories } from '@/lib/api' | |
import { Client } from 'pg' | |
import { default as csv } from 'csv-parser' | |
import { createReadStream } from 'fs' | |
async function writeData(groupName: string, currency: string, data: any) { | |
const prisma = await getPrisma() | |
// Get map of category name to ID | |
const categoryMapping: Record<string, number> = {} | |
const categories = await getCategories() | |
for (const categoryRow of categories) { | |
categoryMapping[categoryRow.name.toLowerCase()] = categoryRow.id | |
} | |
// Create the Group | |
const groupId = randomId() | |
const group: Prisma.GroupCreateInput = { | |
id: groupId, | |
name: groupName, | |
currency: currency, | |
createdAt: new Date(), | |
} | |
const participantIdsMapping: Record<string, string> = {} | |
const participants: Prisma.ParticipantCreateManyInput[] = [] | |
// Find Participants and add | |
const participantList = Object.keys(data[0]).slice(5) | |
for (const participant of participantList) { | |
const id = randomId() | |
participantIdsMapping[participant] = id | |
participants.push({ | |
id, | |
groupId: groupId, | |
name: participant, | |
}) | |
} | |
// Iterate expense data and add expenses | |
const expenses: Prisma.ExpenseCreateManyInput[] = [] | |
const expenseParticipants: Prisma.ExpensePaidForCreateManyInput[] = [] | |
for (const expenseRow of data) { | |
const id = randomId() | |
let paidBy:string = "" | |
// replace the "other" category names. e.g. "Entertainment - other" -> "Entertainment" | |
const expenseCategory = expenseRow.Category.toLowerCase().replace(" - other", "") | |
// Find the remaining amount for the paying participant | |
const totalAmt = participantList.reduce((sum, participant) => sum + (expenseRow[participant] < 0 ? Math.abs(expenseRow[participant]) : 0),0) | |
const paidByShare = Math.round((expenseRow.Cost - totalAmt)*100) | |
for (const participant of participantList) { | |
const participantShare = expenseRow[participant] | |
const absShare = Math.abs(participantShare*100) | |
if (participantShare > 0) { | |
paidBy = participant | |
} | |
if (expenseCategory == "payment") { | |
// This is a repayment so expenseParticipants is any other | |
// group participant that has a negative amount in the row. | |
// This should generally just be one other participant. | |
if (participantShare < 0) { | |
expenseParticipants.push({ | |
expenseId: id, | |
participantId: participantIdsMapping[participant], | |
shares: absShare | |
}) | |
} | |
} else if (participantShare != 0) { | |
// This group participant is part of this expense | |
expenseParticipants.push({ | |
expenseId: id, | |
participantId: participantIdsMapping[participant], | |
shares: (paidBy == participant) ? paidByShare : absShare | |
}) | |
} | |
} | |
if (paidBy !== "") { | |
expenses.push({ | |
id, | |
amount: Math.round(Number(expenseRow.Cost) * 100), | |
groupId: groupId, | |
title: expenseRow.Description, | |
expenseDate: new Date(expenseRow.Date), | |
categoryId: expenseCategory === "payment" ? 2 : categoryMapping[expenseCategory] ?? 1, | |
createdAt: new Date(), | |
isReimbursement: expenseCategory === "payment", | |
paidById: participantIdsMapping[paidBy], | |
splitMode: "BY_AMOUNT" | |
}) | |
} | |
} | |
console.log('Creating group:', group) | |
await prisma.group.create({ data: group }) | |
console.log('Creating participants:', participants) | |
await prisma.participant.createMany({ data: participants }) | |
console.log('Creating expenses:', expenses) | |
await prisma.expense.createMany({ data: expenses }) | |
console.log('Creating expenseParticipants:', expenseParticipants) | |
await prisma.expensePaidFor.createMany({data: expenseParticipants }) | |
console.log(groupId) | |
} | |
async function main() { | |
const groupName = "Test Group" | |
const currency = "£" | |
const fileName = "./test-group_export.csv" | |
withClient(async (client) => { | |
// Load CSV | |
const data:any = [] | |
createReadStream(fileName) | |
.pipe(csv()) | |
.on('data', (r) => { | |
// console.log(r); | |
data.push(r); | |
}) | |
.on('end', async () => { | |
// console.log(data); | |
await writeData(groupName, currency, data) | |
}) | |
}) | |
} | |
async function withClient(fn: (client: Client) => void | Promise<void>) { | |
const client = new Client({ | |
connectionString: process.env.POSTGRES_PRISMA_URL, | |
ssl: false, | |
}) | |
await client.connect() | |
console.log('Connected.') | |
try { | |
await fn(client) | |
} finally { | |
await client.end() | |
console.log('Disconnected.') | |
} | |
} | |
// Run using: npx ts-node ./src/scripts/import.ts | |
// Need to downgrade nanoid to 3.3.4 to avoid import errors | |
// npm uninstall nanoid | |
// npm install [email protected] | |
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Date | Description | Category | Cost | Currency | Participant A | Participant B | |
---|---|---|---|---|---|---|---|
2020-04-26 | Test Even Split Decimal | General | 12.10 | GBP | 6.05 | -6.05 | |
2020-09-17 | Test Even Split | General | 69 | GBP | -34.5 | 34.5 | |
2020-09-19 | Test Round Up | General | 169.25 | GBP | -84.62 | 84.62 | |
2020-09-20 | Test round down | General | 10.13 | GBP | 5.06 | -5.06 | |
2020-10-01 | Test A owes B | General | 100.00 | GBP | -100 | 100 | |
2020-10-02 | Participant A. pays Participant B. | Payment | 208.01 | GBP | 208.01 | -208.01 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment