@burgan-tech/morph-touch-runtime 0.0.7 → 0.0.9

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.
Files changed (68) hide show
  1. package/burgan-tech-morph-touch-runtime-0.0.9.tgz +0 -0
  2. package/morph-touch/Functions/get-absence-entry.1.0.0.json +1 -1
  3. package/morph-touch/Functions/get-advisor-stats.1.0.0.json +1 -1
  4. package/morph-touch/Functions/get-available-slots.1.0.0.json +1 -1
  5. package/morph-touch/Functions/get-chat-rooms.http +4 -4
  6. package/morph-touch/Functions/get-rezervations.1.0.0.json +1 -1
  7. package/morph-touch/Functions/src/GetAbsenceEntryMapping.csx +18 -4
  8. package/morph-touch/Functions/src/GetAdvisorStatsMapping.csx +22 -8
  9. package/morph-touch/Functions/src/GetAvailableSlotsMapping.csx +22 -14
  10. package/morph-touch/Functions/src/GetRezervationsMapping.csx +19 -2
  11. package/morph-touch/Functions/src/SendCancelNotificationMapping.csx +27 -2
  12. package/morph-touch/Tasks/check-existing-permanent-chat-room.1.0.0.json +1 -1
  13. package/morph-touch/Tasks/login-for-chat.1.0.0.json +27 -0
  14. package/morph-touch/Tasks/register-for-chat.1.0.0.json +27 -0
  15. package/morph-touch/Tasks/start-register-matrix-user.1.0.0.json +22 -0
  16. package/morph-touch/Workflows/chat-room.json +196 -14
  17. package/morph-touch/Workflows/notification-sender.json +2 -2
  18. package/morph-touch/Workflows/register-matrix-user.json +128 -0
  19. package/morph-touch/Workflows/rezervation-start.json +28 -2
  20. package/morph-touch/Workflows/rezervation-transfer.json +34 -8
  21. package/morph-touch/Workflows/rezervation-update.json +5 -5
  22. package/morph-touch/Workflows/rezervation.json +135 -45
  23. package/morph-touch/Workflows/src/AlwaysTrueRule.csx +0 -3
  24. package/morph-touch/Workflows/src/CanStartRezervationTimerMapping.csx +18 -3
  25. package/morph-touch/Workflows/src/ChatRoomRemoveMapping.csx +10 -1
  26. package/morph-touch/Workflows/src/ChatRoomTransferInviteMapping.csx +10 -1
  27. package/morph-touch/Workflows/src/ChatRoomTransferRemoveMapping.csx +9 -1
  28. package/morph-touch/Workflows/src/ChatRoomUpdateMapping.csx +10 -1
  29. package/morph-touch/Workflows/src/CheckDuplicateRezervationMapping.csx +17 -3
  30. package/morph-touch/Workflows/src/CheckExistingPermanentChatRoomMapping.csx +4 -7
  31. package/morph-touch/Workflows/src/CheckRandevuTimeMapping.csx +16 -2
  32. package/morph-touch/Workflows/src/CreateChatRoomForRezervationMapping.csx +12 -2
  33. package/morph-touch/Workflows/src/CreatePermanentChatRoomMapping.csx +11 -1
  34. package/morph-touch/Workflows/src/EnrichRezervationsForTransferMapping.csx +18 -4
  35. package/morph-touch/Workflows/src/FetchRoomMessagesForSummaryMapping.csx +10 -2
  36. package/morph-touch/Workflows/src/InviteAdvisorForRandevuUpdateMapping.csx +10 -1
  37. package/morph-touch/Workflows/src/InviteNewAdvisorToRezervationRoomMapping.csx +11 -2
  38. package/morph-touch/Workflows/src/InviteNewParticipantToRandevuRoomMapping.csx +10 -1
  39. package/morph-touch/Workflows/src/JoinChatRoomForAddParticipantMapping.csx +5 -1
  40. package/morph-touch/Workflows/src/JoinChatRoomForRandevuStartMapping.csx +5 -1
  41. package/morph-touch/Workflows/src/JoinChatRoomForRezervationMapping.csx +4 -0
  42. package/morph-touch/Workflows/src/JoinMatrixRoomMapping.csx +11 -1
  43. package/morph-touch/Workflows/src/JoinUserToRoomMapping.csx +6 -0
  44. package/morph-touch/Workflows/src/LoginForAddParticipantChatMapping.csx +35 -6
  45. package/morph-touch/Workflows/src/LoginForChatRoomMapping.csx +121 -0
  46. package/morph-touch/Workflows/src/LoginForJoinUserToRoomMapping.csx +142 -0
  47. package/morph-touch/Workflows/src/LoginForRandevuStartChatMapping.csx +35 -10
  48. package/morph-touch/Workflows/src/LoginForTransferChatMapping.csx +153 -0
  49. package/morph-touch/Workflows/src/RegisterForAddParticipantChatMapping.csx +74 -0
  50. package/morph-touch/Workflows/src/RegisterForChatRoomMapping.csx +52 -0
  51. package/morph-touch/Workflows/src/RegisterForJoinUserToRoomMapping.csx +63 -0
  52. package/morph-touch/Workflows/src/RegisterForRandevuStartChatMapping.csx +56 -0
  53. package/morph-touch/Workflows/src/RegisterForTransferChatMapping.csx +90 -0
  54. package/morph-touch/Workflows/src/RegisterMatrixUserMapping.csx +73 -0
  55. package/morph-touch/Workflows/src/SendPushNotificationMapping.csx +25 -4
  56. package/morph-touch/Workflows/src/SendSmsNotificationMapping.csx +25 -4
  57. package/morph-touch/Workflows/src/SendSummaryToNewRoomMapping.csx +10 -2
  58. package/morph-touch/Workflows/src/SetRoomHistoryVisibilityMapping.csx +12 -4
  59. package/morph-touch/Workflows/src/ValidateDateForRezervationMapping.csx +16 -2
  60. package/morph-touch/Workflows/src/ValidateSlotForRandevuUpdateMapping.csx +22 -15
  61. package/morph-touch/Workflows/src/ValidateSlotForRezervationMapping.csx +23 -16
  62. package/morph-touch/Workflows/src/ValidateTransferMapping.csx +16 -2
  63. package/morph-touch/Workflows/start-chat.json +1 -1
  64. package/morph-touch/doc/morph-touch-domain.en.md +1 -1
  65. package/morph-touch/doc/morph-touch-domain.md +1 -1
  66. package/package.json +1 -1
  67. package/vnext.config.json +1 -1
  68. package/burgan-tech-morph-touch-runtime-0.0.7.tgz +0 -0
