Skip to content

Commit a31ca73

Browse files
authored
Merge pull request #9 from ndu2/develop
new gtt for invoices
2 parents fa2cdfe + e765d29 commit a31ca73

File tree

12 files changed

+186
-22
lines changed

12 files changed

+186
-22
lines changed

documentation.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ Defaults to `table`. `csv` and `markdown` can be printed to stdout, `pdf` and `x
282282
There are additional options for the invoice output as given in the following example:
283283

284284
```shell
285-
gtt report --output=invoice --file=invoice.md --from 2021-02-01 --to 2021-02-28 --closed --invoiceCurrencyMaxUnit 1 --invoiceTitle "Rechnung" --invoiceAddress "Firma" "Mr. X" "Strasse" "10000 Ort" "Land" --invoiceCurrency "EUR" --invoiceCurrencyPerHour "50" --invoiceVAT "0.15" --invoiceDate "1.03.2021"
285+
gtt report --output=invoice --file=invoice.md --from 2021-02-01 --to 2021-02-28 --closed --invoiceCurrencyMaxUnit 1 --invoiceTitle "Rechnung" --invoiceAddress "Firma" "Mr. X" "Strasse" "10000 Ort" "Land" --invoiceCurrency "EUR" --invoiceCurrencyPerHour "50" --invoiceVAT "0.15" --invoiceDate "1.03.2021" --invoicePositionText "Position Text"
286286
```
287287

288288
For paper invoice, further process the output with a css, see the folder preview (styles.css, invoice.pdf)
@@ -658,7 +658,6 @@ invoiceSettings:
658658
opening:
659659
- Satz 1.
660660
- Satz 2.
661-
positionText: Positionstext
662661
closing:
663662
- Grussformel
664663
-

src/gtt-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ if (program.opts().local) {
1313
config.assertLocalConfig();
1414
}
1515

16-
Fs.open(program.opts().local ? config.local : config.global);
16+
Fs.open(program.opts().local ? config.local : config.global);

src/gtt-list.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const program = require('commander');
2+
const colors = require('colors');
3+
const moment = require('moment');
4+
const Table = require('cli-table');
5+
6+
7+
const Config = require('./include/file-config');
8+
const Cli = require('./include/cli');
9+
const Tasks = require('./include/tasks');
10+
11+
program
12+
.arguments('[project]')
13+
.option('--verbose', 'show verbose output')
14+
.option('-c, --closed', 'show closed issues (instead of opened only)')
15+
.option('--my', 'show only issues assigned to me')
16+
.parse(process.argv);
17+
18+
Cli.verbose = program.verbose;
19+
20+
let config = new Config(process.cwd()),
21+
tasks = new Tasks(config),
22+
type = program.type ? program.type : 'issue',
23+
project = program.args[0];
24+
25+
tasks.list(project, program.closed ? 'closed' : 'opened', program.my)
26+
.then(issues => {
27+
let table = new Table({
28+
style : {compact : true, 'padding-left' : 1}
29+
});
30+
if (issues.length == 0) {
31+
console.log("No issues found.");
32+
}
33+
issues.forEach(issue => {
34+
table.push([issue.iid.toString().magenta, issue.title.green + "\n" + issue.data.web_url.gray, issue.state])
35+
})
36+
console.log(table.toString());
37+
})
38+
.catch(error => Cli.error(error));
39+

src/gtt-log.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ tasks.log()
3939
});
4040
}
4141
)
42-
.catch(error => Cli.error(error));
42+
.catch(error => Cli.error(error));

src/gtt-report.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ program
7272
.option('--invoiceVAT <number>', 'vat decimal (20% = 0.2)')
7373
.option('--invoiceDate <number>', 'date string')
7474
.option('--invoiceCurrencyMaxUnit <number>', 'rouning invoice total, e.g. 0.01, 0.05 or 1')
75+
.option('--invoicePositionText <text>', 'invoice position text')
7576
.parse(process.argv);
7677

7778
// init helpers
@@ -138,6 +139,7 @@ config
138139
.set('invoiceVAT', program.opts().invoiceVAT)
139140
.set('invoiceDate', program.opts().invoiceDate)
140141
.set('invoiceCurrencyMaxUnit', program.opts().invoiceCurrencyMaxUnit)
142+
.set('invoicePositionText', program.opts().invoicePositionText)
141143
.set('_createDump', program.opts().output === 'dump');
142144

143145
// date shortcuts

src/gtt.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ program
1212
.command('stop', 'stop monitoring time')
1313
.command('resume [project]', 'resume monitoring time for last stopped record')
1414
.command('cancel', 'cancel and discard active monitoring time')
15+
.command('list [project]', 'list all open issues')
1516
.command('log', 'log recorded time records')
1617
.command('sync', 'sync local time records to GitLab')
1718
.command('edit [id]', 'edit time record by the given id')

src/models/hasTimes.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class hasTimes extends Base {
1515
constructor(config) {
1616
super(config);
1717
this.times = [];
18+
this.timesWarnings = [];
1819
}
1920

2021
/**
@@ -58,6 +59,7 @@ class hasTimes extends Base {
5859
*/
5960
getTimes() {
6061
let times = [],
62+
timesWarnings = [],
6163
timeSpent = 0,
6264
totalTimeSpent = 0,
6365
timeUsers = {},
@@ -127,18 +129,22 @@ class hasTimes extends Base {
127129
!(created.isSameOrAfter(moment(this.config.get('from'))) && created.isSameOrBefore(moment(this.config.get('to'))))
128130
) return resolve();
129131

132+
// warn about difference, but do not correct as gitlab API
133+
// stats forget the times after an issue is moved to another project.
130134
let difference = this.data.time_stats.total_time_spent - totalTimeSpent,
131135
note = Object.assign({noteable_type: this._typeSingular}, this.data);
132-
133-
times.unshift(new Time(Time.toHumanReadable(difference, this.config.get('hoursPerDay')), null, note, this, this.config));
134-
136+
note.timeWarning = {};
137+
note.timeWarning['stats'] = this.data.time_stats.total_time_spent;
138+
note.timeWarning['notes'] = totalTimeSpent;
139+
timesWarnings.push(new Time(Time.toHumanReadable(difference, this.config.get('hoursPerDay')), null, note, this, this.config));
135140
resolve();
136141
}));
137142

138143
promise.then(() => {
139144
_.each(timeUsers, (time, name) => this[`time_${name}`] = Time.toHumanReadable(time, this.config.get('hoursPerDay'), timeFormat));
140145
this.timeSpent = timeSpent;
141-
this.times = times
146+
this.times = times;
147+
this.timesWarnings = timesWarnings;
142148
});
143149

144150
return promise;

src/models/issue.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,23 @@ class issue extends hasTimes {
2929
return promise;
3030
}
3131

32+
list(project, state, my) {
33+
return new Promise((resolve, reject) => {
34+
let promise;
35+
const query = `scope=${my ? "assigned-to-me" : "all"}&state=${state}`;
36+
if (project) {
37+
promise = this.get(`projects/${encodeURIComponent(project)}/issues?${query}`);
38+
} else {
39+
promise = this.get(`issues/?${query}`);
40+
}
41+
promise.then(response => {
42+
const issues = response.body.map(issue => new this.constructor(this.config, issue))
43+
resolve(issues)
44+
});
45+
promise.catch(error => reject(error))
46+
})
47+
}
48+
3249
/*
3350
* properties
3451
*/

src/models/report.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ class report extends Base {
7979
*/
8080
getMergeRequests() {
8181
let promise = this.all(`projects/${this.project.id}/merge_requests${this.params()}`);
82-
promise.then(mergeRequests => this.mergeRequests = mergeRequests);
82+
let excludes = this.config.get('excludeByLabels');
83+
promise.then(mergeRequests => this.mergeRequests = mergeRequests.filter(mr => (
84+
excludes.filter(l=>mr.labels.includes(l)).length==0 // keep all merge requests not including a exclude label
85+
)));
8386

8487
return promise;
8588
}
@@ -90,8 +93,11 @@ class report extends Base {
9093
*/
9194
getIssues() {
9295
let promise = this.all(`projects/${this.project.id}/issues${this.params()}`);
93-
promise.then(issues => this.issues = issues);
94-
96+
let excludes = this.config.get('excludeByLabels');
97+
promise.then(issues => this.issues = issues.filter(issue => (
98+
issue.moved_to_id == null && // filter moved issues in any case
99+
excludes.filter(l=>issue.labels.includes(l)).length==0 // keep all issues not including a exclude label
100+
)));
95101
return promise;
96102
}
97103

src/output/base.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ class base {
3838
this.write(this.formats.headline(string));
3939
}
4040

41+
/**
42+
* print a headline for warnings
43+
* @param string
44+
*/
45+
warningHeadline(string) {
46+
if (this.config.get('noWarnings')) return;
47+
this.headline(string);
48+
}
4149
/**
4250
* print a warning
4351
* @param string
@@ -104,6 +112,9 @@ class base {
104112
let users = {};
105113
let projects = {};
106114
let times = [];
115+
let timesWarnings = [];
116+
let days = {};
117+
let daysMoment = {};
107118

108119
let spentFreeLabels = this.config.get('freeLabels');
109120
if(undefined === spentFreeLabels) {
@@ -113,11 +124,24 @@ class base {
113124
['issues', 'mergeRequests'].forEach(type => {
114125
this.report[type].forEach(issue => {
115126
issue.times.forEach(time => {
127+
let dateGrp = time.date.format(this.config.get('dateFormatGroupReport'));
116128
if (!users[time.user]) users[time.user] = 0;
117129
if (!projects[time.project_namespace]) projects[time.project_namespace] = 0;
130+
if (!days[dateGrp]) {
131+
days[dateGrp] = {}
132+
daysMoment[dateGrp] = time.date;
133+
};
134+
if(!days[dateGrp][time.project_namespace]) {
135+
days[dateGrp][time.project_namespace] = {};
136+
}
137+
if(!days[dateGrp][time.project_namespace][time.iid]) {
138+
days[dateGrp][time.project_namespace][time.iid] = 0;
139+
}
140+
118141

119142
users[time.user] += time.seconds;
120143
projects[time.project_namespace] += time.seconds;
144+
days[dateGrp][time.project_namespace][time.iid] += time.seconds;
121145

122146
spent += time.seconds;
123147
//if(time.parent.labels)
@@ -132,6 +156,7 @@ class base {
132156
}
133157
times.push(time);
134158
});
159+
issue.timesWarnings.forEach(warning => timesWarnings.push(warning));
135160

136161
totalEstimate += parseInt(issue.stats.time_estimate);
137162
totalSpent += parseInt(issue.stats.total_time_spent);
@@ -152,6 +177,8 @@ class base {
152177
return a.date.isBefore(b.date) ? 1 : -1;
153178
});
154179

180+
this.days = days;
181+
this.daysMoment = daysMoment;
155182
this.users = _.mapObject(users, user => this.config.toHumanReadable(user, 'stats'));
156183
this.projects = _.mapObject(projects, project => this.config.toHumanReadable(project, 'stats'));
157184
this.stats = {
@@ -164,6 +191,7 @@ class base {
164191
this.spent = spent;
165192
this.spentFree = spentFree;
166193
this.totalSpent = totalSpent;
194+
this.timesWarnings = timesWarnings;
167195
}
168196

169197
/**

0 commit comments

Comments
 (0)