biz-a-cli 2.3.80-15419 → 2.3.80
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/bin/app.js +20 -255
- package/bin/hub.js +0 -16
- package/bin/hubEvent.js +5 -98
- package/bin/tunnelTarget.js +0 -4
- package/engine/bpm/workflow.js +6 -7
- package/engine/cc/configValidate.js +0 -53
- package/engine/cc/guides/V2-SCREEN-AUTHORING.md +512 -531
- package/engine/cc/layer3Behavior.js +1 -2
- package/engine/cc/layer3Schema.js +1 -3
- package/engine/cc/livePipelines.js +3 -9
- package/engine/cc/publish.js +105 -86
- package/engine/domain/appConfig.js +18 -108
- package/engine/domain/applicationConfig.js +12 -87
- package/engine/domain/dialect.js +0 -138
- package/engine/domain/domainNavigation.js +50 -86
- package/engine/domain/entityConfigLoader.js +4 -16
- package/engine/domain/numericScale.js +0 -2
- package/engine/domain/system.js +0 -2
- package/engine/ext/publicTheme.js +1 -8
- package/engine/orm/apiRoute.js +61 -187
- package/engine/orm/entityConfig.js +2 -32
- package/engine/orm/finaAuth.js +0 -0
- package/engine/orm/finaEndpoint.js +0 -19
- package/engine/orm/imgQuery.js +0 -92
- package/engine/orm/listQuery.js +1 -6
- package/package.json +1 -1
- package/worker/cliScriptWorker.js +0 -5
- package/engine/attachments/store.js +0 -195
- package/engine/domain/domainRegistration.js +0 -65
- package/engine/fina/approval.js +0 -141
- package/engine/fina/approvalService.js +0 -59
- package/engine/fina/attachments.js +0 -165
- package/engine/fina/client.js +0 -84
- package/engine/fina/config.js +0 -26
- package/engine/fina/context.js +0 -58
- package/engine/fina/document.js +0 -67
- package/engine/fina/documentWrite.js +0 -173
- package/engine/fina/documents/index.js +0 -28
- package/engine/fina/documents/jv.js +0 -105
- package/engine/fina/documents/jvWrite.js +0 -260
- package/engine/fina/documents/si.js +0 -40
- package/engine/fina/errors.js +0 -17
- package/engine/fina/features.js +0 -86
- package/engine/fina/guard.js +0 -102
- package/engine/fina/identity.js +0 -87
- package/engine/fina/live.js +0 -19
- package/engine/fina/lock.js +0 -35
- package/engine/fina/master.js +0 -331
- package/engine/fina/masters/coa.js +0 -104
- package/engine/fina/masters/currency.js +0 -39
- package/engine/fina/masters/department.js +0 -32
- package/engine/fina/masters/index.js +0 -21
- package/engine/fina/masters/project.js +0 -53
- package/engine/fina/period.js +0 -56
- package/engine/fina/print.js +0 -136
- package/engine/fina/readPolicy.js +0 -67
- package/engine/fina/recurring.js +0 -200
- package/engine/fina/route.js +0 -61
- package/engine/fina/routes/attachment.js +0 -41
- package/engine/fina/routes/document.js +0 -66
- package/engine/fina/routes/master.js +0 -26
- package/engine/fina/routes/period.js +0 -24
- package/engine/fina/routes/print.js +0 -19
- package/engine/fina/routes/recurring.js +0 -27
- package/engine/fina/routes/session.js +0 -23
- package/engine/fina/routes/setup.js +0 -58
- package/engine/fina/routes/submission.js +0 -46
- package/engine/fina/routes/template.js +0 -31
- package/engine/fina/settings.js +0 -159
- package/engine/fina/sql.js +0 -34
- package/engine/fina/submission.js +0 -290
- package/engine/fina/submissionCompare.js +0 -171
- package/engine/fina/submissionRepo.js +0 -114
- package/engine/fina/template.js +0 -118
- package/engine/fina/userMap.js +0 -79
- package/engine/orm/companyLocale.js +0 -74
- package/engine/orm/userApiDataset.js +0 -153
- package/migrations/postgres/1790600000000__fina.sql +0 -33
- package/migrations/postgres/1790700000000__attachment.sql +0 -29
- package/migrations/postgres/1790800000000__fina_jv_submission.sql +0 -41
package/engine/fina/print.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M4 — a document's printed pages (T-24–T-26; D9 as amended 2026-10-10): FINA renders, Biz-A shows them.
|
|
3
|
-
*
|
|
4
|
-
* The FINA API answers the report as JPEGs CONCATENATED in one byte array, one per page (contract §3); an 80-line JV
|
|
5
|
-
* came back as 20 pages, with a stray byte or two between some of them. They are split here by walking the JPEG
|
|
6
|
-
* markers — never by searching for FFD8/FFD9, which also occur inside a segment (an EXIF thumbnail is a whole JPEG).
|
|
7
|
-
*
|
|
8
|
-
* ⚠️ ANY TEMPLATE FOR A DOCUMENT. The API loads the layout with `select layout from template where templateid=(select
|
|
9
|
-
* templateid from <table> where <keyfield>=<id>)`, built as a string (FINAMethod.pas:1125). Table TEMPLATE / keyfield
|
|
10
|
-
* TEMPLATEID / id = the chosen template therefore renders that template, for the document the dictionary names
|
|
11
|
-
* (verified live: byte-identical to the normal path). Every one of those values is built here, from integers checked
|
|
12
|
-
* against the document's own template type.
|
|
13
|
-
*
|
|
14
|
-
* The page size: JFIF density in dots per inch when the JPEG says so, else 96 dpi — FINA's pages carry an aspect ratio
|
|
15
|
-
* only (density 1:1).
|
|
16
|
-
*/
|
|
17
|
-
import { finaError } from "./errors.js";
|
|
18
|
-
import { requireRight } from "./identity.js";
|
|
19
|
-
import { documentOf, documentId } from "./documents/index.js";
|
|
20
|
-
import { readMaster } from "./document.js";
|
|
21
|
-
import { listTemplates, assertTemplateOfType } from "./template.js";
|
|
22
|
-
|
|
23
|
-
const IMAGE_QUALITY = 85;
|
|
24
|
-
const SCREEN_DPI = 96;
|
|
25
|
-
|
|
26
|
-
/* Markers with no length: SOI, EOI, TEM, RST0–7. */
|
|
27
|
-
const standalone = (marker) => marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7);
|
|
28
|
-
|
|
29
|
-
/* One JPEG starting at `start` (its SOI): the index just past its EOI, or -1 if it does not end. */
|
|
30
|
-
const jpegEnd = (bytes, start) => {
|
|
31
|
-
let i = start + 2;
|
|
32
|
-
while (i + 1 < bytes.length) {
|
|
33
|
-
if (bytes[i] !== 0xff) return -1;
|
|
34
|
-
const marker = bytes[i + 1];
|
|
35
|
-
if (marker === 0xff) {
|
|
36
|
-
i += 1; /* fill byte */
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (marker === 0xd9) return i + 2;
|
|
40
|
-
if (standalone(marker)) {
|
|
41
|
-
i += 2;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (i + 3 >= bytes.length) return -1;
|
|
45
|
-
const length = (bytes[i + 2] << 8) | bytes[i + 3];
|
|
46
|
-
i += 2 + length;
|
|
47
|
-
if (marker === 0xda) {
|
|
48
|
-
/* Entropy-coded data: FF00 is a stuffed byte and FFD0–FFD7 a restart; anything else is the next marker. */
|
|
49
|
-
while (i + 1 < bytes.length) {
|
|
50
|
-
if (bytes[i] === 0xff && bytes[i + 1] !== 0x00 && !(bytes[i + 1] >= 0xd0 && bytes[i + 1] <= 0xd7)) break;
|
|
51
|
-
i += 1;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return -1;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const splitJpegs = (input) => {
|
|
59
|
-
const bytes = input instanceof Uint8Array ? input : Uint8Array.from(input ?? []);
|
|
60
|
-
const pages = [];
|
|
61
|
-
let i = 0;
|
|
62
|
-
while (i + 1 < bytes.length) {
|
|
63
|
-
if (bytes[i] === 0xff && bytes[i + 1] === 0xd8) {
|
|
64
|
-
const end = jpegEnd(bytes, i);
|
|
65
|
-
if (end < 0) break;
|
|
66
|
-
pages.push(bytes.subarray(i, end));
|
|
67
|
-
i = end;
|
|
68
|
-
} else {
|
|
69
|
-
i += 1; /* bytes between pages */
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return pages;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/* Pixel size from the frame header; density from JFIF when its unit is dots per inch (1) or per cm (2). */
|
|
76
|
-
export const jpegSize = (jpeg) => {
|
|
77
|
-
let widthPx = null;
|
|
78
|
-
let heightPx = null;
|
|
79
|
-
let dpi = null;
|
|
80
|
-
let i = 2;
|
|
81
|
-
while (i + 3 < jpeg.length && jpeg[i] === 0xff) {
|
|
82
|
-
const marker = jpeg[i + 1];
|
|
83
|
-
if (standalone(marker)) {
|
|
84
|
-
i += 2;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const length = (jpeg[i + 2] << 8) | jpeg[i + 3];
|
|
88
|
-
if (marker === 0xe0 && jpeg[i + 4] === 0x4a && jpeg[i + 5] === 0x46 && jpeg[i + 6] === 0x49 && jpeg[i + 7] === 0x46) {
|
|
89
|
-
const units = jpeg[i + 11];
|
|
90
|
-
const x = (jpeg[i + 12] << 8) | jpeg[i + 13];
|
|
91
|
-
if (units === 1 && x > 1) dpi = x;
|
|
92
|
-
if (units === 2 && x > 1) dpi = x * 2.54;
|
|
93
|
-
}
|
|
94
|
-
/* SOF0–SOF15, except DHT (C4), JPG (C8) and DAC (CC). */
|
|
95
|
-
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
|
|
96
|
-
heightPx = (jpeg[i + 5] << 8) | jpeg[i + 6];
|
|
97
|
-
widthPx = (jpeg[i + 7] << 8) | jpeg[i + 8];
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
if (marker === 0xda) break;
|
|
101
|
-
i += 2 + length;
|
|
102
|
-
}
|
|
103
|
-
return { widthPx, heightPx, dpi };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const mm = (px, dpi) => Math.round(((px * 25.4) / dpi) * 100) / 100;
|
|
107
|
-
export const pageMm = ({ widthPx, heightPx, dpi }) => {
|
|
108
|
-
if (!widthPx || !heightPx) return { widthMm: null, heightMm: null };
|
|
109
|
-
const d = dpi ?? SCREEN_DPI;
|
|
110
|
-
return { widthMm: mm(widthPx, d), heightMm: mm(heightPx, d) };
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const createPrintService = ({ client, identity }) => ({
|
|
114
|
-
render: async (principal, name, { id: rawId, templateId: rawTemplateId } = {}) => {
|
|
115
|
-
const document = documentOf(name);
|
|
116
|
-
const id = documentId(rawId);
|
|
117
|
-
const who = requireRight(await identity.resolve(principal), document.rights, "print");
|
|
118
|
-
|
|
119
|
-
const master = await readMaster(client, document, id);
|
|
120
|
-
if (!master) throw finaError("DOCUMENT_NOT_FOUND", `${document.label} ${id} was not found.`);
|
|
121
|
-
const templates = await listTemplates(client, document.templateType);
|
|
122
|
-
const asked = rawTemplateId ?? master.templateid;
|
|
123
|
-
const templateId = asked === null || asked === undefined || asked === "" ? null : assertTemplateOfType(templates, asked);
|
|
124
|
-
|
|
125
|
-
const target = templateId !== null ? { table: "TEMPLATE", keyfield: "TEMPLATEID", id: String(templateId) } : { table: document.master.table, keyfield: document.master.key, id: String(id) };
|
|
126
|
-
const bytes = await client.template({
|
|
127
|
-
...target,
|
|
128
|
-
userId: who.finaUser?.id ?? 0,
|
|
129
|
-
imageQuality: IMAGE_QUALITY,
|
|
130
|
-
dictionary: await document.print.dictionary({ client, id }),
|
|
131
|
-
});
|
|
132
|
-
const pages = splitJpegs(bytes).map((page) => ({ data: Buffer.from(page).toString("base64"), ...pageMm(jpegSize(page)) }));
|
|
133
|
-
if (pages.length === 0) throw finaError("FINA_ERROR", `FINA rendered no pages for ${document.label} ${id}.`);
|
|
134
|
-
return { id, templateId, pages };
|
|
135
|
-
},
|
|
136
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M8 — S-02, the read side: a browser may READ the FINA Cloud database only as a Biz-A user mapped to an active
|
|
3
|
-
* FINA user (D4: view = every mapped user). Agung 2026-10-15.
|
|
4
|
-
*
|
|
5
|
-
* The write guard (guard.js) refuses every FINA write that skips engine/fina. Reads were left open to any signed-in
|
|
6
|
-
* user of the tenant — every FINA table but USERS — whether or not FINA knew them.
|
|
7
|
-
*
|
|
8
|
-
* - Only a FINA-bound READ is checked (guard.js `finaBoundRead`); writes stay the guard's, other databases and logins
|
|
9
|
-
* pass untouched, and with FINA Cloud off this does nothing.
|
|
10
|
-
* - The principal is the VERIFIED one (context.js `finaContext`): none, one signed for another database, or a revoked
|
|
11
|
-
* session → refused, in every principal mode.
|
|
12
|
-
* - The verdict is kept per (tenant database, user) for `ttlMs` (a minute): a grid's lookups do not each ask FINA, and
|
|
13
|
-
* unmapping takes effect within the minute. A check that fails (FINA unreachable) refuses and is not remembered.
|
|
14
|
-
*
|
|
15
|
-
* Runs at BOTH browser entrances, right after the write guard: bin/hubEvent.js (socket) and engine/orm/finaEndpoint.js
|
|
16
|
-
* (HTTP). The two transports must not diverge.
|
|
17
|
-
*/
|
|
18
|
-
import { finaBoundRead, FINA_READ_REFUSED } from "./guard.js";
|
|
19
|
-
|
|
20
|
-
const NOT_MAPPED = "Your Biz-A user is not mapped to a FINA user. An administrator maps it in the FINA Cloud console.";
|
|
21
|
-
|
|
22
|
-
const refused = (reason) => ({ status: 403, body: { code: FINA_READ_REFUSED, error: `FINA Cloud: reading the FINA database is refused. ${reason}` } });
|
|
23
|
-
|
|
24
|
-
export const createFinaReadPolicy = ({ finaCloudDbIndex, contextOf, isMapped, now = () => Date.now(), ttlMs = 60_000 }) => {
|
|
25
|
-
const target = Number(finaCloudDbIndex);
|
|
26
|
-
const on = finaCloudDbIndex !== null && finaCloudDbIndex !== undefined && finaCloudDbIndex !== "" && Number.isInteger(target) && target > 0;
|
|
27
|
-
const verdicts = new Map();
|
|
28
|
-
|
|
29
|
-
return async ({ path, body, headers }) => {
|
|
30
|
-
if (!on || !finaBoundRead(path, body, target)) return null;
|
|
31
|
-
|
|
32
|
-
const context = await contextOf(headers ?? {});
|
|
33
|
-
if (!context?.ok) return refused(context?.error ?? "No signed-in Biz-A user came with this request. Sign in again.");
|
|
34
|
-
|
|
35
|
-
const key = `${context.config?.tenantDbIndex ?? ""}:${context.principal?.userId ?? ""}`;
|
|
36
|
-
const at = now();
|
|
37
|
-
const kept = verdicts.get(key);
|
|
38
|
-
if (kept && at - kept.at < ttlMs) return kept.mapped ? null : refused(NOT_MAPPED);
|
|
39
|
-
|
|
40
|
-
let mapped;
|
|
41
|
-
try {
|
|
42
|
-
mapped = (await isMapped(context)) === true;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
return refused(String(error?.message ?? error));
|
|
45
|
-
}
|
|
46
|
-
verdicts.set(key, { at, mapped });
|
|
47
|
-
return mapped ? null : refused(NOT_MAPPED);
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/*
|
|
52
|
-
* The live policy for a hub: the same context the `fina/*` engine routes get (hubEvent `fina/` branch), and the
|
|
53
|
-
* identity those routes resolve (FINA$USER_MAP → an active FINA user). `readSession` is the hub's session reader.
|
|
54
|
-
*/
|
|
55
|
-
export const createLiveFinaReadPolicy = ({ argv = {}, isPostgresIndex, readSession = null }) =>
|
|
56
|
-
createFinaReadPolicy({
|
|
57
|
-
finaCloudDbIndex: argv?.finaCloudDbindex,
|
|
58
|
-
contextOf: async (headers) => {
|
|
59
|
-
const { finaContext } = await import("./context.js");
|
|
60
|
-
return finaContext(headers, { argv, isPostgresIndex, readSession });
|
|
61
|
-
},
|
|
62
|
-
isMapped: async (context) => {
|
|
63
|
-
const [{ createLiveFinaDeps }, { createIdentity }] = await Promise.all([import("./live.js"), import("./identity.js")]);
|
|
64
|
-
const identity = await createIdentity(createLiveFinaDeps(context.config)).resolve(context.principal);
|
|
65
|
-
return identity?.mapped === true;
|
|
66
|
-
},
|
|
67
|
-
});
|
package/engine/fina/recurring.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M4 — recurring detail lines (T-33–T-38, D8): FINA desktop's own table RECURRING, in its own format, so the same
|
|
3
|
-
* list serves both (T-36). From FINA's RecurringUtility.pas:
|
|
4
|
-
*
|
|
5
|
-
* - RECURRINGHEADER: the field names, joined by SEPARATOR ('|'); RECURRINGDETAIL: one line per row, joined by CRLF,
|
|
6
|
-
* the values in header order. The desktop reads a row by the header's names, without regard to case.
|
|
7
|
-
* - Money uses '.' as the decimal separator; a date is m/d/yyyy, or NULL; in a string, '|' becomes '»' and CRLF '•'.
|
|
8
|
-
* - A name (DESCRIPTION, up to 80) is unique per RECURRINGCLASS; FINA's desktop compares it as typed, Biz-A refuses a
|
|
9
|
-
* name in any case (T-34). There is no edit (T-37) and no schedule (T-38).
|
|
10
|
-
*
|
|
11
|
-
* The rights (D4): every mapped user lists and applies them; saving one needs the create right, deleting the edit right.
|
|
12
|
-
*/
|
|
13
|
-
import { finaError } from "./errors.js";
|
|
14
|
-
import { requireRight } from "./identity.js";
|
|
15
|
-
import { documentOf } from "./documents/index.js";
|
|
16
|
-
import { readMappings } from "./template.js";
|
|
17
|
-
import { sqlString } from "./sql.js";
|
|
18
|
-
|
|
19
|
-
const SEPARATOR = "|";
|
|
20
|
-
const SEPARATOR_REPLACE = "»";
|
|
21
|
-
const RETURN_REPLACE = "•";
|
|
22
|
-
const NULL_STRING = "NULL";
|
|
23
|
-
const MAX_NAME = 80;
|
|
24
|
-
/* One literal || literal chain in one statement: keep well inside FINA's statement limits. */
|
|
25
|
-
const MAX_DETAIL_CHARS = 30000;
|
|
26
|
-
|
|
27
|
-
const blank = (v) => v === null || v === undefined || (typeof v === "string" && v.trim() === "");
|
|
28
|
-
|
|
29
|
-
const fieldsOf = (recurring, extendedFields = []) => [
|
|
30
|
-
...recurring.fields,
|
|
31
|
-
...extendedFields.map((field) => ({ name: `${recurring.extendedPrefix}${String(field).toLowerCase()}`, key: `ext_${String(field).toLowerCase()}`, type: "string" })),
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const encodeValue = (value, type) => {
|
|
35
|
-
if (blank(value)) return type === "date" ? NULL_STRING : "";
|
|
36
|
-
if (type === "money") {
|
|
37
|
-
const n = Number(value);
|
|
38
|
-
return Number.isFinite(n) ? String(n) : "";
|
|
39
|
-
}
|
|
40
|
-
if (type === "date") {
|
|
41
|
-
const d = new Date(value);
|
|
42
|
-
return Number.isNaN(d.getTime()) ? NULL_STRING : `${d.getUTCMonth() + 1}/${d.getUTCDate()}/${d.getUTCFullYear()}`;
|
|
43
|
-
}
|
|
44
|
-
return String(value).split(SEPARATOR).join(SEPARATOR_REPLACE).replace(/\r\n/g, RETURN_REPLACE);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const decodeValue = (text, type) => {
|
|
48
|
-
if (text === undefined || text === null || text === "") return null;
|
|
49
|
-
if (type === "money") {
|
|
50
|
-
const n = Number(text);
|
|
51
|
-
return Number.isFinite(n) ? n : null;
|
|
52
|
-
}
|
|
53
|
-
if (type === "date") {
|
|
54
|
-
if (text === NULL_STRING) return null;
|
|
55
|
-
const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(text);
|
|
56
|
-
return m ? `${m[3]}-${m[1].padStart(2, "0")}-${m[2].padStart(2, "0")}` : null;
|
|
57
|
-
}
|
|
58
|
-
return String(text).split(SEPARATOR_REPLACE).join(SEPARATOR).split(RETURN_REPLACE).join("\r\n");
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/* `rows` keyed by the descriptor's keys → the header and the detail FINA stores. */
|
|
62
|
-
export const encodeRecurring = (recurring, rows, extendedFields = []) => {
|
|
63
|
-
const fields = fieldsOf(recurring, extendedFields);
|
|
64
|
-
return {
|
|
65
|
-
header: fields.map((f) => f.name).join(SEPARATOR),
|
|
66
|
-
detail: (rows ?? []).map((row) => fields.map((f) => encodeValue(row?.[f.key], f.type)).join(SEPARATOR)).join("\r\n"),
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/* A stored row → rows keyed by the descriptor's keys. Fields the descriptor does not know are left out; an amount
|
|
71
|
-
stored as the desktop's Debit / Credit is turned back into one signed amount. */
|
|
72
|
-
export const decodeRecurring = (recurring, { header, detail, separator }, extendedFields = []) => {
|
|
73
|
-
const sep = separator || SEPARATOR;
|
|
74
|
-
const names = String(header ?? "").split(sep).map((n) => n.trim().toUpperCase());
|
|
75
|
-
const fields = fieldsOf(recurring, extendedFields);
|
|
76
|
-
const byName = new Map(fields.map((f) => [f.name.toUpperCase(), f]));
|
|
77
|
-
const lines = String(detail ?? "")
|
|
78
|
-
.split(/\r\n|\n/)
|
|
79
|
-
.filter((line) => line.length > 0);
|
|
80
|
-
return lines.map((line) => {
|
|
81
|
-
const values = line.split(sep);
|
|
82
|
-
const row = {};
|
|
83
|
-
names.forEach((name, index) => {
|
|
84
|
-
const field = byName.get(name);
|
|
85
|
-
if (field) row[field.key] = decodeValue(values[index], field.type);
|
|
86
|
-
});
|
|
87
|
-
const from = recurring.amountFrom;
|
|
88
|
-
if (from && !(from.key in row)) {
|
|
89
|
-
const at = (name) => names.indexOf(name);
|
|
90
|
-
if (at(from.debit) >= 0 || at(from.credit) >= 0) {
|
|
91
|
-
const debit = Number(values[at(from.debit)] || 0);
|
|
92
|
-
const credit = Number(values[at(from.credit)] || 0);
|
|
93
|
-
row[from.key] = debit - credit;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return row;
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/* A document keeps recurring lines only when its descriptor says how. */
|
|
101
|
-
const recurringOf = (document) => {
|
|
102
|
-
if (!document.recurring) throw finaError("INVALID", `${document.label} keeps no recurring lines.`);
|
|
103
|
-
return document.recurring;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const recurringId = (value) => {
|
|
107
|
-
const id = Number(value);
|
|
108
|
-
if (!Number.isInteger(id) || id <= 0 || !/^\d+$/.test(String(value ?? "").trim())) {
|
|
109
|
-
throw finaError("INVALID", `A recurring id is a positive whole number, not ${JSON.stringify(value)}.`);
|
|
110
|
-
}
|
|
111
|
-
return id;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/* A string literal per line, joined by CRLF in SQL: a raw newline would end the statement on this transport. */
|
|
115
|
-
const detailSql = (detail) =>
|
|
116
|
-
detail
|
|
117
|
-
.split("\r\n")
|
|
118
|
-
.map((line) => sqlString(line))
|
|
119
|
-
.join(" || ASCII_CHAR(13) || ASCII_CHAR(10) || ");
|
|
120
|
-
|
|
121
|
-
export const createRecurringService = ({ client, identity }) => {
|
|
122
|
-
const detailFields = async (document) =>
|
|
123
|
-
(await readMappings(client, document.extended)).filter((m) => m.part === "detail").map((m) => m.field);
|
|
124
|
-
const byName = async (recurring, name) =>
|
|
125
|
-
(await client.executeSql(
|
|
126
|
-
`SELECT RECURRINGID FROM RECURRING WHERE UPPER(DESCRIPTION) = UPPER(${sqlString(name)}) AND RECURRINGCLASS = ${sqlString(recurring.class)}`,
|
|
127
|
-
)) ?? [];
|
|
128
|
-
const one = async (recurring, id) =>
|
|
129
|
-
(
|
|
130
|
-
(await client.executeSql(
|
|
131
|
-
`SELECT RECURRINGID, DESCRIPTION, RECURRINGHEADER, RECURRINGDETAIL, SEPARATOR FROM RECURRING WHERE RECURRINGID = ${id} AND RECURRINGCLASS = ${sqlString(recurring.class)}`,
|
|
132
|
-
)) ?? []
|
|
133
|
-
)[0] ?? null;
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
list: async (principal, name) => {
|
|
137
|
-
const document = documentOf(name);
|
|
138
|
-
recurringOf(document);
|
|
139
|
-
requireRight(await identity.resolve(principal), document.rights, null);
|
|
140
|
-
const rows =
|
|
141
|
-
(await client.executeSql(
|
|
142
|
-
`SELECT RECURRINGID, DESCRIPTION FROM RECURRING WHERE RECURRINGCLASS = ${sqlString(document.recurring.class)} ORDER BY DESCRIPTION`,
|
|
143
|
-
)) ?? [];
|
|
144
|
-
return rows.map((r) => ({ id: Number(r.recurringid), name: String(r.description ?? "") }));
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
get: async (principal, name, rawId) => {
|
|
148
|
-
const document = documentOf(name);
|
|
149
|
-
recurringOf(document);
|
|
150
|
-
const id = recurringId(rawId);
|
|
151
|
-
requireRight(await identity.resolve(principal), document.rights, null);
|
|
152
|
-
const row = await one(document.recurring, id);
|
|
153
|
-
if (!row) throw finaError("RECURRING_NOT_FOUND", `Recurring ${id} was not found.`);
|
|
154
|
-
const rows = decodeRecurring(
|
|
155
|
-
document.recurring,
|
|
156
|
-
{ header: row.recurringheader, detail: row.recurringdetail, separator: row.separator },
|
|
157
|
-
await detailFields(document),
|
|
158
|
-
);
|
|
159
|
-
return { id, name: String(row.description ?? ""), rows };
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
save: async (principal, name, { name: rawName, rows } = {}) => {
|
|
163
|
-
const document = documentOf(name);
|
|
164
|
-
recurringOf(document);
|
|
165
|
-
requireRight(await identity.resolve(principal), document.rights, "create");
|
|
166
|
-
const label = String(rawName ?? "").trim();
|
|
167
|
-
if (!label) throw finaError("INVALID", "A recurring needs a name.");
|
|
168
|
-
if (label.length > MAX_NAME) throw finaError("INVALID", `A recurring name is at most ${MAX_NAME} characters.`);
|
|
169
|
-
const lines = (Array.isArray(rows) ? rows : []).filter((row) => row && typeof row === "object");
|
|
170
|
-
if (lines.length === 0) throw finaError("RECURRING_EMPTY", "A recurring needs at least one detail line.");
|
|
171
|
-
|
|
172
|
-
const recurring = document.recurring;
|
|
173
|
-
if ((await byName(recurring, label)).length > 0) {
|
|
174
|
-
throw finaError("RECURRING_DUPLICATE", `A recurring named "${label}" already exists. Choose another name.`);
|
|
175
|
-
}
|
|
176
|
-
const { header, detail } = encodeRecurring(recurring, lines, await detailFields(document));
|
|
177
|
-
if (detail.length > MAX_DETAIL_CHARS) throw finaError("INVALID", "These detail lines are too long to keep as one recurring.");
|
|
178
|
-
|
|
179
|
-
await client.block(
|
|
180
|
-
"EXECUTE BLOCK AS BEGIN INSERT INTO RECURRING (RECURRINGID, TABLENAME, DESCRIPTION, RECURRINGCLASS, EXCEPTIONFIELD, SEPARATOR, RECURRINGHEADER, RECURRINGDETAIL) " +
|
|
181
|
-
`VALUES (GEN_ID(RECURRING_GEN, 1), ${sqlString(recurring.tableName)}, ${sqlString(label)}, ${sqlString(recurring.class)}, ` +
|
|
182
|
-
`${sqlString(recurring.exception)}, ${sqlString(SEPARATOR)}, ${sqlString(header)}, ${detailSql(detail)}); END`,
|
|
183
|
-
);
|
|
184
|
-
const saved = await byName(recurring, label);
|
|
185
|
-
return { id: saved.length ? Number(saved[0].recurringid) : null, name: label };
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
remove: async (principal, name, rawId) => {
|
|
189
|
-
const document = documentOf(name);
|
|
190
|
-
recurringOf(document);
|
|
191
|
-
const id = recurringId(rawId);
|
|
192
|
-
requireRight(await identity.resolve(principal), document.rights, "delete");
|
|
193
|
-
if (!(await one(document.recurring, id))) throw finaError("RECURRING_NOT_FOUND", `Recurring ${id} was not found.`);
|
|
194
|
-
await client.block(
|
|
195
|
-
`EXECUTE BLOCK AS BEGIN DELETE FROM RECURRING WHERE RECURRINGID = ${id} AND RECURRINGCLASS = ${sqlString(document.recurring.class)}; END`,
|
|
196
|
-
);
|
|
197
|
-
return { id };
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
};
|
package/engine/fina/route.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 — how every `fina/*` engine route runs.
|
|
3
|
-
*
|
|
4
|
-
* 1. The caller context (context.js) must be ok; its refusal is the answer.
|
|
5
|
-
* 2. An administrator-only route checks SYS$ADMINISTRATOR for the VERIFIED user before anything else happens.
|
|
6
|
-
* 3. The route's work runs with engine/fina's dependencies (live.js, or a test's).
|
|
7
|
-
*
|
|
8
|
-
* ⚠️ IT NEVER THROWS. The answer is `{ok:true, data}` or `{ok:false, code, error}`. A thrown engine command makes
|
|
9
|
-
* the client retry it over HTTP (data.service invokeEngine), which would run a FINA write twice.
|
|
10
|
-
*/
|
|
11
|
-
import { logger } from "../../logger.js";
|
|
12
|
-
|
|
13
|
-
const KNOWN = new Set([
|
|
14
|
-
"NOT_CONFIGURED",
|
|
15
|
-
"FINA_CLOUD_NEEDS_POSTGRES",
|
|
16
|
-
"UNAUTHENTICATED",
|
|
17
|
-
"FORBIDDEN",
|
|
18
|
-
"INVALID",
|
|
19
|
-
"FINA_UNREACHABLE",
|
|
20
|
-
"FINA_ERROR",
|
|
21
|
-
"FINA_REFUSED",
|
|
22
|
-
"FEATURE_IN_USE",
|
|
23
|
-
"FEATURES_CHANGED",
|
|
24
|
-
/* Doc 8 M3 — the masters (engine/fina/master.js). */
|
|
25
|
-
"MASTER_NOT_FOUND",
|
|
26
|
-
"MASTER_DUPLICATE",
|
|
27
|
-
"MASTER_IN_USE",
|
|
28
|
-
"MASTER_RULE",
|
|
29
|
-
"MASTER_PARENT_IN_USE",
|
|
30
|
-
/* Doc 8 M4 — documents, templates, print, recurring. */
|
|
31
|
-
"DOCUMENT_NOT_FOUND",
|
|
32
|
-
"TEMPLATE_NOT_FOUND",
|
|
33
|
-
"RECURRING_NOT_FOUND",
|
|
34
|
-
"RECURRING_DUPLICATE",
|
|
35
|
-
"RECURRING_EMPTY",
|
|
36
|
-
/* Doc 8 M5a — saving a document (documentWrite.js, period.js). */
|
|
37
|
-
"DOCUMENT_RULE",
|
|
38
|
-
"DOCUMENT_CHANGED",
|
|
39
|
-
"DATE_LOCKED",
|
|
40
|
-
"PERIOD_CLOSED",
|
|
41
|
-
/* Doc 8 M6 — attachments (engine/attachments/store.js, engine/fina/attachments.js). */
|
|
42
|
-
"ATTACHMENT_RULE",
|
|
43
|
-
"ATTACHMENT_NOT_FOUND",
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
export const runFinaRoute = async ({ name, context, admin = false }, work) => {
|
|
47
|
-
try {
|
|
48
|
-
if (!context) return { ok: false, code: "UNAUTHENTICATED", error: "No signed-in Biz-A user came with this request. Sign in again." };
|
|
49
|
-
if (context.ok !== true) return { ok: false, code: context.code ?? "UNAUTHENTICATED", error: context.error ?? "Refused." };
|
|
50
|
-
|
|
51
|
-
const deps = context.deps ?? (await import("./live.js")).createLiveFinaDeps(context.config);
|
|
52
|
-
if (admin && !(await deps.isAdministrator(context.principal?.userId))) {
|
|
53
|
-
return { ok: false, code: "FORBIDDEN", error: "Only an administrator may change the FINA Cloud setup." };
|
|
54
|
-
}
|
|
55
|
-
return { ok: true, data: await work({ deps, principal: context.principal, config: context.config }) };
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (KNOWN.has(error?.code)) return { ok: false, code: error.code, error: String(error.message ?? error) };
|
|
58
|
-
logger.error(`[fina] ${name ?? "route"} failed:`, error);
|
|
59
|
-
return { ok: false, code: "FAILED", error: String(error?.message ?? error) };
|
|
60
|
-
}
|
|
61
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M6 — a FINA document's attachments (F-34–F-37, L-11), kept in Biz-A (SYS$ATTACHMENT), never in FINA (F-36).
|
|
3
|
-
* The rule is engine/fina/attachments.js; the files are the platform store's (engine/attachments/store.js).
|
|
4
|
-
*
|
|
5
|
-
* - `list` `{ document, id }` → the document's files (id, fileName, mimeType, size, createdBy, createdAt).
|
|
6
|
-
* - `read` `{ document, id, attachmentId, offset }` → one chunk (base64, ≤ 1 MiB) and whether it was the last.
|
|
7
|
-
* - `begin` `{ document, id, fileName, size }` → the upload id, within the console limits (F-35).
|
|
8
|
-
* - `append` `{ document, id, uploadId, chunk }` / `finish` `{ document, id, uploadId }`.
|
|
9
|
-
* - `remove` `{ document, id, ids }`; `copy` `{ document, from, to, ids, fromOwner? }` (Salin, L-11).
|
|
10
|
-
* - Doc 8 M7: `{ submission: 'jv', id }` in place of `{ document, id }` is a JV submission's files; `fromOwner`
|
|
11
|
-
* (`{ document: 'jv' }` / `{ submission: 'jv' }`) names a copy's source when it is the other kind.
|
|
12
|
-
*
|
|
13
|
-
* ⚠️ Registered as an engine target: export routes only.
|
|
14
|
-
*/
|
|
15
|
-
import { runFinaRoute } from "../route.js";
|
|
16
|
-
import { createIdentity } from "../identity.js";
|
|
17
|
-
import { createSettingsStore } from "../settings.js";
|
|
18
|
-
import { createFinaAttachments } from "../attachments.js";
|
|
19
|
-
import { createAttachmentStore } from "../../attachments/store.js";
|
|
20
|
-
import { createSubmissionRepo } from "../submissionRepo.js";
|
|
21
|
-
|
|
22
|
-
const serviceOf = (deps) =>
|
|
23
|
-
createFinaAttachments({
|
|
24
|
-
client: deps.client,
|
|
25
|
-
identity: createIdentity(deps),
|
|
26
|
-
settings: createSettingsStore(deps),
|
|
27
|
-
store: createAttachmentStore({ exec: deps.exec }),
|
|
28
|
-
/* Doc 8 M7 — a JV submission's files (`{ submission: 'jv', id }`). */
|
|
29
|
-
submissions: createSubmissionRepo({ exec: deps.exec }),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const route = (method) => (payload, _argv, context) =>
|
|
33
|
-
runFinaRoute({ name: `attachment/${method}`, context }, ({ deps, principal }) => serviceOf(deps)[method](principal, payload ?? {}));
|
|
34
|
-
|
|
35
|
-
export const list = route("list");
|
|
36
|
-
export const read = route("read");
|
|
37
|
-
export const begin = route("begin");
|
|
38
|
-
export const append = route("append");
|
|
39
|
-
export const finish = route("finish");
|
|
40
|
-
export const remove = route("remove");
|
|
41
|
-
export const copy = route("copy");
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M4 / M5a — FINA documents for their forms.
|
|
3
|
-
*
|
|
4
|
-
* - `fina/document/get` `{ document, id }`: the master, its lines, their mapped extended values, and the `version` an
|
|
5
|
-
* edit is sent against. Any mapped FINA user (D4: view). Doc 8 M7b: a JV also says the submission it came from
|
|
6
|
-
* (`submission: { id, number } | null`).
|
|
7
|
-
* - `fina/document/save` `{ document, id|null, master, lines, version, requestId }` (M5a): written through the
|
|
8
|
-
* document's server-owned `write` block (documentWrite.js): right → approval → current → rules → Biz-A lock →
|
|
9
|
-
* FINA period → json2fina → number. Answers `{ id, number, warnings }`. Only the JV writes; the others are read only.
|
|
10
|
-
* - `fina/document/remove` `{ document, id, requestId }` (M5b): delete — the delete right (= FINA's edit, D4), the
|
|
11
|
-
* document as FINA has it (gone, a reconciled line), the Biz-A lock and FINA's period on its date. `{ id, number }`.
|
|
12
|
-
* - `fina/document/patch` `{ document, id, master, requestId }` (M5b, Ubah terpilih): the header only (date,
|
|
13
|
-
* description, mapped master extended fields), then the single edit's path with the document's own lines.
|
|
14
|
-
*
|
|
15
|
-
* ⚠️ Registered as an engine target: export routes only.
|
|
16
|
-
*/
|
|
17
|
-
import { runFinaRoute } from "../route.js";
|
|
18
|
-
import { createIdentity } from "../identity.js";
|
|
19
|
-
import { createDocumentService } from "../document.js";
|
|
20
|
-
import { createDocumentWriter } from "../documentWrite.js";
|
|
21
|
-
import { createSettingsStore } from "../settings.js";
|
|
22
|
-
import { createFeatures } from "../features.js";
|
|
23
|
-
import { createAttachmentStore } from "../../attachments/store.js";
|
|
24
|
-
import { createSubmissionRepo } from "../submissionRepo.js";
|
|
25
|
-
|
|
26
|
-
export const get = (payload, _argv, context) =>
|
|
27
|
-
runFinaRoute({ name: "document/get", context }, ({ deps, principal }) =>
|
|
28
|
-
createDocumentService({
|
|
29
|
-
client: deps.client,
|
|
30
|
-
identity: createIdentity(deps),
|
|
31
|
-
/* Doc 8 M7b (L-02): the submission a JV came from, so its form can open the two side by side. */
|
|
32
|
-
submissionOf: (id) => createSubmissionRepo({ exec: deps.exec }).findByJv(id),
|
|
33
|
-
}).get(principal, payload?.document, payload?.id),
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const writerOf = (deps) =>
|
|
37
|
-
createDocumentWriter({
|
|
38
|
-
client: deps.client,
|
|
39
|
-
identity: createIdentity(deps),
|
|
40
|
-
settings: createSettingsStore(deps),
|
|
41
|
-
features: createFeatures(deps),
|
|
42
|
-
/* Doc 8 M6: a deleted document's attachments go with it. */
|
|
43
|
-
attachments: createAttachmentStore({ exec: deps.exec }),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
export const save = (payload, _argv, context) =>
|
|
47
|
-
runFinaRoute({ name: "document/save", context }, ({ deps, principal, config }) =>
|
|
48
|
-
writerOf(deps).save(
|
|
49
|
-
principal,
|
|
50
|
-
payload?.document,
|
|
51
|
-
{ id: payload?.id, master: payload?.master, lines: payload?.lines, version: payload?.version, requestId: payload?.requestId },
|
|
52
|
-
{ dbIndex: config?.dbIndex },
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
/* Doc 8 M5b — `remove` because `delete` is a reserved word (as for masters). */
|
|
57
|
-
export const remove = (payload, _argv, context) =>
|
|
58
|
-
runFinaRoute({ name: "document/remove", context }, ({ deps, principal, config }) =>
|
|
59
|
-
writerOf(deps).remove(principal, payload?.document, { id: payload?.id, requestId: payload?.requestId }, { dbIndex: config?.dbIndex }),
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
/* Doc 8 M5b — Ubah terpilih: the header of one document (M-13–M-17). */
|
|
63
|
-
export const patch = (payload, _argv, context) =>
|
|
64
|
-
runFinaRoute({ name: "document/patch", context }, ({ deps, principal, config }) =>
|
|
65
|
-
writerOf(deps).patch(principal, payload?.document, { id: payload?.id, master: payload?.master, requestId: payload?.requestId }, { dbIndex: config?.dbIndex }),
|
|
66
|
-
);
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M3 — `fina/master/*`: FINA's masters (COA, Departemen, Proyek, Kurs) for a verified user.
|
|
3
|
-
*
|
|
4
|
-
* Payload `{ master, key?, data? }` — `master` names one of engine/fina/masters (never a table). Every mapped user may
|
|
5
|
-
* read (D4); a write needs the master's FINA right, and every E-09 rule runs on the server (engine/fina/master.js).
|
|
6
|
-
* Lists read FINA directly through the platform list engine (reads pass the write guard); only one record is read here,
|
|
7
|
-
* for its form.
|
|
8
|
-
*
|
|
9
|
-
* ⚠️ Registered as an engine target: export routes only. `remove`, because `delete` is a reserved word.
|
|
10
|
-
*/
|
|
11
|
-
import { runFinaRoute } from "../route.js";
|
|
12
|
-
import { createIdentity } from "../identity.js";
|
|
13
|
-
import { createMasterService } from "../master.js";
|
|
14
|
-
|
|
15
|
-
const service = (deps) => createMasterService({ client: deps.client, identity: createIdentity(deps) });
|
|
16
|
-
|
|
17
|
-
export const get = (payload, _argv, context) =>
|
|
18
|
-
runFinaRoute({ name: "master/get", context }, ({ deps, principal }) => service(deps).get(principal, payload?.master, payload?.key));
|
|
19
|
-
|
|
20
|
-
export const save = (payload, _argv, context) =>
|
|
21
|
-
runFinaRoute({ name: "master/save", context }, ({ deps, principal }) =>
|
|
22
|
-
service(deps).save(principal, payload?.master, { key: payload?.key ?? null, data: payload?.data ?? {} }),
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
export const remove = (payload, _argv, context) =>
|
|
26
|
-
runFinaRoute({ name: "master/remove", context }, ({ deps, principal }) => service(deps).remove(principal, payload?.master, payload?.key));
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M5b — FINA's periods for a list (L-08).
|
|
3
|
-
*
|
|
4
|
-
* - `fina/period/check` `{ months: ['YYYY-MM', …] }` (at most 60): `{ months: { 'YYYY-MM': open } }`, from FINA's
|
|
5
|
-
* CHECK_PERIOD. Any mapped FINA user (D4: view). Daftar JV disables Ubah / Hapus on a row whose month is not open;
|
|
6
|
-
* the writer refuses them on its own (period.js), so this is only what the screen shows.
|
|
7
|
-
*
|
|
8
|
-
* ⚠️ Registered as an engine target: export routes only.
|
|
9
|
-
*/
|
|
10
|
-
import { runFinaRoute } from "../route.js";
|
|
11
|
-
import { createIdentity, requireRight } from "../identity.js";
|
|
12
|
-
import { periodStates } from "../period.js";
|
|
13
|
-
import { finaError } from "../errors.js";
|
|
14
|
-
|
|
15
|
-
const MAX_MONTHS = 60;
|
|
16
|
-
|
|
17
|
-
export const check = (payload, _argv, context) =>
|
|
18
|
-
runFinaRoute({ name: "period/check", context }, async ({ deps, principal }) => {
|
|
19
|
-
requireRight(await createIdentity(deps).resolve(principal), "jv", null);
|
|
20
|
-
const months = payload?.months;
|
|
21
|
-
if (Array.isArray(months) && months.length > MAX_MONTHS) throw finaError("INVALID", `At most ${MAX_MONTHS} months at a time.`);
|
|
22
|
-
const states = await periodStates(deps.client, months);
|
|
23
|
-
return { months: Object.fromEntries(Object.entries(states).map(([month, res]) => [month, res === 0])) };
|
|
24
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Doc 8 M4 — `fina/print/render`: a saved document's pages as FINA renders them (T-24–T-26), with any template of its
|
|
3
|
-
* type (the one the form shows). Payload `{ document, id, templateId? }`. Needs the FINA print right (R, D4).
|
|
4
|
-
* Answers `{ id, templateId, pages: [{ data: <base64 JPEG>, widthMm, heightMm }] }`; the client previews, prints or
|
|
5
|
-
* saves them as a PDF.
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ Registered as an engine target: export routes only.
|
|
8
|
-
*/
|
|
9
|
-
import { runFinaRoute } from "../route.js";
|
|
10
|
-
import { createIdentity } from "../identity.js";
|
|
11
|
-
import { createPrintService } from "../print.js";
|
|
12
|
-
|
|
13
|
-
export const render = (payload, _argv, context) =>
|
|
14
|
-
runFinaRoute({ name: "print/render", context }, ({ deps, principal }) =>
|
|
15
|
-
createPrintService({ client: deps.client, identity: createIdentity(deps) }).render(principal, payload?.document, {
|
|
16
|
-
id: payload?.id,
|
|
17
|
-
templateId: payload?.templateId,
|
|
18
|
-
}),
|
|
19
|
-
);
|