@@ -0,0 +1,56 @@
1
+ using System;
2
+ using System.Threading.Tasks;
3
+ using BBT.Workflow.Scripting;
4
+ using BBT.Workflow.Definitions;
5
+
6
+ /// <summary>
7
+ /// Mapping for start-register-matrix-user StartTask (Type 11) in rezervation-start flow.
8
+ /// Starts register-matrix-user flow with matrixUsername based on participantType (advisor/invited/customer).
9
+ /// </summary>
10
+ public class RegisterForRandevuStartChatMapping : ScriptBase, IMapping
11
+ {
12
+ public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
13
+ {
14
+ var startTask = task as StartTask;
15
+ if (startTask == null)
16
+ throw new InvalidOperationException("Task must be a StartTask");
17
+
18
+ var data = context.Instance?.Data;
19
+ var participantType = GetString(data, "participantType");
20
+ var user = GetString(data, "user");
21
+ var advisor = GetString(data, "advisor");
22
+
23
+ var matrixUsername = participantType == "advisor" ? advisor
24
+ : participantType == "invited" ? GetString(data, "invitedUserId")
25
+ : "u" + user;
26
+ if (string.IsNullOrWhiteSpace(matrixUsername))
27
+ throw new InvalidOperationException("Cannot determine register user: participantType, user, advisor, or invitedUserId missing");
28
+
29
+ startTask.SetDomain("morph-touch");
30
+ startTask.SetFlow("register-matrix-user");
31
+ startTask.SetSync(true);
32
+ startTask.SetBody(new { matrixUsername });
33
+
34
+ return Task.FromResult(new ScriptResponse());
35
+ }
36
+
37
+ public Task<ScriptResponse> OutputHandler(ScriptContext context)
38
+ {
39
+ return Task.FromResult(new ScriptResponse());
40
+ }
41
+
42
+ private string GetString(dynamic obj, string name)
43
+ {
44
+ if (obj == null) return null;
45
+ try
46
+ {
47
+ if (HasProperty(obj, name))
48
+ {
49
+ var v = GetPropertyValue(obj, name);
50
+ return v?.ToString();
51
+ }
52
+ }
53
+ catch { }
54
+ return null;
55
+ }
56
+ }
@@ -0,0 +1,90 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+ using System.Threading.Tasks;
5
+ using BBT.Workflow.Scripting;
6
+ using BBT.Workflow.Definitions;
7
+
8
+ /// <summary>
9
+ /// Mapping for start-register-matrix-user StartTask (Type 11) in rezervation-transfer flow.
10
+ /// Starts register-matrix-user flow with matrixUsername looked up from permanentChatRooms by chatRoomKey.
11
+ /// </summary>
12
+ public class RegisterForTransferChatMapping : ScriptBase, IMapping
13
+ {
14
+ public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
15
+ {
16
+ var startTask = task as StartTask;
17
+ if (startTask == null)
18
+ throw new InvalidOperationException("Task must be a StartTask");
19
+
20
+ var data = context.Instance?.Data;
21
+ if (data == null)
22
+ return Task.FromResult(new ScriptResponse());
23
+
24
+ string user = null;
25
+ var permanentRoomPlan = data.permanentRoomPlan;
26
+ if (permanentRoomPlan != null)
27
+ {
28
+ int processedIndex = 0;
29
+ if (HasProperty(data, "permanentProcessedIndex"))
30
+ int.TryParse(data.permanentProcessedIndex?.ToString() ?? "0", out processedIndex);
31
+
32
+ var items = permanentRoomPlan as IEnumerable<object>;
33
+ if (items != null)
34
+ {
35
+ var itemsList = items.ToList();
36
+ if (processedIndex < itemsList.Count)
37
+ {
38
+ dynamic currentItem = itemsList[processedIndex];
39
+ var chatRoomKey = currentItem?.chatRoomKey?.ToString();
40
+ if (!string.IsNullOrEmpty(chatRoomKey))
41
+ user = LookupUser(data, chatRoomKey);
42
+ }
43
+ }
44
+ }
45
+
46
+ if (string.IsNullOrWhiteSpace(user))
47
+ return Task.FromResult(new ScriptResponse());
48
+
49
+ startTask.SetDomain("morph-touch");
50
+ startTask.SetFlow("register-matrix-user");
51
+ startTask.SetSync(true);
52
+ startTask.SetBody(new { matrixUsername = user });
53
+
54
+ return Task.FromResult(new ScriptResponse());
55
+ }
56
+
57
+ public Task<ScriptResponse> OutputHandler(ScriptContext context)
58
+ {
59
+ return Task.FromResult(new ScriptResponse());
60
+ }
61
+
62
+ private string LookupUser(dynamic data, string chatRoomKey)
63
+ {
64
+ if (!HasProperty(data, "permanentChatRooms") || data.permanentChatRooms == null)
65
+ return null;
66
+
67
+ foreach (var room in data.permanentChatRooms)
68
+ {
69
+ var key = GetString(room, "chatRoomKey");
70
+ if (string.Equals(key, chatRoomKey, StringComparison.OrdinalIgnoreCase))
71
+ return GetString(room, "user");
72
+ }
73
+ return null;
74
+ }
75
+
76
+ private string GetString(dynamic obj, string name)
77
+ {
78
+ if (obj == null) return null;
79
+ try
80
+ {
81
+ if (HasProperty(obj, name))
82
+ {
83
+ var v = GetPropertyValue(obj, name);
84
+ return v?.ToString()?.Trim();
85
+ }
86
+ }
87
+ catch { }
88
+ return null;
89
+ }
90
+ }
@@ -0,0 +1,73 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Threading.Tasks;
4
+ using BBT.Workflow.Scripting;
5
+ using BBT.Workflow.Definitions;
6
+
7
+ /// <summary>
8
+ /// Generic mapping for register-for-chat inside the register-matrix-user flow.
9
+ /// Reads matrixUsername from instance data and registers on Synapse via POST /_matrix/client/v3/register.
10
+ /// </summary>
11
+ public class RegisterMatrixUserMapping : ScriptBase, IMapping
12
+ {
13
+ public async Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
14
+ {
15
+ var httpTask = task as HttpTask;
16
+ if (httpTask == null)
17
+ throw new InvalidOperationException("Task must be an HttpTask");
18
+
19
+ var data = context.Instance?.Data;
20
+ var matrixUsername = GetString(data, "matrixUsername");
21
+ if (string.IsNullOrWhiteSpace(matrixUsername))
22
+ return new ScriptResponse();
23
+
24
+ var secretName = GetConfigValue("DAPR_SECRET_STORE_NAME");
25
+ if (string.IsNullOrWhiteSpace(secretName))
26
+ throw new InvalidOperationException("DAPR_SECRET_STORE_NAME configuration is required");
27
+
28
+ var matrixBaseUrl = await GetSecretAsync(secretName, "workflow-secret", "MatrixBaseUrl");
29
+ if (string.IsNullOrWhiteSpace(matrixBaseUrl))
30
+ throw new InvalidOperationException("MatrixBaseUrl secret is required");
31
+
32
+ var synapsePassword = await GetSecretAsync(secretName, "workflow-secret", "SynapsePassword");
33
+ if (string.IsNullOrWhiteSpace(synapsePassword))
34
+ throw new InvalidOperationException("SynapsePassword secret is required");
35
+
36
+ var fullUrl = matrixBaseUrl.TrimEnd('/') + "/_matrix/client/v3/register";
37
+ httpTask.SetUrl(fullUrl);
38
+
39
+ httpTask.SetHeaders(new Dictionary<string, string>
40
+ {
41
+ ["Content-Type"] = "application/json",
42
+ ["Accept"] = "application/json"
43
+ });
44
+
45
+ httpTask.SetBody(new
46
+ {
47
+ auth = new { type = "m.login.dummy" },
48
+ username = matrixUsername,
49
+ password = synapsePassword
50
+ });
51
+ return new ScriptResponse();
52
+ }
53
+
54
+ public Task<ScriptResponse> OutputHandler(ScriptContext context)
55
+ {
56
+ return Task.FromResult(new ScriptResponse());
57
+ }
58
+
59
+ private string GetString(dynamic obj, string name)
60
+ {
61
+ if (obj == null) return null;
62
+ try
63
+ {
64
+ if (HasProperty(obj, name))
65
+ {
66
+ var v = GetPropertyValue(obj, name);
67
+ return v?.ToString();
68
+ }
69
+ }
70
+ catch { }
71
+ return null;
72
+ }
73
+ }
@@ -52,10 +52,7 @@ public class SendPushNotificationMapping : ScriptBase, IMapping
52
52
  var startDateTime = ctx != null ? GetString(ctx, "startDateTime") : null;
53
53
  var reservationKey = ctx != null ? GetString(ctx, "reservationKey") : null;
54
54
 
55
- string formattedDate = startDateTime ?? "";
56
- DateTime dt = default;
57
- if (!string.IsNullOrEmpty(startDateTime) && DateTime.TryParse(startDateTime, out dt))
58
- formattedDate = dt.ToString("dd.MM.yyyy HH:mm");
55
+ string formattedDate = FormatInBusinessTimeZone(startDateTime);
59
56
 
60
57
  string title;
61
58
  string content;
@@ -133,6 +130,30 @@ public class SendPushNotificationMapping : ScriptBase, IMapping
133
130
  return result;
