mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-29 08:01:02 +03:00
fix(web-test): readTable разбирает сгруппированную шапку по «Группа / Колонка»
1С кладёт заголовок группы колонок и её листья в ОДНУ строку шапки, различая их только высотой и шириной. Колонка заводилась на каждый бокс с текстом, а строка ключуется по имени — поэтому одноимённые листья соседних групп («План» под «Цена» и под «Количество») схлопывались в один ключ и склеивались через " / ", а заголовки групп становились пустыми колонками. Заголовок группы отличается от настоящей широкой колонки над узкими (паттерн «Исполнитель» над «Срок»/«Выполнена») одним надёжным признаком: его colindex не встречается ни в одной ячейке тела. По нему листья переименовываются в «Группа / Лист» — той же конвенцией, что уже применяет читалка табличного документа, — а заголовок из колонок убирается. Разворот объединённой шапки («Субконто 1/2/3») не задет: под ним боксов-листьев в шапке нет. Заодно у пути записи заголовок ячейки резолвился x-сканом шапки и под группой отдавал «Цена» всем трём листьям — fillTableRow по имени из readTable отвечал notFilled. Теперь имя берётся из общей модели, как в чтении и клике. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ba27f2218f
commit
006e64405c
@@ -191,6 +191,8 @@ if (t.rows[0]['Присоединенные файлы']) { /* has an attached f
|
||||
t.rows[0]['ЭДО'] === 'pic:1'; // connected to 1С-ЭДО ('pic:0' = not)
|
||||
```
|
||||
|
||||
**Grouped headers.** Columns merged under a group caption are reported with that caption: `'Цена / План'`, `'Цена / Факт'` — the caption alone is not a data column. Such names also work in `clickElement({row, column})` and `fillTableRow`; a short name (`'Факт'`) resolves too, picking the leftmost match.
|
||||
|
||||
Special row fields:
|
||||
- `_kind: 'group'` — hierarchical group row
|
||||
- `_kind: 'parent'` — parent row in hierarchy
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test dom shared v1.8 — embedded JS function constants
|
||||
// web-test dom shared v1.9 — embedded JS function constants
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* Shared function strings embedded into page.evaluate() generators.
|
||||
@@ -259,6 +259,35 @@ function buildColumnModel(grid) {
|
||||
columns.push(Object.assign(base, { name: name, text: '', title: title, kind: kind }));
|
||||
});
|
||||
|
||||
// Column GROUPS («Цена» over «План»/«Факт»/«Откл.»). 1С puts the group caption and its leaves
|
||||
// into the SAME head line, differing by y and width. A group caption is not a column: it has no
|
||||
// cells of its own, and leaf names repeat across groups («План» under both «Цена» and
|
||||
// «Количество») — keyed by bare name, values of different groups collided into one key.
|
||||
// The caption is told apart from a genuine wide column (pattern «Исполнитель» over «Срок»/
|
||||
// «Выполнена», see 24-multirow-header) by ONE reliable fact: its colindex never appears among
|
||||
// body cells. Geometry alone cannot tell them apart — both sit above narrower boxes.
|
||||
// Leaves are renamed «Группа / Лист», the same convention the spreadsheet reader uses.
|
||||
const bodyCi = new Set();
|
||||
lines.slice(0, 5).forEach(line => {
|
||||
[...line.children].forEach(b => {
|
||||
if (b.offsetWidth === 0) return;
|
||||
const ci = b.getAttribute('colindex');
|
||||
if (ci != null) bodyCi.add(ci);
|
||||
});
|
||||
});
|
||||
const covers = (g, c) => { const cx = c.x + (c.right - c.x) / 2; return c.y > g.y && cx >= g.x && cx < g.right; };
|
||||
const groupHdrs = columns.filter(g => g.ci != null && !bodyCi.has(g.ci) && columns.some(c => c !== g && covers(g, c)));
|
||||
if (groupHdrs.length) {
|
||||
columns.forEach(c => {
|
||||
if (groupHdrs.indexOf(c) >= 0) return;
|
||||
const parents = groupHdrs.filter(g => covers(g, c)).sort((a, b) => a.y - b.y);
|
||||
if (!parents.length) return;
|
||||
c.name = parents.map(g => g.text).concat(c.name).join(' / ');
|
||||
c.group = parents.map(g => g.text).join(' / ');
|
||||
});
|
||||
groupHdrs.forEach(g => { const at = columns.indexOf(g); if (at >= 0) columns.splice(at, 1); });
|
||||
}
|
||||
|
||||
const keyOf = c => Math.round(c.x) + ':' + Math.round(c.right);
|
||||
const groups = new Map();
|
||||
columns.forEach(c => { const k = keyOf(c); if (!groups.has(k)) groups.set(k, []); groups.get(k).push(c); });
|
||||
@@ -637,7 +666,7 @@ function readForm(p) {
|
||||
const text = (textEl || box).innerText?.trim().replace(/\\n/g, ' ') || '';
|
||||
if (text) {
|
||||
const r = box.getBoundingClientRect();
|
||||
columns.push({ text, x: r.x, right: r.x + r.width, y: r.y, h: r.height });
|
||||
columns.push({ text, ci: box.getAttribute('colindex'), x: r.x, right: r.x + r.width, y: r.y, h: r.height });
|
||||
} else {
|
||||
// Unnamed column — check if data cells contain checkboxes
|
||||
const firstLine = body?.querySelector('.gridLine');
|
||||
@@ -651,6 +680,27 @@ function readForm(p) {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Column groups → «Группа / Лист». Mirrors buildColumnModel: a group caption owns no
|
||||
// cells, so its colindex is absent from the body; leaf names repeat across groups.
|
||||
const dataLines = [...(body?.querySelectorAll('.gridLine') || [])].slice(0, 5);
|
||||
if (dataLines.length && columns.length > 0) {
|
||||
const bodyCi = new Set();
|
||||
dataLines.forEach(line => [...line.children].forEach(b => {
|
||||
if (b.offsetWidth === 0) return;
|
||||
const ci = b.getAttribute('colindex');
|
||||
if (ci != null) bodyCi.add(ci);
|
||||
}));
|
||||
const covers = (g, c) => { const cx = c.x + (c.right - c.x) / 2; return c.y > g.y && cx >= g.x && cx < g.right; };
|
||||
const grpHdrs = columns.filter(g => g.ci != null && !bodyCi.has(g.ci) && columns.some(c => c !== g && covers(g, c)));
|
||||
if (grpHdrs.length) {
|
||||
columns.forEach(c => {
|
||||
if (grpHdrs.indexOf(c) >= 0) return;
|
||||
const parents = grpHdrs.filter(g => covers(g, c)).sort((a, b) => a.y - b.y);
|
||||
if (parents.length) c.text = parents.map(g => g.text).concat(c.text).join(' / ');
|
||||
});
|
||||
grpHdrs.forEach(g => { const at = columns.indexOf(g); if (at >= 0) columns.splice(at, 1); });
|
||||
}
|
||||
}
|
||||
// Expand single merged headers with multiple data sub-rows (e.g. "Субконто Дт" → 1/2/3)
|
||||
const firstLine = body?.querySelector('.gridLine');
|
||||
if (firstLine && columns.length > 0) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// web-test dom/grid-edit v1.3 — DOM scripts for row-fill (grid edit-time operations)
|
||||
// web-test dom/grid-edit v1.4 — DOM scripts for row-fill (grid edit-time operations)
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
//
|
||||
import { HEADERLESS_GRID_FN, COLUMN_MODEL_FN } from './_shared.mjs';
|
||||
import { COLUMN_MODEL_FN } from './_shared.mjs';
|
||||
//
|
||||
// All helpers below accept an optional `gridSelector`. When passed, they target
|
||||
// that exact grid; when null/undefined they pick the LAST visible `.grid` on
|
||||
@@ -26,23 +26,16 @@ function gridResolver(gridSelector) {
|
||||
*/
|
||||
export function sortFieldKeysByColindexScript(gridSelector, fieldKeys) {
|
||||
return `(() => {
|
||||
${HEADERLESS_GRID_FN}
|
||||
${COLUMN_MODEL_FN}
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
if (!grid) return null;
|
||||
const head = grid.querySelector('.gridHead');
|
||||
// Names come from the shared model (headed and headerless alike), so a key written the way
|
||||
// readTable reports it — «Цена / Факт» — sorts into its real position instead of the tail.
|
||||
const cols = [];
|
||||
if (head) {
|
||||
const headLine = head.querySelector('.gridLine') || head;
|
||||
[...headLine.children].forEach(box => {
|
||||
if (box.offsetWidth === 0) return;
|
||||
const t = ((box.querySelector('.gridBoxText') || box).innerText?.trim() || '').toLowerCase();
|
||||
const ci = parseInt(box.getAttribute('colindex') || '-1');
|
||||
if (t) cols.push({ text: t, colindex: ci });
|
||||
});
|
||||
} else {
|
||||
// Headerless: synthesized columns (КолонкаN/(checkbox)) ordered by colindex
|
||||
synthHeaderlessColumns(grid).forEach(c => cols.push({ text: c.name.toLowerCase(), colindex: parseInt(c.colindex) }));
|
||||
}
|
||||
buildColumnModel(grid).columns.forEach(c => {
|
||||
if (!c.name) return;
|
||||
cols.push({ text: c.name.toLowerCase(), colindex: c.ci != null ? parseInt(c.ci) : 999 });
|
||||
});
|
||||
const keys = ${JSON.stringify(fieldKeys)};
|
||||
const mapped = keys.map(k => {
|
||||
const exact = cols.find(c => c.text === k);
|
||||
@@ -233,7 +226,7 @@ export function getGridEditCheckScript() {
|
||||
*/
|
||||
export function readActiveGridCellScript() {
|
||||
return `(() => {
|
||||
${HEADERLESS_GRID_FN}
|
||||
${COLUMN_MODEL_FN}
|
||||
const f = document.activeElement;
|
||||
if (!f) return { tag: 'none' };
|
||||
if (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA') {
|
||||
@@ -244,8 +237,22 @@ export function readActiveGridCellScript() {
|
||||
if (grid) {
|
||||
const fr = f.getBoundingClientRect();
|
||||
const head = grid.querySelector('.gridHead');
|
||||
// Column name from the shared model — same naming as readTable, including
|
||||
// «Группа / Колонка» under a grouped header. The editing INPUT sits in an overlay,
|
||||
// so the cell is located by x against the first body line, then by its colindex.
|
||||
const model = buildColumnModel(grid);
|
||||
const bLine = grid.querySelector('.gridBody .gridLine');
|
||||
if (bLine) for (const b of bLine.children) {
|
||||
if (b.offsetWidth === 0) continue;
|
||||
const br = b.getBoundingClientRect();
|
||||
if (fr.x >= br.x && fr.x < br.x + br.width) {
|
||||
const ci = b.getAttribute('colindex');
|
||||
if (ci != null && model.byCi[ci]) headerText = model.byCi[ci].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const hl = head?.querySelector('.gridLine') || head;
|
||||
if (hl) for (const h of hl.children) {
|
||||
if (!headerText && hl) for (const h of hl.children) {
|
||||
if (h.offsetWidth === 0) continue;
|
||||
const hr = h.getBoundingClientRect();
|
||||
if (fr.x >= hr.x && fr.x < hr.x + hr.width) {
|
||||
@@ -254,7 +261,7 @@ export function readActiveGridCellScript() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!head) {
|
||||
if (!headerText && !head) {
|
||||
// Headerless: the editing INPUT is rendered in an overlay (.inputs) OUTSIDE
|
||||
// the .gridBox, so walking ancestors for colindex fails. Resolve colindex by
|
||||
// matching the input's x against the body cells (same idea as the headed branch).
|
||||
|
||||
Reference in New Issue
Block a user