mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-08 04:30:19 +03:00
feat(web-test): multi-row grid support in readTable — split merged headers
readTable now handles multi-row grids (e.g. accounting journal entries) where a single column header spans multiple data sub-rows: - "Субконто Дт" with 3 data cells → "Субконто Дт 1", "Субконто Дт 2", "Субконто Дт 3" - Stacked headers (2+ at same X) matched by Y-order (e.g. "Счет Дт" / "Подразделение Дт") - getFormState tables[].columns also expanded for consistency - Flat/simple grids unaffected (no multi-row detection triggers) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d3520a8945
commit
338faa253d
@@ -232,7 +232,8 @@ const READ_FORM_FN = `function readForm(p) {
|
||||
const textEl = box.querySelector('.gridBoxText');
|
||||
const text = (textEl || box).innerText?.trim().replace(/\\n/g, ' ') || '';
|
||||
if (text) {
|
||||
columns.push(text);
|
||||
const r = box.getBoundingClientRect();
|
||||
columns.push({ text, 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');
|
||||
@@ -241,18 +242,47 @@ const READ_FORM_FN = `function readForm(p) {
|
||||
const idx = visibleHeaders.indexOf(box);
|
||||
const cells = [...firstLine.children].filter(c => c.offsetWidth > 0);
|
||||
if (cells[idx]?.querySelector('.checkbox')) {
|
||||
columns.push('(checkbox)');
|
||||
columns.push({ text: '(checkbox)', x: 0, right: 0, y: 0, h: 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Expand single merged headers with multiple data sub-rows (e.g. "Субконто Дт" → 1/2/3)
|
||||
const firstLine = body?.querySelector('.gridLine');
|
||||
if (firstLine && columns.length > 0) {
|
||||
const xGrp = new Map();
|
||||
columns.forEach(c => {
|
||||
const k = Math.round(c.x) + ':' + Math.round(c.right);
|
||||
if (!xGrp.has(k)) xGrp.set(k, []);
|
||||
xGrp.get(k).push(c);
|
||||
});
|
||||
for (const [k, hdrs] of xGrp) {
|
||||
if (hdrs.length !== 1) continue;
|
||||
let cnt = 0;
|
||||
[...firstLine.children].forEach(box => {
|
||||
if (box.offsetWidth === 0) return;
|
||||
const r = box.getBoundingClientRect();
|
||||
const cx = r.x + r.width / 2;
|
||||
if (cx >= hdrs[0].x && cx < hdrs[0].right) cnt++;
|
||||
});
|
||||
if (cnt > 1) {
|
||||
const base = hdrs[0];
|
||||
const baseIdx = columns.indexOf(base);
|
||||
columns.splice(baseIdx, 1);
|
||||
for (let si = 0; si < cnt; si++) {
|
||||
columns.splice(baseIdx + si, 0, { text: base.text + ' ' + (si + 1), x: base.x, right: base.right, y: 0, h: 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const colNames = columns.map(c => c.text);
|
||||
const rowCount = body ? body.querySelectorAll('.gridLine').length : 0;
|
||||
// Visual label from group title (e.g. "Входящие:" for grid "Входящие")
|
||||
const titleEl = document.getElementById(p + name + '#title_div')
|
||||
|| document.getElementById(p + 'Группа' + name + '#title_div');
|
||||
const label = titleEl ? (titleEl.innerText?.trim().replace(/:\\s*$/, '').replace(/\\u00a0/g, ' ') || null) : null;
|
||||
return { name, columns, rowCount, ...(label ? { label } : {}) };
|
||||
return { name, columns: colNames, rowCount, ...(label ? { label } : {}) };
|
||||
});
|
||||
result.tables = tables;
|
||||
// Backward compat: table = first grid summary
|
||||
@@ -536,15 +566,91 @@ export function readTableScript(formNum, { maxRows = 20, offset = 0, gridSelecto
|
||||
const cells = [...firstLine.children].filter(c => c.offsetWidth > 0);
|
||||
if (cells[idx]?.querySelector('.checkbox')) {
|
||||
const r = box.getBoundingClientRect();
|
||||
columns.push({ text: '(checkbox)', x: r.x, w: r.width, right: r.x + r.width });
|
||||
columns.push({ text: '(checkbox)', x: r.x, w: r.width, right: r.x + r.width, y: r.y, h: r.height });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const r = box.getBoundingClientRect();
|
||||
columns.push({ text, x: r.x, w: r.width, right: r.x + r.width });
|
||||
columns.push({ text, x: r.x, w: r.width, right: r.x + r.width, y: r.y, h: r.height });
|
||||
});
|
||||
|
||||
// Multi-row grid support: detect stacked/merged headers.
|
||||
// Group headers by X-range. For each group, count data sub-rows from first line.
|
||||
// - Stacked headers (2+ headers at same X) with multiple data rows → match by Y-order
|
||||
// - Single merged header with multiple data rows → expand to numbered columns (e.g. "Субконто Дт 1")
|
||||
const xGroups = new Map();
|
||||
columns.forEach(c => {
|
||||
const key = Math.round(c.x) + ':' + Math.round(c.right);
|
||||
if (!xGroups.has(key)) xGroups.set(key, []);
|
||||
xGroups.get(key).push(c);
|
||||
});
|
||||
for (const [, hdrs] of xGroups) hdrs.sort((a, b) => a.y - b.y);
|
||||
|
||||
const firstDataLine = body?.querySelector('.gridLine');
|
||||
const subRowMap = new Map();
|
||||
if (firstDataLine) {
|
||||
[...firstDataLine.children].forEach(box => {
|
||||
if (box.offsetWidth === 0) return;
|
||||
const r = box.getBoundingClientRect();
|
||||
const cx = r.x + r.width / 2;
|
||||
for (const [key, hdrs] of xGroups) {
|
||||
const h0 = hdrs[0];
|
||||
if (cx >= h0.x && cx < h0.right) {
|
||||
if (!subRowMap.has(key)) subRowMap.set(key, []);
|
||||
subRowMap.get(key).push({ y: r.y });
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
for (const [, subs] of subRowMap) subs.sort((a, b) => a.y - b.y);
|
||||
}
|
||||
|
||||
const multiRowGroups = new Map();
|
||||
for (const [key, hdrs] of xGroups) {
|
||||
const subs = subRowMap.get(key);
|
||||
if (!subs || subs.length <= 1) continue;
|
||||
if (hdrs.length >= 2) {
|
||||
multiRowGroups.set(key, hdrs);
|
||||
} else if (hdrs.length === 1 && subs.length > 1) {
|
||||
const base = hdrs[0];
|
||||
const baseIdx = columns.indexOf(base);
|
||||
columns.splice(baseIdx, 1);
|
||||
const expanded = [];
|
||||
for (let si = 0; si < subs.length; si++) {
|
||||
const numbered = {
|
||||
text: base.text + ' ' + (si + 1),
|
||||
x: base.x, w: base.w, right: base.right,
|
||||
y: base.y + si, h: base.h / subs.length, _subIdx: si
|
||||
};
|
||||
columns.splice(baseIdx + si, 0, numbered);
|
||||
expanded.push(numbered);
|
||||
}
|
||||
multiRowGroups.set(key, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
function matchColumn(cellX, cellW, cellY) {
|
||||
const cx = cellX + cellW / 2;
|
||||
for (const [key, hdrs] of multiRowGroups) {
|
||||
const h0 = hdrs[0];
|
||||
if (cx >= h0.x && cx < h0.right) {
|
||||
const subs = subRowMap.get(key);
|
||||
if (subs) {
|
||||
const subIdx = subs.findIndex(s => Math.abs(s.y - cellY) < 5);
|
||||
if (subIdx >= 0 && subIdx < hdrs.length) return hdrs[subIdx];
|
||||
}
|
||||
let best = hdrs[0], bestDist = Infinity;
|
||||
for (const h of hdrs) {
|
||||
const dist = Math.abs(cellY - h.y);
|
||||
if (dist < bestDist) { bestDist = dist; best = h; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
return columns.find(c => cx >= c.x && cx < c.right);
|
||||
}
|
||||
|
||||
// Extract data rows from gridBody
|
||||
const allLines = body.querySelectorAll('.gridLine');
|
||||
const total = allLines.length;
|
||||
@@ -566,10 +672,9 @@ export function readTableScript(formNum, { maxRows = 20, offset = 0, gridSelecto
|
||||
val = (textEl || box).innerText?.trim().replace(/\\n/g, ' ') || '';
|
||||
if (!val) return;
|
||||
}
|
||||
// Match cell to column by X-coordinate overlap
|
||||
// Match cell to column by X+Y overlap (multi-row aware)
|
||||
const r = box.getBoundingClientRect();
|
||||
const cx = r.x + r.width / 2;
|
||||
const col = columns.find(c => cx >= c.x && cx < c.right);
|
||||
const col = matchColumn(r.x, r.width, r.y);
|
||||
if (col) {
|
||||
row[col.text] = row[col.text] ? row[col.text] + ' / ' + val : val;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user