diff --git a/app/analysis/edit/template.hbs b/app/analysis/edit/template.hbs index 6aa4d595d..8972f685a 100644 --- a/app/analysis/edit/template.hbs +++ b/app/analysis/edit/template.hbs @@ -157,6 +157,26 @@ + + + Reject report + {{#if (eq model.rejected null)}} + + {{/if}} + {{#if (not (eq f.model.rejected model.rejected))}} + + {{/if}} + + + + + Rejected + + + Review + + + @@ -303,6 +323,7 @@ @current={{this.ordering}} @by="verified_by__username" >Verified by + Rejected Review Not billable Billed @@ -323,6 +344,8 @@ + + {{#each reports as |report|}} @@ -351,6 +374,7 @@ report.verifiedBy report.verifiedBy.username }} + @@ -381,13 +405,6 @@ - - - - - - - diff --git a/app/components/report-review-warning/component.js b/app/components/report-review-warning/component.js index b44f974c4..405728753 100644 --- a/app/components/report-review-warning/component.js +++ b/app/components/report-review-warning/component.js @@ -1,14 +1,13 @@ -import { tagName } from "@ember-decorators/component"; import Component from "@ember/component"; import { inject as service } from "@ember/service"; -import classic from "ember-classic-decorator"; -@classic -@tagName("") export default class ReportReviewWarning extends Component { @service session; @service unverifiedReports; + + @service + rejectedReports; } diff --git a/app/components/report-review-warning/template.hbs b/app/components/report-review-warning/template.hbs index 07aca80b9..b2054f9ae 100644 --- a/app/components/report-review-warning/template.hbs +++ b/app/components/report-review-warning/template.hbs @@ -1,25 +1,53 @@ -{{#if this.unverifiedReports.hasReports}} - -{{/if}} +
+ {{#if this.unverifiedReports.hasReports}} + + {{/if}} + {{#if this.rejectedReports.hasReports}} + + {{/if}} +
diff --git a/app/components/user-selection/component.js b/app/components/user-selection/component.js index 545282754..cd874a5db 100644 --- a/app/components/user-selection/component.js +++ b/app/components/user-selection/component.js @@ -17,18 +17,7 @@ export default class UserSelection extends Component { constructor(...args) { super(...args); - this.fetchUsers(); - } - - async fetchUsers() { - try { - await this.tracking.users.perform(); - } catch (e) { - if (e.taskInstance && e.taskInstance.isCanceling) { - return; - } - throw e; - } + this.tracking.users.perform(); } @restartableTask diff --git a/app/models/report-intersection.js b/app/models/report-intersection.js index e3185ae71..7fa4f79b5 100644 --- a/app/models/report-intersection.js +++ b/app/models/report-intersection.js @@ -3,6 +3,7 @@ import Model, { attr, belongsTo } from "@ember-data/model"; export default Model.extend({ comment: attr("string"), notBillable: attr("boolean", { allowNull: true, defaultValue: null }), + rejected: attr("boolean", { allowNull: true, defaultValue: null }), review: attr("boolean", { allowNull: true, defaultValue: null }), billed: attr("boolean", { allowNull: true, defaultValue: null }), verified: attr("boolean", { allowNull: true, defaultValue: null }), diff --git a/app/models/report.js b/app/models/report.js index 7c09df072..a755ec489 100644 --- a/app/models/report.js +++ b/app/models/report.js @@ -56,6 +56,14 @@ export default class Report extends Model { */ @attr("boolean", { defaultValue: false }) review; + /** + * Whether the report got rejected by the reviewer + * + * @property {Boolean} reject + * @public + */ + @attr("boolean", { defaultValue: false }) rejected; + /** * Whether the report is not billable * diff --git a/app/services/rejected-reports.js b/app/services/rejected-reports.js new file mode 100644 index 000000000..6a0570cc7 --- /dev/null +++ b/app/services/rejected-reports.js @@ -0,0 +1,64 @@ +import Service, { inject as service } from "@ember/service"; +import { macroCondition, isTesting } from "@embroider/macros"; +import { tracked } from "@glimmer/tracking"; +import moment from "moment"; + +const INTERVAL_DELAY = 10 * 60000; // 10 Minutes + +export default class RejectedReportsService extends Service { + @service + store; + + @service + session; + + @service + notify; + + @tracked amountReports = 0; + @tracked intervalId; + + get hasReports() { + return this.amountReports > 0; + } + + get reportsToDate() { + return moment().subtract(1, "month").endOf("month"); + } + + constructor(...args) { + super(...args); + + this.pollReports(); + + /* instanbul ignore next */ + if (macroCondition(isTesting())) { + this.intervalId = null; + } else { + this.intervalId = setInterval( + this.pollReports.bind(this), + INTERVAL_DELAY + ); + } + } + + async pollReports() { + try { + const reports = await this.store.query("report", { + to_date: this.reportsToDate.format("YYYY-MM-DD"), + reviewer: this.session.data.user.id, + editable: 1, + rejected: 1, + page: { number: 1, size: 1 }, + }); + + this.amountReports = reports.meta.pagination.count; + } catch (e) { + this.notify.error("Error while polling reports"); + } + } + + willDestroy() { + clearInterval(this.intervalId); + } +} diff --git a/app/services/tracking.js b/app/services/tracking.js index 46ed05742..8fdea1cf9 100644 --- a/app/services/tracking.js +++ b/app/services/tracking.js @@ -258,7 +258,7 @@ export default class TrackingService extends Service { * @property {EmberConcurrency.Task} users * @public */ - @task + @dropTask *users() { return yield this.store.query("user", {}); } @@ -288,7 +288,7 @@ export default class TrackingService extends Service { * @param {Number} customer The customer id to filter by * @public */ - @task + @dropTask *projects(customer) { if (!customer) { // We can't test this because the UI prevents it @@ -305,7 +305,7 @@ export default class TrackingService extends Service { * @param {Number} project The project id to filter by * @public */ - @task + @dropTask *tasks(project) { if (!project) { // We can't test this because the UI prevents it diff --git a/app/styles/analysis.scss b/app/styles/analysis.scss index bed5a8fb6..e04bdd774 100644 --- a/app/styles/analysis.scss +++ b/app/styles/analysis.scss @@ -25,7 +25,7 @@ &:nth-child(4), &:nth-child(5), &:nth-child(6) { - width: 11%; + width: 10%; } &:nth-child(8) { @@ -34,12 +34,13 @@ &:nth-child(9), &:nth-child(10), - &:nth-child(11){ - width: 6%; + &:nth-child(11), + &:nth-child(12) { + width: 5%; } &:nth-child(7) { - width: 20%; + width: 21%; } } @@ -50,7 +51,7 @@ .analysis-scrollable-container::after, .analysis-scrollable-container::before { - content: ''; + content: ""; position: absolute; z-index: 1; left: 0; diff --git a/app/styles/app.scss b/app/styles/app.scss index 4b8654732..d9e40c809 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -55,6 +55,10 @@ strong { font-weight: bold; } +.flex { + display: flex; +} + .pointer { cursor: pointer; } @@ -322,6 +326,7 @@ strong { color: darken($color-border, 25%); height: 100%; display: flex; + flex-direction: column; align-items: center; justify-content: center; text-align: center; diff --git a/tests/unit/services/rejected-reports-test.js b/tests/unit/services/rejected-reports-test.js new file mode 100644 index 000000000..c30a50dcd --- /dev/null +++ b/tests/unit/services/rejected-reports-test.js @@ -0,0 +1,12 @@ +import { setupTest } from "ember-qunit"; +import { module, test } from "qunit"; + +module("Unit | Service | rejectedReports", function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test("it exists", function (assert) { + const service = this.owner.lookup("service:rejected-reports"); + assert.ok(service); + }); +});