commit 36a08455d67c67ce2fb27a38faee45600979dbed
parent 0e4fda08fd481b02d49ac814e96c46a529219ebd
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 5 Aug 2026 23:29:48 +0200
only one lock per lock_uuid and product, re-posting the lock is supposed to update, not create another one
Diffstat:
1 file changed, 135 insertions(+), 0 deletions(-)
diff --git a/src/backenddb/insert_inventory_lock.sql b/src/backenddb/insert_inventory_lock.sql
@@ -0,0 +1,135 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_insert_inventory_lock;
+CREATE FUNCTION merchant_do_insert_inventory_lock (
+ IN in_product_id TEXT,
+ IN in_lock_uuid BYTEA,
+ IN in_total_locked INT8,
+ IN in_total_locked_frac INT4,
+ IN in_expiration INT8,
+ OUT out_no_product BOOL,
+ OUT out_insufficient_stock BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_rec RECORD;
+ my_requested NUMERIC;
+ my_locked NUMERIC;
+ my_available NUMERIC;
+BEGIN
+
+SELECT product_serial
+ ,total_stock
+ ,total_stock_frac
+ ,total_sold
+ ,total_sold_frac
+ ,total_lost
+ ,total_lost_frac
+ ,allow_fractional_quantity
+ INTO my_rec
+ FROM merchant_inventory
+ WHERE product_id = in_product_id;
+
+IF NOT FOUND
+THEN
+ out_no_product = TRUE;
+ out_insufficient_stock = FALSE;
+ RETURN;
+END IF;
+out_no_product = FALSE;
+
+IF ( (NOT my_rec.allow_fractional_quantity) AND
+ (in_total_locked_frac <> 0) )
+THEN
+ -- Product is not sold in fractional quantities.
+ out_insufficient_stock = TRUE;
+ RETURN;
+END IF;
+
+-- Per the API specification, locking a quantity of zero releases
+-- the lock held under this UUID.
+IF ( (in_total_locked = 0) AND
+ (in_total_locked_frac = 0) )
+THEN
+ DELETE FROM merchant_inventory_locks
+ WHERE product_serial = my_rec.product_serial
+ AND lock_uuid = in_lock_uuid;
+ out_insufficient_stock = FALSE;
+ RETURN;
+END IF;
+
+-- INT64_MAX in total_stock is the sentinel for "unlimited stock",
+-- for which no availability check is made.
+IF (my_rec.total_stock <> 9223372036854775807)
+THEN
+ my_requested = in_total_locked::NUMERIC * 1000000
+ + in_total_locked_frac::NUMERIC;
+ -- Note that a lock already held under _this_ UUID is replaced and
+ -- thus must not count against the available stock.
+ SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000
+ + total_locked_frac::NUMERIC), 0)
+ INTO my_locked
+ FROM merchant_inventory_locks
+ WHERE product_serial = my_rec.product_serial
+ AND lock_uuid <> in_lock_uuid;
+ my_locked = my_locked
+ + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000
+ + total_locked_frac::NUMERIC), 0)
+ FROM merchant_order_locks
+ WHERE product_serial = my_rec.product_serial);
+ my_available = (my_rec.total_stock::NUMERIC * 1000000
+ + my_rec.total_stock_frac::NUMERIC)
+ - (my_rec.total_sold::NUMERIC * 1000000
+ + my_rec.total_sold_frac::NUMERIC)
+ - (my_rec.total_lost::NUMERIC * 1000000
+ + my_rec.total_lost_frac::NUMERIC);
+ IF (my_available < my_requested + my_locked)
+ THEN
+ out_insufficient_stock = TRUE;
+ RETURN;
+ END IF;
+END IF;
+out_insufficient_stock = FALSE;
+
+INSERT INTO merchant_inventory_locks
+ (product_serial
+ ,lock_uuid
+ ,total_locked
+ ,total_locked_frac
+ ,expiration)
+ VALUES
+ (my_rec.product_serial
+ ,in_lock_uuid
+ ,in_total_locked
+ ,in_total_locked_frac
+ ,in_expiration)
+ ON CONFLICT (product_serial
+ ,lock_uuid)
+ DO UPDATE
+ SET total_locked = EXCLUDED.total_locked
+ ,total_locked_frac = EXCLUDED.total_locked_frac
+ ,expiration = EXCLUDED.expiration;
+
+END $$;
+
+COMMENT ON FUNCTION merchant_do_insert_inventory_lock
+ IS 'Locks in_total_locked(_frac) units of the given product under the'
+ ' given lock UUID. A lock already held under the same UUID is'
+ ' replaced (not added to), a quantity of zero releases it. Sets'
+ ' out_no_product if the product does not exist and'
+ ' out_insufficient_stock if not enough unlocked stock remains.';