AURORA-1883: справочник ВСП — просмотр закрытых ВСП (статус, период закрытия), подпись закрытых ВСП в формах (поверх #328) #332
@ -4,6 +4,7 @@ import { useCellLock } from '../../contexts/RealtimeContext';
|
||||
import { resolveCellTextColor } from '../../utils/cellTextColor';
|
||||
import { formatNumber } from '../../utils/numberFormat';
|
||||
import { isTextColumn } from '../../utils/moneyUtils';
|
||||
import { getVspLabel } from '../../utils/vspLabel';
|
||||
|
||||
const BASE_CELL_STYLES = {
|
||||
width: '100%',
|
||||
@ -164,6 +165,10 @@ const CellComponent = ({
|
||||
|
||||
const { rawValue, displayValue, isNumeric, isNegative } = useMemo(() => {
|
||||
const value = cell.getValue();
|
||||
// AURORA-1883: id ВСП — не сумма; подпись (рег. номер, «(закрыт)», «ВСП #id») без разрядности.
|
||||
if (isVspDropdown) {
|
||||
return { rawValue: value, displayValue: getVspLabel(value, vspOptions) ?? '', isNumeric: false, isNegative: false };
|
||||
}
|
||||
const isNum = value !== null && value !== undefined && value !== '' && !Number.isNaN(Number(value)) && !isTextColumn(column);
|
||||
let display = String(value || '');
|
||||
let negative = false;
|
||||
@ -173,13 +178,6 @@ const CellComponent = ({
|
||||
negative = formatted.isNegative;
|
||||
}
|
||||
|
||||
if (isVspDropdown && value != null) {
|
||||
const vsp = vspOptions?.find((v) => v.id === Number(value));
|
||||
if (vsp) {
|
||||
return { rawValue: value, displayValue: vsp.registration_number, isNumeric: false, isNegative: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { rawValue: value, displayValue: display, isNumeric: isNum, isNegative: negative };
|
||||
}, [cell, column.id, isVspDropdown, vspOptions, displayFactor]);
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { FormControl, MenuItem, Select } from '@mui/material';
|
||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRealtimeActions, useVspOptions } from '../../contexts/RealtimeContext';
|
||||
import { getVspLabel, selectableVspOptions } from '../../utils/vspLabel';
|
||||
|
||||
const EDITOR_STYLES = {
|
||||
position: 'absolute',
|
||||
@ -11,6 +12,12 @@ const EDITOR_STYLES = {
|
||||
const VspDropdownEditCell = memo(({ refCell, onChange, disabled, value, table, cell }) => {
|
||||
const { endEditing: contextEndEditing } = useRealtimeActions();
|
||||
const vspOptions = useVspOptions();
|
||||
// AURORA-1883: выбрать можно только действующий ВСП; закрытый текущий — подписан, но не выбирается.
|
||||
const selectableOptions = useMemo(() => selectableVspOptions(vspOptions), [vspOptions]);
|
||||
const currentIsSelectable = useMemo(
|
||||
() => value == null || value === '' || selectableOptions.some((option) => Number(option.id) === Number(value)),
|
||||
[selectableOptions, value],
|
||||
);
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState(() => value ?? '');
|
||||
const [position, setPosition] = useState();
|
||||
@ -140,7 +147,12 @@ const VspDropdownEditCell = memo(({ refCell, onChange, disabled, value, table, c
|
||||
<MenuItem value='' disabled>
|
||||
Выберите ВСП
|
||||
</MenuItem>
|
||||
{vspOptions.map((option) => (
|
||||
{!currentIsSelectable && (
|
||||
<MenuItem value={value} disabled sx={{ display: 'none' }}>
|
||||
{getVspLabel(value, vspOptions)}
|
||||
</MenuItem>
|
||||
)}
|
||||
{selectableOptions.map((option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.registration_number}
|
||||
</MenuItem>
|
||||
|
||||
@ -8,6 +8,7 @@ import { Cancel } from '../../common/icons/icons';
|
||||
|
||||
const SelectVspModalComponent = ({ isOpen, onClose, onSelect, formId, title = 'Выбор ВСП', isSaving = false }) => {
|
||||
const [selectedValue, setSelectedValue] = useState('');
|
||||
// AURORA-1883: модалка сама грузит dropdown без include_inactive — в выборе только действующие ВСП.
|
||||
const [vspOptions, setVspOption] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -308,7 +308,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
if (!hasVspDropdown(columnsConfig.columns)) return;
|
||||
let isCancelled = false;
|
||||
|
||||
DictVspApi.getDropdownVsp({ form_id: formId }).then((data) => {
|
||||
// AURORA-1883: с закрытыми — для подписей строк на закрытых ВСП; выбор ограничен в VspDropdownEditCell.
|
||||
DictVspApi.getDropdownVsp({ form_id: formId, include_inactive: true }).then((data) => {
|
||||
if (!isCancelled && data.success) {
|
||||
setVspOptions(data.result);
|
||||
}
|
||||
|
||||
17
web/src/components/RealtimeTable/utils/vspLabel.js
Normal file
17
web/src/components/RealtimeTable/utils/vspLabel.js
Normal file
@ -0,0 +1,17 @@
|
||||
// AURORA-1883: подпись ВСП в ячейке листа формы (колонки editType 'vsp_dropdown').
|
||||
// vspOptions — ответ /dict/info/dropdown?include_inactive=true: действующие и закрытые ВСП ССП формы.
|
||||
|
||||
const isEmptyValue = (value) => value === null || value === undefined || value === '';
|
||||
|
||||
/** Рег. номер ВСП; у закрытого — «<рег. номер> (закрыт)»; не найден — «ВСП #<id>»; пусто — null. */
|
||||
export function getVspLabel(value, vspOptions) {
|
||||
if (isEmptyValue(value)) return null;
|
||||
const vsp = (vspOptions || []).find((option) => Number(option.id) === Number(value));
|
||||
if (!vsp) return `ВСП #${value}`;
|
||||
return vsp.is_active === false ? `${vsp.registration_number} (закрыт)` : vsp.registration_number;
|
||||
}
|
||||
|
||||
/** ВСП, которые можно выбрать для строки: только действующие. */
|
||||
export function selectableVspOptions(vspOptions) {
|
||||
return (vspOptions || []).filter((option) => option.is_active !== false);
|
||||
}
|
||||
40
web/src/components/RealtimeTable/utils/vspLabel.test.js
Normal file
40
web/src/components/RealtimeTable/utils/vspLabel.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { getVspLabel, selectableVspOptions } from './vspLabel.js';
|
||||
|
||||
// AURORA-1883: в листах форм закрытый ВСП подписан рег. номером с пометкой, а не числом.
|
||||
const opts = [
|
||||
{ id: 1, registration_number: '1234/567', is_active: true },
|
||||
{ id: 2, registration_number: '7777/001', is_active: false },
|
||||
];
|
||||
|
||||
test('active VSP is labelled by its registration number', () => {
|
||||
assert.equal(getVspLabel(1, opts), '1234/567');
|
||||
});
|
||||
|
||||
test('closed VSP is labelled with the "(закрыт)" suffix', () => {
|
||||
assert.equal(getVspLabel(2, opts), '7777/001 (закрыт)');
|
||||
});
|
||||
|
||||
test('string id is matched like a number', () => {
|
||||
assert.equal(getVspLabel('1', opts), '1234/567');
|
||||
});
|
||||
|
||||
test('unknown id is labelled "ВСП #<id>", never formatted as a number', () => {
|
||||
assert.equal(getVspLabel(99, opts), 'ВСП #99');
|
||||
assert.equal(getVspLabel(5, undefined), 'ВСП #5');
|
||||
});
|
||||
|
||||
test('empty value has no label', () => {
|
||||
assert.equal(getVspLabel(null, opts), null);
|
||||
assert.equal(getVspLabel(undefined, opts), null);
|
||||
assert.equal(getVspLabel('', opts), null);
|
||||
});
|
||||
|
||||
test('only active VSPs are selectable', () => {
|
||||
assert.deepEqual(
|
||||
selectableVspOptions(opts).map((v) => v.id),
|
||||
[1],
|
||||
);
|
||||
assert.deepEqual(selectableVspOptions(undefined), []);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user