diff --git a/migrations/1716653153304-projectActive.ts b/migrations/1716653153304-projectActive.ts new file mode 100644 index 00000000..96547c1b --- /dev/null +++ b/migrations/1716653153304-projectActive.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ProjectActive1716653153304 implements MigrationInterface { + name = 'ProjectActive1716653153304' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "project" ADD "active" boolean NOT NULL DEFAULT true`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "project" DROP COLUMN "active"`); + } + +} diff --git a/src/Application/Project/Command/CreateProjectCommand.ts b/src/Application/Project/Command/CreateProjectCommand.ts index 37757cc4..a1cfc8e1 100644 --- a/src/Application/Project/Command/CreateProjectCommand.ts +++ b/src/Application/Project/Command/CreateProjectCommand.ts @@ -5,6 +5,7 @@ export class CreateProjectCommand implements ICommand { constructor( public readonly name: string, public readonly invoiceUnit: InvoiceUnits, + public readonly active: boolean, public readonly customerId: string ) {} } diff --git a/src/Application/Project/Command/CreateProjectCommandHandler.spec.ts b/src/Application/Project/Command/CreateProjectCommandHandler.spec.ts index be5e2a7f..df9d7181 100644 --- a/src/Application/Project/Command/CreateProjectCommandHandler.spec.ts +++ b/src/Application/Project/Command/CreateProjectCommandHandler.spec.ts @@ -18,6 +18,7 @@ describe('CreateProjectCommandHandler', () => { const command = new CreateProjectCommand( 'Project', InvoiceUnits.DAY, + true, 'b5e8dc18-ca67-4323-bdae-654afe09499f' ); @@ -83,7 +84,9 @@ describe('CreateProjectCommandHandler', () => { ).thenResolve(instance(customer)); when( projectRepository.save( - deepEqual(new Project('Project', InvoiceUnits.DAY, instance(customer))) + deepEqual( + new Project('Project', InvoiceUnits.DAY, true, instance(customer)) + ) ) ).thenResolve(instance(createdProject)); @@ -97,7 +100,9 @@ describe('CreateProjectCommandHandler', () => { verify(isProjectAlreadyExist.isSatisfiedBy('Project')).once(); verify( projectRepository.save( - deepEqual(new Project('Project', InvoiceUnits.DAY, instance(customer))) + deepEqual( + new Project('Project', InvoiceUnits.DAY, true, instance(customer)) + ) ) ).once(); verify(createdProject.getId()).once(); diff --git a/src/Application/Project/Command/CreateProjectCommandHandler.ts b/src/Application/Project/Command/CreateProjectCommandHandler.ts index 1a865eee..cd9b7473 100644 --- a/src/Application/Project/Command/CreateProjectCommandHandler.ts +++ b/src/Application/Project/Command/CreateProjectCommandHandler.ts @@ -19,7 +19,7 @@ export class CreateProjectCommandHandler { ) {} public async execute(command: CreateProjectCommand): Promise { - const { name, customerId, invoiceUnit } = command; + const { name, customerId, active, invoiceUnit } = command; const customer = await this.customerRepository.findOneById(customerId); if (!customer) { @@ -31,7 +31,7 @@ export class CreateProjectCommandHandler { } const project = await this.projectRepository.save( - new Project(name, invoiceUnit, customer) + new Project(name, invoiceUnit, active, customer) ); return project.getId(); diff --git a/src/Application/Project/Command/UpdateProjectCommand.ts b/src/Application/Project/Command/UpdateProjectCommand.ts index 47353651..22cf18c3 100644 --- a/src/Application/Project/Command/UpdateProjectCommand.ts +++ b/src/Application/Project/Command/UpdateProjectCommand.ts @@ -6,6 +6,7 @@ export class UpdateProjectCommand implements ICommand { public readonly id: string, public readonly name: string, public readonly invoiceUnit: InvoiceUnits, + public readonly active: boolean, public readonly customerId: string ) {} } diff --git a/src/Application/Project/Command/UpdateProjectCommandHandler.spec.ts b/src/Application/Project/Command/UpdateProjectCommandHandler.spec.ts index 6070d9b6..b2da8b4e 100644 --- a/src/Application/Project/Command/UpdateProjectCommandHandler.spec.ts +++ b/src/Application/Project/Command/UpdateProjectCommandHandler.spec.ts @@ -22,6 +22,7 @@ describe('UpdateProjectCommandHandler', () => { 'afda00b1-bf49-4102-9bc2-bce17f3acd48', 'Project', InvoiceUnits.HOUR, + true, 'd4aa560e-d2f7-422e-ae8d-6af5d0455eeb' ); @@ -64,10 +65,20 @@ describe('UpdateProjectCommandHandler', () => { ).once(); verify(projectRepository.save(instance(updatedProject))).once(); verify( - updatedProject.update(instance(customer), InvoiceUnits.HOUR, 'Project') + updatedProject.update( + instance(customer), + InvoiceUnits.HOUR, + 'Project', + true + ) ).once(); verify( - updatedProject.update(instance(customer), InvoiceUnits.HOUR, 'Project') + updatedProject.update( + instance(customer), + InvoiceUnits.HOUR, + 'Project', + true + ) ).calledBefore(projectRepository.save(instance(updatedProject))); verify(updatedProject.getName()).once(); }); @@ -88,7 +99,9 @@ describe('UpdateProjectCommandHandler', () => { projectRepository.findOneById('afda00b1-bf49-4102-9bc2-bce17f3acd48') ).once(); verify(projectRepository.save(anything())).never(); - verify(updatedProject.update(anything(), anything(), anything())).never(); + verify( + updatedProject.update(anything(), anything(), anything(), anything()) + ).never(); verify(updatedProject.getName()).never(); } }); @@ -114,7 +127,9 @@ describe('UpdateProjectCommandHandler', () => { customerRepository.findOneById('d4aa560e-d2f7-422e-ae8d-6af5d0455eeb') ).once(); verify(projectRepository.save(anything())).never(); - verify(updatedProject.update(anything(), anything(), anything())).never(); + verify( + updatedProject.update(anything(), anything(), anything(), anything()) + ).never(); verify(updatedProject.getName()).never(); } }); @@ -141,7 +156,9 @@ describe('UpdateProjectCommandHandler', () => { customerRepository.findOneById('d4aa560e-d2f7-422e-ae8d-6af5d0455eeb') ).once(); verify(projectRepository.save(anything())).never(); - verify(updatedProject.update(anything(), anything(), anything())).never(); + verify( + updatedProject.update(anything(), anything(), anything(), anything()) + ).never(); verify(updatedProject.getName()).once(); } }); diff --git a/src/Application/Project/Command/UpdateProjectCommandHandler.ts b/src/Application/Project/Command/UpdateProjectCommandHandler.ts index 1043ccaf..339e560d 100644 --- a/src/Application/Project/Command/UpdateProjectCommandHandler.ts +++ b/src/Application/Project/Command/UpdateProjectCommandHandler.ts @@ -19,7 +19,7 @@ export class UpdateProjectCommandHandler { ) {} public async execute(command: UpdateProjectCommand): Promise { - const { id, name, customerId, invoiceUnit } = command; + const { id, name, customerId, invoiceUnit, active } = command; const project = await this.projectRepository.findOneById(id); if (!project) { @@ -38,7 +38,7 @@ export class UpdateProjectCommandHandler { throw new ProjectAlreadyExistException(); } - project.update(customer, invoiceUnit, name); + project.update(customer, invoiceUnit, name, active); await this.projectRepository.save(project); } } diff --git a/src/Application/Project/Query/GetProjectByIdQueryHandler.spec.ts b/src/Application/Project/Query/GetProjectByIdQueryHandler.spec.ts index 3d2186a3..82bb56db 100644 --- a/src/Application/Project/Query/GetProjectByIdQueryHandler.spec.ts +++ b/src/Application/Project/Query/GetProjectByIdQueryHandler.spec.ts @@ -24,6 +24,7 @@ describe('GetProjectByIdQueryHandler', () => { when(project.getId()).thenReturn('eb9e1d9b-dce2-48a9-b64f-f0872f3157d2'); when(project.getName()).thenReturn('Project'); + when(project.isActive()).thenReturn(true); when(project.getInvoiceUnit()).thenReturn(InvoiceUnits.DAY); when(project.getCustomer()).thenReturn(instance(customer)); when( @@ -34,6 +35,7 @@ describe('GetProjectByIdQueryHandler', () => { new ProjectView( 'eb9e1d9b-dce2-48a9-b64f-f0872f3157d2', 'Project', + true, InvoiceUnits.DAY, new CustomerView('aeb50974-0dcd-4ef4-af43-d656250e43bc', 'Customer') ) @@ -44,6 +46,7 @@ describe('GetProjectByIdQueryHandler', () => { ).once(); verify(project.getId()).once(); verify(project.getName()).once(); + verify(project.isActive()).once(); verify(project.getInvoiceUnit()).once(); verify(project.getCustomer()).once(); verify(customer.getId()).once(); diff --git a/src/Application/Project/Query/GetProjectByIdQueryHandler.ts b/src/Application/Project/Query/GetProjectByIdQueryHandler.ts index 6fcfad94..ab6fef7e 100644 --- a/src/Application/Project/Query/GetProjectByIdQueryHandler.ts +++ b/src/Application/Project/Query/GetProjectByIdQueryHandler.ts @@ -24,6 +24,7 @@ export class GetProjectByIdQueryHandler { return new ProjectView( project.getId(), project.getName(), + project.isActive(), project.getInvoiceUnit(), new CustomerView(customer.getId(), customer.getName()) ); diff --git a/src/Application/Project/Query/GetProjectsQuery.ts b/src/Application/Project/Query/GetProjectsQuery.ts index b6928e5e..0c5bd0cb 100644 --- a/src/Application/Project/Query/GetProjectsQuery.ts +++ b/src/Application/Project/Query/GetProjectsQuery.ts @@ -3,6 +3,7 @@ import { IQuery } from 'src/Application/IQuery'; export class GetProjectsQuery implements IQuery { constructor( public readonly page: number | null, + public readonly activeOnly = true, public readonly customerId?: string ) {} } diff --git a/src/Application/Project/Query/GetProjectsQueryHandler.spec.ts b/src/Application/Project/Query/GetProjectsQueryHandler.spec.ts index af4e6105..ff4750db 100644 --- a/src/Application/Project/Query/GetProjectsQueryHandler.spec.ts +++ b/src/Application/Project/Query/GetProjectsQueryHandler.spec.ts @@ -23,22 +23,25 @@ describe('GetProjectsQueryHandler', () => { const project1 = mock(Project); when(project1.getId()).thenReturn('eb9e1d9b-dce2-48a9-b64f-f0872f3157d2'); when(project1.getName()).thenReturn('z51'); + when(project1.isActive()).thenReturn(false); when(project1.getInvoiceUnit()).thenReturn(InvoiceUnits.DAY); when(project1.getCustomer()).thenReturn(instance(customer1)); const project2 = mock(Project); when(project2.getId()).thenReturn('d54f15d6-1a1d-47e8-8672-9f46018f9960'); when(project2.getName()).thenReturn('BO cruiser'); + when(project2.isActive()).thenReturn(true); when(project2.getInvoiceUnit()).thenReturn(InvoiceUnits.HOUR); when(project2.getCustomer()).thenReturn(instance(customer1)); const project3 = mock(Project); when(project3.getId()).thenReturn('992eb372-cc02-4ffe-86e0-7b955b7f1a6e'); when(project3.getInvoiceUnit()).thenReturn(InvoiceUnits.HOUR); + when(project3.isActive()).thenReturn(true); when(project3.getName()).thenReturn('Vimeet'); when(project3.getCustomer()).thenReturn(instance(customer2)); - when(projectRepository.findProjects(1, undefined)).thenResolve([ + when(projectRepository.findProjects(1, false, undefined)).thenResolve([ [instance(project3), instance(project2), instance(project1)], 3 ]); @@ -52,12 +55,14 @@ describe('GetProjectsQueryHandler', () => { new ProjectView( '992eb372-cc02-4ffe-86e0-7b955b7f1a6e', 'Vimeet', + true, InvoiceUnits.HOUR, new CustomerView('b9a9b094-5bb2-4d0b-b01e-231b6cb50039', 'Proximum') ), new ProjectView( 'd54f15d6-1a1d-47e8-8672-9f46018f9960', 'BO cruiser', + true, InvoiceUnits.HOUR, new CustomerView( '58958f69-d104-471b-b780-bbb0ec6c52da', @@ -67,6 +72,7 @@ describe('GetProjectsQueryHandler', () => { new ProjectView( 'eb9e1d9b-dce2-48a9-b64f-f0872f3157d2', 'z51', + false, InvoiceUnits.DAY, new CustomerView( '58958f69-d104-471b-b780-bbb0ec6c52da', @@ -77,10 +83,10 @@ describe('GetProjectsQueryHandler', () => { 3 ); - expect(await queryHandler.execute(new GetProjectsQuery(1))).toMatchObject( - expectedResult - ); - verify(projectRepository.findProjects(1, undefined)).once(); + expect( + await queryHandler.execute(new GetProjectsQuery(1, false)) + ).toMatchObject(expectedResult); + verify(projectRepository.findProjects(1, false, undefined)).once(); }); it('testGetAllProjects', async () => { @@ -93,16 +99,18 @@ describe('GetProjectsQueryHandler', () => { const project1 = mock(Project); when(project1.getId()).thenReturn('eb9e1d9b-dce2-48a9-b64f-f0872f3157d2'); when(project1.getName()).thenReturn('z51'); + when(project1.isActive()).thenReturn(true); when(project1.getInvoiceUnit()).thenReturn(InvoiceUnits.DAY); when(project1.getCustomer()).thenReturn(instance(customer1)); const project2 = mock(Project); when(project2.getId()).thenReturn('d54f15d6-1a1d-47e8-8672-9f46018f9960'); when(project2.getName()).thenReturn('BO cruiser'); + when(project2.isActive()).thenReturn(true); when(project2.getInvoiceUnit()).thenReturn(InvoiceUnits.HOUR); when(project2.getCustomer()).thenReturn(instance(customer1)); - when(projectRepository.findProjects(null, undefined)).thenResolve([ + when(projectRepository.findProjects(null, true, undefined)).thenResolve([ [instance(project2), instance(project1)], 2 ]); @@ -116,6 +124,7 @@ describe('GetProjectsQueryHandler', () => { new ProjectView( 'd54f15d6-1a1d-47e8-8672-9f46018f9960', 'BO cruiser', + true, InvoiceUnits.HOUR, new CustomerView( '58958f69-d104-471b-b780-bbb0ec6c52da', @@ -125,6 +134,7 @@ describe('GetProjectsQueryHandler', () => { new ProjectView( 'eb9e1d9b-dce2-48a9-b64f-f0872f3157d2', 'z51', + true, InvoiceUnits.DAY, new CustomerView( '58958f69-d104-471b-b780-bbb0ec6c52da', @@ -136,8 +146,8 @@ describe('GetProjectsQueryHandler', () => { ); expect( - await queryHandler.execute(new GetProjectsQuery(null)) + await queryHandler.execute(new GetProjectsQuery(null, true)) ).toMatchObject(expectedResult); - verify(projectRepository.findProjects(null, undefined)).once(); + verify(projectRepository.findProjects(null, true, undefined)).once(); }); }); diff --git a/src/Application/Project/Query/GetProjectsQueryHandler.ts b/src/Application/Project/Query/GetProjectsQueryHandler.ts index 3e6bfe7b..58fe0421 100644 --- a/src/Application/Project/Query/GetProjectsQueryHandler.ts +++ b/src/Application/Project/Query/GetProjectsQueryHandler.ts @@ -16,12 +16,13 @@ export class GetProjectsQueryHandler { public async execute( query: GetProjectsQuery ): Promise> { - const { customerId, page } = query; + const { customerId, activeOnly, page } = query; const projectViews: ProjectView[] = []; const [projects, total] = await this.projectRepository.findProjects( page, + activeOnly, customerId ); @@ -32,6 +33,7 @@ export class GetProjectsQueryHandler { new ProjectView( project.getId(), project.getName(), + project.isActive(), project.getInvoiceUnit(), new CustomerView(customer.getId(), customer.getName()) ) diff --git a/src/Application/Project/View/ProjectView.ts b/src/Application/Project/View/ProjectView.ts index ee83557f..214208be 100644 --- a/src/Application/Project/View/ProjectView.ts +++ b/src/Application/Project/View/ProjectView.ts @@ -4,6 +4,7 @@ export class ProjectView { constructor( public readonly id: string, public readonly name: string, + public readonly active?: boolean, public readonly invoiceUnit?: string, public readonly customer?: CustomerView ) {} diff --git a/src/Domain/Project/Project.entity.spec.ts b/src/Domain/Project/Project.entity.spec.ts index c65f1266..1bf6b7a4 100644 --- a/src/Domain/Project/Project.entity.spec.ts +++ b/src/Domain/Project/Project.entity.spec.ts @@ -10,6 +10,7 @@ describe('Project.entity', () => { const project = new Project( 'Project name', InvoiceUnits.DAY, + true, instance(customer) ); @@ -17,6 +18,7 @@ describe('Project.entity', () => { expect(project.getFullName()).toBe('[Radio France] Project name'); expect(project.getName()).toBe('Project name'); expect(project.getInvoiceUnit()).toBe(InvoiceUnits.DAY); + expect(project.isActive()).toBe(true); expect(project.getCustomer()).toBe(instance(customer)); }); @@ -29,13 +31,15 @@ describe('Project.entity', () => { const project = new Project( 'Project name', InvoiceUnits.DAY, + false, instance(customer) ); - project.update(instance(customer2), InvoiceUnits.HOUR, 'project'); + project.update(instance(customer2), InvoiceUnits.HOUR, 'project', true); expect(project.getId()).toBe(undefined); expect(project.getFullName()).toBe('[RF] project'); expect(project.getName()).toBe('project'); + expect(project.isActive()).toBe(true); expect(project.getInvoiceUnit()).toBe(InvoiceUnits.HOUR); expect(project.getCustomer()).toBe(instance(customer2)); }); diff --git a/src/Domain/Project/Project.entity.ts b/src/Domain/Project/Project.entity.ts index 49a1468a..8305df87 100644 --- a/src/Domain/Project/Project.entity.ts +++ b/src/Domain/Project/Project.entity.ts @@ -20,13 +20,22 @@ export class Project { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) private createdAt: Date; + @Column({ type: 'boolean', nullable: false, default: true }) + private active: boolean; + @ManyToOne(type => Customer, { nullable: false, onDelete: 'CASCADE' }) private customer: Customer; - constructor(name: string, invoiceUnit: InvoiceUnits, customer: Customer) { + constructor( + name: string, + invoiceUnit: InvoiceUnits, + active: boolean, + customer: Customer + ) { this.name = name; this.invoiceUnit = invoiceUnit; this.customer = customer; + this.active = active; } public getId(): string { @@ -53,13 +62,19 @@ export class Project { return `[${this.customer.getName()}] ${this.getName()}`; } + public isActive(): boolean { + return this.active; + } + public update( customer: Customer, invoiceUnit: InvoiceUnits, - name: string + name: string, + active: boolean ): void { this.customer = customer; this.invoiceUnit = invoiceUnit; this.name = name; + this.active = active; } } diff --git a/src/Domain/Project/Repository/IProjectRepository.ts b/src/Domain/Project/Repository/IProjectRepository.ts index e651b7dc..32e81629 100644 --- a/src/Domain/Project/Repository/IProjectRepository.ts +++ b/src/Domain/Project/Repository/IProjectRepository.ts @@ -6,6 +6,7 @@ export interface IProjectRepository { findOneById(id: string): Promise; findProjects( page: number | null, + activeOnly: boolean, customerId?: string ): Promise<[Project[], number]>; } diff --git a/src/Domain/Project/Specification/IsProjectAlreadyExist.spec.ts b/src/Domain/Project/Specification/IsProjectAlreadyExist.spec.ts index dead4d40..dc42ff73 100644 --- a/src/Domain/Project/Specification/IsProjectAlreadyExist.spec.ts +++ b/src/Domain/Project/Specification/IsProjectAlreadyExist.spec.ts @@ -17,7 +17,7 @@ describe('IsProjectAlreadyExist', () => { it('testProjectAlreadyExist', async () => { when(projectRepository.findOneByName('Encom')).thenResolve( - new Project('Encom', InvoiceUnits.DAY, new Customer('Radio France')) + new Project('Encom', InvoiceUnits.DAY, true, new Customer('Radio France')) ); expect(await isProjectAlreadyExist.isSatisfiedBy('Encom')).toBe(true); verify(projectRepository.findOneByName('Encom')).once(); diff --git a/src/Infrastructure/FairCalendar/Controller/AddEventController.ts b/src/Infrastructure/FairCalendar/Controller/AddEventController.ts index 7dac423f..897e151c 100644 --- a/src/Infrastructure/FairCalendar/Controller/AddEventController.ts +++ b/src/Infrastructure/FairCalendar/Controller/AddEventController.ts @@ -53,7 +53,7 @@ export class AddEventController { const tasksPagination = await this.queryBus.execute(new GetTasksQuery(1)); const projectsPagination = await this.queryBus.execute( - new GetProjectsQuery(null) + new GetProjectsQuery(null, true) ); const { dayDuration } = await this.queryBus.execute( diff --git a/src/Infrastructure/FairCalendar/Controller/EditEventController.ts b/src/Infrastructure/FairCalendar/Controller/EditEventController.ts index 7d267685..00729abc 100644 --- a/src/Infrastructure/FairCalendar/Controller/EditEventController.ts +++ b/src/Infrastructure/FairCalendar/Controller/EditEventController.ts @@ -63,7 +63,7 @@ export class EditEventController { ); const projectsPagination: Pagination = await this.queryBus.execute( - new GetProjectsQuery(null) + new GetProjectsQuery(null, true) ); const { dayDuration }: CooperativeView = await this.queryBus.execute( diff --git a/src/Infrastructure/Project/Controller/AddProjectController.ts b/src/Infrastructure/Project/Controller/AddProjectController.ts index 873ca5a1..e907a754 100644 --- a/src/Infrastructure/Project/Controller/AddProjectController.ts +++ b/src/Infrastructure/Project/Controller/AddProjectController.ts @@ -42,17 +42,18 @@ export class AddProjectController { ); return { + active: true, customers: customers.items }; } @Post() public async poqr(@Body() projectDto: ProjectDTO, @Res() res: Response) { - const { name, customerId } = projectDto; + const { name, customerId, active } = projectDto; try { await this.commandBus.execute( - new CreateProjectCommand(name, InvoiceUnits.DAY, customerId) + new CreateProjectCommand(name, InvoiceUnits.DAY, active, customerId) ); res.redirect(303, this.resolver.resolve('crm_projects_list')); diff --git a/src/Infrastructure/Project/Controller/EditProjectController.ts b/src/Infrastructure/Project/Controller/EditProjectController.ts index c5fe67bd..362ee6f7 100644 --- a/src/Infrastructure/Project/Controller/EditProjectController.ts +++ b/src/Infrastructure/Project/Controller/EditProjectController.ts @@ -60,11 +60,17 @@ export class EditProjectController { @Body() dto: ProjectDTO, @Res() res: Response ) { - const { name, customerId } = dto; + const { name, customerId, active } = dto; try { await this.commandBus.execute( - new UpdateProjectCommand(idDto.id, name, InvoiceUnits.DAY, customerId) + new UpdateProjectCommand( + idDto.id, + name, + InvoiceUnits.DAY, + active, + customerId + ) ); res.redirect(303, this.resolver.resolve('crm_projects_list')); diff --git a/src/Infrastructure/Project/Controller/ListProjectsController.ts b/src/Infrastructure/Project/Controller/ListProjectsController.ts index 8ca87152..41bd1d73 100644 --- a/src/Infrastructure/Project/Controller/ListProjectsController.ts +++ b/src/Infrastructure/Project/Controller/ListProjectsController.ts @@ -29,7 +29,7 @@ export class ListProjectsController { @Render('pages/projects/list.njk') public async get(@Query() pagination: PaginationDTO) { const projects: Pagination = await this.queryBus.execute( - new GetProjectsQuery(pagination.page) + new GetProjectsQuery(pagination.page, false) ); const table = this.tableFactory.create(projects.items); diff --git a/src/Infrastructure/Project/DTO/ProjectDTO.spec.ts b/src/Infrastructure/Project/DTO/ProjectDTO.spec.ts index a3551896..bf23e15e 100644 --- a/src/Infrastructure/Project/DTO/ProjectDTO.spec.ts +++ b/src/Infrastructure/Project/DTO/ProjectDTO.spec.ts @@ -5,6 +5,7 @@ describe('ProjectDTO', () => { it('testValidDTO', async () => { const dto = new ProjectDTO(); dto.name = 'Project'; + dto.active = false; dto.customerId = '2218609f-293b-4438-b3a0-cce8961e8acc'; const validation = await validate(dto); @@ -14,6 +15,7 @@ describe('ProjectDTO', () => { it('testInvalidDTO', async () => { const dto = new ProjectDTO(); dto.name = ''; + dto.active = false; dto.customerId = '12'; const validation = await validate(dto); diff --git a/src/Infrastructure/Project/DTO/ProjectDTO.ts b/src/Infrastructure/Project/DTO/ProjectDTO.ts index 4295810a..768e7a20 100644 --- a/src/Infrastructure/Project/DTO/ProjectDTO.ts +++ b/src/Infrastructure/Project/DTO/ProjectDTO.ts @@ -1,8 +1,13 @@ -import { IsNotEmpty, IsUUID } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; export class ProjectDTO { @IsNotEmpty() public name: string; + @IsOptional() + @Transform((_, { active }) => active === 'true') + @IsBoolean() + public active? = false; @IsNotEmpty() @IsUUID() diff --git a/src/Infrastructure/Project/Repository/ProjectRepository.ts b/src/Infrastructure/Project/Repository/ProjectRepository.ts index 268204c9..7f3168cd 100644 --- a/src/Infrastructure/Project/Repository/ProjectRepository.ts +++ b/src/Infrastructure/Project/Repository/ProjectRepository.ts @@ -30,6 +30,7 @@ export class ProjectRepository implements IProjectRepository { 'project.id', 'project.name', 'project.invoiceUnit', + 'project.active', 'customer.id', 'customer.name' ]) @@ -40,6 +41,7 @@ export class ProjectRepository implements IProjectRepository { public findProjects( page: number | null = 1, + activeOnly: boolean, customerId?: string ): Promise<[Project[], number]> { let query = this.repository @@ -47,6 +49,7 @@ export class ProjectRepository implements IProjectRepository { .select([ 'project.id', 'project.name', + 'project.active', 'project.invoiceUnit', 'customer.id', 'customer.name' @@ -55,6 +58,10 @@ export class ProjectRepository implements IProjectRepository { .orderBy('customer.name', 'ASC') .addOrderBy('project.name', 'ASC'); + if (activeOnly === true) { + query = query.andWhere('project.active = true'); + } + if (typeof page === 'number') { query = query .limit(MAX_ITEMS_PER_PAGE) diff --git a/src/Infrastructure/Project/Table/ProjectTableFactory.ts b/src/Infrastructure/Project/Table/ProjectTableFactory.ts index ffb82a64..1468d3aa 100644 --- a/src/Infrastructure/Project/Table/ProjectTableFactory.ts +++ b/src/Infrastructure/Project/Table/ProjectTableFactory.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { ProjectView } from 'src/Application/Project/View/ProjectView'; import { RouteNameResolver } from 'src/Infrastructure/Common/ExtendedRouting/RouteNameResolver'; import { RowFactory } from 'src/Infrastructure/Tables/RowFactory'; @@ -13,6 +13,7 @@ export class ProjectTableFactory { public create(projects: ProjectView[]): Table { const columns = [ + 'crm-projects-active', 'crm-projects-name-title', 'crm-projects-customer-title', 'common-actions' @@ -21,8 +22,13 @@ export class ProjectTableFactory { const rows = projects.map(project => this.rowFactory .createBuilder() + .picto( + project.active ? 'active' : 'disabled', + project.active ? 'common-yes' : 'common-no' + ) .value(project.name) .value(project.customer.name) + // .trans(project.active ? '✅ ' : '🚫 ') .actions({ edit: { url: this.resolver.resolve('crm_projects_edit', { diff --git a/src/Infrastructure/Tables/RowFactory.ts b/src/Infrastructure/Tables/RowFactory.ts index 89bc3aa6..13781da7 100644 --- a/src/Infrastructure/Tables/RowFactory.ts +++ b/src/Infrastructure/Tables/RowFactory.ts @@ -124,6 +124,17 @@ class RowBuilder { return this; } + public picto(picto: string, message: string): RowBuilder { + this.cells.push( + new TemplateCell( + 'tables/cells/picto.njk', + { message, picto }, + this.templates + ) + ); + return this; + } + public actions(options: ActionsOptions): RowBuilder { this.cells.push(new ActionsCell(options, this.templates)); return this; diff --git a/src/assets/styles/components/icon.css b/src/assets/styles/components/icon.css index e75ecce8..1dbfd667 100644 --- a/src/assets/styles/components/icon.css +++ b/src/assets/styles/components/icon.css @@ -39,3 +39,14 @@ html[data-theme='dark'] .pc-icon--action-violet { .pc-icon--right > .pc-icon { margin-inline-start: calc(2 * var(--v)); } + +.pc-picto::before { + margin-inline-end: var(--v); +} + +.pc-picto--active::before { + content: '✅'; +} +.pc-picto--disabled::before { + content: '🚫'; +} diff --git a/src/templates/pages/projects/_form.njk b/src/templates/pages/projects/_form.njk index 2dbbd384..2702461c 100644 --- a/src/templates/pages/projects/_form.njk +++ b/src/templates/pages/projects/_form.njk @@ -17,6 +17,14 @@ +
+ +
+ + {{ buttons.save(attr={type: 'submit'}) }} {% endmacro %} diff --git a/src/templates/tables/cells/picto.njk b/src/templates/tables/cells/picto.njk new file mode 100644 index 00000000..c0f993e7 --- /dev/null +++ b/src/templates/tables/cells/picto.njk @@ -0,0 +1 @@ +{{ message|trans }} \ No newline at end of file diff --git a/src/translations/fr-FR.ftl b/src/translations/fr-FR.ftl index e47940ff..6dc5d766 100644 --- a/src/translations/fr-FR.ftl +++ b/src/translations/fr-FR.ftl @@ -94,6 +94,7 @@ crm-projects-add-title = Ajouter un projet crm-projects-edit-title = Édition du projet "{$name}" crm-projects-name-title = Nom du projet crm-projects-customer-title = Client +crm-projects-active = Actif crm-tasks-title = Missions crm-tasks-name = Nom de la mission