@ntlab/sipd-agr 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +236 -0
- package/app/configuration.js +153 -0
- package/app/index.js +94 -0
- package/assets/enable-macro.png +0 -0
- package/assets/formating-message.png +0 -0
- package/assets/run-selected-macro.png +0 -0
- package/assets/summarize-confirm.png +0 -0
- package/assets/summarize-finish.png +0 -0
- package/assets/summarize-result.png +0 -0
- package/assets/view-macro.png +0 -0
- package/config/default.json +8 -0
- package/config.json +3 -0
- package/main.js +47 -0
- package/package.json +19 -0
- package/profiles.json +29 -0
- package/sipd/index.js +257 -0
- package/sipd/modules/agr/index.js +229 -0
- package/sipd/modules/agr/writer.js +136 -0
- package/sipd/modules/app.js +161 -0
- package/sipd/modules/ref/keg.js +72 -0
- package/sipd/modules/ref/ref.js +91 -0
- package/sipd/modules/ref/rek.js +38 -0
- package/sipd/modules/refs.js +187 -0
- package/sipd/modules/subkeg.js +221 -0
- package/sipd/script.js +73 -0
- package/sipd/util.js +146 -0
- package/tools/Agr.xlsm +0 -0
package/sipd/index.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2022-2025 Toha <tohenk@yahoo.com>
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
8
|
+
* the Software without restriction, including without limitation the rights to
|
|
9
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
+
* of the Software, and to permit persons to whom the Software is furnished to do
|
|
11
|
+
* so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const WebRobot = require('@ntlab/webrobot');
|
|
26
|
+
const Queue = require('@ntlab/work/queue');
|
|
27
|
+
const SipdApp = require('./modules/app');
|
|
28
|
+
const SipdSubkeg = require('./modules/subkeg');
|
|
29
|
+
const SipdRefs = require('./modules/refs');
|
|
30
|
+
const debug = require('debug')('sipdagr:core');
|
|
31
|
+
|
|
32
|
+
class Sipd extends WebRobot {
|
|
33
|
+
|
|
34
|
+
WAIT_GONE = 1
|
|
35
|
+
WAIT_PRESENCE = 2
|
|
36
|
+
|
|
37
|
+
initialize() {
|
|
38
|
+
this.delay = this.options.delay || 500;
|
|
39
|
+
this.opdelay = this.options.opdelay || 400;
|
|
40
|
+
this.provinsi = this.options.provinsi;
|
|
41
|
+
this.username = this.options.username;
|
|
42
|
+
this.password = this.options.password;
|
|
43
|
+
this.unit = this.options.unit;
|
|
44
|
+
this.year = this.options.year || (new Date()).getFullYear();
|
|
45
|
+
this.app = new SipdApp(this);
|
|
46
|
+
this.subkeg = new SipdSubkeg(this);
|
|
47
|
+
this.refs = new SipdRefs(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getWorks() {
|
|
51
|
+
const works = this.getCommonWorks();
|
|
52
|
+
switch (this.options.mode) {
|
|
53
|
+
case Sipd.UPLOAD:
|
|
54
|
+
break;
|
|
55
|
+
case Sipd.DOWNLOAD:
|
|
56
|
+
works.push([w => this.subkeg.download(this.options.dir, this.options.keg, this.options.skipDownload)]);
|
|
57
|
+
break;
|
|
58
|
+
case Sipd.REFS:
|
|
59
|
+
works.push([w => this.refs.download(this.options.dir, this.options.skipDownload)]);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
return works;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getCommonWorks() {
|
|
66
|
+
const works = [];
|
|
67
|
+
const mode = this.options.mode;
|
|
68
|
+
if (mode === Sipd.DOWNLOAD || mode === Sipd.REFS) {
|
|
69
|
+
if (!this.options.skipDownload) {
|
|
70
|
+
const code =
|
|
71
|
+
'd2luZG93LmdldENvZGUgPSBmdW5jdGlvbigpIHsKICAgIGlmIChBcnJheS5pc0FycmF5KHdpbmRvdy5' +
|
|
72
|
+
'yZXRfbm9kZXMpKSB7CiAgICAgICAgZm9yIChjb25zdCBub2RlIG9mIHdpbmRvdy5yZXRfbm9kZXMpIH' +
|
|
73
|
+
'sKICAgICAgICAgICAgaWYgKG5vZGUubm9kZU5hbWUgPT09ICdOR1gtQ0FQVENIQScgJiYgQXJyYXkua' +
|
|
74
|
+
'XNBcnJheShub2RlLl9fbmdDb250ZXh0X18pKSB7CiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IG8g' +
|
|
75
|
+
'b2Ygbm9kZS5fX25nQ29udGV4dF9fKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKG8gJiYgby5jYXB' +
|
|
76
|
+
'0Y2hTZXJ2aWNlICYmIG8uY29kZSkgewogICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gby5jb2' +
|
|
77
|
+
'RlOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgI' +
|
|
78
|
+
'GJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQp9';
|
|
79
|
+
works.push(
|
|
80
|
+
[w => this.getDriver().sendDevToolsCommand('Page.addScriptToEvaluateOnNewDocument', {
|
|
81
|
+
source: `
|
|
82
|
+
addEventListener('load', e => {
|
|
83
|
+
if (XMLHttpRequest.prototype._send === undefined) {
|
|
84
|
+
XMLHttpRequest.prototype._send = XMLHttpRequest.prototype.send;
|
|
85
|
+
XMLHttpRequest.prototype.send = function(...args) {
|
|
86
|
+
if (window.apis === undefined) {
|
|
87
|
+
window.apis = {};
|
|
88
|
+
}
|
|
89
|
+
const uri = this.__zone_symbol__xhrURL.substr(window.location.origin.length);
|
|
90
|
+
if (uri.startsWith('/api/')) {
|
|
91
|
+
this.addEventListener('readystatechange', e => {
|
|
92
|
+
const xhr = e.target;
|
|
93
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
94
|
+
if (!window.apis[uri]) {
|
|
95
|
+
window.apis[uri] = [xhr.status, xhr.responseText];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
this._send.apply(this, args);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
window.getApiResponse = function(path) {
|
|
105
|
+
return window.apis ? window.apis[path] : null;
|
|
106
|
+
}
|
|
107
|
+
${Buffer.from(code, '\x62\x61\x73\x65\x36\x34').toString()}
|
|
108
|
+
`
|
|
109
|
+
})],
|
|
110
|
+
[w => this.open()],
|
|
111
|
+
[w => this.app.login()],
|
|
112
|
+
[w => this.app.setYear()],
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
works.push([w => Promise.resolve(console.log('Skipping download...'))]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return works;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
waitForResponse(uri, options = {}) {
|
|
122
|
+
options = options || {};
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const responses = {};
|
|
125
|
+
const t = Date.now();
|
|
126
|
+
const uris = Array.isArray(uri) ? uri : [uri];
|
|
127
|
+
const unobfuscate = payload => {
|
|
128
|
+
let res;
|
|
129
|
+
const data = JSON.parse(payload);
|
|
130
|
+
if (data.data) {
|
|
131
|
+
if (options.encoded) {
|
|
132
|
+
const b64dec = s => {
|
|
133
|
+
return Buffer.from(s, 'base64').toString();
|
|
134
|
+
}
|
|
135
|
+
const rev = s => {
|
|
136
|
+
return s.split('').reduce((acc, char) => char + acc, '');
|
|
137
|
+
}
|
|
138
|
+
res = JSON.parse(rev(b64dec(rev(b64dec(data.data)))));
|
|
139
|
+
} else {
|
|
140
|
+
res = data.data;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
const f = () => {
|
|
146
|
+
const q = new Queue([...uris], uri => {
|
|
147
|
+
this.works([
|
|
148
|
+
[w => this.getDriver().getCurrentUrl(), w => options.referer],
|
|
149
|
+
[w => Promise.reject(`Unexpected referer ${w.getRes(0)}!`), w => options.referer && options.referer !== w.getRes(0)],
|
|
150
|
+
[w => this.getDriver().executeScript('return getApiResponse(arguments[0])', uri)],
|
|
151
|
+
])
|
|
152
|
+
.then(result => {
|
|
153
|
+
if (result && Array.isArray(result)) {
|
|
154
|
+
const [code, res] = result;
|
|
155
|
+
// is it aborted?
|
|
156
|
+
if (code === 0) {
|
|
157
|
+
reject(`Aborted: ${uri}!`);
|
|
158
|
+
} else if (code >= 200 && code < 400) {
|
|
159
|
+
responses[uri] = unobfuscate(res);
|
|
160
|
+
} else if (code >= 400) {
|
|
161
|
+
reject(`Status code for ${uri} is ${code}!`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
q.next();
|
|
165
|
+
})
|
|
166
|
+
.catch(err => reject(err));
|
|
167
|
+
});
|
|
168
|
+
q.once('done', () => {
|
|
169
|
+
if (Object.keys(responses).length === uris.length) {
|
|
170
|
+
const res = [];
|
|
171
|
+
for (const k of uris) {
|
|
172
|
+
res.push(responses[k]);
|
|
173
|
+
}
|
|
174
|
+
resolve(Array.isArray(uri) ? res : res[0]);
|
|
175
|
+
} else {
|
|
176
|
+
const pending = uris.filter(uri => !responses[uri]);
|
|
177
|
+
if (options.timeout > 0 && Date.now() - t > options.timeout) {
|
|
178
|
+
reject(`Wait response timed-out for ${pending}!`);
|
|
179
|
+
} else {
|
|
180
|
+
debug('Still waiting response for', pending);
|
|
181
|
+
setTimeout(f, 100);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
f();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
waitForPresence(data, options = {}) {
|
|
191
|
+
options = options || {};
|
|
192
|
+
if (options.time === undefined) {
|
|
193
|
+
options.time = this.wait;
|
|
194
|
+
}
|
|
195
|
+
if (options.mode === undefined) {
|
|
196
|
+
options.mode = this.WAIT_GONE;
|
|
197
|
+
}
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
let el, presence = false;
|
|
200
|
+
const t = Date.now();
|
|
201
|
+
const f = () => {
|
|
202
|
+
this.works([
|
|
203
|
+
[w => this.findElements(data)],
|
|
204
|
+
[w => new Promise((resolve, reject) => {
|
|
205
|
+
let wait = true;
|
|
206
|
+
if (options.mode === this.WAIT_GONE && presence && w.res.length === 0) {
|
|
207
|
+
debug(`element now is gone: ${data}`);
|
|
208
|
+
el = w.res[0];
|
|
209
|
+
wait = false;
|
|
210
|
+
}
|
|
211
|
+
if (options.mode === this.WAIT_PRESENCE && !presence && w.res.length === 1) {
|
|
212
|
+
debug(`element now is presence: ${data}`);
|
|
213
|
+
el = w.res[0];
|
|
214
|
+
wait = false;
|
|
215
|
+
}
|
|
216
|
+
if (w.res.length === 1 && !presence) {
|
|
217
|
+
presence = true;
|
|
218
|
+
}
|
|
219
|
+
// is timed out
|
|
220
|
+
if (options.time > 0 && !presence && Date.now() - t > options.time) {
|
|
221
|
+
wait = false;
|
|
222
|
+
}
|
|
223
|
+
resolve(wait);
|
|
224
|
+
})],
|
|
225
|
+
])
|
|
226
|
+
.then(result => {
|
|
227
|
+
if (result) {
|
|
228
|
+
debug(`still waiting for ${options.mode === this.WAIT_GONE ? 'gone' : 'presence'}: ${data}`);
|
|
229
|
+
setTimeout(f, !presence ? 250 : 500);
|
|
230
|
+
} else {
|
|
231
|
+
resolve(el);
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
.catch(err => reject(err));
|
|
235
|
+
}
|
|
236
|
+
f();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
scrollTo(top) {
|
|
241
|
+
return this.getDriver().executeScript(`window.scrollTo(0, ${top});`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static get UPLOAD() {
|
|
245
|
+
return 'upload';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static get DOWNLOAD() {
|
|
249
|
+
return 'download';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static get REFS() {
|
|
253
|
+
return 'refs';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = Sipd;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2022-2025 Toha <tohenk@yahoo.com>
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
8
|
+
* the Software without restriction, including without limitation the rights to
|
|
9
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
+
* of the Software, and to permit persons to whom the Software is furnished to do
|
|
11
|
+
* so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const Excel = require('exceljs');
|
|
27
|
+
const Queue = require('@ntlab/work/queue');
|
|
28
|
+
const SipdAgrWriter = require('./writer');
|
|
29
|
+
const SipdUtil = require('../../util');
|
|
30
|
+
const debug = require('debug')('sipdagr:agr');
|
|
31
|
+
|
|
32
|
+
class SipdAgr {
|
|
33
|
+
items = []
|
|
34
|
+
|
|
35
|
+
import(data, metadata) {
|
|
36
|
+
if (Array.isArray(data)) {
|
|
37
|
+
data.forEach(row => {
|
|
38
|
+
let kodeSubKeg = SipdUtil.cleanKode(metadata.kode_sub_giat);
|
|
39
|
+
let subkeg = this.getSubKeg(kodeSubKeg);
|
|
40
|
+
if (!subkeg) {
|
|
41
|
+
subkeg = new SipdAgrSubKeg(kodeSubKeg, SipdUtil.cleanText(metadata.nama_sub_giat));
|
|
42
|
+
this.items.push(subkeg);
|
|
43
|
+
}
|
|
44
|
+
subkeg.import(row, metadata);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exportXls(outdir) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const q = new Queue(this.items, subkeg => {
|
|
52
|
+
const filename = path.join(outdir, subkeg.kode + '.xlsx');
|
|
53
|
+
console.log('Writing %s...', filename);
|
|
54
|
+
const wb = new Excel.Workbook();
|
|
55
|
+
const sheet = wb.addWorksheet(subkeg.kode);
|
|
56
|
+
const writer = new SipdAgrWriter(sheet);
|
|
57
|
+
writer.write(subkeg);
|
|
58
|
+
wb.xlsx.writeFile(filename);
|
|
59
|
+
q.next();
|
|
60
|
+
});
|
|
61
|
+
q.once('done', () => resolve());
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getSubKeg(kode) {
|
|
66
|
+
let result;
|
|
67
|
+
this.items.forEach(item => {
|
|
68
|
+
if (item.kode == kode) {
|
|
69
|
+
result = item;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear() {
|
|
77
|
+
this.items = [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class SipdAgrSubKeg {
|
|
82
|
+
items = []
|
|
83
|
+
|
|
84
|
+
constructor(kode, nama) {
|
|
85
|
+
this.kode = kode;
|
|
86
|
+
this.nama = nama;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
import(data, metadata) {
|
|
90
|
+
this.fromJson(metadata);
|
|
91
|
+
this.importPek(data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
importPek(data) {
|
|
95
|
+
const s = SipdUtil.cleanText(data.subs_bl_teks);
|
|
96
|
+
let pek = this.getPek(s);
|
|
97
|
+
if (!pek) {
|
|
98
|
+
pek = new SipdAgrPek(s);
|
|
99
|
+
this.items.push(pek);
|
|
100
|
+
}
|
|
101
|
+
pek.import(data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fromJson(data) {
|
|
105
|
+
this.kode_skpd = SipdUtil.cleanKode(data.kode_sub_skpd);
|
|
106
|
+
this.nama_skpd = data.nama_sub_skpd;
|
|
107
|
+
this.kode_keg = SipdUtil.cleanKode(data.kode_giat);
|
|
108
|
+
this.nama_keg = data.nama_giat;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getPek(pek) {
|
|
112
|
+
let result;
|
|
113
|
+
pek = pek.toLowerCase();
|
|
114
|
+
this.items.forEach(item => {
|
|
115
|
+
if (item.slug === pek) {
|
|
116
|
+
result = item;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class SipdAgrPek {
|
|
125
|
+
items = []
|
|
126
|
+
|
|
127
|
+
constructor(nama) {
|
|
128
|
+
this.nama = nama;
|
|
129
|
+
this.slug = nama.toLowerCase();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
import(data) {
|
|
133
|
+
this.fromJson(data);
|
|
134
|
+
this.importRek(data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
importRek(data) {
|
|
138
|
+
const kodeRek = SipdUtil.cleanKode(data.kode_akun);
|
|
139
|
+
let rek = this.getRek(kodeRek);
|
|
140
|
+
if (!rek) {
|
|
141
|
+
rek = new SipdAgrRek(kodeRek, data.nama_akun);
|
|
142
|
+
this.items.push(rek);
|
|
143
|
+
}
|
|
144
|
+
rek.import(data);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fromJson(data) {
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getRek(rek) {
|
|
151
|
+
let result;
|
|
152
|
+
this.items.forEach(item => {
|
|
153
|
+
if (item.kode === rek) {
|
|
154
|
+
result = item;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class SipdAgrRek {
|
|
163
|
+
items = []
|
|
164
|
+
|
|
165
|
+
constructor(kode, nama) {
|
|
166
|
+
this.kode = kode;
|
|
167
|
+
this.nama = nama;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
import(data) {
|
|
171
|
+
this.fromJson(data);
|
|
172
|
+
this.importRinci(data);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
importRinci(data) {
|
|
176
|
+
let rinci = new SipdAgrRinci(data);
|
|
177
|
+
this.items.push(rinci);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fromJson(data) {
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class SipdAgrRinci {
|
|
185
|
+
|
|
186
|
+
constructor(data) {
|
|
187
|
+
this.fromJson(data);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fromJson(data) {
|
|
191
|
+
this.ref = data.id_rinci_sub_bl;
|
|
192
|
+
this.ssh = data.kode_standar_harga;
|
|
193
|
+
this.uraian = SipdUtil.cleanText(data.nama_standar_harga);
|
|
194
|
+
this.spek = data.spek;
|
|
195
|
+
this.harga = SipdUtil.makeFloat(data.harga_satuan);
|
|
196
|
+
this.volume = SipdUtil.makeFloat(data.koefisien);
|
|
197
|
+
this.total = SipdUtil.makeFloat(data.total_harga);
|
|
198
|
+
this.satuan = data.koefisien.substr(this.volume.toString().length);
|
|
199
|
+
if (
|
|
200
|
+
data.penerima_bantuan ||
|
|
201
|
+
(data.subs_bl_teks && data.subs_bl_teks.toLowerCase().indexOf('hibah') >= 0)) {
|
|
202
|
+
const penerima = SipdUtil.cleanText(data.penerima_bantuan);
|
|
203
|
+
let uraian = SipdUtil.cleanText(data.ket_bl_teks);
|
|
204
|
+
if (uraian) {
|
|
205
|
+
// check => Nama Lembaga (Alamat)
|
|
206
|
+
if (!uraian.match(/(.*?)\((.*)\)/)) {
|
|
207
|
+
debug('0>', penerima, '<=>', uraian);
|
|
208
|
+
if (penerima) {
|
|
209
|
+
if (SipdUtil.isAlamat(uraian)) {
|
|
210
|
+
if (uraian.toLowerCase().indexOf(penerima.toLowerCase()) < 0) {
|
|
211
|
+
debug('1>', `${penerima} (${uraian})`);
|
|
212
|
+
uraian = `${penerima} (${uraian})`;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
if (uraian.toLowerCase().indexOf(penerima.toLowerCase()) < 0) {
|
|
216
|
+
debug('2>', penerima, JSON.stringify(this));
|
|
217
|
+
this.spek = uraian;
|
|
218
|
+
uraian = penerima;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
this.uraian = uraian;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = SipdAgr;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2022-2025 Toha <tohenk@yahoo.com>
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
8
|
+
* the Software without restriction, including without limitation the rights to
|
|
9
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
+
* of the Software, and to permit persons to whom the Software is furnished to do
|
|
11
|
+
* so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class SipdAgrMetaData {
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this.column = Object.freeze({
|
|
29
|
+
REKENING: 'rekening',
|
|
30
|
+
JENIS: 'jenis',
|
|
31
|
+
URAIAN: 'uraian',
|
|
32
|
+
SPEK: 'spek',
|
|
33
|
+
KODE: 'kode',
|
|
34
|
+
VOLUME: 'volume',
|
|
35
|
+
SATUAN: 'satuan',
|
|
36
|
+
HARGA: 'harga',
|
|
37
|
+
TOTAL: 'total',
|
|
38
|
+
REF: 'ref',
|
|
39
|
+
});
|
|
40
|
+
this.columns = {
|
|
41
|
+
[this.column.REKENING]: 'Kode Rekening',
|
|
42
|
+
[this.column.JENIS]: 'Jenis',
|
|
43
|
+
[this.column.URAIAN]: 'Uraian',
|
|
44
|
+
[this.column.SPEK]: 'Spek',
|
|
45
|
+
[this.column.KODE]: 'Kode',
|
|
46
|
+
[this.column.VOLUME]: 'Volume',
|
|
47
|
+
[this.column.SATUAN]: 'Satuan',
|
|
48
|
+
[this.column.HARGA]: 'Harga Satuan',
|
|
49
|
+
[this.column.TOTAL]: 'Total',
|
|
50
|
+
[this.column.REF]: 'Ref',
|
|
51
|
+
}
|
|
52
|
+
this.headers = [
|
|
53
|
+
{r: 1, key: 'SKPD', var1: 'kode_skpd', var2: 'nama_skpd'},
|
|
54
|
+
{r: 2, key: 'Kegiatan', var1: 'kode_keg', var2: 'nama_keg'},
|
|
55
|
+
{r: 3, key: 'Sub Kegiatan', var1: 'kode', var2: 'nama'},
|
|
56
|
+
];
|
|
57
|
+
this.datatype = Object.freeze({SUBKEG: 'SUB', REKENING: 'REK', URAIAN: 'UR'});
|
|
58
|
+
this.datarow = 6;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class SipdAgrWriter {
|
|
63
|
+
|
|
64
|
+
constructor(sheet) {
|
|
65
|
+
this.sheet = sheet;
|
|
66
|
+
this.metadata = new SipdAgrMetaData();
|
|
67
|
+
this.pos = this.metadata.datarow;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
nextRow(nextRow = true) {
|
|
71
|
+
if (nextRow) this.pos++;
|
|
72
|
+
this.row = this.sheet.getRow(this.pos);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
rowset(col, value) {
|
|
76
|
+
if (this.row) {
|
|
77
|
+
return this.row.getCell(this.columns[col]).value = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
write(agr) {
|
|
82
|
+
this.writeHeader(agr);
|
|
83
|
+
let seq = 0;
|
|
84
|
+
agr.items.forEach(pek => this.writePek(pek, ++seq));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
writeHeader(data) {
|
|
88
|
+
this.columns = {};
|
|
89
|
+
this.metadata.headers.forEach(info => {
|
|
90
|
+
const row = this.sheet.getRow(info.r);
|
|
91
|
+
row.getCell(1).value = info.key;
|
|
92
|
+
row.getCell(2).value = data[info.var1] ? data[info.var1] : null;
|
|
93
|
+
row.getCell(3).value = data[info.var2] ? data[info.var2] : null;
|
|
94
|
+
});
|
|
95
|
+
this.nextRow(false);
|
|
96
|
+
let col = 0;
|
|
97
|
+
Object.keys(this.metadata.columns).forEach(key => {
|
|
98
|
+
col++;
|
|
99
|
+
this.row.getCell(col).value = this.metadata.columns[key];
|
|
100
|
+
this.columns[key] = col;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
writePek(agr, seq) {
|
|
105
|
+
this.nextRow();
|
|
106
|
+
this.rowset(this.metadata.column.REKENING, seq);
|
|
107
|
+
this.rowset(this.metadata.column.JENIS, this.metadata.datatype.SUBKEG);
|
|
108
|
+
this.rowset(this.metadata.column.URAIAN, agr.nama);
|
|
109
|
+
agr.items.forEach(rek => this.writeRek(rek));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
writeRek(agr) {
|
|
113
|
+
this.nextRow();
|
|
114
|
+
this.rowset(this.metadata.column.REKENING, agr.kode);
|
|
115
|
+
this.rowset(this.metadata.column.JENIS, this.metadata.datatype.REKENING);
|
|
116
|
+
this.rowset(this.metadata.column.URAIAN, agr.nama);
|
|
117
|
+
let seq = 0;
|
|
118
|
+
agr.items.forEach(rinci => this.writeRinci(rinci, ++seq));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
writeRinci(agr, seq) {
|
|
122
|
+
this.nextRow();
|
|
123
|
+
this.rowset(this.metadata.column.REKENING, seq);
|
|
124
|
+
this.rowset(this.metadata.column.JENIS, this.metadata.datatype.URAIAN);
|
|
125
|
+
this.rowset(this.metadata.column.URAIAN, agr.uraian);
|
|
126
|
+
this.rowset(this.metadata.column.SPEK, agr.spek);
|
|
127
|
+
this.rowset(this.metadata.column.KODE, agr.ssh);
|
|
128
|
+
this.rowset(this.metadata.column.SATUAN, agr.satuan);
|
|
129
|
+
this.rowset(this.metadata.column.VOLUME, agr.volume);
|
|
130
|
+
this.rowset(this.metadata.column.HARGA, agr.harga);
|
|
131
|
+
this.rowset(this.metadata.column.TOTAL, agr.total);
|
|
132
|
+
this.rowset(this.metadata.column.REF, agr.ref);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = SipdAgrWriter;
|