@things-factory/erp-systemever 10.1.117 → 10.1.118

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 (53) hide show
  1. package/README.md +44 -0
  2. package/dist-server/index.d.ts +8 -0
  3. package/dist-server/index.js +9 -4
  4. package/dist-server/index.js.map +1 -1
  5. package/dist-server/systemever-adapter.js +12 -20
  6. package/dist-server/systemever-adapter.js.map +1 -1
  7. package/dist-server/systemever-connector.d.ts +1 -0
  8. package/dist-server/systemever-connector.js +88 -0
  9. package/dist-server/systemever-connector.js.map +1 -0
  10. package/dist-server/systemever-query-policy.d.ts +30 -0
  11. package/dist-server/systemever-query-policy.js +37 -0
  12. package/dist-server/systemever-query-policy.js.map +1 -0
  13. package/dist-server/systemever-query-response.d.ts +2 -0
  14. package/dist-server/systemever-query-response.js +26 -0
  15. package/dist-server/systemever-query-response.js.map +1 -0
  16. package/dist-server/systemever-query-runner.d.ts +16 -0
  17. package/dist-server/systemever-query-runner.js +90 -0
  18. package/dist-server/systemever-query-runner.js.map +1 -0
  19. package/dist-server/systemever-query-task.d.ts +7 -0
  20. package/dist-server/systemever-query-task.js +65 -0
  21. package/dist-server/systemever-query-task.js.map +1 -0
  22. package/dist-server/systemever-schedule-task.d.ts +1 -0
  23. package/dist-server/systemever-schedule-task.js +35 -0
  24. package/dist-server/systemever-schedule-task.js.map +1 -0
  25. package/dist-server/systemever-work-order-query.d.ts +14 -0
  26. package/dist-server/systemever-work-order-query.js +157 -0
  27. package/dist-server/systemever-work-order-query.js.map +1 -0
  28. package/dist-server/systemever-work-order.d.ts +33 -0
  29. package/dist-server/systemever-work-order.js +160 -0
  30. package/dist-server/systemever-work-order.js.map +1 -0
  31. package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +15 -0
  32. package/logs/connections-2026-10-05-11.log +0 -0
  33. package/package.json +5 -3
  34. package/server/index.ts +8 -0
  35. package/server/systemever-connector.ts +88 -0
  36. package/server/systemever-query-policy.ts +47 -0
  37. package/server/systemever-query-response.ts +29 -0
  38. package/server/systemever-query-runner.ts +79 -0
  39. package/server/systemever-query-task.ts +62 -0
  40. package/server/systemever-schedule-task.ts +37 -0
  41. package/server/systemever-work-order-query.ts +157 -0
  42. package/server/systemever-work-order.ts +184 -0
  43. package/test/fixtures/work-order-query-request.json +153 -0
  44. package/test/fixtures/work-order-query.json +239 -0
  45. package/test/systemever-connector.test.ts +84 -0
  46. package/test/systemever-query-policy.test.ts +41 -0
  47. package/test/systemever-query-response.test.ts +37 -0
  48. package/test/systemever-query-runner.test.ts +73 -0
  49. package/test/systemever-query-task.test.ts +106 -0
  50. package/test/systemever-schedule-task.test.ts +46 -0
  51. package/test/systemever-work-order.test.ts +101 -0
  52. package/tsconfig.json +2 -0
  53. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,88 @@
