From c77b1deff29dc8b4213bb5924777b7118f13fa0a Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Wed, 23 Dec 2020 17:11:56 +0100
Subject: [PATCH 4/4] maelis: decompose units related to moments of the day
 (#48480)

---
 passerelle/apps/maelis/models.py              |   7 +-
 passerelle/apps/maelis/utils.py               |  57 +++
 .../child_planning_after_decomposition.json   | 383 ++++++++++++++++++
 tests/test_maelis.py                          |  47 ++-
 4 files changed, 485 insertions(+), 9 deletions(-)
 create mode 100644 tests/data/maelis/child_planning_after_decomposition.json

diff --git a/passerelle/apps/maelis/models.py b/passerelle/apps/maelis/models.py
index 919dfea0..e01b4f35 100644
--- a/passerelle/apps/maelis/models.py
+++ b/passerelle/apps/maelis/models.py
@@ -404,18 +404,19 @@ class Maelis(BaseResource):
         display_order=3,
         description=_('Read child planning'),
         name='child-planning',
         parameters={
             'NameID': {'description': _('Publik ID')},
             'childID': {'description': _('Child ID')},
             'start_date': {'description': _('Start date (YYYY-MM-DD format)')},
             'end_date': {'description': _('End date (YYYY-MM-DD format)')},
+            'legacy': {'description': _('Decompose events releted to parts of the day if set')},
     })
-    def child_planning(self, request, NameID, childID, start_date=None, end_date=None):
+    def child_planning(self, request, NameID, childID, start_date=None, end_date=None, legacy=None):
         """ Return an events list sorted by id """
         link = self.get_link(NameID)
         family_data = self.get_family_data(link.family_id)
         if childID not in [c['id'] for c in family_data['childInfoList']]:
             raise APIError('Child not found', err_code='not-found')
         if start_date and end_date:
             start = utils.get_datetime(start_date)
             end = utils.get_datetime(end_date)
@@ -436,16 +437,20 @@ class Maelis(BaseResource):
             r = self.call('ActivityService?wsdl', 'readChildMonthPlanning',
                           year=date.year,
                           numMonth=date.month,
                           numPerson=childID)
             planning = serialize_object(r['calendList'])
             for schedule in planning:
                 utils.book_event(events, schedule, start, end)
 
+        if not legacy:
+            events = {x['id']: x  # dictionary is used de remove dupplicated events
+                      for e in events.values()
+                      for x in utils.decompose_event(e)}
         return {'data': [s[1] for s in sorted(events.items())]}
 
 
 class Link(models.Model):
     resource = models.ForeignKey(Maelis, on_delete=models.CASCADE)
     name_id = models.CharField(blank=False, max_length=256)
     family_id = models.CharField(blank=False, max_length=128)
     created = models.DateTimeField(auto_now_add=True)
diff --git a/passerelle/apps/maelis/utils.py b/passerelle/apps/maelis/utils.py
index 1406c90b..0cb95374 100644
--- a/passerelle/apps/maelis/utils.py
+++ b/passerelle/apps/maelis/utils.py
@@ -12,28 +12,63 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
 #
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import unicode_literals
 
+from copy import copy
 from datetime import datetime
 from dateutil.relativedelta import relativedelta
 
 from django.utils import timezone
 from django.utils.dateparse import parse_date
 
 from passerelle.utils.jsonresponse import APIError
 
 DATE_FORMAT = '%Y-%m-%d'
 TIME_FORMAT = '%H:%M:%S'
 DATETIME_FORMAT = DATE_FORMAT + ' ' + TIME_FORMAT
 
