Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit f58b937

Browse files
vmaubertMichaelBitard
authored andcommitted
ajoute la fin de la machine
1 parent d139c9b commit f58b937

File tree

8 files changed

+333
-131
lines changed

8 files changed

+333
-131
lines changed

packages/api/src/business/rules-demarches/machine-common.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { EtapeStatutId, EtapeStatutKey, ETAPES_STATUTS, isStatut, etapeStatutIdV
33
import { EtapeTypeId, etapeTypeIdValidator, isEtapeTypeId } from 'camino-common/src/static/etapesTypes'
44
import { ADMINISTRATION_IDS } from 'camino-common/src/static/administrations'
55
import { EtapeTypeEtapeStatut } from 'camino-common/src/static/etapesTypesEtapesStatuts'
6-
import { DemarcheStatutId } from 'camino-common/src/static/demarchesStatuts'
6+
import { DemarchesStatutsIds, DemarcheStatutId } from 'camino-common/src/static/demarchesStatuts'
77
import { CaminoDate, caminoDateValidator } from 'camino-common/src/date'
88
import { Departements, toDepartementId } from 'camino-common/src/static/departement'
99
import { Regions } from 'camino-common/src/static/region'
@@ -23,6 +23,13 @@ export interface Etape {
2323
surface?: number
2424
}
2525

26+
export const globalGuards = {
27+
isVisibilitePublique: ({ context }) => context.visibilite === 'publique',
28+
isVisibiliteConfidentielle: ({ context }) => context.visibilite === 'confidentielle',
29+
isDemarcheStatutAcceptee: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.Accepte,
30+
isDemarcheStatutAccepteeEtPublie: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.AccepteEtPublie,
31+
} as const satisfies Record<string, (value: { context: CaminoCommonContext }) => boolean>
32+
2633
export interface CaminoCommonContext {
2734
demarcheStatut: DemarcheStatutId
2835
visibilite: 'confidentielle' | 'publique'

packages/api/src/business/rules-demarches/machine-helper.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AnyMachineSnapshot, createActor, EventObject, MachineSnapshot, StateMac
22
import { CaminoCommonContext, DBEtat, Etape, Intervenant, intervenants, tags } from './machine-common'
33
import { DemarchesStatutsIds, DemarcheStatutId } from 'camino-common/src/static/demarchesStatuts'
44
import { CaminoDate } from 'camino-common/src/date'
5-
import { OmitDistributive } from 'camino-common/src/typescript-tools'
5+
import { isNotNullNorUndefined, OmitDistributive } from 'camino-common/src/typescript-tools'
66

77
type CaminoState<CaminoContext extends CaminoCommonContext, CaminoEvent extends EventObject> = MachineSnapshot<CaminoContext, CaminoEvent, any, any, any, any, any, any>
88

@@ -214,28 +214,31 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
214214
return intervenants.filter(r => responsables.includes(tags.responsable[r]))
215215
}
216216

217+
public possibleNextEvents(state: CaminoState<CaminoContext, CaminoEvent>, date: CaminoDate): CaminoEvent[] {
218+
return getNextEvents(state)
219+
.filter((event: string) => this.isEvent(event))
220+
.flatMap(event => {
221+
const events = this.toPotentialCaminoXStateEvent(event, date)
222+
223+
return events.filter(event => {
224+
return state.can(event) && state.status !== 'done'
225+
})
226+
})
227+
.filter(isNotNullNorUndefined)
228+
.toSorted((a, b) => a.type.localeCompare(b.type))
229+
}
230+
217231
public possibleNextEtapes(etapes: readonly Etape[], date: CaminoDate): (OmitDistributive<Etape, 'date' | 'titreTypeId' | 'demarcheTypeId'> & { mainStep: boolean })[] {
218232
const state = this.assertGoTo(etapes)
219233

220-
if (state !== undefined) {
221-
return getNextEvents(state)
222-
.filter((event: string) => this.isEvent(event))
223-
.flatMap(event => {
224-
const events = this.toPotentialCaminoXStateEvent(event, date)
225-
226-
return events
227-
.filter(event => {
228-
return state.can(event) && state.status !== 'done'
229-
})
230-
.flatMap(event => this.caminoXStateEventToEtapes(event))
231-
})
232-
.filter(event => event !== undefined)
234+
if (isNotNullNorUndefined(state)) {
235+
return this.possibleNextEvents(state, date).flatMap(this.caminoXStateEventToEtapes).filter(isNotNullNorUndefined)
233236
}
234237

235238
return []
236239
}
237240
}
238241

239-
export function getNextEvents(snapshot: AnyMachineSnapshot): string[] {
242+
function getNextEvents(snapshot: AnyMachineSnapshot): string[] {
240243
return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])]
241244
}

