@dforge-core/dforge-mcp 0.2.44 → 0.2.46-next
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/CHANGELOG.md +73 -0
- package/README.md +3 -2
- package/dist/server.js +109 -84
- package/package.json +48 -46
- package/resources/docs/conventions.md +17 -14
- package/resources/docs/dsl-reference.md +15 -5
- package/resources/schemas/entity.schema.json +1 -1
- package/resources/schemas/registers.schema.json +8 -3
- package/scripts/install-skills.mjs +1 -0
- package/skills/dforge-1c-import/SKILL.md +374 -0
- package/skills/dforge-1c-import/references/environment.md +97 -0
- package/skills/dforge-1c-import/references/loading.md +161 -0
- package/skills/dforge-1c-import/references/measurements.md +210 -0
- package/skills/dforge-1c-import/references/print-forms.md +148 -0
- package/skills/dforge-1c-import/scripts/1c.py +178 -0
- package/skills/dforge-1c-import/scripts/README.md +110 -0
- package/skills/dforge-1c-import/scripts/lib/analyze.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/attrs.py +278 -0
- package/skills/dforge-1c-import/scripts/lib/autospec.py +964 -0
- package/skills/dforge-1c-import/scripts/lib/binding.py +257 -0
- package/skills/dforge-1c-import/scripts/lib/bsl.py +811 -0
- package/skills/dforge-1c-import/scripts/lib/callgraph.py +717 -0
- package/skills/dforge-1c-import/scripts/lib/cli.py +53 -0
- package/skills/dforge-1c-import/scripts/lib/columnmap.py +346 -0
- package/skills/dforge-1c-import/scripts/lib/coverage.py +242 -0
- package/skills/dforge-1c-import/scripts/lib/custom.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/delta.py +741 -0
- package/skills/dforge-1c-import/scripts/lib/deps.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/docusage.py +105 -0
- package/skills/dforge-1c-import/scripts/lib/dump1c.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/explore.py +55 -0
- package/skills/dforge-1c-import/scripts/lib/external.py +201 -0
- package/skills/dforge-1c-import/scripts/lib/extract_code.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/forms.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/genmodule.py +1432 -0
- package/skills/dforge-1c-import/scripts/lib/handlers.py +202 -0
- package/skills/dforge-1c-import/scripts/lib/handmap.py +142 -0
- package/skills/dforge-1c-import/scripts/lib/i18n_coverage.py +87 -0
- package/skills/dforge-1c-import/scripts/lib/idioms.py +273 -0
- package/skills/dforge-1c-import/scripts/lib/integrity.py +264 -0
- package/skills/dforge-1c-import/scripts/lib/inventory.py +535 -0
- package/skills/dforge-1c-import/scripts/lib/layers.py +106 -0
- package/skills/dforge-1c-import/scripts/lib/meta.py +315 -0
- package/skills/dforge-1c-import/scripts/lib/migrate.py +1299 -0
- package/skills/dforge-1c-import/scripts/lib/mxl.py +368 -0
- package/skills/dforge-1c-import/scripts/lib/naming.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/paths.py +90 -0
- package/skills/dforge-1c-import/scripts/lib/peek.py +70 -0
- package/skills/dforge-1c-import/scripts/lib/posted.py +218 -0
- package/skills/dforge-1c-import/scripts/lib/posting.py +655 -0
- package/skills/dforge-1c-import/scripts/lib/printgen.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/rebuild.py +194 -0
- package/skills/dforge-1c-import/scripts/lib/recorded.py +170 -0
- package/skills/dforge-1c-import/scripts/lib/recorders.py +94 -0
- package/skills/dforge-1c-import/scripts/lib/reftargets.py +95 -0
- package/skills/dforge-1c-import/scripts/lib/reportgen.py +157 -0
- package/skills/dforge-1c-import/scripts/lib/roles.py +33 -0
- package/skills/dforge-1c-import/scripts/lib/seedstate.py +488 -0
- package/skills/dforge-1c-import/scripts/lib/sizing.py +184 -0
- package/skills/dforge-1c-import/scripts/lib/spec.py +400 -0
- package/skills/dforge-1c-import/scripts/lib/statements.py +774 -0
- package/skills/dforge-1c-import/scripts/lib/subsystems.py +480 -0
- package/skills/dforge-1c-import/scripts/lib/terms.py +451 -0
- package/skills/dforge-1c-import/scripts/lib/terms_uk.py +555 -0
- package/skills/dforge-1c-import/scripts/lib/totals.py +387 -0
- package/skills/dforge-1c-import/scripts/lib/typemap.py +73 -0
- package/skills/dforge-1c-import/scripts/lib/unpack.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/validate.py +508 -0
- package/skills/dforge-1c-import/scripts/project-template/contours.py +47 -0
- package/skills/dforge-1c-import/scripts/project-template/sources.py +14 -0
- package/skills/dforge-mcp-author/SKILL.md +8 -2
- package/skills/dforge-mcp-author/examples/column-security/entities/product.json +1 -1
- package/skills/dforge-mcp-author/examples/column-security/manifest.json +0 -1
- package/skills/dforge-mcp-author/examples/column-security/security/roles.json +2 -2
- package/skills/dforge-mcp-author/references/action-dsl.md +20 -7
- package/skills/dforge-mcp-author/references/conventions.md +7 -1
- package/skills/dforge-mcp-author/references/field-types.md +3 -2
- package/skills/dforge-mcp-author/references/formulas.md +5 -1
- package/skills/dforge-mcp-author/references/print-templates.md +5 -2
- package/skills/dforge-mcp-author/references/queries.md +1 -1
- package/skills/dforge-mcp-author/references/registers.md +18 -3
- package/skills/dforge-mcp-author/references/reports.md +4 -1
- package/skills/dforge-mcp-author/references/schema-import.md +1 -1
- package/skills/dforge-mcp-author/references/security.md +1 -1
- package/skills/dforge-mcp-author/references/translations.md +38 -15
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -3
- package/skills/dforge-module-build/SKILL.md +10 -1
- package/skills/dforge-module-ship/SKILL.md +31 -3
- package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +0 -35
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Which document types write a register, and how much.
|
|
5
|
+
|
|
6
|
+
A register with _RecorderTRef is written by several document kinds; the TRef is the
|
|
7
|
+
target's type id, which appears in the reference columns of any table pointing at the
|
|
8
|
+
same type. Matching it back to a document is done through DBNames: the type id is the
|
|
9
|
+
low bytes of the table number, so comparing against every document's table number
|
|
10
|
+
identifies it.
|
|
11
|
+
|
|
12
|
+
python tools/recorders.py _AccumRg17050
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import print_function
|
|
15
|
+
|
|
16
|
+
import codecs
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import paths
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
|
|
25
|
+
import dump1c
|
|
26
|
+
|
|
27
|
+
OUT = paths.OUT
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_report():
|
|
31
|
+
with codecs.open(os.path.join(OUT, "report.json"), "r", "utf-8") as f:
|
|
32
|
+
return json.load(f)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def probe_single(cur, table):
|
|
36
|
+
"""Without a TRef the recorder's type is implicit, so find the one document table
|
|
37
|
+
whose PK matches the recorders we sampled."""
|
|
38
|
+
cur.execute("SELECT DISTINCT TOP (20) _RecorderRRef FROM [%s]" % table)
|
|
39
|
+
refs = [bytes(r[0]) for r in cur.fetchall()]
|
|
40
|
+
if not refs:
|
|
41
|
+
print(" (register is empty)")
|
|
42
|
+
return
|
|
43
|
+
placeholders = ",".join("?" * len(refs))
|
|
44
|
+
for row in load_report():
|
|
45
|
+
physical = row.get("physical") or ""
|
|
46
|
+
suffix = physical[len("_Document"):] if physical.startswith("_Document") else ""
|
|
47
|
+
if not suffix.isdigit() or not row.get("rows"):
|
|
48
|
+
continue
|
|
49
|
+
cur.execute("SELECT COUNT(*) FROM [%s] WHERE _IDRRef IN (%s)"
|
|
50
|
+
% (physical, placeholders), *refs)
|
|
51
|
+
hits = cur.fetchone()[0]
|
|
52
|
+
if hits:
|
|
53
|
+
print(" %s - %s (%d/%d sampled recorders matched)"
|
|
54
|
+
% (row["name"], row["synonym"] or "", hits, len(refs)))
|
|
55
|
+
return
|
|
56
|
+
print(" no document table matched the sampled recorders")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
table = sys.argv[1]
|
|
61
|
+
cur = dump1c.connect(paths.required("DF1C_SERVER", "which MS SQL server holds it"),
|
|
62
|
+
paths.required("DF1C_DB",
|
|
63
|
+
"which MS SQL infobase to read")).cursor()
|
|
64
|
+
cur.execute("""SELECT COUNT(*) FROM sys.columns
|
|
65
|
+
WHERE object_id = OBJECT_ID(?) AND name = '_RecorderTRef'""", table)
|
|
66
|
+
if not cur.fetchone()[0]:
|
|
67
|
+
print("%s has no _RecorderTRef - one document type writes it; probing for it"
|
|
68
|
+
% table)
|
|
69
|
+
probe_single(cur, table)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
cur.execute("SELECT _RecorderTRef, COUNT(*) FROM [%s] GROUP BY _RecorderTRef "
|
|
73
|
+
"ORDER BY 2 DESC" % table)
|
|
74
|
+
rows = cur.fetchall()
|
|
75
|
+
|
|
76
|
+
# a document's type id is its table number; _RecorderTRef stores it big-endian
|
|
77
|
+
by_number = {}
|
|
78
|
+
for row in load_report():
|
|
79
|
+
physical = row.get("physical") or ""
|
|
80
|
+
suffix = physical[len("_Document"):] if physical.startswith("_Document") else ""
|
|
81
|
+
if suffix.isdigit(): # skips _DocumentJournal<N>
|
|
82
|
+
by_number[int(suffix)] = row
|
|
83
|
+
|
|
84
|
+
print("%-12s %-12s %s" % ("TRef", "movements", "document"))
|
|
85
|
+
for tref, n in rows:
|
|
86
|
+
number = int.from_bytes(bytes(tref), "big") & 0xFFFF
|
|
87
|
+
doc = by_number.get(number)
|
|
88
|
+
print("%-12s %-12s %s" % (bytes(tref).hex(), "{:,}".format(n),
|
|
89
|
+
"%s - %s" % (doc["name"], doc["synonym"] or "") if doc
|
|
90
|
+
else "number=%d (unmatched)" % number))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
main()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Does every authored reference point where the 1C attribute actually points?
|
|
5
|
+
|
|
6
|
+
The generated modules resolve a reference from the attribute's declared type, so they
|
|
7
|
+
cannot get this wrong. The authored ones name their target by hand, and a name is a poor
|
|
8
|
+
guide: `БазоваяЕдиницаИзмерения` reads like the product's own units and points at the unit
|
|
9
|
+
classifier, and `МестоОтгрузки` reads like a counterparty and is not one.
|
|
10
|
+
|
|
11
|
+
A wrong target costs nothing until the data arrives, and then it is every row: 7,981 of
|
|
12
|
+
8,400 products dangled on the first load of `masterdata`.
|
|
13
|
+
|
|
14
|
+
python tools/reftargets.py
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import print_function
|
|
17
|
+
|
|
18
|
+
import codecs
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
import attrs
|
|
24
|
+
import autospec
|
|
25
|
+
import handmap
|
|
26
|
+
import binding
|
|
27
|
+
|
|
28
|
+
sources = binding.sources()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def ported(mapping):
|
|
32
|
+
"""1C object name -> the entity the port gave it."""
|
|
33
|
+
out = {}
|
|
34
|
+
for module, entities in mapping.items():
|
|
35
|
+
for entity, found in entities.items():
|
|
36
|
+
name = (found.get("object") or "").split(" / ")[0]
|
|
37
|
+
if name and "columns" in found:
|
|
38
|
+
out.setdefault(name, "%s.%s" % (module, entity))
|
|
39
|
+
return out
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
model = autospec.Model()
|
|
44
|
+
path = os.path.join(attrs.OUT, "columnmap.json")
|
|
45
|
+
with codecs.open(path, "r", "utf-8") as f:
|
|
46
|
+
mapping = json.load(f)
|
|
47
|
+
owner = ported(mapping)
|
|
48
|
+
|
|
49
|
+
wrong = 0
|
|
50
|
+
for module in sorted(handmap.SPECS):
|
|
51
|
+
for entity, spec in handmap.SPECS[module]["entities"].items():
|
|
52
|
+
declared = sources.SOURCES.get(module, {}).get(entity, {})
|
|
53
|
+
name = declared.get("object")
|
|
54
|
+
if not name:
|
|
55
|
+
continue
|
|
56
|
+
family, section = declared.get("family"), declared.get("section")
|
|
57
|
+
_, columns = model.physical(name, family)
|
|
58
|
+
by_physical = {}
|
|
59
|
+
for field in model.fields(name, family):
|
|
60
|
+
if (field.get("section") or None) != section:
|
|
61
|
+
continue
|
|
62
|
+
physical = autospec.physical_of(field, model, columns)
|
|
63
|
+
if physical:
|
|
64
|
+
by_physical[physical] = field
|
|
65
|
+
|
|
66
|
+
for item in spec["fields"]:
|
|
67
|
+
if not item[1].startswith("ref:"):
|
|
68
|
+
continue
|
|
69
|
+
target = item[1][len("ref:"):]
|
|
70
|
+
if "." not in target:
|
|
71
|
+
target = module + "." + target
|
|
72
|
+
physical = mapping[module][entity]["columns"].get(item[0] + "_id")
|
|
73
|
+
field = by_physical.get(physical)
|
|
74
|
+
if not field:
|
|
75
|
+
continue
|
|
76
|
+
declared_targets = [p.strip()[3:].strip()
|
|
77
|
+
for p in (field["type"] or "").split(",")
|
|
78
|
+
if p.strip().startswith("->")]
|
|
79
|
+
real = [t.split(".", 1)[1] for t in declared_targets if "." in t]
|
|
80
|
+
if not real:
|
|
81
|
+
continue
|
|
82
|
+
expected = [owner.get(r) for r in real]
|
|
83
|
+
if target in expected:
|
|
84
|
+
continue
|
|
85
|
+
wrong += 1
|
|
86
|
+
print("%-10s %-22s %-20s declared %-38s 1C: %s -> %s"
|
|
87
|
+
% (module, entity, item[0], target, ", ".join(real),
|
|
88
|
+
", ".join(str(e) for e in expected)))
|
|
89
|
+
|
|
90
|
+
print("\n%d reference(s) pointing at the wrong entity" % wrong)
|
|
91
|
+
return 1 if wrong else 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
sys.exit(main())
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Write a module's ported 1C reports from one compact spec.
|
|
3
|
+
|
|
4
|
+
A report over a stored procedure is declared in four places that have to agree - the
|
|
5
|
+
report (`ui/reports.json`), the procedure's call signature and columns
|
|
6
|
+
(`logic/stored_procedures.json`), the Ukrainian labels of both (`translations/uk-UA.json`)
|
|
7
|
+
and the rights to run them (`security/roles.json`) - and the platform binds them by code,
|
|
8
|
+
so a parameter spelled one way in the report and another in the procedure binds NULL
|
|
9
|
+
without a word. This writes all four from one spec, into the project's `authored/<module>/`
|
|
10
|
+
tree, which `genmodule` lays over the generated module; the menu gets a Reports section
|
|
11
|
+
from the reports file (genmodule.auto_menu).
|
|
12
|
+
|
|
13
|
+
A spec module (tools/project/reports_<module>.py) defines MODULE and REPORTS, an ordered
|
|
14
|
+
list of `report(...)`. The SQL of each procedure is the project's own file under
|
|
15
|
+
`authored/<module>/logic/reports/`.
|
|
16
|
+
|
|
17
|
+
python tools/lib/reportgen.py tools/project/reports_production.py
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import print_function
|
|
20
|
+
|
|
21
|
+
import codecs
|
|
22
|
+
import collections
|
|
23
|
+
import imp
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
O = collections.OrderedDict
|
|
29
|
+
|
|
30
|
+
KINDS = {
|
|
31
|
+
"money": ("currency", "number", 140),
|
|
32
|
+
"qty": ("number", "number", 120),
|
|
33
|
+
"int": ("number", "number", 70),
|
|
34
|
+
"date": ("date", "date", 110),
|
|
35
|
+
"datetime": ("datetime", "timestamp", 150),
|
|
36
|
+
"text": ("text", "string", 160),
|
|
37
|
+
"name": ("text", "string", 380),
|
|
38
|
+
"bool": ("checkbox", "bool", 80),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
PG_FIELD = {"date": "date", "integer": "number", "numeric": "number", "boolean": "checkbox",
|
|
42
|
+
"text": "text", "uuid": "lookup"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def col(code, en, uk, kind="text", width=None):
|
|
46
|
+
"""A result column: its code, English and Ukrainian labels, and kind (KINDS)."""
|
|
47
|
+
return dict(code=code, en=en, uk=uk, kind=kind, width=width)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def param(code, pg, en, uk, required=False, default=None, link=None):
|
|
51
|
+
"""A parameter: the procedure's argument and the report's, the same code in both. `link`
|
|
52
|
+
names the entity a uuid parameter picks from (`masterdata.warehouse`)."""
|
|
53
|
+
return dict(code=code, pg=pg, en=en, uk=uk, required=required, default=default, link=link)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def report(code, en, uk, sp, description, params, columns, source=None):
|
|
57
|
+
"""A report over one procedure. `source` names the 1C report it ports."""
|
|
58
|
+
return dict(code=code, en=en, uk=uk, sp=sp, description=description, params=params,
|
|
59
|
+
columns=columns, source=source)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def date_from(required=True):
|
|
63
|
+
return param("p_date_from", "date", "From", u"Початок періоду", required, "=STARTMONTH(NOW())")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def date_to(required=True):
|
|
67
|
+
return param("p_date_to", "date", "To", u"Кінець періоду", required, "=ENDMONTH(NOW())")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _field(p):
|
|
71
|
+
field = O([("fieldTypeCd", "lookup" if p["link"] else PG_FIELD.get(p["pg"], "text")),
|
|
72
|
+
("label", p["en"]), ("required", p["required"])])
|
|
73
|
+
if p["default"] is not None:
|
|
74
|
+
field["default"] = p["default"]
|
|
75
|
+
if p["link"]:
|
|
76
|
+
field["params"] = {"link": {"entity": p["link"]}}
|
|
77
|
+
return field
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _read(path):
|
|
81
|
+
if not os.path.isfile(path):
|
|
82
|
+
return O()
|
|
83
|
+
with codecs.open(path, "r", "utf-8") as f:
|
|
84
|
+
return json.load(f, object_pairs_hook=O)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _write(path, doc):
|
|
88
|
+
directory = os.path.dirname(path)
|
|
89
|
+
if not os.path.isdir(directory):
|
|
90
|
+
os.makedirs(directory)
|
|
91
|
+
with codecs.open(path, "w", "utf-8") as f:
|
|
92
|
+
f.write(json.dumps(doc, ensure_ascii=False, indent="\t") + "\n")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build(module, reports, roles, root):
|
|
96
|
+
"""Write the four files for `module` under `root` (authored/<module>/)."""
|
|
97
|
+
ui, sps, uk_reports, uk_sps, rights = O(), O(), O(), O(), O()
|
|
98
|
+
for r in reports:
|
|
99
|
+
columns = []
|
|
100
|
+
for c in r["columns"]:
|
|
101
|
+
field, base, width = KINDS[c["kind"]]
|
|
102
|
+
columns.append(O([("columnCd", c["code"]), ("label", c["en"]), ("fieldTypeCd", field),
|
|
103
|
+
("baseDatatypeCd", base), ("width", c["width"] or width)]))
|
|
104
|
+
sps[r["sp"]] = O([
|
|
105
|
+
("description", r["description"]), ("functionName", r["sp"]),
|
|
106
|
+
("params", [O([("paramCd", p["code"]), ("pgType", p["pg"]),
|
|
107
|
+
("fieldTypeCd", _field(p)["fieldTypeCd"]), ("label", p["en"]),
|
|
108
|
+
("required", p["required"])]) for p in r["params"]]),
|
|
109
|
+
("columns", columns)])
|
|
110
|
+
uk_sps[r["sp"]] = {"columns": O((c["code"], {"label": c["uk"]}) for c in r["columns"])}
|
|
111
|
+
ui[r["code"]] = O([
|
|
112
|
+
("label", r["en"]),
|
|
113
|
+
("description", r["description"] + (" (1C %s)" % r["source"] if r["source"] else "")),
|
|
114
|
+
("parameters", O((p["code"], _field(p)) for p in r["params"])),
|
|
115
|
+
("layout", {"panels": [O([("vizType", "table"), ("datasetCd", "rows"), ("title", r["en"])])]}),
|
|
116
|
+
("datasets", {"rows": O([("caption", r["en"]), ("datasetType", "S"), ("spCd", r["sp"])])})])
|
|
117
|
+
uk_reports[r["code"]] = O([("label", r["uk"]),
|
|
118
|
+
("params", O((p["code"], {"label": p["uk"]}) for p in r["params"])),
|
|
119
|
+
("datasets", {"rows": {"caption": r["uk"]}})])
|
|
120
|
+
rights["report:" + r["code"]] = "E"
|
|
121
|
+
rights["sp:" + r["sp"]] = "E"
|
|
122
|
+
|
|
123
|
+
_write(os.path.join(root, "ui", "reports.json"), ui)
|
|
124
|
+
# the print forms' procedures (printgen, prn_*) share the file and are kept
|
|
125
|
+
kept = _read(os.path.join(root, "logic", "stored_procedures.json"))
|
|
126
|
+
sps.update((k, v) for k, v in kept.items() if k.startswith("prn_"))
|
|
127
|
+
_write(os.path.join(root, "logic", "stored_procedures.json"), sps)
|
|
128
|
+
|
|
129
|
+
path = os.path.join(root, "translations", "uk-UA.json")
|
|
130
|
+
translations = _read(path)
|
|
131
|
+
translations["reports"] = uk_reports
|
|
132
|
+
translations["storedProcedures"] = uk_sps
|
|
133
|
+
_write(path, translations)
|
|
134
|
+
|
|
135
|
+
path = os.path.join(root, "security", "roles.json")
|
|
136
|
+
doc = _read(path)
|
|
137
|
+
for role in roles:
|
|
138
|
+
entry = doc.setdefault(role, O([("rights", O())]))
|
|
139
|
+
entry.setdefault("rights", O())
|
|
140
|
+
for key in [k for k in entry["rights"] if k.startswith(("report:", "sp:")) and not k.startswith("sp:prn_")]:
|
|
141
|
+
del entry["rights"][key]
|
|
142
|
+
entry["rights"].update(rights)
|
|
143
|
+
_write(path, doc)
|
|
144
|
+
return len(reports)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main(argv):
|
|
148
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
149
|
+
for spec_path in argv[1:]:
|
|
150
|
+
spec = imp.load_source(os.path.splitext(os.path.basename(spec_path))[0], spec_path)
|
|
151
|
+
root = os.path.join(here, "..", "project", "authored", spec.MODULE)
|
|
152
|
+
count = build(spec.MODULE, spec.REPORTS, spec.ROLES, os.path.normpath(root))
|
|
153
|
+
print("%s: %d report(s)" % (spec.MODULE, count))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
main(sys.argv)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""List the distinct collection class GUIDs across a set of objects, with samples,
|
|
4
|
+
so each one can be labelled (dimension / resource / attribute / tabular column)."""
|
|
5
|
+
from __future__ import print_function
|
|
6
|
+
|
|
7
|
+
import collections
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import attrs
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
objects = attrs.load_json("objects.json")
|
|
14
|
+
types = attrs.load_json("typemap.json")
|
|
15
|
+
report = attrs.load_json("report.json")
|
|
16
|
+
|
|
17
|
+
names = sys.argv[1:] or [r["name"] for r in report if r.get("name")]
|
|
18
|
+
seen = collections.OrderedDict()
|
|
19
|
+
for name in names:
|
|
20
|
+
guid, fields = attrs.fields_of(name, objects, types)
|
|
21
|
+
if not fields:
|
|
22
|
+
continue
|
|
23
|
+
family = next((r["family"] for r in report if r["name"] == name), "?")
|
|
24
|
+
for f in fields:
|
|
25
|
+
key = (family, f["collection"])
|
|
26
|
+
entry = seen.setdefault(key, {"count": 0, "samples": []})
|
|
27
|
+
entry["count"] += 1
|
|
28
|
+
if len(entry["samples"]) < 3:
|
|
29
|
+
entry["samples"].append("%s/%s" % (name, f["name"]))
|
|
30
|
+
|
|
31
|
+
for (family, guid), entry in sorted(seen.items()):
|
|
32
|
+
print("%-16s %-40s %5d %s" % (family, guid, entry["count"],
|
|
33
|
+
", ".join(entry["samples"])[:90]))
|