1
+ import integration from '@things-factory/integration-base'
2
+ import { SystemEverQueryError } from './systemever-query-policy.js'
3
+ const { ConnectionManager, HeadlessConnector, lookupDomainEnvVar } = integration
4
+
5
+ /** Domain-isolated cookie reuse; one initial login and at most one login retry. */
6
+ class SystemEverConnector extends HeadlessConnector {
7
+ get taskPrefixes() { return ['systemever-query'] }
8
+ get description() { return 'SystemEver read-only browser connection' }
9
+ get parameterSpec() {
10
+ return super.parameterSpec.filter(parameter => ![
11
+ 'username', 'password', 'loginPagePath', 'loginApiUrl', 'forceLogin', 'retries'
12
+ ].includes(parameter.name))
13
+ }
14
+
15
+ async connect(connection: any) {
16
+ try {
17
+ const endpoint = new URL(connection.endpoint)
18
+ if (endpoint.origin !== 'https://mfg.systemevererp.com' || endpoint.username || endpoint.password ||
19
+ endpoint.pathname !== '/' || endpoint.search || endpoint.hash) {
20
+ throw new SystemEverQueryError('systemever-query-invalid-connection')
21
+ }
22
+ const [username, password] = await Promise.all([
23
+ lookupDomainEnvVar(connection.domain, 'ErpConnection::systemever::username'),
24
+ lookupDomainEnvVar(connection.domain, 'ErpConnection::systemever::password')
25
+ ])
26
+ if (!username || !password) {
27
+ throw new SystemEverQueryError('systemever-query-missing-credentials')
28
+ }
29
+ connection.params = { ...connection.params, username, password, retries: 2, forceLogin: false,
30
+ loginPagePath: '/Home/Oauth2OpenIdSignIn', loginApiUrl: null }
31
+ await super.connect(connection)
32
+ } catch (error) {
33
+ if (error instanceof SystemEverQueryError) throw error
34
+ throw new SystemEverQueryError('systemever-query-connection-failed')
35
+ }
36
+ }
37
+
38
+ protected isCookieValid(cookies: any[]) {
39
+ const now = Date.now() / 1000
40
+ return Array.isArray(cookies) && cookies.some(cookie => cookie.expires === -1 || cookie.expires > now)
41
+ }
42
+
43
+ async setupPage(page: any, uri: string, timeout: number) {
44
+ await super.setupPage(page, uri, timeout)
45
+ // Vendor console/request messages may contain session values. Do not forward them to logs.
46
+ page.removeAllListeners('console')
47
+ page.removeAllListeners('requestfailed')
48
+ }
49
+
50
+ async applyCookiesAndVerifySession(page: any, cookies: any[], loginInfo: any) {
51
+ await page.setCookie(...cookies)
52
+ await page.reload({ waitUntil: 'networkidle2', timeout: loginInfo.timeout })
53
+ const loggedIn = await page.evaluate(() => !!(window as any).Home?._base?.ConnInfo?.LoginSeq)
54
+ if (!loggedIn) await this.performLogin(page, '', loginInfo)
55
+ }
56
+
57
+ async performLogin(page: any, _uri: string, loginInfo: any) {
58
+ if (!loginInfo.username || !loginInfo.password) {
59
+ throw new SystemEverQueryError('systemever-query-missing-credentials')
60
+ }
61
+ for (let attempt = 0; attempt < 2; attempt++) {
62
+ try {
63
+ await page.goto('https://mfg.systemevererp.com/Home/Oauth2OpenIdSignIn', {
64
+ waitUntil: 'networkidle2', timeout: loginInfo.timeout
65
+ })
66
+ if (new URL(page.url()).origin !== 'https://mfg.systemevererp.com') {
67
+ throw new SystemEverQueryError('systemever-query-login-origin-mismatch')
68
+ }
69
+ const selectors = loginInfo.loginSelectors
70
+ const username = await this.resolveShadowDom(page, selectors.shadowDomSelectors, selectors.usernameSelector)
71
+ const password = await this.resolveShadowDom(page, selectors.shadowDomSelectors, selectors.passwordSelector)
72
+ const submit = await this.resolveShadowDom(page, selectors.shadowDomSelectors, selectors.submitSelector)
73
+ if (!username || !password || !submit) throw new Error('login-form-missing')
74
+ await username.type(loginInfo.username)
75
+ await password.type(loginInfo.password)
76
+ await submit.click()
77
+ await page.waitForFunction(() => !!(window as any).Home?._base?.ConnInfo?.LoginSeq, {
78
+ timeout: loginInfo.timeout
79
+ })
80
+ return
81
+ } catch {
82
+ if (attempt === 1) throw new SystemEverQueryError('systemever-query-login-failed')
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ ConnectionManager.registerConnector('systemever-connector', new SystemEverConnector())
@@ -0,0 +1,47 @@
1
+ /** ADR-0110: only verified read methods belong in the headless query task. */
2
+ const screens = Object.freeze({
3
+ 'work-order': Object.freeze({
4
+ pgmSeq: 500880,
5
+ serviceSeq: 501041,
6
+ methods: Object.freeze([
7
+ Object.freeze({ name: 'ConfirmQuery', methodSeq: 6 }),
8
+ Object.freeze({ name: 'Query', methodSeq: 1 })
9
+ ])
10
+ })
11
+ })
12
+
13
+ export class SystemEverQueryError extends Error {
14
+ readonly code: string
15
+
16
+ constructor(code: string, cause?: unknown) {
17
+ // Do not include input values: they may contain credentials or script text. A cause is named, not quoted —
18
+ // its message may carry the same values — and kept whole on `cause` for whoever reads the log.
19
+ const named = cause && typeof cause === 'object' ? String((cause as any).code || (cause as any).name || '') : ''
20
+ super(named ? `${code} (cause: ${named})` : code, cause === undefined ? undefined : { cause })
21
+ this.name = 'SystemEverQueryError'
22
+ this.code = code
23
+ }
24
+ }
25
+
26
+ export interface SystemEverQueryPlan {
27
+ screen: keyof typeof screens
28
+ pgmSeq: number
29
+ serviceSeq: number
30
+ methods: readonly { readonly name: string; readonly methodSeq: number }[]
31
+ }
32
+
33
+ /** No caller-provided service, method, URL or executable code is accepted. */
34
+ export function planSystemEverQuery(input: unknown): SystemEverQueryPlan {
35
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
36
+ throw new SystemEverQueryError('systemever-query-invalid-params')
37
+ }
38
+ const params = input as Record<string, unknown>
39
+ if (Object.keys(params).some(key => key !== 'screen')) {
40
+ throw new SystemEverQueryError('systemever-query-unsupported-param')
41
+ }
42
+ if (typeof params.screen !== 'string' || !Object.hasOwn(screens, params.screen)) {
43
+ throw new SystemEverQueryError('systemever-query-unknown-screen')
44
+ }
45
+ const screen = params.screen as keyof typeof screens
46
+ return Object.freeze({ screen, ...screens[screen] })
47
+ }
@@ -0,0 +1,29 @@
1
+ import { SystemEverQueryError } from './systemever-query-policy.js'
2
+
3
+ /** Decode a single named query block; never interpret a missing block as an empty result. */
4
+ export function decodeSystemEverQueryBlock(
5
+ response: unknown,
6
+ tableName: string,
7
+ requiredColumns: readonly string[]
8
+ ): Record<string, unknown>[] {
9
+ const tables = (response as any)?.JSonData?.Tables
10
+ if (!Array.isArray(tables)) throw new SystemEverQueryError('systemever-query-invalid-response')
11
+ const matches = tables.filter(table => table?.TableName === tableName)
12
+ if (matches.length !== 1) throw new SystemEverQueryError('systemever-query-block-mismatch')
13
+ const { Columns: columns, Rows: rows } = matches[0]
14
+ if (
15
+ !Array.isArray(columns) ||
16
+ columns.some(column => typeof column !== 'string' || !column) ||
17
+ new Set(columns).size !== columns.length ||
18
+ !Array.isArray(rows)
19
+ ) throw new SystemEverQueryError('systemever-query-invalid-table')
20
+ if (requiredColumns.some(column => !columns.includes(column))) {
21
+ throw new SystemEverQueryError('systemever-query-missing-columns')
22
+ }
23
+ return rows.map(row => {
24
+ if (!Array.isArray(row) || row.length !== columns.length) {
25
+ throw new SystemEverQueryError('systemever-query-invalid-row')
26
+ }
27
+ return Object.fromEntries(columns.map((column, index) => [column, row[index]]))
28
+ })
29
+ }
@@ -0,0 +1,79 @@
1
+ import { planSystemEverQuery, SystemEverQueryError } from './systemever-query-policy.js'
2
+ import { workOrderQueryData } from './systemever-work-order-query.js'
3
+ import { decodeSystemEverQueryBlock } from './systemever-query-response.js'
4
+ import { WORK_ORDER_COLUMNS } from './systemever-work-order.js'
5
+
6
+ /** Reject invalid task inputs before acquiring a browser or authenticating. */
7
+ export function prepareSystemEverQuery(input: unknown) {
8
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
9
+ throw new SystemEverQueryError('systemever-query-invalid-params')
10
+ }
11
+ const params = input as Record<string, any>
12
+ if (Object.keys(params).some(key => !['screen', 'dateFrom', 'dateTo', 'bizUnit'].includes(key))) {
13
+ throw new SystemEverQueryError('systemever-query-unsupported-param')
14
+ }
15
+ const plan = planSystemEverQuery({ screen: params.screen })
16
+ const data = workOrderQueryData(params as any)
17
+ return { plan, data }
18
+ }
19
+
20
+ /** Runs only the fixed vendor query methods inside an authenticated Puppeteer page. */
21
+ export async function runSystemEverQuery(page: any, input: unknown) {
22
+ const { plan, data } = prepareSystemEverQuery(input)
23
+ try {
24
+ if (new URL(page.url()).origin !== 'https://mfg.systemevererp.com') {
25
+ throw new SystemEverQueryError('systemever-query-unexpected-origin')
26
+ }
27
+ const result = await page.evaluate(async ({ data, methods }) => {
28
+ const home = (window as any).Home
29
+ if (!home?._base?.ConnInfo?.LoginSeq) return { code: 'systemever-query-session-expired' }
30
+ home.OpenPageSeq(500880)
31
+ let token: string | undefined
32
+ const deadline = Date.now() + 30000
33
+ while (!token && Date.now() < deadline) {
34
+ const frame = document.querySelector<HTMLIFrameElement>('iframe[id="500880_iframe"]')
35
+ token = frame?.contentDocument?.querySelector<HTMLInputElement>('#__PageVerificationToken')?.value
36
+ if (!token) await new Promise(resolve => setTimeout(resolve, 100))
37
+ }
38
+ if (!token) return { code: 'systemever-query-frame-timeout' }
39
+ let response: any
40
+ for (const method of methods) {
41
+ const message = new home.Const.Message()
42
+ message.Seq = home._base.ConnInfo.LoginSeq
43
+ message.PageToken = token
44
+ message.Action = 228002
45
+ message.ActionType = 0
46
+ message.PgmMethodName = 'Query'
47
+ message.ServiceSeq = 501041
48
+ message.MethodSeq = method.methodSeq
49
+ Object.assign(message.Option, { PgmSeq: 500880, Timeout: 180, PgmMethodSeq: 2, XmlFlags: '2' })
50
+ message.JSonData = structuredClone(data)
51
+ response = await new Promise<any>(resolve => {
52
+ const timer = setTimeout(() => resolve(null), 190000)
53
+ const finish = (value: any) => { clearTimeout(timer); resolve(value) }
54
+ try { home.Util.Ajax('', message, 'POST', finish, () => finish(null), 'json', true) }
55
+ catch { finish(null) }
56
+ })
57
+ if (!response) return { code: 'systemever-query-request-failed' }
58
+ if (Number(response.ExeMsg?.ErrorSeq || 0) !== 0 || response.ExeMsg?.IsSystemError === true ||
59
+ (response.ExeMsg?.ErrStatus != null && Number(response.ExeMsg.ErrStatus) !== 0)) {
60
+ return { code: 'systemever-query-vendor-error' }
61
+ }
62
+ if (method.methodSeq === 6 && response.JSonData?.Tables?.some((table: any) => {
63
+ const status = table.Columns?.indexOf('Status') ?? -1
64
+ return status >= 0 && table.Rows?.some((row: any[]) => Number(row[status] || 0) !== 0)
65
+ })) return { code: 'systemever-query-vendor-error' }
66
+ }
67
+ // Project only tabular data: no session values or vendor object methods leave the page.
68
+ return { response: { JSonData: { Tables: response.JSonData?.Tables?.map((table: any) => ({
69
+ TableName: table.TableName, Columns: table.Columns, Rows: table.Rows
70
+ })) } } }
71
+ }, { data, methods: plan.methods })
72
+ if (result.code) throw new SystemEverQueryError(result.code)
73
+ return decodeSystemEverQueryBlock(result.response, 'DataBlock1', WORK_ORDER_COLUMNS)
74
+ } catch (error) {
75
+ if (error instanceof SystemEverQueryError) throw error
76
+ // Browser exceptions can contain URLs, scripts and authentication values.
77
+ throw new SystemEverQueryError('systemever-query-browser-failed')
78
+ }
79
+ }
@@ -0,0 +1,62 @@
1
+ import integration from '@things-factory/integration-base'
2
+ import { prepareSystemEverQuery, runSystemEverQuery } from './systemever-query-runner.js'
3
+ import { SystemEverQueryError } from './systemever-query-policy.js'
4
+ const { ConnectionManager, TaskRegistry } = integration
5
+
6
+ /**
7
+ * The least time between two queries of one screen over one connection in one domain — a person does not press
8
+ * 조회 more often than this, and a schedule must not either (ADR-0110, ruling 2026-10-05, condition 3).
9
+ */
10
+ export const SYSTEMEVER_QUERY_FLOOR_MS = 60_000
11
+
12
+ /** When each (domain, connection, screen) last started a query, in this process. */
13
+ export const lastQueryStartedAt = new Map<string, number>()
14
+
15
+ /** Refusals that are not ours to rename: they already say what happened and where (e.g. integration-runtime-off). */
16
+ function isNamedPlatformRefusal(error: unknown): boolean {
17
+ const code = (error as any)?.code
18
+ return typeof code === 'string' && code.startsWith('integration-')
19
+ }
20
+
21
+ async function SystemEverQuery(step: any, { domain }: any) {
22
+ try {
23
+ prepareSystemEverQuery(step.params)
24
+ const key = `${domain?.id}:${step.connection}:${step.params?.screen}`
25
+ const now = Date.now()
26
+ const last = lastQueryStartedAt.get(key)
27
+ if (last !== undefined && now - last < SYSTEMEVER_QUERY_FLOOR_MS) {
28
+ throw new SystemEverQueryError('systemever-query-too-frequent')
29
+ }
30
+ lastQueryStartedAt.set(key, now)
31
+ const entity = await ConnectionManager.getConnectionEntityByName(domain, step.connection)
32
+ if (!entity || entity.type !== 'systemever-connector' ||
33
+ new URL(entity.endpoint).origin !== 'https://mfg.systemevererp.com') {
34
+ throw new SystemEverQueryError('systemever-query-invalid-connection')
35
+ }
36
+ const instance = await ConnectionManager.getConnectionInstanceByName(domain, step.connection)
37
+ const connection = { ...instance, type: entity.type, name: entity.name, domain }
38
+ const data = await ConnectionManager.withSession(connection, async page => {
39
+ try {
40
+ return await runSystemEverQuery(page, step.params)
41
+ } catch (error) {
42
+ if (!(error instanceof SystemEverQueryError) || error.code !== 'systemever-query-session-expired') throw error
43
+ const recovered = await connection.reAuthenticateSession()
44
+ try { return await runSystemEverQuery(recovered.page, step.params) }
45
+ finally { await connection.releasePage(recovered.page) }
46
+ }
47
+ })
48
+ return { data }
49
+ } catch (error) {
50
+ if (error instanceof SystemEverQueryError || isNamedPlatformRefusal(error)) throw error
51
+ throw new SystemEverQueryError('systemever-query-connection-failed', error)
52
+ }
53
+ }
54
+
55
+ SystemEverQuery.parameterSpec = [
56
+ { type: 'select', name: 'screen', label: 'screen', property: { options: [{ display: '작업지시조회', value: 'work-order' }] } },
57
+ { type: 'string', name: 'dateFrom', label: 'date-from' },
58
+ { type: 'string', name: 'dateTo', label: 'date-to' },
59
+ { type: 'string', name: 'bizUnit', label: 'business-unit' }
60
+ ]
61
+
62
+ TaskRegistry.registerTaskHandler('systemever-query', SystemEverQuery)
@@ -0,0 +1,37 @@
1
+ import integration from '@things-factory/integration-base'
2
+ import utils from '@things-factory/utils'
3
+
4
+ import { SystemEverQueryError } from './systemever-query-policy.js'
5
+ import { toScheduleEntries } from './systemever-work-order.js'
6
+
7
+ const { TaskRegistry } = integration
8
+ const { access } = utils
9
+
10
+ /**
11
+ * **SystemEver work-order rows → contract schedule entries**, as a scenario step (ADR-0110 addition 1 amended).
12
+ *
13
+ * `systemever-query` returns the 작업지시조회 rows with SystemEver's column names; this step maps them with the same
14
+ * `toScheduleEntries` the adapter would use, so the plant task that comes next receives only the contract's
15
+ * `ProductionScheduleEntry` and knows nothing of SystemEver. Orders it cannot map are passed on as `skipped`,
16
+ * each with its reason — none is dropped without a word.
17
+ */
18
+ async function SystemEverWorkOrdersToSchedule(step: any, { data }: any) {
19
+ const { accessor, timeZone } = step.params || {}
20
+ const rows = accessor ? access(accessor, data) : undefined
21
+ if (!Array.isArray(rows)) throw new SystemEverQueryError('systemever-schedule-invalid-rows')
22
+ if (typeof timeZone !== 'string' || !timeZone.trim()) throw new SystemEverQueryError('systemever-schedule-invalid-time-zone')
23
+
24
+ try {
25
+ return { data: toScheduleEntries(rows, { timeZone }) }
26
+ } catch (error) {
27
+ /* toScheduleEntries throws only for the zone: missing or unknown. */
28
+ throw new SystemEverQueryError('systemever-schedule-invalid-time-zone', error)
29
+ }
30
+ }
31
+
32
+ SystemEverWorkOrdersToSchedule.parameterSpec = [
33
+ { type: 'scenario-step-input', name: 'accessor', label: 'accessor' },
34
+ { type: 'string', name: 'timeZone', label: 'time-zone' }
35
+ ]
36
+
37
+ TaskRegistry.registerTaskHandler('systemever-work-orders-to-schedule', SystemEverWorkOrdersToSchedule)
@@ -0,0 +1,157 @@
1
+ import { SystemEverQueryError } from './systemever-query-policy.js'
2
+
3
+ // Captured from a read-only work-order query on 2026-10-05. No session values.
4
+ const template = {
5
+ "TableName": "DataBlock1",
6
+ "Columns": [
7
+ "WorkingTag",
8
+ "IDX_NO",
9
+ "DataSeq",
10
+ "Status",
11
+ "Selected",
12
+ "IsChangedMst",
13
+ "TABLE_NAME",
14
+ "IsSum",
15
+ "ItemClassSSeq",
16
+ "ItemClassSName",
17
+ "ItemClassLSeq",
18
+ "ItemClassLName",
19
+ "ItemClassMSeq",
20
+ "BizUnit",
21
+ "BizUnitName",
22
+ "WorkDateFr",
23
+ "WorkDateTo",
24
+ "WorkOrderNo",
25
+ "ProgStatus",
26
+ "ProgStatusName",
27
+ "WorkCenterSeq",
28
+ "WorkCenterName",
29
+ "DeptSeq",
30
+ "DeptName",
31
+ "ProcSeq",
32
+ "ProcName",
33
+ "WorkType",
34
+ "WorkTypeName",
35
+ "GoodItemName",
36
+ "GoodItemNo",
37
+ "GoodItemSpec",
38
+ "WorkOrderDateFr",
39
+ "WorkOrderDateTo",
40
+ "FileSeq",
41
+ "ProdPlanNo",
42
+ "OrderNo",
43
+ "CustSeq",
44
+ "CustName",
45
+ "WHSeq",
46
+ "WHName",
47
+ "PoNo"
48
+ ],
49
+ "ColumnsType": [
50
+ 0,
51
+ 1,
52
+ 1,
53
+ 1,
54
+ 1,
55
+ 0,
56
+ 0,
57
+ 0,
58
+ 0,
59
+ 0,
60
+ 0,
61
+ 0,
62
+ 0,
63
+ 0,
64
+ 0,
65
+ 0,
66
+ 0,
67
+ 0,
68
+ 0,
69
+ 0,
70
+ 0,
71
+ 0,
72
+ 0,
73
+ 0,
74
+ 0,
75
+ 0,
76
+ 0,
77
+ 0,
78
+ 0,
79
+ 0,
80
+ 0,
81
+ 0,
82
+ 0,
83
+ 0,
84
+ 0,
85
+ 0,
86
+ 0,
87
+ 0,
88
+ 0,
89
+ 0,
90
+ 0
91
+ ],
92
+ "Rows": [
93
+ [
94
+ "A",
95
+ 1,
96
+ 1,
97
+ 0,
98
+ 1,
99
+ "0",
100
+ "DataBlock1",
101
+ "0",
102
+ "",
103
+ "",
104
+ "",
105
+ "",
106
+ "",
107
+ "1",
108
+ "본사",
109
+ "20261005",
110
+ "20261012",
111
+ "",
112
+ "",
113
+ "",
114
+ "",
115
+ "",
116
+ "",
117
+ "",
118
+ "",
119
+ "",
120
+ "",
121
+ "",
122
+ "",
123
+ "",
124
+ "",
125
+ "",
126
+ "",
127
+ 0,
128
+ "",
129
+ "",
130
+ "",
131
+ "",
132
+ "",
133
+ "",
134
+ ""
135
+ ]
136
+ ]
137
+ }
138
+
139
+ export function workOrderQueryData(params: { dateFrom: string; dateTo: string; bizUnit: string }) {
140
+ for (const value of [params.dateFrom, params.dateTo]) {
141
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value) ||
142
+ !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) {
143
+ throw new SystemEverQueryError('systemever-query-invalid-date')
144
+ }
145
+ }
146
+ if (params.dateFrom > params.dateTo) throw new SystemEverQueryError('systemever-query-invalid-date-range')
147
+ if (typeof params.bizUnit !== 'string' || !/^\d+$/.test(params.bizUnit)) {
148
+ throw new SystemEverQueryError('systemever-query-invalid-biz-unit')
149
+ }
150
+ const table = structuredClone(template)
151
+ const row = table.Rows[0]
152
+ row[table.Columns.indexOf('WorkDateFr')] = params.dateFrom.replaceAll('-', '')
153
+ row[table.Columns.indexOf('WorkDateTo')] = params.dateTo.replaceAll('-', '')
154
+ row[table.Columns.indexOf('BizUnit')] = params.bizUnit
155
+ row[table.Columns.indexOf('BizUnitName')] = ''
156
+ return { Tables: [table], IsSendXml: true, DataBlock1: 'DataBlock1' }
157
+ }