@remit/backend 0.0.137 → 0.0.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.137",
3
+ "version": "0.0.139",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -13,6 +13,7 @@ import {
13
13
  type CalendarInstance,
14
14
  type CalendarWindow,
15
15
  deleteCalendarObject,
16
+ followingUidOf,
16
17
  listBusySpans,
17
18
  listCalendarInstances,
18
19
  listObjectInstances,
@@ -445,7 +446,10 @@ export const updateCalendarEventFor = async (
445
446
  {
446
447
  scope: request.scope,
447
448
  recurrenceId: request.recurrenceId,
448
- followingUid: `${deps.newId()}@reader.remit`,
449
+ followingUid: followingUidOf(
450
+ object.icalUid,
451
+ `${deps.newId()}@reader.remit`,
452
+ ),
449
453
  },
450
454
  patch,
451
455
  );
@@ -1,20 +1,27 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { after, before, describe, test } from "node:test";
3
+ import {
4
+ acceptCalendarSuggestion,
5
+ reopenCalendarSuggestion,
6
+ } from "@remit/calendar-service";
3
7
  import type {
4
8
  CalendarSuggestionItem,
5
9
  CreateFilterInput,
6
10
  FilterItem,
7
11
  ICalendarSuggestionRepository,
12
+ ICalendarUnitOfWork,
8
13
  MessageData,
9
14
  PutCalendarSuggestionInput,
10
15
  SettleCalendarSuggestionInput,
11
16
  } from "@remit/data-ports";
12
17
  import {
18
+ CalendarAnswerOvertaken,
13
19
  CalendarInviteAnswer,
14
20
  CalendarInviteMethod,
15
21
  CalendarSuggestionSource,
16
22
  CalendarSuggestionState,
17
23
  FilterState,
24
+ RecurrenceScope,
18
25
  } from "@remit/domain-enums";
19
26
  import type { APIGatewayProxyEvent } from "aws-lambda";
20
27
  import type { Context } from "openapi-backend";
@@ -24,6 +31,11 @@ import {
24
31
  type RemitClient,
25
32
  setClient,
26
33
  } from "../service/data-client.js";
34
+ import { calendarDepsOf } from "./calendar.js";
35
+ import {
36
+ calendarEventDepsOf,
37
+ updateCalendarEventFor,
38
+ } from "./calendar-event.js";
27
39
  import { createCalendarSqliteClient } from "./calendar-sqlite-fixture.js";
28
40
  import {
29
41
  assertSettleable,
@@ -135,7 +147,10 @@ const muteRule = (sender: string): FilterItem =>
135
147
 
136
148
  describe("toCalendarSuggestionResponse", () => {
137
149
  test("keeps the raw invitation bytes on the server", async () => {
138
- const response = toCalendarSuggestionResponse(suggestion());
150
+ const response = toCalendarSuggestionResponse(
151
+ suggestion(),
152
+ CalendarAnswerOvertaken.None,
153
+ );
139
154
 
140
155
  assert.equal("icalData" in response, false);
141
156
  assert.equal(response.summary, "Quarterly review");
@@ -312,6 +327,8 @@ const dismissSuggestion =
312
327
  CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_dismissCalendarSuggestion as Handler;
313
328
  const listMessageSuggestions =
314
329
  MessageCalendarSuggestionOperations.MessageCalendarSuggestionOperations_listMessageCalendarSuggestions as Handler;
330
+ const reopenSuggestion =
331
+ CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_reopenCalendarSuggestion as Handler;
315
332
 
316
333
  interface Card {
317
334
  suggestionId: string;
@@ -764,3 +781,382 @@ describe("POST /calendar-suggestions/{suggestionId}/accept with corrections", ()
764
781
  );
765
782
  });
766
783
  });
784
+
785
+ describe("POST /calendar-suggestions/{suggestionId}/reopen", () => {
786
+ const answeredInto = async (
787
+ accountConfigId: string,
788
+ messageId: string,
789
+ answer: (typeof CalendarInviteAnswer)[keyof typeof CalendarInviteAnswer],
790
+ ): Promise<{ calendarId: string; card: CalendarSuggestionItem }> => {
791
+ const calendar = await client.calendarCollection.create({
792
+ accountConfigId,
793
+ urlSegment: "default",
794
+ displayName: "Calendar",
795
+ });
796
+ const accepted = await acceptCalendarSuggestion(client.calendarUnitOfWork, {
797
+ accountConfigId,
798
+ calendarId: calendar.calendarId,
799
+ suggestion: await putSuggestion(accountConfigId, messageId),
800
+ attendee: "user@example.test",
801
+ answer,
802
+ });
803
+ assert.ok(accepted.ok);
804
+ return { calendarId: calendar.calendarId, card: accepted.value.suggestion };
805
+ };
806
+
807
+ for (const answer of [
808
+ CalendarInviteAnswer.Accepted,
809
+ CalendarInviteAnswer.Tentative,
810
+ ]) {
811
+ test(`takes a ${answer} answer back and removes the event it wrote`, async () => {
812
+ const { accountConfigId, event } = anAccount();
813
+ const { calendarId, card } = await answeredInto(
814
+ accountConfigId,
815
+ `msg-reopen-${answer}`,
816
+ answer,
817
+ );
818
+ assert.equal(
819
+ (await client.calendarObject.listByCalendar(calendarId)).length,
820
+ 1,
821
+ );
822
+
823
+ const reopened = (await reopenSuggestion(
824
+ contextOf({ params: { suggestionId: card.suggestionId } }),
825
+ event,
826
+ )) as unknown as CalendarSuggestionItem;
827
+
828
+ assert.equal(reopened.state, CalendarSuggestionState.Pending);
829
+ assert.equal(reopened.acceptedCalendarObjectId, "");
830
+ assert.deepEqual(
831
+ await client.calendarObject.listByCalendar(calendarId),
832
+ [],
833
+ );
834
+ const stored = await client.calendarSuggestion.get(
835
+ accountConfigId,
836
+ card.suggestionId,
837
+ );
838
+ assert.equal(stored.state, CalendarSuggestionState.Pending);
839
+ });
840
+ }
841
+
842
+ for (const state of [
843
+ CalendarSuggestionState.Declined,
844
+ CalendarSuggestionState.Dismissed,
845
+ ]) {
846
+ test(`returns a ${state} card to Pending`, async () => {
847
+ const { accountConfigId, event } = anAccount();
848
+ const card = await putSuggestion(accountConfigId, `msg-reopen-${state}`);
849
+ await client.calendarSuggestion.settle(
850
+ accountConfigId,
851
+ card.suggestionId,
852
+ { state, acceptedCalendarObjectId: "" },
853
+ );
854
+
855
+ const reopened = (await reopenSuggestion(
856
+ contextOf({ params: { suggestionId: card.suggestionId } }),
857
+ event,
858
+ )) as unknown as Card;
859
+
860
+ assert.equal(reopened.state, CalendarSuggestionState.Pending);
861
+ });
862
+ }
863
+
864
+ test("refuses a superseded card and leaves it as it is", async () => {
865
+ const { accountConfigId, event } = anAccount();
866
+ const card = await putSuggestion(accountConfigId, "msg-reopen-superseded");
867
+ await client.calendarSuggestion.supersedeIfPending(
868
+ accountConfigId,
869
+ card.suggestionId,
870
+ "msg-reopen-newer",
871
+ );
872
+
873
+ await assert.rejects(
874
+ () =>
875
+ reopenSuggestion(
876
+ contextOf({ params: { suggestionId: card.suggestionId } }),
877
+ event,
878
+ ),
879
+ (error: unknown) => (error as { statusCode?: number }).statusCode === 400,
880
+ );
881
+
882
+ const untouched = await client.calendarSuggestion.get(
883
+ accountConfigId,
884
+ card.suggestionId,
885
+ );
886
+ assert.equal(untouched.state, CalendarSuggestionState.Superseded);
887
+ });
888
+
889
+ test("refuses a cancellation, whose answer it cannot undo", async () => {
890
+ const { accountConfigId, event } = anAccount();
891
+ const card = await putSuggestion(
892
+ accountConfigId,
893
+ "msg-reopen-cancel",
894
+ CalendarInviteMethod.Cancel,
895
+ );
896
+ await client.calendarSuggestion.settle(accountConfigId, card.suggestionId, {
897
+ state: CalendarSuggestionState.Dismissed,
898
+ acceptedCalendarObjectId: "",
899
+ });
900
+
901
+ await assert.rejects(
902
+ () =>
903
+ reopenSuggestion(
904
+ contextOf({ params: { suggestionId: card.suggestionId } }),
905
+ event,
906
+ ),
907
+ (error: unknown) => (error as { statusCode?: number }).statusCode === 400,
908
+ );
909
+ });
910
+
911
+ test("answers not-found for a card on another account", async () => {
912
+ const stranger = anAccount();
913
+ const { card } = await answeredInto(
914
+ stranger.accountConfigId,
915
+ "msg-reopen-stranger",
916
+ CalendarInviteAnswer.Accepted,
917
+ );
918
+ const { event } = anAccount();
919
+
920
+ await assert.rejects(
921
+ () =>
922
+ reopenSuggestion(
923
+ contextOf({ params: { suggestionId: card.suggestionId } }),
924
+ event,
925
+ ),
926
+ (error: unknown) => (error as { statusCode?: number }).statusCode === 404,
927
+ );
928
+ const untouched = await client.calendarSuggestion.get(
929
+ stranger.accountConfigId,
930
+ card.suggestionId,
931
+ );
932
+ assert.equal(untouched.state, CalendarSuggestionState.Accepted);
933
+ });
934
+ });
935
+
936
+ describe("POST /calendar-suggestions/{suggestionId}/reopen, against what else holds the event", () => {
937
+ const recurring = (sequence: number): string =>
938
+ INVITATION.replace(
939
+ "SUMMARY:Quarterly review",
940
+ `SEQUENCE:${sequence}\r\nRRULE:FREQ=WEEKLY;COUNT=4\r\nSUMMARY:Quarterly review`,
941
+ );
942
+
943
+ const putInvite = (
944
+ accountConfigId: string,
945
+ messageId: string,
946
+ sequence: number,
947
+ ): Promise<CalendarSuggestionItem> =>
948
+ client.calendarSuggestion.put({
949
+ accountConfigId,
950
+ messageId,
951
+ bodyPartId: "part-1",
952
+ icalUid: "invite@example.test",
953
+ sequence,
954
+ method: CalendarInviteMethod.Request,
955
+ source: CalendarSuggestionSource.IcalendarPart,
956
+ summary: "Quarterly review",
957
+ dtStart: "2026-09-01T10:00:00+02:00",
958
+ dtEnd: "2026-09-01T11:00:00+02:00",
959
+ allDay: false,
960
+ location: "Room 4",
961
+ organizer: "organizer@example.test",
962
+ zoneCertainty: "Explicit",
963
+ icalData: recurring(sequence),
964
+ } as PutCalendarSuggestionInput);
965
+
966
+ const acceptInto = async (
967
+ accountConfigId: string,
968
+ calendarId: string,
969
+ suggestion: CalendarSuggestionItem,
970
+ ): Promise<CalendarSuggestionItem> => {
971
+ const accepted = await acceptCalendarSuggestion(client.calendarUnitOfWork, {
972
+ accountConfigId,
973
+ calendarId,
974
+ suggestion,
975
+ attendee: "user@example.test",
976
+ answer: CalendarInviteAnswer.Accepted,
977
+ });
978
+ assert.ok(accepted.ok);
979
+ return accepted.value.suggestion;
980
+ };
981
+
982
+ const calendarOf = (accountConfigId: string) =>
983
+ client.calendarCollection.create({
984
+ accountConfigId,
985
+ urlSegment: "default",
986
+ displayName: "Calendar",
987
+ });
988
+
989
+ test("refuses a request's answer once its cancellation was answered into the event, and says so on the card", async () => {
990
+ const { accountConfigId, event } = anAccount();
991
+ const calendar = await calendarOf(accountConfigId);
992
+ const first = await acceptInto(
993
+ accountConfigId,
994
+ calendar.calendarId,
995
+ await putInvite(accountConfigId, "msg-overtaken-0", 0),
996
+ );
997
+ await acceptInto(
998
+ accountConfigId,
999
+ calendar.calendarId,
1000
+ await putSuggestion(
1001
+ accountConfigId,
1002
+ "msg-overtaken-cancel",
1003
+ CalendarInviteMethod.Cancel,
1004
+ ),
1005
+ );
1006
+
1007
+ await assert.rejects(
1008
+ () =>
1009
+ reopenSuggestion(
1010
+ contextOf({ params: { suggestionId: first.suggestionId } }),
1011
+ event,
1012
+ ),
1013
+ (error: unknown) => (error as { statusCode?: number }).statusCode === 400,
1014
+ );
1015
+
1016
+ assert.equal(
1017
+ (await client.calendarObject.listByCalendar(calendar.calendarId)).length,
1018
+ 1,
1019
+ );
1020
+ const listed = (await listMessageSuggestions(
1021
+ contextOf({ params: { messageId: "msg-overtaken-0" } }),
1022
+ event,
1023
+ )) as unknown as { items: { answerOvertakenBy: string }[] };
1024
+ assert.deepEqual(
1025
+ listed.items.map((item) => item.answerOvertakenBy),
1026
+ [CalendarAnswerOvertaken.Cancellation],
1027
+ );
1028
+ });
1029
+
1030
+ test("takes back both halves of a this-and-following split, and a re-add writes one event", async () => {
1031
+ const { accountConfigId, event } = anAccount();
1032
+ const calendar = await calendarOf(accountConfigId);
1033
+ const card = await acceptInto(
1034
+ accountConfigId,
1035
+ calendar.calendarId,
1036
+ await putInvite(accountConfigId, "msg-split", 0),
1037
+ );
1038
+ const split = await updateCalendarEventFor(
1039
+ calendarEventDepsOf(calendarDepsOf(client)),
1040
+ accountConfigId,
1041
+ {
1042
+ calendarId: calendar.calendarId,
1043
+ calendarObjectId: card.acceptedCalendarObjectId,
1044
+ scope: RecurrenceScope.Following,
1045
+ recurrenceId: "2026-09-15T08:00:00Z",
1046
+ ifMatch: undefined,
1047
+ },
1048
+ { summary: "Quarterly review (moved)" },
1049
+ );
1050
+ assert.ok(split.ok, JSON.stringify(split));
1051
+ assert.equal(
1052
+ (await client.calendarObject.listByCalendar(calendar.calendarId)).length,
1053
+ 2,
1054
+ );
1055
+
1056
+ const reopened = (await reopenSuggestion(
1057
+ contextOf({ params: { suggestionId: card.suggestionId } }),
1058
+ event,
1059
+ )) as unknown as CalendarSuggestionItem;
1060
+
1061
+ assert.equal(reopened.state, CalendarSuggestionState.Pending);
1062
+ assert.deepEqual(
1063
+ await client.calendarObject.listByCalendar(calendar.calendarId),
1064
+ [],
1065
+ );
1066
+ await acceptInto(accountConfigId, calendar.calendarId, {
1067
+ ...card,
1068
+ ...reopened,
1069
+ });
1070
+ assert.equal(
1071
+ (await client.calendarObject.listByCalendar(calendar.calendarId)).length,
1072
+ 1,
1073
+ );
1074
+ });
1075
+
1076
+ test("keeps the sender rule a mute wrote", async () => {
1077
+ const { accountConfigId, event } = anAccount();
1078
+ const card = await putSuggestion(accountConfigId, "msg-mute-reopen");
1079
+ await seedMessageFrom("msg-mute-reopen", "organizer@example.test");
1080
+ await dismissSuggestion(
1081
+ contextOf({
1082
+ params: { suggestionId: card.suggestionId },
1083
+ requestBody: { muteSender: true },
1084
+ }),
1085
+ event,
1086
+ );
1087
+
1088
+ const reopened = (await reopenSuggestion(
1089
+ contextOf({ params: { suggestionId: card.suggestionId } }),
1090
+ event,
1091
+ )) as unknown as Card;
1092
+
1093
+ assert.equal(reopened.state, CalendarSuggestionState.Pending);
1094
+ const rules = await client.filter.listByAccountAndState(
1095
+ accountConfigId,
1096
+ FilterState.Active,
1097
+ );
1098
+ assert.deepEqual(
1099
+ rules.map((rule) => rule.literalClauses),
1100
+ [[{ field: "From", value: "organizer@example.test" }]],
1101
+ );
1102
+ });
1103
+
1104
+ test("a delete that fails part-way leaves the event and the answer as they were", async () => {
1105
+ const { accountConfigId } = anAccount();
1106
+ const calendar = await calendarOf(accountConfigId);
1107
+ const card = await acceptInto(
1108
+ accountConfigId,
1109
+ calendar.calendarId,
1110
+ await putInvite(accountConfigId, "msg-rollback", 0),
1111
+ );
1112
+ const failing: ICalendarUnitOfWork = {
1113
+ transaction: (fn) =>
1114
+ client.calendarUnitOfWork.transaction((repos) =>
1115
+ fn({
1116
+ ...repos,
1117
+ calendarCollection: Object.assign(
1118
+ Object.create(repos.calendarCollection),
1119
+ {
1120
+ bumpSyncSequence: async () => {
1121
+ throw new Error("disk full");
1122
+ },
1123
+ },
1124
+ ),
1125
+ }),
1126
+ ),
1127
+ };
1128
+
1129
+ await assert.rejects(
1130
+ () =>
1131
+ reopenCalendarSuggestion(failing, {
1132
+ accountConfigId,
1133
+ suggestion: card,
1134
+ }),
1135
+ /disk full/,
1136
+ );
1137
+
1138
+ const object = await client.calendarObject.find(
1139
+ calendar.calendarId,
1140
+ card.acceptedCalendarObjectId,
1141
+ );
1142
+ assert.ok(object, "the event is still on the calendar");
1143
+ assert.ok(
1144
+ (
1145
+ await client.calendarEventIndex.listForObject(
1146
+ calendar.calendarId,
1147
+ card.acceptedCalendarObjectId,
1148
+ )
1149
+ ).length > 0,
1150
+ "and so are its occurrences",
1151
+ );
1152
+ const untouched = await client.calendarSuggestion.get(
1153
+ accountConfigId,
1154
+ card.suggestionId,
1155
+ );
1156
+ assert.equal(untouched.state, CalendarSuggestionState.Accepted);
1157
+ assert.equal(
1158
+ untouched.acceptedCalendarObjectId,
1159
+ card.acceptedCalendarObjectId,
1160
+ );
1161
+ });
1162
+ });
@@ -5,7 +5,9 @@ import type {
5
5
  } from "@remit/api-openapi-types";
6
6
  import {
7
7
  acceptCalendarSuggestion,
8
+ answersOvertakenBy,
8
9
  correctCalendarSuggestion,
10
+ reopenCalendarSuggestion,
9
11
  } from "@remit/calendar-service";
10
12
  import {
11
13
  type CalendarSuggestionItem,
@@ -44,28 +46,54 @@ import { pickEventUpdate } from "./calendar-event.js";
44
46
  */
45
47
  export const toCalendarSuggestionResponse = (
46
48
  item: CalendarSuggestionItem,
49
+ answerOvertakenBy: CalendarSuggestionResponse["answerOvertakenBy"],
47
50
  ): CalendarSuggestionResponse => {
48
51
  const { icalData: _icalData, ...response } = item;
49
- return { ...response, supersededByThreadId: "" };
52
+ return { ...response, supersededByThreadId: "", answerOvertakenBy };
50
53
  };
51
54
 
52
- export const toCalendarSuggestionResponses = (
53
- threads: Pick<IThreadMessageRepository, "findByMessageId">,
55
+ export const toCalendarSuggestionResponses = async (
56
+ deps: {
57
+ threadMessage: Pick<IThreadMessageRepository, "findByMessageId">;
58
+ calendarSuggestion: Pick<
59
+ ICalendarSuggestionRepository,
60
+ "listByAcceptedCalendarObjects"
61
+ >;
62
+ },
54
63
  accountConfigId: string,
55
64
  items: CalendarSuggestionItem[],
56
65
  ): Promise<CalendarSuggestionResponse[]> =>
57
66
  Promise.all(
58
- items.map(async (item) => {
59
- const response = toCalendarSuggestionResponse(item);
60
- if (item.supersededByMessageId === "") return response;
61
- const newer = await threads.findByMessageId(
67
+ (
68
+ await answersOvertakenBy(deps.calendarSuggestion, accountConfigId, items)
69
+ ).map(async ({ suggestion, answerOvertakenBy }) => {
70
+ const response = toCalendarSuggestionResponse(
71
+ suggestion,
72
+ answerOvertakenBy,
73
+ );
74
+ if (suggestion.supersededByMessageId === "") return response;
75
+ const newer = await deps.threadMessage.findByMessageId(
62
76
  accountConfigId,
63
- item.supersededByMessageId,
77
+ suggestion.supersededByMessageId,
64
78
  );
65
79
  return { ...response, supersededByThreadId: newer?.threadId ?? "" };
66
80
  }),
67
81
  );
68
82
 
83
+ const respondOne = async (
84
+ client: RemitClient,
85
+ accountConfigId: string,
86
+ item: CalendarSuggestionItem,
87
+ ): Promise<CalendarSuggestionResponse> => {
88
+ const [response] = await toCalendarSuggestionResponses(
89
+ client,
90
+ accountConfigId,
91
+ [item],
92
+ );
93
+ if (!response) throw new Error("unreachable: one item in, one response out");
94
+ return response;
95
+ };
96
+
69
97
  /**
70
98
  * The states a person can move a pending card into. `Superseded` is the
71
99
  * producer's alone — a revision retires a card, a person never does — and the
@@ -196,7 +224,7 @@ export const CalendarSuggestionOperations: Record<
196
224
 
197
225
  return {
198
226
  items: await toCalendarSuggestionResponses(
199
- client.threadMessage,
227
+ client,
200
228
  accountConfigId,
201
229
  page.items,
202
230
  ),
@@ -225,7 +253,7 @@ export const MessageCalendarSuggestionOperations: Record<
225
253
 
226
254
  return {
227
255
  items: await toCalendarSuggestionResponses(
228
- client.threadMessage,
256
+ client,
229
257
  accountConfigId,
230
258
  items,
231
259
  ),
@@ -312,7 +340,7 @@ export const CalendarSuggestionActionOperations: Record<
312
340
  throw new BadRequestError(accepted.error.message);
313
341
  }
314
342
 
315
- return toCalendarSuggestionResponse(accepted.value.suggestion);
343
+ return respondOne(client, accountConfigId, accepted.value.suggestion);
316
344
  },
317
345
 
318
346
  CalendarSuggestionActionOperations_declineCalendarSuggestion: async (
@@ -333,7 +361,7 @@ export const CalendarSuggestionActionOperations: Record<
333
361
  CalendarSuggestionState.Declined,
334
362
  );
335
363
 
336
- return toCalendarSuggestionResponse(declined);
364
+ return respondOne(client, accountConfigId, declined);
337
365
  },
338
366
 
339
367
  CalendarSuggestionActionOperations_dismissCalendarSuggestion: async (
@@ -360,6 +388,32 @@ export const CalendarSuggestionActionOperations: Record<
360
388
  await muteSender(client, accountConfigId, dismissed.messageId);
361
389
  }
362
390
 
363
- return toCalendarSuggestionResponse(dismissed);
391
+ return respondOne(client, accountConfigId, dismissed);
392
+ },
393
+
394
+ CalendarSuggestionActionOperations_reopenCalendarSuggestion: async (
395
+ context,
396
+ ...args: unknown[]
397
+ ) => {
398
+ const event = args[0] as APIGatewayProxyEvent;
399
+ const accountConfigId = getAccountConfigIdFromEvent(event);
400
+ const { suggestionId } = context.request.params as {
401
+ suggestionId: string;
402
+ };
403
+
404
+ const client = await getClient();
405
+ const suggestion = await client.calendarSuggestion.get(
406
+ accountConfigId,
407
+ suggestionId,
408
+ );
409
+ const reopened = await reopenCalendarSuggestion(client.calendarUnitOfWork, {
410
+ accountConfigId,
411
+ suggestion,
412
+ });
413
+ if (!reopened.ok) {
414
+ throw new BadRequestError(reopened.error.message);
415
+ }
416
+
417
+ return respondOne(client, accountConfigId, reopened.value);
364
418
  },
365
419
  };