From 7f31fb5105258f839f89efc78424fd87eb59563b Mon Sep 17 00:00:00 2001 From: "reza.g" Date: Tue, 8 Sep 2026 14:41:57 +0330 Subject: [PATCH] =?UTF-8?q?fix(materials):=20remove=20nested=20db.begin=20?= =?UTF-8?q?=E2=80=94=20material=20PATCH=20was=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The router loads the material row (starting a transaction) before calling material_service.update_material, which then opened its own async with db.begin() → SQLAlchemy 'A transaction is already begun on this Session' → INTERNAL_ERROR on every PATCH (incl. changing the reference unit from ضرایب تبدیل). Mutations now run inside the request transaction committed by the get_db dependency. --- backend/app/services/material_service.py | 42 +++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/backend/app/services/material_service.py b/backend/app/services/material_service.py index d0f2002..0da33f2 100644 --- a/backend/app/services/material_service.py +++ b/backend/app/services/material_service.py @@ -160,27 +160,31 @@ async def update_material(db: AsyncSession, material: Material, payload: Materia v2.5/2.6: category is DERIVED (from the first linked supplier) — it is no longer part of the update payload. + + NOTE: no ``db.begin()`` here — the router already starts the transaction + (it loads the row first), and a nested begin raises + "A transaction is already begun on this Session" (500). The request + session commits via the get_db dependency. """ - async with db.begin(): - if payload.name is not None: - duplicate = await db.scalar( - select(Material.id).where( - Material.name == payload.name, - Material.id != material.id, - ) + if payload.name is not None: + duplicate = await db.scalar( + select(Material.id).where( + Material.name == payload.name, + Material.id != material.id, ) - if duplicate is not None: - raise ConflictError("متریالی با این نام در کل کاتالوگ قبلاً ثبت شده است", field="name") - if payload.name is not None: - material.name = payload.name - if payload.topic is not None: - material.topic = payload.topic - if payload.canonical_unit_id is not None and payload.canonical_unit_id != material.canonical_unit_id: - if not await db.get(Unit, payload.canonical_unit_id): - raise NotFoundError("واحد مرجع یافت نشد", field="canonical_unit_id") - material.canonical_unit_id = payload.canonical_unit_id - if payload.status is not None: - material.status = payload.status + ) + if duplicate is not None: + raise ConflictError("متریالی با این نام در کل کاتالوگ قبلاً ثبت شده است", field="name") + if payload.name is not None: + material.name = payload.name + if payload.topic is not None: + material.topic = payload.topic + if payload.canonical_unit_id is not None and payload.canonical_unit_id != material.canonical_unit_id: + if not await db.get(Unit, payload.canonical_unit_id): + raise NotFoundError("واحد مرجع یافت نشد", field="canonical_unit_id") + material.canonical_unit_id = payload.canonical_unit_id + if payload.status is not None: + material.status = payload.status return material