134
131
  }
135
132
 
133
+ private string FormatInBusinessTimeZone(string dateStr)
134
+ {
135
+ if (string.IsNullOrEmpty(dateStr)) return dateStr ?? "";
136
+ if (!DateTimeOffset.TryParse(dateStr,
137
+ System.Globalization.CultureInfo.InvariantCulture,
138
+ System.Globalization.DateTimeStyles.None, out var dto))
139
+ return dateStr;
140
+
141
+ var tz = ResolveBusinessTimeZone();
142
+ var local = TimeZoneInfo.ConvertTimeFromUtc(dto.UtcDateTime, tz);
143
+ return local.ToString("dd.MM.yyyy HH:mm");
144
+ }
145
+
146
+ private TimeZoneInfo ResolveBusinessTimeZone()
147
+ {
148
+ var id = GetConfigValue("Appointments:TimeZone")
149
+ ?? GetConfigValue("APPOINTMENT_TIMEZONE");
150
+ if (string.IsNullOrWhiteSpace(id)) id = "Europe/Istanbul";
151
+ try { return TimeZoneInfo.FindSystemTimeZoneById(id.Trim()); } catch { }
152
+ try { return TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"); } catch { }
153
+ return TimeZoneInfo.CreateCustomTimeZone(
154
+ "TRT", TimeSpan.FromHours(3), "TRT (UTC+3)", "TRT (UTC+3)");
155
+ }
156
+
136
157
  private string GetString(dynamic obj, string name)
137
158
  {
138
159
  if (obj == null) return null;
@@ -48,10 +48,7 @@ public class SendSmsNotificationMapping : ScriptBase, IMapping
48
48
  var startDateTime = ctx != null ? GetString(ctx, "startDateTime") : null;
49
49
  var reservationKey = ctx != null ? GetString(ctx, "reservationKey") : null;
50
50
 
51
- string formattedDate = startDateTime ?? "";
52
- DateTime dt = default;
53
- if (!string.IsNullOrEmpty(startDateTime) && DateTime.TryParse(startDateTime, out dt))
54
- formattedDate = dt.ToString("dd.MM.yyyy HH:mm");
51
+ string formattedDate = FormatInBusinessTimeZone(startDateTime);
55
52
 
56
53
  string content;
57
54
  if (userType == "advisor")
@@ -125,6 +122,30 @@ public class SendSmsNotificationMapping : ScriptBase, IMapping
125
122
  return result;
126
123
  }
127
124
 
125
+ private string FormatInBusinessTimeZone(string dateStr)
126
+ {
127
+ if (string.IsNullOrEmpty(dateStr)) return dateStr ?? "";
128
+ if (!DateTimeOffset.TryParse(dateStr,
129
+ System.Globalization.CultureInfo.InvariantCulture,
130
+ System.Globalization.DateTimeStyles.None, out var dto))
131
+ return dateStr;
132
+
133
+ var tz = ResolveBusinessTimeZone();
134
+ var local = TimeZoneInfo.ConvertTimeFromUtc(dto.UtcDateTime, tz);
135
+ return local.ToString("dd.MM.yyyy HH:mm");
136
+ }
137
+
138
+ private TimeZoneInfo ResolveBusinessTimeZone()
139
+ {
140
+ var id = GetConfigValue("Appointments:TimeZone")
141
+ ?? GetConfigValue("APPOINTMENT_TIMEZONE");
142
+ if (string.IsNullOrWhiteSpace(id)) id = "Europe/Istanbul";
143
+ try { return TimeZoneInfo.FindSystemTimeZoneById(id.Trim()); } catch { }
144
+ try { return TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"); } catch { }
145
+ return TimeZoneInfo.CreateCustomTimeZone(
146
+ "TRT", TimeSpan.FromHours(3), "TRT (UTC+3)", "TRT (UTC+3)");
147
+ }
148
+
128
149
  private string GetString(dynamic obj, string name)
129
150
  {
130
151
  if (obj == null) return null;
@@ -69,12 +69,20 @@ public class SendSummaryToNewRoomMapping : ScriptBase, IMapping
69
69
  "/send/m.room.message/" + txnId;
70
70
 
71
71
  httpTask.SetUrl(fullUrl);
72
- httpTask.SetHeaders(new Dictionary<string, string>
72
+
73
+ string sessionId = null;
74
+ if (HasProperty(data, "chatIntegration") && data.chatIntegration != null)
75
+ sessionId = GetString(data.chatIntegration, "sessionId");
76
+
77
+ var sendHeaders = new Dictionary<string, string>
73
78
  {
74
79
  ["Content-Type"] = "application/json",
75
80
  ["Accept"] = "application/json",
76
81
  ["X-Matrix-User"] = user
77
- });
82
+ };
83
+ if (!string.IsNullOrWhiteSpace(sessionId))
84
+ sendHeaders["Authorization"] = "Bearer " + sessionId;
85
+ httpTask.SetHeaders(sendHeaders);
78
86
 
79
87
  var messageBody =
80
88
  "Onceki danisman ile yapilan gorusme ozeti:\n\n" +
@@ -43,27 +43,35 @@ public class SetRoomHistoryVisibilityMapping : ScriptBase, IMapping
43
43
  if (string.IsNullOrWhiteSpace(roomId) || string.IsNullOrWhiteSpace(user))
44
44
  return new ScriptResponse();
45
45
 
46
+ var sessionId = GetString(chatIntegration, "sessionId");
47
+
46
48
  if (string.IsNullOrEmpty(hv))
47
49
  {
48
50
  TrySetHttpMethod(httpTask, "GET");
49
51
  httpTask.SetUrl(baseTrim + "/_matrix/client/v3/versions");
50
- httpTask.SetHeaders(new Dictionary<string, string>
52
+ var noOpHeaders = new Dictionary<string, string>
51
53
  {
52
54
  ["Accept"] = "application/json",
53
55
  ["X-Matrix-User"] = user
54
- });
56
+ };
57
+ if (!string.IsNullOrWhiteSpace(sessionId))
58
+ noOpHeaders["Authorization"] = "Bearer " + sessionId;
59
+ httpTask.SetHeaders(noOpHeaders);
55
60
  return new ScriptResponse();
56
61
  }
57
62
 
58
63
  TrySetHttpMethod(httpTask, "PUT");
59
64
  var fullUrl = baseTrim + "/_matrix/client/v3/rooms/" + Uri.EscapeDataString(roomId) + "/state/m.room.history_visibility/";
60
65
  httpTask.SetUrl(fullUrl);
61
- httpTask.SetHeaders(new Dictionary<string, string>
66
+ var putHeaders = new Dictionary<string, string>
62
67
  {
63
68
  ["Content-Type"] = "application/json",
64
69
  ["Accept"] = "application/json",
65
70
  ["X-Matrix-User"] = user
66
- });
71
+ };
72
+ if (!string.IsNullOrWhiteSpace(sessionId))
73
+ putHeaders["Authorization"] = "Bearer " + sessionId;
74
+ httpTask.SetHeaders(putHeaders);
67
75
  httpTask.SetBody(new { history_visibility = hv });
68
76
 
69
77
  return new ScriptResponse();
@@ -23,10 +23,10 @@ public class ValidateDateForRezervationMapping : ScriptBase, IMapping
23
23
  throw new ArgumentException("startDateTime and endDateTime are required.", "date");
24
24
 
25
25
  DateTime startDt;
26
- if (!DateTime.TryParse(startDateTimeStr, out startDt))
26
+ if (!TryParseToUtc(startDateTimeStr, out startDt))
27
27
  throw new ArgumentException("Invalid startDateTime format.", "startDateTime");
28
28
  DateTime endDt;
29
- if (!DateTime.TryParse(endDateTimeStr, out endDt))
29
+ if (!TryParseToUtc(endDateTimeStr, out endDt))
30
30
  throw new ArgumentException("Invalid endDateTime format.", "endDateTime");
31
31
 
32
32
  var now = DateTime.UtcNow;
