Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
82ec0cf
feat: anihilate landbot
fuuuzz Jan 28, 2026
0fae33a
feat: clean eligibility thresholds component
fuuuzz Jan 28, 2026
3009914
feat: new simulator cta
fuuuzz Jan 28, 2026
37ce2c5
rebase
fuuuzz Jan 28, 2026
c8afac7
feat: commonize simulator components
fuuuzz Jan 28, 2026
32641ba
feat initial steps on new simulator
fuuuzz Jan 29, 2026
eb8d6dd
feat: starting flow
fuuuzz Feb 2, 2026
00aabc3
feat: end to end empty steps
fuuuzz Feb 3, 2026
8ce6ada
feat: add rfr and property situation step
fuuuzz Feb 3, 2026
9c68f22
specs: refacto specs
fuuuzz Feb 4, 2026
ddbf01c
clean: refacto
fuuuzz Feb 4, 2026
07dad93
specs: fixing e2e frotnend
fuuuzz Feb 5, 2026
2ac0b11
specs: fixing e2e frotnend
fuuuzz Feb 5, 2026
46cad00
specs: fixing e2e frontend
fuuuzz Feb 9, 2026
8ef095e
feat: calculate categories and eligible zones
fuuuzz Feb 9, 2026
1ed0359
feat: eligibility result screen skeleton
fuuuzz Feb 10, 2026
d0cf8e2
feat: add ineligible result
fuuuzz Feb 10, 2026
b7ef077
specs: refacto simulator e2e specs
fuuuzz Feb 10, 2026
1f0904f
feat: add user details screen
fuuuzz Feb 11, 2026
8c14423
feat: complete form for user detail, and exit simulator case
fuuuzz Feb 11, 2026
f2c6797
feat: housing informations screen
fuuuzz Feb 11, 2026
a1516a0
feat: financial informations screen
fuuuzz Feb 11, 2026
2f02673
feat: user search screen
fuuuzz Feb 12, 2026
53e2a9a
feat: complete synthesis screen
fuuuzz Feb 16, 2026
ba7e682
feat: multiple adress selection
fuuuzz Feb 24, 2026
cf7baf3
feat: reset consent
fuuuzz Feb 24, 2026
d044176
feat: create eligibility-simulations and locations interface
fuuuzz Feb 24, 2026
8d695cf
spec: entities specs
fuuuzz Feb 24, 2026
9b121f1
feat: eligibility-simulation and location repositories
fuuuzz Feb 24, 2026
e44ab39
feat: save location usecase
fuuuzz Feb 24, 2026
2fef443
feat: create eligibility simulation usecase
fuuuzz Feb 25, 2026
c0dabab
feat: create eligibility simulation controller
fuuuzz Feb 25, 2026
03cf8c7
feat: update eligibility simulation
fuuuzz Feb 25, 2026
0b22969
feat: update eligibility simulation
fuuuzz Feb 25, 2026
0c76880
fix: harmonize migrations
fuuuzz Feb 26, 2026
9006ac5
feat: plug front to api
fuuuzz Feb 26, 2026
7234c37
specs: fix specs to adapt api calls
fuuuzz Feb 27, 2026
af9c7b3
specs: fix
fuuuzz Feb 27, 2026
1b84fb2
feat: mailer
fuuuzz Mar 2, 2026
02d3d98
feat: insert rows in gsheet
fuuuzz Mar 3, 2026
a96b37a
feat: add a flag to disable/enable google sheet insertion
fuuuzz Mar 3, 2026
e25812c
feat: add a flag to disable/enable google sheet insertion
fuuuzz Mar 3, 2026
688d7f0
feat: small tweaks
fuuuzz Mar 3, 2026
a50aa0a
specs: fix
fuuuzz Mar 3, 2026
0b6d935
feat: import existing landbot customers into eligibility simulations
fuuuzz Mar 4, 2026
81bacf1
specs: fix
fuuuzz Mar 4, 2026
21ffbfe
cleanup
fuuuzz Mar 4, 2026
e303d3c
fix
fuuuzz Mar 4, 2026
873762e
feat: last tweaks
fuuuzz Mar 6, 2026
8fb631e
Typo
fuuuzz Mar 6, 2026
6cd89fa
chore: remove unnecessary step in Procfile
fuuuzz Mar 9, 2026
787bd3c
chore: using npx to run migration
fuuuzz Mar 9, 2026
92e5db0
chore: truing to fix deployment
fuuuzz Mar 9, 2026
9266879
chore: truing to fix deployment
fuuuzz Mar 9, 2026
2c2c80f
feat: remove landbot customers import cron task
fuuuzz Mar 9, 2026
3f6bb0f
chore: undo procfile changes
fuuuzz Mar 9, 2026
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
1 change: 0 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ jobs:
runs-on: ubuntu-latest

