w-web-perm 1.1.11 → 1.1.13

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 (122) hide show
  1. package/README.md +158 -117
  2. package/dist/css/app.f09c7433.css +1 -0
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/app-legacy.8af87052.js +1 -0
  5. package/dist/js/app.8af87052.js +1 -0
  6. package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
  7. package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.js} +1 -1
  8. package/dist/w-web-perm.umd.js +2 -2
  9. package/dist/w-web-perm.umd.js.map +1 -1
  10. package/docs/WWebPerm.html +84 -4
  11. package/docs/global.html +24747 -832
  12. package/docs/index.html +2 -2
  13. package/docs/server_WWebPerm.mjs.html +245 -32
  14. package/docs/server_cascadeWrite.mjs.html +225 -0
  15. package/docs/server_dataChannel.mjs.html +2 -2
  16. package/docs/server_keyCheck.mjs.html +353 -0
  17. package/docs/server_lockSave.mjs.html +2 -2
  18. package/docs/server_maskLog.mjs.html +2 -2
  19. package/docs/server_procCore.mjs.html +180 -41
  20. package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
  21. package/docs/server_syncKeep.mjs.html +311 -0
  22. package/docs/server_tools_authRefsAudit.mjs.html +247 -0
  23. package/docs/server_tools_authRefsClean.mjs.html +335 -0
  24. package/docs/server_tools_fromTools.mjs.html +797 -0
  25. package/docs/server_tools_isMain.mjs.html +103 -0
  26. package/docs/server_tools_nameDupReport.mjs.html +280 -0
  27. package/docs/server_validateRows.mjs.html +15 -4
  28. package/docs/server_wrapInjected.mjs.html +3 -3
  29. package/docs/src_fetchJson.mjs.html +2 -2
  30. package/docs/src_js_cvKpToArr.mjs.html +125 -0
  31. package/docs/src_js_cvKpToObj.mjs.html +210 -0
  32. package/docs/src_js_draftSync.mjs.html +142 -0
  33. package/docs/src_js_eachOwn.mjs.html +3 -3
  34. package/docs/src_js_editorRefRows.mjs.html +356 -0
  35. package/docs/src_js_externalRows.mjs.html +338 -0
  36. package/docs/src_js_externalUi.mjs.html +230 -0
  37. package/docs/src_js_findRefRows.mjs.html +107 -0
  38. package/docs/src_js_findTouchedDup.mjs.html +150 -0
  39. package/docs/src_js_fmtLossyMsg.mjs.html +95 -0
  40. package/docs/src_js_fromAudit.mjs.html +489 -0
  41. package/docs/src_js_getAddImpact.mjs.html +269 -0
  42. package/docs/src_js_getAuthWarn.mjs.html +463 -0
  43. package/docs/src_js_getCascadePreview.mjs.html +258 -0
  44. package/docs/src_js_getDelRefCount.mjs.html +210 -0
  45. package/docs/src_js_getEffMode.mjs.html +92 -0
  46. package/docs/src_js_getKpLossy.mjs.html +155 -0
  47. package/docs/src_js_getNameErrs.mjs.html +266 -0
  48. package/docs/src_js_getOwn.mjs.html +3 -3
  49. package/docs/src_js_getPropKey.mjs.html +109 -0
  50. package/docs/src_js_getRuleEnable.mjs.html +2 -2
  51. package/docs/src_js_isAuthMode.mjs.html +92 -0
  52. package/docs/src_js_joinNames.mjs.html +96 -0
  53. package/docs/src_js_legendFit.mjs.html +2 -2
  54. package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
  55. package/docs/src_js_refAudit.mjs.html +359 -0
  56. package/docs/src_js_refCascade.mjs.html +572 -0
  57. package/docs/src_js_relDialogSend.mjs.html +199 -0
  58. package/docs/src_js_rowDigest.mjs.html +265 -0
  59. package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
  60. package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
  61. package/docs/src_plugins_submitGuard.mjs.html +2 -2
  62. package/g_authRefsClean.mjs +79 -0
  63. package/g_fixFromType.mjs +92 -0
  64. package/g_initialData.mjs +3 -3
  65. package/g_purgeFrom.mjs +127 -0
  66. package/package.json +1 -1
  67. package/server/WWebPerm.mjs +243 -30
  68. package/server/cascadeWrite.mjs +153 -0
  69. package/server/keyCheck.mjs +281 -0
  70. package/server/procCore.mjs +178 -39
  71. package/server/procLang.mjs +271 -0
  72. package/server/syncKeep.mjs +239 -0
  73. package/server/tools/authRefsAudit.mjs +175 -0
  74. package/server/tools/authRefsClean.mjs +263 -0
  75. package/server/tools/fromTools.mjs +725 -0
  76. package/server/tools/isMain.mjs +31 -0
  77. package/server/tools/nameDupReport.mjs +208 -0
  78. package/server/validateRows.mjs +13 -2
  79. package/server/wrapInjected.mjs +1 -1
  80. package/src/components/LayoutContentGrups.vue +399 -77
  81. package/src/components/LayoutContentPemis.vue +400 -78
  82. package/src/components/LayoutContentTargets.vue +261 -25
  83. package/src/components/LayoutContentUsers.vue +336 -98
  84. package/src/components/VeCgrups.vue +119 -115
  85. package/src/components/VeCpemis.vue +117 -115
  86. package/src/components/VeCrules.vue +18 -2
  87. package/src/components/VeGrupBlngUsers.vue +240 -73
  88. package/src/components/VePemiBlngGrups.vue +247 -83
  89. package/src/js/cvKpToArr.mjs +53 -0
  90. package/src/js/cvKpToObj.mjs +138 -0
  91. package/src/js/draftSync.mjs +70 -0
  92. package/src/js/eachOwn.mjs +1 -1
  93. package/src/js/editorRefRows.mjs +284 -0
  94. package/src/js/externalRows.mjs +266 -0
  95. package/src/js/externalUi.mjs +158 -0
  96. package/src/js/findRefRows.mjs +35 -0
  97. package/src/js/findTouchedDup.mjs +78 -0
  98. package/src/js/fmtLossyMsg.mjs +23 -0
  99. package/src/js/fromAudit.mjs +417 -0
  100. package/src/js/getAddImpact.mjs +197 -0
  101. package/src/js/getAuthWarn.mjs +391 -0
  102. package/src/js/getCascadePreview.mjs +186 -0
  103. package/src/js/getDelRefCount.mjs +138 -0
  104. package/src/js/getEffMode.mjs +20 -0
  105. package/src/js/getKpLossy.mjs +83 -0
  106. package/src/js/getNameErrs.mjs +194 -0
  107. package/src/js/getOwn.mjs +1 -1
  108. package/src/js/getPropKey.mjs +37 -0
  109. package/src/js/isAuthMode.mjs +20 -0
  110. package/src/js/joinNames.mjs +24 -0
  111. package/src/js/pairRowsToSnapshot.mjs +84 -0
  112. package/src/js/refAudit.mjs +287 -0
  113. package/src/js/refCascade.mjs +500 -0
  114. package/src/js/relDialogSend.mjs +127 -0
  115. package/src/js/rowDigest.mjs +193 -0
  116. package/src/plugins/dataReloadGuard.mjs +37 -0
  117. package/src/plugins/mShare.mjs +147 -174
  118. package/src/plugins/mUI.mjs +3 -8
  119. package/src/schema/tables/users.mjs +152 -152
  120. package/dist/css/app.6918ec45.css +0 -1
  121. package/dist/js/app-legacy.963e800c.js +0 -1
  122. package/dist/js/app.963e800c.js +0 -1
