UNPKG

@furkot/export-expense-report

Version:

Generate CSV expense report from Furkot trip data.

160 lines (139 loc) 3.68 kB
import { formatAmount, formatDate, formatDescription, formatDistance } from './format.js'; expenseReport.contentType = 'text/csv'; expenseReport.extension = 'csv'; expenseReport.encoding = 'utf8'; const types = init( [98, 161], 'parking', init( [10, 25, 178, 179], 'fuel', init([9, 24, 27, 28, 30, 31, 32, 90, 139, 143, 156, 158, 159, 172], 'meal', Object.create(null)) ) ); function init(arr, tp, r) { return arr.reduce((r, v) => { r[v] = tp; return r; }, r); } function prepare(line) { const items = line.map(item => { switch (typeof item) { case 'number': return item.toString(); case 'string': // quote strings return `"${item.replace(/"/g, '""')}"`; default: // empty string for everything else return ''; } }); return `${items.join(',')}\n`; } function getType({ nights, sym }) { if (nights) { return 'lodging'; } return types[sym] || ''; } export default function* expenseReport(options) { const { metadata: { currency, units, mileageRate, mode: tripMode }, routes } = options; const header = ['Description', 'Date', 'Amount', 'Currency', 'Type', 'Notes']; const steps = routes[0].points; let from; let passthruDistance = 0; function getLines(i) { const { address, arrival_time, cost, costRoute, day, dayLabel, distance, mode, name, nights, notes, passthru, per_diem, tags } = steps[i]; if (passthru) { passthruDistance += distance; return []; } const stepDistance = (distance ?? 0) + passthruDistance; passthruDistance = 0; const to = name || address; const date = formatDate(new Date(arrival_time)); const inTripMode = mode === undefined || mode === tripMode; const lines = []; if (mileageRate && stepDistance && inTripMode) { const line = []; line.push( formatDescription( [from, to].join(' - '), dayLabel, formatDistance(stepDistance, 1, units, true), formatAmount(mileageRate / 100, 3, currency) ) ); line.push(date); line.push(formatDistance((stepDistance * mileageRate) / 100, 2, units)); line.push(currency); line.push('mileage'); lines.push(prepare(line)); } if (costRoute) { const line = []; line.push(formatDescription([from, to].join(' - '), dayLabel)); line.push(date); line.push(formatAmount(costRoute / 100, 2)); line.push(currency); line.push(inTripMode ? 'tolls' : 'transportation'); lines.push(prepare(line)); } if (cost) { const line = []; line.push( formatDescription( name || address, tags, dayLabel, nights > 1 && [formatAmount(cost / 100, 2, currency), `${day + 1}/${nights}`] ) ); line.push(date); line.push(formatAmount(cost / (nights || 1) / 100, 2)); line.push(currency); line.push(getType(steps[i])); line.push(notes); lines.push(prepare(line)); } if (per_diem) { const line = []; line.push(formatDescription(address || name, tags, dayLabel)); line.push(date); line.push(formatAmount(per_diem / 100, 2)); line.push(currency); line.push('per diem'); line.push(notes); lines.push(prepare(line)); } from = to; return lines; } yield prepare(header); for (let i = 0; i < steps.length; i += 1) { const lines = getLines(i); for (let j = 0; j < lines.length; j += 1) { yield lines[j]; } } }