BEGIN;
    -- Load the TAP functions.
    CREATE EXTENSION pgtap;

    -- Plan the tests.

    SELECT plan(41);

    -- Add data

    \i sql/modules/test/Base.sql
    insert into defaults values ('curr', 'USD');

    -- Validate required tables

    SELECT has_table('ap');
    SELECT has_table('payment');
    SELECT has_table('payment_links');
    SELECT has_table('payment_map');
    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','numeric','character']);
    SELECT has_function('payment_bulk_post',ARRAY['numeric[]','integer','text','numeric','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']);
    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']);
    SELECT has_function('payment_get_open_invoices',ARRAY['integer','integer','character','date','date','numeric','numeric']);
    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','text','text','integer[]','numeric[]','boolean[]','text[]','text[]','integer[]','numeric[]','integer[]','text[]','text[]','integer[]','integer[]','boolean']);
    SELECT has_function('payment__reverse',ARRAY['text','date','integer','text','date','integer','integer','integer','numeric','character']);
    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('payments_set_exchangerate',ARRAY['integer','numeric','character','date']);
    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, 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 entity_credit_account
                (id, meta_number, threshold, entity_id, entity_class,
                 business_id, ar_ap_account_id)
         values (-101, 'TEST1', 100000, -101, 1, -101,
                 (select id from account where accno = '00001'));

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount, netamount, curr)
            values ('test_hide', -101, false, '1', '1', 'USD');

    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;


    -- Test contact invoice selection (payment__get_all_contact_invoices())

    INSERT INTO ap (invnumber, entity_credit_account, approved,
                    amount, netamount, curr, transdate)
            VALUES ('test_show2', -101, true, 100000, 100000,
                    'USD', now()::date);

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

    INSERT INTO ap (id, invnumber, entity_credit_account, approved,
                    amount, netamount, curr, transdate)
            VALUES (-300, 'test_show3', -101, true, 1000000, 1000000,
                    'USD', now()::date);

    INSERT INTO acc_trans (approved, transdate, amount, trans_id, chart_id)
                   VALUES (true, now()::date, '1000000', -300,
                           (select id from account where accno = '00001')),
                          (true, now()::date, '-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, netamount, curr, transdate)
            values ('test_show', -101, false, '1', '1', 'USD', now()::date);

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

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

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

    PREPARE test AS
    SELECT test_convert_array(invoices) LIKE '%::test_show::%'
      FROM (
        SELECT invoices
          FROM payment_get_all_contact_invoices(1, NULL, 'USD', NULL,
                        NULL, currval('batch_id_seq')::int, '00001', 'TEST1')
        ) 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, 'USD', NULL,
                       NULL, currval('batch_id_seq')::int, '00001', 'TEST1')
      ) 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, 'USD', NULL,
                        NULL, NULL, '00001', 'TEST1')
      ) 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, 'USD', NULL,
                         NULL, currval('batch_id_seq')::int, '00001', 'TEST1')
       ) 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, 'USD', NULL,
                    NULL, currval('batch_id_seq')::int, '00001', 'TEST1');
    SELECT results_eq('test', ARRAY[true], 'Locked Invoice not in total');
    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,
                           transdate, source)
                   values (currval('id')::int,
                           (select id from account where accno = '00003'),
                           -100, true, '1', now(), '_test_src1'),
                          (currval('id')::int,
                           (select id from account where accno = '00001'),
                           -100, true, '-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, netamount, curr, transdate)
            values ('test_reverse', -101, true, '1', '1', 'USD', now()::date);

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


    PREPARE test AS
    SELECT amount::int, due::int
      FROM payment_get_open_invoice(
             1, -101, 'USD', null, null, null, null, 'test_reverse');
    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, 'USD', 'notes', 'description',
                        array[(select id from account where accno = '00003')],
                        array[1]::numeric[], array[true]::bool[],
                        array['pmt-rev-src1']::text[], NULL,
                        array[currval('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, 'USD', null, null, null, null, 'test_reverse');
    SELECT results_eq('test', array[true],
                      'After payment, the invoice is paid');
    DEALLOCATE test;

   SELECT payment__reverse('pmt-rev-src1', now()::date, -101, '00003',
                           now()::date, 1, null, null, null, null);

   PREPARE test AS
   SELECT amount::int, due::int
     FROM payment_get_open_invoice(
            1, -101, 'USD', null, null, null, null, 'test_reverse');
   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;


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

ROLLBACK;
