@furkot/export-expense-report 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +36 -0
- package/index.js +1 -0
- package/lib/expense-report.js +136 -0
- package/lib/format.js +43 -0
- package/package.json +32 -0
package/Readme.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[![NPM version][npm-image]][npm-url]
|
|
2
|
+
[![Build Status][build-image]][build-url]
|
|
3
|
+
[![Dependency Status][deps-image]][deps-url]
|
|
4
|
+
|
|
5
|
+
# expense-report
|
|
6
|
+
|
|
7
|
+
Generate CSV expense report from [Furkot] trip data.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
$ npm install --save @furkot/export-expense-report
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const expenseReport = require('@furkot/export-expense-report');
|
|
19
|
+
|
|
20
|
+
expenseReport(trip);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT © [Natalia Kowalczyk](https://melitele.me)
|
|
26
|
+
|
|
27
|
+
[Furkot]: https://trips.furkot.com
|
|
28
|
+
|
|
29
|
+
[npm-image]: https://img.shields.io/npm/v/@furkot/export-expense-report
|
|
30
|
+
[npm-url]: https://npmjs.org/package/@furkot/export-expense-report
|
|
31
|
+
|
|
32
|
+
[build-url]: https://github.com/furkot/export-expense-report/actions/workflows/check.yaml
|
|
33
|
+
[build-image]: https://img.shields.io/github/actions/workflow/status/furkot/export-expense-report/check.yaml?branch=main
|
|
34
|
+
|
|
35
|
+
[deps-image]: https://img.shields.io/librariesio/release/npm/@furkot/export-expense-report
|
|
36
|
+
[deps-url]: https://libraries.io/npm/@furkot%2Fexport-expense-report
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./lib/expense-report');
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const format = require('./format');
|
|
2
|
+
|
|
3
|
+
exports = module.exports = expenseReport;
|
|
4
|
+
|
|
5
|
+
exports.contentType = 'text/csv';
|
|
6
|
+
exports.extension = 'csv';
|
|
7
|
+
exports.encoding = 'utf8';
|
|
8
|
+
|
|
9
|
+
const types = init(
|
|
10
|
+
[98, 161],
|
|
11
|
+
'parking',
|
|
12
|
+
init(
|
|
13
|
+
[10, 25, 178, 179],
|
|
14
|
+
'fuel',
|
|
15
|
+
init(
|
|
16
|
+
[9, 24, 27, 28, 30, 31, 32, 90, 139, 143, 156, 158, 159, 172],
|
|
17
|
+
'meal',
|
|
18
|
+
Object.create(null)
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function init(arr, tp, r) {
|
|
24
|
+
return arr.reduce((r, v) => {
|
|
25
|
+
r[v] = tp;
|
|
26
|
+
return r;
|
|
27
|
+
}, r);
|
|
28
|
+
}
|
|
29
|
+
function prepare(line) {
|
|
30
|
+
return line.map(function (item) {
|
|
31
|
+
if (typeof item === 'number') {
|
|
32
|
+
return '' + item;
|
|
33
|
+
}
|
|
34
|
+
if (typeof item === 'string') {
|
|
35
|
+
// quote strings
|
|
36
|
+
return '"' + item.replace(/"/g, '""') + '"';
|
|
37
|
+
}
|
|
38
|
+
// empty string for everything else
|
|
39
|
+
return '';
|
|
40
|
+
}).join(',') + '\n';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getType({
|
|
44
|
+
nights,
|
|
45
|
+
sym
|
|
46
|
+
}) {
|
|
47
|
+
if (nights) {
|
|
48
|
+
return 'lodging';
|
|
49
|
+
}
|
|
50
|
+
return types[sym] || '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function* expenseReport(options) {
|
|
54
|
+
const {
|
|
55
|
+
metadata: {
|
|
56
|
+
currency,
|
|
57
|
+
units,
|
|
58
|
+
mileageRate
|
|
59
|
+
},
|
|
60
|
+
routes
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
63
|
+
const header = [
|
|
64
|
+
'Description',
|
|
65
|
+
'Date',
|
|
66
|
+
'Amount',
|
|
67
|
+
'Type',
|
|
68
|
+
'Notes'
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const steps = routes[0].points;
|
|
72
|
+
let from;
|
|
73
|
+
|
|
74
|
+
function getLines(i) {
|
|
75
|
+
const {
|
|
76
|
+
address,
|
|
77
|
+
arrival_time,
|
|
78
|
+
cost,
|
|
79
|
+
costRoute,
|
|
80
|
+
distance,
|
|
81
|
+
name,
|
|
82
|
+
notes,
|
|
83
|
+
tags
|
|
84
|
+
} = steps[i];
|
|
85
|
+
const to = name || address;
|
|
86
|
+
const date = format.date(new Date(arrival_time));
|
|
87
|
+
const lines = [];
|
|
88
|
+
|
|
89
|
+
if (mileageRate && distance) {
|
|
90
|
+
const line = [];
|
|
91
|
+
|
|
92
|
+
line.push(format.description(
|
|
93
|
+
[from, to].join(' - '),
|
|
94
|
+
format.distance(distance, 1, units, true),
|
|
95
|
+
currency
|
|
96
|
+
));
|
|
97
|
+
line.push(date);
|
|
98
|
+
line.push(format.distance(distance * mileageRate / 100, 2, units));
|
|
99
|
+
line.push('mileage');
|
|
100
|
+
|
|
101
|
+
lines.push(prepare(line));
|
|
102
|
+
}
|
|
103
|
+
if (costRoute) {
|
|
104
|
+
const line = [];
|
|
105
|
+
|
|
106
|
+
line.push(format.description([from, to].join(' - '), currency));
|
|
107
|
+
line.push(date);
|
|
108
|
+
line.push(format.amount(costRoute / 100, 2));
|
|
109
|
+
line.push('tolls');
|
|
110
|
+
|
|
111
|
+
lines.push(prepare(line));
|
|
112
|
+
}
|
|
113
|
+
if (cost) {
|
|
114
|
+
const line = [];
|
|
115
|
+
|
|
116
|
+
line.push(format.description(name || address, tags, currency));
|
|
117
|
+
line.push(date);
|
|
118
|
+
line.push(format.amount(cost / 100, 2));
|
|
119
|
+
line.push(getType(steps[i]));
|
|
120
|
+
line.push(notes);
|
|
121
|
+
|
|
122
|
+
lines.push(prepare(line));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
from = to;
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
yield prepare(header);
|
|
130
|
+
for (let i = 0; i < steps.length; i += 1) {
|
|
131
|
+
const lines = getLines(i);
|
|
132
|
+
for (let j = 0; j < lines.length; j += 1) {
|
|
133
|
+
yield lines[j];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
package/lib/format.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
amount,
|
|
3
|
+
date,
|
|
4
|
+
description,
|
|
5
|
+
distance
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function pad(n) {
|
|
9
|
+
return n < 10 ? '0' + n : n;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function amount(amt, precision) {
|
|
13
|
+
precision = precision || 0;
|
|
14
|
+
return amt.toFixed(precision);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function date(d) {
|
|
18
|
+
return [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()].map(pad).join('-');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function description() {
|
|
22
|
+
return Array.prototype.reduce.call(arguments, (r, v) => {
|
|
23
|
+
if (v) {
|
|
24
|
+
if (Array.isArray(v)) {
|
|
25
|
+
r.push(...v);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
r.push(v);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return r;
|
|
32
|
+
}, []).join(' / ');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function distance(dist, precision, units, withUnits) {
|
|
36
|
+
const div = units === 'km' ? 1000 : 1609.344;
|
|
37
|
+
precision = precision || 0;
|
|
38
|
+
const result = (dist / div).toFixed(precision);
|
|
39
|
+
if (withUnits) {
|
|
40
|
+
return `${result} ${units}`;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@furkot/export-expense-report",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Generate CSV expense report from Furkot trip data.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Natalia Kowalczyk",
|
|
7
|
+
"email": "melitele@code42day.com",
|
|
8
|
+
"url": "https://melitele.me"
|
|
9
|
+
},
|
|
10
|
+
"repository": "furkot/export-expense-report",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"expense-report",
|
|
14
|
+
"furkot",
|
|
15
|
+
"roadtrip",
|
|
16
|
+
"export",
|
|
17
|
+
"planner",
|
|
18
|
+
"CSV"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@pirxpilot/jshint": "~3",
|
|
23
|
+
"should": "~13"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "make check"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"lib"
|
|
31
|
+
]
|
|
32
|
+
}
|