Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ describe('GetMealTicketsPerMonthQueryHandler', () => {
} catch (e) {
expect(e).toBeInstanceOf(CooperativeNotFoundException);
expect(e.message).toBe('settings.errors.cooperative_not_found');
verify(userRepository.findUsers(anything(), anything())).never();
verify(
userRepository.findUsers(anything(), anything(), anything())
).never();
verify(mealTicketRemovalRepository.findByMonth(anything())).never();
verify(
eventRepository.findAllEventsByMonth(anything(), anything())
Expand All @@ -70,7 +72,7 @@ describe('GetMealTicketsPerMonthQueryHandler', () => {
when(user2.getFirstName()).thenReturn('Hélène');
when(user2.getLastName()).thenReturn('MARCHOIS');

when(userRepository.findUsers(false, true)).thenResolve([
when(userRepository.findUsers(false, true, false)).thenResolve([
instance(user),
instance(user2)
]);
Expand Down Expand Up @@ -119,7 +121,7 @@ describe('GetMealTicketsPerMonthQueryHandler', () => {
)
]);

verify(userRepository.findUsers(false, true)).once();
verify(userRepository.findUsers(false, true, false)).once();
verify(mealTicketRemovalRepository.findByMonth(date)).once();
verify(eventRepository.findAllEventsByMonth(date, anything())).once();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class GetMealTicketsPerMonthQueryHandler {

const { date } = query;
const [users, mealTicketRemovals] = await Promise.all([
this.userRepository.findUsers(false, true),
this.userRepository.findUsers(false, true, false),
this.mealTicketRemovalRepository.findByMonth(date)
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class GetUserAdministrativeByIdQueryHandler {
user.getEmail(),
user.getRole(),
user.isAdministrativeEditable(),
user.isActive(),
userAdministrativeView
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class GetUserByIdQueryHandler {
user.getLastName(),
user.getEmail(),
user.getRole(),
user.isAdministrativeEditable()
user.isAdministrativeEditable(),
);
}
}
1 change: 0 additions & 1 deletion src/Application/HumanResource/User/Query/GetUsersQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ import { IQuery } from 'src/Application/IQuery';
export class GetUsersQuery implements IQuery {
constructor(
public readonly withAccountant: boolean = false,
public readonly activeOnly: boolean = false
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('GetUsersQueryHandler', () => {
when(user2.getRole()).thenReturn(UserRole.COOPERATOR);
when(user2.isAdministrativeEditable()).thenReturn(true);

when(userRepository.findUsers(true, false)).thenResolve([
when(userRepository.findUsers(true)).thenResolve([
instance(user1),
instance(user2)
]);
Expand All @@ -38,6 +38,7 @@ describe('GetUsersQueryHandler', () => {
'MARCHOIS',
'mathieu@fairness.coop',
UserRole.COOPERATOR,
true,
true
),
new UserView(
Expand All @@ -46,40 +47,55 @@ describe('GetUsersQueryHandler', () => {
'MARCHOIS',
'helene@fairness.coop',
UserRole.COOPERATOR,
true
true,
false
)
];

expect(await handler.execute(new GetUsersQuery(true, false))).toMatchObject(
expect(await handler.execute(new GetUsersQuery(true))).toMatchObject(
expectedResult
);
verify(userRepository.findUsers(true, false)).once();
verify(userRepository.findUsers(true)).once();
});

it('testGetEmptyUsers', async () => {
const userRepository = mock(UserRepository);

when(userRepository.findUsers(false, false)).thenResolve([]);
when(userRepository.findUsers(false)).thenResolve([]);

const handler = new GetUsersQueryHandler(instance(userRepository));

expect(
await handler.execute(new GetUsersQuery(false, false))
await handler.execute(new GetUsersQuery(false))
).toMatchObject([]);
verify(userRepository.findUsers(false, false)).once();
verify(userRepository.findUsers(false)).once();
});

it('testGetActiveUsers', async () => {
const userRepository = mock(UserRepository);

when(userRepository.findUsers(true, true)).thenResolve([]);
when(userRepository.findUsers(true)).thenResolve([]);

const handler = new GetUsersQueryHandler(instance(userRepository));
const expectedResult = [];

expect(await handler.execute(new GetUsersQuery(true, true))).toMatchObject(
expect(await handler.execute(new GetUsersQuery(true))).toMatchObject(
expectedResult
);
verify(userRepository.findUsers(true, true)).once();
verify(userRepository.findUsers(true)).once();
});

it('testGetInactiveUsers', async () => {
const userRepository = mock(UserRepository);

when(userRepository.findUsers(true)).thenResolve([]);

const handler = new GetUsersQueryHandler(instance(userRepository));
const expectedResult = [];

expect(
await handler.execute(new GetUsersQuery(true))
).toMatchObject(expectedResult);
verify(userRepository.findUsers(true)).once();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,8 @@ export class GetUsersQueryHandler {
) {}

public async execute(query: GetUsersQuery): Promise<UserView[]> {
let noLeavingDate = false;
if (query.activeOnly) {
noLeavingDate = true;
}

const users = await this.userRepository.findUsers(
query.withAccountant,
noLeavingDate
);
const userViews: UserView[] = [];

Expand All @@ -31,7 +25,8 @@ export class GetUsersQueryHandler {
user.getLastName(),
user.getEmail(),
user.getRole(),
user.isAdministrativeEditable()
user.isAdministrativeEditable(),
user.isActive(),
)
);
}
Expand Down
1 change: 1 addition & 0 deletions src/Application/HumanResource/User/View/UserView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class UserView {
public readonly email: string,
public readonly role: UserRole,
public readonly isAdministrativeEditable: boolean,
public readonly isActive: boolean = null,
public readonly administrativeView: UserAdministrativeView = null
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface IUserRepository {
findOneByEmail(email: string): Promise<User | undefined>;
findOneById(id: string): Promise<User | undefined>;
save(user: User): Promise<User>;
findUsers(withAccountant: boolean, noLeaveDate: boolean): Promise<User[]>;
findUsers(withAccountant: boolean): Promise<User[]>;
findUsersWithPayslipInfo(): Promise<User[]>;
}
1 change: 1 addition & 0 deletions src/Domain/HumanResource/User/User.entity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('User.entity', () => {
expect(user.getPassword()).toBe('hashPassword');
expect(user.getApiToken()).toBe('hashToken');
expect(user.getRole()).toBe(UserRole.COOPERATOR);
expect(user.isActive()).toBe(true);
expect(user.getUserAdministrative()).toBe(instance(admin));
expect(user.isAdministrativeEditable()).toBe(true);
});
Expand Down
4 changes: 4 additions & 0 deletions src/Domain/HumanResource/User/User.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export class User {
return this.role !== UserRole.ACCOUNTANT;
}

public isActive(): boolean {
return this.userAdministrative.getLeavingDate() === null;
}

public getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import { LoggedUser } from '../../User/Decorator/LoggedUser';
import { LeaveRequestTableFactory } from '../Table/LeaveRequestTableFactory';
import { LeaveRequestsOverviewTableFactory } from '../Table/LeaveRequestOverviewTableFactory';
import { GetLeaveRequestsOverviewQuery } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery';
import { UserView } from 'src/Application/HumanResource/User/View/UserView';
import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery';
import { ListLeaveRequestsControllerDTO } from '../DTO/ListLeaveRequestsControllerDTO';

@Controller('app/people/leave_requests')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class EditProfileController {
user.getLastName(),
user.getEmail(),
user.getRole(),
false
false,
);

return { user: me };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Controller, Inject, UseGuards, Get, Render } from '@nestjs/common';
import { IQueryBus } from 'src/Application/IQueryBus';
import { IsAuthenticatedGuard } from 'src/Infrastructure/HumanResource/User/Security/IsAuthenticatedGuard';
import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName';
import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery';
import { UserTableFactory } from '../Table/UserTableFactory';
import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery';

@Controller('app/people/users')
@UseGuards(IsAuthenticatedGuard)
Expand All @@ -18,10 +18,11 @@ export class ListUsersController {
@WithName('people_users_list')
@Render('pages/users/list.njk')
public async get() {
const users = await this.queryBus.execute(new GetUsersQuery());

const table = this.tableFactory.create(users);
const users = await this.queryBus.execute(
new GetUsersQuery(false)
);
const [ activeUsersTable, inactiveUsersTable ] = this.tableFactory.create(users);

return { table };
return { activeUsersTable, inactiveUsersTable };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export class UserRepository implements IUserRepository {

public findUsers(
withAccountant: boolean,
noLeavingDate: boolean = false
): Promise<User[]> {
const query = this.repository
.createQueryBuilder('user')
Expand All @@ -66,18 +65,16 @@ export class UserRepository implements IUserRepository {
'user.firstName',
'user.lastName',
'user.email',
'user.role'
'user.role',
'userAdministrative'
])
.innerJoin('user.userAdministrative', 'userAdministrative')
.orderBy('user.lastName', 'ASC')
.addOrderBy('user.firstName', 'ASC');

if (false === withAccountant) {
query.andWhere('user.role <> :role', { role: UserRole.ACCOUNTANT });
}
if (noLeavingDate) {
query.innerJoin('user.userAdministrative', 'userAdministrative');
query.andWhere('userAdministrative.leavingDate IS NULL');
}

return query.getMany();
}
Expand Down
26 changes: 17 additions & 9 deletions src/Infrastructure/HumanResource/User/Table/UserTableFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { UserView } from 'src/Application/HumanResource/User/View/UserView';
import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver';
import { RowFactory } from 'src/Infrastructure/Tables/RowFactory';
import { Table } from 'src/Infrastructure/Tables';
import { ICell, Row, Table } from 'src/Infrastructure/Tables';

@Injectable()
export class UserTableFactory {
Expand All @@ -11,20 +11,20 @@ export class UserTableFactory {
private readonly rowFactory: RowFactory
) {}

public create(users: UserView[]): Table {
public create(users: UserView[]): Table[] {
const columns = [
'users-firstName',
'users-lastName',
'users-firstName',
'users-email',
'users-role',
'common-actions'
];

const rows = users.map(user =>
this.rowFactory
const rows = users.reduce((carry, user) => {
const row = this.rowFactory
.createBuilder()
.value(user.firstName)
.value(user.lastName)
.value(user.firstName)
.value(user.email)
.trans('users-role-value', { role: user.role })
.actions({
Expand All @@ -34,9 +34,17 @@ export class UserTableFactory {
})
}
})
.build()
);
.build();

if (user.isActive) {
carry[0].push(row);
} else {
carry[1].push(row);
}

return carry;
}, [[], []] as Array<Row[]>);

return new Table(columns, rows);
return [new Table(columns, rows[0]), new Table(columns, rows[1])];
}
}
4 changes: 4 additions & 0 deletions src/assets/styles/utilities/text.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@
.pc-text--small {
font-size: var(--font-size-sm);
}

.pc-text--muted {
color: var(--text-muted);
}
6 changes: 5 additions & 1 deletion src/templates/pages/users/list.njk
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
{{ links.add(path('people_users_add'))}}
</div>

{% table table %}
<h2>{{ 'users-active-title'|trans }}</h2>
{% table activeUsersTable %}

<h2 class="pc-m" style="--m: calc(4 * var(--w)) 0 0 0">{{ 'users-inactive-title'|trans }}</h2>
{% table inactiveUsersTable %}
</div>
{% endblock main %}
6 changes: 6 additions & 0 deletions src/translations/fr-FR.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# NOTE : ces traductions sont écrites avec Fluent
# Voir : https://projectfluent.org/
# L'intégration à NestJS / Nunjucks est une implémentation custom (voir FluentTranslatorAdapter.ts et fichiers associés)

common-form-save = Enregistrer
common-form-update = Mettre à jour
common-form-delete = Supprimer
Expand Down Expand Up @@ -237,6 +241,8 @@ meal-tickets-removal-date = Je ne souhaite pas recevoir de ticket restaurant pou


users-title = Coopérateur·ices et salarié·es
users-active-title = Actuellement salarié⋅e
users-inactive-title = Précédemment salarié⋅e
users-add-title = Ajouter un·e coopérateur·ice-salarié·e
users-edit-title = Mise à jour des informations administratives de {$user}
users-firstName = Prénom
Expand Down
Loading