form3-expense: добавил новые статьи в форму 3, возможность удаления задач
This commit is contained in:
parent
94c18b4ca7
commit
d6da54ecf4
15
api/alembic/versions/0038_form3_expense_item_links.py
Normal file
15
api/alembic/versions/0038_form3_expense_item_links.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
|
||||||
|
revision = "0038"
|
||||||
|
down_revision = "0037"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
run_sql_file_migration("0038_form3_expense_item_links.sql")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
15
api/alembic/versions/0039_budget_form_delete_workflow.py
Normal file
15
api/alembic/versions/0039_budget_form_delete_workflow.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
|
||||||
|
revision = "0039"
|
||||||
|
down_revision = "0038"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
run_sql_file_migration("0039_budget_form_delete_workflow.sql")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
22
api/alembic/versions/sql/0038_form3_expense_item_links.sql
Normal file
22
api/alembic/versions/sql/0038_form3_expense_item_links.sql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
insert into v3.expense_item_form_type (expense_item_id, form_type_code)
|
||||||
|
values
|
||||||
|
(84, 'FORM_3'),
|
||||||
|
(85, 'FORM_3'),
|
||||||
|
(86, 'FORM_3'),
|
||||||
|
(87, 'FORM_3'),
|
||||||
|
(88, 'FORM_3'),
|
||||||
|
(89, 'FORM_3'),
|
||||||
|
(90, 'FORM_3'),
|
||||||
|
(91, 'FORM_3'),
|
||||||
|
(92, 'FORM_3'),
|
||||||
|
(93, 'FORM_3'),
|
||||||
|
(94, 'FORM_3'),
|
||||||
|
(95, 'FORM_3'),
|
||||||
|
(97, 'FORM_3'),
|
||||||
|
(98, 'FORM_3'),
|
||||||
|
(99, 'FORM_3'),
|
||||||
|
(100, 'FORM_3'),
|
||||||
|
(101, 'FORM_3'),
|
||||||
|
(83, 'FORM_3'),
|
||||||
|
(96, 'FORM_3')
|
||||||
|
on conflict (expense_item_id, form_type_code) do nothing;
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
ALTER TABLE v3.budget_form
|
||||||
|
ADD COLUMN IF NOT EXISTS to_delete boolean NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS deleted_by integer NULL REFERENCES v3.app_user(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS deleted_at timestamp with time zone NULL;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_v3_budget_form_to_delete
|
||||||
|
ON v3.budget_form (to_delete);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION v3.hard_delete_budget_form(p_form_id integer)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM v3.budget_form WHERE id = p_form_id) THEN
|
||||||
|
RAISE EXCEPTION 'budget_form id=% не существует', p_form_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM v3.booking
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.sequestration
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.budget_line_quarter
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.rent_detail
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.utility_detail
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.security_detail
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.contract_detail
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.contract_summary
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.plan
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.reserve
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.allocation
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.ckk
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.collegial_approval
|
||||||
|
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
||||||
|
|
||||||
|
DELETE FROM v3.fixed_asset_report
|
||||||
|
WHERE budget_form_id = p_form_id;
|
||||||
|
|
||||||
|
DELETE FROM v3.form_limit
|
||||||
|
WHERE budget_form_id = p_form_id;
|
||||||
|
|
||||||
|
DELETE FROM v3.form_phase
|
||||||
|
WHERE budget_form_id = p_form_id;
|
||||||
|
|
||||||
|
DELETE FROM v3.budget_line
|
||||||
|
WHERE budget_form_id = p_form_id;
|
||||||
|
|
||||||
|
DELETE FROM v3.form4_project
|
||||||
|
WHERE form_id = p_form_id;
|
||||||
|
|
||||||
|
DELETE FROM v3.budget_form
|
||||||
|
WHERE id = p_form_id;
|
||||||
|
END;
|
||||||
|
$function$;
|
||||||
@ -8,8 +8,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from src.services.org_unit_service import OrgUnitService
|
from src.services.org_unit_service import OrgUnitService
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
|
from src.db.models.role import UserRoleEnum
|
||||||
from src.services.sheet_service import SheetService
|
from src.services.sheet_service import SheetService
|
||||||
from src.domain.schemas import AddLineSchema, BaseListResponse, BaseSingleResponse, BudgetFormResponse, CellPatch, CellsPatch, DirectionSchemaEnum, FormCreateSchema, SheetFormTypeResponse, SheetResponse
|
from src.domain.schemas import AddLineSchema, BaseListResponse, BaseSingleResponse, ResponseBase, BudgetFormResponse, CellPatch, CellsPatch, DirectionSchemaEnum, FormCreateSchema, SheetFormTypeResponse, SheetResponse
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
from src.services.budget_form_service import BudgetFormService
|
from src.services.budget_form_service import BudgetFormService
|
||||||
from src.api.v1.deps import get_current_active_user_with_set_db, require_admin
|
from src.api.v1.deps import get_current_active_user_with_set_db, require_admin
|
||||||
@ -80,6 +81,7 @@ async def get_forms(
|
|||||||
org_units: str | None = None,
|
org_units: str | None = None,
|
||||||
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
|
delete_status: Literal["active", "marked", "all"] | None = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(get_current_active_user_with_set_db),
|
current_user: AppUser = Depends(get_current_active_user_with_set_db),
|
||||||
) -> BaseListResponse[BudgetFormResponse]:
|
) -> BaseListResponse[BudgetFormResponse]:
|
||||||
@ -95,6 +97,7 @@ async def get_forms(
|
|||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
load_org=True,
|
load_org=True,
|
||||||
|
delete_status=delete_status,
|
||||||
)
|
)
|
||||||
return BaseListResponse(
|
return BaseListResponse(
|
||||||
result = [
|
result = [
|
||||||
@ -112,12 +115,15 @@ async def get_form(
|
|||||||
) -> BaseSingleResponse[BudgetFormResponse]:
|
) -> BaseSingleResponse[BudgetFormResponse]:
|
||||||
bf_service = BudgetFormService(db)
|
bf_service = BudgetFormService(db)
|
||||||
form = await bf_service.get(
|
form = await bf_service.get(
|
||||||
user=current_user,
|
user=current_user,
|
||||||
budget_form_id=form_id,
|
budget_form_id=form_id,
|
||||||
load_org=True,
|
load_org=True,
|
||||||
|
include_to_delete=current_user.role_id == UserRoleEnum.ADMIN,
|
||||||
)
|
)
|
||||||
|
if not form:
|
||||||
|
raise HTTPException(404, f"Форма {form_id} не найдена")
|
||||||
return BaseSingleResponse(
|
return BaseSingleResponse(
|
||||||
result = BudgetFormResponse.model_validate(form),
|
result=BudgetFormResponse.model_validate(form),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -464,6 +470,7 @@ async def add_form(
|
|||||||
form_type_code=form.form_type_code,
|
form_type_code=form.form_type_code,
|
||||||
year=form.year,
|
year=form.year,
|
||||||
org_unit_ids=org_unit_ids,
|
org_unit_ids=org_unit_ids,
|
||||||
|
user=current_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
@ -485,3 +492,36 @@ async def add_form(
|
|||||||
result=[BudgetFormResponse.model_validate(new_form) for new_form in new_forms],
|
result=[BudgetFormResponse.model_validate(new_form) for new_form in new_forms],
|
||||||
count=len(new_forms),
|
count=len(new_forms),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{form_id}/mark-delete")
|
||||||
|
async def mark_form_for_deletion(
|
||||||
|
form_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: AppUser = Depends(require_admin),
|
||||||
|
) -> BaseSingleResponse[BudgetFormResponse]:
|
||||||
|
bf_service = BudgetFormService(db)
|
||||||
|
form = await bf_service.mark_for_deletion(budget_form_id=form_id, user=current_user)
|
||||||
|
return BaseSingleResponse(result=BudgetFormResponse.model_validate(form))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{form_id}/restore")
|
||||||
|
async def restore_form(
|
||||||
|
form_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: AppUser = Depends(require_admin),
|
||||||
|
) -> BaseSingleResponse[BudgetFormResponse]:
|
||||||
|
bf_service = BudgetFormService(db)
|
||||||
|
form = await bf_service.restore(budget_form_id=form_id, user=current_user)
|
||||||
|
return BaseSingleResponse(result=BudgetFormResponse.model_validate(form))
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{form_id}/hard")
|
||||||
|
async def hard_delete_form(
|
||||||
|
form_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: AppUser = Depends(require_admin),
|
||||||
|
) -> ResponseBase:
|
||||||
|
bf_service = BudgetFormService(db)
|
||||||
|
await bf_service.hard_delete(budget_form_id=form_id, user=current_user)
|
||||||
|
return ResponseBase(success=True, message="Задача полностью удалена")
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, func
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
@ -34,6 +34,9 @@ class BudgetForm(Base):
|
|||||||
org_unit_id: Mapped[int | None] = mapped_column(
|
org_unit_id: Mapped[int | None] = mapped_column(
|
||||||
Integer, ForeignKey("v3.org_unit.id", name="fk_v3_budget_form_org_unit")
|
Integer, ForeignKey("v3.org_unit.id", name="fk_v3_budget_form_org_unit")
|
||||||
)
|
)
|
||||||
|
to_delete: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||||
|
deleted_by: Mapped[int | None] = mapped_column(Integer, ForeignKey("v3.app_user.id"))
|
||||||
|
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
|
|
||||||
form_type: Mapped["FormType"] = relationship(
|
form_type: Mapped["FormType"] = relationship(
|
||||||
"FormType", #back_populates="budget_forms"
|
"FormType", #back_populates="budget_forms"
|
||||||
|
|||||||
@ -330,6 +330,9 @@ class BudgetFormResponse(BaseModel):
|
|||||||
form_type_code: FormTypeSchemaEnum
|
form_type_code: FormTypeSchemaEnum
|
||||||
year: int
|
year: int
|
||||||
org_unit_id: int
|
org_unit_id: int
|
||||||
|
to_delete: bool = False
|
||||||
|
deleted_by: int | None = None
|
||||||
|
deleted_at: datetime | None = None
|
||||||
org_unit: OrgUnitSchema | None = None
|
org_unit: OrgUnitSchema | None = None
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import func, select, text
|
from sqlalchemy import func, select, text, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
@ -6,20 +6,20 @@ from src.db.models.budget_form import BudgetForm
|
|||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BudgetFormRepository:
|
class BudgetFormRepository:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_list(
|
async def get_list(
|
||||||
self,
|
self,
|
||||||
offset: int | None = None,
|
offset: int | None = None,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
org_unit: int | list[int] | None = None,
|
org_unit: int | list[int] | None = None,
|
||||||
with_count: bool = False,
|
with_count: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
form_type: FormTypeEnum | str | None = None,
|
form_type: FormTypeEnum | str | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
|
delete_status: str = "active",
|
||||||
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
||||||
if with_count:
|
if with_count:
|
||||||
query = select(func.count().over().label("total_count"), BudgetForm)
|
query = select(func.count().over().label("total_count"), BudgetForm)
|
||||||
@ -38,6 +38,11 @@ class BudgetFormRepository:
|
|||||||
if year is not None:
|
if year is not None:
|
||||||
where.append(BudgetForm.year == year)
|
where.append(BudgetForm.year == year)
|
||||||
|
|
||||||
|
if delete_status == "marked":
|
||||||
|
where.append(BudgetForm.to_delete.is_(True))
|
||||||
|
elif delete_status != "all":
|
||||||
|
where.append(BudgetForm.to_delete.is_(False))
|
||||||
|
|
||||||
query = query.where(*where)
|
query = query.where(*where)
|
||||||
if load_org:
|
if load_org:
|
||||||
query = query.options(
|
query = query.options(
|
||||||
@ -63,19 +68,20 @@ class BudgetFormRepository:
|
|||||||
return (await self.db.execute(count_query)).scalar(), []
|
return (await self.db.execute(count_query)).scalar(), []
|
||||||
else:
|
else:
|
||||||
return (await self.db.execute(query)).scalars().all()
|
return (await self.db.execute(query)).scalars().all()
|
||||||
|
|
||||||
async def get(
|
async def get(
|
||||||
self,
|
self,
|
||||||
budget_form_id: int | None = None,
|
budget_form_id: int | None = None,
|
||||||
org_unit: int | list[int] | None = None,
|
org_unit: int | list[int] | None = None,
|
||||||
form_type_code: FormTypeEnum | str | None = None,
|
form_type_code: FormTypeEnum | str | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
load_form_type: bool = False,
|
load_form_type: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
|
include_to_delete: bool = False,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
assert budget_form_id or all((form_type_code, year, org_unit))
|
assert budget_form_id or all((form_type_code, year, org_unit))
|
||||||
query = select(BudgetForm)
|
query = select(BudgetForm)
|
||||||
|
|
||||||
where = []
|
where = []
|
||||||
if budget_form_id is not None:
|
if budget_form_id is not None:
|
||||||
where.append(BudgetForm.id == budget_form_id)
|
where.append(BudgetForm.id == budget_form_id)
|
||||||
@ -85,16 +91,19 @@ class BudgetFormRepository:
|
|||||||
where.append(BudgetForm.org_unit_id == org_unit)
|
where.append(BudgetForm.org_unit_id == org_unit)
|
||||||
else:
|
else:
|
||||||
where.append(BudgetForm.org_unit_id.in_(org_unit))
|
where.append(BudgetForm.org_unit_id.in_(org_unit))
|
||||||
|
|
||||||
if form_type_code is not None:
|
if form_type_code is not None:
|
||||||
if isinstance(form_type_code, FormTypeEnum):
|
if isinstance(form_type_code, FormTypeEnum):
|
||||||
where.append(BudgetForm.form_type_code == form_type_code.value)
|
where.append(BudgetForm.form_type_code == form_type_code.value)
|
||||||
else:
|
else:
|
||||||
where.append(BudgetForm.form_type_code == form_type_code)
|
where.append(BudgetForm.form_type_code == form_type_code)
|
||||||
|
|
||||||
if year is not None:
|
if year is not None:
|
||||||
where.append(BudgetForm.year == year)
|
where.append(BudgetForm.year == year)
|
||||||
|
|
||||||
|
if not include_to_delete:
|
||||||
|
where.append(BudgetForm.to_delete.is_(False))
|
||||||
|
|
||||||
query = query.where(*where)
|
query = query.where(*where)
|
||||||
if load_org:
|
if load_org:
|
||||||
query = query.options(
|
query = query.options(
|
||||||
@ -106,12 +115,13 @@ class BudgetFormRepository:
|
|||||||
joinedload(BudgetForm.form_type)
|
joinedload(BudgetForm.form_type)
|
||||||
)
|
)
|
||||||
return (await self.db.execute(query)).scalars().first()
|
return (await self.db.execute(query)).scalars().first()
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
|
user_id: int | None = None,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
form_id = (
|
form_id = (
|
||||||
await self.db.execute(
|
await self.db.execute(
|
||||||
@ -134,6 +144,42 @@ class BudgetFormRepository:
|
|||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if form_id is None:
|
if form_id is None:
|
||||||
return None
|
return None
|
||||||
|
if user_id is not None:
|
||||||
|
await self.db.execute(
|
||||||
|
update(BudgetForm)
|
||||||
|
.where(BudgetForm.id == form_id)
|
||||||
|
.values(created_by=user_id, updated_by=user_id)
|
||||||
|
)
|
||||||
return await self.get(
|
return await self.get(
|
||||||
budget_form_id=form_id,
|
budget_form_id=form_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def mark_for_deletion(self, budget_form_id: int, user_id: int) -> None:
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
UPDATE v3.budget_form
|
||||||
|
SET to_delete = true, deleted_by = :user_id, deleted_at = now(), updated_by = :user_id, updated_at = now()
|
||||||
|
WHERE id = :form_id
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"form_id": budget_form_id, "user_id": user_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def restore(self, budget_form_id: int, user_id: int) -> None:
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
UPDATE v3.budget_form
|
||||||
|
SET to_delete = false, deleted_by = NULL, deleted_at = NULL, updated_by = :user_id, updated_at = now()
|
||||||
|
WHERE id = :form_id
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"form_id": budget_form_id, "user_id": user_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def hard_delete(self, budget_form_id: int) -> None:
|
||||||
|
await self.db.execute(
|
||||||
|
text("SELECT v3.hard_delete_budget_form(:form_id)"),
|
||||||
|
{"form_id": budget_form_id},
|
||||||
|
)
|
||||||
|
|||||||
@ -4,12 +4,15 @@ from typing import Literal
|
|||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.repository.user_repository import UserRepository
|
from src.core.errors import AccessDeniedException, ValidationException
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
|
from src.db.models.audit_log import AuditLog
|
||||||
from src.db.models.budget_form import BudgetForm
|
from src.db.models.budget_form import BudgetForm
|
||||||
from src.db.models.role import UserRoleEnum
|
from src.db.models.role import UserRoleEnum
|
||||||
from src.repository.budget_form_repository import BudgetFormRepository
|
from src.repository.budget_form_repository import BudgetFormRepository
|
||||||
|
from src.repository.user_repository import UserRepository
|
||||||
|
|
||||||
|
DeleteStatus = Literal["active", "marked", "all"]
|
||||||
|
|
||||||
|
|
||||||
class BudgetFormService:
|
class BudgetFormService:
|
||||||
@ -28,8 +31,15 @@ class BudgetFormService:
|
|||||||
org_unit: list[int] | None = None,
|
org_unit: list[int] | None = None,
|
||||||
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
|
delete_status: DeleteStatus | None = None,
|
||||||
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
||||||
if user.role_id == UserRoleEnum.ADMIN:
|
if delete_status is not None and delete_status not in {"active", "marked", "all"}:
|
||||||
|
raise ValidationException("delete_status должен быть active, marked или all")
|
||||||
|
|
||||||
|
is_admin = user.role_id == UserRoleEnum.ADMIN
|
||||||
|
effective_delete_status = (delete_status or "all") if is_admin else "active"
|
||||||
|
|
||||||
|
if is_admin:
|
||||||
return await self.bf_repo.get_list(
|
return await self.bf_repo.get_list(
|
||||||
offset=offset,
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
@ -38,6 +48,7 @@ class BudgetFormService:
|
|||||||
org_unit=org_unit,
|
org_unit=org_unit,
|
||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
|
delete_status=effective_delete_status,
|
||||||
)
|
)
|
||||||
user = await self.user_repo.get(
|
user = await self.user_repo.get(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@ -58,6 +69,7 @@ class BudgetFormService:
|
|||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
|
delete_status=effective_delete_status,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get(
|
async def get(
|
||||||
@ -66,12 +78,15 @@ class BudgetFormService:
|
|||||||
user: AppUser,
|
user: AppUser,
|
||||||
load_form_type: bool = False,
|
load_form_type: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
|
include_to_delete: bool = False,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
if user.role_id == UserRoleEnum.ADMIN:
|
is_admin = user.role_id == UserRoleEnum.ADMIN
|
||||||
|
if is_admin:
|
||||||
return await self.bf_repo.get(
|
return await self.bf_repo.get(
|
||||||
budget_form_id=budget_form_id,
|
budget_form_id=budget_form_id,
|
||||||
load_form_type=load_form_type,
|
load_form_type=load_form_type,
|
||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
|
include_to_delete=True,
|
||||||
)
|
)
|
||||||
user = await self.user_repo.get(
|
user = await self.user_repo.get(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@ -82,30 +97,35 @@ class BudgetFormService:
|
|||||||
org_unit=[ou.id for ou in user.org_units],
|
org_unit=[ou.id for ou in user.org_units],
|
||||||
load_form_type=load_form_type,
|
load_form_type=load_form_type,
|
||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
|
include_to_delete=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
|
user: AppUser | None = None,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await self.bf_repo.create(
|
return await self.bf_repo.create(
|
||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
|
user_id=user.id if user else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create_if_not_exists(
|
async def create_if_not_exists(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
|
user: AppUser | None = None,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
existing_bf = await self.bf_repo.get(
|
existing_bf = await self.bf_repo.get(
|
||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit=org_unit_id,
|
org_unit=org_unit_id,
|
||||||
|
include_to_delete=True,
|
||||||
)
|
)
|
||||||
if existing_bf:
|
if existing_bf:
|
||||||
return existing_bf
|
return existing_bf
|
||||||
@ -113,13 +133,15 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
|
user_id=user.id if user else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def bulk_create(
|
async def bulk_create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_ids: list[int],
|
org_unit_ids: list[int],
|
||||||
|
user: AppUser | None = None,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await asyncio.gather(
|
return await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
@ -127,16 +149,18 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
|
user=user,
|
||||||
)
|
)
|
||||||
for org_unit_id in org_unit_ids
|
for org_unit_id in org_unit_ids
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def bulk_create_if_not_exists(
|
async def bulk_create_if_not_exists(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_ids: list[int],
|
org_unit_ids: list[int],
|
||||||
|
user: AppUser | None = None,
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await asyncio.gather(
|
return await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
@ -144,7 +168,118 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
|
user=user,
|
||||||
)
|
)
|
||||||
for org_unit_id in org_unit_ids
|
for org_unit_id in org_unit_ids
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def mark_for_deletion(self, budget_form_id: int, user: AppUser) -> BudgetForm:
|
||||||
|
self._ensure_admin(user)
|
||||||
|
form = await self._get_admin_form(budget_form_id)
|
||||||
|
if form.to_delete:
|
||||||
|
return form
|
||||||
|
await self.bf_repo.mark_for_deletion(budget_form_id=budget_form_id, user_id=user.id)
|
||||||
|
await self.db.refresh(form)
|
||||||
|
self._add_audit_log(
|
||||||
|
event="FORM_MARKED_FOR_DELETION",
|
||||||
|
user=user,
|
||||||
|
form=form,
|
||||||
|
event_data={
|
||||||
|
"entity_type": "form",
|
||||||
|
"entity_id": form.id,
|
||||||
|
"form_id": form.id,
|
||||||
|
"form_type_code": self._form_type_value(form.form_type_code),
|
||||||
|
"year": form.year,
|
||||||
|
"org_unit_id": form.org_unit_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return form
|
||||||
|
|
||||||
|
async def restore(self, budget_form_id: int, user: AppUser) -> BudgetForm:
|
||||||
|
self._ensure_admin(user)
|
||||||
|
form = await self._get_admin_form(budget_form_id)
|
||||||
|
if not form.to_delete:
|
||||||
|
return form
|
||||||
|
snapshot = self._form_snapshot(form)
|
||||||
|
await self.bf_repo.restore(budget_form_id=budget_form_id, user_id=user.id)
|
||||||
|
await self.db.refresh(form)
|
||||||
|
self._add_audit_log(
|
||||||
|
event="FORM_RESTORED",
|
||||||
|
user=user,
|
||||||
|
form=form,
|
||||||
|
event_data={
|
||||||
|
"entity_type": "form",
|
||||||
|
"entity_id": form.id,
|
||||||
|
"before": snapshot,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return form
|
||||||
|
|
||||||
|
async def hard_delete(self, budget_form_id: int, user: AppUser) -> None:
|
||||||
|
self._ensure_admin(user)
|
||||||
|
form = await self._get_admin_form(budget_form_id)
|
||||||
|
if not form.to_delete:
|
||||||
|
raise ValidationException("Задача не помечена на удаление")
|
||||||
|
if form.deleted_by == user.id:
|
||||||
|
raise AccessDeniedException("Полностью удалить задачу должен другой администратор")
|
||||||
|
snapshot = self._form_snapshot(form)
|
||||||
|
self._add_audit_log(
|
||||||
|
event="FORM_HARD_DELETED",
|
||||||
|
user=user,
|
||||||
|
form=form,
|
||||||
|
event_data={
|
||||||
|
"entity_type": "form",
|
||||||
|
"entity_id": form.id,
|
||||||
|
"before": snapshot,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self.db.flush()
|
||||||
|
await self.bf_repo.hard_delete(budget_form_id=budget_form_id)
|
||||||
|
|
||||||
|
def _ensure_admin(self, user: AppUser) -> None:
|
||||||
|
if user.role_id != UserRoleEnum.ADMIN:
|
||||||
|
raise AccessDeniedException("Недостаточно прав")
|
||||||
|
|
||||||
|
async def _get_admin_form(self, budget_form_id: int) -> BudgetForm:
|
||||||
|
form = await self.bf_repo.get(budget_form_id=budget_form_id, include_to_delete=True, load_org=True)
|
||||||
|
if not form:
|
||||||
|
raise ValidationException("Форма не найдена")
|
||||||
|
return form
|
||||||
|
|
||||||
|
def _add_audit_log(
|
||||||
|
self,
|
||||||
|
event: str,
|
||||||
|
user: AppUser,
|
||||||
|
form: BudgetForm,
|
||||||
|
event_data: dict,
|
||||||
|
) -> None:
|
||||||
|
self.db.add(
|
||||||
|
AuditLog(
|
||||||
|
user_id=user.id,
|
||||||
|
org_unit_id=form.org_unit_id,
|
||||||
|
form_id=form.id,
|
||||||
|
task_id=form.id,
|
||||||
|
event=event,
|
||||||
|
event_type="FORM",
|
||||||
|
event_data=event_data,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _form_type_value(self, form_type_code) -> str:
|
||||||
|
return getattr(form_type_code, "value", str(form_type_code))
|
||||||
|
|
||||||
|
def _form_snapshot(self, form: BudgetForm) -> dict:
|
||||||
|
return {
|
||||||
|
"id": form.id,
|
||||||
|
"form_type_code": self._form_type_value(form.form_type_code),
|
||||||
|
"year": form.year,
|
||||||
|
"org_unit_id": form.org_unit_id,
|
||||||
|
"created_by": form.created_by,
|
||||||
|
"created_at": form.created_at.isoformat() if form.created_at else None,
|
||||||
|
"updated_by": form.updated_by,
|
||||||
|
"updated_at": form.updated_at.isoformat() if form.updated_at else None,
|
||||||
|
"to_delete": form.to_delete,
|
||||||
|
"deleted_by": form.deleted_by,
|
||||||
|
"deleted_at": form.deleted_at.isoformat() if form.deleted_at else None,
|
||||||
|
}
|
||||||
|
|||||||
@ -31,6 +31,15 @@ export const TasksApi = {
|
|||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
markForDeletion(taskId) {
|
||||||
|
return api.post(`/form/${taskId}/mark-delete`).then((r) => r.data);
|
||||||
|
},
|
||||||
|
restore(taskId) {
|
||||||
|
return api.post(`/form/${taskId}/restore`).then((r) => r.data);
|
||||||
|
},
|
||||||
|
hardDelete(taskId) {
|
||||||
|
return api.delete(`/form/${taskId}/hard`).then((r) => r.data);
|
||||||
|
},
|
||||||
exportTasks(payload, params = {}) {
|
exportTasks(payload, params = {}) {
|
||||||
return api.post(`export/bulk`, payload, {
|
return api.post(`export/bulk`, payload, {
|
||||||
params,
|
params,
|
||||||
|
|||||||
@ -59,6 +59,9 @@ export const TaskCardComponent = ({
|
|||||||
onAddForExport,
|
onAddForExport,
|
||||||
onExportClick,
|
onExportClick,
|
||||||
isLastOpened = false,
|
isLastOpened = false,
|
||||||
|
isAdmin = false,
|
||||||
|
onChanged,
|
||||||
|
currentUserId = null,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -67,11 +70,14 @@ export const TaskCardComponent = ({
|
|||||||
if (isLastOpened) cardRef.current?.scrollIntoView({ block: 'nearest' });
|
if (isLastOpened) cardRef.current?.scrollIntoView({ block: 'nearest' });
|
||||||
}, [isLastOpened]);
|
}, [isLastOpened]);
|
||||||
const { id, title, org_unit, form_type_code, year } = taskForm;
|
const { id, title, org_unit, form_type_code, year } = taskForm;
|
||||||
|
const canHardDelete = taskForm.to_delete && taskForm.deleted_by !== currentUserId;
|
||||||
|
|
||||||
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
||||||
const [editedTitle, setEditedTitle] = useState('');
|
const [editedTitle, setEditedTitle] = useState('');
|
||||||
const [currentTypeCode, setCurrentTypeCode] = useState(FORM_TYPE_TRANSLATE[form_type_code] || '');
|
const [currentTypeCode, setCurrentTypeCode] = useState(FORM_TYPE_TRANSLATE[form_type_code] || '');
|
||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
const [confirmAction, setConfirmAction] = useState(null);
|
||||||
|
const [actionLoading, setActionLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsChecked(false);
|
setIsChecked(false);
|
||||||
@ -102,6 +108,52 @@ export const TaskCardComponent = ({
|
|||||||
onExportClick?.(id, title);
|
onExportClick?.(id, title);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openConfirmAction = (event, action) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setConfirmAction(action);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeConfirmAction = () => {
|
||||||
|
if (!actionLoading) setConfirmAction(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const runConfirmedAction = async () => {
|
||||||
|
if (!confirmAction) return;
|
||||||
|
try {
|
||||||
|
setActionLoading(true);
|
||||||
|
if (confirmAction === 'mark') {
|
||||||
|
await TasksApi.markForDeletion(id);
|
||||||
|
toast.success('Задача помечена на удаление');
|
||||||
|
}
|
||||||
|
if (confirmAction === 'restore') {
|
||||||
|
await TasksApi.restore(id);
|
||||||
|
toast.success('Задача возвращена');
|
||||||
|
}
|
||||||
|
if (confirmAction === 'hardDelete') {
|
||||||
|
await TasksApi.hardDelete(id);
|
||||||
|
toast.success('Задача полностью удалена');
|
||||||
|
}
|
||||||
|
setConfirmAction(null);
|
||||||
|
onChanged?.();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error?.response?.data?.detail || 'Не удалось выполнить действие');
|
||||||
|
} finally {
|
||||||
|
setActionLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionTitle = {
|
||||||
|
mark: 'Удалить задачу',
|
||||||
|
restore: 'Вернуть задачу',
|
||||||
|
hardDelete: 'Полностью удалить задачу',
|
||||||
|
}[confirmAction];
|
||||||
|
|
||||||
|
const actionText = {
|
||||||
|
mark: 'Задача будет скрыта от пользователей. Админы смогут вернуть её или удалить окончательно.',
|
||||||
|
restore: 'Задача снова станет доступна пользователям.',
|
||||||
|
hardDelete: 'Будут удалены задача и все связанные данные. Это действие нельзя отменить.',
|
||||||
|
}[confirmAction];
|
||||||
|
|
||||||
const updateTask = async (nextTitle) => {
|
const updateTask = async (nextTitle) => {
|
||||||
try {
|
try {
|
||||||
await TasksApi.editTask(id, { title: nextTitle });
|
await TasksApi.editTask(id, { title: nextTitle });
|
||||||
@ -147,6 +199,7 @@ export const TaskCardComponent = ({
|
|||||||
<TaskCard
|
<TaskCard
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
$fluidWidth={fluidWidth}
|
$fluidWidth={fluidWidth}
|
||||||
|
$marked={Boolean(taskForm.to_delete)}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (exportMode) {
|
if (exportMode) {
|
||||||
handleCheckboxClick(event);
|
handleCheckboxClick(event);
|
||||||
@ -159,7 +212,10 @@ export const TaskCardComponent = ({
|
|||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: '100%' }}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<TaskId>ID: {id}</TaskId>
|
<TaskId>ID: {id}</TaskId>
|
||||||
{isLastOpened && <LastOpenedBadge sx={{ mb: 0 }}>Последняя открытая</LastOpenedBadge>}
|
<Stack direction='row' gap={0.75} alignItems='center'>
|
||||||
|
{taskForm.to_delete && <Chip sx={{ background: '#fde8e8', color: '#b42318' }}>На удаление</Chip>}
|
||||||
|
{isLastOpened && <LastOpenedBadge sx={{ mb: 0 }}>Последняя открытая</LastOpenedBadge>}
|
||||||
|
</Stack>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<TaskTitle>{org_unit?.title || 'Подразделение не указано'}</TaskTitle>
|
<TaskTitle>{org_unit?.title || 'Подразделение не указано'}</TaskTitle>
|
||||||
@ -182,10 +238,47 @@ export const TaskCardComponent = ({
|
|||||||
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
{!isForm && !exportMode && <ExportButton onClick={handleExportTask} />}
|
<Stack direction='row' gap={0.75} alignItems='center' onClick={(event) => event.stopPropagation()}>
|
||||||
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
{isAdmin && !isForm && !exportMode && !taskForm.to_delete && (
|
||||||
|
<Button size='small' variant='text' color='error' onClick={(event) => openConfirmAction(event, 'mark')}>
|
||||||
|
Удалить
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isAdmin && !isForm && !exportMode && taskForm.to_delete && (
|
||||||
|
<>
|
||||||
|
<Button size='small' variant='text' onClick={(event) => openConfirmAction(event, 'restore')}>
|
||||||
|
Вернуть
|
||||||
|
</Button>
|
||||||
|
{canHardDelete && (
|
||||||
|
<Button size='small' variant='text' color='error' onClick={(event) => openConfirmAction(event, 'hardDelete')}>
|
||||||
|
Удалить
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isForm && !exportMode && !taskForm.to_delete && <ExportButton onClick={handleExportTask} />}
|
||||||
|
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</TaskCard>
|
</TaskCard>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={Boolean(confirmAction)}
|
||||||
|
onClose={closeConfirmAction}
|
||||||
|
title={actionTitle}
|
||||||
|
size='small'
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<Button variant='grey' onClick={closeConfirmAction} disabled={actionLoading}>
|
||||||
|
Отменить
|
||||||
|
</Button>
|
||||||
|
<Button variant='contained' color={confirmAction === 'hardDelete' || confirmAction === 'mark' ? 'error' : 'primary'} onClick={runConfirmedAction} disabled={actionLoading}>
|
||||||
|
Подтвердить
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
{actionText}
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,12 +10,13 @@ export const TaskCard = styled.div`
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 0.06rem solid var(--Stroke, rgba(0, 0, 0, 0.1));
|
border: 0.06rem solid ${({ $marked }) => ($marked ? 'rgba(211, 47, 47, 0.35)' : 'var(--Stroke, rgba(0, 0, 0, 0.1))')};
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
||||||
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
||||||
background: var(--White, rgba(255, 255, 255, 1));
|
background: ${({ $marked }) => ($marked ? '#fff7f7' : 'var(--White, rgba(255, 255, 255, 1))')};
|
||||||
|
opacity: ${({ $marked }) => ($marked ? 0.72 : 1)};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -5,6 +5,9 @@ export const DEFAULT_ACTIONS = {
|
|||||||
ACCESS_WINDOW_CHANGE: 'Изменение этапа',
|
ACCESS_WINDOW_CHANGE: 'Изменение этапа',
|
||||||
USER_ROLE_CHANGE: 'Изменение роли пользователя',
|
USER_ROLE_CHANGE: 'Изменение роли пользователя',
|
||||||
CREATE_TASK: 'Создание задачи',
|
CREATE_TASK: 'Создание задачи',
|
||||||
|
FORM_MARKED_FOR_DELETION: 'Задача отмечена на удаление',
|
||||||
|
FORM_RESTORED: 'Задача возвращена',
|
||||||
|
FORM_HARD_DELETED: 'Задача полностью удалена',
|
||||||
// UPDATE_TASK: 'Обновление задачи', //сейчас нет события
|
// UPDATE_TASK: 'Обновление задачи', //сейчас нет события
|
||||||
// DELETE_TASK: 'Удаление задачи',
|
// DELETE_TASK: 'Удаление задачи',
|
||||||
USER_CREATE: 'Создание пользователя',
|
USER_CREATE: 'Создание пользователя',
|
||||||
|
|||||||
@ -58,6 +58,21 @@ export const EVENT_CONFIGS = {
|
|||||||
form_type: 'Форма',
|
form_type: 'Форма',
|
||||||
ssp_list: 'Создана для СПП/РФ',
|
ssp_list: 'Создана для СПП/РФ',
|
||||||
},
|
},
|
||||||
|
FORM_MARKED_FOR_DELETION: {
|
||||||
|
entity_id: 'Задача',
|
||||||
|
form_id: 'Задача',
|
||||||
|
form_type_code: 'Форма',
|
||||||
|
year: 'Год',
|
||||||
|
org_unit_id: 'ССП/РФ',
|
||||||
|
},
|
||||||
|
FORM_RESTORED: {
|
||||||
|
entity_id: 'Задача',
|
||||||
|
before: 'Данные до возврата',
|
||||||
|
},
|
||||||
|
FORM_HARD_DELETED: {
|
||||||
|
entity_id: 'Задача',
|
||||||
|
before: 'Удалённые данные',
|
||||||
|
},
|
||||||
ROW_CREATE: {
|
ROW_CREATE: {
|
||||||
sheet: 'Лист',
|
sheet: 'Лист',
|
||||||
expense_item_id: 'Статья расходов',
|
expense_item_id: 'Статья расходов',
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export default function TasksPage() {
|
|||||||
const yearFilter = searchParams.get('year') || '';
|
const yearFilter = searchParams.get('year') || '';
|
||||||
const formTypeFilter = searchParams.get('form_type') || '';
|
const formTypeFilter = searchParams.get('form_type') || '';
|
||||||
const orgUnitsFilter = searchParams.get('org_units') || '';
|
const orgUnitsFilter = searchParams.get('org_units') || '';
|
||||||
|
const deleteStatusFilter = searchParams.get('delete_status') || 'all';
|
||||||
const orgUnitIdsFilter = useMemo(
|
const orgUnitIdsFilter = useMemo(
|
||||||
() =>
|
() =>
|
||||||
orgUnitsFilter
|
orgUnitsFilter
|
||||||
@ -185,6 +186,7 @@ export default function TasksPage() {
|
|||||||
...(yearFilter && { year: Number(yearFilter) }),
|
...(yearFilter && { year: Number(yearFilter) }),
|
||||||
...(formTypeFilter && { form_type: formTypeFilter }),
|
...(formTypeFilter && { form_type: formTypeFilter }),
|
||||||
...(orgUnitIdsFilter.length > 0 && { org_units: orgUnitIdsFilter.join(',') }),
|
...(orgUnitIdsFilter.length > 0 && { org_units: orgUnitIdsFilter.join(',') }),
|
||||||
|
...(user?.role_id === ROLES_NAME_ID.admin && deleteStatusFilter && { delete_status: deleteStatusFilter }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await TasksApi.list(params);
|
const res = await TasksApi.list(params);
|
||||||
@ -216,7 +218,7 @@ export default function TasksPage() {
|
|||||||
if (currentRequest === requestId.current) setLoading(false);
|
if (currentRequest === requestId.current) setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[yearFilter, formTypeFilter, orgUnitIdsFilter, setSearchParams],
|
[yearFilter, formTypeFilter, orgUnitIdsFilter, deleteStatusFilter, user?.role_id, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePageChange = (_event, value) => {
|
const handlePageChange = (_event, value) => {
|
||||||
@ -263,6 +265,7 @@ export default function TasksPage() {
|
|||||||
nextParams.delete('year');
|
nextParams.delete('year');
|
||||||
nextParams.delete('form_type');
|
nextParams.delete('form_type');
|
||||||
nextParams.delete('org_units');
|
nextParams.delete('org_units');
|
||||||
|
nextParams.delete('delete_status');
|
||||||
return nextParams;
|
return nextParams;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -286,7 +289,18 @@ export default function TasksPage() {
|
|||||||
onLimitChange={handleLimitChange}
|
onLimitChange={handleLimitChange}
|
||||||
entityName='задач'
|
entityName='задач'
|
||||||
emptyMessage='Задачи не найдены'
|
emptyMessage='Задачи не найдены'
|
||||||
renderItem={(task, exportProps) => <TaskCardComponent key={task.id} taskForm={task} isLastOpened={String(task.id) === lastVisit?.id} fluidWidth {...exportProps} />}
|
renderItem={(task, exportProps) => (
|
||||||
|
<TaskCardComponent
|
||||||
|
key={task.id}
|
||||||
|
taskForm={task}
|
||||||
|
isLastOpened={String(task.id) === lastVisit?.id}
|
||||||
|
fluidWidth
|
||||||
|
isAdmin={user.role_id === ROLES_NAME_ID.admin}
|
||||||
|
currentUserId={user.id}
|
||||||
|
onChanged={() => loadTasks(page, limit)}
|
||||||
|
{...exportProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
getItemId={(task) => task.id}
|
getItemId={(task) => task.id}
|
||||||
getItemTitle={(task) =>
|
getItemTitle={(task) =>
|
||||||
task.title ||
|
task.title ||
|
||||||
@ -345,6 +359,19 @@ export default function TasksPage() {
|
|||||||
sx={{ width: 480, maxWidth: '100%' }}
|
sx={{ width: 480, maxWidth: '100%' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{user.role_id === ROLES_NAME_ID.admin && (
|
||||||
|
<Select
|
||||||
|
size='small'
|
||||||
|
displayEmpty
|
||||||
|
value={deleteStatusFilter}
|
||||||
|
onChange={(event) => handleFilterChange('delete_status', event.target.value)}
|
||||||
|
sx={{ width: 230 }}>
|
||||||
|
<MenuItem value='active'>Непомеченные</MenuItem>
|
||||||
|
<MenuItem value='marked'>Помеченные</MenuItem>
|
||||||
|
<MenuItem value='all'>Все задачи</MenuItem>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
|
||||||
<ResetFiltersButton onClick={handleClearFilters} />
|
<ResetFiltersButton onClick={handleClearFilters} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user