@@ -0,0 +1,338 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/js/externalRows.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/js/externalRows.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>//外部列受保護(F-23; R-PRINCIPLE-00 原則 5「開了外部同步就以外部為主」、原則 3; 規則帳本 R-EXT-OWN-01; ADR-037): 後台整表寫入對「外部列」之守門, 純函數(不依賴 Vue/DOM/ORM),
49
+ //伺服端(server/procCore.mjs)為強制者, 前端(清單頁唯讀、複製、刪除過濾)共用同一份判定, 不各自重寫.
50
+ //
51
+ //判別(定案 D1): from 為非空字串者為「外部列」(外部同步 syncAndReplaceTabs 只收非空字串 from, 故非空 from 必曾由外部同步寫入或後台誤植);
52
+ //from 為 ''、缺欄、或非字串(null/數字/布林)者為「後台列」——非字串 from 必為後台寫入(同步入口 isestr 把關), 遷移工具正規化為 ''(g_fixFromType.mjs), 後台寫入起拒收非字串 from.
53
+ //Lv1 鎖定層級: 外部列之身分/結構欄(指派欄、順序、伺服器管理欄以外之全部欄位)後台不得改, 不得刪除, 不得改 from; 指派欄(users.cgrups/grups.cpemis/pemis.crules)後台可寫
54
+ // (改指派須附載入基準, 見 rowDigest.mjs; 無基準無從證明快照新舊, 依原則 3 收嚴拒收); 外部列之 order 由外部擁有(後台儲存不重編, 還原為庫內值).
55
+
56
+
57
+ //SERVER_FIELDS: 伺服器管理欄(procOrm 寫入時覆寫), 不納入內容比對
58
+ let SERVER_FIELDS = ['userId', 'timeCreate', 'userIdUpdate', 'timeUpdate']
59
+
60
+
61
+ //ASSIGN_FIELD: 各表之授權指派欄(後台可寫); targets 無指派欄
62
+ let ASSIGN_FIELD = { users: 'cgrups', grups: 'cpemis', pemis: 'crules', targets: null }
63
+
64
+
65
+ let isStr = (v) => typeof v === 'string'
66
+ let hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k)
67
+ let isObj = (v) => v !== null &amp;&amp; typeof v === 'object'
68
+
69
+
70
+ //normV: 比對用正規化(缺欄與 '' 視為同一; 其餘以 JSON 字串比對, 值為資料庫與前端同源之純量/字串)
71
+ let normV = (v) => JSON.stringify(v === undefined ? '' : v)
72
+
73
+
74
+ /**
75
+ * 判斷是否為外部列(from 為非空字串)
76
+ *
77
+ * @param {Object} row 輸入列物件
78
+ * @returns {Boolean} 回傳是否為外部列
79
+ * @example
80
+ *
81
+ * isExternalRow({ id: '1', from: 'pd' })
82
+ * // => true
83
+ * isExternalRow({ id: '1', from: '' })
84
+ * // => false
85
+ * isExternalRow({ id: '1', from: null })
86
+ * // => false
87
+ *
88
+ */
89
+ function isExternalRow(row) {
90
+ return isObj(row) &amp;&amp; isStr(row.from) &amp;&amp; row.from !== ''
91
+ }
92
+
93
+
94
+ /**
95
+ * 取得外部列之鎖定欄位(身分/結構欄): schema 全部欄位扣除 id、from、order、指派欄、伺服器管理欄
96
+ *
97
+ * @param {String} keyTable 輸入表名(targets/pemis/grups/users)
98
+ * @param {Array} keys 輸入該表 schema 之欄位名陣列(ds[keyTable].keys)
99
+ * @returns {Array} 回傳鎖定欄位名陣列
100
+ */
101
+ function getLockFields(keyTable, keys) {
102
+ let ka = hasOwn(ASSIGN_FIELD, keyTable) ? ASSIGN_FIELD[keyTable] : null
103
+ return (Array.isArray(keys) ? keys : []).filter((k) => k !== 'id' &amp;&amp; k !== 'from' &amp;&amp; k !== 'order' &amp;&amp; k !== ka &amp;&amp; SERVER_FIELDS.indexOf(k) &lt; 0)
104
+ }
105
+
106
+
107
+ /**
108
+ * 外部列之 order 由外部擁有: 把送入列中「庫內為外部列者」之 order 還原為庫內值(後台儲存重編 order=k+1 不得改外部列之順序), 回傳新陣列(不改輸入)
109
+ *
110
+ * @param {Array} rows 輸入送入之整表列(已重給 order)
111
+ * @param {Array} ltdtOld 輸入該表庫內全部列(select 結果)
112
+ * @returns {Array} 回傳 order 已還原之列陣列
113
+ */
114
+ function restoreExternalOrder(rows, ltdtOld) {
115
+ let oldById = new Map()
116
+ ;(Array.isArray(ltdtOld) ? ltdtOld : []).forEach((o) => {
117
+ if (isObj(o) &amp;&amp; isStr(o.id)) {
118
+ oldById.set(o.id, o)
119
+ }
120
+ })
121
+ return rows.map((row) => {
122
+ let o = isObj(row) &amp;&amp; isStr(row.id) ? oldById.get(row.id) : undefined
123
+ if (o !== undefined &amp;&amp; isExternalRow(o) &amp;&amp; hasOwn(o, 'order')) {
124
+ return { ...row, order: o.order }
125
+ }
126
+ return row
127
+ })
128
+ }
129
+
130
+
131
+ /**
132
+ * 將「外部列且僅伺服器管理欄(建立者/時間、更新者/時間)不同」之 diff 列移入 same(視為無變更, 不寫入)
133
+ *
134
+ * 外部同步每次整來源重寫, 使外部列之 timeUpdate/timeCreate/userId 隨之更新; 持舊快照之後台存檔若原樣送回該列, 其內容與庫內相同而僅伺服器管理欄較舊,
135
+ * 寫入會把建立者與建立時間回退為舊值(D2 設計稿 S2). 內容相同即無須寫入. 只處理外部列, 後台列之 diff 判定不變.
136
+ *
137
+ * @param {Object} p 輸入參數物件
138
+ * @param {Array} p.keys 輸入該表 schema 欄位名陣列
139
+ * @param {Object} p.r 輸入 ltdtDiffByKey 結果 { add, del, diff, same }
140
+ * @param {Array} p.ltdtOld 輸入該表庫內全部列
141
+ * @returns {Object} 回傳新之 { add, del, diff, same }
142
+ */
143
+ function dropServerOnlyDiff({ keys, r, ltdtOld }) {
144
+ let oldById = new Map()
145
+ ltdtOld.forEach((o) => {
146
+ if (isObj(o) &amp;&amp; isStr(o.id)) {
147
+ oldById.set(o.id, o)
148
+ }
149
+ })
150
+ let fields = (Array.isArray(keys) ? keys : []).filter((k) => SERVER_FIELDS.indexOf(k) &lt; 0)
151
+ let diff = []
152
+ let same = [...r.same]
153
+ r.diff.forEach((row) => {
154
+ let o = isObj(row) &amp;&amp; isStr(row.id) ? oldById.get(row.id) : undefined
155
+ if (o !== undefined &amp;&amp; isExternalRow(o) &amp;&amp; fields.every((k) => normV(row[k]) === normV(o[k]))) {
156
+ same.push(row)
157
+ }
158
+ else {
159
+ diff.push(row)
160
+ }
161
+ })
162
+ return { add: r.add, del: r.del, diff, same }
163
+ }
164
+
165
+
166
+ /**
167
+ * 外部列之 diff 列(內容確有改動, 例如指派欄)改帶庫內之伺服器管理欄(userId/timeCreate/userIdUpdate/timeUpdate)
168
+ *
169
+ * 伺服器管理欄由伺服器擁有(procOrm save 只覆寫 userIdUpdate/timeUpdate, 建立者與建立時間取自送入之列); 外部同步每次整來源重寫使外部列之建立者/建立時間更新,
170
+ * 持舊草稿(前端保留未存編輯, 見 draftSync)送回該列時, 基準因排除伺服器管理欄而通過(內容未變), 若原樣寫入就把較新之建立者與建立時間回退為舊值(R-EXT-OWN-01 S2 之同一缺陷, 外審 sol 第 3 點).
171
+ * 故改動之外部列一律以庫內之伺服器管理欄寫入. 只處理庫內為外部列者(後台列之寫入不變); 庫內無該欄者不補.
172
+ *
173
+ * @param {Object} p 輸入參數物件
174
+ * @param {Object} p.r 輸入 ltdtDiffByKey(及 dropServerOnlyDiff)結果 { add, del, diff, same }
175
+ * @param {Array} p.ltdtOld 輸入該表庫內全部列
176
+ * @returns {Object} 回傳新之 { add, del, diff, same }(不改輸入; 未改動之列為同一物件)
177
+ */
178
+ function restoreServerFields({ r, ltdtOld }) {
179
+ let oldById = new Map()
180
+ ltdtOld.forEach((o) => {
181
+ if (isObj(o) &amp;&amp; isStr(o.id)) {
182
+ oldById.set(o.id, o)
183
+ }
184
+ })
185
+ let diff = r.diff.map((row) => {
186
+ let o = isObj(row) &amp;&amp; isStr(row.id) ? oldById.get(row.id) : undefined
187
+ if (o === undefined || !isExternalRow(o)) {
188
+ return row
189
+ }
190
+ let out = { ...row }
191
+ SERVER_FIELDS.forEach((k) => {
192
+ if (hasOwn(o, k)) {
193
+ out[k] = o[k]
194
+ }
195
+ })
196
+ return out
197
+ })
198
+ return { add: r.add, del: r.del, diff, same: r.same }
199
+ }
200
+
201
+
202
+ /**
203
+ * 後台整表寫入對外部列之守門(於 ltdtDiffByKey 與 dropServerOnlyDiff 之後、寫入之前; 違規整次拒收、零寫入, 不靜默忽略)
204
+ *
205
+ * from 型別(不論 lockExternalRows): 新增列之 from 須為字串(缺欄視為 ''), 修改列之 from 若改為非字串須與庫內值相同(歷史 null 原樣送回不拒, 否則已有歷史資料之庫無法儲存);
206
+ * lockExternalRows 為真時另有: ①新增列之 from 須為 ''(含復活他來源之列、複製繼承、手打); ②不得刪除外部列; ③修改列: 外部列不得改 from 與鎖定欄位(getLockFields), 後台列不得改成非空 from.
207
+ * 指派欄與 order/伺服器管理欄不在比對內.
208
+ *
209
+ * @param {Object} p 輸入參數物件
210
+ * @param {String} p.keyTable 輸入表名
211
+ * @param {Array} p.keys 輸入該表 schema 欄位名陣列
212
+ * @param {Object} p.r 輸入 { add, del, diff, same }
213
+ * @param {Array} p.ltdtOld 輸入該表庫內全部列
214
+ * @param {Boolean} [p.lockExternalRows=true] 輸入是否啟用外部列保護, 預設 true
215
+ * @returns {Object|null} 回傳 null 為通過; 否則 { key, reason, k, n, froms }: key 為語系鍵(saveFromNotAllowed/saveExternalRowChanged), reason 為原因(fromType/addFrom/del/fromChanged/fieldChanged), k 為 r 內該陣列之第一個違規索引, n 為違規列數, froms 為涉及之來源(至多 5 個)
216
+ */
217
+ function checkExternalWrite({ keyTable, keys, r, ltdtOld, lockExternalRows = true }) {
218
+ let oldById = new Map()
219
+ ltdtOld.forEach((o) => {
220
+ if (isObj(o) &amp;&amp; isStr(o.id)) {
221
+ oldById.set(o.id, o)
222
+ }
223
+ })
224
+ let normFrom = (v) => (v === undefined ? '' : v)
225
+ let lockFields = getLockFields(keyTable, keys)
226
+
227
+ let bad = [] //{ key, reason, k, from }
228
+ let push = (key, reason, k, from) => {
229
+ bad.push({ key, reason, k, from: isStr(from) ? from : '' })
230
+ }
231
+
232
+ //add
233
+ r.add.forEach((row, k) => {
234
+ let f = isObj(row) ? normFrom(row.from) : ''
235
+ if (!isStr(f)) {
236
+ push('saveFromNotAllowed', 'fromType', k, '')
237
+ }
238
+ else if (lockExternalRows &amp;&amp; f !== '') {
239
+ push('saveFromNotAllowed', 'addFrom', k, f)
240
+ }
241
+ })
242
+
243
+ //del
244
+ if (lockExternalRows) {
245
+ r.del.forEach((row, k) => {
246
+ if (isExternalRow(row)) {
247
+ push('saveExternalRowChanged', 'del', k, row.from)
248
+ }
249
+ })
250
+ }
251
+
252
+ //diff
253
+ r.diff.forEach((row, k) => {
254
+ let o = isObj(row) &amp;&amp; isStr(row.id) ? oldById.get(row.id) : undefined
255
+ let nf = isObj(row) ? normFrom(row.from) : ''
256
+ let of = o === undefined ? '' : normFrom(o.from)
257
+ if (!isStr(nf) &amp;&amp; nf !== of) {
258
+ push('saveFromNotAllowed', 'fromType', k, '')
259
+ return
260
+ }
261
+ if (!lockExternalRows || o === undefined) {
262
+ return
263
+ }
264
+ if (isExternalRow(o)) {
265
+ if (nf !== o.from) {
266
+ push('saveExternalRowChanged', 'fromChanged', k, o.from)
267
+ }
268
+ else if (lockFields.some((f) => normV(row[f]) !== normV(o[f]))) {
269
+ push('saveExternalRowChanged', 'fieldChanged', k, o.from)
270
+ }
271
+ }
272
+ else if (isStr(nf) &amp;&amp; nf !== '') {
273
+ push('saveFromNotAllowed', 'fromChanged', k, nf)
274
+ }
275
+ })
276
+
277
+ if (bad.length === 0) {
278
+ return null
279
+ }
280
+ let froms = []
281
+ bad.forEach((b) => {
282
+ if (b.from !== '' &amp;&amp; froms.indexOf(b.from) &lt; 0 &amp;&amp; froms.length &lt; 5) {
283
+ froms.push(b.from)
284
+ }
285
+ })
286
+ return { key: bad[0].key, reason: bad[0].reason, k: bad[0].k, n: bad.length, froms }
287
+ }
288
+
289
+
290
+ /**
291
+ * 本次寫入是否修改了外部列之指派欄(須附載入基準, 見 rowDigest.mjs checkBaseline)
292
+ *
293
+ * @param {Object} p 輸入參數物件
294
+ * @param {Object} p.r 輸入 { add, del, diff, same }(經 dropServerOnlyDiff 後)
295
+ * @param {Array} p.ltdtOld 輸入該表庫內全部列
296
+ * @returns {Boolean} 回傳是否有外部列之 diff
297
+ */
298
+ function hasExternalDiff({ r, ltdtOld }) {
299
+ let oldById = new Map()
300
+ ltdtOld.forEach((o) => {
301
+ if (isObj(o) &amp;&amp; isStr(o.id)) {
302
+ oldById.set(o.id, o)
303
+ }
304
+ })
305
+ return r.diff.some((row) => {
306
+ let o = isObj(row) &amp;&amp; isStr(row.id) ? oldById.get(row.id) : undefined
307
+ return o !== undefined &amp;&amp; isExternalRow(o)
308
+ })
309
+ }
310
+
311
+
312
+ export { SERVER_FIELDS, ASSIGN_FIELD, isExternalRow, getLockFields, restoreExternalOrder, dropServerOnlyDiff, restoreServerFields, checkExternalWrite, hasExternalDiff }
313
+ export default isExternalRow
314
+ </code></pre>
315
+ </article>
316
+ </section>
317
+
318
+
319
+
320
+
321
+
322
+
323
+ </div>
324
+
325
+ <br class="clear">
326
+
327
+ <footer>
328
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
329
+ </footer>
330
+
331
+ <script>prettyPrint();</script>
332
+ <script src="scripts/polyfill.js"></script>
333
+ <script src="scripts/linenumber.js"></script>
334
+
335
+
336
+
337
+ </body>
338
+ </html>
@@ -0,0 +1,230 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/js/externalUi.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/js/externalUi.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import { isExternalRow, getLockFields } from './externalRows.mjs'
49
+
50
+
51
+ //清單頁(標的/權限/群組/使用者)之外部列(from 為非空字串者)前端輔助純函數(F-23; R-PRINCIPLE-00 原則 3、5; 規則帳本 R-EXT-OWN-01; ADR-037): 不依賴 Vue/DOM/語系檔,
52
+ //判別外部列一律經 externalRows.isExternalRow(單一擁有者, 本檔不另寫判準); 伺服端為強制者, 本檔只供畫面提示與送出前擋下(不取代伺服端守門).
53
+
54
+
55
+ let isStr = (v) => typeof v === 'string'
56
+ let isObj = (v) => v !== null &amp;&amp; typeof v === 'object'
57
+
58
+
59
+ //normV: 比對用正規化, 同 externalRows 之 checkExternalWrite(缺欄與 '' 視為同一; 其餘以 JSON 字串比對)
60
+ let normV = (v) => JSON.stringify(v === undefined ? '' : v)
61
+
62
+
63
+ /**
64
+ * 把範本之 {key} 代入(單趟掃描: 代入之值不再解析, 名稱可含 {n}、{from} 等字樣原樣顯示; 未給之佔位符原樣保留)
65
+ *
66
+ * 與 getAuthWarn.fmtWarn 同設計, 但佔位符不限定名單(如 {from}、{name}); 值一律轉字串, 不特殊處理 $ 字樣.
67
+ *
68
+ * @param {String} tpl 輸入範本
69
+ * @param {Object} [vars={}] 輸入 { 佔位符名: 值 }
70
+ * @returns {String} 回傳代入後之文字
71
+ * @example
72
+ *
73
+ * console.log(fmtVars('{from}: {n}', { from: 'teamA', n: 3 }))
74
+ * // => 'teamA: 3'
75
+ *
76
+ */
77
+ function fmtVars(tpl, vars = {}) {
78
+ let kp = Object.create(null)
79
+ Object.keys(isObj(vars) ? vars : {}).forEach((k) => {
80
+ kp[k] = String(vars[k])
81
+ })
82
+ return String(tpl).replace(/\{(\w+)\}/g, (hit, k) => (k in kp ? kp[k] : hit))
83
+ }
84
+
85
+
86
+ /**
87
+ * 計算外部列(from 為非空字串者)之列數
88
+ *
89
+ * @param {Array} rows 輸入列陣列(非陣列視為空)
90
+ * @returns {Number} 回傳外部列數
91
+ */
92
+ function countExternalRows(rows) {
93
+ if (!Array.isArray(rows)) {
94
+ return 0
95
+ }
96
+ return rows.filter((r) => isExternalRow(r)).length
97
+ }
98
+
99
+
100
+ /**
101
+ * 取出屬於某表之外部同步警示(getWebInfor().syncWarns: [{ keyTable, from, n, time }]; 外部同步疑似 id 重編)
102
+ *
103
+ * 只取 keyTable 與本頁表名相符、from 為非空字串、n 為正數者; 輸出為 { from, n }(次序同輸入); 非陣列或元素格式不符者略過.
104
+ *
105
+ * @param {Array} syncWarns 輸入 webInfor.syncWarns
106
+ * @param {String} keyTable 輸入本頁表名(targets/pemis/grups/users)
107
+ * @returns {Array} 回傳 [{ from, n }]
108
+ */
109
+ function getSyncWarnLines(syncWarns, keyTable) {
110
+ let rs = []
111
+ if (!Array.isArray(syncWarns)) {
112
+ return rs
113
+ }
114
+ syncWarns.forEach((w) => {
115
+ if (isObj(w) &amp;&amp; w.keyTable === keyTable &amp;&amp; isStr(w.from) &amp;&amp; w.from !== '' &amp;&amp; typeof w.n === 'number' &amp;&amp; w.n > 0) {
116
+ rs.push({ from: w.from, n: w.n })
117
+ }
118
+ })
119
+ return rs
120
+ }
121
+
122
+
123
+ /**
124
+ * 產生清單上方橫幅之各行: 外部列提示(啟用外部列保護且外部列數 > 0 才有)+本表之外部同步警示(每個來源一行); 皆無則回空陣列(頁面不渲染任何節點)
125
+ *
126
+ * 外部列提示用語系鍵 externalRowsNotice(群組/權限/使用者: 身分欄唯讀、不可刪除、指派可改)或 externalRowsNoticeTarget(標的無指派欄); 警示用 syncWarnIdRenumber({from}、{n}).
127
+ *
128
+ * @param {Object} p 輸入參數物件
129
+ * @param {String} p.keyTable 輸入本頁表名
130
+ * @param {String} p.kind 輸入頁面種類: 'Target'(標的頁)或其他
131
+ * @param {Array} p.rows 輸入本頁之基準列(store 之該表列, 已儲存者; 外部列只可能由外部同步寫入)
132
+ * @param {Boolean} p.lock 輸入是否啟用外部列保護(webInfor.lockExternalRows === 'y')
133
+ * @param {Array} p.syncWarns 輸入 webInfor.syncWarns
134
+ * @param {Function} p.$t 輸入語系函數(key → 文字)
135
+ * @returns {Array} 回傳 [{ kind: 'ext'|'warn', text }]
136
+ */
137
+ function getBannerLines({ keyTable, kind, rows, lock, syncWarns, $t }) {
138
+ let rs = []
139
+ let n = lock === true ? countExternalRows(rows) : 0
140
+ if (n > 0) {
141
+ rs.push({ kind: 'ext', text: fmtVars($t(kind === 'Target' ? 'externalRowsNoticeTarget' : 'externalRowsNotice'), { n }) })
142
+ }
143
+ getSyncWarnLines(syncWarns, keyTable).forEach((w) => {
144
+ rs.push({ kind: 'warn', text: fmtVars($t('syncWarnIdRenumber'), { from: w.from, n: w.n }) })
145
+ })
146
+ return rs
147
+ }
148
+
149
+
150
+ /**
151
+ * 把勾選列依外部列保護切成「可處理」與「被擋下」: 啟用外部列保護時外部列不得刪除(伺服端 saveExternalRowChanged), 先於前端過濾並計數供提示
152
+ *
153
+ * @param {Array} items 輸入勾選之項目(清單頁 vo.itemsCheck, 每項之 data 為該列資料)
154
+ * @param {Boolean} lock 輸入是否啟用外部列保護
155
+ * @returns {Object} 回傳 { rows, nBlocked }: rows 為可處理之項目(未啟用保護時為全部), nBlocked 為被擋下之外部列數
156
+ */
157
+ function splitCheckedRows(items, lock) {
158
+ let all = Array.isArray(items) ? items : []
159
+ if (lock !== true) {
160
+ return { rows: all, nBlocked: 0 }
161
+ }
162
+ let rows = all.filter((v) => !isExternalRow(isObj(v) ? v.data : undefined))
163
+ return { rows, nBlocked: all.length - rows.length }
164
+ }
165
+
166
+
167
+ /**
168
+ * 送出前檢查: 外部列(以基準列判定)是否被改動身分/結構欄、from 或被刪除(含改 id 致原 id 消失)——清單頁以逐列唯讀與刪除過濾擋下, 但貼上(Ctrl+V)等路徑可繞過逐列唯讀,
169
+ * 故送出前再比對一次(判準同 externalRows.checkExternalWrite: 鎖定欄位=getLockFields, 另含 from; 指派欄與 order/伺服器管理欄不比對), 有違規即不送出並提示(伺服端同樣整次拒收)
170
+ *
171
+ * @param {Object} p 輸入參數物件
172
+ * @param {String} p.keyTable 輸入表名(targets/pemis/grups/users)
173
+ * @param {Array} p.keys 輸入該表 schema 欄位名陣列(ds[keyTable].keys)
174
+ * @param {Array} p.rowsNew 輸入欲送出之整表(清單目前之列)
175
+ * @param {Array} p.rowsOld 輸入基準列(store 之該表列)
176
+ * @returns {Object|null} 回傳 null 為無違規; 否則 { n, froms }: n 為違規之外部列數, froms 為涉及之來源(至多 5 個)
177
+ */
178
+ function getExternalEdits({ keyTable, keys, rowsNew, rowsOld }) {
179
+ let fields = [...getLockFields(keyTable, keys), 'from']
180
+ let newById = new Map()
181
+ ;(Array.isArray(rowsNew) ? rowsNew : []).forEach((r) => {
182
+ if (isObj(r) &amp;&amp; (isStr(r.id) || (typeof r.id === 'number' &amp;&amp; Number.isFinite(r.id)))) {
183
+ newById.set(String(r.id), r)
184
+ }
185
+ })
186
+ let n = 0
187
+ let froms = []
188
+ ;(Array.isArray(rowsOld) ? rowsOld : []).forEach((o) => {
189
+ if (!isExternalRow(o) || !isStr(o.id)) {
190
+ return
191
+ }
192
+ let r = newById.get(o.id)
193
+ if (r === undefined || fields.some((f) => normV(r[f]) !== normV(o[f]))) {
194
+ n++
195
+ if (froms.indexOf(o.from) &lt; 0 &amp;&amp; froms.length &lt; 5) {
196
+ froms.push(o.from)
197
+ }
198
+ }
199
+ })
200
+ return n > 0 ? { n, froms } : null
201
+ }
202
+
203
+
204
+ export { fmtVars, countExternalRows, getSyncWarnLines, getBannerLines, splitCheckedRows, getExternalEdits }
205
+ export default fmtVars
206
+ </code></pre>
207
+ </article>
208
+ </section>
209
+
210
+
211
+
212
+
213
+
214
+
215
+ </div>
216
+
217
+ <br class="clear">
218
+
219
+ <footer>
220
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
221
+ </footer>
222
+
223
+ <script>prettyPrint();</script>
224
+ <script src="scripts/polyfill.js"></script>
225
+ <script src="scripts/linenumber.js"></script>
226
+
227
+
228
+
229
+ </body>
230
+ </html>