@@ -43,6 +43,20 @@ public class ValidateDateForRezervationMapping : ScriptBase, IMapping
43
43
  return Task.FromResult(new ScriptResponse());
44
44
  }
45
45
 
46
+ private bool TryParseToUtc(string dateStr, out DateTime utcDateTime)
47
+ {
48
+ utcDateTime = default;
49
+ if (string.IsNullOrEmpty(dateStr)) return false;
50
+ if (DateTimeOffset.TryParse(dateStr,
51
+ System.Globalization.CultureInfo.InvariantCulture,
52
+ System.Globalization.DateTimeStyles.None, out var dto))
53
+ {
54
+ utcDateTime = dto.UtcDateTime;
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+
46
60
  private string GetString(dynamic obj, string name)
47
61
  {
48
62
  if (obj == null) return null;
@@ -44,7 +44,7 @@ public class ValidateSlotForRandevuUpdateMapping : ScriptBase, IMapping
44
44
  }
45
45
 
46
46
  DateTime startDateTime;
47
- if (!DateTime.TryParse(startDateTimeStr, out startDateTime))
47
+ if (!TryParseToUtc(startDateTimeStr, out startDateTime))
48
48
  {
49
49
  return Task.FromResult(new ScriptResponse
50
50
  {
@@ -103,12 +103,12 @@ public class ValidateSlotForRandevuUpdateMapping : ScriptBase, IMapping
103
103
  });
104
104
  }
105
105
 
106
- DateTime startDt = DateTime.MinValue;
107
- DateTime endDt = DateTime.MinValue;
106
+ DateTime startDtUtc = DateTime.MinValue;
107
+ DateTime endDtUtc = DateTime.MinValue;
108
108
  try
109
109
  {
110
- startDt = DateTime.Parse(startDateTimeStr);
111
- endDt = DateTime.Parse(endDateTimeStr);
110
+ startDtUtc = ParseToUtc(startDateTimeStr);
111
+ endDtUtc = ParseToUtc(endDateTimeStr);
112
112
  }
113
113
  catch (Exception)
114
114
  {
@@ -119,8 +119,8 @@ public class ValidateSlotForRandevuUpdateMapping : ScriptBase, IMapping
119
119
  }
120
120
 
121
121
  var tz = ResolveBusinessTimeZone();
122
- var localStart = ConvertToTimeZone(startDt, startDateTimeStr, tz);
123
- var localEnd = ConvertToTimeZone(endDt, endDateTimeStr, tz);
122
+ var localStart = TimeZoneInfo.ConvertTimeFromUtc(startDtUtc, tz);
123
+ var localEnd = TimeZoneInfo.ConvertTimeFromUtc(endDtUtc, tz);
124
124
  var requestedSlot = localStart.ToString("HH:mm") + "-" + localEnd.ToString("HH:mm");
125
125
  bool available = false;
126
126
  string responseKey = null;
@@ -208,18 +208,25 @@ public class ValidateSlotForRandevuUpdateMapping : ScriptBase, IMapping
208
208
  return TimeZoneInfo.CreateCustomTimeZone("TRT", TimeSpan.FromHours(3), "TRT (UTC+3)", "TRT (UTC+3)");
209
209
  }
210
210
 
211
- private static DateTime ConvertToTimeZone(DateTime dt, string original, TimeZoneInfo tz)
211
+ private bool TryParseToUtc(string dateStr, out DateTime utcDateTime)
212
212
  {
213
- if (dt.Kind == DateTimeKind.Utc)
214
- return TimeZoneInfo.ConvertTimeFromUtc(dt, tz);
215
-
216
- if (DateTimeOffset.TryParse(original, out var dto))
213
+ utcDateTime = default;
214
+ if (string.IsNullOrEmpty(dateStr)) return false;
215
+ if (DateTimeOffset.TryParse(dateStr,
216
+ System.Globalization.CultureInfo.InvariantCulture,
217
+ System.Globalization.DateTimeStyles.None, out var dto))
217
218
  {
218
- var utc = dto.UtcDateTime;
219
- return TimeZoneInfo.ConvertTimeFromUtc(utc, tz);
219
+ utcDateTime = dto.UtcDateTime;
220
+ return true;
220
221
  }
222
+ return false;
223
+ }
221
224
 
222
- return dt;
225
+ private DateTime ParseToUtc(string dateStr)
226
+ {
227
+ var dto = DateTimeOffset.Parse(dateStr,
228
+ System.Globalization.CultureInfo.InvariantCulture);
229
+ return dto.UtcDateTime;
223
230
  }
224
231
 
225
232
  private string GetString(dynamic obj, string name)
@@ -49,7 +49,7 @@ public class ValidateSlotForRezervationMapping : ScriptBase, IMapping
49
49
  }
50
50
 
51
51
  DateTime startDateTime;
52
- if (!DateTime.TryParse(startDateTimeStr, out startDateTime))
52
+ if (!TryParseToUtc(startDateTimeStr, out startDateTime))
53
53
  {
54
54
  return Task.FromResult(new ScriptResponse
55
55
  {
@@ -119,14 +119,14 @@ public class ValidateSlotForRezervationMapping : ScriptBase, IMapping
119
119
  });
120
120
  }
121
121
 
122
- DateTime startDt = DateTime.MinValue;
123
- DateTime endDt = DateTime.MinValue;
122
+ DateTime startDtUtc = DateTime.MinValue;
123
+ DateTime endDtUtc = DateTime.MinValue;
124
124
  try
125
125
  {
126
- startDt = DateTime.Parse(startDateTimeStr);
127
- endDt = DateTime.Parse(endDateTimeStr);
126
+ startDtUtc = ParseToUtc(startDateTimeStr);
127
+ endDtUtc = ParseToUtc(endDateTimeStr);
128
128
  }