env:
LANDBOT_CONFIG_URL: ${{ secrets.LANDBOT_CONFIG_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
API_URL: ${{ secrets.API_URL }}
API_KEY: ${{ secrets.API_KEY }}
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ jobs:
runs-on: ubuntu-latest

env:
LANDBOT_CONFIG_URL: ${{ secrets.LANDBOT_CONFIG_URL }}
PUBLIC_NODE_ENV: ${{ secrets.PUBLIC_NODE_ENV }}
API_URL: ${{ secrets.API_URL }}
API_KEY: ${{ secrets.API_KEY }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ node_modules
apps/backend/coverage
apps/backend/.nyc_output
apps/backend/src/assets/styles/output.css
apps/backend/*-*-*-*.json

# Logs
logs
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ LANDBOT_API_URL= # Fill with correct value
LANDBOT_API_TOKEN= # Fill with correct value
DATAGOUV_API_KEY= # Fill with correct value
DATAGOUV_DATASET_ID= # Fill with correct value
CRON_TASKS= # Fill with correct value
CRON_TASKS= # Fill with correct value
BREVO_API_KEY= # Fill with correct value
GOOGLE_APPLICATION_CREDENTIALS= # Fill with correct value
GOOGLE_SHEETS_SPREADSHEET_ID= # Fill with correct value
DISABLE_GOOGLE_SHEETS_INSERT= # Fill with correct value
2 changes: 2 additions & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"seed:municipality": "ts-node -r tsconfig-paths/register src/infrastructure/persistence/seeds/municipality/run.ts",
"seed:landbot-customer": "ts-node -r tsconfig-paths/register src/infrastructure/persistence/seeds/landbot-customer/run.ts",
"seed:regions-code": "ts-node -r tsconfig-paths/register src/infrastructure/persistence/seeds/regions-code/run.ts",
"seed:eligibility-simulations": "ts-node -r tsconfig-paths/register src/infrastructure/persistence/seeds/eligibility-simulations/run.ts",
"seed:test-data": "NODE_ENV=test ts-node -r tsconfig-paths/register src/infrastructure/persistence/seeds/test-data/run.ts",
"generate-address-datagouv-types": "openapi-typescript https://data.geopf.fr/geocodage/openapi.yaml --output src/infrastructure/common/types/generated-address-datagouv-types.ts"
},
Expand All @@ -56,6 +57,7 @@
"connect-flash": "^0.1.1",
"connect-typeorm": "^2.0.0",
"express-session": "^1.18.1",
"googleapis": "^144.0.0",
"hbs": "^4.2.0",
"method-override": "^3.0.0",
"nestjs-pino": "^4.4.0",
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { BrsDiffusionWebsiteModule } from './infrastructure/brs-diffusion-websit
import { APP_FILTER } from '@nestjs/core';
import { MunicipalityModule } from './infrastructure/municipality/municipality.module';
import { AcquisitionSimulationModule } from './infrastructure/acquisition-simulation/acquisition-simulation.module';
import { EligibilitySimulationModule } from './infrastructure/eligibility-simulation/eligibility-simulation.module';
import { LandbotCustomerModule } from './infrastructure/landbot-customer/landbot-customer.module';
import { LocationModule } from './infrastructure/location/location.module';
import { ScheduleModule } from '@nestjs/schedule';
import { DatagouvModule } from './infrastructure/datagouv/datagouv.module';

Expand Down Expand Up @@ -63,7 +65,9 @@ import { DatagouvModule } from './infrastructure/datagouv/datagouv.module';
BrsDiffusionWebsiteModule,
MunicipalityModule,
AcquisitionSimulationModule,
EligibilitySimulationModule,
LandbotCustomerModule,
LocationModule,
DatagouvModule,
AdminHomeModule,
NotFoundModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface CreateEligibilitySimulationParams {
householdSize: number;
hasDisability?: boolean;
dependantsAmount?: number;
birthday?: Date;
coBuyerBirthday?: Date;
isFromLandbot?: boolean;
landbotDate?: Date;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Inject } from '@nestjs/common';
import { EligibilitySimulationRepositoryInterface } from 'src/domain/eligibility-simulation/eligibility-simulation.repository.interface';
import { EligibilitySimulationView } from '../views/eligibility-simulation.view';
import { CreateEligibilitySimulationParams } from './create.params';
import { EligibilitySimulationEntity } from 'src/infrastructure/eligibility-simulation/eligibility-simulation.entity';

export class CreateEligibilitySimulationUsecase {
constructor(
@Inject('EligibilitySimulationRepositoryInterface')
private readonly eligibilitySimulationRepository: EligibilitySimulationRepositoryInterface,
) {}

public async execute(
params: CreateEligibilitySimulationParams,
): Promise<EligibilitySimulationView> {
const {
householdSize,
hasDisability,
dependantsAmount,
birthday,
coBuyerBirthday,
isFromLandbot = false,
landbotDate,
} = params;

const eligibilitySimulationEntity = new EligibilitySimulationEntity();
eligibilitySimulationEntity.householdSize = householdSize;
eligibilitySimulationEntity.hasDisability = hasDisability;
eligibilitySimulationEntity.dependantsAmount = dependantsAmount;
eligibilitySimulationEntity.birthday = birthday;
eligibilitySimulationEntity.coBuyerBirthday = coBuyerBirthday;
eligibilitySimulationEntity.isFromLandbot = isFromLandbot;

if (landbotDate) {
eligibilitySimulationEntity.landbotDate = landbotDate;
}

const eligibilitySimulation =
await this.eligibilitySimulationRepository.save(
eligibilitySimulationEntity,
);

return new EligibilitySimulationView(eligibilitySimulation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { SaveLocationParams } from 'src/application/location/usecases/save.params';
import {
ContractType,
DeclarationType,
EligibilityCategory,
EmploymentStatus,
HousingType,
PositionType,
PropertySituation,
} from 'src/domain/eligibility-simulation/eligibility-simulation.interface';

export interface UpdateEligibilitySimulationParams {
id: string;
householdSize?: number;
hasDisability?: boolean;
dependantsAmount?: number;
birthday?: Date;
coBuyerBirthday?: Date;
propertySituation?: PropertySituation;
taxableIncome?: number;
declarationType?: DeclarationType;
firstCoBuyerTaxableIncome?: number;
secondCoBuyerTaxableIncome?: number;
eligibility?: EligibilityCategory;
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
hasRefusedConnection?: boolean;
housingType?: HousingType;
contribution?: number;
resources?: number;
hadBrsKnowledge?: boolean;
employmentStatus?: EmploymentStatus;
laposteEmployer?: string;
canSendInformationsToLaposte?: boolean;
positionType?: PositionType;
positionStage?: boolean;
hasCompanyMoreThan10Employees?: boolean;
hasCompanyMoreThan50Employees?: boolean;
allowFinancingAndOwnershipAdvices?: boolean;
positionContractType?: ContractType;
locations?: SaveLocationParams[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { Inject, NotFoundException } from '@nestjs/common';
import { EligibilitySimulationRepositoryInterface } from 'src/domain/eligibility-simulation/eligibility-simulation.repository.interface';
import type { GoogleSheetsServiceInterface } from 'src/domain/google-sheets/google-sheets.service.interface';
import { UpdateEligibilitySimulationParams } from './update.params';
import { EligibilitySimulationView } from '../views/eligibility-simulation.view';
import { LocationEntity } from 'src/infrastructure/location/location.entity';
import { LocationView } from 'src/application/location/views/location.view';
import { SaveLocationUsecase } from 'src/application/location/usecases/save.usecase';
import { DeleteLocationUsecase } from 'src/application/location/usecases/delete.usecase';
import { MailerService } from 'src/infrastructure/mailer/mailer.service';

export class UpdateEligibilitySimulationUsecase {
constructor(
@Inject('EligibilitySimulationRepositoryInterface')
private readonly eligibilitySimulationRepository: EligibilitySimulationRepositoryInterface,
private readonly saveLocationUsecase: SaveLocationUsecase,
private readonly deleteLocationUsecase: DeleteLocationUsecase,
@Inject('MailerServiceInterface')
private readonly mailerService: MailerService,
@Inject('GoogleSheetsServiceInterface')
private readonly googleSheets: GoogleSheetsServiceInterface,
) {}

public async execute(
params: UpdateEligibilitySimulationParams,
): Promise<EligibilitySimulationView> {
let eligibilitySimulation =
await this.eligibilitySimulationRepository.findById(params.id);

if (!eligibilitySimulation) {
throw new NotFoundException();
}

if (params.locations) {
for (const location of eligibilitySimulation.locations) {
await this.deleteLocationUsecase.execute({
id: location.id,
});
}

for (const location of params.locations) {
const locationEntity = new LocationEntity();
locationEntity.latitude = location.latitude;
locationEntity.longitude = location.longitude;
locationEntity.city = location.city;
locationEntity.citycode = location.citycode;
locationEntity.label = location.label;
locationEntity.municipality = location.municipality;
locationEntity.postalCode = location.postalCode;

await this.saveLocationUsecase.execute({
...location,
eligibilitySimulationId: eligibilitySimulation?.id,
});
}

eligibilitySimulation =
await this.eligibilitySimulationRepository.findById(params.id);

if (!eligibilitySimulation) {
throw new NotFoundException();
}
}

const hasEmailChanged =
params.email && params.email !== eligibilitySimulation.email;
const shouldInsertInGoogleSheet = params.contribution && params.resources;

eligibilitySimulation.householdSize =
params.householdSize || eligibilitySimulation.householdSize;

eligibilitySimulation.hasDisability =
typeof params.hasDisability === 'boolean'
? params.hasDisability
: eligibilitySimulation.hasDisability;

eligibilitySimulation.dependantsAmount =
typeof params.dependantsAmount === 'number'
? params.dependantsAmount
: eligibilitySimulation.dependantsAmount;
eligibilitySimulation.birthday =
params.birthday || eligibilitySimulation.birthday;
eligibilitySimulation.coBuyerBirthday =
params.coBuyerBirthday || eligibilitySimulation.coBuyerBirthday;
eligibilitySimulation.propertySituation =
params.propertySituation || eligibilitySimulation.propertySituation;
eligibilitySimulation.taxableIncome =
params.taxableIncome || eligibilitySimulation.taxableIncome;
eligibilitySimulation.declarationType =
params.declarationType || eligibilitySimulation.declarationType;
eligibilitySimulation.firstCoBuyerTaxableIncome =
params.firstCoBuyerTaxableIncome ||
eligibilitySimulation.firstCoBuyerTaxableIncome;
eligibilitySimulation.secondCoBuyerTaxableIncome =
params.secondCoBuyerTaxableIncome ||
eligibilitySimulation.secondCoBuyerTaxableIncome;
eligibilitySimulation.eligibility =
params.eligibility || eligibilitySimulation.eligibility;
eligibilitySimulation.firstName =
params.firstName || eligibilitySimulation.firstName;
eligibilitySimulation.lastName =
params.lastName || eligibilitySimulation.lastName;
eligibilitySimulation.email = params.email || eligibilitySimulation.email;
eligibilitySimulation.phone = params.phone || eligibilitySimulation.phone;
eligibilitySimulation.hasRefusedConnection =
typeof params.hasRefusedConnection === 'boolean'
? params.hasRefusedConnection
: eligibilitySimulation.hasRefusedConnection;
eligibilitySimulation.housingType =
params.housingType || eligibilitySimulation.housingType;
eligibilitySimulation.contribution =
params.contribution || eligibilitySimulation.contribution;
eligibilitySimulation.resources =
params.resources || eligibilitySimulation.resources;
eligibilitySimulation.hadBrsKnowledge =
typeof params.hadBrsKnowledge === 'boolean'
? params.hadBrsKnowledge
: eligibilitySimulation.hadBrsKnowledge;
eligibilitySimulation.employmentStatus =
params.employmentStatus || eligibilitySimulation.employmentStatus;
eligibilitySimulation.laposteEmployer =
params.laposteEmployer || eligibilitySimulation.laposteEmployer;
eligibilitySimulation.canSendInformationsToLaposte =
typeof params.canSendInformationsToLaposte === 'boolean'
? params.canSendInformationsToLaposte
: eligibilitySimulation.canSendInformationsToLaposte;
eligibilitySimulation.positionType =
params.positionType || eligibilitySimulation.positionType;
eligibilitySimulation.positionStage =
params.positionStage || eligibilitySimulation.positionStage;
eligibilitySimulation.hasCompanyMoreThan10Employees =
typeof params.hasCompanyMoreThan10Employees === 'boolean'
? params.hasCompanyMoreThan10Employees
: eligibilitySimulation.hasCompanyMoreThan10Employees;
eligibilitySimulation.hasCompanyMoreThan50Employees =
typeof params.hasCompanyMoreThan50Employees === 'boolean'
? params.hasCompanyMoreThan50Employees
: eligibilitySimulation.hasCompanyMoreThan50Employees;
eligibilitySimulation.allowFinancingAndOwnershipAdvices =
typeof params.allowFinancingAndOwnershipAdvices === 'boolean'
? params.allowFinancingAndOwnershipAdvices
: eligibilitySimulation.allowFinancingAndOwnershipAdvices;
eligibilitySimulation.positionContractType =
params.positionContractType || eligibilitySimulation.positionContractType;

eligibilitySimulation = await this.eligibilitySimulationRepository.save(
eligibilitySimulation,
);

if (hasEmailChanged) {
await this.mailerService.sendEmail(
[
{
email: params.email as string,
name: `${params.firstName} ${params.lastName}`,
params: {
firstName: params.firstName,
},
},
],
'Votre projet en BRS : toutes les étapes clés',
6,
);
}

if (shouldInsertInGoogleSheet) {
try {
const cells = eligibilitySimulation.locations.map((location) => [
'N/A',
'N/A',
new Date().toISOString(),
`${eligibilitySimulation.firstName} ${eligibilitySimulation.lastName}`,
eligibilitySimulation.email,
eligibilitySimulation.phone,
location.departement.code,
eligibilitySimulation.contribution,
eligibilitySimulation.householdSize,
eligibilitySimulation.hasDisability,
eligibilitySimulation.taxableIncome,
location.city,
eligibilitySimulation.propertySituation,
eligibilitySimulation.housingType,
eligibilitySimulation.resources,
]);

await this.googleSheets.appendRows(
process.env.GOOGLE_SHEETS_SPREADSHEET_ID as string,
{ range: 'Sheet1' },
cells,
);
} catch (e) {
console.log(e);
}
}

return new EligibilitySimulationView({
...eligibilitySimulation,
locations: eligibilitySimulation.locations?.map((location) => {
return new LocationView(
location.id,
location.latitude,
location.longitude,
location.city,
location.citycode,
location.label,
location.municipality,
location.postalCode,
location.departement,
);
}),
});
}
}
Loading