diff --git a/api/alembic/versions/0011_form3_log.py b/api/alembic/versions/0011_form3_log.py
new file mode 100644
index 0000000..175abc9
--- /dev/null
+++ b/api/alembic/versions/0011_form3_log.py
@@ -0,0 +1,84 @@
+import os
+import re
+import time
+
+from alembic import op
+import sqlalchemy as sa
+
+
+revision = "0011"
+down_revision = "0010"
+branch_labels = None
+depends_on = None
+
+
+_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
+
+
+def _find_dollar_tag(line: str) -> str | None:
+ m = _DOLLAR_TAG_RE.search(line.strip())
+ return m.group(0) if m else None
+
+
+def _split_statements(sql: str) -> list[str]:
+ statements: list[str] = []
+ current: list[str] = []
+ in_dollar = False
+ dollar_tag: str | None = None
+
+ for line in sql.split("\n"):
+ stripped = line.strip()
+ if stripped.startswith("--"):
+ continue
+
+ if not in_dollar:
+ tag = _find_dollar_tag(stripped)
+ if tag and tag.endswith("$") and tag.startswith("$"):
+ dollar_tag = tag
+ in_dollar = True
+ current.append(line)
+ continue
+
+ if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
+ after = stripped[len(dollar_tag):].strip()
+ if after == ";" or after == "":
+ in_dollar = False
+ dollar_tag = None
+ if after == ";":
+ current.append(line)
+ statements.append("\n".join(current))
+ current = []
+ continue
+
+ if not in_dollar and stripped.rstrip().endswith(";"):
+ current.append(line)
+ statements.append("\n".join(current))
+ current = []
+ continue
+
+ current.append(line)
+
+ remaining = "\n".join(current).strip()
+ if remaining:
+ statements.append(remaining)
+
+ return statements
+
+
+def upgrade() -> None:
+ ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0011_form3_log.sql")
+ with open(ddl_path) as f:
+ content = f.read()
+
+ statements = _split_statements(content)
+ for stmt in statements:
+ stripped = stmt.strip().rstrip(";").strip()
+ if not stripped:
+ continue
+ if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
+ continue
+ op.execute(stripped)
+
+
+def downgrade() -> None:
+ pass
diff --git a/api/alembic/versions/sql/0011_form3_log.sql b/api/alembic/versions/sql/0011_form3_log.sql
new file mode 100644
index 0000000..6a5a4ab
--- /dev/null
+++ b/api/alembic/versions/sql/0011_form3_log.sql
@@ -0,0 +1,172 @@
+CREATE OR REPLACE FUNCTION v3.add_project_year(p_project_id integer, p_year integer, p_emit_audit boolean DEFAULT true)
+ RETURNS TABLE(limit_report_id integer, current_expenses_report_id integer)
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+ v_lim INT;
+ v_cur INT;
+BEGIN
+ IF p_year IS NULL THEN
+ RAISE EXCEPTION 'p_year обязателен';
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN
+ RAISE EXCEPTION 'project id=% не существует', p_project_id;
+ END IF;
+ IF EXISTS (SELECT 1 FROM v3.rf_project_report
+ WHERE project_id = p_project_id AND year = p_year) THEN
+ RAISE EXCEPTION 'год % у проекта id=% уже заведён', p_year, p_project_id;
+ END IF;
+
+ INSERT INTO v3.rf_project_report (project_id, year, report_type)
+ VALUES (p_project_id, p_year, 'LIMIT') RETURNING id INTO v_lim;
+ INSERT INTO v3.rf_project_report (project_id, year, report_type)
+ VALUES (p_project_id, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur;
+
+ -- Snapshot RBAC-фаз (иначе default deny → отчёт read-only)
+ PERFORM v3.copy_template_to_report(v_lim);
+ PERFORM v3.copy_template_to_report(v_cur);
+
+ -- Фиксированная сетка статей на оба трека
+ PERFORM v3.seed_form3_report_lines(v_lim);
+ PERFORM v3.seed_form3_report_lines(v_cur);
+
+ IF p_emit_audit THEN
+ PERFORM v3.log_event(
+ 'PROJECT_YEAR_ADD', 'PROJECT',
+ jsonb_build_object(
+ 'project_id', p_project_id,
+ 'year', p_year,
+ 'limit_report_id', v_lim,
+ 'current_expenses_report_id', v_cur
+ )
+ );
+ END IF;
+
+ RETURN QUERY SELECT v_lim, v_cur;
+END;
+$function$
+;
+
+-- FKs
+ALTER TABLE v3.app_user ADD CONSTRAINT app_user_role_fk FOREIGN KEY (role_id) REFERENCES v3."role"(id);
+ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_app_user_fk FOREIGN KEY (user_id) REFERENCES v3.app_user(id);
+ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id);
+ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_budget_form_fk FOREIGN KEY (form_id) REFERENCES v3.budget_form(id);
+ALTER TABLE v3.booking ADD CONSTRAINT booking_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id);
+ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id);
+ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id);
+ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id);
+ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.budget_line_quarter ADD CONSTRAINT budget_line_quarter_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.ckk ADD CONSTRAINT ckk_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.collegial_approval ADD CONSTRAINT collegial_approval_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.contract_detail ADD CONSTRAINT contract_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.contract_summary ADD CONSTRAINT contract_summary_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.expense_item ADD CONSTRAINT expense_item_expense_item_fk FOREIGN KEY (parent_id) REFERENCES v3.expense_item(id);
+ALTER TABLE v3.expense_item_form_type ADD CONSTRAINT expense_item_form_type_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id);
+ALTER TABLE v3.fixed_asset_report ADD CONSTRAINT fixed_asset_report_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id);
+ALTER TABLE v3.fixed_asset_report ADD CONSTRAINT fixed_asset_report_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id);
+ALTER TABLE v3.form3_phase ADD CONSTRAINT form3_phase_rf_project_report_fk FOREIGN KEY (rf_project_report_id) REFERENCES v3.rf_project_report(id);
+ALTER TABLE v3.form_limit ADD CONSTRAINT form_limit_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id);
+ALTER TABLE v3.form_phase ADD CONSTRAINT form_phase_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id);
+ALTER TABLE v3.phase_template ADD CONSTRAINT phase_template_form_type_fk FOREIGN KEY (form_type) REFERENCES v3.form_type(code);
+ALTER TABLE v3."plan" ADD CONSTRAINT plan_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.project ADD CONSTRAINT project_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id);
+ALTER TABLE v3.rent_detail ADD CONSTRAINT rent_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.rent_detail ADD CONSTRAINT rent_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id);
+ALTER TABLE v3.reserve ADD CONSTRAINT reserve_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_project_fk FOREIGN KEY (project_id) REFERENCES v3.project(id);
+ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.rf_project_report_line ADD CONSTRAINT rf_project_report_line_rf_project_report_fk FOREIGN KEY (rf_project_report_id) REFERENCES v3.rf_project_report(id);
+ALTER TABLE v3.rf_project_report_line ADD CONSTRAINT rf_project_report_line_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id);
+ALTER TABLE v3.rf_project_report_quarter ADD CONSTRAINT rf_project_report_quarter_rf_project_report_line_fk FOREIGN KEY (rf_project_report_line_id) REFERENCES v3.rf_project_report_line(id);
+ALTER TABLE v3.security_detail ADD CONSTRAINT security_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.security_detail ADD CONSTRAINT security_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id);
+ALTER TABLE v3.sequestration ADD CONSTRAINT sequestration_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.user_org ADD CONSTRAINT user_org_app_user_fk FOREIGN KEY (user_id) REFERENCES v3.app_user(id);
+ALTER TABLE v3.user_org ADD CONSTRAINT user_org_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id);
+ALTER TABLE v3.utility_detail ADD CONSTRAINT utility_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id);
+ALTER TABLE v3.utility_detail ADD CONSTRAINT utility_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id);
+ALTER TABLE v3.vsp ADD CONSTRAINT vsp_org_unit_fk FOREIGN KEY (branch_id) REFERENCES v3.org_unit(id);
+ALTER TABLE v3.vsp ADD CONSTRAINT vsp_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id);
+ALTER TABLE v3.vsp ADD CONSTRAINT vsp_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id);
+
+
+-- DROP FUNCTION v3.add_project(varchar, int4, int4, varchar, int4, varchar, varchar, varchar, varchar, int4, numeric, varchar);
+
+CREATE OR REPLACE FUNCTION v3.add_project(p_name character varying, p_year integer, p_org_unit_id integer, p_level character varying DEFAULT 'project'::character varying, p_parent_id integer DEFAULT NULL::integer, p_project_type character varying DEFAULT NULL::character varying, p_vsp_format character varying DEFAULT NULL::character varying, p_placement_type character varying DEFAULT NULL::character varying, p_object_address character varying DEFAULT NULL::character varying, p_staff_count integer DEFAULT NULL::integer, p_total_area numeric DEFAULT NULL::numeric, p_system_code character varying DEFAULT NULL::character varying)
+ RETURNS TABLE(project_id integer, limit_report_id integer, current_expenses_report_id integer)
+ LANGUAGE plpgsql
+AS $function$
+ DECLARE
+ v_pid INT;
+ v_lim INT;
+ v_cur INT;
+ BEGIN
+ IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
+ RAISE EXCEPTION 'p_name обязателен';
+ END IF;
+ IF p_year IS NULL THEN
+ RAISE EXCEPTION 'p_year обязателен';
+ END IF;
+ IF p_org_unit_id IS NULL THEN
+ RAISE EXCEPTION 'p_org_unit_id обязателен';
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM v3.org_unit ou WHERE id = p_org_unit_id AND ou.is_active ) THEN
+ RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id;
+ END IF;
+ IF p_parent_id IS NOT NULL AND
+ NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_parent_id AND level = 'program') THEN
+ RAISE EXCEPTION 'parent project id=% не существует или не имеет level=program', p_parent_id;
+ END IF;
+
+ -- INSERT project (CHECK-ы name/project_type/vsp_format/placement_type
+ -- из migrate_form3_v1.sql применятся автоматически).
+ INSERT INTO v3.project (
+ name, level, parent_id, org_unit_id,
+ project_type, vsp_format, placement_type, object_address, staff_count, total_area,
+ system_code
+ ) VALUES (
+ p_name, p_level, p_parent_id, p_org_unit_id,
+ p_project_type, p_vsp_format, p_placement_type, p_object_address, p_staff_count, p_total_area,
+ p_system_code
+ ) RETURNING id INTO v_pid;
+
+ -- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей.
+ -- p_emit_audit=false — событие года покрыто PROJECT_CREATE ниже. Дальнейшие
+ -- годы добавляются отдельно через v3.add_project_year.
+ SELECT y.limit_report_id, y.current_expenses_report_id
+ INTO v_lim, v_cur
+ FROM v3.add_project_year(v_pid, p_year, false) y;
+
+ -- Аудит: PROJECT_CREATE — все заполненные поля + id отчётов
+ PERFORM v3.log_event(
+ 'PROJECT_CREATE', 'PROJECT',
+ jsonb_strip_nulls(jsonb_build_object(
+ 'project_id', v_pid,
+ 'name', p_name,
+ 'year', p_year,
+ 'level', p_level,
+ 'parent_id', p_parent_id,
+ 'org_unit_id', p_org_unit_id,
+ 'project_type', p_project_type,
+ 'vsp_format', p_vsp_format,
+ 'placement_type', p_placement_type,
+ 'object_address', p_object_address,
+ 'system_code', p_system_code,
+ 'staff_count', p_staff_count,
+ 'total_area', p_total_area,
+ 'limit_report_id', v_lim,
+ 'current_expenses_report_id', v_cur
+ ))
+ );
+
+ RETURN QUERY SELECT v_pid, v_lim, v_cur;
+ END;
+ $function$
+;
\ No newline at end of file
diff --git a/api/src/api/v1/vsp.py b/api/src/api/v1/vsp.py
index 4ab76da..0a2e0b3 100644
--- a/api/src/api/v1/vsp.py
+++ b/api/src/api/v1/vsp.py
@@ -46,6 +46,7 @@ async def get_vsp(
staff_count_min=staff_count_min,
staff_count_max=staff_count_max,
load_org_unit=True,
+ is_active=True,
with_count=True,
)
return BaseListResponse(
diff --git a/api/src/db/models/vsp.py b/api/src/db/models/vsp.py
index a907fa5..84aa9a9 100644
--- a/api/src/db/models/vsp.py
+++ b/api/src/db/models/vsp.py
@@ -33,6 +33,7 @@ class Vsp(Base):
total_area: Mapped[float | None] = mapped_column(Numeric)
closed_at: Mapped[date | None] = mapped_column(Date)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
+ #ToDo: а нужно ли нам и is_active и is_deleted?
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
system_code: Mapped[str | None] = mapped_column(String(100), nullable=True)
created_at: Mapped[datetime] = mapped_column(
diff --git a/api/src/repository/vsp_repository.py b/api/src/repository/vsp_repository.py
index a6b1b94..4858f04 100644
--- a/api/src/repository/vsp_repository.py
+++ b/api/src/repository/vsp_repository.py
@@ -14,8 +14,14 @@ class VSPRepository:
def __init__(self, db: AsyncSession):
self.db = db
- async def get(self, vsp_id: int, is_active: bool | None = None, load_org_unit: bool = False) -> Optional[Vsp]:
- queries = [Vsp.id == vsp_id, Vsp.is_deleted.is_(False)]
+ async def get(
+ self,
+ vsp_id: int,
+ is_active: bool | None = None,
+ load_org_unit: bool = False,
+ with_deleted: bool = False,
+ ) -> Optional[Vsp]:
+ queries = [Vsp.id == vsp_id, Vsp.is_deleted.is_(with_deleted)]
if is_active is not None:
queries.append(Vsp.is_active == is_active)
if load_org_unit:
diff --git a/api/src/services/export_service/export_service.py b/api/src/services/export_service/export_service.py
index bbf33cf..c198455 100644
--- a/api/src/services/export_service/export_service.py
+++ b/api/src/services/export_service/export_service.py
@@ -49,6 +49,7 @@ SHEET_NAMES = {
'AHR_SECURITY': 'АХР Безопасность',
'AHR_UTILITY': 'АХР Коммунальные услуги',
'SMETA': 'Смета',
+ 'STRUCTURE': 'Структура',
}
SKIP_SHEETS = ('OTCH9F',)
@@ -327,8 +328,8 @@ class ExportService:
("Форма ID", form.id),
("Тип формы", form.form_type_code),
("Год", form.year),
- ("Org Unit ID", form.org_unit_id),
- ("Org Unit", form.org_unit.title if form.org_unit else None),
+ ("ID ССП.РФ", form.org_unit_id),
+ ("ССП/РФ", form.org_unit.title if form.org_unit else None),
("Экспортировано", datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")),
]
for row_idx, (key, value) in enumerate(metadata_rows, start=1):
diff --git a/api/src/services/vsp_service.py b/api/src/services/vsp_service.py
index fe577e0..df6d30c 100644
--- a/api/src/services/vsp_service.py
+++ b/api/src/services/vsp_service.py
@@ -36,7 +36,11 @@ class VSPService:
self.user_repo = UserRepository(db)
async def get(self, vsp_id: int, user: AppUser, load_org_unit: bool = False) -> Optional[Vsp]:
- vsp = await self.vsp_repo.get(vsp_id=vsp_id, load_org_unit=load_org_unit)
+ vsp = await self.vsp_repo.get(
+ vsp_id=vsp_id,
+ load_org_unit=load_org_unit,
+ with_deleted=user.role_id == UserRoleEnum.ADMIN,
+ )
if not vsp:
return None
if user.role_id == UserRoleEnum.ADMIN:
@@ -173,7 +177,7 @@ class VSPService:
placement_type=placement_type,
staff_count_min=staff_count_min,
staff_count_max=staff_count_max,
- is_active=None,
+ is_active=True,
load_org_unit=True,
)
diff --git a/api/tests/integration/test_vsp.py b/api/tests/integration/test_vsp.py
index 3d926a6..cdbd118 100644
--- a/api/tests/integration/test_vsp.py
+++ b/api/tests/integration/test_vsp.py
@@ -58,8 +58,8 @@ def test_vsp_dropdown_with_filter(client, admin_tokens, auth_headers, params, co
assert len(payload["result"]) == count
-def test_vsp_get_by_id(client, admin_tokens, auth_headers):
- response = client.get("/api/v1/dict/info/1", headers=auth_headers(admin_tokens))
+def test_vsp_get_by_id(client, isp_tokens, auth_headers):
+ response = client.get("/api/v1/dict/info/1", headers=auth_headers(isp_tokens))
assert response.status_code == 200
payload = response.json()
assert "result" in payload
diff --git a/api/tests/sqls/fixture.sql b/api/tests/sqls/fixture.sql
index da9efeb..154004d 100644
--- a/api/tests/sqls/fixture.sql
+++ b/api/tests/sqls/fixture.sql
@@ -28,9 +28,9 @@ INSERT INTO v3.form_phase (budget_form_id,sheet,phase_code,"role",column_keys,op
(4,'AHR','test','DFIP','{{plan.q1}}','2029-05-05 03:00:00+05','2029-06-06 03:00:00+05');
INSERT INTO v3.vsp (id,branch_id,reg_number,address,format,opened_at,placement_type,staff_count,total_area,closed_at,is_active,updated_at,is_deleted,created_at,created_by,system_code,updated_by,vsp_type,notes,rent_contract_num,rent_end_date) VALUES
- (1,2,'Тестовый всп 1','Тестовая 12','укукк','2026-05-07','ывс',2026,230,'2026-06-16',false,'2026-06-18 15:46:38.611903',false,'2026-06-04 14:36:59.737727',1,'1233',91,'','','',NULL),
- (2,2,'Test12 -----','Тестовая 125','формат 15 ','2026-06-08',NULL,NULL,58,NULL,true,'2026-06-16 18:53:17.986651',false,'2026-06-09 09:04:33.29858',91,'123-1234',91,'субаренда','Работает ','12-45','2027-10-02'),
- (3,2,'3434','победы 12','формат','2026-06-01','субаренда', NULL,34,NULL,true,'2026-07-01 13:32:35.327097',false,'2026-06-29 15:53:50.236062',91,'3434',NULL,'2','примечание','567','2027-07-01');
+ (1,2,'Тестовый всп 1','Тестовая 12','укукк','2026-05-07','ывс',2026,230,'2026-06-16',false,'2026-06-18 15:46:38.611903',false,'2026-06-04 14:36:59.737727',1,'1233',1,'','','',NULL),
+ (2,2,'Test12 -----','Тестовая 125','формат 15 ','2026-06-08',NULL,NULL,58,NULL,true,'2026-06-16 18:53:17.986651',false,'2026-06-09 09:04:33.29858',1,'123-1234',1,'субаренда','Работает ','12-45','2027-10-02'),
+ (3,2,'3434','победы 12','формат','2026-06-01','субаренда', NULL,34,NULL,true,'2026-07-01 13:32:35.327097',false,'2026-06-29 15:53:50.236062',1,'3434',NULL,'2','примечание','567','2027-07-01');
SELECT setval('v3.vsp_id_seq', 3);
INSERT INTO v3.project ("id","name","level",parent_id,project_type,vsp_format,placement_type,object_address,staff_count,total_area,org_unit_id) VALUES
diff --git a/web/src/components/RealtimeTable/utils/cellUtils.js b/web/src/components/RealtimeTable/utils/cellUtils.js
index 304a8ab..6609f72 100644
--- a/web/src/components/RealtimeTable/utils/cellUtils.js
+++ b/web/src/components/RealtimeTable/utils/cellUtils.js
@@ -1,3 +1,5 @@
+import { getDataRowId } from "./rowUtils";
+
export function setNewValueToData(data, colId, rowId, value) {
let targetObject = data;
if (rowId && rowId.length) {
@@ -167,8 +169,9 @@ export function deleteRow(data, lineId) {
function findAndDelete(rows) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
+ const rowId = getDataRowId(row.data);
- if (row.data.line_id == lineId) {
+ if (rowId == lineId) {
rowDeleteSortOrder = row.sort_order;
rows.splice(i, 1);
i--;
@@ -179,7 +182,7 @@ export function deleteRow(data, lineId) {
}
if (row.subRows && Array.isArray(row.subRows) && row.subRows.length > 0) {
- const found = findAndDelete(row.subRows);
+ findAndDelete(row.subRows);
}
}
}
diff --git a/web/src/components/RealtimeTable/utils/rowUtils.js b/web/src/components/RealtimeTable/utils/rowUtils.js
index 5ba9a51..1f43dc3 100644
--- a/web/src/components/RealtimeTable/utils/rowUtils.js
+++ b/web/src/components/RealtimeTable/utils/rowUtils.js
@@ -1,5 +1,9 @@
+export function getDataRowId(data) {
+ return data?.id || data?.line_id;
+}
+
export function getRowId(row) {
- return row.original.data.id || row.original.data.line_id;
+ return getDataRowId(row.original.data);
}
export function isVspRow(row) {
diff --git a/web/src/components/Stages/AddStagesModal.jsx b/web/src/components/Stages/AddStagesModal.jsx
index 9bde595..511a9d0 100644
--- a/web/src/components/Stages/AddStagesModal.jsx
+++ b/web/src/components/Stages/AddStagesModal.jsx
@@ -22,7 +22,7 @@ import { CalendarInput } from './Calendar/CalendarInput';
import { Cancel } from '../common/icons/icons';
import { getStageStatus, getStatusColors } from './StagesTable/utils';
import { StageStatusChip } from './StageStatusChip';
-import { formatDateForApi } from './utils';
+import { formatDateForApi } from '../../utils/formatDate';
import { stageColumnsHiddenFromPicker } from './constants';
import CollapsibleTree from '../common/CollapsibleTree';
import { collectLeafColumns } from '../RealtimeTable/utils/columnUtils';
diff --git a/web/src/components/Stages/utils.js b/web/src/components/Stages/utils.js
index 4f0a434..3c496db 100644
--- a/web/src/components/Stages/utils.js
+++ b/web/src/components/Stages/utils.js
@@ -1,11 +1,3 @@
-export const formatDateForApi = (value) => {
- if (!value) return '';
- const d = new Date(value);
- const year = d.getFullYear();
- const month = String(d.getMonth() + 1).padStart(2, '0');
- const day = String(d.getDate()).padStart(2, '0');
- return `${year}-${month}-${day}`;
-};
export const isPastDate = (value) => {
if (!value) return false;
const date = new Date(value);
@@ -13,5 +5,4 @@ export const isPastDate = (value) => {
const today = new Date();
today.setHours(0, 0, 0, 0);
return date < today;
-};
-
+};
\ No newline at end of file
diff --git a/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx b/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx
index cc4a9ec..782002d 100644
--- a/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx
+++ b/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx
@@ -12,6 +12,7 @@ import { UsersApi } from '../../../api/users';
import AuditLogsFilters from './components/AuditLogsFilters';
import AuditLogsTable from './components/AuditLogsTable';
import { DEFAULT_ACTIONS } from './constants/auditLogs.jsx';
+import {formatDateForApi} from '../../../utils/formatDate.js'
const AuditLogsPage = () => {
const { user } = useAuth();
@@ -71,12 +72,13 @@ const AuditLogsPage = () => {
user_id: selectedUser?.id,
org_unit_id: selectedSsp?.id,
event: selectedEventType?.id,
- date_from: dateFrom ? dateFrom.toISOString().slice(0, 10) : undefined,
- date_to: dateTo ? dateTo.toISOString().slice(0, 10) : undefined,
+ // date_from: dateFrom ? new Date(dateFrom.getTime() - (dateFrom.getTimezoneOffset() * 60 * 1000)).toISOString().slice(0, 10): undefined,
+ // date_to: dateTo ? new Date(dateTo.getTime() - (dateTo.getTimezoneOffset() * 60 * 1000)).toISOString().slice(0, 10) : undefined,
+ date_from: dateFrom ? formatDateForApi(dateFrom) : undefined,
+ date_to: dateTo ? formatDateForApi(dateTo) : undefined,
task_id: taskId ? taskId : undefined,
form_id: formId ? formId : undefined,
};
-
const data = await AuditLogsApi.getAll(params);
setLogs(data.result ?? []);
setTotalCount(data.count ?? 0);
diff --git a/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsTable.jsx b/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsTable.jsx
index 31128c5..2ed78fd 100644
--- a/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsTable.jsx
+++ b/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsTable.jsx
@@ -129,7 +129,11 @@ const AuditLogsTable = ({
header: 'Контекст',
size: 160,
Cell: ({ row }) => (
- {`${ENTITY_RUSSIAN_NAMES[row.original.entity.toUpperCase()][1]} #${row.original.entity_id}`}
+
+ {ENTITY_RUSSIAN_NAMES[row.original.entity?.toUpperCase()]?.[1] != null
+ ? `${ENTITY_RUSSIAN_NAMES[row.original.entity?.toUpperCase()]?.[1]} #${row.original.entity_id}`
+ : '-'}
+
),
enableColumnActions: false,
enableSorting: false,
diff --git a/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx b/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx
index 195a5d7..d379289 100644
--- a/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx
+++ b/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx
@@ -13,6 +13,8 @@ export const DEFAULT_ACTIONS = {
VSP_CREATE: 'Создание ВСП',
VSP_UPDATE: 'Обновление ВСП',
DELETE_VSP: 'Удаление ВСП',
+ PROJECT_CREATE: 'Создание проекта',
+ PROJECT_DELETE: 'Удаление проекта',
};
export const ENTITY_RUSSIAN_NAMES = {
diff --git a/web/src/pages/ProjectsPage/ProjectsPage.jsx b/web/src/pages/ProjectsPage/ProjectsPage.jsx
index 4b126b2..f9e6df7 100644
--- a/web/src/pages/ProjectsPage/ProjectsPage.jsx
+++ b/web/src/pages/ProjectsPage/ProjectsPage.jsx
@@ -76,7 +76,7 @@ export default function ProjectsPage() {
useEffect(() => {
if (!user) return;
if (user.role_id == ROLES_NAME_ID.admin) {
- SspApi.getAll().then(data => {
+ SspApi.getAll({is_active: true}).then(data => {
if (data.success) {
setProjectData(prev => ({
...prev, 'ССП/РФ': {
diff --git a/web/src/utils/formatDate.js b/web/src/utils/formatDate.js
index 5e78b22..4edbce6 100644
--- a/web/src/utils/formatDate.js
+++ b/web/src/utils/formatDate.js
@@ -5,3 +5,22 @@ export const formatDate = (dateString) => {
const year = date.getFullYear();
return `${day}.${month}.${year}`;
};
+
+
+export const formatDateForApi = (value) => {
+ if (!value) return '';
+ const d = new Date(value);
+ const year = String(d.getFullYear()).padStart(4, '0');
+ const month = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+};
+export const isPastDate = (value) => {
+ if (!value) return false;
+ const date = new Date(value);
+ date.setHours(0, 0, 0, 0);
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ return date < today;
+};
+