commit 64ba5ebfbc188e8648f8cfcda55ce0bb90a4b05d
parent 5a1786aea8150379a7087717194cad3ea1c9b48a
Author: Sebastian <sebasjm@taler-systems.com>
Date: Mon, 18 May 2026 13:25:21 -0300
force delete when locked
Diffstat:
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>"{deleting.description}"</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>"{deleting.description}"</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,