@furkot/export-expense-report
Version:
Generate CSV expense report from Furkot trip data.
160 lines (139 loc) • 3.68 kB
JavaScript
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];
}
}
}