129
- catch (Exception ex)
129
+ catch (Exception)
130
130
  {
131
131
  return Task.FromResult(new ScriptResponse
132
132
  {
@@ -135,8 +135,8 @@ public class ValidateSlotForRezervationMapping : ScriptBase, IMapping
135
135
  }
136
136
 
137
137
  var tz = ResolveBusinessTimeZone();
138
- var localStart = ConvertToTimeZone(startDt, startDateTimeStr, tz);
139
- var localEnd = ConvertToTimeZone(endDt, endDateTimeStr, tz);
138
+ var localStart = TimeZoneInfo.ConvertTimeFromUtc(startDtUtc, tz);
139
+ var localEnd = TimeZoneInfo.ConvertTimeFromUtc(endDtUtc, tz);
140
140
  var requestedSlot = localStart.ToString("HH:mm") + "-" + localEnd.ToString("HH:mm");
141
141
 
142
142
  bool available = false;
@@ -243,18 +243,25 @@ public class ValidateSlotForRezervationMapping : ScriptBase, IMapping
243
243
  return TimeZoneInfo.CreateCustomTimeZone("TRT", TimeSpan.FromHours(3), "TRT (UTC+3)", "TRT (UTC+3)");
244
244
  }
245
245
 
246
- private static DateTime ConvertToTimeZone(DateTime dt, string original, TimeZoneInfo tz)
246
+ private bool TryParseToUtc(string dateStr, out DateTime utcDateTime)
247
247
  {
248
- if (dt.Kind == DateTimeKind.Utc)
249
- return TimeZoneInfo.ConvertTimeFromUtc(dt, tz);
250
-
251
- if (DateTimeOffset.TryParse(original, out var dto))
248
+ utcDateTime = default;
249
+ if (string.IsNullOrEmpty(dateStr)) return false;
250
+ if (DateTimeOffset.TryParse(dateStr,
251
+ System.Globalization.CultureInfo.InvariantCulture,
252
+ System.Globalization.DateTimeStyles.None, out var dto))
252
253
  {
253
- var utc = dto.UtcDateTime;
254
- return TimeZoneInfo.ConvertTimeFromUtc(utc, tz);
254
+ utcDateTime = dto.UtcDateTime;
255
+ return true;
255
256
  }
257
+ return false;
258
+ }
256
259
 
257
- return dt;
260
+ private DateTime ParseToUtc(string dateStr)
261
+ {
262
+ var dto = DateTimeOffset.Parse(dateStr,
263
+ System.Globalization.CultureInfo.InvariantCulture);
264
+ return dto.UtcDateTime;
258
265
  }
259
266
 
260
267
  private string GetString(dynamic obj, string name)
@@ -125,7 +125,7 @@ public class ValidateTransferMapping : ScriptBase, IMapping
125
125
 
126
126
  DateTime rezStart = DateTime.MinValue;
127
127
  DateTime rezEnd = DateTime.MinValue;
128
- if (!DateTime.TryParse(rezStartStr, out rezStart) || !DateTime.TryParse(rezEndStr, out rezEnd))
128
+ if (!TryParseToUtc(rezStartStr, out rezStart) || !TryParseToUtc(rezEndStr, out rezEnd))
129
129
  {
130
130
  details.Add(new { rezervationKey, targetAdvisor, valid = true, reason = "unparseable-time" });
131
131
  continue;
@@ -151,7 +151,7 @@ public class ValidateTransferMapping : ScriptBase, IMapping
151
151
 
152
152
  DateTime itemStart = DateTime.MinValue;
153
153
  DateTime itemEnd = DateTime.MinValue;
154
- if (DateTime.TryParse(itemStartStr, out itemStart) && DateTime.TryParse(itemEndStr, out itemEnd))
154
+ if (TryParseToUtc(itemStartStr, out itemStart) && TryParseToUtc(itemEndStr, out itemEnd))
155
155
  {
156
156
  var itemKey = HasProperty(item, "key") ? item.key?.ToString() : "";
157
157
  if (itemKey == rezervationKey) continue;
@@ -206,6 +206,20 @@ public class ValidateTransferMapping : ScriptBase, IMapping
206
206
  }
207
207
  }
208
208
 
209
+ private bool TryParseToUtc(string dateStr, out DateTime utcDateTime)
210
+ {
211
+ utcDateTime = default;
212
+ if (string.IsNullOrEmpty(dateStr)) return false;
213
+ if (DateTimeOffset.TryParse(dateStr,
214
+ System.Globalization.CultureInfo.InvariantCulture,
215
+ System.Globalization.DateTimeStyles.None, out var dto))
216
+ {
217
+ utcDateTime = dto.UtcDateTime;
218
+ return true;
219
+ }
220
+ return false;
221
+ }
222
+
209
223
  private string GetString(dynamic obj, string name)
210
224
  {
211
225
  if (obj == null) return null;
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "mapping": {
51
51
  "location": "./src/CheckExistingPermanentChatRoomMapping.csx",
52
- "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gTWFwcGluZyBmb3IgY2hlY2stZXhpc3RpbmctcGVybWFuZW50LWNoYXQtcm9vbSBEYXByU2VydmljZVRhc2sgKFR5cGUgMykuCi8vLyBDYWxscyBnZXQtY2hhdC1yb29tcyBmbG93IGZ1bmN0aW9uIHdpdGggdXNlciBhbmQgcm9vbVR5cGU9cGVybWFuZW50IGZyb20gSW5zdGFuY2VEYXRhLgovLy8gT3V0cHV0OiBoYXNFeGlzdGluZ1Blcm1hbmVudFJvb20gKGJvb2wpLCBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcyAoYm9vbCksIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleSAoc3RyaW5nKS4KLy8vIC0gaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tOiB0cnVlIG9ubHkgaWYgdXNlciBoYXMgYSBwZXJtYW5lbnQgcm9vbSB3aXRoIHRoZSBzYW1lIGFkdmlzb3JUeXBlIChlLmcuIFBNIHJlcXVlc3Qgb25seSBjb3VudHMgUE0gcm9vbXM7IElBIHJlcXVlc3Qgb25seSBjb3VudHMgSUEgcm9vbXMpLgovLy8gLSBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlczogdHJ1ZSBpZiB0aGF0IHJvb20ncyBhZHZpc29ySWQgYWxzbyBtYXRjaGVzOyBmYWxzZSBvdGhlcndpc2UuCi8vLyAtIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleToga2V5IG9mIG1hdGNoaW5nIHJvb20sIG9yIGZpcnN0IHJvb20gd2l0aCBzYW1lIGFkdmlzb3JUeXBlIHdoZW4gbm8gbWF0Y2ggKGZvciB0cmFuc2ZlcikuCi8vLyA8L3N1bW1hcnk+CnB1YmxpYyBjbGFzcyBDaGVja0V4aXN0aW5nUGVybWFuZW50Q2hhdFJvb21NYXBwaW5nIDogU2NyaXB0QmFzZSwgSU1hcHBpbmcKewogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IElucHV0SGFuZGxlcihXb3JrZmxvd1Rhc2sgdGFzaywgU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHZhciBzZXJ2aWNlVGFzayA9IHRhc2sgYXMgRGFwclNlcnZpY2VUYXNrOwogICAgICAgIGlmIChzZXJ2aWNlVGFzayA9PSBudWxsKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigiVGFzayBtdXN0IGJlIGEgRGFwclNlcnZpY2VUYXNrIik7CgogICAgICAgIHZhciBhcHBJZCA9IEdldENvbmZpZ1ZhbHVlKCJEQVBSX0FQUF9JRCIpOwogICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkoYXBwSWQpKQogICAgICAgICAgICBzZXJ2aWNlVGFzay5TZXRBcHBJZChhcHBJZCk7CgogICAgICAgIHZhciBkYXRhID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YTsKICAgICAgICBpZiAoZGF0YSA9PSBudWxsKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigiSW5zdGFuY2UgZGF0YSBpcyByZXF1aXJlZCIpOwoKICAgICAgICB2YXIgdXNlciA9IEdldFN0cmluZyhkYXRhLCAidXNlciIpOwogICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JXaGl0ZVNwYWNlKHVzZXIpKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigidXNlciBpcyByZXF1aXJlZCBpbiBpbnN0YW5jZSBkYXRhIik7CgogICAgICAgIHZhciBxdWVyeVBhcmFtcyA9IG5ldyBMaXN0PHN0cmluZz4KICAgICAgICB7CiAgICAgICAgICAgICJ1c2VyPSIgKyBVcmkuRXNjYXBlRGF0YVN0cmluZyh1c2VyLlRyaW0oKSksCiAgICAgICAgICAgICJyb29tVHlwZT1wZXJtYW5lbnQiLAogICAgICAgICAgICAic3RhdGU9YWN0aXZhdGVkIiwKICAgICAgICAgICAgInBhZ2VTaXplPTIwIgogICAgICAgIH07CiAgICAgICAgc2VydmljZVRhc2suU2V0UXVlcnlTdHJpbmcoc3RyaW5nLkpvaW4oIiYiLCBxdWVyeVBhcmFtcykpOwoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpKTsKICAgIH0KCiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gT3V0cHV0SGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgdmFyIHJlc3BvbnNlID0gY29udGV4dC5Cb2R5OwogICAgICAgIHZhciBkYXRhID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YTsKICAgICAgICB2YXIgYWR2aXNvcklkID0gR2V0U3RyaW5nKGRhdGEsICJhZHZpc29ySWQiKTsKICAgICAgICB2YXIgYWR2aXNvclR5cGUgPSBHZXRTdHJpbmcoZGF0YSwgImFkdmlzb3JUeXBlIik7CgogICAgICAgIGJvb2wgaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tID0gZmFsc2U7CiAgICAgICAgYm9vbCBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcyA9IGZhbHNlOwogICAgICAgIHN0cmluZyBleGlzdGluZ0NoYXRSb29tSW5zdGFuY2VLZXkgPSBudWxsOwogICAgICAgIHN0cmluZyBmaXJzdFJvb21XaXRoU2FtZUFkdmlzb3JUeXBlS2V5ID0gbnVsbDsKCiAgICAgICAgaWYgKHJlc3BvbnNlPy5pc1N1Y2Nlc3MgPT0gdHJ1ZSkKICAgICAgICB7CiAgICAgICAgICAgIHZhciByZXNwb25zZURhdGEgPSByZXNwb25zZT8uZGF0YSA/PyByZXNwb25zZT8uRGF0YSA/PyByZXNwb25zZTsKICAgICAgICAgICAgdmFyIHJvb21zID0gR2V0Um9vbXNMaXN0KHJlc3BvbnNlRGF0YSk7CiAgICAgICAgICAgIGlmIChyb29tcyAhPSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICBmb3JlYWNoICh2YXIgcm9vbSBpbiByb29tcykKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbUFkdmlzb3JUeXBlID0gR2V0U3RyaW5nKHJvb20sICJhZHZpc29yVHlwZSIpOwogICAgICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLkVxdWFscyhyb29tQWR2aXNvclR5cGU/LlRyaW0oKSwgYWR2aXNvclR5cGU/LlRyaW0oKSwgU3RyaW5nQ29tcGFyaXNvbi5PcmRpbmFsSWdub3JlQ2FzZSkpCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlOwoKICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbUtleSA9IEdldFN0cmluZyhyb29tLCAiaW5zdGFuY2VLZXkiKSA/PyBHZXRTdHJpbmcocm9vbSwgImtleSIpOwogICAgICAgICAgICAgICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JXaGl0ZVNwYWNlKHJvb21LZXkpKSBjb250aW51ZTsKCiAgICAgICAgICAgICAgICAgICAgaWYgKGZpcnN0Um9vbVdpdGhTYW1lQWR2aXNvclR5cGVLZXkgPT0gbnVsbCkKICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3RSb29tV2l0aFNhbWVBZHZpc29yVHlwZUtleSA9IHJvb21LZXk7CgogICAgICAgICAgICAgICAgICAgIGhhc0V4aXN0aW5nUGVybWFuZW50Um9vbSA9IHRydWU7CgogICAgICAgICAgICAgICAgICAgIHZhciByb29tQWR2aXNvcklkID0gR2V0U3RyaW5nKHJvb20sICJhZHZpc29ySWQiKTsKICAgICAgICAgICAgICAgICAgICBpZiAoc3RyaW5nLkVxdWFscyhyb29tQWR2aXNvcklkPy5UcmltKCksIGFkdmlzb3JJZD8uVHJpbSgpLCBTdHJpbmdDb21wYXJpc29uLk9yZGluYWxJZ25vcmVDYXNlKSkKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgIGV4aXN0aW5nUm9vbUFkdmlzb3JNYXRjaGVzID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID0gcm9vbUtleTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmIChoYXNFeGlzdGluZ1Blcm1hbmVudFJvb20gJiYgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID09IG51bGwpCiAgICAgICAgICAgICAgICAgICAgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID0gZmlyc3RSb29tV2l0aFNhbWVBZHZpc29yVHlwZUtleTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UKICAgICAgICB7CiAgICAgICAgICAgIERhdGEgPSBuZXcKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tID0gaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tLAogICAgICAgICAgICAgICAgZXhpc3RpbmdSb29tQWR2aXNvck1hdGNoZXMgPSBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcywKICAgICAgICAgICAgICAgIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleSA9IGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleQogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICB9CgogICAgcHJpdmF0ZSBTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmFibGUgR2V0Um9vbXNMaXN0KGR5bmFtaWMgcmVzcG9uc2VEYXRhKQogICAgewogICAgICAgIGlmIChyZXNwb25zZURhdGEgPT0gbnVsbCkgcmV0dXJuIG51bGw7CiAgICAgICAgdHJ5CiAgICAgICAgewogICAgICAgICAgICAvLyBEaXJlY3Qgcm9vbXMKICAgICAgICAgICAgaWYgKEhhc1Byb3BlcnR5KHJlc3BvbnNlRGF0YSwgInJvb21zIikgJiYgcmVzcG9uc2VEYXRhLnJvb21zICE9IG51bGwpCiAgICAgICAgICAgICAgICByZXR1cm4gcmVzcG9uc2VEYXRhLnJvb21zOwogICAgICAgICAgICBpZiAoSGFzUHJvcGVydHkocmVzcG9uc2VEYXRhLCAiZGF0YSIpICYmIHJlc3BvbnNlRGF0YS5kYXRhICE9IG51bGwgJiYgSGFzUHJvcGVydHkocmVzcG9uc2VEYXRhLmRhdGEsICJyb29tcyIpKQogICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlRGF0YS5kYXRhLnJvb21zOwogICAgICAgICAgICBpZiAoSGFzUHJvcGVydHkocmVzcG9uc2VEYXRhLCAiRGF0YSIpICYmIHJlc3BvbnNlRGF0YS5EYXRhICE9IG51bGwgJiYgSGFzUHJvcGVydHkocmVzcG9uc2VEYXRhLkRhdGEsICJyb29tcyIpKQogICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlRGF0YS5EYXRhLnJvb21zOwoKICAgICAgICAgICAgLy8gV3JhcHBlZDogY2hlY2tFeGlzdGluZ0RhdGEuaXRlbXNbXS5nZXRDaGF0Um9vbXMucm9vbXMgKHJ1bnRpbWUgdGFzayByZXN1bHQgc2hhcGUpCiAgICAgICAgICAgIHZhciBjaGVja0V4aXN0aW5nID0gR2V0VmFsdWUocmVzcG9uc2VEYXRhLCAiY2hlY2tFeGlzdGluZ0RhdGEiKSA/PyBHZXRWYWx1ZShyZXNwb25zZURhdGEsICJkYXRhIik7CiAgICAgICAgICAgIHZhciByb290ID0gY2hlY2tFeGlzdGluZyA/PyByZXNwb25zZURhdGE7CiAgICAgICAgICAgIGlmIChyb290ICE9IG51bGwgJiYgSGFzUHJvcGVydHkocm9vdCwgIml0ZW1zIikgJiYgcm9vdC5pdGVtcyAhPSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICB2YXIgaXRlbXMgPSByb290Lml0ZW1zIGFzIFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYWJsZTsKICAgICAgICAgICAgICAgIGlmIChpdGVtcyAhPSBudWxsKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZvcmVhY2ggKHZhciBpdGVtIGluIGl0ZW1zKQogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGl0ZW0gPT0gbnVsbCkgY29udGludWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBnZXRDaGF0Um9vbXMgPSBHZXRWYWx1ZShpdGVtLCAiZ2V0Q2hhdFJvb21zIik7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChnZXRDaGF0Um9vbXMgIT0gbnVsbCAmJiBIYXNQcm9wZXJ0eShnZXRDaGF0Um9vbXMsICJyb29tcyIpKQogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbXMgPSBHZXRWYWx1ZShnZXRDaGF0Um9vbXMsICJyb29tcyIpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHJvb21zIGlzIFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYWJsZSBlbiAmJiBlbiAhPSBudWxsKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBlbjsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjYXRjaCB7IH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KCiAgICBwcml2YXRlIG9iamVjdCBHZXRWYWx1ZShkeW5hbWljIG9iaiwgc3RyaW5nIG5hbWUpCiAgICB7CiAgICAgICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gbnVsbDsKICAgICAgICB0cnkKICAgICAgICB7CiAgICAgICAgICAgIGlmIChIYXNQcm9wZXJ0eShvYmosIG5hbWUpKQogICAgICAgICAgICAgICAgcmV0dXJuIEdldFByb3BlcnR5VmFsdWUob2JqLCBuYW1lKTsKICAgICAgICB9CiAgICAgICAgY2F0Y2ggeyB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgogICAgcHJpdmF0ZSBzdHJpbmcgR2V0U3RyaW5nKGR5bmFtaWMgb2JqLCBzdHJpbmcgbmFtZSkKICAgIHsKICAgICAgICBpZiAob2JqID09IG51bGwpIHJldHVybiBudWxsOwogICAgICAgIHRyeQogICAgICAgIHsKICAgICAgICAgICAgaWYgKEhhc1Byb3BlcnR5KG9iaiwgbmFtZSkpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIHZhciB2ID0gR2V0UHJvcGVydHlWYWx1ZShvYmosIG5hbWUpOwogICAgICAgICAgICAgICAgcmV0dXJuIHY/LlRvU3RyaW5nKCk/LlRyaW0oKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjYXRjaCB7IH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KfQo="
52
+ "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gTWFwcGluZyBmb3IgY2hlY2stZXhpc3RpbmctcGVybWFuZW50LWNoYXQtcm9vbSBEYXByU2VydmljZVRhc2sgKFR5cGUgMykuCi8vLyBDYWxscyBnZXQtY2hhdC1yb29tcyBmbG93IGZ1bmN0aW9uIHdpdGggdXNlciBhbmQgcm9vbVR5cGU9cGVybWFuZW50IGZyb20gSW5zdGFuY2VEYXRhLgovLy8gT3V0cHV0OiBoYXNFeGlzdGluZ1Blcm1hbmVudFJvb20gKGJvb2wpLCBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcyAoYm9vbCksIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleSAoc3RyaW5nKS4KLy8vIC0gaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tOiB0cnVlIG9ubHkgaWYgdXNlciBoYXMgYSBwZXJtYW5lbnQgcm9vbSB3aXRoIHRoZSBzYW1lIGFkdmlzb3JUeXBlIChlLmcuIFBNIHJlcXVlc3Qgb25seSBjb3VudHMgUE0gcm9vbXM7IElBIHJlcXVlc3Qgb25seSBjb3VudHMgSUEgcm9vbXMpLgovLy8gLSBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlczogdHJ1ZSBpZiB0aGF0IHJvb20ncyBhZHZpc29ySWQgYWxzbyBtYXRjaGVzOyBmYWxzZSBvdGhlcndpc2UuCi8vLyAtIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleToga2V5IG9mIG1hdGNoaW5nIHJvb20sIG9yIGZpcnN0IHJvb20gd2l0aCBzYW1lIGFkdmlzb3JUeXBlIHdoZW4gbm8gbWF0Y2ggKGZvciB0cmFuc2ZlcikuCi8vLyA8L3N1bW1hcnk+CnB1YmxpYyBjbGFzcyBDaGVja0V4aXN0aW5nUGVybWFuZW50Q2hhdFJvb21NYXBwaW5nIDogU2NyaXB0QmFzZSwgSU1hcHBpbmcKewogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IElucHV0SGFuZGxlcihXb3JrZmxvd1Rhc2sgdGFzaywgU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHZhciBzZXJ2aWNlVGFzayA9IHRhc2sgYXMgRGFwclNlcnZpY2VUYXNrOwogICAgICAgIGlmIChzZXJ2aWNlVGFzayA9PSBudWxsKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigiVGFzayBtdXN0IGJlIGEgRGFwclNlcnZpY2VUYXNrIik7CgogICAgICAgIHZhciBhcHBJZCA9IEdldENvbmZpZ1ZhbHVlKCJEQVBSX0FQUF9JRCIpOwogICAgICAgIGlmICghc3RyaW5nLklzTnVsbE9yRW1wdHkoYXBwSWQpKQogICAgICAgICAgICBzZXJ2aWNlVGFzay5TZXRBcHBJZChhcHBJZCk7CgogICAgICAgIHZhciBkYXRhID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YTsKICAgICAgICBpZiAoZGF0YSA9PSBudWxsKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigiSW5zdGFuY2UgZGF0YSBpcyByZXF1aXJlZCIpOwoKICAgICAgICB2YXIgdXNlciA9IEdldFN0cmluZyhkYXRhLCAidXNlciIpOwogICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JXaGl0ZVNwYWNlKHVzZXIpKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigidXNlciBpcyByZXF1aXJlZCBpbiBpbnN0YW5jZSBkYXRhIik7CgogICAgICAgIHZhciBxdWVyeVBhcmFtcyA9IG5ldyBMaXN0PHN0cmluZz4KICAgICAgICB7CiAgICAgICAgICAgICJ1c2VyPSIgKyBVcmkuRXNjYXBlRGF0YVN0cmluZyh1c2VyLlRyaW0oKSksCiAgICAgICAgICAgICJyb29tVHlwZT1wZXJtYW5lbnQiLAogICAgICAgICAgICAic3RhdGU9YWN0aXZhdGVkIiwKICAgICAgICAgICAgInBhZ2VTaXplPTIwIgogICAgICAgIH07CiAgICAgICAgc2VydmljZVRhc2suU2V0UXVlcnlTdHJpbmcoc3RyaW5nLkpvaW4oIiYiLCBxdWVyeVBhcmFtcykpOwoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpKTsKICAgIH0KCiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gT3V0cHV0SGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgdmFyIHJlc3BvbnNlID0gY29udGV4dC5Cb2R5OwogICAgICAgIHZhciBkYXRhID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YTsKICAgICAgICB2YXIgYWR2aXNvcklkID0gR2V0U3RyaW5nKGRhdGEsICJhZHZpc29ySWQiKTsKICAgICAgICB2YXIgYWR2aXNvclR5cGUgPSBHZXRTdHJpbmcoZGF0YSwgImFkdmlzb3JUeXBlIik7CgogICAgICAgIGJvb2wgaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tID0gZmFsc2U7CiAgICAgICAgYm9vbCBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcyA9IGZhbHNlOwogICAgICAgIHN0cmluZyBleGlzdGluZ0NoYXRSb29tSW5zdGFuY2VLZXkgPSBudWxsOwogICAgICAgIHN0cmluZyBmaXJzdFJvb21XaXRoU2FtZUFkdmlzb3JUeXBlS2V5ID0gbnVsbDsKCiAgICAgICAgaWYgKHJlc3BvbnNlPy5pc1N1Y2Nlc3MgPT0gdHJ1ZSkKICAgICAgICB7CiAgICAgICAgICAgIHZhciByZXNwb25zZURhdGEgPSByZXNwb25zZT8uZGF0YSA/PyByZXNwb25zZT8uRGF0YSA/PyByZXNwb25zZTsKICAgICAgICAgICAgdmFyIHJvb21zID0gR2V0Um9vbXNMaXN0KHJlc3BvbnNlRGF0YSk7CiAgICAgICAgICAgIGlmIChyb29tcyAhPSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICBmb3JlYWNoICh2YXIgcm9vbSBpbiByb29tcykKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbUFkdmlzb3JUeXBlID0gR2V0U3RyaW5nKHJvb20sICJhZHZpc29yVHlwZSIpOwogICAgICAgICAgICAgICAgICAgIGlmICghc3RyaW5nLkVxdWFscyhyb29tQWR2aXNvclR5cGU/LlRyaW0oKSwgYWR2aXNvclR5cGU/LlRyaW0oKSwgU3RyaW5nQ29tcGFyaXNvbi5PcmRpbmFsSWdub3JlQ2FzZSkpCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlOwoKICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbUtleSA9IEdldFN0cmluZyhyb29tLCAiaW5zdGFuY2VLZXkiKSA/PyBHZXRTdHJpbmcocm9vbSwgImtleSIpOwogICAgICAgICAgICAgICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JXaGl0ZVNwYWNlKHJvb21LZXkpKSBjb250aW51ZTsKCiAgICAgICAgICAgICAgICAgICAgaWYgKGZpcnN0Um9vbVdpdGhTYW1lQWR2aXNvclR5cGVLZXkgPT0gbnVsbCkKICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3RSb29tV2l0aFNhbWVBZHZpc29yVHlwZUtleSA9IHJvb21LZXk7CgogICAgICAgICAgICAgICAgICAgIGhhc0V4aXN0aW5nUGVybWFuZW50Um9vbSA9IHRydWU7CgogICAgICAgICAgICAgICAgICAgIHZhciByb29tQWR2aXNvcklkID0gR2V0U3RyaW5nKHJvb20sICJhZHZpc29ySWQiKTsKICAgICAgICAgICAgICAgICAgICBpZiAoc3RyaW5nLkVxdWFscyhyb29tQWR2aXNvcklkPy5UcmltKCksIGFkdmlzb3JJZD8uVHJpbSgpLCBTdHJpbmdDb21wYXJpc29uLk9yZGluYWxJZ25vcmVDYXNlKSkKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgIGV4aXN0aW5nUm9vbUFkdmlzb3JNYXRjaGVzID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID0gcm9vbUtleTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmIChoYXNFeGlzdGluZ1Blcm1hbmVudFJvb20gJiYgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID09IG51bGwpCiAgICAgICAgICAgICAgICAgICAgZXhpc3RpbmdDaGF0Um9vbUluc3RhbmNlS2V5ID0gZmlyc3RSb29tV2l0aFNhbWVBZHZpc29yVHlwZUtleTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UKICAgICAgICB7CiAgICAgICAgICAgIERhdGEgPSBuZXcKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tID0gaGFzRXhpc3RpbmdQZXJtYW5lbnRSb29tLAogICAgICAgICAgICAgICAgZXhpc3RpbmdSb29tQWR2aXNvck1hdGNoZXMgPSBleGlzdGluZ1Jvb21BZHZpc29yTWF0Y2hlcywKICAgICAgICAgICAgICAgIGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleSA9IGV4aXN0aW5nQ2hhdFJvb21JbnN0YW5jZUtleQogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICB9CgogICAgcHJpdmF0ZSBTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmFibGUgR2V0Um9vbXNMaXN0KGR5bmFtaWMgcmVzcG9uc2VEYXRhKQogICAgewogICAgICAgIGlmIChyZXNwb25zZURhdGEgPT0gbnVsbCkgcmV0dXJuIG51bGw7CiAgICAgICAgdHJ5CiAgICAgICAgewogICAgICAgICAgICAvLyByZXNwb25zZURhdGEuZ2V0Q2hhdFJvb21zLnJvb21zCiAgICAgICAgICAgIHZhciBkaXJlY3RDaGF0Um9vbXMgPSBHZXRWYWx1ZShyZXNwb25zZURhdGEsICJnZXRDaGF0Um9vbXMiKTsKICAgICAgICAgICAgaWYgKGRpcmVjdENoYXRSb29tcyAhPSBudWxsICYmIEhhc1Byb3BlcnR5KGRpcmVjdENoYXRSb29tcywgInJvb21zIikgJiYgZGlyZWN0Q2hhdFJvb21zLnJvb21zICE9IG51bGwpCiAgICAgICAgICAgICAgICByZXR1cm4gZGlyZWN0Q2hhdFJvb21zLnJvb21zOwoKICAgICAgICAgICAgLy8gV3JhcHBlZDogY2hlY2tFeGlzdGluZ0RhdGEuaXRlbXNbXS5nZXRDaGF0Um9vbXMucm9vbXMgKHJ1bnRpbWUgdGFzayByZXN1bHQgc2hhcGUpCiAgICAgICAgICAgIHZhciBjaGVja0V4aXN0aW5nID0gR2V0VmFsdWUocmVzcG9uc2VEYXRhLCAiY2hlY2tFeGlzdGluZ0RhdGEiKSA/PyBHZXRWYWx1ZShyZXNwb25zZURhdGEsICJkYXRhIik7CiAgICAgICAgICAgIHZhciByb290ID0gY2hlY2tFeGlzdGluZyA/PyByZXNwb25zZURhdGE7CiAgICAgICAgICAgIGlmIChyb290ICE9IG51bGwgJiYgSGFzUHJvcGVydHkocm9vdCwgIml0ZW1zIikgJiYgcm9vdC5pdGVtcyAhPSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICB2YXIgaXRlbXMgPSByb290Lml0ZW1zIGFzIFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYWJsZTsKICAgICAgICAgICAgICAgIGlmIChpdGVtcyAhPSBudWxsKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZvcmVhY2ggKHZhciBpdGVtIGluIGl0ZW1zKQogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGl0ZW0gPT0gbnVsbCkgY29udGludWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBnZXRDaGF0Um9vbXMgPSBHZXRWYWx1ZShpdGVtLCAiZ2V0Q2hhdFJvb21zIik7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChnZXRDaGF0Um9vbXMgIT0gbnVsbCAmJiBIYXNQcm9wZXJ0eShnZXRDaGF0Um9vbXMsICJyb29tcyIpKQogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgcm9vbXMgPSBHZXRWYWx1ZShnZXRDaGF0Um9vbXMsICJyb29tcyIpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHJvb21zIGlzIFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYWJsZSBlbiAmJiBlbiAhPSBudWxsKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBlbjsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjYXRjaCB7IH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KCiAgICBwcml2YXRlIG9iamVjdCBHZXRWYWx1ZShkeW5hbWljIG9iaiwgc3RyaW5nIG5hbWUpCiAgICB7CiAgICAgICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gbnVsbDsKICAgICAgICB0cnkKICAgICAgICB7CiAgICAgICAgICAgIGlmIChIYXNQcm9wZXJ0eShvYmosIG5hbWUpKQogICAgICAgICAgICAgICAgcmV0dXJuIEdldFByb3BlcnR5VmFsdWUob2JqLCBuYW1lKTsKICAgICAgICB9CiAgICAgICAgY2F0Y2ggeyB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgogICAgcHJpdmF0ZSBzdHJpbmcgR2V0U3RyaW5nKGR5bmFtaWMgb2JqLCBzdHJpbmcgbmFtZSkKICAgIHsKICAgICAgICBpZiAob2JqID09IG51bGwpIHJldHVybiBudWxsOwogICAgICAgIHRyeQogICAgICAgIHsKICAgICAgICAgICAgaWYgKEhhc1Byb3BlcnR5KG9iaiwgbmFtZSkpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIHZhciB2ID0gR2V0UHJvcGVydHlWYWx1ZShvYmosIG5hbWUpOwogICAgICAgICAgICAgICAgcmV0dXJuIHY/LlRvU3RyaW5nKCk/LlRyaW0oKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjYXRjaCB7IH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KfQo="
53
53
  }
54
54
  }
55
55
  ]
