--
-- 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.';