+COMPONENTS = {
+    'PART01': {
+        'text': 'Matinée',
+        'time': '08:00:00',
+    },
+    'PART02': {
+        'text': 'Repas',
+        'time': '12:00:00',
+    },
+    'PART03': {
+        'text': 'Après-midi',
+        'time': '14:00:00',
+    }
+}
+
+COMPOSED_UNITS = {
+    'A10003132034': {  # JOURNEE
+        'virtual_unit': 'EO0001',
+        'components': ['PART01', 'PART02', 'PART03'],
+    },
+    'A10003132036': {  # MATIN
+        'virtual_unit': 'EO0001',
+        'components': ['PART01'],
+    },
+    'A10003132038': {  # MATIN ET REPAS
+        'virtual_unit': 'EO0001',
+        'components': ['PART01', 'PART02'],
+    },
+    'A10003132040': {  # APRES MIDI
+        'virtual_unit': 'EO0001',
+        'components': ['PART03'],
+    },
+}
+
 
 def normalize_invoice(invoice):
     data = {
         'id': '%s-%s' % (invoice.numFamily, invoice.numInvoice),
         'display_id': str(invoice.numInvoice),
         'label': invoice.TTFInfo.libelle,
         'created': invoice.dateInvoice.strftime(DATETIME_FORMAT),
         'amount': invoice.amountInvoice - invoice.amountPaid,
@@ -142,8 +177,30 @@ def book_event(events, schedule, start_date, end_date):
         # database may be corrupted by using updateScheduleCalendars
         try:
             event = events[event_id]
         except KeyError:
             raise APIError('The planning returns an unknow day on activities: %s'
                            % day['datePlanning'])
 
         event['user_booking_status'] = 'booked'
+
+
+def decompose_event(event):
+    """ Break down 'JOURNEE', 'MATIN', 'MATIN ET REPAS' and APRES MIDI' units
+    into 'Matin', 'Repas' and 'Après-midi' virtual units.  """
+    if event['slot_id'] not in COMPOSED_UNITS.keys():
+        yield event
+        return
+
+    date_time = datetime.strptime(event['datetime'], DATETIME_FORMAT)
+    date_string = date_time.strftime(DATE_FORMAT)
+    composition = COMPOSED_UNITS[event['slot_id']]
+
+    for component_id in composition['components']:
+        component = COMPONENTS[component_id]
+        new_event = copy(event)
+        new_event['datetime'] = '%s %s' % (date_string, component['time'])
+        new_event['slot_id'] = "%s%s" % (composition['virtual_unit'], component_id)
+        new_event['id'] = '%s-%s-%s' % (
+            date_string, event['category_id'], new_event['slot_id'])
+        new_event['text'] = component['text']
+        yield new_event
diff --git a/tests/data/maelis/child_planning_after_decomposition.json b/tests/data/maelis/child_planning_after_decomposition.json
new file mode 100644
index 00000000..536cf9f5
--- /dev/null
+++ b/tests/data/maelis/child_planning_after_decomposition.json
@@ -0,0 +1,383 @@
+{
+   "data" : [
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10000003840-A10000003844",
+         "slot_id" : "A10000003844",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10000003840-A10000003845",
+         "slot_id" : "A10000003845",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10000006100-A10000006104",
+         "slot_id" : "A10000006104",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10000006100-A10000006105",
+         "slot_id" : "A10000006105",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "1 2020-2021 GARDERIE MATIN",
+         "category_id" : "A10003121692",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10003121692-A10003121694",
+         "slot_id" : "A10003121694",
+         "text" : "1 2020-2021 GARDERIE MATIN",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "category_id" : "A10003123490",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10003123490-A10003123492",
+         "slot_id" : "A10003123492",
+         "text" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "3 2020-2021 GARDERIE SOIR",
+         "category_id" : "A10003123507",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10003123507-A10003123509",
+         "slot_id" : "A10003123509",
+         "text" : "3 2020-2021 GARDERIE SOIR",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "COURS DE DANSE",
+         "category_id" : "A10003132090",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10003132090-A10003132091",
+         "slot_id" : "A10003132091",
+         "text" : "COURS DE DANSE",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "ATELIER D'EXPRESSION THEATRALE",
+         "category_id" : "A10003132293",
+         "datetime" : "2020-12-14 00:00:00",
+         "id" : "2020-12-14-A10003132293-A10003132295",
+         "slot_id" : "A10003132295",
+         "text" : "ATELIER D'EXPRESSION THEATRALE",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10000003840-A10000003844",
+         "slot_id" : "A10000003844",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10000003840-A10000003845",
+         "slot_id" : "A10000003845",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10000006100-A10000006104",
+         "slot_id" : "A10000006104",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10000006100-A10000006105",
+         "slot_id" : "A10000006105",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "1 2020-2021 GARDERIE MATIN",
+         "category_id" : "A10003121692",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10003121692-A10003121694",
+         "slot_id" : "A10003121694",
+         "text" : "1 2020-2021 GARDERIE MATIN",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "category_id" : "A10003123490",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10003123490-A10003123492",
+         "slot_id" : "A10003123492",
+         "text" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "3 2020-2021 GARDERIE SOIR",
+         "category_id" : "A10003123507",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10003123507-A10003123509",
+         "slot_id" : "A10003123509",
+         "text" : "3 2020-2021 GARDERIE SOIR",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "COURS DE DANSE",
+         "category_id" : "A10003132090",
+         "datetime" : "2020-12-15 00:00:00",
+         "id" : "2020-12-15-A10003132090-A10003132091",
+         "slot_id" : "A10003132091",
+         "text" : "COURS DE DANSE",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10000003840-A10000003844",
+         "slot_id" : "A10000003844",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10000003840-A10000003845",
+         "slot_id" : "A10000003845",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10000006100-A10000006104",
+         "slot_id" : "A10000006104",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10000006100-A10000006105",
+         "slot_id" : "A10000006105",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "2020-2021 CENTRE DE LOISIRS MERCREDI",
+         "category_id" : "A10003132030",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10003132030-A10003132032",
+         "slot_id" : "A10003132032",
+         "text" : "2020-2021 CENTRE DE LOISIRS MERCREDI",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "2020-2021 CENTRE DE LOISIRS MERCREDI",
+         "category_id" : "A10003132030",
+         "datetime" : "2020-12-16 08:00:00",
+         "id" : "2020-12-16-A10003132030-EO0001PART01",
+         "slot_id" : "EO0001PART01",
+         "text" : "Matinée",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "2020-2021 CENTRE DE LOISIRS MERCREDI",
+         "category_id" : "A10003132030",
+         "datetime" : "2020-12-16 12:00:00",
+         "id" : "2020-12-16-A10003132030-EO0001PART02",
+         "slot_id" : "EO0001PART02",
+         "text" : "Repas",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "2020-2021 CENTRE DE LOISIRS MERCREDI",
+         "category_id" : "A10003132030",
+         "datetime" : "2020-12-16 14:00:00",
+         "id" : "2020-12-16-A10003132030-EO0001PART03",
+         "slot_id" : "EO0001PART03",
+         "text" : "Après-midi",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "COURS DE DANSE",
+         "category_id" : "A10003132090",
+         "datetime" : "2020-12-16 00:00:00",
+         "id" : "2020-12-16-A10003132090-A10003132091",
+         "slot_id" : "A10003132091",
+         "text" : "COURS DE DANSE",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10000003840-A10000003844",
+         "slot_id" : "A10000003844",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10000003840-A10000003845",
+         "slot_id" : "A10000003845",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10000006100-A10000006104",
+         "slot_id" : "A10000006104",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10000006100-A10000006105",
+         "slot_id" : "A10000006105",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "1 2020-2021 GARDERIE MATIN",
+         "category_id" : "A10003121692",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10003121692-A10003121694",
+         "slot_id" : "A10003121694",
+         "text" : "1 2020-2021 GARDERIE MATIN",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "category_id" : "A10003123490",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10003123490-A10003123492",
+         "slot_id" : "A10003123492",
+         "text" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "3 2020-2021 GARDERIE SOIR",
+         "category_id" : "A10003123507",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10003123507-A10003123509",
+         "slot_id" : "A10003123509",
+         "text" : "3 2020-2021 GARDERIE SOIR",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "COURS DE DANSE",
+         "category_id" : "A10003132090",
+         "datetime" : "2020-12-17 00:00:00",
+         "id" : "2020-12-17-A10003132090-A10003132091",
+         "slot_id" : "A10003132091",
+         "text" : "COURS DE DANSE",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10000003840-A10000003844",
+         "slot_id" : "A10000003844",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "MULTI ACCUEIL LES OLIVIERS",
+         "category_id" : "A10000003840",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10000003840-A10000003845",
+         "slot_id" : "A10000003845",
+         "text" : "MULTI ACCUEIL LES OLIVIERS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10000006100-A10000006104",
+         "slot_id" : "A10000006104",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Réguliers",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "HALTE GARDERIE LES MAGNOLIAS",
+         "category_id" : "A10000006100",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10000006100-A10000006105",
+         "slot_id" : "A10000006105",
+         "text" : "HALTE GARDERIE LES MAGNOLIAS - Occasionnels",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "1 2020-2021 GARDERIE MATIN",
+         "category_id" : "A10003121692",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10003121692-A10003121694",
+         "slot_id" : "A10003121694",
+         "text" : "1 2020-2021 GARDERIE MATIN",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "category_id" : "A10003123490",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10003123490-A10003123492",
+         "slot_id" : "A10003123492",
+         "text" : "2 2020-2021  RESTAURATION SCOLAIRE",
+         "user_booking_status" : "booked"
+      },
+      {
+         "category" : "3 2020-2021 GARDERIE SOIR",
+         "category_id" : "A10003123507",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10003123507-A10003123509",
+         "slot_id" : "A10003123509",
+         "text" : "3 2020-2021 GARDERIE SOIR",
+         "user_booking_status" : "not-booked"
+      },
+      {
+         "category" : "COURS DE DANSE",
+         "category_id" : "A10003132090",
+         "datetime" : "2020-12-18 00:00:00",
+         "id" : "2020-12-18-A10003132090-A10003132091",
+         "slot_id" : "A10003132091",
+         "text" : "COURS DE DANSE",
+         "user_booking_status" : "not-booked"
+      }
+   ],
+   "err" : 0
+}
diff --git a/tests/test_maelis.py b/tests/test_maelis.py
index 0e103fd8..64b4db52 100644
--- a/tests/test_maelis.py
+++ b/tests/test_maelis.py
@@ -5,17 +5,17 @@ import mock
 import os
 import pytest
 
 from django.test import override_settings
 from django.utils.dateparse import parse_date
 
 from passerelle.apps.maelis.models import Maelis, Link
 from passerelle.apps.maelis.utils import (
-    get_school_year,  week_boundaries_datetimes,  month_range)
+    get_school_year,  week_boundaries_datetimes,  month_range, decompose_event)
 
 from passerelle.utils.jsonresponse import APIError
 
 import utils
 
 pytestmark = pytest.mark.django_db
 
 TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'maelis')
