@xano/xanoscript-language-server 11.8.4 → 11.9.0
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/.claude/settings.local.json +2 -1
- package/cache/documentCache.js +58 -10
- package/lexer/comment.js +14 -24
- package/lexer/db.js +1 -2
- package/lexer/security.js +16 -0
- package/onCompletion/onCompletion.js +61 -1
- package/onDefinition/onDefinition.js +150 -0
- package/onDefinition/onDefinition.spec.js +313 -0
- package/onDidChangeContent/onDidChangeContent.js +53 -6
- package/onHover/functions.md +28 -0
- package/package.json +1 -1
- package/parser/base_parser.js +61 -3
- package/parser/clauses/middlewareClause.js +16 -0
- package/parser/definitions/columnDefinition.js +5 -0
- package/parser/functions/api/apiCallFn.js +5 -3
- package/parser/functions/controls/functionCallFn.js +5 -3
- package/parser/functions/controls/functionRunFn.js +61 -5
- package/parser/functions/controls/taskCallFn.js +5 -3
- package/parser/functions/db/captureFieldName.js +63 -0
- package/parser/functions/db/dbAddFn.js +5 -3
- package/parser/functions/db/dbAddOrEditFn.js +13 -3
- package/parser/functions/db/dbBulkAddFn.js +5 -3
- package/parser/functions/db/dbBulkDeleteFn.js +5 -3
- package/parser/functions/db/dbBulkPatchFn.js +5 -3
- package/parser/functions/db/dbBulkUpdateFn.js +5 -3
- package/parser/functions/db/dbDelFn.js +10 -3
- package/parser/functions/db/dbEditFn.js +13 -3
- package/parser/functions/db/dbGetFn.js +10 -3
- package/parser/functions/db/dbHasFn.js +9 -3
- package/parser/functions/db/dbPatchFn.js +10 -3
- package/parser/functions/db/dbQueryFn.js +29 -3
- package/parser/functions/db/dbSchemaFn.js +5 -3
- package/parser/functions/db/dbTruncateFn.js +5 -3
- package/parser/functions/middlewareCallFn.js +3 -1
- package/parser/functions/security/register.js +19 -9
- package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
- package/parser/functions/securityFn.js +2 -0
- package/parser/functions/varFn.js +1 -1
- package/parser/generic/asVariable.js +2 -0
- package/parser/generic/assignableVariableAs.js +1 -0
- package/parser/generic/assignableVariableProperty.js +5 -2
- package/parser/task_parser.js +2 -1
- package/parser/tests/task/valid_sources/create_leak.xs +165 -0
- package/parser/tests/variable_test/coverage_check.xs +293 -0
- package/parser/variableScanner.js +64 -0
- package/parser/variableValidator.js +44 -0
- package/parser/variableValidator.spec.js +179 -0
- package/server.js +206 -18
- package/utils.js +32 -0
- package/utils.spec.js +93 -1
- package/workspace/crossFileValidator.js +166 -0
- package/workspace/crossFileValidator.spec.js +654 -0
- package/workspace/referenceTracking.spec.js +420 -0
- package/workspace/workspaceIndex.js +149 -0
- package/workspace/workspaceIndex.spec.js +189 -0
|
@@ -2,6 +2,7 @@ import { CallToken } from "../../lexer/action.js";
|
|
|
2
2
|
import { StringLiteral } from "../../lexer/literal.js";
|
|
3
3
|
import { MiddlewareToken } from "../../lexer/middleware.js";
|
|
4
4
|
import { DotToken, Identifier } from "../../lexer/tokens.js";
|
|
5
|
+
import { getVarName } from "../generic/utils.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
*
|
|
@@ -15,10 +16,11 @@ export function middlewareCallFn($) {
|
|
|
15
16
|
$.CONSUME(DotToken); // "."
|
|
16
17
|
const fnToken = $.CONSUME(CallToken); // "call"
|
|
17
18
|
|
|
18
|
-
$.OR([
|
|
19
|
+
const nameToken = $.OR([
|
|
19
20
|
{ ALT: () => $.CONSUME(Identifier) }, // user
|
|
20
21
|
{ ALT: () => $.CONSUME1(StringLiteral) }, // "user auth"
|
|
21
22
|
]);
|
|
23
|
+
$.addReference("middleware", getVarName(nameToken), nameToken);
|
|
22
24
|
|
|
23
25
|
$.OPTION(() => {
|
|
24
26
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
@@ -8,7 +8,9 @@ import { securityCreateUuidFn } from "./securityCreateUuidFn.js";
|
|
|
8
8
|
import { securityDecryptFn } from "./securityDecryptFn.js";
|
|
9
9
|
import { securityEncryptFn } from "./securityEncryptFn.js";
|
|
10
10
|
import { securityJweDecodeFn } from "./securityJweDecodeFn.js";
|
|
11
|
+
import { securityJweDecodeLegacyFn } from "./securityJweDecodeLegacyFn.js";
|
|
11
12
|
import { securityJweEncodeFn } from "./securityJweEncodeFn.js";
|
|
13
|
+
import { securityJweEncodeLegacyFn } from "./securityJweEncodeLegacyFn.js";
|
|
12
14
|
import { securityJwsDecodeFn } from "./securityJwsDecodeFn.js";
|
|
13
15
|
import { securityJwsEncodeFn } from "./securityJwsEncodeFn.js";
|
|
14
16
|
import { securityRandomBytesFn } from "./securityRandomBytesFn.js";
|
|
@@ -21,44 +23,52 @@ import { securityRandomNumberFn } from "./securityRandomNumberFn.js";
|
|
|
21
23
|
export const register = ($) => {
|
|
22
24
|
$.securityCheckPasswordFn = $.RULE(
|
|
23
25
|
"securityCheckPasswordFn",
|
|
24
|
-
securityCheckPasswordFn($)
|
|
26
|
+
securityCheckPasswordFn($),
|
|
25
27
|
);
|
|
26
28
|
$.securityCreateAuthTokenFn = $.RULE(
|
|
27
29
|
"securityCreateAuthTokenFn",
|
|
28
|
-
securityCreateAuthTokenFn($)
|
|
30
|
+
securityCreateAuthTokenFn($),
|
|
29
31
|
);
|
|
30
32
|
$.securityCreateCurveKeyFn = $.RULE(
|
|
31
33
|
"securityCreateCurveKeyFn",
|
|
32
|
-
securityCreateCurveKeyFn($)
|
|
34
|
+
securityCreateCurveKeyFn($),
|
|
33
35
|
);
|
|
34
36
|
$.securityCreatePasswordFn = $.RULE(
|
|
35
37
|
"securityCreatePasswordFn",
|
|
36
|
-
securityCreatePasswordFn($)
|
|
38
|
+
securityCreatePasswordFn($),
|
|
37
39
|
);
|
|
38
40
|
$.securityCreateRsaKeyFn = $.RULE(
|
|
39
41
|
"securityCreateRsaKeyFn",
|
|
40
|
-
securityCreateRsaKeyFn($)
|
|
42
|
+
securityCreateRsaKeyFn($),
|
|
41
43
|
);
|
|
42
44
|
$.securityCreateSecretKeyFn = $.RULE(
|
|
43
45
|
"securityCreateSecretKeyFn",
|
|
44
|
-
securityCreateSecretKeyFn($)
|
|
46
|
+
securityCreateSecretKeyFn($),
|
|
45
47
|
);
|
|
46
48
|
$.securityCreateUuidFn = $.RULE(
|
|
47
49
|
"securityCreateUuidFn",
|
|
48
|
-
securityCreateUuidFn($)
|
|
50
|
+
securityCreateUuidFn($),
|
|
49
51
|
);
|
|
50
52
|
$.securityDecryptFn = $.RULE("securityDecryptFn", securityDecryptFn($));
|
|
51
53
|
$.securityEncryptFn = $.RULE("securityEncryptFn", securityEncryptFn($));
|
|
52
54
|
$.securityJweDecodeFn = $.RULE("securityJweDecodeFn", securityJweDecodeFn($));
|
|
53
55
|
$.securityJweEncodeFn = $.RULE("securityJweEncodeFn", securityJweEncodeFn($));
|
|
56
|
+
$.securityJweDecodeLegacyFn = $.RULE(
|
|
57
|
+
"securityJweDecodeLegacyFn",
|
|
58
|
+
securityJweDecodeLegacyFn($),
|
|
59
|
+
);
|
|
60
|
+
$.securityJweEncodeLegacyFn = $.RULE(
|
|
61
|
+
"securityJweEncodeLegacyFn",
|
|
62
|
+
securityJweEncodeLegacyFn($),
|
|
63
|
+
);
|
|
54
64
|
$.securityJwsDecodeFn = $.RULE("securityJwsDecodeFn", securityJwsDecodeFn($));
|
|
55
65
|
$.securityJwsEncodeFn = $.RULE("securityJwsEncodeFn", securityJwsEncodeFn($));
|
|
56
66
|
$.securityRandomBytesFn = $.RULE(
|
|
57
67
|
"securityRandomBytesFn",
|
|
58
|
-
securityRandomBytesFn($)
|
|
68
|
+
securityRandomBytesFn($),
|
|
59
69
|
);
|
|
60
70
|
$.securityRandomNumberFn = $.RULE(
|
|
61
71
|
"securityRandomNumberFn",
|
|
62
|
-
securityRandomNumberFn($)
|
|
72
|
+
securityRandomNumberFn($),
|
|
63
73
|
);
|
|
64
74
|
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { SingleQuotedStringLiteral,StringLiteral } from "../../../lexer/literal.js";
|
|
1
2
|
import { CreateAuthTokenToken } from "../../../lexer/security.js";
|
|
3
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
|
+
|
|
5
|
+
const stringTokens = new Set([StringLiteral.name, SingleQuotedStringLiteral.name]);
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
@@ -8,6 +12,7 @@ export function securityCreateAuthTokenFn($) {
|
|
|
8
12
|
$.sectionStack.push("securityCreateAuthTokenFn");
|
|
9
13
|
const fnToken = $.CONSUME(CreateAuthTokenToken); // "create_auth_token"
|
|
10
14
|
|
|
15
|
+
const captured = {};
|
|
11
16
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
12
17
|
ARGS: [
|
|
13
18
|
fnToken,
|
|
@@ -19,9 +24,26 @@ export function securityCreateAuthTokenFn($) {
|
|
|
19
24
|
"description?": "[string]",
|
|
20
25
|
"disabled?": "[boolean]",
|
|
21
26
|
},
|
|
27
|
+
captured,
|
|
22
28
|
],
|
|
23
29
|
});
|
|
24
30
|
|
|
31
|
+
$.ACTION(() => {
|
|
32
|
+
if (!captured.table) return;
|
|
33
|
+
const keyOffset = captured.table.key?.startOffset;
|
|
34
|
+
if (keyOffset == null) return;
|
|
35
|
+
const tokens = $.input;
|
|
36
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
37
|
+
if (tokens[i].startOffset === keyOffset) {
|
|
38
|
+
const valueToken = tokens[i + 2];
|
|
39
|
+
if (valueToken && stringTokens.has(valueToken.tokenType.name)) {
|
|
40
|
+
$.addReference("table", getVarName(valueToken), valueToken);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
25
47
|
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
26
48
|
$.sectionStack.pop();
|
|
27
49
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
|
|
2
|
+
import { JweDecodeLegacyToken } from "../../../lexer/security.js";
|
|
3
|
+
import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
7
|
+
*/
|
|
8
|
+
export function securityJweDecodeLegacyFn($) {
|
|
9
|
+
return () => {
|
|
10
|
+
$.sectionStack.push("securityJweDecodeLegacyFn");
|
|
11
|
+
const fnToken = $.CONSUME(JweDecodeLegacyToken); // "jwe_decode_legacy"
|
|
12
|
+
$.CONSUME(LCurly); // "{"
|
|
13
|
+
$.MANY(() => {
|
|
14
|
+
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
|
|
15
|
+
$.CONSUME1(Identifier); // "field_name"
|
|
16
|
+
$.CONSUME(EqualToken); // "="
|
|
17
|
+
$.SUBRULE($.expressionFn);
|
|
18
|
+
});
|
|
19
|
+
$.MANY1(() => $.CONSUME1(NewlineToken));
|
|
20
|
+
$.CONSUME(RCurly); // "}"
|
|
21
|
+
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
22
|
+
$.sectionStack.pop();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { lexDocument } from "../../../lexer/lexer.js";
|
|
4
|
+
import { parser } from "../../test_parser.js";
|
|
5
|
+
|
|
6
|
+
function parse(inputText) {
|
|
7
|
+
parser.reset();
|
|
8
|
+
const lexResult = lexDocument(inputText);
|
|
9
|
+
parser.input = lexResult.tokens;
|
|
10
|
+
parser.securityJweDecodeLegacyFn();
|
|
11
|
+
return parser;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("securityJweDecodeLegacyFn", () => {
|
|
15
|
+
it("securityJweDecodeLegacyFn accepts attributes and store value in a variable", () => {
|
|
16
|
+
const parser = parse(`jwe_decode_legacy {
|
|
17
|
+
token = $input.magic_token
|
|
18
|
+
key = $env.magic_jwt_secret
|
|
19
|
+
audience = "Xano"
|
|
20
|
+
key_algorithm = "A256KW"
|
|
21
|
+
content_algorithm = "A256CBC-HS512"
|
|
22
|
+
} as $decoded_magic_token`);
|
|
23
|
+
console.log(parser.errors);
|
|
24
|
+
expect(parser.errors).to.be.empty;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
|
|
2
|
+
import { JweEncodeLegacyToken } from "../../../lexer/security.js";
|
|
3
|
+
import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
7
|
+
*/
|
|
8
|
+
export function securityJweEncodeLegacyFn($) {
|
|
9
|
+
return () => {
|
|
10
|
+
$.sectionStack.push("securityJweEncodeLegacyFn");
|
|
11
|
+
const fnToken = $.CONSUME(JweEncodeLegacyToken); // "jwe_encode_legacy"
|
|
12
|
+
$.CONSUME(LCurly); // "{"
|
|
13
|
+
$.MANY(() => {
|
|
14
|
+
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
|
|
15
|
+
$.CONSUME1(Identifier); // "field_name"
|
|
16
|
+
$.CONSUME(EqualToken); // "="
|
|
17
|
+
$.SUBRULE($.expressionFn);
|
|
18
|
+
});
|
|
19
|
+
$.MANY1(() => $.CONSUME1(NewlineToken));
|
|
20
|
+
$.CONSUME(RCurly); // "}"
|
|
21
|
+
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
22
|
+
$.sectionStack.pop();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { lexDocument } from "../../../lexer/lexer.js";
|
|
4
|
+
import { parser } from "../../test_parser.js";
|
|
5
|
+
|
|
6
|
+
function parse(inputText) {
|
|
7
|
+
parser.reset();
|
|
8
|
+
const lexResult = lexDocument(inputText);
|
|
9
|
+
parser.input = lexResult.tokens;
|
|
10
|
+
parser.securityJweEncodeLegacyFn();
|
|
11
|
+
return parser;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("securityJweEncodeLegacyFn", () => {
|
|
15
|
+
it("securityJweEncodeLegacyFn accepts attributes and store value in a variable", () => {
|
|
16
|
+
const parser = parse(`jwe_encode_legacy {
|
|
17
|
+
payload = $jwt_payload
|
|
18
|
+
audience = "Xano"
|
|
19
|
+
key = $env.magic_jwt_secret
|
|
20
|
+
key_algorithm = "A256KW"
|
|
21
|
+
content_algorithm = "A256CBC-HS512"
|
|
22
|
+
} as $jwt`);
|
|
23
|
+
expect(parser.errors).to.be.empty;
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -20,6 +20,8 @@ export function securityFn($) {
|
|
|
20
20
|
{ ALT: () => $.SUBRULE($.securityEncryptFn) }, // security.encrypt
|
|
21
21
|
{ ALT: () => $.SUBRULE($.securityJweDecodeFn) }, // security.jwe_decode
|
|
22
22
|
{ ALT: () => $.SUBRULE($.securityJweEncodeFn) }, // security.jwe_encode
|
|
23
|
+
{ ALT: () => $.SUBRULE($.securityJweDecodeLegacyFn) }, // security.jwe_decode_legacy
|
|
24
|
+
{ ALT: () => $.SUBRULE($.securityJweEncodeLegacyFn) }, // security.jwe_encode_legacy
|
|
23
25
|
{ ALT: () => $.SUBRULE($.securityJwsDecodeFn) }, // security.jws_decode
|
|
24
26
|
{ ALT: () => $.SUBRULE($.securityJwsEncodeFn) }, // security.jws_encode
|
|
25
27
|
{ ALT: () => $.SUBRULE($.securityRandomBytesFn) }, // security.random_bytes
|
|
@@ -68,7 +68,7 @@ export function varFn($) {
|
|
|
68
68
|
// we can directly add the variable here but
|
|
69
69
|
// not necessary for the `assignableVariableProperty` below
|
|
70
70
|
// which will add it on its own
|
|
71
|
-
$.addVariable(variable.image, "unknown");
|
|
71
|
+
$.addVariable(variable.image, "unknown", variable);
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
},
|
|
@@ -13,7 +13,9 @@ export function asVariable($) {
|
|
|
13
13
|
$.OPTION(() => {
|
|
14
14
|
hasAs = true;
|
|
15
15
|
$.CONSUME(AsToken); // "as"
|
|
16
|
+
$.__assignableIsDeclaration = true;
|
|
16
17
|
$.SUBRULE($.assignableVariableProperty);
|
|
18
|
+
$.__assignableIsDeclaration = false;
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
if (!hasAs && parent) {
|
|
@@ -128,8 +128,11 @@ export function assignableVariableProperty($) {
|
|
|
128
128
|
},
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
// Only register as a declaration when used in an "as $var" context.
|
|
132
|
+
// Other callers (var.update, math.*, text.*) are usages, not declarations,
|
|
133
|
+
// and the token scanner will pick them up automatically.
|
|
134
|
+
if (variableName && $.__assignableIsDeclaration) {
|
|
135
|
+
$.addVariable(variableName, "unknown", variableToken);
|
|
133
136
|
}
|
|
134
137
|
};
|
|
135
138
|
}
|
package/parser/task_parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActiveToken } from "../lexer/api_group.js";
|
|
2
|
+
import { CommentToken } from "../lexer/comment.js";
|
|
2
3
|
import { EqualToken, LCurly, RCurly } from "../lexer/control.js";
|
|
3
4
|
import { StringLiteral } from "../lexer/literal.js";
|
|
4
5
|
import { DataSourceToken, TaskToken } from "../lexer/task.js";
|
|
@@ -32,7 +33,7 @@ export function taskDeclaration($) {
|
|
|
32
33
|
$.MANY(() => {
|
|
33
34
|
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
|
|
34
35
|
$.OR2([
|
|
35
|
-
{ ALT: () => $.
|
|
36
|
+
{ ALT: () => $.CONSUME(CommentToken) },
|
|
36
37
|
{
|
|
37
38
|
GATE: () => !hasActive,
|
|
38
39
|
ALT: () => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
task claim_reminders {
|
|
2
|
+
description = "Runs every 6 hours to send reminders for claims stalled at the assessment, review, or approval workflow stages."
|
|
3
|
+
|
|
4
|
+
stack {
|
|
5
|
+
debug.log {
|
|
6
|
+
value = "Claim reminders task started"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// --- Cutoff timestamps ---
|
|
10
|
+
|
|
11
|
+
var $forty_eight_hours_ago {
|
|
12
|
+
value = now|transform_timestamp:"-48 hours"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
var $twenty_four_hours_ago {
|
|
16
|
+
value = now|transform_timestamp:"-24 hours"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var $twelve_hours_ago {
|
|
20
|
+
value = now|transform_timestamp:"-12 hours"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var $assessment_reminder_count {
|
|
24
|
+
value = 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var $review_reminder_count {
|
|
28
|
+
value = 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var $approval_reminder_count {
|
|
32
|
+
value = 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// === Step 1: Garage Assessment Reminders (stalled > 48h) ===
|
|
36
|
+
|
|
37
|
+
db.query claim {
|
|
38
|
+
where = ($db.claim.workflow_status == "assessment" && $db.claim.updated_at < $forty_eight_hours_ago)
|
|
39
|
+
} as $assessment_claims
|
|
40
|
+
|
|
41
|
+
foreach ($assessment_claims) {
|
|
42
|
+
each as $claim {
|
|
43
|
+
try_catch {
|
|
44
|
+
try {
|
|
45
|
+
function.run "communication/send_notification" {
|
|
46
|
+
input = {
|
|
47
|
+
user_id : $claim.garage_id
|
|
48
|
+
claim_id: $claim.id
|
|
49
|
+
message : "Reminder: Vehicle assessment for claim " ~ $claim.claim_number ~ " is awaiting your action. Please complete the assessment report at your earliest convenience."
|
|
50
|
+
subject : "Assessment Reminder - Claim " ~ $claim.claim_number
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var.update $assessment_reminder_count {
|
|
55
|
+
value = $assessment_reminder_count + 1
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
debug.log {
|
|
60
|
+
value = {
|
|
61
|
+
error : "Failed to send assessment reminder"
|
|
62
|
+
claim_number: $claim.claim_number
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debug.log {
|
|
71
|
+
value = "Assessment reminders sent: " ~ $assessment_reminder_count
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// === Step 2: Agent Review Reminders (stalled > 24h) ===
|
|
75
|
+
|
|
76
|
+
db.query claim {
|
|
77
|
+
where = ($db.claim.workflow_status == "review" && $db.claim.updated_at < $twenty_four_hours_ago)
|
|
78
|
+
} as $review_claims
|
|
79
|
+
|
|
80
|
+
foreach ($review_claims) {
|
|
81
|
+
each as $claim {
|
|
82
|
+
try_catch {
|
|
83
|
+
try {
|
|
84
|
+
function.run "communication/send_notification" {
|
|
85
|
+
input = {
|
|
86
|
+
user_id : $claim.assigned_agent_id
|
|
87
|
+
claim_id: $claim.id
|
|
88
|
+
message : "Reminder: Claim " ~ $claim.claim_number ~ " is awaiting your review. Please complete your review to keep the claim moving forward."
|
|
89
|
+
subject : "Review Reminder - Claim " ~ $claim.claim_number
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var.update $review_reminder_count {
|
|
94
|
+
value = $review_reminder_count + 1
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
debug.log {
|
|
99
|
+
value = {
|
|
100
|
+
error : "Failed to send review reminder"
|
|
101
|
+
claim_number: $claim.claim_number
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
debug.log {
|
|
110
|
+
value = "Review reminders sent: " ~ $review_reminder_count
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// === Step 3: Agent Approval Reminders - URGENT (stalled > 12h) ===
|
|
114
|
+
|
|
115
|
+
db.query claim {
|
|
116
|
+
where = ($db.claim.workflow_status == "approval" && $db.claim.updated_at < $twelve_hours_ago)
|
|
117
|
+
} as $approval_claims
|
|
118
|
+
|
|
119
|
+
foreach ($approval_claims) {
|
|
120
|
+
each as $claim {
|
|
121
|
+
try_catch {
|
|
122
|
+
try {
|
|
123
|
+
function.run "communication/send_notification" {
|
|
124
|
+
input = {
|
|
125
|
+
user_id : $claim.assigned_agent_id
|
|
126
|
+
claim_id: $claim.id
|
|
127
|
+
message : "URGENT: Claim " ~ $claim.claim_number ~ " requires your immediate approval. This claim has been pending approval for over 12 hours. Please review and take action as soon as possible."
|
|
128
|
+
subject : "URGENT: Approval Required - Claim " ~ $claim.claim_number
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var.update $approval_reminder_count {
|
|
133
|
+
value = $approval_reminder_count + 1
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
debug.log {
|
|
138
|
+
value = {
|
|
139
|
+
error : "Failed to send approval reminder"
|
|
140
|
+
claim_number: $claim.claim_number
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
debug.log {
|
|
149
|
+
value = "Approval reminders sent: " ~ $approval_reminder_count
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// === Step 4: Final summary ===
|
|
153
|
+
|
|
154
|
+
debug.log {
|
|
155
|
+
value = {
|
|
156
|
+
assessment_reminders_sent: $assessment_reminder_count
|
|
157
|
+
review_reminders_sent : $review_reminder_count
|
|
158
|
+
approval_reminders_sent : $approval_reminder_count
|
|
159
|
+
total_reminders_sent : ($assessment_reminder_count + $review_reminder_count + $approval_reminder_count)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
schedule = [{starts_on: 2026-01-01 00:00:00+0000, freq: 21600}]
|
|
165
|
+
}
|