BEGIN;
    -- Load the TAP functions.
    CREATE EXTENSION pgtap;
    SET client_min_messages TO warning;

    -- Plan the tests.

    SELECT plan(53);

    -- Add data

    \i xt/data/42-pg/Base.sql

    -- Validate required tables

    SELECT has_table('ap');
    SELECT has_table('payment');
    SELECT has_table('payment_links');
    SELECT has_table('payment_type');

    -- Validate required functions

    SELECT has_function('business_type__list','{}'::text[]);
    SELECT has_function('overpayment__reverse',ARRAY['integer','date','integer','integer']);
    SELECT has_function('payment_bulk_post',ARRAY['numeric[]','integer','text','text','text','date','integer','numeric','text']);
    SELECT has_function('payment_gather_header_info',ARRAY['integer','integer']);
    SELECT has_function('payment_gather_line_info',ARRAY['integer','integer']);
    SELECT has_function('payment_get_all_accounts',ARRAY['integer']);
    SELECT has_function('payment_get_all_contact_invoices',ARRAY['integer','integer','character','date','date','integer','text','text','text','date']);
    SELECT has_function('payment_get_available_overpayment_amount',ARRAY['integer','integer']);
    SELECT has_function('payment_get_entity_account_payment_info',ARRAY['integer']);
    SELECT has_function('payment_get_entity_accounts',ARRAY['integer','text','text','date','date']);
    SELECT has_function('payment__get_gl',ARRAY['integer']);
    SELECT has_function('payment_get_open_accounts',ARRAY['integer','date','date']);
    SELECT has_function('payment_get_open_invoice',ARRAY['integer','integer','character','date','date','numeric','numeric','text','date']);
    SELECT has_function('payment_get_open_invoices',ARRAY['integer','integer','character','date','date','numeric','numeric','date']);
    SELECT has_function('payment_get_open_overpayment_entities',ARRAY['integer']);
    SELECT has_function('payment_get_unused_overpayment',ARRAY['integer','integer','integer']);
    SELECT has_function('payment_get_vc_info',ARRAY['integer','integer']);
    SELECT has_function('payment__overpayments_list',ARRAY['date','date','text','text','text']);
    SELECT has_function('payment_post',ARRAY['date','integer','integer','character','numeric','text','text','integer[]','numeric[]','text[]','text[]','integer[]','numeric[]','integer[]','text[]','text[]','integer[]','integer[]','boolean']);
    SELECT has_function('payment__reverse',ARRAY['integer','date','boolean','integer']);
    SELECT has_function('payment__search',ARRAY['text','date','date','integer','text','integer','character','text']);
    SELECT has_function('payments_get_open_currencies',ARRAY['integer']);
    SELECT has_function('payment_type__get_label',ARRAY['integer']);
    SELECT has_function('payment_type__list','{}'::text[]);

    -- Add specific data

    insert into users (entity_id, username, id)
    values (-200, '_test1', -200);

    insert into session (session_id, users_id, token, last_used)
    values (-200, -200, md5(random()::text), now());

    SELECT account__save(null, accno, description, category, null,
                         (select id from account_heading limit 1),
                         null, false, false, array[link], false, false)
      FROM (VALUES ('00001', 'testing', 'L', 'AP'),
                   ('00001AP2', 'testing', 'L', 'AP'),
                   ('00002', 'testing2', 'E', 'AP_amount'),
                   ('00003', 'testing2', 'A', 'AP_paid')) f
                   (accno, description, category, link);

    INSERT INTO session (users_id, last_used, token)
    values (currval('users_id_seq'),  now(), md5('test2'));

    -- Run tests


    -- Test payment batch creation

    PREPARE test AS SELECT batch_create('test', 'test', 'ap', now()::date)
                    IS NOT NULL;
    SELECT results_eq('test',ARRAY[true],'AP Batch created');
    DEALLOCATE test;

    INSERT INTO company (id, legal_name, entity_id)
    VALUES (-101, 'TEST', -101);

    INSERT INTO business (id, description)
    values (-101, 'test');

    INSERT INTO account(id, accno, description, category, contra, heading)
    values (-2000, '-2000000000', 'Test discount', 'A', false,
      (select id from account_heading WHERE accno  = '000000000000000000000'));
    INSERT INTO entity_credit_account
                (id, meta_number, threshold, entity_id, entity_class,
                 business_id, ar_ap_account_id, curr,
                 discount, discount_terms, discount_account_id)
         values (-101, 'TEST1', 100000, -101, 1, -101,
                 (select id from account where accno = '00001'), 'XTS',
                 0.1, 30, -2000); -- 10%, 30 days

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr)
            values ('test_hide', -101, false, '1', '1', '1', 'XTS');

    INSERT INTO voucher (trans_id, batch_class, batch_id)
                 VALUES (currval('id'), 1, currval('batch_id_seq'));

    PREPARE test AS SELECT batch_create('test2', 'test2', 'ap', now()::date)
                    IS NOT NULL;
    SELECT results_eq('test',ARRAY[true],'Payment Batch created');
    DEALLOCATE test;

    SELECT currval('batch_id_seq') as batch_id
    \gset

    -- Test contact invoice selection (payment__get_all_contact_invoices())

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr, transdate)
            VALUES ('test_show2', -101, true, 100000, 100000, 100000,
                    'XTS', now()::date);

    INSERT INTO acc_trans (approved, transdate, amount_bc, curr,
                           amount_tc, trans_id, chart_id)
                   VALUES (true, now()::date, '100000', 'XTS',
                           '100000', currval('id'),
                           (select id from account where accno = '00001')),
                          (true, now()::date, '-100000', 'XTS',
                           '-100000', currval('id'),
                           (select id from account where accno = '00002'));

    INSERT INTO ap (id, invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr, transdate)
            VALUES (-300, 'test_show3', -101, true, 1000000, 1000000, 1000000,
                    'XTS', now()::date);

    INSERT INTO acc_trans (approved, transdate, amount_bc, curr,
                           amount_tc, trans_id, chart_id)
                   VALUES (true, now()::date, '1000000', 'XTS',
                           '1000000', -300,
                           (select id from account where accno = '00001')),
                          (true, now()::date, '-1000000', 'XTS',
                           '-1000000', -300,
                           (select id from account where accno = '00002'));

    update transactions set locked_by = -200 where id = -300;

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr, transdate)
            values ('test_show', -101, false, '1', '1', '1', 'XTS', now()::date);

    INSERT INTO acc_trans (approved, transdate, amount_bc,
                           curr, amount_tc, trans_id, chart_id)
                   VALUES (true, now()::date, '1', 'XTS', '1', currval('id'),
                           (select id from account where accno = '00001')),
                          (true, now()::date, '-1', 'XTS', '-1', currval('id'),
                           (select id from account where accno = '00002'));

    INSERT INTO voucher (trans_id, batch_class, batch_id)
                 VALUES (currval('id'), 1, :batch_id);

    CREATE FUNCTION test_convert_array(anyarray) RETURNS text AS $$
        SELECT array_to_string($1, '::');
    $$ LANGUAGE SQL;

    PREPARE test AS
    SELECT amount::int, due::numeric, discount::numeric,
           amount_tc::int, due_fx::numeric, discount_tc::numeric
     FROM payment_get_open_invoice(
            1, -101, 'XTS', null, null, null, null, 'test_show3', null);
    SELECT results_eq('test',
                     $$VALUES (1000000::int, 999000::numeric, 1000::numeric,
                               1000000::int, 999000::numeric, 1000::numeric)$$,
                     'On direct payment, a discount applies');
    DEALLOCATE test;

    PREPARE test AS
    SELECT amount::int, due::numeric, discount::numeric
     FROM payment_get_open_invoice(
            1, -101, 'XTS', null, null, null, null, 'test_show3',
            (now() + '60 days'::interval)::date);
    SELECT results_eq('test',
                     $$VALUES (1000000::int, 1000000::numeric, 0::numeric)$$,
                     'On past-term payment, no discount applies');
    DEALLOCATE test;

    PREPARE test AS
    SELECT test_convert_array(invoices) LIKE '%::test_show::%'
      FROM (
        SELECT invoices
          FROM payment_get_all_contact_invoices(1, NULL, 'XTS', NULL,
                        NULL, (:batch_id)::int, '00001', 'TEST1', NULL, NULL)
        ) p;
    SELECT results_eq('test',ARRAY[true],'Batch Voucher In Payment Selection');
    DEALLOCATE test;

    PREPARE test AS
    SELECT test_convert_array(invoices) LIKE '%::test_show3::%'
      FROM (
        SELECT invoices
         FROM payment_get_all_contact_invoices(1, NULL, 'XTS', NULL,
                       NULL, currval('batch_id_seq')::int, '00001',
                       'TEST1', NULL, NULL)
      ) p;
    SELECT results_eq('test',ARRAY[true],'Locked Invoice In Payment Selection');
    DEALLOCATE test;

    PREPARE test AS
    SELECT test_convert_array(invoices) LIKE '%::test_show2::%'
      FROM (
        SELECT invoices
          FROM payment_get_all_contact_invoices(1, NULL, 'XTS', NULL,
                        NULL, NULL, '00001', 'TEST1', NULL, NULL)
      ) p;
    SELECT results_eq('test',ARRAY[true],'Threshold met');
    DEALLOCATE test;

    PREPARE test AS
    SELECT test_convert_array(invoices) NOT LIKE '%::test_hide::%'
       FROM (
         SELECT invoices
           FROM payment_get_all_contact_invoices(1, NULL, 'XTS', NULL,
                         NULL, currval('batch_id_seq')::int, '00001',
                         'TEST1', NULL, NULL)
       ) p;
    SELECT results_eq('test', ARRAY[true],
                      'Non-Batch Voucher Not In Payment Selection');
    DEALLOCATE test;

    PREPARE test AS
    SELECT total_due < 1000000
      FROM payment_get_all_contact_invoices(1, NULL, 'XTS', NULL,
                    NULL, currval('batch_id_seq')::int, '00001', 'TEST1',
                    NULL, NULL);
    SELECT results_eq('test', ARRAY[true], 'Locked Invoice not in total');
    DEALLOCATE test;

    UPDATE entity_credit_account SET enddate = '2022-01-01', startdate = null;
    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, null, null);
    SELECT results_eq('test', ARRAY[1], 'Open accounts(enddate), no startdate & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, '2020-01-01', null);
    SELECT results_eq('test', ARRAY[1], 'Open accounts(enddate), startdate(before enddate) & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, '2023-01-01', null);
    SELECT results_eq('test', ARRAY[0], 'Open accounts(enddate), startdate(after enddate) & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, null, '2023-01-01');
    SELECT results_eq('test', ARRAY[1], 'Open accounts(enddate), no startdate & enddate(after enddate)');
    DEALLOCATE test;

    UPDATE entity_credit_account SET enddate = null, startdate = '2022-01-01';
    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, null, null);
    SELECT results_eq('test', ARRAY[1], 'Open accounts(startdate), no startdate & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, '2020-01-01', null);
    SELECT results_eq('test', ARRAY[1], 'Open accounts(startdate), startdate(before startdate) & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, '2023-01-01', null);
    SELECT results_eq('test', ARRAY[1], 'Open accounts(startdate), startdate(after startdate) & no enddate');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, null, '2023-01-01');
    SELECT results_eq('test', ARRAY[1], 'Open accounts(startdate), no startdate & enddate(after startdate)');
    DEALLOCATE test;

    PREPARE test AS
      SELECT count(*)::int
      FROM payment_get_open_accounts(1, null, '2020-01-01');
    SELECT results_eq('test', ARRAY[0], 'Open accounts(startdate), no startdate & enddate(before startdate)');
    DEALLOCATE test;

    -- Test vouchers (TODO)

    INSERT INTO voucher (batch_id, batch_class, id, trans_id)
                 values (currval('batch_id_seq')::int, 4, -100,
                         currval('id')::int);
    INSERT INTO acc_trans (trans_id, chart_id, voucher_id, approved,
                           amount_bc, curr, amount_tc, transdate, source)
                   values (currval('id')::int,
                           (select id from account where accno = '00003'),
                           -100, true, '1', 'XTS', '1', now(), '_test_src1'),
                          (currval('id')::int,
                           (select id from account where accno = '00001'),
                           -100, true, '-1', 'XTS', '-1', now(), '_test_src1');

    SELECT todo('Missing Voucher test',0);


    -- Test payment reversal

    -- Explicitly tests posting to a non-default (for the ECA) AP account
    -- as a test for GitHub issue #2558

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr, transdate)
            values ('test_reverse', -101, true, '1', '1', '1',
                    'XTS', now()::date)
       returning id as invoice_id
    \gset

    INSERT INTO acc_trans (approved, transdate, amount_bc, curr, amount_tc,
                           trans_id, chart_id)
                   VALUES (true, now()::date, '1', 'XTS', '1', currval('id'),
                           (select id from account where accno = '00001AP2')),
                          (true, now()::date, '-1', 'XTS', '-1', :invoice_id,
                           (select id from account where accno = '00002'));


    PREPARE test AS
    SELECT amount::int, due::int
      FROM payment_get_open_invoice(
             1, -101, 'XTS', null, null, null, null, 'test_reverse', null);
    SELECT results_eq('test', $$VALUES (1::int, 1::int)$$,
                      'Before payment, the open amount is 1');
    DEALLOCATE test;


    SELECT payment_post(now()::date, 1, -101, 'XTS', 1, 'notes', 'description',
                        array[(select id from account where accno = '00003')],
                        array[1]::numeric[],
                        array['pmt-rev-src1']::text[], NULL,
                        array[:invoice_id]::int[],
                        -- overpayments section
                        null, null, null, null, null, null,
                        -- approved:
                        true);

    PREPARE test AS
    SELECT count(*) = 0
      FROM payment_get_open_invoice(
             1, -101, 'XTS', null, null, null, null, 'test_reverse', null);
    SELECT results_eq('test', array[true],
                      'After payment, the invoice is paid');
    DEALLOCATE test;

   SELECT payment__reverse(currval('payment_id_seq')::int, now()::date,
                           null::boolean, null::int);

   PREPARE test AS
   SELECT amount::int, due::int
     FROM payment_get_open_invoice(
            1, -101, 'XTS', null, null, null, null, 'test_reverse', null);
   SELECT results_eq('test', $$VALUES (1::int, 1::int)$$,
                     'After reversal, paid amount reverts back to nil');
   DEALLOCATE test;

   PREPARE test AS
   SELECT count(distinct act.id) = 1
     FROM ap
          JOIN acc_trans ac ON ap.id = ac.trans_id
          JOIN account act ON ac.chart_id = act.id
          JOIN account_link al ON al.account_id = act.id
    WHERE ap.invnumber = 'test_reverse'
          AND al.description = 'AP';
    SELECT results_eq('test', array[true],
                      'AP ');
    DEALLOCATE test;


    -- bulk post payment
    SELECT batch_create('test3', 'test3', 'payment', now()::date) as batch_id
    \gset
    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount_bc, amount_tc, netamount_bc, curr, transdate)
            values ('test_bulk', -101, true, '1', '1', '1',
                    'XTS', now()::date)
       returning id as invoice_id
    \gset
    SELECT payment_bulk_post(ARRAY[ARRAY[(:invoice_id)::numeric, 1::numeric]]::numeric[],
                             (:batch_id)::int, 'bulk'::text, '00001'::text, '00003'::text, now()::date,
                             1::int, 1::numeric, 'XTS'::text);
    SELECT currval('payment_id_seq') as payment_id
    \gset

    PREPARE test AS
    SELECT count(*)::int FROM payment p
     WHERE notes = 'generated from bulk payment'
           AND EXISTS (select 1 from payment_links pl
                                join acc_trans ac on pl.entry_id = ac.entry_id
                                join ap on ac.trans_id = ap.id
                        where ap.invnumber = 'test_bulk');
    SELECT results_eq('test', array[1::int],
                      'After payment, the payment table contains a record');
    DEALLOCATE test;

    SELECT id as payment_id FROM payment p
     WHERE notes = 'generated from bulk payment'
           AND EXISTS (select 1 from payment_links pl
                                join acc_trans ac on pl.entry_id = ac.entry_id
                                join ap on ac.trans_id = ap.id
                        where ap.invnumber = 'test_bulk')
    \gset



    PREPARE test AS
    SELECT count(*)::int FROM payment_links
     WHERE payment_id = :payment_id ;
    SELECT results_eq('test', array[3::int],
                      'There are 3 links: the cash account, the discount account and the ap account line');
    DEALLOCATE test;

    PREPARE test AS
    SELECT count(*)::int FROM acc_trans a
                    JOIN voucher v ON v.id = a.voucher_id
                    JOIN batch b ON v.batch_id = b.id
     WHERE b.id = :batch_id ;
    SELECT results_eq('test', array[3::int],
                      'There are 3 journal lines as there are 3 payment links');
    DEALLOCATE test;

    -- test that the command runs ok.
    SELECT batch_delete(:batch_id);

    -- Finish the tests and clean up.
    SELECT * FROM finish();

ROLLBACK;
