refactor: factor out charging schedule composition code
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 3 Aug 2023 11:26:00 +0000 (13:26 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 3 Aug 2023 11:26:00 +0000 (13:26 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
package.json
pnpm-lock.yaml
src/charging-station/Helpers.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/types/ocpp/1.6/ChargingProfile.ts
ui/web/package.json
ui/web/pnpm-lock.yaml

index a8004c940302da0b017d13c01d3093640db43ed2..3d334e97aa81172d4bb3a890e62dd7047c4a271c 100644 (file)
     "@rollup/plugin-typescript": "^11.1.2",
     "@types/mocha": "^10.0.1",
     "@types/mochawesome": "^6.2.1",
-    "@types/node": "^20.4.5",
+    "@types/node": "^20.4.6",
     "@types/sinon": "^10.0.16",
     "@types/tar": "^6.1.5",
     "@types/ws": "^8.5.5",
     "clinic": "^13.0.0",
     "cross-env": "^7.0.3",
     "eslint": "^8.46.0",
-    "eslint-config-prettier": "^8.9.0",
+    "eslint-config-prettier": "^8.10.0",
     "eslint-import-resolver-typescript": "^3.5.5",
     "eslint-plugin-import": "^2.28.0",
     "eslint-plugin-jsdoc": "^46.4.5",
     "lint-staged": "^13.2.3",
     "mocha": "^10.2.0",
     "mochawesome": "^7.1.3",
-    "prettier": "^3.0.0",
+    "prettier": "^3.0.1",
     "release-it": "^16.1.3",
     "rimraf": "^5.0.1",
     "robohydra": "^0.6.9",
-    "rollup": "^3.27.0",
+    "rollup": "^3.27.1",
     "rollup-plugin-analyzer": "^4.0.0",
     "rollup-plugin-delete": "^2.0.0",
     "semver": "^7.5.4",
index a9f15e80374dc536bee951982d8bf562c9d6f544..d7db88978846d80ad7d8600e178017b1c0beedd4 100644 (file)
@@ -102,13 +102,13 @@ devDependencies:
     version: 5.1.0(release-it@16.1.3)
   '@rollup/plugin-json':
     specifier: ^6.0.0
-    version: 6.0.0(rollup@3.27.0)
+    version: 6.0.0(rollup@3.27.1)
   '@rollup/plugin-terser':
     specifier: ^0.4.3
-    version: 0.4.3(rollup@3.27.0)
+    version: 0.4.3(rollup@3.27.1)
   '@rollup/plugin-typescript':
     specifier: ^11.1.2
-    version: 11.1.2(rollup@3.27.0)(tslib@2.6.1)(typescript@5.1.6)
+    version: 11.1.2(rollup@3.27.1)(tslib@2.6.1)(typescript@5.1.6)
   '@types/mocha':
     specifier: ^10.0.1
     version: 10.0.1
@@ -116,8 +116,8 @@ devDependencies:
     specifier: ^6.2.1
     version: 6.2.1
   '@types/node':
-    specifier: ^20.4.5
-    version: 20.4.5
+    specifier: ^20.4.6
+    version: 20.4.6
   '@types/sinon':
     specifier: ^10.0.16
     version: 10.0.16
@@ -152,8 +152,8 @@ devDependencies:
     specifier: ^8.46.0
     version: 8.46.0
   eslint-config-prettier:
-    specifier: ^8.9.0
-    version: 8.9.0(eslint@8.46.0)
+    specifier: ^8.10.0
+    version: 8.10.0(eslint@8.46.0)
   eslint-import-resolver-typescript:
     specifier: ^3.5.5
     version: 3.5.5(@typescript-eslint/parser@6.2.1)(eslint-plugin-import@2.28.0)(eslint@8.46.0)
@@ -168,7 +168,7 @@ devDependencies:
     version: 16.0.1(eslint@8.46.0)
   eslint-plugin-prettier:
     specifier: ^5.0.0
-    version: 5.0.0(eslint-config-prettier@8.9.0)(eslint@8.46.0)(prettier@3.0.0)
+    version: 5.0.0(eslint-config-prettier@8.10.0)(eslint@8.46.0)(prettier@3.0.1)
   eslint-plugin-tsdoc:
     specifier: ^0.2.17
     version: 0.2.17
@@ -188,8 +188,8 @@ devDependencies:
     specifier: ^7.1.3
     version: 7.1.3(mocha@10.2.0)
   prettier:
-    specifier: ^3.0.0
-    version: 3.0.0
+    specifier: ^3.0.1
+    version: 3.0.1
   release-it:
     specifier: ^16.1.3
     version: 16.1.3
@@ -200,8 +200,8 @@ devDependencies:
     specifier: ^0.6.9
     version: 0.6.9(bufferutil@4.0.7)(utf-8-validate@6.0.3)
   rollup:
-    specifier: ^3.27.0
-    version: 3.27.0
+    specifier: ^3.27.1
+    version: 3.27.1
   rollup-plugin-analyzer:
     specifier: ^4.0.0
     version: 4.0.0
@@ -216,7 +216,7 @@ devDependencies:
     version: 15.2.0
   ts-node:
     specifier: ^10.9.1
-    version: 10.9.1(@types/node@20.4.5)(typescript@5.1.6)
+    version: 10.9.1(@types/node@20.4.6)(typescript@5.1.6)
   typescript:
     specifier: ^5.1.6
     version: 5.1.6
@@ -524,15 +524,15 @@ packages:
       '@commitlint/execute-rule': 17.4.0
       '@commitlint/resolve-extends': 17.6.7
       '@commitlint/types': 17.4.4
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       chalk: 4.1.2
       cosmiconfig: 8.2.0
-      cosmiconfig-typescript-loader: 4.4.0(@types/node@20.4.5)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6)
+      cosmiconfig-typescript-loader: 4.4.0(@types/node@20.4.6)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
       resolve-from: 5.0.0
-      ts-node: 10.9.1(@types/node@20.4.5)(typescript@5.1.6)
+      ts-node: 10.9.1(@types/node@20.4.6)(typescript@5.1.6)
       typescript: 5.1.6
     transitivePeerDependencies:
       - '@swc/core'
@@ -742,7 +742,7 @@ packages:
       '@jest/schemas': 29.6.0
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       '@types/yargs': 17.0.24
       chalk: 4.1.2
     dev: true
@@ -1272,7 +1272,7 @@ packages:
       semver: 7.5.4
     dev: true
 
-  /@rollup/plugin-json@6.0.0(rollup@3.27.0):
+  /@rollup/plugin-json@6.0.0(rollup@3.27.1):
     resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -1281,11 +1281,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.27.0)