packages/api/src/business/rules-demarches/machine-test-helper.ts

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { CaminoCommonContext, Etape } from './machine-common'
22
import { Actor, EventObject, createActor } from 'xstate'
3-
import { CaminoMachine, getNextEvents } from './machine-helper'
3+
import { CaminoMachine } from './machine-helper'
44
import { expect } from 'vitest'
55
import { CaminoDate, dateAddDays, toCaminoDate } from 'camino-common/src/date'
66
import { EtapeTypeEtapeStatutValidPair } from 'camino-common/src/static/etapesTypesEtapesStatuts'
7-
import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
7+
import { isNotNullNorUndefined, onlyUnique } from 'camino-common/src/typescript-tools'
8+
import { DemarchesStatuts } from 'camino-common/src/static/demarchesStatuts'
89
interface CustomMatchers<R = unknown> {
910
canOnlyTransitionTo<T extends EventObject, C extends CaminoCommonContext>(context: { machine: CaminoMachine<C, T>; date: CaminoDate }, _events: T['type'][]): R
1011
}
@@ -19,21 +20,18 @@ declare global {
1920
interface InverseAsymmetricMatchers extends CustomMatchers {}
2021
}
2122
}
23+
2224
expect.extend({
2325
canOnlyTransitionTo<T extends EventObject, C extends CaminoCommonContext>(
2426
service: Actor<CaminoMachine<C, T>['machine']>,
2527
{ machine, date }: { machine: CaminoMachine<C, T>; date: CaminoDate },
2628
events: T['type'][]
2729
) {
2830
events = events.toSorted()
29-
const passEvents: (typeof events)[number][] = getNextEvents(service.getSnapshot())
30-
.filter((event: string) => machine.isEvent(event))
31-
.filter((event: (typeof events)[number]) => {
32-
const events = machine.toPotentialCaminoXStateEvent(event, date)
33-
34-
return events.some(event => service.getSnapshot().can(event) && service.getSnapshot().status !== 'done')
35-
})
36-
.toSorted()
31+
const passEvents = machine
32+
.possibleNextEvents(service.getSnapshot(), date)
33+
.map(({ type }) => type)
34+
.filter(onlyUnique)
3735

3836
if (passEvents.length !== events.length || passEvents.some((entry, index) => entry !== events[index])) {
3937
return {
@@ -62,14 +60,10 @@ export const interpretMachine = <T extends EventObject, C extends CaminoCommonCo
6260
throw new Error(
6361
`Error: cannot execute step: '${JSON.stringify(etapeAFaire)}' after '${JSON.stringify(
6462
etapes.slice(0, i).map(etape => etape.etapeTypeId + '_' + etape.etapeStatutId)
65-
)}'. The event ${JSON.stringify(event)} should be one of '${getNextEvents(service.getSnapshot())
66-
.filter(event => machine.isEvent(event))
67-
.filter((event: EventObject['type']) => {
68-
const events = machine.toPotentialCaminoXStateEvent(event, etapeAFaire.date)
69-
70-
return events.some(event => service.getSnapshot().can(event) && service.getSnapshot().status !== 'done')
71-
})
72-
.toSorted()}'`
63+
)}'. The event ${JSON.stringify(event)} should be one of '${machine
64+
.possibleNextEvents(service.getSnapshot(), etapeAFaire.date)
65+
.map(({ type }) => type)
66+
.filter(onlyUnique)}'`
7367
)
7468
}
7569
service.send(event)
@@ -78,11 +72,55 @@ export const interpretMachine = <T extends EventObject, C extends CaminoCommonCo
7872
return service
7973
}
8074

75+
export const getEventsTree = <T extends EventObject, C extends CaminoCommonContext>(
76+
machine: CaminoMachine<C, T>,
77+
initDate: `${number}-${number}-${number}`,
78+
etapes: readonly (EtapeTypeEtapeStatutValidPair & Omit<Etape, 'date' | 'etapeTypeId' | 'etapeStatutId' | 'titreTypeId' | 'demarcheTypeId'> & { addDays?: number })[]
79+
): string[] => {
80+
const { service, dateFin } = setDateAndOrderAndInterpretMachine(machine, initDate, [])
81+
const passEvents: T['type'][] = machine
82+
.possibleNextEvents(service.getSnapshot(), dateFin)
83+
.map(({ type }) => type)
84+
.filter(onlyUnique)
85+
86+
const steps = [
87+
{
88+
type: 'RIEN',
89+
visibilite: service.getSnapshot().context.visibilite,
90+
demarcheStatut: DemarchesStatuts[service.getSnapshot().context.demarcheStatut].nom,
91+
events: passEvents,
92+
},
93+
...etapes.map((_etape, index) => {
94+
const etapesToLaunch = etapes.slice(0, index + 1)
95+
const { service, dateFin, etapes: etapesWithDates } = setDateAndOrderAndInterpretMachine(machine, initDate, etapesToLaunch)
96+
97+
const passEvents: T['type'][] = machine
98+
.possibleNextEvents(service.getSnapshot(), dateFin)
99+
.map(({ type }) => type)
100+
.filter(onlyUnique)
101+
const event = machine.eventFrom(etapesWithDates[etapesWithDates.length - 1])
102+
103+
return {
104+
type: event.type,
105+
visibilite: service.getSnapshot().context.visibilite,
106+
demarcheStatut: DemarchesStatuts[service.getSnapshot().context.demarcheStatut].nom,
107+
events: passEvents,
108+
}
109+
}),
110+
]
111+
112+
const maxPadType = Math.max(...steps.map(({ type }) => type.length))
113+
114+
return steps.map(step => {
115+
return `${step.type.padEnd(maxPadType, ' ')} (${step.visibilite.padEnd(14, ' ')}, ${step.demarcheStatut.padEnd(23, ' ')}) -> [${step.events.join(',')}]`
116+
})
117+
}
118+
81119
export const setDateAndOrderAndInterpretMachine = <T extends EventObject, C extends CaminoCommonContext>(
82120
machine: CaminoMachine<C, T>,
83121
initDate: `${number}-${number}-${number}`,
84122
etapes: readonly (EtapeTypeEtapeStatutValidPair & Omit<Etape, 'date' | 'etapeTypeId' | 'etapeStatutId' | 'titreTypeId' | 'demarcheTypeId'> & { addDays?: number })[]
85-
): { service: Actor<(typeof machine)['machine']>; dateFin: CaminoDate; etapes: Etape[] } => {
123+
): { service: Actor<(typeof machine)['machine']>; dateFin: CaminoDate; etapes: Etape[]; machine: CaminoMachine<C, T> } => {
86124
const firstDate = toCaminoDate(initDate)
87125
let index = 0
88126
const fullEtapes = etapes.map(etape => {
@@ -96,7 +134,7 @@ export const setDateAndOrderAndInterpretMachine = <T extends EventObject, C exte
96134
})
97135
const service = orderAndInterpretMachine(machine, fullEtapes)
98136

99-
return { service, dateFin: dateAddDays(firstDate, etapes.length), etapes: fullEtapes }
137+
return { service, dateFin: dateAddDays(firstDate, etapes.length), etapes: fullEtapes, machine }
100138
}
101139
export const orderAndInterpretMachine = <T extends EventObject, C extends CaminoCommonContext>(
102140
machine: CaminoMachine<C, T>,

packages/api/src/business/rules-demarches/procedure-simplifiee/ps.machine.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assign, createMachine } from 'xstate'
1+
import { assign, setup } from 'xstate'
22
import { CaminoMachine } from '../machine-helper'
33
import { CaminoCommonContext, DBEtat } from '../machine-common'
44
import { EtapesTypesEtapesStatuts as ETES } from 'camino-common/src/static/etapesTypesEtapesStatuts'
@@ -93,7 +93,7 @@ export class ProcedureSimplifieeMachine extends CaminoMachine<ProcedureSimplifie
9393
{ type: event, status: ETAPES_STATUTS.TERMINE },
9494
]
9595
default:
96-
return [{ type: event }]
96+
return super.toPotentialCaminoXStateEvent(event, date)
9797
}
9898
}
9999
}
@@ -105,8 +105,14 @@ interface ProcedureSimplifieeContext extends CaminoCommonContext {
105105
const defaultDemarcheStatut = DemarchesStatutsIds.EnConstruction
106106
const procedureHistoriqueDateMax = toCaminoDate('2024-07-01')
107107
const procedureIncompleteDateMax = toCaminoDate('2000-01-01')
108-
const procedureSimplifieeMachine = createMachine({
108+
const procedureSimplifieeMachine = setup({
109109
types: {} as { context: ProcedureSimplifieeContext; events: ProcedureSimplifieeXStateEvent },
110+
guards: {
111+
isProcedureHistorique: ({ context, event }) => 'date' in event && isBefore(event.date, procedureHistoriqueDateMax) && context.demarcheStatut === defaultDemarcheStatut,
112+
isProcedureIncomplete: ({ context, event }) => 'date' in event && isBefore(event.date, procedureIncompleteDateMax) && context.demarcheStatut === defaultDemarcheStatut,
113+
isDemarcheEnInstruction: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.EnInstruction,
114+
},
115+
}).createMachine({
110116
id: 'ProcedureSimplifiee',
111117
initial: 'demandeAFaire',
112118
context: {
@@ -117,73 +123,73 @@ const procedureSimplifieeMachine = createMachine({
117123
},
118124
on: {
119125
RENDRE_DECISION_ADMINISTRATION_ACCEPTEE: {
120-
guard: ({ context, event }) => isBefore(event.date, procedureHistoriqueDateMax) && context.demarcheStatut === defaultDemarcheStatut,
126+
guard: 'isProcedureHistorique',
121127
target: '.publicationAuRecueilDesActesAdministratifsOupublicationAuJORFAFaire',
122128
actions: assign({
123129
visibilite: 'publique',
124130
demarcheStatut: DemarchesStatutsIds.Accepte,
125131
}),
126132
},
127133
PUBLIER_DECISION_ACCEPTEE_AU_JORF: {
128-
guard: ({ context, event }) => isBefore(event.date, procedureIncompleteDateMax) && context.demarcheStatut === defaultDemarcheStatut,
134+
guard: 'isProcedureIncomplete',
129135
target: '.abrogationAFaire',
130136
actions: assign({
131137
visibilite: 'publique',
132138
demarcheStatut: DemarchesStatutsIds.AccepteEtPublie,
133139
}),
134140
},
135141
PUBLIER_DECISION_AU_RECUEIL_DES_ACTES_ADMINISTRATIFS: {
136-
guard: ({ context, event }) => isBefore(event.date, procedureIncompleteDateMax) && context.demarcheStatut === defaultDemarcheStatut,
142+
guard: 'isProcedureIncomplete',
137143
target: '.abrogationAFaire',
138144
actions: assign({
139145
visibilite: 'publique',
140146
demarcheStatut: DemarchesStatutsIds.AccepteEtPublie,
141147
}),
142148
},
143149
SAISIR_INFORMATION_HISTORIQUE_INCOMPLETE: {
144-
guard: ({ context, event }) => isBefore(event.date, procedureIncompleteDateMax) && context.demarcheStatut === defaultDemarcheStatut,
150+
guard: 'isProcedureIncomplete',
145151
target: '.finDeMachine',
146152
actions: assign({
147153
visibilite: 'confidentielle',
148154
demarcheStatut: DemarchesStatutsIds.Accepte,
149155
}),
150156
},
151157
RENDRE_DECISION_ADMINISTRATION_REJETEE: {
152-
guard: ({ context, event }) => isBefore(event.date, procedureHistoriqueDateMax) && context.demarcheStatut === defaultDemarcheStatut,
158+
guard: 'isProcedureHistorique',
153159
target: '.publicationAuJorfApresRejetAFaire',
154160
actions: assign({
155161
visibilite: 'confidentielle',
156162
demarcheStatut: DemarchesStatutsIds.Rejete,
157163
}),
158164
},
159165
RENDRE_DECISION_ADMINISTRATION_REJETEE_DECISION_IMPLICITE: {
160-
guard: ({ context, event }) => isBefore(event.date, procedureHistoriqueDateMax) && context.demarcheStatut === defaultDemarcheStatut,
166+
guard: 'isProcedureHistorique',
161167
target: '.finDeMachine',
162168
actions: assign({
163169
visibilite: 'publique',
164170
demarcheStatut: DemarchesStatutsIds.Rejete,
165171
}),
166172
},
167173
CLASSER_SANS_SUITE: {
168-
guard: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.EnInstruction,
174+
guard: 'isDemarcheEnInstruction',
169175
target: '.finDeMachine',
170176
actions: assign({
171177
demarcheStatut: DemarchesStatutsIds.ClasseSansSuite,
172178
}),
173179
},
174180
DESISTER_PAR_LE_DEMANDEUR: {
175-
guard: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.EnInstruction,
181+
guard: 'isDemarcheEnInstruction',
176182
target: '.finDeMachine',
177183
actions: assign({
178184
demarcheStatut: DemarchesStatutsIds.Desiste,
179185
}),
180186
},
181187
DEMANDER_INFORMATION: {
182-
guard: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.EnInstruction,
188+
guard: 'isDemarcheEnInstruction',
183189
actions: assign({}),
184190
},
185191
RECEVOIR_INFORMATION: {
186-
guard: ({ context }) => context.demarcheStatut === DemarchesStatutsIds.EnInstruction,
192+
guard: 'isDemarcheEnInstruction',
187193
actions: assign({}),
188194
},
189195
},

0 commit comments

Comments
 (0)