@@ -242,20 +242,51 @@ def test_week_boundaries(date, monday, sunday):
     ('2021-01-03', '2020-12-01', []),
 ])
 def test_month_range(start, end, items):
     start = parse_date(start)
     end = parse_date(end)
     assert [x.strftime('%Y-%m-%d') for x in month_range(start, end)] == items
 
 
+def test_decompose_event():
+    resp = get_json_file('child_planning_before_decomposition')
+    data = resp['data']
+    assert len(data) == 43
+    assert data[22]['text'] == 'JOURNEE'
+    assert data[23]['text'] == 'MATIN'
+    assert data[24]['text'] == 'MATIN ET REPAS'
+    assert data[25]['text'] == 'APRES MIDI'
+    assert not [x for x in data if x['text'] == 'REPAS']
+
+    # unit is break down into its components
+    assert [x['text'] for x in decompose_event(data[22])] == [
+        'Matinée', 'Repas', 'Après-midi']
+    assert [x['text'] for x in decompose_event(data[23])] == [
+        'Matinée']
+    assert [x['text'] for x in decompose_event(data[24])] == [
+        'Matinée', 'Repas']
+    assert [x['text'] for x in decompose_event(data[25])] == [
+        'Après-midi']
+
+    # child_planning function use a dict to remove dupplicated components
+    data = {x['id']: x for e in data for x in decompose_event(e)}.values()
+    assert len(data) == 42
+    assert len([x for x in data if x['text'] == 'Repas']) == 1
+    assert len([x for x in data if 'EO0001' in x['slot_id']]) == 3
+
+
 @override_settings(TIME_ZONE='Europe/Paris')