-      rollup: 3.27.0
+      '@rollup/pluginutils': 5.0.2(rollup@3.27.1)
+      rollup: 3.27.1
     dev: true
 
-  /@rollup/plugin-terser@0.4.3(rollup@3.27.0):
+  /@rollup/plugin-terser@0.4.3(rollup@3.27.1):
     resolution: {integrity: sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -1294,13 +1294,13 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 3.27.0
+      rollup: 3.27.1
       serialize-javascript: 6.0.1
       smob: 1.4.0
       terser: 5.19.2
     dev: true
 
-  /@rollup/plugin-typescript@11.1.2(rollup@3.27.0)(tslib@2.6.1)(typescript@5.1.6):
+  /@rollup/plugin-typescript@11.1.2(rollup@3.27.1)(tslib@2.6.1)(typescript@5.1.6):
     resolution: {integrity: sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -1313,14 +1313,14 @@ packages:
       tslib:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.27.0)
+      '@rollup/pluginutils': 5.0.2(rollup@3.27.1)
       resolve: 1.22.2
-      rollup: 3.27.0
+      rollup: 3.27.1
       tslib: 2.6.1
       typescript: 5.1.6
     dev: true
 
-  /@rollup/pluginutils@5.0.2(rollup@3.27.0):
+  /@rollup/pluginutils@5.0.2(rollup@3.27.1):
     resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -1332,7 +1332,7 @@ packages:
       '@types/estree': 1.0.1
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 3.27.0
+      rollup: 3.27.1
     dev: true
 
   /@sinclair/typebox@0.27.8:
@@ -1454,7 +1454,7 @@ packages:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
     dev: true
 
   /@types/http-cache-semantics@4.0.1:
@@ -1510,8 +1510,8 @@ packages:
   /@types/node@17.0.45:
     resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
 
-  /@types/node@20.4.5:
-    resolution: {integrity: sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==}
+  /@types/node@20.4.6:
+    resolution: {integrity: sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==}
 
   /@types/normalize-package-data@2.4.1:
     resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -1546,7 +1546,7 @@ packages:
   /@types/tar@6.1.5:
     resolution: {integrity: sha512-qm2I/RlZij5RofuY7vohTpYNaYcrSQlN2MyjucQc7ZweDwaEWkdN/EeNh6e9zjK6uEm6PwjdMXkcj05BxZdX1Q==}
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       minipass: 4.2.8
     dev: true
 
@@ -1565,14 +1565,14 @@ packages:
   /@types/whatwg-url@8.2.2:
     resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       '@types/webidl-conversions': 7.0.0
     dev: false
 
   /@types/ws@8.5.5:
     resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
     dev: true
 
   /@types/yargs-parser@21.0.0:
@@ -3096,7 +3096,7 @@ packages:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
     dev: true
 
-  /cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.5)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6):
+  /cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.6)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6):
     resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==}
     engines: {node: '>=v14.21.3'}
     peerDependencies:
@@ -3105,9 +3105,9 @@ packages:
       ts-node: '>=10'
       typescript: '>=4'
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       cosmiconfig: 8.2.0
-      ts-node: 10.9.1(@types/node@20.4.5)(typescript@5.1.6)
+      ts-node: 10.9.1(@types/node@20.4.6)(typescript@5.1.6)
       typescript: 5.1.6
     dev: true
 
@@ -3968,8 +3968,8 @@ packages:
       source-map: 0.6.1
     dev: true
 
-  /eslint-config-prettier@8.9.0(eslint@8.46.0):
-    resolution: {integrity: sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==}
+  /eslint-config-prettier@8.10.0(eslint@8.46.0):
+    resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==}
     hasBin: true
     peerDependencies:
       eslint: '>=7.0.0'
@@ -4125,7 +4125,7 @@ packages:
       semver: 7.5.4
     dev: true
 
