From ace9a77dfdaa9bacc01714954b5a18e763104669 Mon Sep 17 00:00:00 2001 From: "guilhem.carron" <gcarron@grandlyon.com> Date: Wed, 9 Mar 2022 18:25:26 +0100 Subject: [PATCH] feat(prices): Add new price managment with remote doctype --- manifest.webapp | 20 + src/components/Splash/SplashRoot.tsx | 6 +- src/db/fluidPrices.json | 500 -------------------- src/db/profileData.json | 1 - src/migrations/migration.data.ts | 16 + src/models/fluidPrice.model.ts | 1 + src/models/profile.model.ts | 1 - src/services/fluidsPrices.service.spec.ts | 56 +++ src/services/fluidsPrices.service.ts | 70 ++- src/services/initialization.service.spec.ts | 101 +--- src/services/initialization.service.ts | 89 ++-- src/targets/services/fluidsPrices.ts | 302 ++++++++---- tests/__mocks__/profile.mock.ts | 1 - 13 files changed, 422 insertions(+), 742 deletions(-) delete mode 100644 src/db/fluidPrices.json diff --git a/manifest.webapp b/manifest.webapp index cd6cfdb29..f247e4c6d 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -65,6 +65,12 @@ }, "backoffice-partners-info": { "description": "Requis pour la récupération du status des services partenaires" + }, + "backoffice-prices-rec": { + "description": "Requis pour la récupération du prix des fluides dans un environnement de test." + }, + "backoffice-prices": { + "description": "Requis pour la récupération du prix des fluides." } } }, @@ -125,6 +131,12 @@ }, "backoffice-partners-info": { "description": "Required for getting the status of partners' services" + }, + "backoffice-prices-rec": { + "description": "Required for getting fluid prices form backoffice rec." + }, + "backoffice-prices": { + "description": "Required for getting fluid prices form backoffice prod" } } } @@ -231,6 +243,14 @@ "backoffice-partners-info": { "type": "org.ecolyo.backoffice.partners.info", "verbs": ["ALL"] + }, + "backoffice-prices-rec": { + "type": "org.ecolyo.backoffice.prices.rec", + "verbs": ["ALL"] + }, + "backoffice-prices": { + "type": "org.ecolyo.backoffice.prices", + "verbs": ["ALL"] } } } diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 3d7eba0a5..b7b56ecd6 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -131,6 +131,9 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { const termsStatus: TermsStatus = await initializationService.initConsent() if (subscribed) dispatch(updateTermValidation(termsStatus)) + // Init fluidPrices + await initializationService.initFluidPrices() + // Init profile and update ecogestures, challenges, analysis const profile = await initializationService.initProfile() const profileType = await initializationService.initProfileType() @@ -139,7 +142,6 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { setValidExploration(UserExplorationID.EXPLORATION007) const [ ecogestureHash, - fluidPricesHash, duelHash, quizHash, challengeHash, @@ -147,7 +149,6 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { analysisResult, ] = await Promise.all([ initializationService.initEcogesture(profile.ecogestureHash), - initializationService.initFluidPrices(profile.fluidPricesHash), initializationService.initDuelEntity(profile.duelHash), initializationService.initQuizEntity(profile.quizHash), initializationService.initExplorationEntity(profile.challengeHash), @@ -155,7 +156,6 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { initializationService.initAnalysis(profile), ]) profile.ecogestureHash = ecogestureHash - profile.fluidPricesHash = fluidPricesHash profile.duelHash = duelHash profile.quizHash = quizHash profile.challengeHash = challengeHash diff --git a/src/db/fluidPrices.json b/src/db/fluidPrices.json deleted file mode 100644 index 42e6b7167..000000000 --- a/src/db/fluidPrices.json +++ /dev/null @@ -1,500 +0,0 @@ -[ - { - "fluidType": 0, - "price": 0.1256, - "startDate": "2012-07-23T00:00:00.000Z", - "endDate": "2013-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1329, - "startDate": "2013-08-01T00:00:00.000Z", - "endDate": "2014-10-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1401, - "startDate": "2014-01-11T00:00:00.000Z", - "endDate": "2015-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1437, - "startDate": "2015-08-01T00:00:00.000Z", - "endDate": "2016-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1503, - "startDate": "2016-08-01T00:00:00.000Z", - "endDate": "2017-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1546, - "startDate": "2017-08-01T00:00:00.000Z", - "endDate": "2018-01-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1555, - "startDate": "2018-02-01T00:00:00.000Z", - "endDate": "2018-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.145, - "startDate": "2018-08-01T00:00:00.000Z", - "endDate": "2019-05-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1531, - "startDate": "2019-06-01T00:00:00.000Z", - "endDate": "2019-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1524, - "startDate": "2019-08-01T00:00:00.000Z", - "endDate": "2020-01-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1546, - "startDate": "2020-02-01T00:00:00.000Z", - "endDate": "2020-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1557, - "startDate": "2020-08-01T00:00:00.000Z", - "endDate": "2021-01-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1582, - "startDate": "2021-02-01T00:00:00.000Z", - "endDate": "2021-07-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.1558, - "startDate": "2021-08-01T00:00:00.000Z", - "endDate": "2022-01-31T23:59:59.000Z" - }, - { - "fluidType": 0, - "price": 0.174, - "startDate": "2022-02-01T00:00:00.000Z", - "endDate": null - }, - { - "fluidType": 1, - "price": 0.0030735, - "startDate": "2012-01-01T00:00:00.000Z", - "endDate": "2012-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.0031483, - "startDate": "2013-01-01T00:00:00.000Z", - "endDate": "2013-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.0031381, - "startDate": "2014-01-01T00:00:00.000Z", - "endDate": "2014-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00307, - "startDate": "2015-01-01T00:00:00.000Z", - "endDate": "2015-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.0031, - "startDate": "2016-01-01T00:00:00.000Z", - "endDate": "2016-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00311, - "startDate": "2017-01-01T00:00:00.000Z", - "endDate": "2017-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00313, - "startDate": "2018-01-01T00:00:00.000Z", - "endDate": "2018-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00313, - "startDate": "2019-01-01T00:00:00.000Z", - "endDate": "2019-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00315, - "startDate": "2020-01-01T00:00:00.000Z", - "endDate": "2020-12-31T23:59:59.000Z" - }, - { - "fluidType": 1, - "price": 0.00319, - "startDate": "2021-01-01T00:00:00.000Z", - "endDate": null - }, - { - "fluidType": 2, - "price": 0.0919, - "startDate": "2017-01-01T00:00:00.000Z", - "endDate": "2017-01-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0915, - "startDate": "2017-02-01T00:00:00.000Z", - "endDate": "2017-02-28T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0932, - "startDate": "2017-03-01T00:00:00.000Z", - "endDate": "2017-03-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0927, - "startDate": "2017-04-01T00:00:00.000Z", - "endDate": "2017-04-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0906, - "startDate": "2017-05-01T00:00:00.000Z", - "endDate": "2017-05-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0906, - "startDate": "2017-06-01T00:00:00.000Z", - "endDate": "2017-06-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0788, - "startDate": "2017-07-01T00:00:00.000Z", - "endDate": "2017-07-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0783, - "startDate": "2017-08-01T00:00:00.000Z", - "endDate": "2017-08-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0783, - "startDate": "2017-09-01T00:00:00.000Z", - "endDate": "2017-09-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0791, - "startDate": "2017-10-01T00:00:00.000Z", - "endDate": "2017-10-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0806, - "startDate": "2017-11-01T00:00:00.000Z", - "endDate": "2017-11-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0812, - "startDate": "2017-12-01T00:00:00.000Z", - "endDate": "2017-12-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0857, - "startDate": "2018-01-01T00:00:00.000Z", - "endDate": "2018-01-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0866, - "startDate": "2018-02-01T00:00:00.000Z", - "endDate": "2018-02-28T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0847, - "startDate": "2018-03-01T00:00:00.000Z", - "endDate": "2018-03-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0839, - "startDate": "2018-04-01T00:00:00.000Z", - "endDate": "2018-04-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0842, - "startDate": "2018-05-01T00:00:00.000Z", - "endDate": "2018-05-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0855, - "startDate": "2018-06-01T00:00:00.000Z", - "endDate": "2018-06-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0959, - "startDate": "2018-07-01T00:00:00.000Z", - "endDate": "2018-07-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0961, - "startDate": "2018-08-01T00:00:00.000Z", - "endDate": "2018-08-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0967, - "startDate": "2018-09-01T00:00:00.000Z", - "endDate": "2018-09-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0989, - "startDate": "2018-10-01T00:00:00.000Z", - "endDate": "2018-10-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.1031, - "startDate": "2018-11-01T00:00:00.000Z", - "endDate": "2018-11-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.1013, - "startDate": "2018-12-01T00:00:00.000Z", - "endDate": "2018-12-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0999, - "startDate": "2019-01-01T00:00:00.000Z", - "endDate": "2019-01-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0993, - "startDate": "2019-02-01T00:00:00.000Z", - "endDate": "2019-02-28T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0993, - "startDate": "2019-03-01T00:00:00.000Z", - "endDate": "2019-03-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0977, - "startDate": "2019-04-01T00:00:00.000Z", - "endDate": "2019-04-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0973, - "startDate": "2019-05-01T00:00:00.000Z", - "endDate": "2019-05-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0969, - "startDate": "2019-06-01T00:00:00.000Z", - "endDate": "2019-06-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0795, - "startDate": "2019-07-01T00:00:00.000Z", - "endDate": "2019-07-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0791, - "startDate": "2019-08-01T00:00:00.000Z", - "endDate": "2019-08-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0785, - "startDate": "2019-09-01T00:00:00.000Z", - "endDate": "2019-09-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.077, - "startDate": "2019-10-01T00:00:00.000Z", - "endDate": "2019-10-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0789, - "startDate": "2019-11-01T00:00:00.000Z", - "endDate": "2019-11-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0793, - "startDate": "2019-12-01T00:00:00.000Z", - "endDate": "2019-12-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0787, - "startDate": "2020-01-01T00:00:00.000Z", - "endDate": "2020-01-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0765, - "startDate": "2020-02-01T00:00:00.000Z", - "endDate": "2020-02-29T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0736, - "startDate": "2020-03-01T00:00:00.000Z", - "endDate": "2020-03-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.071, - "startDate": "2020-04-01T00:00:00.000Z", - "endDate": "2020-04-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0703, - "startDate": "2020-05-01T00:00:00.000Z", - "endDate": "2020-05-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0687, - "startDate": "2020-06-01T00:00:00.000Z", - "endDate": "2020-06-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0698, - "startDate": "2020-07-01T00:00:00.000Z", - "endDate": "2020-07-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0705, - "startDate": "2020-08-01T00:00:00.000Z", - "endDate": "2020-08-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0709, - "startDate": "2020-09-01T00:00:00.000Z", - "endDate": "2020-09-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0735, - "startDate": "2020-10-01T00:00:00.000Z", - "endDate": "2020-10-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0745, - "startDate": "2020-11-01T00:00:00.000Z", - "endDate": "2020-11-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0759, - "startDate": "2020-12-01T00:00:00.000Z", - "endDate": "2020-12-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.076, - "startDate": "2021-01-01T00:00:00.000Z", - "endDate": "2021-01-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0782, - "startDate": "2021-02-01T00:00:00.000Z", - "endDate": "2021-02-28T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0818, - "startDate": "2021-03-01T00:00:00.000Z", - "endDate": "2021-03-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.079, - "startDate": "2021-04-01T00:00:00.000Z", - "endDate": "2021-04-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0797, - "startDate": "2021-05-01T00:00:00.000Z", - "endDate": "2021-05-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0826, - "startDate": "2021-06-01T00:00:00.000Z", - "endDate": "2021-06-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0895, - "startDate": "2021-07-01T00:00:00.000Z", - "endDate": "2021-07-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.0934, - "startDate": "2021-08-01T00:00:00.000Z", - "endDate": "2021-08-31T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.1002, - "startDate": "2021-09-01T00:00:00.000Z", - "endDate": "2021-09-30T23:59:59.000Z" - }, - { - "fluidType": 2, - "price": 0.1121, - "startDate": "2021-10-01T00:00:00.000Z", - "endDate": null - } -] diff --git a/src/db/profileData.json b/src/db/profileData.json index 27e8408d2..c8007e374 100644 --- a/src/db/profileData.json +++ b/src/db/profileData.json @@ -5,7 +5,6 @@ "mailToken": "", "duelHash": "", "quizHash": "", - "fluidPricesHash": "", "isFirstConnection": true, "lastConnectionDate": "0000-01-01T00:00:00.000Z", "haveSeenLastAnalysis": true, diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index 1f4220283..c7c630402 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -516,4 +516,20 @@ export const migrations: Migration[] = [ return docs }, }, + { + baseSchemaVersion: 19, + targetSchemaVersion: 20, + appVersion: '1.8.0', + description: + 'Empty fluidPrices db so it can be fetched with right format from remote doctype', + releaseNotes: null, + docTypes: FLUIDPRICES_DOCTYPE, + run: async (_client: Client, docs: any[]): Promise<any> => { + docs.map(doc => { + doc.deleteAction = true + return doc + }) + return docs + }, + }, ] diff --git a/src/models/fluidPrice.model.ts b/src/models/fluidPrice.model.ts index e1ca1fcf9..97bd62279 100644 --- a/src/models/fluidPrice.model.ts +++ b/src/models/fluidPrice.model.ts @@ -5,6 +5,7 @@ export interface FluidPrice { price: number startDate: string endDate: string + UpdatedAt?: string _id: string _rev?: string _type?: string diff --git a/src/models/profile.model.ts b/src/models/profile.model.ts index e3179a77a..0a4b3e712 100644 --- a/src/models/profile.model.ts +++ b/src/models/profile.model.ts @@ -11,7 +11,6 @@ export interface ProfileEntity { duelHash: string quizHash: string explorationHash: string - fluidPricesHash: string isFirstConnection: boolean lastConnectionDate: string haveSeenLastAnalysis: boolean diff --git a/src/services/fluidsPrices.service.spec.ts b/src/services/fluidsPrices.service.spec.ts index c3a9fe500..e3c1c5f78 100644 --- a/src/services/fluidsPrices.service.spec.ts +++ b/src/services/fluidsPrices.service.spec.ts @@ -118,4 +118,60 @@ describe('FluidPrices service', () => { expect(result).toBe(false) }) }) + it('should checkIfPriceExists and return it', async () => { + const mockQueryResult: QueryResult<FluidPrice[]> = { + data: [fluidPrices[0]], + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValueOnce(mockQueryResult) + const price = await fluidPricesService.checkIfPriceExists(fluidPrices[0]) + expect(price).toStrictEqual(fluidPrices[0]) + expect(mockClient.query).toBeCalled() + }) + it('should create a price and return it', async () => { + const mockQueryResult: QueryResult<FluidPrice> = { + data: fluidPrices[0], + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValueOnce(mockQueryResult) + const result = await fluidPricesService.createPrice(fluidPrices[0]) + expect(result).toEqual(fluidPrices[0]) + }) + it('should fail to create a price', async () => { + mockClient.create.mockRejectedValue(new Error()) + try { + await fluidPricesService.createPrice(fluidPrices[0]) + } catch (error) { + expect(error).toEqual(new Error()) + } + }) + it('should update a price', async () => { + const updatedPrice = { ...fluidPrices[0], price: 0.1 } + const mockQueryResult: QueryResult<FluidPrice | null> = { + data: updatedPrice, + bookmark: '', + next: false, + skip: 0, + } + mockClient.save.mockResolvedValue(mockQueryResult) + const price = await fluidPricesService.updatePrice(fluidPrices[0], { + price: 0.1, + }) + expect(price).toStrictEqual(updatedPrice) + expect(mockClient.query).toBeCalled() + }) + it('should fail to update a price', async () => { + mockClient.save.mockRejectedValue(new Error()) + try { + await fluidPricesService.updatePrice(fluidPrices[0], { + price: 0.1, + }) + } catch (error) { + expect(error).toEqual(new Error()) + } + }) }) diff --git a/src/services/fluidsPrices.service.ts b/src/services/fluidsPrices.service.ts index bea791b40..48d95edb3 100644 --- a/src/services/fluidsPrices.service.ts +++ b/src/services/fluidsPrices.service.ts @@ -36,7 +36,13 @@ export default class FluidPricesService { date: DateTime ): Promise<FluidPrice> { const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE) - .where({ startDate: { $lt: date.toString() }, fluidType }) + .indexFields(['startDate']) + .where({ + startDate: { + $lte: date.toISO({ suppressMilliseconds: true }).toString(), + }, + fluidType, + }) .sortBy([{ startDate: 'desc' }]) .limitBy(1) @@ -52,6 +58,7 @@ export default class FluidPricesService { */ public async getAllLastPrices(): Promise<FluidPrice[]> { const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE) + .indexFields(['fluidType']) .where({ endDate: { $eq: null } }) .sortBy([{ fluidType: 'asc' }]) .limitBy(3) @@ -104,4 +111,65 @@ export default class FluidPricesService { return false } } + + /** + * Check if a fluidprice exists in db + * @param {FluidPrice} fluidPrice + * @returns {FluidPrice | null} price or null + */ + public async checkIfPriceExists( + fluidPrice: FluidPrice + ): Promise<FluidPrice | null> { + const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE).where({ + startDate: { $eq: fluidPrice.startDate }, + fluidType: { $eq: fluidPrice.fluidType }, + }) + const { + data: [price], + }: QueryResult<FluidPrice[]> = await this._client.query(query) + if (price) return price + else return null + } + + /** + * Creates a new fluidPrice + * @param {FluidPrice} fluidPrice + * @returns {FluidPrice | null} price or null + */ + public async createPrice(newPrice: FluidPrice): Promise<FluidPrice | null> { + try { + const { + data: createdPrice, + }: QueryResult<FluidPrice> = await this._client.create( + FLUIDPRICES_DOCTYPE, + newPrice + ) + return createdPrice + } catch (error) { + console.log('Error creating new createdPrice: ', error) + throw error + } + } + + /** + * Updates a price in db + * @param {FluidPrice} doc + * @param {Partial<FluidPrice>} attributes + * @returns {FluidPrice | null} + */ + public async updatePrice( + doc: FluidPrice, + attributes: Partial<FluidPrice> + ): Promise<FluidPrice | null> { + const { + data: fluidPrice, + }: QueryResult<FluidPrice | null> = await this._client.save({ + ...doc, + ...attributes, + }) + if (fluidPrice) { + return fluidPrice + } + return null + } } diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index 7a6fc2ce0..e782eedb8 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -9,7 +9,6 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' -import fluidPrices from 'db/fluidPrices.json' import { hashFile } from 'utils/hash' import { getActualAnalysisDate } from 'utils/date' @@ -23,11 +22,11 @@ import { allChallengeEntityData } from '../../tests/__mocks__/challengeEntity.mo import { allDuelEntity } from '../../tests/__mocks__/duelData.mock' import { allQuizEntities } from '../../tests/__mocks__/quizData.mock' import { allExplorationEntities } from '../../tests/__mocks__/explorationData.mock' -import { fluidPrices as allFluidPrices } from '../../tests/__mocks__/fluidPrice.mock' import { mockOutdatedTerm, mockUpToDateTerm, } from '../../tests/__mocks__/termsData.mock' +import { fluidPrices } from '../../tests/__mocks__/fluidPrice.mock' const mockCreateIndexKonnector = jest.fn() jest.mock('./konnector.service', () => { @@ -364,101 +363,17 @@ describe('Initialization service', () => { mockGetAllPrices.mockClear() mockDeleteAllFluidsPrices.mockClear() }) - it('should return hash when fluidPrices hash is already up to date', async () => { + it('should do nothing because prices are already created', async () => { mockGetAllPrices.mockResolvedValueOnce(fluidPrices) - const hash = hashFile(fluidPrices) - await expect( - initializationService.initFluidPrices(hash) - ).resolves.toEqual(hash) - }) - it('should return hash when fluidPrices are created', async () => { - mockGetAllPrices - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(fluidPrices) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - const hash = hashFile(fluidPrices) - await expect( - initializationService.initFluidPrices(hash) - ).resolves.toEqual(hash) + const isDone = await initializationService.initFluidPrices() + expect(isDone).toBeTruthy() }) - it('should throw an error when fluidPrices entities should be created and created fluidPrices entities number does not match', async () => { - mockGetAllPrices - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(allFluidPrices) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect( - initializationService.initFluidPrices(hashFile(fluidPrices)) - ).rejects.toThrow( - new Error( - 'initFluidPrices: Created fluidPrices type entities does not match' - ) - ) - }) - it('should throw an error when fluidPrices should be created and creation failed', async () => { - mockGetAllPrices.mockResolvedValueOnce(null) - mockClient.create.mockRejectedValue(new Error()) - await expect( - initializationService.initChallengeEntity(hashFile(fluidPrices)) - ).rejects.toThrow(new Error()) - }) - it('should return hash when fluidPrices are updated', async () => { - mockGetAllPrices - .mockResolvedValueOnce(fluidPrices) - .mockResolvedValueOnce(fluidPrices) - mockDeleteAllFluidsPrices.mockResolvedValue(true) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect(initializationService.initFluidPrices('')).resolves.toEqual( - hashFile(fluidPrices) - ) - }) - it('should throw an error when fluidPrices should be updated and fluidPrices entities number does not match', async () => { - mockGetAllPrices - .mockResolvedValueOnce('') - .mockResolvedValueOnce(allFluidPrices) - mockDeleteAllFluidsPrices.mockResolvedValue(true) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect(initializationService.initFluidPrices('')).rejects.toThrow( - new Error( - 'initFluidPrices: Created fluidPrices type entities does not match' - ) - ) - }) - it('should throw an error whenfluidPrices should be updated and fluidPrices entities creation failed', async () => { - mockGetAllPrices - .mockResolvedValueOnce(fluidPrices) - .mockResolvedValueOnce(fluidPrices) - mockDeleteAllFluidsPrices.mockResolvedValue(true) - mockClient.create.mockRejectedValueOnce(new Error()) - expect(initializationService.initFluidPrices('')).rejects.toThrow( - new Error() - ) + it('should try to fetch prices from remote doctype', async () => { + mockGetAllPrices.mockResolvedValueOnce('') + const isDone = await initializationService.initFluidPrices() + expect(isDone).toBeFalsy() }) }) - describe('initChallengeEntity method', () => { beforeEach(() => { mockGetAllChallengeEntities.mockClear() diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index 6ef652ac7..43ff56592 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -16,12 +16,12 @@ import { DUEL_DOCTYPE, QUIZ_DOCTYPE, EXPLORATION_DOCTYPE, - FLUIDPRICES_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' import { Dataload, + FluidPrice, FluidStatus, Profile, ProfileType, @@ -36,7 +36,6 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' -import fluidPrices from 'db/fluidPrices.json' import ProfileService from 'services/profile.service' import profileData from 'db/profileData.json' @@ -58,6 +57,7 @@ import log from 'utils/logger' import { ProfileEcogesture } from 'models/profileEcogesture.model' import ProfileEcogestureService from './profileEcogesture.service' import FluidPricesService from './fluidsPrices.service' +import EnvironmentService from './environment.service' import React from 'react' import { InitSteps, InitStepsErrors } from 'models/initialisationSteps.model' @@ -312,71 +312,44 @@ export default class InitializationService { } } - public async initFluidPrices(hash: string): Promise<string> { - this._setinitStep(InitSteps.PRICES) - const hashFluidPricesType = hashFile(fluidPrices) + public async initFluidPrices(): Promise<boolean> { const fpService = new FluidPricesService(this._client) // Populate data if none ecogesture exists const loadedPrices = await fpService.getAllPrices() - if (!loadedPrices || (loadedPrices && loadedPrices.length === 0)) { - // Populate the doctype with data - + if (loadedPrices && loadedPrices.length) { + log.info('[Initialization] FluidPrices db already created') + return true + } else { try { - for (const price of fluidPrices) { - await this._client.create(FLUIDPRICES_DOCTYPE, price) - } - // Check of created document based on count - const checkCount = await fpService.getAllPrices() - if ( - !checkCount || - (checkCount && checkCount.length !== fluidPrices.length) - ) { - this._setinitStepError(InitStepsErrors.PRICES_ERROR) - throw new Error( - 'initFluidPrices: Created fluidPrices type entities does not match' - ) + const fluidTypes: FluidType[] = [ + FluidType.ELECTRICITY, + FluidType.WATER, + FluidType.GAS, + ] + const allPrices: FluidPrice[] = [] + const env = new EnvironmentService() + const remoteUrl = env.isProduction() + ? `/remote/org.ecolyo.backoffice.prices` + : `/remote/org.ecolyo.backoffice.prices.rec` + + for (const fluid of fluidTypes) { + const prices = await this._client + .getStackClient() + .fetchJSON('GET', `${remoteUrl}?fluidtype=${fluid}`) + allPrices.push(...prices) } - log.info('[Initialization] FluidPrices list created') - return hashFluidPricesType - } catch (error) { - this._setinitStepError(InitStepsErrors.PRICES_ERROR) - log.error('Initialization error - initFluidPrices: ', error) - throw error - } - } - // Update if the hash is not the same as the one from profile - if (hash !== hashFluidPricesType) { - // Update the doctype - try { - // Deletion of all documents - await fpService.deleteAllFluidsPrices() - // Population with the data - for (const price of fluidPrices) { - await this._client.create(FLUIDPRICES_DOCTYPE, price) + for (const price of allPrices) { + await fpService.createPrice(price) } - // Check of created document based on count - const checkCount = await fpService.getAllPrices() - if ( - !checkCount || - (checkCount && checkCount.length !== fluidPrices.length) - ) { - this._setinitStepError(InitStepsErrors.PRICES_ERROR) - throw new Error( - 'initFluidPrices: Created fluidPrices type entities does not match' - ) - } - log.info('[Initialization] FluidPrices updated') - return hashFluidPricesType - } catch (error) { + log.info('[Initialization] FluidPrices db created successfully') + return true + } catch (err) { this._setinitStepError(InitStepsErrors.PRICES_ERROR) - log.error('Initialization error - initFluidPrices: ', error) - throw error + + log.error('Initialization error - initFluidPrices: ', err) + return false } - } else { - // Doctype already up to date - log.info('[Initialization] FluidPrices already up-to-date') - return hashFluidPricesType } } diff --git a/src/targets/services/fluidsPrices.ts b/src/targets/services/fluidsPrices.ts index 32a9e11d1..4096d459a 100644 --- a/src/targets/services/fluidsPrices.ts +++ b/src/targets/services/fluidsPrices.ts @@ -3,20 +3,101 @@ import { Client } from 'cozy-client' import { runService } from './service' import { DateTime } from 'luxon' import FluidPricesService from 'services/fluidsPrices.service' -import { DataloadEntity, TimePeriod } from 'models' +import { DataloadEntity, FluidPrice, TimePeriod } from 'models' import ConsumptionDataManager from 'services/consumption.service' import { TimeStep } from 'enum/timeStep.enum' import { ENEDIS_DAY_DOCTYPE, GRDF_DAY_DOCTYPE, EGL_DAY_DOCTYPE } from 'doctypes' import { FluidType } from 'enum/fluid.enum' import QueryRunner from 'services/queryRunner.service' +import EnvironmentService from 'services/environment.service' const log = logger.namespace('fluidPrices') interface PricesProps { client: Client } -const price = (item: DataloadEntity): number | null => { - return item.price ? item.price : null +const getRemotePricesByfluid = async ( + client: Client, + fluidType: FluidType +): Promise<FluidPrice[]> => { + const env = new EnvironmentService() + const remoteUrl = env.isProduction() + ? `/remote/org.ecolyo.backoffice.prices` + : `/remote/org.ecolyo.backoffice.prices.rec` + const prices = await client + .getStackClient() + .fetchJSON('GET', `${remoteUrl}?fluidtype=${fluidType}`) + return prices +} + +/** + * Synchro the remote prices with database and returns a date where we have to relaunch aggregation if a price has been edited in backoffice + * @param {Client} client + * @param {FluidType} fluidType + * @returns {string | null} the oldest startDate + */ +const synchroPricesToUpdate = async ( + client: Client, + fluidType: FluidType +): Promise<string | null> => { + const fps = new FluidPricesService(client) + const remotePrices = await getRemotePricesByfluid(client, fluidType) + let firstEditedPrice: string | null = null + await Promise.all( + remotePrices.map(async remotePrice => { + return new Promise<void>(async resolve => { + try { + //Check if price exist in database + const existingPrice = await fps.checkIfPriceExists(remotePrice) + if (existingPrice) { + //Check if the remote price is more recent + if ( + existingPrice.UpdatedAt && + remotePrice.UpdatedAt && + existingPrice.UpdatedAt < remotePrice.UpdatedAt + ) { + log('info', `Price exist in db but not up to date, updating it`) + //If a price has been updated, set the oldest startDate of the edited price so we can redo aggregation + if (firstEditedPrice === null) { + firstEditedPrice = remotePrice.startDate + } + if (firstEditedPrice >= remotePrice.startDate) { + firstEditedPrice = remotePrice.startDate + } + + //update this price in db + await fps.updatePrice(existingPrice, { + price: remotePrice.price, + UpdatedAt: remotePrice.UpdatedAt, + startDate: remotePrice.startDate, + endDate: remotePrice.endDate, + }) + } else if (!existingPrice.UpdatedAt && remotePrice.UpdatedAt) { + //updatedAt key doesn't exist in db + await fps.updatePrice(existingPrice, { + UpdatedAt: remotePrice.UpdatedAt, + }) + } else { + log('info', `Price up to date`) + } + } else { + log('info', `Price doesn't exist in db, creating new price`) + //create price in db + await fps.createPrice(remotePrice) + } + } catch (err) { + log('error', `Error: ${err}`) + } finally { + resolve() + } + }) + }) + ) + return firstEditedPrice +} + +const price = (item: DataloadEntity): number => { + return item.price ? item.price : 0 } const sum = (prev: number, next: number): number => { @@ -51,7 +132,6 @@ const getTimePeriod = async ( } const aggregatePrices = async ( - client: Client, qr: QueryRunner, cdm: ConsumptionDataManager, firstDate: DateTime, @@ -59,41 +139,47 @@ const aggregatePrices = async ( fluidType: FluidType ) => { const tsa = [TimeStep.MONTH, TimeStep.YEAR] - log('debug', `Aggregation...`) - const aggregatePromises = tsa.map(async ts => { - return new Promise<void>(async resolve => { - let date: DateTime = DateTime.local() - Object.assign(date, firstDate) - do { - log( - 'debug', - `Step: ${ts} | Fluid: ${fluidType} | Date: ${date.day}/${date.month}/${date.year}` - ) - const tp = await getTimePeriod(ts, date) - // Get doc for aggregation - const data = await qr.fetchFluidRawDoctype(tp, TimeStep.DAY, fluidType) - - // Get doc to update - const docToUpdate = await qr.fetchFluidRawDoctype(tp, ts, fluidType) - - if (docToUpdate && data && docToUpdate.data && data.data) { - docToUpdate.data[0].price = data.data.map(price).reduce(sum) - } + log( + 'debug', + `Aggregation started for fluid: ${fluidType}, from ${firstDate} ` + ) + await Promise.all( + tsa.map(async ts => { + return new Promise<void>(async resolve => { + let date: DateTime = DateTime.local() + Object.assign(date, firstDate) + try { + do { + const tp = await getTimePeriod(ts, date) + // Get doc for aggregation + const data = await qr.fetchFluidRawDoctype( + tp, + TimeStep.DAY, + fluidType + ) + // Get doc to update + const docToUpdate = await qr.fetchFluidRawDoctype(tp, ts, fluidType) - // Save updated docs - await cdm.saveDocs(docToUpdate.data) - // Update date according to timestep - if (ts === TimeStep.YEAR) { - date = date.plus({ year: 1 }).startOf('month') - } else { - date = date.plus({ month: 1 }).startOf('month') + if (docToUpdate && data && docToUpdate.data && data.data) { + docToUpdate.data[0].price = data.data.map(price).reduce(sum) + } + // Save updated docs + await cdm.saveDocs(docToUpdate.data) + // Update date according to timestep + if (ts === TimeStep.YEAR) { + date = date.plus({ year: 1 }).startOf('month') + } else { + date = date.plus({ month: 1 }).startOf('month') + } + } while (date < today) + } catch (err) { + log('info', `Error : ${err}`) + } finally { + resolve() } - } while (date < today) - resolve() + }) }) - }) - - await Promise.all(aggregatePromises) + ) log('debug', `Aggregation done`) } @@ -128,6 +214,10 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { const fluidsPricesService = new FluidPricesService(client) const cdm = new ConsumptionDataManager(client) const qr = new QueryRunner(client) + + //Synchro dbprices with remote prices + const firstEditedPriceDate = await synchroPricesToUpdate(client, fluidType) + const firstDataDate = await cdm.fetchAllFirstDateData([fluidType]) const prices = await fluidsPricesService.getAllPrices() // Prices data exsit if (prices.length > 0) { @@ -135,65 +225,109 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { const firstMinuteData = await cdm.getFirstDataDateFromDoctypeWithPrice( getDoctypeTypeByFluid(fluidType) ) + // const firstDoctypeData = await cdm.getFirsDataDateFromDoctype() // If there is data, update hourly data and daily data - if (firstMinuteData) { - // Format first date - const firstDate = DateTime.fromObject({ - year: firstMinuteData.year, - month: firstMinuteData.month, - day: firstMinuteData.day, - }) + if ( + firstDataDate && + firstDataDate[0] && + (firstMinuteData || firstEditedPriceDate !== null) + ) { const today = DateTime.now() const tsa = getTimeSetByFluid(fluidType) + let firstDate: DateTime - // Hourly and daily prices - const promises = tsa.map(async timeStep => { - return new Promise<void>(async resolve => { - let date: DateTime = DateTime.local() - Object.assign(date, firstDate) - do { - // Get price - const priceData = await fluidsPricesService.getPrices( - fluidType, - date - ) - // log( - // 'debug', - // `Step: ${timeStep} | Fluid : ${fluidType} | Date: ${date.day}/${date.month}/${date.year} | Price: ${priceData.price}` - // ) - const tp = await getTimePeriod(timeStep, date) + if (firstMinuteData && firstEditedPriceDate) { + // If there is first data without price and a price edited, set the smallest date + const firstMinuteDataDate = DateTime.fromObject({ + year: firstMinuteData.year, + month: firstMinuteData.month, + day: firstMinuteData.day, + }).setZone('utc', { + keepLocalTime: true, + }) + const formattedFirstEditedPrice = DateTime.fromISO( + firstEditedPriceDate + ).setZone('utc', { + keepLocalTime: true, + }) + // we want to exclude the period with no data if the edited date is smaller than the first data date + firstDate = DateTime.min( + DateTime.max(formattedFirstEditedPrice, firstDataDate[0]), + firstMinuteDataDate + ) + } else if (firstMinuteData) { + firstDate = DateTime.fromObject({ + year: firstMinuteData.year, + month: firstMinuteData.month, + day: firstMinuteData.day, + }).setZone('utc', { + keepLocalTime: true, + }) + } else if (firstEditedPriceDate) { + firstDate = DateTime.max( + DateTime.fromISO(firstEditedPriceDate).setZone('utc', { + keepLocalTime: true, + }), + firstDataDate[0] + ) + } else { + firstDate = today + } - // Get doc to update - const data = await qr.fetchFluidRawDoctype(tp, timeStep, fluidType) - - // If lastItem has a price, skip this day (in order to save perf) - const lastItem = data.data[data.data.length - 1] - if (lastItem && !lastItem.price && priceData) { - data && - data.data.forEach((element: DataloadEntity) => { - element.price = element.load * priceData.price - }) - - // Save updated docs - await cdm.saveDocs(data.data) - } + // Hourly and daily prices + await Promise.all( + tsa.map(async timeStep => { + return new Promise<void>(async resolve => { + let date: DateTime = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + Object.assign(date, firstDate) + try { + do { + // Get price + const priceData = await fluidsPricesService.getPrices( + fluidType, + date + ) + const tp = await getTimePeriod(timeStep, date) + // Get doc to update + const data = await qr.fetchFluidRawDoctype( + tp, + timeStep, + fluidType + ) - // Update date - if (timeStep === TimeStep.HALF_AN_HOUR) { - date = date.plus({ days: 1 }) - } else { - date = date.plus({ month: 1 }).startOf('month') + // If lastItem has a price, skip this day (in order to save perf) + const lastItem = + data && data.data && data.data[data.data.length - 1] + if (lastItem && priceData) { + //if a price has been updated in backoffice re-calculates all price from the firstEditedPriceDate + data && + data.data.forEach((element: DataloadEntity) => { + element.price = element.load * priceData.price + }) + // Save updated docs + await cdm.saveDocs(data.data) + } + // Update date + if (timeStep === TimeStep.HALF_AN_HOUR) { + date = date.plus({ days: 1 }) + } else { + date = date.plus({ month: 1 }).startOf('month') + } + } while (date < today) + } catch (err) { + log('error', `ERROR : ${err} `) + } finally { + resolve() } - } while (date < today) - resolve() + }) }) - }) - - await Promise.all(promises) + ) // Call aggregation method - await aggregatePrices(client, qr, cdm, firstDate, today, fluidType) + await aggregatePrices(qr, cdm, firstDate, today, fluidType) } else log('info', `No data found for fluid ${fluidType}`) } else log('info', 'No fluidesPrices data') } diff --git a/tests/__mocks__/profile.mock.ts b/tests/__mocks__/profile.mock.ts index ea9f83b07..f235a0c21 100644 --- a/tests/__mocks__/profile.mock.ts +++ b/tests/__mocks__/profile.mock.ts @@ -6,7 +6,6 @@ export const profileData: Profile = { _rev: '16-57473da4fc26315247c217083175dfa0', id: '4d9403218ef13e65b2e3a8ad1700bc41', ecogestureHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8b', - fluidPricesHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8a', challengeHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', duelHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', quizHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', -- GitLab