+@pytest.mark.parametrize('legacy, nb_events, nb_booked, response', [
+    ('please', 43, 9, 'child_planning_before_decomposition'),
+    ('', 42, 9, 'child_planning_after_decomposition'),
+])
 @mock.patch('passerelle.utils.Request.get')
 @mock.patch('passerelle.utils.Request.post')
-def test_get_child_planning(mocked_post, mocked_get,
+def test_get_child_planning(mocked_post, mocked_get, legacy, nb_events, nb_booked, response,
                             family_service_wsdl, activity_service_wsdl, app, connector):
     mocked_get.side_effect = (
         utils.FakedResponse(
             content=family_service_wsdl,
             status_code=200,
             headers={'Content-Type': 'text/xml'}),
         utils.FakedResponse(
             content=activity_service_wsdl,
@@ -276,22 +307,22 @@ def test_get_child_planning(mocked_post, mocked_get,
             status_code=200,
             headers={'Content-Type': 'text/xml'}),
         utils.FakedResponse(
             content=get_xml_file('child_planning_readChildMonthPlanningResponse.xml'),
             status_code=200,
             headers={'Content-Type': 'text/xml'})
     )
     Link.objects.create(resource=connector, family_id='3264', name_id='local')
-    resp = app.get(
-        '/maelis/test/child-planning?NameID=local&childID=21293&start_date=2020-12-19')
+    url = '/maelis/test/child-planning?NameID=local&childID=21293&start_date=2020-12-19'
+    url += '&legacy=' + legacy
+    resp = app.get(url)
     data = resp.json['data']
     previous_event = {}
     for event in data:
         assert event['category']
         assert event['text']
         if previous_event:
             assert previous_event['id'] <= event['id']
         previous_event = event
-    assert len(data) == 43
-    assert len([s for s in data if s['user_booking_status'] == 'not-booked']) == 34
-    assert len([s for s in data if s['user_booking_status'] == 'booked']) == 9
-    assert resp.json == get_json_file('child_planning_before_decomposition')
+    assert len(data) == nb_events
+    assert len([s for s in data if s['user_booking_status'] == 'booked']) == nb_booked
+    assert resp.json == get_json_file(response)
-- 
2.29.2