-  /eslint-plugin-prettier@5.0.0(eslint-config-prettier@8.9.0)(eslint@8.46.0)(prettier@3.0.0):
+  /eslint-plugin-prettier@5.0.0(eslint-config-prettier@8.10.0)(eslint@8.46.0)(prettier@3.0.1):
     resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -4140,8 +4140,8 @@ packages:
         optional: true
     dependencies:
       eslint: 8.46.0
-      eslint-config-prettier: 8.9.0(eslint@8.46.0)
-      prettier: 3.0.0
+      eslint-config-prettier: 8.10.0(eslint@8.46.0)
+      prettier: 3.0.1
       prettier-linter-helpers: 1.0.0
       synckit: 0.8.5
     dev: true
@@ -4367,7 +4367,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/expect-utils': 29.6.2
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       jest-get-type: 29.4.3
       jest-matcher-utils: 29.6.2
       jest-message-util: 29.6.2
@@ -5917,7 +5917,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.1
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       chalk: 4.1.2
       ci-info: 3.8.0
       graceful-fs: 4.2.11
@@ -7784,8 +7784,8 @@ packages:
       fast-diff: 1.3.0
     dev: true
 
-  /prettier@3.0.0:
-    resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==}
+  /prettier@3.0.1:
+    resolution: {integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==}
     engines: {node: '>=14'}
     hasBin: true
     dev: true
@@ -8407,8 +8407,8 @@ packages:
       del: 5.1.0
     dev: true
 
-  /rollup@3.27.0:
-    resolution: {integrity: sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==}
+  /rollup@3.27.1:
+    resolution: {integrity: sha512-tXNDFwOkN6C2w5Blj1g6ForKeFw6c1mDu5jxoeDO3/pmYjgt+8yvIFjKzH5FQUq70OKZBkOt0zzv0THXL7vwzQ==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -9360,7 +9360,7 @@ packages:
       code-block-writer: 12.0.0
     dev: false
 
-  /ts-node@10.9.1(@types/node@20.4.5)(typescript@5.1.6):
+  /ts-node@10.9.1(@types/node@20.4.6)(typescript@5.1.6):
     resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
     hasBin: true
     peerDependencies:
@@ -9379,7 +9379,7 @@ packages:
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       acorn: 8.10.0
       acorn-walk: 8.2.0
       arg: 4.1.3
index b722926205f0d5b949ae450a92990abfe86e971b..dd6bcee18fe5ccdf798e18e2df09a70ecebca3ff 100644 (file)
@@ -761,6 +761,15 @@ const getLimitFromChargingProfiles = (
       // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
       chargingSchedule.startSchedule = connectorStatus?.transactionStart;
     }
+    if (
+      !isNullOrUndefined(chargingSchedule?.startSchedule) &&
+      !isDate(chargingSchedule?.startSchedule)
+    ) {
+      logger.warn(
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`,
+      );
+      chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!;
+    }
     if (
       !isNullOrUndefined(chargingSchedule?.startSchedule) &&
       isNullOrUndefined(chargingSchedule?.duration)
@@ -771,12 +780,6 @@ const getLimitFromChargingProfiles = (
       // OCPP specifies that if duration is not defined, it should be infinite
       chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule!);
     }
-    if (!isDate(chargingSchedule?.startSchedule)) {
-      logger.warn(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date object. Trying to convert it to a Date object`,
-      );
-      chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!;
-    }
     if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) {
       continue;
     }
@@ -785,7 +788,6 @@ const getLimitFromChargingProfiles = (
     }
     // Check if the charging profile is active
     if (
-      isValidTime(chargingSchedule?.startSchedule) &&
       isWithinInterval(currentDate, {
         start: chargingSchedule.startSchedule!,
         end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
@@ -891,8 +893,10 @@ export const prepareChargingProfileKind = (
         );
         delete chargingProfile.chargingSchedule.startSchedule;
       }
-      connectorStatus?.transactionStarted &&
-        (chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart);
+      if (connectorStatus?.transactionStarted) {
+        chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
+      }
+      // FIXME: Handle relative charging profile duration
       break;
   }
   return true;
@@ -914,10 +918,30 @@ export const canProceedChargingProfile = (
     );
     return false;
   }
-  const chargingSchedule = chargingProfile.chargingSchedule;
-  if (isNullOrUndefined(chargingSchedule?.startSchedule)) {
+  if (
+    isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) ||
+    isNullOrUndefined(chargingProfile.chargingSchedule.duration)
+  ) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`,
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule or duration defined`,
+    );
+    return false;
+  }
+  if (
+    !isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) &&
+    !isValidTime(chargingProfile.chargingSchedule.startSchedule)
+  ) {
+    logger.error(
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined`,
+    );
+    return false;
+  }
+  if (
+    !isNullOrUndefined(chargingProfile.chargingSchedule.duration) &&
+    !Number.isSafeInteger(chargingProfile.chargingSchedule.duration)
+  ) {
+    logger.error(
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`,
     );
     return false;
   }
@@ -937,6 +961,12 @@ const canProceedRecurringChargingProfile = (
     );
     return false;
   }
