diff --git a/client/i18n/fr.json b/client/i18n/fr.json
index 7cfb15d7..9da9f18a 100644
--- a/client/i18n/fr.json
+++ b/client/i18n/fr.json
@@ -95,10 +95,10 @@
},
"daily_rates": {
"title": "TJM",
- "users": "Coopérateurs - salariés",
+ "user": "Coopérateur - salarié",
"amount": "Montant HT",
- "tasks": "Mission",
- "customers": "Nom du client",
+ "task": "Nom de la mission",
+ "customer": "Nom du client",
"errors": {
"already_exist": "Ce TJM a déjà été configuré.",
"not_found": "Le TJM n'existe pas."
@@ -251,11 +251,10 @@
"requests": {
"title": "Demandes de congé",
"view": "Demande de {user}",
- "users": "Coopérateurs - salariés",
+ "user": "Coopérateur - salarié",
"all_day": "Toute la journée",
"periods": "Périodes",
"period": "Du {from} au {to}",
- "leave_types": "Type de congé",
"status": "Etat",
"duration": "Durée",
"comment": "Commentaire",
diff --git a/client/src/routes/accounting/daily_rates/_Table.svelte b/client/src/routes/accounting/daily_rates/_Table.svelte
index 422422fd..441d5880 100644
--- a/client/src/routes/accounting/daily_rates/_Table.svelte
+++ b/client/src/routes/accounting/daily_rates/_Table.svelte
@@ -10,10 +10,10 @@
- {$_('accounting.daily_rates.users')} |
+ {$_('accounting.daily_rates.user')} |
+ {$_('accounting.daily_rates.customer')} |
+ {$_('accounting.daily_rates.task')} |
{$_('accounting.daily_rates.amount')} |
- {$_('accounting.daily_rates.tasks')} |
- {$_('accounting.daily_rates.customers')} |
{$_('common.actions')} |
@@ -21,9 +21,9 @@
{#each items as { id, user, task, customer, amount } (id)}
{user.firstName} {user.lastName} |
- {format(amount)} |
- {task.name} |
{customer.name} |
+ {task.name} |
+ {format(amount)} |
diff --git a/client/src/routes/human_resources/leaves/requests/[id]/_Detail.svelte b/client/src/routes/human_resources/leaves/requests/[id]/_Detail.svelte
index 722588c2..0bcff6ec 100644
--- a/client/src/routes/human_resources/leaves/requests/[id]/_Detail.svelte
+++ b/client/src/routes/human_resources/leaves/requests/[id]/_Detail.svelte
@@ -30,7 +30,7 @@
|
- {$_('human_resources.leaves.requests.leave_types')} |
+ {$_('human_resources.leaves.requests.leave_type.title')} |
{$_(`human_resources.leaves.requests.leave_type.${leaveRequest.type}`)}
|
diff --git a/client/src/routes/human_resources/leaves/requests/_Table.svelte b/client/src/routes/human_resources/leaves/requests/_Table.svelte
index a735e620..34d9c6a8 100644
--- a/client/src/routes/human_resources/leaves/requests/_Table.svelte
+++ b/client/src/routes/human_resources/leaves/requests/_Table.svelte
@@ -19,9 +19,9 @@
- {$_('human_resources.leaves.requests.users')} |
+ {$_('human_resources.leaves.requests.user')} |
{$_('human_resources.leaves.requests.periods')} |
- {$_('human_resources.leaves.requests.leave_types')} |
+ {$_('human_resources.leaves.requests.leave_type.title')} |
{$_('human_resources.leaves.requests.status')} |
{$_('common.actions')} |
diff --git a/server/migrations/1606403084585-InvoiceUnitPrice.ts b/server/migrations/1606403084585-InvoiceUnitPrice.ts
new file mode 100644
index 00000000..8b728cfa
--- /dev/null
+++ b/server/migrations/1606403084585-InvoiceUnitPrice.ts
@@ -0,0 +1,14 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class InvoiceUnitPrice1606403084585 implements MigrationInterface {
+ name = 'InvoiceUnitPrice1606403084585'
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`ALTER TABLE "invoice_item" RENAME COLUMN "timeSpent" TO "quantity"`);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`ALTER TABLE "invoice_item" RENAME COLUMN "quantity" TO "timeSpent"`);
+ }
+
+}
diff --git a/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.spec.ts b/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.spec.ts
index 7c2b1883..29f55707 100644
--- a/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.spec.ts
+++ b/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.spec.ts
@@ -111,7 +111,7 @@ describe('GenerateInvoiceCommandHandler', () => {
task_name: 'Développement',
first_name: 'Mathieu',
last_name: 'MARCHOIS',
- amount: 60000
+ daily_rate: 60000
},
{
time_spent: '420',
@@ -119,7 +119,7 @@ describe('GenerateInvoiceCommandHandler', () => {
task_name: 'Architecture',
first_name: 'Mathieu',
last_name: 'MARCHOIS',
- amount: null
+ daily_rate: null
},
{
time_spent: '4200',
@@ -127,7 +127,7 @@ describe('GenerateInvoiceCommandHandler', () => {
task_name: 'Développement',
first_name: 'Mathieu',
last_name: 'MARCHOIS',
- amount: 60000
+ daily_rate: 60000
}
];
@@ -141,11 +141,12 @@ describe('GenerateInvoiceCommandHandler', () => {
const savedInvoice = mock(Invoice);
when(savedInvoice.getId()).thenReturn('fc8a4cd9-31eb-4fca-814d-b30c05de485d');
+ when(project.getDayDuration()).thenReturn(420);
const invoiceItems = [
- new InvoiceItem(invoice, 'Développement - Mathieu MARCHOIS', 180, 60000, 100),
- new InvoiceItem(invoice, 'Architecture - Mathieu MARCHOIS', 420, 0, 0),
- new InvoiceItem(invoice, 'Développement - Mathieu MARCHOIS', 4200, 60000, 0),
+ new InvoiceItem(invoice, 'Développement - Mathieu MARCHOIS', 43, 60000, 10000),
+ new InvoiceItem(invoice, 'Architecture - Mathieu MARCHOIS', 100, 0, 0),
+ new InvoiceItem(invoice, 'Développement - Mathieu MARCHOIS', 1000, 60000, 0),
];
when(
diff --git a/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.ts b/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.ts
index 0c6c9e1c..0e7642e1 100644
--- a/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.ts
+++ b/server/src/Application/Accounting/Command/Invoice/GenerateInvoiceCommandHandler.ts
@@ -11,6 +11,7 @@ import { NoBillableEventsFoundException } from 'src/Domain/Accounting/Exception/
import { IDateUtils } from 'src/Application/IDateUtils';
import { IProjectRepository } from 'src/Domain/Project/Repository/IProjectRepository';
import { ProjectNotFoundException } from 'src/Domain/Project/Exception/ProjectNotFoundException';
+import { Project } from 'src/Domain/Project/Project.entity';
@CommandHandler(GenerateInvoiceCommand)
export class GenerateInvoiceCommandHandler {
@@ -57,7 +58,7 @@ export class GenerateInvoiceCommandHandler {
);
for (const event of events) {
- invoiceItems.push(this.buildInvoiceItem(invoice, event));
+ invoiceItems.push(this.buildInvoiceItem(invoice, project, event));
}
const savedInvoice = await this.invoiceRepository.save(invoice);
@@ -66,20 +67,22 @@ export class GenerateInvoiceCommandHandler {
return savedInvoice.getId();
}
- private buildInvoiceItem(invoice: Invoice, {
+ private buildInvoiceItem(invoice: Invoice, project: Project, {
time_spent,
billable,
task_name,
first_name,
last_name,
- amount
+ daily_rate
}): InvoiceItem {
+ const quantity = Math.round(time_spent / project.getDayDuration() * 100) / 100;
+
return new InvoiceItem(
invoice,
`${task_name} - ${first_name} ${last_name}`,
- Number(time_spent),
- amount ? Number(amount) : 0,
- billable ? 0 : 100
+ Math.round(quantity * 100),
+ daily_rate ? daily_rate : 0,
+ billable ? 0 : 10000
);
}
}
diff --git a/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.spec.ts b/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.spec.ts
index 655844e9..c3235a67 100644
--- a/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.spec.ts
+++ b/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.spec.ts
@@ -9,6 +9,7 @@ import { Pagination } from 'src/Application/Common/Pagination';
import { Project } from 'src/Domain/Project/Project.entity';
import { InvoiceView } from '../../View/DailyRate/InvoiceView';
import { ProjectView } from 'src/Application/Project/View/ProjectView';
+import { InvoiceItem } from 'src/Domain/Accounting/InvoiceItem.entity';
describe('GetInvoicesQueryHandler', () => {
let invoiceRepository: InvoiceRepository;
@@ -27,6 +28,16 @@ describe('GetInvoicesQueryHandler', () => {
when(project.getName()).thenReturn('Plateforme web');
when(project.getCustomer()).thenReturn(instance(customer));
+ const item11 = mock(InvoiceItem);
+ when(item11.getAmount()).thenReturn(60000); // 600
+ when(item11.getQuantity()).thenReturn(100); // 1 day
+ when(item11.getDiscount()).thenReturn(0);
+
+ const item12 = mock(InvoiceItem);
+ when(item12.getAmount()).thenReturn(60000); // 600
+ when(item12.getQuantity()).thenReturn(300); // 3 day
+ when(item12.getDiscount()).thenReturn(5000); // 50
+
const invoice1 = mock(Invoice);
when(invoice1.getId()).thenReturn('d54f15d6-1a1d-47e8-8672-9f46018f9960');
when(invoice1.getInvoiceId()).thenReturn('FS-2020-0001');
@@ -34,6 +45,17 @@ describe('GetInvoicesQueryHandler', () => {
when(invoice1.getCreatedAt()).thenReturn('2020-11-25T17:43:14.299Z');
when(invoice1.getExpiryDate()).thenReturn('2020-12-25T17:43:14.299Z');
when(invoice1.getProject()).thenReturn(instance(project));
+ when(invoice1.getItems()).thenReturn([instance(item11), instance(item12)]);
+
+ const item21 = mock(InvoiceItem);
+ when(item21.getAmount()).thenReturn(60000); // 600
+ when(item21.getQuantity()).thenReturn(700); // 7 day
+ when(item21.getDiscount()).thenReturn(0);
+
+ const item22 = mock(InvoiceItem);
+ when(item22.getAmount()).thenReturn(70000); // 700
+ when(item22.getQuantity()).thenReturn(43); // 0.43 day
+ when(item22.getDiscount()).thenReturn(0);
const invoice2 = mock(Invoice);
when(invoice2.getId()).thenReturn('b3332cd1-5631-4b7b-a5d4-ba49910cb877');
@@ -42,6 +64,7 @@ describe('GetInvoicesQueryHandler', () => {
when(invoice2.getExpiryDate()).thenReturn('2020-12-25T17:43:14.299Z');
when(invoice2.getStatus()).thenReturn(InvoiceStatus.PAYED);
when(invoice2.getProject()).thenReturn(instance(project));
+ when(invoice2.getItems()).thenReturn([instance(item21), instance(item22)]);
when(invoiceRepository.findInvoices(1)).thenResolve([
[instance(invoice1), instance(invoice2)],
@@ -60,7 +83,7 @@ describe('GetInvoicesQueryHandler', () => {
InvoiceStatus.DRAFT,
'2020-11-25T17:43:14.299Z',
'2020-12-25T17:43:14.299Z',
- 0,
+ 1800,
new ProjectView(
'deffa668-b9af-4a52-94dd-61a35401b917',
'Plateforme web',
@@ -78,7 +101,7 @@ describe('GetInvoicesQueryHandler', () => {
InvoiceStatus.PAYED,
'2020-11-25T17:43:14.299Z',
'2020-12-25T17:43:14.299Z',
- 0,
+ 5401.2,
new ProjectView(
'deffa668-b9af-4a52-94dd-61a35401b917',
'Plateforme web',
diff --git a/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.ts b/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.ts
index 8803250f..be920e8d 100644
--- a/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.ts
+++ b/server/src/Application/Accounting/Query/Invoice/GetInvoicesQueryHandler.ts
@@ -24,6 +24,20 @@ export class GetInvoicesQueryHandler {
const project = invoice.getProject();
const customer = project.getCustomer();
+ let amountExcludingVat = 0;
+
+ for (const item of invoice.getItems()) {
+ const amount = item.getAmount() / 100;
+ const quantity = item.getQuantity() / 100;
+ let totalAmount = amount * quantity;
+
+ if (item.getDiscount() > 0) {
+ totalAmount *= (item.getDiscount() / 10000);
+ }
+
+ amountExcludingVat += totalAmount;
+ }
+
results.push(
new InvoiceView(
invoice.getId(),
@@ -31,7 +45,7 @@ export class GetInvoicesQueryHandler {
invoice.getStatus(),
invoice.getCreatedAt(),
invoice.getExpiryDate(),
- 0,
+ amountExcludingVat * 1.2,
new ProjectView(
project.getId(),
project.getName(),
diff --git a/server/src/Domain/Accounting/DailyRate.entity.ts b/server/src/Domain/Accounting/DailyRate.entity.ts
index 11e23b78..5ef5be69 100644
--- a/server/src/Domain/Accounting/DailyRate.entity.ts
+++ b/server/src/Domain/Accounting/DailyRate.entity.ts
@@ -8,7 +8,7 @@ export class DailyRate {
@PrimaryGeneratedColumn('uuid')
private id: string;
- @Column({type: 'integer', nullable: false})
+ @Column({type: 'integer', nullable: false, comment: 'Stored in base 100'})
private amount: number;
@Column({type: 'timestamp', default: () => 'CURRENT_TIMESTAMP'})
diff --git a/server/src/Domain/Accounting/Invoice.entity.spec.ts b/server/src/Domain/Accounting/Invoice.entity.spec.ts
index cfef9e82..e187359f 100644
--- a/server/src/Domain/Accounting/Invoice.entity.spec.ts
+++ b/server/src/Domain/Accounting/Invoice.entity.spec.ts
@@ -24,5 +24,6 @@ describe('Invoice.entity', () => {
expect(invoice.getOwner()).toBe(instance(user));
expect(invoice.getProject()).toBe(instance(project));
expect(invoice.getQuote()).toBeUndefined();
+ expect(invoice.getItems()).toBeUndefined();
});
});
diff --git a/server/src/Domain/Accounting/Invoice.entity.ts b/server/src/Domain/Accounting/Invoice.entity.ts
index ca2015d8..f1ebdfa5 100644
--- a/server/src/Domain/Accounting/Invoice.entity.ts
+++ b/server/src/Domain/Accounting/Invoice.entity.ts
@@ -5,7 +5,7 @@ import {
ManyToOne,
OneToMany
} from 'typeorm';
-import { Project } from '../Project/Project.entity';
+import { InvoiceUnits, Project } from '../Project/Project.entity';
import { User } from '../HumanResource/User/User.entity';
import { InvoiceItem } from './InvoiceItem.entity';
import { Quote } from './Quote.entity';
@@ -94,4 +94,8 @@ export class Invoice {
public getQuote(): Quote | undefined {
return this.quote;
}
+
+ public getItems(): InvoiceItem[] {
+ return this.items;
+ }
}
diff --git a/server/src/Domain/Accounting/InvoiceItem.entity.spec.ts b/server/src/Domain/Accounting/InvoiceItem.entity.spec.ts
index 7c1af194..c878685b 100644
--- a/server/src/Domain/Accounting/InvoiceItem.entity.spec.ts
+++ b/server/src/Domain/Accounting/InvoiceItem.entity.spec.ts
@@ -8,7 +8,7 @@ describe('InvoiceItem.entity', () => {
const invoiceitem = new InvoiceItem(
instance(invoice),
'Développement web',
- 420,
+ 1,
72000,
0
);
@@ -16,7 +16,8 @@ describe('InvoiceItem.entity', () => {
expect(invoiceitem.getId()).toBe(undefined);
expect(invoiceitem.getAmount()).toBe(72000);
expect(invoiceitem.getDiscount()).toBe(0);
- expect(invoiceitem.getTimeSpent()).toBe(420);
+ expect(invoiceitem.getQuantity()).toBe(1);
+ expect(invoiceitem.getAmount()).toBe(72000);
expect(invoiceitem.getTitle()).toBe('Développement web');
expect(invoiceitem.getInvoice()).toBe(instance(invoice));
});
diff --git a/server/src/Domain/Accounting/InvoiceItem.entity.ts b/server/src/Domain/Accounting/InvoiceItem.entity.ts
index 7090a26c..a3235aa0 100644
--- a/server/src/Domain/Accounting/InvoiceItem.entity.ts
+++ b/server/src/Domain/Accounting/InvoiceItem.entity.ts
@@ -9,13 +9,13 @@ export class InvoiceItem {
@Column({type: 'varchar', nullable: false})
private title: string;
- @Column({type: 'integer', nullable: false, comment: 'Stored in minutes'})
- private timeSpent: number;
+ @Column({type: 'integer', nullable: false, comment: 'Stored in base 100'})
+ private quantity: number;
- @Column({type: 'integer', nullable: false})
+ @Column({type: 'integer', nullable: false, comment: 'Stored in base 100'})
private amount: number;
- @Column({type: 'integer', nullable: true, default: 0})
+ @Column({type: 'integer', nullable: true, default: 0, comment: 'Stored in base 100'})
private discount: number;
@ManyToOne(
@@ -28,13 +28,13 @@ export class InvoiceItem {
constructor(
invoice: Invoice,
title: string,
- timeSpent: number,
+ quantity: number,
amount: number,
discount?: number
) {
this.invoice = invoice;
this.title = title;
- this.timeSpent = timeSpent;
+ this.quantity = quantity;
this.amount = amount;
this.discount = discount;
}
@@ -55,8 +55,8 @@ export class InvoiceItem {
return this.discount;
}
- public getTimeSpent(): number {
- return this.timeSpent;
+ public getQuantity(): number {
+ return this.quantity;
}
public getInvoice(): Invoice {
diff --git a/server/src/Domain/Accounting/QuoteItem.entity.ts b/server/src/Domain/Accounting/QuoteItem.entity.ts
index e223aff4..148841be 100644
--- a/server/src/Domain/Accounting/QuoteItem.entity.ts
+++ b/server/src/Domain/Accounting/QuoteItem.entity.ts
@@ -9,10 +9,10 @@ export class QuoteItem {
@Column({type: 'varchar', nullable: false})
private title: string;
- @Column({type: 'integer', nullable: false})
+ @Column({type: 'integer', nullable: false, comment: 'Stored in base 100'})
private quantity: number;
- @Column({type: 'integer', nullable: false})
+ @Column({type: 'integer', nullable: false, comment: 'Stored in base 100'})
private dailyRate: number;
@ManyToOne(
diff --git a/server/src/Infrastructure/Accounting/Repository/InvoiceRepository.ts b/server/src/Infrastructure/Accounting/Repository/InvoiceRepository.ts
index 7f54d1bd..6dc9fb6b 100644
--- a/server/src/Infrastructure/Accounting/Repository/InvoiceRepository.ts
+++ b/server/src/Infrastructure/Accounting/Repository/InvoiceRepository.ts
@@ -36,8 +36,9 @@ export class InvoiceRepository implements IInvoiceRepository {
'customer.id',
'customer.name',
'invoiceItem.id',
+ 'invoiceItem.quantity',
'invoiceItem.amount',
- 'invoiceItem.timeSpent'
+ 'invoiceItem.discount'
])
.where('invoiceItem.discount <> 100')
.innerJoin('invoice.project', 'project')
diff --git a/server/src/Infrastructure/FairCalendar/Repository/EventRepository.ts b/server/src/Infrastructure/FairCalendar/Repository/EventRepository.ts
index a431bc70..93bacccc 100644
--- a/server/src/Infrastructure/FairCalendar/Repository/EventRepository.ts
+++ b/server/src/Infrastructure/FairCalendar/Repository/EventRepository.ts
@@ -116,7 +116,7 @@ export class EventRepository implements IEventRepository {
.innerJoin('dailyRate.user', 'd_user')
.innerJoin('dailyRate.task', 'd_task')
.innerJoin('dailyRate.customer', 'd_customer')
- }, 'amount')
+ }, 'daily_rate')
.innerJoin('event.project', 'project')
.innerJoin('event.user', 'user')
.innerJoin('event.task', 'task')