mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-04 18:50:23 +03:00
fix(web-test): единая модель колонок грида — colindex первым, геометрия запасной
Резолверов колонок было пять, все независимые, и каждый ломался по-своему:
readTable (геометрия X + Y-подряды), clickElement (геометрия X, без Y),
поиск строки {кол: знач} (геометрия X, без Y и fixed-гарда), filterList
(порядковый индекс шапки), fillTableRow (colindex — единственный целый).
Механика поломки (снята живьём на списке задач ERP): шапка «Исполнитель»
широкая (x 1085..1515) и накрывает «Срок» (1085..1251) и «Выполнена»
(1251..1515). Ячейка «Исполнитель» имеет центр 1300 → приписывается к группе
«Выполнена» → та получает лишний под-ряд → срабатывает эвристика «объединённая
шапка» → фантомные «Выполнена 1/2», а значения соседей склеиваются через ' / '.
Теперь COLUMN_MODEL_FN (dom/_shared.mjs) — единственный источник правды:
buildColumnModel / columnForCell / cellForColumn / resolveColumnByName. Идентичность
колонки — colindex (собственный id колонки в 1С, есть и на шапке, и на ячейке);
геометрия работает только там, где своей шапки у ячейки нет — под-ряды
объединённой шапки («Субконто Дт» над тремя ячейками). Путь записи пришёл к этому
решению раньше (grid-edit.mjs: «reliable across merged headers») — остальные
выровнены по нему.
Следствие: имя колонки из readTable теперь годится для клика/заполнения/фильтра —
раньше readTable отдавал «Субконто Дт 2», а клик про такое имя не знал.
Попутно закрыт второй дефект: безымянная picture-колонка определялась по ПЕРВОЙ
строке, а picField над Boolean не рисует картинку при Ложь → колонка пропадала из
columns целиком. Модель сэмплит до 10 строк и ищет ячейку по colindex.
Проверено:
- 24-multirow-header (стенд, оба паттерна ERP) — зелёный; до правки красный;
- клик проверяется по факту (DOM select+focus), а не по эху clicked.column:
до правки клик по «Срок» молча жал «Исполнитель 2» и рапортовал успех;
- живьём на ERP: список задач — 9 честных колонок вместо фантомов, значения на
местах; форма операции (шапка 2 этажа, строка 3 под-ряда) — «Субконто Дт/Кт 1..3»
сохранены, ничего не поехало;
- полный регресс 28/28.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
96b38d9f91
commit
7696a8b3ee
@@ -1,7 +1,7 @@
|
||||
// web-test dom/grid-edit v1.2 — DOM scripts for row-fill (grid edit-time operations)
|
||||
// web-test dom/grid-edit v1.3 — DOM scripts for row-fill (grid edit-time operations)
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
//
|
||||
import { HEADERLESS_GRID_FN } from './_shared.mjs';
|
||||
import { HEADERLESS_GRID_FN, 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
|
||||
@@ -67,45 +67,28 @@ export function sortFieldKeysByColindexScript(gridSelector, fieldKeys) {
|
||||
*/
|
||||
export function findCellCoordsByFieldsScript(gridSelector, row, fieldKeys) {
|
||||
return `(() => {
|
||||
${HEADERLESS_GRID_FN}
|
||||
${COLUMN_MODEL_FN}
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
if (!grid) return { error: 'no_grid' };
|
||||
const head = grid.querySelector('.gridHead');
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (!body) return { error: 'no_grid_body' };
|
||||
|
||||
// Read columns to find target colindex (+ subTarget for headerless split mark-boxes)
|
||||
const cols = [];
|
||||
if (head) {
|
||||
const headLine = head.querySelector('.gridLine') || head;
|
||||
[...headLine.children].forEach(box => {
|
||||
if (box.offsetWidth === 0) return;
|
||||
const t = box.querySelector('.gridBoxText');
|
||||
const ci = box.getAttribute('colindex');
|
||||
cols.push({ colindex: ci, text: ((t || box).innerText?.trim() || '').toLowerCase(), subTarget: null });
|
||||
});
|
||||
} else {
|
||||
synthHeaderlessColumns(grid).forEach(c => cols.push({ colindex: c.colindex, text: c.name.toLowerCase(), subTarget: c.subTarget }));
|
||||
}
|
||||
|
||||
// Column resolution — shared model (dom/_shared.mjs): same names and same physical cell
|
||||
// as readTable/click report, including «Имя 1/2/3» of an expanded merged header.
|
||||
const model = buildColumnModel(grid);
|
||||
const keys = ${JSON.stringify(fieldKeys)};
|
||||
let targetColindex = null, targetSub = null;
|
||||
let targetCol = null;
|
||||
for (const key of keys) {
|
||||
const exact = cols.find(c => c.text === key);
|
||||
if (exact) { targetColindex = exact.colindex; targetSub = exact.subTarget; break; }
|
||||
const inc = cols.find(c => c.text.includes(key) || key.includes(c.text));
|
||||
if (inc) { targetColindex = inc.colindex; targetSub = inc.subTarget; break; }
|
||||
targetCol = resolveColumnByName(model, key);
|
||||
if (targetCol) break;
|
||||
}
|
||||
const targetSub = targetCol ? (targetCol.subTarget || null) : null;
|
||||
|
||||
const rows = [...body.querySelectorAll('.gridLine')];
|
||||
if (${row} >= rows.length) return { error: 'row_out_of_range', total: rows.length };
|
||||
const line = rows[${row}];
|
||||
|
||||
// Find body cell by colindex (reliable across merged headers)
|
||||
let box = null;
|
||||
if (targetColindex != null) {
|
||||
box = [...line.children].find(b => b.offsetWidth > 0 && b.getAttribute('colindex') === targetColindex);
|
||||
}
|
||||
let box = targetCol ? cellForColumn(model, line, targetCol) : null;
|
||||
// Fallback: second visible box (skip checkbox/N column)
|
||||
if (!box) {
|
||||
const boxes = [...line.children].filter(b => b.offsetWidth > 0 && !b.classList.contains('gridBoxComp'));
|
||||
@@ -134,39 +117,30 @@ export function findCellCoordsByFieldsScript(gridSelector, row, fieldKeys) {
|
||||
*/
|
||||
export function findNextCellCoordsByKeyScript(gridSelector, row, key) {
|
||||
return `(() => {
|
||||
${HEADERLESS_GRID_FN}
|
||||
${COLUMN_MODEL_FN}
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
if (!grid) return null;
|
||||
const head = grid.querySelector('.gridHead');
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (!body) return null;
|
||||
const cols = [];
|
||||
if (head) {
|
||||
const headLine = head.querySelector('.gridLine') || head;
|
||||
[...headLine.children].forEach(box => {
|
||||
if (box.offsetWidth === 0) return;
|
||||
const t = box.querySelector('.gridBoxText');
|
||||
const ci = box.getAttribute('colindex');
|
||||
cols.push({ colindex: ci, text: ((t || box).innerText?.trim() || '').toLowerCase(), subTarget: null });
|
||||
});
|
||||
} else {
|
||||
synthHeaderlessColumns(grid).forEach(c => cols.push({ colindex: c.colindex, text: c.name.toLowerCase(), subTarget: c.subTarget }));
|
||||
}
|
||||
const model = buildColumnModel(grid);
|
||||
const kl = ${JSON.stringify(key.toLowerCase())};
|
||||
const klNoSpace = kl.replace(/[\\s\\-]+/g, '');
|
||||
let targetColindex = null, targetSub = null;
|
||||
const exact = cols.find(c => c.text === kl);
|
||||
if (exact) { targetColindex = exact.colindex; targetSub = exact.subTarget; }
|
||||
else {
|
||||
const inc = cols.find(c => c.text.includes(kl) || kl.includes(c.text)
|
||||
|| c.text.includes(klNoSpace) || klNoSpace.includes(c.text));
|
||||
if (inc) { targetColindex = inc.colindex; targetSub = inc.subTarget; }
|
||||
// Extra "no-space/no-dash" fuzz on top of the shared resolver: header «Группа Контрагентов»
|
||||
// must match the key «ГруппаКонтрагентов».
|
||||
let targetCol = resolveColumnByName(model, kl);
|
||||
if (!targetCol) {
|
||||
const squash = s => (s || '').toLowerCase().replace(/ё/g, 'е').replace(/[\\s\\-]+/g, '');
|
||||
const klNoSpace = squash(kl);
|
||||
targetCol = model.columns.find(c => {
|
||||
const t = squash(c.name);
|
||||
return t && (t.includes(klNoSpace) || klNoSpace.includes(t));
|
||||
}) || null;
|
||||
}
|
||||
if (targetColindex == null) return null;
|
||||
if (!targetCol) return null;
|
||||
const targetSub = targetCol.subTarget || null;
|
||||
const rows = [...body.querySelectorAll('.gridLine')];
|
||||
if (${row} >= rows.length) return null;
|
||||
const line = rows[${row}];
|
||||
const box = [...line.children].find(b => b.offsetWidth > 0 && b.getAttribute('colindex') === targetColindex);
|
||||
const box = cellForColumn(model, line, targetCol);
|
||||
if (!box) return null;
|
||||
box.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
let cell;
|
||||
|
||||
Reference in New Issue
Block a user