+  if (isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule)) {
+    logger.error(
+      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`,
+    );
+    return false;
+  }
   return true;
 };
 
index d24a2d4cd36e2716ab35b375af7b433017e4a9f3..0215414862ae253d0a4bfe7455d04f218063ef27 100644 (file)
@@ -6,19 +6,7 @@ import { URL, fileURLToPath } from 'node:url';
 
 import type { JSONSchemaType } from 'ajv';
 import { Client, type FTPResponse } from 'basic-ftp';
-import {
-  addSeconds,
-  differenceInSeconds,
-  isAfter,
-  isBefore,
-  isDate,
-  isWithinInterval,
-  max,
-  maxTime,
-  min,
-  minTime,
-  secondsToMilliseconds,
-} from 'date-fns';
+import { addSeconds, differenceInSeconds, isDate, maxTime, secondsToMilliseconds } from 'date-fns';
 import { create } from 'tar';
 
 import { OCPP16Constants } from './OCPP16Constants';
@@ -60,9 +48,7 @@ import {
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
   OCPP16ChargingProfilePurposeType,
-  OCPP16ChargingRateUnitType,
   type OCPP16ChargingSchedule,
-  type OCPP16ChargingSchedulePeriod,
   type OCPP16ClearCacheRequest,
   type OCPP16DataTransferRequest,
   type OCPP16DataTransferResponse,
@@ -115,7 +101,6 @@ import {
   isNotEmptyString,
   isNullOrUndefined,
   isUndefined,
-  isValidTime,
   logger,
   sleep,
 } from '../../../utils';
@@ -700,59 +685,63 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       return OCPP16Constants.OCPP_RESPONSE_REJECTED;
     }
     const currentDate = new Date();
-    const interval: Interval = {
+    const compositeScheduleInterval: Interval = {
       start: currentDate,
       end: addSeconds(currentDate, duration),
     };
     // Get charging profiles sorted by connector id then stack level
-    const storedChargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
+    const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
       chargingStation,
       connectorId,
     );
-    const chargingProfiles: OCPP16ChargingProfile[] = [];
-    for (const storedChargingProfile of storedChargingProfiles) {
+    let previousCompositeSchedule: OCPP16ChargingSchedule | undefined;
+    let compositeSchedule: OCPP16ChargingSchedule | undefined;
+    for (const chargingProfile of chargingProfiles) {
       if (
-        isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule) &&
+        isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
         connectorStatus?.transactionStarted
       ) {
         logger.debug(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
-            storedChargingProfile.chargingProfileId
+            chargingProfile.chargingProfileId
           } has no startSchedule defined. Trying to set it to the connector current transaction start date`,
         );
         // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
-        storedChargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
+        chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
       }
       if (
-        !isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule) &&
-        isNullOrUndefined(storedChargingProfile.chargingSchedule?.duration)
+        !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+        !isDate(chargingProfile.chargingSchedule?.startSchedule)
+      ) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
+            chargingProfile.chargingProfileId
+          } startSchedule property is not a Date instance. Trying to convert it to a Date instance`,
+        );
+        chargingProfile.chargingSchedule.startSchedule = convertToDate(
+          chargingProfile.chargingSchedule?.startSchedule,
+        )!;
+      }
+      if (
+        !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
+        isNullOrUndefined(chargingProfile.chargingSchedule?.duration)
       ) {
         logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${
-            storedChargingProfile.chargingProfileId
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
+            chargingProfile.chargingProfileId
           } has no duration defined and will be set to the maximum time allowed`,
         );
         // OCPP specifies that if duration is not defined, it should be infinite
-        storedChargingProfile.chargingSchedule.duration = differenceInSeconds(
+        chargingProfile.chargingSchedule.duration = differenceInSeconds(
           maxTime,
-          storedChargingProfile.chargingSchedule.startSchedule!,
+          chargingProfile.chargingSchedule.startSchedule!,
         );
       }
