mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-13 17:34:57 +03:00
c930b4b04d
SpreadsheetDocument (отчёты, печатные формы) — другой домен, чем form-grid (табличные части документов, списки). Раньше лежал внутри table/, что было обманчиво. engine/table/spreadsheet.mjs → engine/spreadsheet/spreadsheet.mjs Структура engine/: core/ плумбинг движка (state, wait, errors, session, click, ...) forms/ работа с формами (fill, close, select-value, state) nav/ навигация table/ form-grid (grid, row-fill, filter, grid-toggle) spreadsheet/ SpreadsheetDocument recording/ запись + overlays В будущем при росте spreadsheet можно распилить — engine/spreadsheet/cells.mjs, engine/spreadsheet/scroll.mjs и т.д. без переименований. 11-report регресс зелёный. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
4.9 KiB
JavaScript
104 lines
4.9 KiB
JavaScript
// web-test table/grid v1.17 — Form-grid operations: read table rows, fill rows, delete rows.
|
|
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
//
|
|
// "Grid" в терминах 1С — таблица на форме (.gridLine/.gridBody/.grid в DOM):
|
|
// табличные части документов, формы списков, ТЧ настроек и т.п.
|
|
// Отдельно от SpreadsheetDocument (engine/spreadsheet/spreadsheet.mjs).
|
|
|
|
import { page, ensureConnected } from '../core/state.mjs';
|
|
import { detectFormScript, readTableScript, resolveGridScript } from '../../dom.mjs';
|
|
import { dismissPendingErrors } from '../core/errors.mjs';
|
|
import { waitForStable } from '../core/wait.mjs';
|
|
import { clickElement } from '../core/click.mjs';
|
|
import { getFormState } from '../forms/state.mjs';
|
|
|
|
/** Read structured table data with pagination. Returns columns, rows, total count. */
|
|
export async function readTable({ maxRows = 20, offset = 0, table } = {}) {
|
|
ensureConnected();
|
|
const formNum = await page.evaluate(detectFormScript());
|
|
if (formNum === null) throw new Error('readTable: no form found');
|
|
let gridSelector;
|
|
if (table) {
|
|
const resolved = await page.evaluate(resolveGridScript(formNum, table));
|
|
if (resolved.error) throw new Error(`readTable: ${resolved.message || resolved.error}. Available: ${resolved.available?.map(a => a.name).join(', ') || 'none'}`);
|
|
gridSelector = resolved.gridSelector;
|
|
}
|
|
return await page.evaluate(readTableScript(formNum, { maxRows, offset, gridSelector }));
|
|
}
|
|
|
|
/**
|
|
* Delete a row from the current table part.
|
|
* Single click to select the row, then Delete key to remove it.
|
|
*
|
|
* @param {number} row - 0-based row index to delete
|
|
* @param {Object} [options]
|
|
* @param {string} [options.tab] - Switch to this form tab before operating
|
|
* @returns {{ deleted, rowsBefore, rowsAfter, form }}
|
|
*/
|
|
export async function deleteTableRow(row, { tab, table } = {}) {
|
|
ensureConnected();
|
|
await dismissPendingErrors();
|
|
const formNum = await page.evaluate(detectFormScript());
|
|
if (formNum === null) throw new Error('deleteTableRow: no form found');
|
|
|
|
// Pre-resolve grid when table is specified
|
|
let gridSelector;
|
|
if (table) {
|
|
const resolved = await page.evaluate(resolveGridScript(formNum, table));
|
|
if (resolved.error) throw new Error(`deleteTableRow: table "${table}" not found. Available: ${resolved.available?.map(a => a.name).join(', ') || 'none'}`);
|
|
gridSelector = resolved.gridSelector;
|
|
}
|
|
|
|
// 1. Switch tab if requested
|
|
if (tab) {
|
|
await clickElement(tab);
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// 2. Find the target row and click to select it
|
|
const cellCoords = await page.evaluate(`(() => {
|
|
const grid = ${gridSelector
|
|
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
|
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
|
if (!grid) return { error: 'no_grid' };
|
|
const body = grid.querySelector('.gridBody');
|
|
if (!body) return { error: 'no_grid_body' };
|
|
const rows = [...body.querySelectorAll('.gridLine')];
|
|
if (${row} >= rows.length) return { error: 'row_out_of_range', total: rows.length };
|
|
const line = rows[${row}];
|
|
// Use visible gridBox containers (not gridBoxText) to avoid clicking checkboxes
|
|
const boxes = [...line.children].filter(b => b.offsetWidth > 0 && !b.classList.contains('gridBoxComp'));
|
|
// Skip first column (row number / checkbox) — pick second visible box
|
|
const box = boxes.length > 1 ? boxes[1] : boxes[0];
|
|
if (!box) return { error: 'no_cell' };
|
|
const cell = box.querySelector('.gridBoxText') || box;
|
|
const r = cell.getBoundingClientRect();
|
|
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2), total: rows.length };
|
|
})()`);
|
|
|
|
if (cellCoords.error) throw new Error(`deleteTableRow: ${cellCoords.error}${cellCoords.total ? ' (total rows: ' + cellCoords.total + ')' : ''}`);
|
|
|
|
const rowsBefore = cellCoords.total;
|
|
|
|
// Single click to select the row
|
|
await page.mouse.click(cellCoords.x, cellCoords.y);
|
|
await page.waitForTimeout(300);
|
|
|
|
// 3. Press Delete to remove the row
|
|
await page.keyboard.press('Delete');
|
|
await waitForStable();
|
|
|
|
// 4. Count rows after deletion
|
|
const rowsAfter = await page.evaluate(`(() => {
|
|
const grid = ${gridSelector
|
|
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
|
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
|
if (!grid) return 0;
|
|
const body = grid.querySelector('.gridBody');
|
|
return body ? body.querySelectorAll('.gridLine').length : 0;
|
|
})()`);
|
|
|
|
const formData = await getFormState();
|
|
return { deleted: row, rowsBefore, rowsAfter, form: formData };
|
|
}
|