taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 64ba5ebfbc188e8648f8cfcda55ce0bb90a4b05d
parent 5a1786aea8150379a7087717194cad3ea1c9b48a
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Mon, 18 May 2026 13:25:21 -0300

force delete when locked

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx | 45+++++++++++++++++++++++++++++++++------------
Mpackages/merchant-backoffice-ui/src/paths/instance/update/DeletePage.tsx | 2+-
Mpackages/taler-util/src/http-client/merchant.ts | 7++++++-
3 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx @@ -58,14 +58,16 @@ export default function ProductList({ onCreate, onSelect }: Props): VNode { const [notification, safeFunctionHandler] = useLocalNotificationBetter(); const { i18n } = useTranslationContext(); + const [forceDeletion, setForceDeletion] = useState(false); const remove = safeFunctionHandler( i18n.str`delete product`, lib.instance.deleteProduct.bind(lib.instance), - !session.token || !deleting ? undefined : [session.token, deleting.id], + !session.token || !deleting ? undefined : [session.token, deleting.id, {force: forceDeletion}], ); remove.onSuccess = (suc, t, id) => { setDeleting(null); + setForceDeletion(false) return i18n.str`Product (ID: ${id}) has been deleted`; }; remove.onFail = (fail) => { @@ -75,7 +77,8 @@ export default function ProductList({ onCreate, onSelect }: Props): VNode { case HttpStatusCode.NotFound: return i18n.str`Not found.`; case HttpStatusCode.Conflict: - return i18n.str`Conflict.`; + setForceDeletion(true); + return i18n.str`The product has locked stock. You should cancel the pending orders.`; default: assertUnreachable(fail); } @@ -122,21 +125,39 @@ export default function ProductList({ onCreate, onSelect }: Props): VNode { {deleting && ( <ConfirmModal - label={i18n.str`Delete product`} + label={ + forceDeletion + ? i18n.str`Cancel orders and delete` + : i18n.str`Delete product` + } description={i18n.str`Delete the product "${deleting.description}"`} danger active - onCancel={() => setDeleting(null)} + onCancel={() => { + setDeleting(null); + setForceDeletion(false) + }} confirm={remove} > - <p> - <i18n.Translate> - If you delete the product named{" "} - <b>&quot;{deleting.description}&quot;</b> (ID:{" "} - <b>{deleting.id}</b> - ), the stock and related information will be lost - </i18n.Translate> - </p> + {forceDeletion ? ( + <p> + <p class="warning"> + <i18n.Translate> + There is locked stock in pending orders. If you force the deletion of this product + those orders will be cancelled. + </i18n.Translate> + </p> + </p> + ) : ( + <p> + <i18n.Translate> + If you delete the product named{" "} + <b>&quot;{deleting.description}&quot;</b> (ID:{" "} + <b>{deleting.id}</b> + ), the stock and related information will be lost + </i18n.Translate> + </p> + )} <p class="warning"> <i18n.Translate> Deleting a product cannot be undone. diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/DeletePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/DeletePage.tsx @@ -36,9 +36,9 @@ import { import { Input } from "../../../components/form/Input.js"; import { InputToggle } from "../../../components/form/InputToggle.js"; import { SolveMFAChallenges } from "../../../components/SolveMFA.js"; +import { Tooltip } from "../../../components/Tooltip.js"; import { useSessionContext } from "../../../context/session.js"; import { undefinedIfEmpty } from "../../../utils/table.js"; -import { Tooltip } from "../../../components/Tooltip.js"; import { maybeTryFirstMFA } from "../accounts/create/CreatePage.js"; const TALER_SCREEN_ID = 73; diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -1517,13 +1517,18 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ - async deleteProduct(token: AccessToken, productId: string) { + async deleteProduct(token: AccessToken, productId: string, params: { + force?: boolean + } = {}) { const url = new URL(`private/products/${productId}`, this.baseUrl); const headers: Record<string, string> = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); } + if (params.force) { + url.searchParams.set("force", "yes"); + } const resp = await this.httpLib.fetch(url.href, { method: "DELETE", headers,