--
-- 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/>

-- @file merchant-0038.sql
-- @brief Track the number of currently locked units per product in a
--   denormalized total_locked/total_locked_frac counter on
--   merchant_inventory.  The counter is kept up to date by a trigger on
--   the two lock tables (merchant_inventory_locks and merchant_order_locks)
--   and is used to report the quantity actually available for new orders
--   (which excludes units reserved by shopping carts and unpaid orders).
-- @author Christian Grothoff

BEGIN;

SELECT _v.register_patch('merchant-0038', NULL, NULL);

SET search_path TO merchant;

CREATE PROCEDURE merchant.merchant_0038_init(s TEXT)
  LANGUAGE plpgsql
  AS $OUTER$
BEGIN
  EXECUTE format('SET LOCAL search_path TO %I', s);

  -- Denormalized counter of how many units of each product are currently
  -- reserved by locks (shopping cart locks plus unpaid order locks).  Kept
  -- in sync by update_inventory_locked() below.  total_locked_frac is in
  -- units of 1/1000000 of the base value, mirroring total_stock_frac etc.
  ALTER TABLE merchant_inventory
    ADD COLUMN total_locked INT8 NOT NULL DEFAULT 0,
    ADD COLUMN total_locked_frac INT4 NOT NULL DEFAULT 0;
  COMMENT ON COLUMN merchant_inventory.total_locked
    IS 'Number of units currently reserved by locks (cart and unpaid order locks); these units are unavailable for new orders. Maintained by the update_inventory_locked() trigger.';
  COMMENT ON COLUMN merchant_inventory.total_locked_frac
    IS 'Fractional part of total_locked in units of 1/1000000 of the base value';

  -- Backfill the counter for instances that already hold locks.  We
  -- temporarily disable the user triggers on merchant_inventory so that
  -- the backfill does not emit spurious "inventory_updated" webhooks.
  ALTER TABLE merchant_inventory DISABLE TRIGGER USER;
  UPDATE merchant_inventory mi
     SET total_locked = div (agg.micros, 1000000)::INT8,
         total_locked_frac = (agg.micros % 1000000)::INT4
    FROM (
      SELECT product_serial,
             SUM(micros) AS micros
        FROM (
          SELECT product_serial,
                 total_locked::NUMERIC * 1000000
                 + total_locked_frac::NUMERIC AS micros
            FROM merchant_inventory_locks
          UNION ALL
          SELECT product_serial,
                 total_locked::NUMERIC * 1000000
                 + total_locked_frac::NUMERIC
            FROM merchant_order_locks
        ) all_locks
       GROUP BY product_serial
    ) agg
   WHERE mi.product_serial = agg.product_serial;
  ALTER TABLE merchant_inventory ENABLE TRIGGER USER;

  -- Dummy function so we can create the trigger, actual
  -- trigger will be loaded later via instance_procedures.sql.
  CREATE OR REPLACE FUNCTION update_inventory_locked()
    RETURNS TRIGGER
    LANGUAGE plpgsql
  AS $LOCKED$
  BEGIN
    RETURN NULL;
  END $LOCKED$;

  -- Add trigger to keep the new columns always up-to-date
  CREATE TRIGGER trigger_inventory_locks_locked
    AFTER INSERT OR UPDATE OR DELETE
    ON merchant_inventory_locks
    FOR EACH ROW EXECUTE FUNCTION update_inventory_locked();

  CREATE TRIGGER trigger_order_locks_locked
    AFTER INSERT OR UPDATE OR DELETE
    ON merchant_order_locks
    FOR EACH ROW EXECUTE FUNCTION update_inventory_locked();
END
$OUTER$;

INSERT INTO merchant.instance_fixups
  (migration_name
  ,version)
  VALUES
  ('merchant_0038_init'
  ,38);
-- Apply new fix-up to existing instances
CALL merchant.fixup_instance_schema (38::INT8);

COMMIT;