@@ -339,7 +339,7 @@ For appointments and **get-available-slots**, define **company working hours** f
339
339
  - In all examples, **`{{BaseUrl}}`** is your API gateway **root** (e.g. `https://<host>` or `https://<host>/ebanking` depending on deployment). Set it per APISIX / environment docs; avoid a trailing slash on the root or you may get **double slashes** (`//morph-touch`) when concatenating.
340
340
  - Standard template: **`{{BaseUrl}}/morph-touch/workflows/...`** and **`{{BaseUrl}}/morph-touch/functions/...`** (e.g. `{{BaseUrl}}/morph-touch/workflows/absence-entry/instances/start?sync=true`).
341
341
  - This matches the role of Postman’s `{{baseUrl}}`; this doc uses the name **`{{BaseUrl}}`**.
342
- - **`Authorization: Bearer <access_token>`:** The access token is **environment-specific** (dev, test, production). Obtain it from your identity provider or whatever process your environment documents (APISIX, API portal, security team). `<access_token>` in examples is only a placeholder; never commit real tokens to the repo or shared docs.
342
+ - **`Authorization: Bearer <access_token>`:** In many setups this header is **required**; whether it is mandatory can **vary by environment** (e.g. local dev may omit it per policy). The access token itself is **environment-specific** (dev, test, production). Obtain it from your identity provider or whatever process your environment documents (APISIX, API portal, security team). `<access_token>` in examples is only a placeholder; never commit real tokens to the repo or shared docs.
343
343
  - In `.../workflows/{flowKey}/instances/<segment>/...`, `<segment>` is **`{{instanceIdOrKey}}`**: either the platform **instance id** or the instance **`key`**.
344
344
 
345
345
  ### 14.3 Company working hours — `absence-entry` start (once)