-      if (!isDate(storedChargingProfile.chargingSchedule?.startSchedule)) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
-            storedChargingProfile.chargingProfileId
-          } startSchedule property is not a Date object. Trying to convert it to a Date object`,
-        );
-        storedChargingProfile.chargingSchedule.startSchedule = convertToDate(
-          storedChargingProfile.chargingSchedule?.startSchedule,
-        )!;
-      }
       if (
         !prepareChargingProfileKind(
           connectorStatus,
-          storedChargingProfile,
-          interval.start as Date,
+          chargingProfile,
+          compositeScheduleInterval.start as Date,
           chargingStation.logPrefix(),
         )
       ) {
@@ -760,199 +749,29 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       }
       if (
         !canProceedChargingProfile(
-          storedChargingProfile,
-          interval.start as Date,
+          chargingProfile,
+          compositeScheduleInterval.start as Date,
           chargingStation.logPrefix(),
         )
       ) {
         continue;
       }
-      // Add active charging profiles into chargingProfiles array
-      if (
-        isValidTime(storedChargingProfile.chargingSchedule?.startSchedule) &&
-        isWithinInterval(storedChargingProfile.chargingSchedule.startSchedule!, interval)
-      ) {
-        if (isEmptyArray(chargingProfiles)) {
-          if (
-            isAfter(
-              addSeconds(
-                storedChargingProfile.chargingSchedule.startSchedule!,
-                storedChargingProfile.chargingSchedule.duration!,
-              ),
-              interval.end,
-            )
-          ) {
-            storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
-              storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
-                (schedulePeriod) =>
-                  isWithinInterval(
-                    addSeconds(
-                      storedChargingProfile.chargingSchedule.startSchedule!,
-                      schedulePeriod.startPeriod,
-                    )!,
-                    interval,
-                  ),
-              );
-            storedChargingProfile.chargingSchedule.duration = differenceInSeconds(
-              interval.end,
-              storedChargingProfile.chargingSchedule.startSchedule!,
-            );
-          }
-          chargingProfiles.push(storedChargingProfile);
-        } else if (isNotEmptyArray(chargingProfiles)) {
-          const chargingProfilesInterval: Interval = {
-            start: min(
-              chargingProfiles.map(
-                (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
-              ),
-            ),
-            end: max(
-              chargingProfiles.map(
-                (chargingProfile) =>
-                  addSeconds(
-                    chargingProfile.chargingSchedule.startSchedule!,
-                    chargingProfile.chargingSchedule.duration!,
-                  ) ?? minTime,
-              ),
-            ),
-          };
-          let addChargingProfile = false;
-          if (
-            isBefore(interval.start, chargingProfilesInterval.start) &&
-            isBefore(
-              storedChargingProfile.chargingSchedule.startSchedule!,
-              chargingProfilesInterval.start,
-            )
-          ) {
-            // Remove charging schedule periods that are after the start of the active profiles interval
-            storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
-              storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
-                (schedulePeriod) =>
-                  isWithinInterval(
-                    addSeconds(
-                      storedChargingProfile.chargingSchedule.startSchedule!,
-                      schedulePeriod.startPeriod,
-                    ),
-                    {
-                      start: interval.start,
-                      end: chargingProfilesInterval.start,
-                    },
-                  ),
-              );
-            addChargingProfile = true;
-          }
-          if (
-            isBefore(chargingProfilesInterval.end, interval.end) &&
-            isAfter(
-              addSeconds(
-                storedChargingProfile.chargingSchedule.startSchedule!,
-                storedChargingProfile.chargingSchedule.duration!,
-              ),
-              chargingProfilesInterval.end,
-            )
-          ) {
-            // Remove charging schedule periods that are before the end of the active profiles interval
-            storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
-              storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
-                (schedulePeriod, index) => {
-                  if (
-                    isWithinInterval(
-                      addSeconds(
-                        storedChargingProfile.chargingSchedule.startSchedule!,
-                        schedulePeriod.startPeriod,
-                      ),
-                      {
-                        start: chargingProfilesInterval.end,
-                        end: interval.end,
-                      },
-                    )
-                  ) {
-                    return true;
-                  }
-                  if (
-                    !isWithinInterval(
-                      addSeconds(
-                        storedChargingProfile.chargingSchedule.startSchedule!,
-                        schedulePeriod.startPeriod,
-                      ),
-                      {
-                        start: chargingProfilesInterval.end,
-                        end: interval.end,
-                      },
-                    ) &&
-                    index <
-                      storedChargingProfile.chargingSchedule.chargingSchedulePeriod.length - 1 &&
-                    isWithinInterval(
-                      addSeconds(
-                        storedChargingProfile.chargingSchedule.startSchedule!,
-                        storedChargingProfile.chargingSchedule.chargingSchedulePeriod[index + 1]
-                          .startPeriod,
-                      ),
-                      {
-                        start: chargingProfilesInterval.end,
-                        end: interval.end,
-                      },
-                    )
-                  ) {
-                    return true;
-                  }
-                  return false;
-                },
-              );
-            addChargingProfile = true;
-          }
-          addChargingProfile && chargingProfiles.push(storedChargingProfile);
-        }
-      }
-    }
-    const compositeScheduleStart: Date = min(
-      chargingProfiles.map(
-        (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
-      ),
-    );
-    const compositeScheduleDuration: number = Math.max(
-      ...chargingProfiles.map((chargingProfile) =>
-        isNaN(chargingProfile.chargingSchedule.duration!)
-          ? -Infinity
-          : chargingProfile.chargingSchedule.duration!,
-      ),
-    );
-    const compositeSchedulePeriods: OCPP16ChargingSchedulePeriod[] = chargingProfiles
-      .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod)
-      .reduce(
-        (accumulator, value) =>
-          accumulator.concat(value).sort((a, b) => a.startPeriod - b.startPeriod),
-        [],
+      compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
+        previousCompositeSchedule,
+        chargingProfile.chargingSchedule,
+        compositeScheduleInterval,
       );
-    const compositeSchedule: OCPP16ChargingSchedule = {
-      startSchedule: compositeScheduleStart,
-      duration: compositeScheduleDuration,
-      chargingRateUnit: chargingProfiles.every(
-        (chargingProfile) =>
-          chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.AMPERE,
-      )
-        ? OCPP16ChargingRateUnitType.AMPERE
-        : chargingProfiles.every(
-            (chargingProfile) =>
-              chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.WATT,
-          )
-        ? OCPP16ChargingRateUnitType.WATT
-        : OCPP16ChargingRateUnitType.AMPERE,
-      chargingSchedulePeriod: compositeSchedulePeriods,
-      minChargeRate: Math.min(
-        ...chargingProfiles.map((chargingProfile) =>
-          isNaN(chargingProfile.chargingSchedule.minChargeRate!)
-            ? Infinity
-            : chargingProfile.chargingSchedule.minChargeRate!,
-        ),
-      ),
-    };
-    return {
-      status: GenericStatus.Accepted,
-      scheduleStart: compositeSchedule.startSchedule!,
-      connectorId,
-      chargingSchedule: compositeSchedule,
-    };
+      previousCompositeSchedule = compositeSchedule;
+    }
+    if (compositeSchedule) {
+      return {
+        status: GenericStatus.Accepted,
+        scheduleStart: compositeSchedule.startSchedule!,
+        connectorId,
+        chargingSchedule: compositeSchedule,
+      };
+    }
+    return OCPP16Constants.OCPP_RESPONSE_REJECTED;
   }
 
   private handleRequestClearChargingProfile(
index 502258c2552c670a5ef0f498a1858118e4f9372c..b510051e2085a3ce0d006a1765f05be7603a3318 100644 (file)
@@ -1,6 +1,14 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
 import type { JSONSchemaType } from 'ajv';
+import {
+  addSeconds,
+  areIntervalsOverlapping,
+  differenceInSeconds,
+  isAfter,
+  isBefore,
+  isWithinInterval,
+} from 'date-fns';
 
 import { OCPP16Constants } from './OCPP16Constants';
 import {
@@ -25,6 +33,7 @@ import {
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
+  type OCPP16ChargingSchedule,
   type OCPP16IncomingRequestCommand,
   type OCPP16MeterValue,
   OCPP16MeterValueMeasurand,
@@ -923,6 +932,52 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return clearedCP;
   };
 
+  public static composeChargingSchedules = (
+    chargingSchedule1: OCPP16ChargingSchedule | undefined,
+    chargingSchedule2: OCPP16ChargingSchedule | undefined,
+    targetInterval: Interval,
+  ): OCPP16ChargingSchedule | undefined => {
+    if (!chargingSchedule1 && !chargingSchedule2) {
+      return undefined;
+    }
+    if (chargingSchedule1 && !chargingSchedule2) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1, targetInterval);
+    }
+    if (!chargingSchedule1 && chargingSchedule2) {
+      return OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2, targetInterval);
+    }
+    const compositeChargingSchedule1: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval);
+    const compositeChargingSchedule2: OCPP16ChargingSchedule | undefined =
+      OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval);
+    const compositeChargingScheduleInterval1: Interval = {
+      start: compositeChargingSchedule1!.startSchedule!,
+      end: addSeconds(
+        compositeChargingSchedule1!.startSchedule!,
+        compositeChargingSchedule1!.duration!,
+      ),
+    };
+    const compositeChargingScheduleInterval2: Interval = {
+      start: compositeChargingSchedule2!.startSchedule!,
+      end: addSeconds(
+        compositeChargingSchedule2!.startSchedule!,
+        compositeChargingSchedule2!.duration!,
+      ),
+    };
+    if (
+      !areIntervalsOverlapping(
+        compositeChargingScheduleInterval1,
+        compositeChargingScheduleInterval2,
+      )
+    ) {
+      return {
+        ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule1!, targetInterval)!,
+        ...OCPP16ServiceUtils.composeChargingSchedule(chargingSchedule2!, targetInterval)!,
+      };
+    }
+    // FIXME: Handle overlapping intervals
+  };
+
   public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -964,6 +1019,69 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     );
   }
 
+  private static composeChargingSchedule = (
+    chargingSchedule: OCPP16ChargingSchedule,
+    targetInterval: Interval,
+  ): OCPP16ChargingSchedule | undefined => {
+    const chargingScheduleInterval: Interval = {
+      start: chargingSchedule.startSchedule!,
+      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
+    };
+    if (areIntervalsOverlapping(chargingScheduleInterval, targetInterval)) {
+      chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod);
+      if (isBefore(chargingScheduleInterval.start, targetInterval.start)) {
+        return {
+          ...chargingSchedule,
+          startSchedule: targetInterval.start as Date,
+          duration: differenceInSeconds(chargingScheduleInterval.end, targetInterval.start as Date),
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(
+            (schedulePeriod, index) => {
+              if (
+                isWithinInterval(
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
+                  targetInterval,
+                )
+              ) {
+                return true;
+              }
+              if (
+                index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
+                !isWithinInterval(
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
+                  targetInterval,
+                ) &&
+                isWithinInterval(
+                  addSeconds(
+                    chargingScheduleInterval.start,
+                    chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod,
+                  ),
+                  targetInterval,
+                )
+              ) {
+                schedulePeriod.startPeriod = 0;
+                return true;
+              }
+              return false;
+            },
+          ),
+        };
+      }
+      if (isAfter(chargingScheduleInterval.end, targetInterval.end)) {
+        return {
+          ...chargingSchedule,
+          duration: differenceInSeconds(targetInterval.end as Date, chargingScheduleInterval.start),
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) =>
+            isWithinInterval(
+              addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
+              targetInterval,
+            ),
+          ),
+        };
+      }
+      return chargingSchedule;
+    }
+  };
+
   private static buildSampledValue(
     sampledValueTemplate: SampledValueTemplate,
     value: number,
index 430f6c2865d39a866f0714b8ba9bd36cec39e008..8a50e895f18b93e9c6cf9df7bbfc1275caee6cd5 100644 (file)
@@ -13,8 +13,8 @@ export interface OCPP16ChargingProfile extends JsonObject {
 }
 
 export interface OCPP16ChargingSchedule extends JsonObject {
-  duration?: number;
   startSchedule?: Date;
+  duration?: number;
   chargingRateUnit: OCPP16ChargingRateUnitType;
   chargingSchedulePeriod: OCPP16ChargingSchedulePeriod[];
   minChargeRate?: number;
index 4f373db619f357bcc71966c07b0400e873c19810..7a98040aa2b959b9f60ce9ad26eba6366cd077bb 100644 (file)
@@ -38,7 +38,7 @@
   "devDependencies": {
     "@tsconfig/node20": "^20.1.0",
     "@types/jsdom": "^21.1.1",
-    "@types/node": "^20.4.5",
+    "@types/node": "^20.4.6",
     "@typescript-eslint/eslint-plugin": "^6.2.1",
     "@typescript-eslint/parser": "^6.2.1",
     "@vitejs/plugin-vue": "^4.2.3",
@@ -55,7 +55,7 @@
     "eslint-plugin-import": "^2.28.0",
     "eslint-plugin-vue": "^9.16.1",
     "jsdom": "^22.1.0",
-    "prettier": "^3.0.0",
+    "prettier": "^3.0.1",
     "rimraf": "^5.0.1",
     "typescript": "~5.1.6",
     "vite": "^4.4.8",
index e62135ce553fa3b9c11b77150a3c64220b398f17..f4ec5a17094495c10ddaaec9c78c36648f7fa1f0 100644 (file)
@@ -29,8 +29,8 @@ devDependencies:
     specifier: ^21.1.1
     version: 21.1.1
   '@types/node':
-    specifier: ^20.4.5
-    version: 20.4.5
+    specifier: ^20.4.6
+    version: 20.4.6
   '@typescript-eslint/eslint-plugin':
     specifier: ^6.2.1
     version: 6.2.1(@typescript-eslint/parser@6.2.1)(eslint@8.46.0)(typescript@5.1.6)
@@ -48,7 +48,7 @@ devDependencies:
     version: 0.34.1(vitest@0.34.1)
   '@vue/eslint-config-prettier':
     specifier: ^8.0.0
-    version: 8.0.0(eslint@8.46.0)(prettier@3.0.0)
+    version: 8.0.0(eslint@8.46.0)(prettier@3.0.1)
   '@vue/eslint-config-typescript':
     specifier: ^11.0.3
     version: 11.0.3(eslint-plugin-vue@9.16.1)(eslint@8.46.0)(typescript@5.1.6)
@@ -80,8 +80,8 @@ devDependencies:
     specifier: ^22.1.0
     version: 22.1.0
   prettier:
-    specifier: ^3.0.0
-    version: 3.0.0
+    specifier: ^3.0.1
+    version: 3.0.1
   rimraf:
     specifier: ^5.0.1
     version: 5.0.1
@@ -90,7 +90,7 @@ devDependencies:
     version: 5.1.6
   vite:
     specifier: ^4.4.8
-    version: 4.4.8(@types/node@20.4.5)
+    version: 4.4.8(@types/node@20.4.6)
   vitest:
     specifier: ^0.34.1
     version: 0.34.1(jsdom@22.1.0)
@@ -785,7 +785,7 @@ packages:
   /@types/jsdom@21.1.1:
     resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==}
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -798,8 +798,8 @@ packages:
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
     dev: true
 
-  /@types/node@20.4.5:
-    resolution: {integrity: sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==}
+  /@types/node@20.4.6:
+    resolution: {integrity: sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==}
     dev: true
 
   /@types/semver@7.5.0:
@@ -1082,7 +1082,7 @@ packages:
       '@babel/core': 7.22.9
       '@babel/plugin-transform-typescript': 7.22.9(@babel/core@7.22.9)
       '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.22.9)
-      vite: 4.4.8(@types/node@20.4.5)
+      vite: 4.4.8(@types/node@20.4.6)
       vue: 3.3.4
     transitivePeerDependencies:
       - supports-color
@@ -1095,7 +1095,7 @@ packages:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.4.8(@types/node@20.4.5)
+      vite: 4.4.8(@types/node@20.4.6)
       vue: 3.3.4
     dev: true
 
@@ -1219,16 +1219,16 @@ packages:
     resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
     dev: false
 
-  /@vue/eslint-config-prettier@8.0.0(eslint@8.46.0)(prettier@3.0.0):
+  /@vue/eslint-config-prettier@8.0.0(eslint@8.46.0)(prettier@3.0.1):
     resolution: {integrity: sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==}
     peerDependencies:
       eslint: '>= 8.0.0'
       prettier: '>= 3.0.0'
     dependencies:
       eslint: 8.46.0
-      eslint-config-prettier: 8.9.0(eslint@8.46.0)
-      eslint-plugin-prettier: 5.0.0(eslint-config-prettier@8.9.0)(eslint@8.46.0)(prettier@3.0.0)
-      prettier: 3.0.0
+      eslint-config-prettier: 8.10.0(eslint@8.46.0)
+      eslint-plugin-prettier: 5.0.0(eslint-config-prettier@8.10.0)(eslint@8.46.0)(prettier@3.0.1)
+      prettier: 3.0.1
     transitivePeerDependencies:
       - '@types/eslint'
     dev: true
@@ -1518,8 +1518,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
     dependencies:
-      caniuse-lite: 1.0.30001518
-      electron-to-chromium: 1.4.480
+      caniuse-lite: 1.0.30001519
+      electron-to-chromium: 1.4.482
       node-releases: 2.0.13
       update-browserslist-db: 1.0.11(browserslist@4.21.10)
     dev: true
@@ -1553,8 +1553,8 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
-  /caniuse-lite@1.0.30001518:
-    resolution: {integrity: sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==}
+  /caniuse-lite@1.0.30001519:
+    resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==}
     dev: true
 
   /chai@4.3.7:
@@ -1828,8 +1828,8 @@ packages:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
     dev: false
 
-  /electron-to-chromium@1.4.480:
-    resolution: {integrity: sha512-IXTgg+bITkQv/FLP9FjX6f9KFCs5hQWeh5uNSKxB9mqYj/JXhHDbu+ekS43LVvbkL3eW6/oZy4+r9Om6lan1Uw==}
+  /electron-to-chromium@1.4.482:
+    resolution: {integrity: sha512-h+UqpfmEr1Qkk0zp7ej/jid7CXoq4m4QzW6wNTb0ELJ/BZCpA4wgUylBIMGCe621tnr4l5VmoHjdoSx2lbnNJA==}
     dev: true
 
   /emoji-regex@8.0.0:
@@ -1976,8 +1976,8 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
-  /eslint-config-prettier@8.9.0(eslint@8.46.0):
-    resolution: {integrity: sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==}
+  /eslint-config-prettier@8.10.0(eslint@8.46.0):
+    resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==}
     hasBin: true
     peerDependencies:
       eslint: '>=7.0.0'
@@ -2090,7 +2090,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-prettier@5.0.0(eslint-config-prettier@8.9.0)(eslint@8.46.0)(prettier@3.0.0):
+  /eslint-plugin-prettier@5.0.0(eslint-config-prettier@8.10.0)(eslint@8.46.0)(prettier@3.0.1):
     resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -2105,8 +2105,8 @@ packages:
         optional: true
     dependencies:
       eslint: 8.46.0
-      eslint-config-prettier: 8.9.0(eslint@8.46.0)
-      prettier: 3.0.0
+      eslint-config-prettier: 8.10.0(eslint@8.46.0)
+      prettier: 3.0.1
       prettier-linter-helpers: 1.0.0
       synckit: 0.8.5
     dev: true
@@ -3443,8 +3443,8 @@ packages:
       fast-diff: 1.3.0
     dev: true
 
-  /prettier@3.0.0:
-    resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==}
+  /prettier@3.0.1:
+    resolution: {integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==}
     engines: {node: '>=14'}
     hasBin: true
     dev: true
@@ -3539,8 +3539,8 @@ packages:
       glob: 10.3.3
     dev: true
 
-  /rollup@3.27.0:
-    resolution: {integrity: sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==}
+  /rollup@3.27.1:
+    resolution: {integrity: sha512-tXNDFwOkN6C2w5Blj1g6ForKeFw6c1mDu5jxoeDO3/pmYjgt+8yvIFjKzH5FQUq70OKZBkOt0zzv0THXL7vwzQ==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -3780,8 +3780,8 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /strip-literal@1.0.1:
-    resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
+  /strip-literal@1.3.0:
+    resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
     dependencies:
       acorn: 8.10.0
     dev: true
@@ -4053,7 +4053,7 @@ packages:
       convert-source-map: 1.9.0
     dev: true
 
-  /vite-node@0.34.1(@types/node@20.4.5):
+  /vite-node@0.34.1(@types/node@20.4.6):
     resolution: {integrity: sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
@@ -4063,7 +4063,7 @@ packages:
       mlly: 1.4.0
       pathe: 1.1.1
       picocolors: 1.0.0
-      vite: 4.4.8(@types/node@20.4.5)
+      vite: 4.4.8(@types/node@20.4.6)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -4075,7 +4075,7 @@ packages:
       - terser
     dev: true
 
-  /vite@4.4.8(@types/node@20.4.5):
+  /vite@4.4.8(@types/node@20.4.6):
     resolution: {integrity: sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
@@ -4103,10 +4103,10 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       esbuild: 0.18.17
       postcss: 8.4.27
-      rollup: 3.27.0
+      rollup: 3.27.1
     optionalDependencies:
       fsevents: 2.3.2
     dev: true
@@ -4144,7 +4144,7 @@ packages:
     dependencies:
       '@types/chai': 4.3.5
       '@types/chai-subset': 1.3.3
-      '@types/node': 20.4.5
+      '@types/node': 20.4.6
       '@vitest/expect': 0.34.1
       '@vitest/runner': 0.34.1
       '@vitest/snapshot': 0.34.1
@@ -4161,11 +4161,11 @@ packages:
       pathe: 1.1.1
       picocolors: 1.0.0
       std-env: 3.3.3
-      strip-literal: 1.0.1
+      strip-literal: 1.3.0
       tinybench: 2.5.0
       tinypool: 0.7.0
-      vite: 4.4.8(@types/node@20.4.5)
-      vite-node: 0.34.1(@types/node@20.4.5)
+      vite: 4.4.8(@types/node@20.4.6)
+      vite-node: 0.34.1(@types/node@20.4.6)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less