Dumped on 2023-10-03

Index of database - ledgersmb


Table: ac_tax_form

Mapping journal_line to country_tax_form for reporting purposes.

ac_tax_form Structure
F-Key Name Type Description
acc_trans.entry_id entry_id integer PRIMARY KEY
reportable boolean

Index - Schema public


Table: acc_trans

This table stores line items for financial transactions. Please note that payments in 1.3 are not full-fledged transactions.

acc_trans Structure
F-Key Name Type Description
transactions.id trans_id integer NOT NULL
account.id chart_id integer NOT NULL
transdate date DEFAULT CURRENT_DATE
source text

Document Source identifier for individual line items, usually used for payments.
cleared boolean DEFAULT false
memo text
invoice.id invoice_id integer
approved boolean NOT NULL DEFAULT true
voucher.id voucher_id integer
entry_id serial PRIMARY KEY
amount_bc numeric NOT NULL
amount_tc numeric NOT NULL
currency.curr curr character(3) NOT NULL

 

acc_trans Constraints
Name Constraint
transdate_nullity CHECK (((NOT approved) OR (transdate IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: account

This table stores the main account info.

account Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
accno text PRIMARY KEY
description text
is_temp boolean NOT NULL DEFAULT false

Only affects equity accounts. If set, close at end of year.
category character(1) NOT NULL

A=asset,L=liability,Q=Equity,I=Income,E=expense
gifi.accno gifi_accno text
account_heading.id heading integer NOT NULL
contra boolean NOT NULL DEFAULT false
tax boolean NOT NULL DEFAULT false
obsolete boolean NOT NULL DEFAULT false
account_heading.id heading_negative_balance integer

Indicates the header for reclassification of negative current asset/liability amounts.

 

account Constraints
Name Constraint
account_category_check CHECK ((category = ANY (ARRAY['A'::bpchar, 'L'::bpchar, 'Q'::bpchar, 'I'::bpchar, 'E'::bpchar])))
account_heading_neg_balance CHECK (((category = ANY (ARRAY['A'::bpchar, 'L'::bpchar])) OR (heading_negative_balance IS NULL)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: account_checkpoint

This table holds account balances at various dates. Transactions MUST NOT be posted prior to the latest end_date in this table, and no unapproved transactions (vouchers or drafts) can remain in the closed period.

account_checkpoint Structure
F-Key Name Type Description
end_date date PRIMARY KEY
account.id account_id integer PRIMARY KEY
id serial UNIQUE NOT NULL
debits numeric
credits numeric
amount_bc numeric NOT NULL
amount_tc numeric NOT NULL
currency.curr curr character(3) PRIMARY KEY

Index - Schema public


Table: account_heading

This table holds the account headings in the system. Each account must belong to a heading, and a heading can belong to another heading. In this way it is possible to nest accounts for reporting purposes.

account_heading Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
accno text PRIMARY KEY
account_heading.id parent_id integer
description text
category character(1)

Same as the column account.category, except that if NULL the category is automatically derived from the linked accounts.

 

account_heading Constraints
Name Constraint
account_heading_category_check CHECK ((category = ANY (ARRAY['A'::bpchar, 'L'::bpchar, 'Q'::bpchar, 'I'::bpchar, 'E'::bpchar])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: account_heading_derived_category

Lists for each row the derived category for each heading, based on the categories of the linked accounts.

account_heading_derived_category Structure
F-Key Name Type Description
id integer
accno text
description text
parent_id integer
original_category character(1)
asset_count bigint
liability_count bigint
expense_count bigint
income_count bigint
equity_count bigint
derived_category text
category bpchar
SELECT derivation.id
,
    derivation.accno
,
    derivation.description
,
    derivation.parent_id
,
    derivation.original_category
,
    derivation.asset_count
,
    derivation.liability_count
,
    derivation.expense_count
,
    derivation.income_count
,
    derivation.equity_count
,
    derivation.derived_category
,
    COALESCE
(derivation.original_category
     , (derivation.derived_category)::bpchar
) AS category
   
FROM (
SELECT category_counts.id
     ,
            category_counts.accno
     ,
            category_counts.description
     ,
            category_counts.parent_id
     ,
            category_counts.original_category
     ,
            category_counts.asset_count
     ,
            category_counts.liability_count
     ,
            category_counts.expense_count
     ,
            category_counts.income_count
     ,
            category_counts.equity_count
     ,
                CASE
                    WHEN 
     (category_counts.equity_count > 0) THEN 'Q'::text
                    WHEN 
     (
           (category_counts.income_count > 0)
         AND (category_counts.expense_count > 0)
     ) THEN 'Q'::text
                    WHEN 
     (
           (category_counts.asset_count > 0)
         AND (category_counts.liability_count > 0)
     ) THEN 'Q'::text
                    WHEN 
     (category_counts.asset_count > 0) THEN 'A'::text
                    WHEN 
     (category_counts.liability_count > 0) THEN 'L'::text
                    WHEN 
     (category_counts.expense_count > 0) THEN 'E'::text
                    WHEN 
     (category_counts.income_count > 0) THEN 'I'::text
                    ELSE NULL::text
                END AS derived_category
           
  FROM (
      SELECT ah.id
           ,
                    ah.accno
           ,
                    ah.description
           ,
                    ah.parent_id
           ,
                    ah.category AS original_category
           ,
                    count
           (
                        CASE
                            WHEN 
                 (acc.category = 'A'::bpchar) THEN acc.category
                            ELSE NULL::bpchar
                        END
           ) AS asset_count
           ,
                    count
           (
                        CASE
                            WHEN 
                 (acc.category = 'L'::bpchar) THEN acc.category
                            ELSE NULL::bpchar
                        END
           ) AS liability_count
           ,
                    count
           (
                        CASE
                            WHEN 
                 (acc.category = 'E'::bpchar) THEN acc.category
                            ELSE NULL::bpchar
                        END
           ) AS expense_count
           ,
                    count
           (
                        CASE
                            WHEN 
                 (acc.category = 'I'::bpchar) THEN acc.category
                            ELSE NULL::bpchar
                        END
           ) AS income_count
           ,
                    count
           (
                        CASE
                            WHEN 
                 (acc.category = 'Q'::bpchar) THEN acc.category
                            ELSE NULL::bpchar
                        END
           ) AS equity_count
                   
        FROM (
                 (account_heading_descendant ahd
                     
                    JOIN account_heading ah 
                      ON (
                             (ahd.id = ah.id)
                       )
                 )
                     
         LEFT JOIN account acc 
                ON (
                       (ahd.descendant_id = acc.heading)
                 )
           )
                  
    GROUP BY ah.id
           , ah.accno
           , ah.description
           , ah.parent_id
           , ah.category
     ) category_counts
) derivation;

Index - Schema public


View: account_heading_descendant

Returns rows for each heading listing its immediate children, children of children, etc., etc. This is primarily practical when calculating subtotals for PNL and B/S headings.

account_heading_descendant Structure
F-Key Name Type Description
id integer
level integer
descendant_id integer
accno text
descendant_accno text
 WITH RECURSIVE account_headings AS 
(
         
SELECT account_heading.id
     ,
            1 AS level
     ,
            account_heading.id AS descendant_id
     ,
            account_heading.accno
     ,
            account_heading.accno AS descendant_accno
           
  FROM account_heading
        
UNION ALL
         
SELECT at.id
     ,
            
     (at.level + 1) AS level
     ,
            ah.id AS descendant_id
     ,
            at.accno
     ,
            ah.accno AS descendant_accno
           
  FROM (account_heading ah
             
        JOIN account_headings at 
          ON (
                 (ah.parent_id = at.descendant_id)
           )
     )
        
)
 
SELECT account_headings.id
,
    account_headings.level
,
    account_headings.descendant_id
,
    account_headings.accno
,
    account_headings.descendant_accno
   
FROM account_headings;

Index - Schema public


Table: account_heading_translation

Translations for account heading descriptions.

account_heading_translation Structure
F-Key Name Type Description
account_heading.id account_heading.id trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Table account_heading_translation Inherits translation,

Index - Schema public


View: account_heading_tree

Returns in the 'path' field an array which contains the path of the heading to its associated root.

account_heading_tree Structure
F-Key Name Type Description
id integer
accno text
description text
level integer
path integer[]
 WITH RECURSIVE account_headings AS 
(
         
SELECT account_heading.id
     ,
            account_heading.accno
     ,
            account_heading.description
     ,
            1 AS level
     ,
            ARRAY[account_heading.id] AS path
           
  FROM account_heading
          
 WHERE (account_heading.parent_id IS NULL)
        
UNION ALL
         
SELECT ah.id
     ,
            ah.accno
     ,
            ah.description
     ,
            
     (at.level + 1) AS level
     ,
            array_append
     (at.path
           , ah.id
     ) AS path
           
  FROM (account_heading ah
             
        JOIN account_headings at 
          ON (
                 (ah.parent_id = at.id)
           )
     )
        
)
 
SELECT account_headings.id
,
    account_headings.accno
,
    account_headings.description
,
    account_headings.level
,
    account_headings.path
   
FROM account_headings;

Index - Schema public


Table: account_link

account_link Structure
F-Key Name Type Description
account.id account_id integer PRIMARY KEY
account_link_description.description description text PRIMARY KEY

Index - Schema public


Table: account_link_description

This is a lookup table which provide basic information as to categories and dropdowns of accounts. In general summary accounts cannot belong to more than one category (an AR summary account cannot appear in other dropdowns for example).

account_link_description Structure
F-Key Name Type Description
description text PRIMARY KEY
summary boolean NOT NULL
custom boolean NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: account_translation

Translations for account descriptions.

account_translation Structure
F-Key Name Type Description
account.id account.id trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Table account_translation Inherits translation,

Index - Schema public


Table: ap

Summary/header information for AP transactions and vendor invoices. Note that some constraints here are hard to enforce because we haven not gotten to rewriting the relevant code here. HV TODO drop entity_id

ap Structure
F-Key Name Type Description
transactions.id id integer PRIMARY KEY DEFAULT nextval('id'::regclass)
invnumber text

Text identifier for the invoice. Must be unique.
transdate date DEFAULT CURRENT_DATE
taxincluded boolean DEFAULT false
duedate date
invoice boolean DEFAULT false

True if the transaction tracks goods/services purchase using the invoice table. False otherwise.
ordnumber text

Order Number
currency.curr curr character(3)

3 letters to identify the currency.
notes text

These notes are displayed on the invoice when printed or emailed
entity_employee.entity_id person_id integer

Person who created the transaction
quonumber text

Quotation Number
intnotes text

These notes are not displayed when the invoice is printed or emailed and may be updated without reposting hte invocie.
shipvia text
language_code character varying(6)
ponumber text

Purchase Order Number
shippingpoint text
on_hold boolean DEFAULT false
approved boolean NOT NULL DEFAULT true

Only show in financial reports if true.
reverse boolean DEFAULT false

If true numbers are displayed after multiplying by -1
terms smallint
description text
force_closed boolean

Not exposed to the UI, but can be set to prevent an invoice from showing up for payment or in outstanding reports.
crdate date
is_return boolean DEFAULT false
entity_credit_account.id entity_credit_account integer NOT NULL

reference for the vendor account used.
amount_bc numeric

This stores the total amount (including taxes) for the transaction in base currency.
amount_tc numeric
netamount_bc numeric

Total amount excluding taxes for the transaction in base currency.
netamount_tc numeric

 

ap Constraints
Name Constraint
ap_check CHECK ((((amount_bc IS NULL) AND (curr IS NULL)) OR ((amount_bc IS NOT NULL) AND (curr IS NOT NULL))))
transdate_nullity CHECK (((NOT approved) OR (transdate IS NOT NULL)))

Index - Schema public


Table: ar

Summary/header information for AR transactions and sales invoices. Note that some constraints here are hard to enforce because we haven not gotten to rewriting the relevant code here. HV TODO drop entity_id

ar Structure
F-Key Name Type Description
transactions.id id integer PRIMARY KEY DEFAULT nextval('id'::regclass)
invnumber text

Text identifier for the invoice. Must be unique.
transdate date DEFAULT CURRENT_DATE
taxincluded boolean
duedate date
invoice boolean DEFAULT false

True if the transaction tracks goods/services purchase using the invoice table. False otherwise.
shippingpoint text
terms smallint
notes text

These notes are displayed on the invoice when printed or emailed
currency.curr curr character(3)

3 letters to identify the currency.
ordnumber text

Order Number
entity_employee.entity_id person_id integer

Person who created the transaction
quonumber text

Quotation Number
intnotes text

These notes are not displayed when the invoice is printed or emailed and may be updated without reposting hte invocie.
shipvia text
language_code character varying(6)
ponumber text

Purchase Order Number
on_hold boolean DEFAULT false
reverse boolean DEFAULT false

If true numbers are displayed after multiplying by -1
approved boolean NOT NULL DEFAULT true

Only show in financial reports if true.
entity_credit_account.id entity_credit_account integer NOT NULL

reference for the customer account used.
force_closed boolean

Not exposed to the UI, but can be set to prevent an invoice from showing up for payment or in outstanding reports.
description text
is_return boolean DEFAULT false
crdate date
setting_sequence text
amount_bc numeric

This stores the total amount (including taxes) for the transaction in base currency.
amount_tc numeric
netamount_bc numeric

Total amount excluding taxes for the transaction in base currency.
netamount_tc numeric

 

ar Constraints
Name Constraint
ar_check CHECK ((((amount_bc IS NULL) AND (curr IS NULL)) OR ((amount_bc IS NOT NULL) AND (curr IS NOT NULL))))
ar_check1 CHECK (((invnumber IS NOT NULL) OR (NOT approved)))
ar_check2 CHECK (((invnumber IS NOT NULL) OR (NOT approved)))
transdate_nullity CHECK (((NOT approved) OR (transdate IS NOT NULL)))

Index - Schema public


Table: assembly

Holds mapping for parts that are members of assemblies.

assembly Structure
F-Key Name Type Description
parts.id id integer PRIMARY KEY

This is the id of the assembly the part is being mapped to.
parts.id parts_id integer PRIMARY KEY

ID of part that is a member of the assembly.
qty numeric
bom boolean
adj boolean

Index - Schema public


Table: asset_class

The account fields here set the defaults for the individual asset items. They are non-authoritative.

asset_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text PRIMARY KEY
account.id asset_account_id integer
account.id dep_account_id integer
asset_dep_method.id method integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_dep_method

Stores asset depreciation methods, and their relevant stored procedures. The fixed asset system is such depreciation methods can be plugged in via this table.

asset_dep_method Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
method text PRIMARY KEY

These are keyed to specific stored procedures. Currently only "straight_line" is supported
sproc text UNIQUE NOT NULL

The sproc mentioned here is a stored procedure which must have the following arguments: (in_asset_ids int[], in_report_date date, in_report_id int). Here in_asset_ids are the assets to be depreciated, in_report_date is the date of the report, and in_report_id is the id of the report. The sproc MUST insert the relevant lines into asset_report_line.
unit_label text NOT NULL
short_name text UNIQUE NOT NULL
asset_unit_class.id unit_class integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_disposal_method

asset_disposal_method Structure
F-Key Name Type Description
label text PRIMARY KEY
id serial UNIQUE NOT NULL
multiple integer
short_label character(1)

 

asset_disposal_method Constraints
Name Constraint
asset_disposal_method_multiple_check CHECK ((multiple = ANY (ARRAY[1, 0, '-1'::integer])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_item

Stores details of asset items. The account fields here are authoritative, while the ones in the asset_class table are defaults.

asset_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
description text
tag text UNIQUE#1 NOT NULL

This can be plugged into other routines to generate it automatically via ALTER TABLE .... SET DEFAULT.....
purchase_value numeric NOT NULL
salvage_value numeric NOT NULL
usable_life numeric NOT NULL
purchase_date date NOT NULL
start_depreciation date NOT NULL
warehouse.id location_id integer
business_unit.id department_id integer
eca_invoice.journal_id invoice_id integer
account.id asset_account_id integer
account.id dep_account_id integer
account.id exp_account_id integer
asset_item.id obsolete_by integer UNIQUE#1
asset_class.id asset_class_id integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_note

asset_note Structure
F-Key Name Type Description
id integer NOT NULL DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL DEFAULT 4
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
asset_item.id ref_key integer NOT NULL
subject text

Table asset_note Inherits note,

 

asset_note Constraints
Name Constraint
asset_note_note_class_check CHECK ((note_class = 4))

Index - Schema public


Table: asset_report

Asset reports are discrete sets of depreciation or disposal transctions, and each one may be turned into no more than one GL transaction.

asset_report Structure
F-Key Name Type Description
id serial PRIMARY KEY
report_date date
gl.id gl_id bigint UNIQUE
asset_class.id asset_class bigint
asset_report_class.id report_class integer
entity.id entered_by bigint NOT NULL DEFAULT person__get_my_entity_id()
entity.id approved_by bigint
entered_at timestamp without time zone DEFAULT now()
approved_at timestamp without time zone
depreciated_qty numeric
dont_approve boolean DEFAULT false
submitted boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_report_class

By default only four types of asset reports are supported. In the future others may be added. Please correspond on the list before adding more types.

asset_report_class Structure
F-Key Name Type Description
id integer UNIQUE NOT NULL
class text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: asset_report_line

asset_report_line Structure
F-Key Name Type Description
asset_item.id asset_id bigint PRIMARY KEY
asset_report.id report_id bigint PRIMARY KEY
amount numeric
business_unit.id department_id integer

In case assets are moved between departments, we have to store this here.
warehouse.id warehouse_id integer

Index - Schema public


Table: asset_rl_to_disposal_method

Maps disposal method to line items in the asset disposal report.

asset_rl_to_disposal_method Structure
F-Key Name Type Description
asset_report.id report_id integer PRIMARY KEY
asset_item.id asset_id integer PRIMARY KEY
asset_disposal_method.id disposal_method_id integer PRIMARY KEY
percent_disposed numeric

Index - Schema public


Table: asset_unit_class

asset_unit_class Structure
F-Key Name Type Description
id integer UNIQUE NOT NULL
class text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: audittrail

This stores information on who entered or updated rows in the ar, ap, or gl tables.

audittrail Structure
F-Key Name Type Description
trans_id integer
tablename text
reference text
formname text
action text
transdate timestamp without time zone DEFAULT CURRENT_TIMESTAMP
entity.id person_id integer
entry_id bigserial PRIMARY KEY
rolname text NOT NULL DEFAULT SESSION_USER

Index - Schema public


Table: batch

Stores batch header info. Batches are groups of vouchers that are posted together.

batch Structure
F-Key Name Type Description
id serial PRIMARY KEY
batch_class.id batch_class_id integer NOT NULL

Note that this field is largely used for sorting the vouchers. A given batch is NOT restricted to this type.
control_code text NOT NULL
description text
default_date date NOT NULL
approved_on date
entity_employee.entity_id approved_by integer
entity_employee.entity_id created_by integer
session.session_id locked_by integer
created_on date DEFAULT now()

 

batch Constraints
Name Constraint
batch_control_code_check CHECK ((length(control_code) > 0))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: batch_class

These values are hard-coded. Please coordinate before adding standard values. Values from 900 to 999 are reserved for local use.

batch_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
class character varying PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: bu_class_to_module

bu_class_to_module Structure
F-Key Name Type Description
business_unit_class.id bu_class_id integer PRIMARY KEY
lsmb_module.id module_id integer PRIMARY KEY

Index - Schema public


Table: budget_info

budget_info Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
start_date date NOT NULL
end_date date NOT NULL
reference text PRIMARY KEY
description text NOT NULL
entity.id entered_by integer NOT NULL DEFAULT person__get_my_entity_id()
entity.id approved_by integer
entity.id obsolete_by integer
entered_at timestamp without time zone NOT NULL DEFAULT now()
approved_at timestamp without time zone
obsolete_at timestamp without time zone

 

budget_info Constraints
Name Constraint
budget_info_check CHECK ((start_date < end_date))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: budget_line

budget_line Structure
F-Key Name Type Description
budget_info.id budget_id integer PRIMARY KEY
account.id account_id integer PRIMARY KEY
description text
amount numeric NOT NULL
amount_tc numeric NOT NULL
currency.curr curr character(3) NOT NULL

Index - Schema public


Table: budget_note

budget_note Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL DEFAULT 6
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
budget_info.id ref_key integer NOT NULL
subject text

Table budget_note Inherits note,

 

budget_note Constraints
Name Constraint
budget_note_note_class_check CHECK ((note_class = 6))

Index - Schema public


Table: budget_to_business_unit

budget_to_business_unit Structure
F-Key Name Type Description
budget_info.id budget_id integer UNIQUE PRIMARY KEY
business_unit.id bu_id integer NOT NULL
business_unit_class.id bu_class integer PRIMARY KEY

Index - Schema public


Table: business

Groups of Customers assigned joint discounts.

business Structure
F-Key Name Type Description
id serial PRIMARY KEY
description text
discount numeric
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: business_unit

Tracks Projects, Departments, Funds, Etc.

business_unit Structure
F-Key Name Type Description
id serial UNIQUE#1 PRIMARY KEY
business_unit_class.id class_id integer UNIQUE#2 UNIQUE#1 NOT NULL
control_code text UNIQUE#2
description text
start_date date
end_date date
business_unit.id parent_id integer
entity_credit_account.id credit_id integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: business_unit_ac

business_unit_ac Structure
F-Key Name Type Description
acc_trans.entry_id entry_id integer PRIMARY KEY
business_unit.class_id#1 business_unit_class.id class_id integer PRIMARY KEY
business_unit.id#1 bu_id integer NOT NULL

Index - Schema public


Table: business_unit_class

Consolidates projects and departments, and allows this to be extended for funds accounting and other purposes.

business_unit_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text PRIMARY KEY
active boolean NOT NULL DEFAULT false
ordering integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: business_unit_inv

business_unit_inv Structure
F-Key Name Type Description
invoice.id entry_id integer PRIMARY KEY
business_unit.class_id#1 business_unit_class.id class_id integer PRIMARY KEY
business_unit.id#1 bu_id integer NOT NULL

Index - Schema public


Table: business_unit_jl

business_unit_jl Structure
F-Key Name Type Description
journal_line.id entry_id integer PRIMARY KEY
business_unit_class.id bu_class integer PRIMARY KEY
business_unit.id bu_id integer NOT NULL

Index - Schema public


Table: business_unit_oitem

business_unit_oitem Structure
F-Key Name Type Description
orderitems.id entry_id integer PRIMARY KEY
business_unit.class_id#1 business_unit_class.id class_id integer PRIMARY KEY
business_unit.id#1 bu_id integer NOT NULL

Index - Schema public


Table: business_unit_translation

Translation information for projects, departments, etc.

business_unit_translation Structure
F-Key Name Type Description
business_unit.id trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Table business_unit_translation Inherits translation,

Index - Schema public


View: cash_impact

This view is used by cash basis reports to determine the fraction of a transaction to be counted.

cash_impact Structure
F-Key Name Type Description
id integer
portion numeric
rel text
transdate date
SELECT gl.id
,
    '1'::numeric AS portion
,
    'gl'::text AS rel
,
    gl.transdate
   
FROM gl

UNION ALL
 
SELECT gl.id
,
        CASE
            WHEN 
(gl.amount_bc = 
     (0)::numeric
) THEN 
(0)::numeric
            WHEN 
(gl.transdate = ac.transdate) THEN 
(
     (1)::numeric + 
     (sum
           (ac.amount_bc) / gl.amount_bc
     )
)
            ELSE 
(
     (1)::numeric - 
     (
           (gl.amount_bc - sum
                 (ac.amount_bc)
           ) / gl.amount_bc
     )
)
        END AS portion
,
    'ar'::text AS rel
,
    ac.transdate
   
FROM (
     (ar gl
     
        JOIN acc_trans ac 
          ON (
                 (ac.trans_id = gl.id)
           )
     )
     
  JOIN account_link al 
    ON (
           (
                 (ac.chart_id = al.account_id)
               AND (al.description = 'AR'::text)
           )
     )
)
  
GROUP BY gl.id
, gl.amount_bc
, ac.transdate
, gl.transdate

UNION ALL
 
SELECT gl.id
,
        CASE
            WHEN 
(gl.amount_bc = 
     (0)::numeric
) THEN 
(0)::numeric
            WHEN 
(gl.transdate = ac.transdate) THEN 
(
     (1)::numeric - 
     (sum
           (ac.amount_bc) / gl.amount_bc
     )
)
            ELSE 
(
     (1)::numeric - 
     (
           (gl.amount_bc + sum
                 (ac.amount_bc)
           ) / gl.amount_bc
     )
)
        END AS portion
,
    'ap'::text AS rel
,
    ac.transdate
   
FROM (
     (ap gl
     
        JOIN acc_trans ac 
          ON (
                 (ac.trans_id = gl.id)
           )
     )
     
  JOIN account_link al 
    ON (
           (
                 (ac.chart_id = al.account_id)
               AND (al.description = 'AP'::text)
           )
     )
)
  
GROUP BY gl.id
, gl.amount_bc
, ac.transdate
, gl.transdate;

Index - Schema public


Table: company

company Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
entity.id entity_id integer PRIMARY KEY
legal_name text PRIMARY KEY
tax_id text

In the US this would be a EIN.
sales_tax_id text
license_number text
sic.code sic_code character varying
created date NOT NULL DEFAULT CURRENT_DATE

 

company Constraints
Name Constraint
company_legal_name_check CHECK ((legal_name ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: contact_class

Stores type of contact information attached to companies and persons. Please coordinate with others before adding new types.

contact_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
class text PRIMARY KEY

 

contact_class Constraints
Name Constraint
contact_class_class_check CHECK ((class ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: country

country Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
short_name text NOT NULL
itu text

The ITU Telecommunication Standardization Sector code for calling internationally. For example, the US is 1, Great Britain is 44
last_updated timestamp without time zone NOT NULL DEFAULT now()

 

country Constraints
Name Constraint
country_name_check CHECK ((name ~ '[[:alnum:]_]'::text))
country_short_name_check CHECK ((short_name ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: country_tax_form

This table was designed for holding information relating to reportable sales or purchases, such as IRS 1099 forms and international equivalents.

country_tax_form Structure
F-Key Name Type Description
country.id country_id integer PRIMARY KEY
form_name text PRIMARY KEY
id serial UNIQUE NOT NULL
default_reportable boolean NOT NULL DEFAULT false
is_accrual boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: cr_coa_to_account

Provides name mapping for the cash reconciliation screen.

cr_coa_to_account Structure
F-Key Name Type Description
account.id chart_id integer PRIMARY KEY
account text NOT NULL

Index - Schema public


Table: cr_report

This table holds header data for cash reports.

cr_report Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
account.id chart_id integer NOT NULL
their_total numeric NOT NULL
approved boolean NOT NULL DEFAULT false
submitted boolean NOT NULL DEFAULT false
end_date date NOT NULL DEFAULT now()
updated timestamp without time zone NOT NULL DEFAULT now()
entity.id entered_by integer NOT NULL DEFAULT person__get_my_entity_id()
entered_username text NOT NULL DEFAULT SESSION_USER
deleted boolean NOT NULL DEFAULT false
entity.id deleted_by integer
entity.id approved_by integer
approved_username text
recon_fx boolean DEFAULT false

 

cr_report Constraints
Name Constraint
cr_report_approved_submitted_check CHECK ((submitted OR (NOT approved)))
cr_report_check CHECK (((deleted IS NOT TRUE) OR (approved IS NOT TRUE)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: cr_report_line

This stores line item data on transaction lines and whether they are cleared.

cr_report_line Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
cr_report.id report_id integer NOT NULL
scn text

This is the check number. Maps to gl.reference
their_balance numeric
our_balance numeric
entity.id user integer NOT NULL
clear_time date
insert_time timestamp with time zone NOT NULL DEFAULT now()
trans_type text
post_date date
cleared boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: cr_report_line_links

This table expresses the explicit relationship between the lines on the reconciliation report and the lines in acc_trans which constitute the ledger lines aggregated into the reconciliation line.

cr_report_line_links Structure
F-Key Name Type Description
cr_report_line.id report_line_id integer PRIMARY KEY
acc_trans.entry_id entry_id integer PRIMARY KEY
unique_exempt boolean NOT NULL DEFAULT false

Excludes the current row from check of acc_trans lines being included in exactly one reconciliation. The only known reason for this value to be 'true' is data that originated outside the current reconcliiation system. Either before 1.8 or by data migration.
cleared boolean NOT NULL DEFAULT false

Indicates that the associated acc_trans line is (going to be) marked as cleared. This prevents the line from being included in other reconciliations which are either submitted or approved. The value is maintained by triggers on the 'cr_report' and 'cr_report_line' tables. It is defined as 'cr_report.submitted and cr_report_line.cleared'. An INSERT trigger on the 'cr_report_line_links' table ensures the value to be correct when creating new records.

Index - Schema public


Table: currency

This table holds the list of currencies available for posting in the system; it mostly serves as the canonical definition of currency codes.

currency Structure
F-Key Name Type Description
curr character(3) PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: defaults

This is a free-form table for managing application settings per company database. We use key-value modelling here because this most accurately maps the actual semantics of the data.

defaults Structure
F-Key Name Type Description
setting_key text PRIMARY KEY
value text

 

defaults Constraints
Name Constraint
defaults_password_duration_check CHECK (((setting_key <> 'password_duration'::text) OR (value IS NULL) OR (value = ''::text) OR ((value ~ '^([0-9]+[.]?[0-9]*|[.][0-9]+)$'::text) AND ((value)::numeric > (0)::numeric) AND ((value)::numeric < (3654)::numeric))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: eca_invoice

Replaces the rest of the ar and ap tables. Also tracks payments and receipts.

eca_invoice Structure
F-Key Name Type Description
order_id integer

Link to order it was created from
journal_entry.id journal_id integer PRIMARY KEY
on_hold boolean DEFAULT false

On hold invoices can not be paid, and overpayments that are on hold cannot be used to pay invoices.
reverse boolean DEFAULT false

When this is set to true, the invoice is shown with opposite normal numbers, i.e. negatives appear as positives, and positives appear as negatives.
entity_credit_account.id credit_id integer NOT NULL
due date NOT NULL
language.code language_code character(6)
force_closed boolean NOT NULL DEFAULT false

When this is set to true, the invoice does not show up on outstanding reports and cannot be paid. Overpayments where this is set to true do not appear on outstanding reports and cannot be paid.
order_number text

This is the order number of the other party. So for a sales invoice, this would be a purchase order, and for a vendor invoice, this would be a sales order.

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: eca_note

Notes for entity_credit_account entries.

eca_note Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
entity_credit_account.id ref_key integer NOT NULL

references entity_credit_account.id
subject text

Table eca_note Inherits note,

 

eca_note Constraints
Name Constraint
eca_note_note_class_check CHECK ((note_class = 3))

Index - Schema public


Table: eca_tax

Mapping customers and vendors to taxes.

eca_tax Structure
F-Key Name Type Description
entity_credit_account.id eca_id integer PRIMARY KEY
account.id chart_id integer PRIMARY KEY

Index - Schema public


Table: eca_to_contact

To keep track of the relationship between multiple contact methods and a single vendor or customer account. For generic contacts, use entity_to_contact instead.

eca_to_contact Structure
F-Key Name Type Description
entity_credit_account.id credit_id integer PRIMARY KEY
contact_class.id contact_class_id integer PRIMARY KEY
contact text PRIMARY KEY
description text

 

eca_to_contact Constraints
Name Constraint
eca_to_contact_contact_check CHECK ((contact ~ '[[:alnum:]_]'::text))

Index - Schema public


Table: eca_to_location

This table is used for locations bound to contracts. For generic contact addresses, use entity_to_location instead

eca_to_location Structure
F-Key Name Type Description
location.id location_id integer PRIMARY KEY
location_class.id location_class integer PRIMARY KEY
entity_credit_account.id credit_id integer PRIMARY KEY

Index - Schema public


Table: email

email Structure
F-Key Name Type Description
workflow.workflow_id workflow_id integer PRIMARY KEY
from text
to text
cc text
bcc text
notify boolean DEFAULT false
subject text
body text
sent_date date
expansions jsonb

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: employee_class

employee_class Structure
F-Key Name Type Description
label text PRIMARY KEY
id serial UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: employee_search

employee_search Structure
F-Key Name Type Description
entity_id integer
startdate date
enddate date
role character varying(20)
ssn text
sales boolean
manager_id integer
employeenumber character varying(32)
dob date
is_manager boolean
manager text
note text
name text
SELECT e.entity_id
,
    e.startdate
,
    e.enddate
,
    e.role
,
    e.ssn
,
    e.sales
,
    e.manager_id
,
    e.employeenumber
,
    e.dob
,
    e.is_manager
,
    em.name AS manager
,
    emn.note
,
    en.name
   
FROM (
     (
           (
                 (entity_employee e
     
               LEFT JOIN entity en 
                      ON (
                             (e.entity_id = en.id)
                       )
                 )
     
         LEFT JOIN entity_employee m 
                ON (
                       (e.manager_id = m.entity_id)
                 )
           )
     
   LEFT JOIN entity em 
          ON (
                 (em.id = m.entity_id)
           )
     )
     
LEFT JOIN entity_note emn 
    ON (
           (emn.ref_key = em.id)
     )
);

Index - Schema public


Table: employee_to_ec

employee_to_ec Structure
F-Key Name Type Description
entity_employee.entity_id employee_id integer PRIMARY KEY
employee_class.id ec_id integer

Index - Schema public


View: employees

employees Structure
F-Key Name Type Description
salutation text
first_name text
last_name text
entity_id integer
startdate date
enddate date
role character varying(20)
ssn text
sales boolean
manager_id integer
employeenumber character varying(32)
dob date
is_manager boolean
SELECT s.salutation
,
    p.first_name
,
    p.last_name
,
    ee.entity_id
,
    ee.startdate
,
    ee.enddate
,
    ee.role
,
    ee.ssn
,
    ee.sales
,
    ee.manager_id
,
    ee.employeenumber
,
    ee.dob
,
    ee.is_manager
   
FROM (
     (person p
     
        JOIN entity_employee ee 
       USING (entity_id)
     )
     
LEFT JOIN salutation s 
    ON (
           (p.salutation_id = s.id)
     )
);

Index - Schema public


Table: entity

The primary entity table to map to all contacts

entity Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text

This is the common name of an entity. If it was a person it may be Joshua Drake, a company Acme Corp. You may also choose to use a domain such as commandprompt.com
entity_class.id entity_class integer NOT NULL
created date NOT NULL DEFAULT CURRENT_DATE
control_code text UNIQUE NOT NULL DEFAULT setting_increment('entity_control'::character varying)
country.id country_id integer NOT NULL

 

entity Constraints
Name Constraint
entity_name_check CHECK ((name ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: entity_bank_account

This stores bank account information for both companies and persons.

entity_bank_account Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
entity.id entity_id integer PRIMARY KEY
bic character varying PRIMARY KEY

Banking Institution Code, such as routing number of SWIFT code.
iban character varying PRIMARY KEY

International Bank Account Number. used to store the actual account number for the banking institution.
remark character varying

This field contains the notes for an account, like: This is USD account, this one is HUF account, this one is the default account, this account for paying specific taxes. If a $

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: entity_class

Defines the class type such as vendor, customer, contact, employee

entity_class Structure
F-Key Name Type Description
id serial PRIMARY KEY

The first 7 values are reserved and permanent. Individuals who create new classes, however, should coordinate with others for ranges to use.
class text NOT NULL
active boolean NOT NULL DEFAULT true

 

entity_class Constraints
Name Constraint
entity_class_class_check CHECK ((class ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: entity_credit_account

This table stores information relating to general relationships regarding moneys owed on invoice. Invoices, whether AR or AP, must be attached to a record in this table.

entity_credit_account Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
entity.id entity_id integer PRIMARY KEY
entity_class.id entity_class integer PRIMARY KEY
pay_to_name text
discount numeric
description text
discount_terms integer
account.id discount_account_id integer
taxincluded boolean DEFAULT false
creditlimit numeric
terms smallint
meta_number character varying(32) PRIMARY KEY

This stores the human readable control code for the customer/vendor record. This is typically called the customer/vendor "account" in the application.
business.id business_id integer
language.code language_code character varying(6) DEFAULT 'en'::character varying
pricegroup.id pricegroup_id integer
currency.curr curr character(3)
startdate date DEFAULT CURRENT_DATE
enddate date
threshold numeric
entity_employee.entity_id employee_id integer
person.id primary_contact integer
account.id ar_ap_account_id integer
account.id cash_account_id integer
entity_bank_account.id bank_account integer
country_tax_form.id taxform_id integer

 

entity_credit_account Constraints
Name Constraint
entity_credit_account_check CHECK (((ar_ap_account_id IS NOT NULL) OR (entity_id = 0)))
entity_credit_account_entity_class_check CHECK ((entity_class = ANY (ARRAY[1, 2])))
entity_credit_account_req_curr CHECK (((entity_class = ANY (ARRAY[1, 2, 3])) AND (curr IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: entity_employee

This contains employee-specific extensions to person/entity.

entity_employee Structure
F-Key Name Type Description
entity.id entity_id integer PRIMARY KEY
startdate date NOT NULL DEFAULT CURRENT_DATE
enddate date
role character varying(20)
ssn text
sales boolean DEFAULT false
entity.id manager_id integer
employeenumber character varying(32)
dob date
is_manager boolean DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: entity_note

entity_note Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
entity.id ref_key integer NOT NULL
subject text
entity.id entity_id integer

Table entity_note Inherits note,

 

entity_note Constraints
Name Constraint
entity_note_note_class_check CHECK ((note_class = 1))

Index - Schema public


Table: entity_other_name

Similar to company_other_name, a person may be jd, Joshua Drake, linuxpoet... all are the same person. Currently unused in the front-end but will likely be added in future versions.

entity_other_name Structure
F-Key Name Type Description
entity.id entity_id integer PRIMARY KEY
other_name text PRIMARY KEY

 

entity_other_name Constraints
Name Constraint
entity_other_name_other_name_check CHECK ((other_name ~ '[[:alnum:]_]'::text))

Index - Schema public


Table: entity_to_contact

This table stores contact information for entities

entity_to_contact Structure
F-Key Name Type Description
entity.id entity_id integer PRIMARY KEY
contact_class.id contact_class_id integer PRIMARY KEY
contact text PRIMARY KEY
description text

 

entity_to_contact Constraints
Name Constraint
entity_to_contact_contact_check CHECK ((contact ~ '[[:alnum:]_]'::text))

Index - Schema public


Table: entity_to_location

This table is used for locations generic to companies. For contract-bound addresses, use eca_to_location instead

entity_to_location Structure
F-Key Name Type Description
location.id location_id integer PRIMARY KEY
location_class.id location_class integer PRIMARY KEY
entity.id entity_id integer PRIMARY KEY

Index - Schema public


Table: exchangerate_default

This table contains applicable rates for various rate types in the indicated interval [valid_from, valid_to]. ### NOTE: This table needs an INSERT trigger to update any 'valid_to' 'infinity' values to ensure non-overlapping records.

exchangerate_default Structure
F-Key Name Type Description
exchangerate_type.id rate_type integer PRIMARY KEY
currency.curr curr character(3) PRIMARY KEY
valid_from date PRIMARY KEY
valid_to date NOT NULL DEFAULT 'infinity'::date
rate numeric NOT NULL

Index - Schema public


Table: exchangerate_type

This table defines various types of exchange rates which may be used for different purposes (posting, valuation, translation, ...).

exchangerate_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
description text NOT NULL DEFAULT ''::text
builtin boolean NOT NULL DEFAULT false

This column is 't' (true) in case the record is a built-in value (and thus can''t be deleted).

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: file_base

Abstract table, holds no records. Inheriting table store actual file attachment data. Can be queried however to retrieve lists of all files.

file_base Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type.id mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
entity.id uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id serial UNIQUE NOT NULL
ref_key integer PRIMARY KEY

This column inheriting tables is used to reference the database row for the attachment. Inheriting tables MUST set the foreign key here appropriately. This can also be used to create classifications of other documents, such as by source of automatic import (where the file is not yet attached) or even standard, long-lived documents.
file_class.id file_class integer PRIMARY KEY

 

file_base Constraints
Name Constraint
file_base_check CHECK (false) NO INHERIT

Index - Schema public


Table: file_class

File classes are collections of files attached against rows in specific tables in the database. They can be used in the future to implement other form of file attachment.

file_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
class text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: file_eca

File attachments primarily attached to customer and vendor agreements.

file_eca Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
entity_credit_account.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_eca Inherits file_base,

 

file_eca Constraints
Name Constraint
file_eca_file_class_check CHECK ((file_class = 5))

Index - Schema public


Table: file_email

file_email Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
email.workflow_id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_email Inherits file_base,

 

file_email Constraints
Name Constraint
file_email_file_class_check CHECK ((file_class = 8))

Index - Schema public


Table: file_entity

File attachments primarily attached to entities.

file_entity Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
entity.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_entity Inherits file_base,

 

file_entity Constraints
Name Constraint
file_entity_file_class_check CHECK ((file_class = 4))

Index - Schema public


Table: file_incoming

This is essentially a spool for files to be reviewed and attached. It is important that the names are somehow guaranteed to be unique, so one may want to prepend them with an email equivalent or the like.

file_incoming Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
ref_key integer PRIMARY KEY

Always must be 0, and we have no primary key since these files all are for interal incoming use, not categorized.
file_class integer PRIMARY KEY

Table file_incoming Inherits file_base,

 

file_incoming Constraints
Name Constraint
file_incoming_file_class_check CHECK ((file_class = 7))
file_incoming_ref_key_check CHECK ((ref_key = 0))

Index - Schema public


Table: file_internal

This is for internal files used operationally by LedgerSMB. For example, company logos would be here.

file_internal Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
ref_key integer PRIMARY KEY

Always must be 0, and we have no primary key since these files all are for internal use and against the company, not categorized.
file_class integer PRIMARY KEY

Table file_internal Inherits file_base,

 

file_internal Constraints
Name Constraint
file_internal_file_class_check CHECK ((file_class = 6))
file_internal_ref_key_check CHECK ((ref_key = 0))

Index - Schema public


View: file_links

file_links Structure
F-Key Name Type Description
file_id integer
ref_key integer
reference text
type text
dest_class integer
source_class integer
dest_ref integer
SELECT file_tx_links.file_id
,
    file_tx_links.ref_key
,
    file_tx_links.reference
,
    file_tx_links.type
,
    file_tx_links.dest_class
,
    file_tx_links.source_class
,
    file_tx_links.dest_ref
   
FROM file_tx_links

UNION
 
SELECT file_order_links.file_id
,
    file_order_links.ref_key
,
    file_order_links.reference
,
    file_order_links.oe_class AS type
,
    file_order_links.dest_class
,
    file_order_links.source_class
,
    file_order_links.dest_ref
   
FROM file_order_links;

Index - Schema public


Table: file_order

File attachments primarily attached to orders and quotations.

file_order Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
oe.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_order Inherits file_base,

 

file_order Constraints
Name Constraint
file_order_file_class_check CHECK ((file_class = 2))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: file_order_links

file_order_links Structure
F-Key Name Type Description
file_id integer
ref_key integer
reference text
oe_class text
dest_class integer
source_class integer
dest_ref integer
SELECT sl.file_id
,
    sl.ref_key
,
    oe.ordnumber AS reference
,
    oc.oe_class
,
    sl.dest_class
,
    sl.source_class
,
    sl.ref_key AS dest_ref
   
FROM (
     (file_secondary_attachment sl
     
        JOIN oe 
          ON (
                 (sl.ref_key = oe.id)
           )
     )
     
  JOIN oe_class oc 
    ON (
           (oe.oe_class_id = oc.id)
     )
)
  
WHERE (sl.source_class = 2);

Index - Schema public


Table: file_order_to_order

Secondary links from one order to another, for example to support order consolidation.

file_order_to_order Structure
F-Key Name Type Description
file_order.id file_id integer PRIMARY KEY
source_class integer PRIMARY KEY
oe.id ref_key integer PRIMARY KEY
dest_class integer PRIMARY KEY
attached_by integer NOT NULL
attached_at timestamp without time zone NOT NULL DEFAULT now()

Table file_order_to_order Inherits file_secondary_attachment,

 

file_order_to_order Constraints
Name Constraint
file_order_to_order_dest_class_check CHECK ((dest_class = 2))
file_order_to_order_source_class_check CHECK ((source_class = 2))

Index - Schema public


Table: file_order_to_tx

Secondary links from orders to transactions, for example to track files when invoices are generated from orders.

file_order_to_tx Structure
F-Key Name Type Description
file_order.id file_id integer PRIMARY KEY
source_class integer PRIMARY KEY
gl.id ref_key integer PRIMARY KEY
dest_class integer PRIMARY KEY
attached_by integer NOT NULL
attached_at timestamp without time zone NOT NULL DEFAULT now()

Table file_order_to_tx Inherits file_secondary_attachment,

 

file_order_to_tx Constraints
Name Constraint
file_order_to_tx_dest_class_check CHECK ((dest_class = 1))
file_order_to_tx_source_class_check CHECK ((source_class = 2))

Index - Schema public


Table: file_part

File attachments primarily attached to goods and services.

file_part Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
parts.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_part Inherits file_base,

 

file_part Constraints
Name Constraint
file_part_file_class_check CHECK ((file_class = 3))

Index - Schema public


Table: file_reconciliation

file_reconciliation Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
cr_report.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_reconciliation Inherits file_base,

 

file_reconciliation Constraints
Name Constraint
file_reconciliation_file_class_check CHECK ((file_class = 9))

Index - Schema public


Table: file_secondary_attachment

Another abstract table. This one will use rewrite rules to make inserts safe because of the difficulty in managing inserts otherwise. Inheriting tables provide secondary links between the file and other database objects. Due to the nature of database inheritance and unique constraints in PostgreSQL, this must be partitioned in a star format.

file_secondary_attachment Structure
F-Key Name Type Description
file_id integer PRIMARY KEY
file_class.id source_class integer PRIMARY KEY
ref_key integer PRIMARY KEY
file_class.id dest_class integer PRIMARY KEY
entity.id attached_by integer NOT NULL
attached_at timestamp without time zone NOT NULL DEFAULT now()

 

file_secondary_attachment Constraints
Name Constraint
file_secondary_attachment_check CHECK (false) NO INHERIT

Index - Schema public


Table: file_transaction

File attachments primarily attached to AR/AP/GL.

file_transaction Structure
F-Key Name Type Description
content bytea NOT NULL
mime_type_id integer NOT NULL
file_name text PRIMARY KEY
description text
uploaded_by integer NOT NULL
uploaded_at timestamp without time zone NOT NULL DEFAULT now()
id integer UNIQUE NOT NULL DEFAULT nextval('file_base_id_seq'::regclass)
transactions.id ref_key integer PRIMARY KEY
file_class integer PRIMARY KEY

Table file_transaction Inherits file_base,

 

file_transaction Constraints
Name Constraint
file_transaction_file_class_check CHECK ((file_class = 1))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: file_tx_links

file_tx_links Structure
F-Key Name Type Description
file_id integer
ref_key integer
reference text
type text
dest_class integer
source_class integer
dest_ref integer
SELECT sl.file_id
,
    sl.ref_key
,
    gl.reference
,
    gl.type
,
    sl.dest_class
,
    sl.source_class
,
    sl.ref_key AS dest_ref
   
FROM (file_secondary_attachment sl
     
  JOIN (
      SELECT gl_1.id
           ,
            gl_1.reference
           ,
            'gl'::text AS type
           
        FROM gl gl_1
        
       UNION
         
      SELECT ar.id
           ,
            ar.invnumber
           ,
                CASE
                    WHEN ar.invoice THEN 'is'::text
                    ELSE 'ar'::text
                END AS type
           
        FROM ar
        
       UNION
         
      SELECT ap.id
           ,
            ap.invnumber
           ,
                CASE
                    WHEN ap.invoice THEN 'ir'::text
                    ELSE 'ap'::text
                END AS type
           
        FROM ap
     ) gl 
    ON (
           (
                 (sl.ref_key = gl.id)
               AND (sl.source_class = 1)
           )
     )
);

Index - Schema public


Table: file_tx_to_order

Secondary links from journal entries to orders.

file_tx_to_order Structure
F-Key Name Type Description
file_transaction.id file_id integer PRIMARY KEY
source_class integer PRIMARY KEY
oe.id ref_key integer PRIMARY KEY
dest_class integer PRIMARY KEY
attached_by integer NOT NULL
attached_at timestamp without time zone NOT NULL DEFAULT now()

Table file_tx_to_order Inherits file_secondary_attachment,

 

file_tx_to_order Constraints
Name Constraint
file_tx_to_order_dest_class_check CHECK ((dest_class = 2))
file_tx_to_order_source_class_check CHECK ((source_class = 1))

Index - Schema public


Table: file_view_catalog

file_view_catalog Structure
F-Key Name Type Description
file_class.id file_class integer PRIMARY KEY
view_name text UNIQUE NOT NULL

Index - Schema public


Table: gifi

GIFI labels for accounts, used in Canada and some EU countries for tax reporting

gifi Structure
F-Key Name Type Description
accno text PRIMARY KEY
description text
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: gl

This table holds summary information for entries in the general journal. Does not hold summary information in 1.3 for AR or AP entries.

gl Structure
F-Key Name Type Description
transactions.id id integer PRIMARY KEY DEFAULT nextval('id'::regclass)
reference text
description text
transdate date NOT NULL DEFAULT CURRENT_DATE
person.id person_id integer

the person_id of the employee who created the entry.
notes text
approved boolean NOT NULL DEFAULT true
trans_type.code trans_type_code character(2) NOT NULL DEFAULT 'gl'::bpchar

This column indicates the source or type of the transaction. Ultimately, every insert specifies the type of transaction being generated. At the time of creation of the column (March 2017), we specify a default value because this goal has not been realised yet.

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: inventory_report

inventory_report Structure
F-Key Name Type Description
id serial PRIMARY KEY
transdate date NOT NULL
source text
gl.id trans_id integer

Indicates the associated transaction representing the financial facts associated with the inventory adjustment. (NULL until the report is approved)

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: inventory_report_line

inventory_report_line Structure
F-Key Name Type Description
inventory_report.id adjust_id integer PRIMARY KEY
parts.id parts_id integer PRIMARY KEY
counted numeric
expected numeric
variance numeric

Index - Schema public


Table: invoice

Line items of invoices with goods/services attached.

invoice Structure
F-Key Name Type Description
id serial PRIMARY KEY
transactions.id trans_id integer
parts.id parts_id integer
description text
qty numeric

Positive is normal for sales invoices, negative for vendor invoices.
allocated numeric

Number of allocated items, negative relative to qty. When qty + allocated = 0, then the item is fully used for purposes of COGS calculations.
sellprice numeric
precision integer
fxsellprice numeric
discount numeric
assemblyitem boolean DEFAULT false
unit character varying
deliverydate date
serialnumber text
vendor_sku text
notes text

 

invoice Constraints
Name Constraint
invoice_allocation_constraint CHECK ((((allocated * ('-1'::integer)::numeric) >= LEAST((0)::numeric, qty)) AND ((allocated * ('-1'::integer)::numeric) <= GREATEST(qty, (0)::numeric))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: invoice_note

invoice_note Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
invoice.id ref_key integer NOT NULL
subject text

Table invoice_note Inherits note,

Index - Schema public


Table: invoice_tax_form

Maping invoice to country_tax_form.

invoice_tax_form Structure
F-Key Name Type Description
invoice.id invoice_id integer PRIMARY KEY
reportable boolean

Index - Schema public


Table: jcitems

Time and materials cards. Materials cards not implemented.

jcitems Structure
F-Key Name Type Description
id serial PRIMARY KEY
business_unit.id business_unit_id integer
parts_id integer
description text
qty numeric
allocated numeric
sellprice numeric
fxsellprice numeric
serialnumber text
checkedin timestamp with time zone
checkedout timestamp with time zone
entity.id person_id integer NOT NULL
notes text
total numeric NOT NULL
non_billable numeric NOT NULL
jctype.id jctype integer NOT NULL
currency.curr curr character(3) NOT NULL

Index - Schema public


Table: jctype

jctype Structure
F-Key Name Type Description
id integer UNIQUE NOT NULL
label text PRIMARY KEY
description text NOT NULL
is_service boolean DEFAULT true
is_timecard boolean DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: job

job Structure
F-Key Name Type Description
business_unit.id bu_id integer PRIMARY KEY
parts_id integer

Job costing/manufacturing here not implemented.
production numeric
completed numeric

Index - Schema public


Table: journal_entry

This tale records the header information for each transaction. It replaces parts of the following tables: acc_trans, ar, ap, gl, transactions. Note now all ar/ap transactions are also journal entries.

journal_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
reference text

Invoice number or journal entry number.
description text
session.session_id locked_by integer
journal_type.id journal integer
post_date date DEFAULT now()
effective_start date

For transactions whose effects are spread out over a period of time, this is the effective start date for the transaction. To be used by add-ons for automating adjustments.
effective_end date

For transactions whose effects are spread out over a period of time, this is the effective end date for the transaction. To be used by add-ons for automating adjustments.
currency character(3) NOT NULL
approved boolean DEFAULT false
is_template boolean DEFAULT false

Set true for template transactions. Templates can never be approved but can be copied into new transactions and are useful for recurrances.
entity.id entered_by integer NOT NULL
entity.id approved_by integer

 

journal_entry Constraints
Name Constraint
journal_entry_check CHECK (((is_template IS FALSE) OR (approved IS FALSE)))
journal_entry_check1 CHECK ((is_template OR (reference IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: journal_line

Replaces acc_trans as the main account transaction line table.

journal_line Structure
F-Key Name Type Description
id serial PRIMARY KEY
account.id account_id integer NOT NULL
journal_entry.id journal_id integer NOT NULL
amount numeric NOT NULL
cleared boolean NOT NULL DEFAULT false

Still needed both for legacy data and in case reconciliation data must eventually be purged.
cr_report.id reconciliation_report integer
account_link_description.description line_type text
amount_tc numeric NOT NULL
currency.curr curr character(3) NOT NULL

 

journal_line Constraints
Name Constraint
journal_line_amount_check CHECK ((amount <> 'NaN'::numeric))
journal_line_amount_tc_check CHECK ((amount_tc <> 'NaN'::numeric))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: journal_note

This stores notes attached to journal entries, including payments and invoices.

journal_note Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('note_id_seq'::regclass)
note_class integer NOT NULL
note text NOT NULL
vector tsvector NOT NULL DEFAULT ''::tsvector
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
journal_entry.id ref_key integer NOT NULL
subject text
internal_only boolean NOT NULL DEFAULT false

When set to true, does not show up in notes list for invoice templates

Table journal_note Inherits note,

 

journal_note Constraints
Name Constraint
journal_note_note_class_check CHECK ((note_class = 5))

Index - Schema public


Table: journal_type

This table describes the journal entry type of the transaction. The following values are hard coded by default: 1: General journal 2: Sales (AR) 3: Purchases (AP) 4: Receipts 5: Payments

journal_type Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
name text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: language

Languages for manual translations and so forth.

language Structure
F-Key Name Type Description
code character varying(6) PRIMARY KEY
description text
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: location

This table stores addresses, such as shipto and bill to addresses.

location Structure
F-Key Name Type Description
id serial PRIMARY KEY
line_one text NOT NULL
line_two text
line_three text
city text NOT NULL
state text
country.id country_id integer NOT NULL
mail_code text
created date NOT NULL DEFAULT now()
inactive_date timestamp without time zone
active boolean NOT NULL DEFAULT true

 

location Constraints
Name Constraint
location_city_check CHECK ((city ~ '[[:alnum:]_]'::text))
location_line_one_check CHECK ((line_one ~ '[[:alnum:]_]'::text))
location_mail_code_check CHECK ((mail_code ~ '[[:alnum:]_-]'::text))
location_state_check CHECK ((state ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: location_class

Individuals seeking to add new location classes should coordinate with others.

location_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
class text PRIMARY KEY
authoritative boolean PRIMARY KEY

 

location_class Constraints
Name Constraint
location_class_class_check CHECK ((class ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: location_class_to_entity_class

This determines which location classes go with which entity classes

location_class_to_entity_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
location_class.id location_class integer NOT NULL
entity_class.id entity_class integer NOT NULL

Index - Schema public


Table: lsmb_module

This stores categories functionality into modules. Addons may add rows here, but the id should be hardcoded. As always 900-1000 will be reserved for internal use, and negative numbers will be reserved for testing.

lsmb_module Structure
F-Key Name Type Description
id integer UNIQUE NOT NULL
label text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: lsmb_sequence

lsmb_sequence Structure
F-Key Name Type Description
label text PRIMARY KEY
defaults.setting_key setting_key text NOT NULL
prefix text
suffix text
sequence text NOT NULL DEFAULT '1'::text
accept_input boolean DEFAULT true

Index - Schema public


Table: makemodel

A single parts entry can have multiple make/model entries. These store manufacturer/model number info.

makemodel Structure
F-Key Name Type Description
parts.id parts_id integer PRIMARY KEY
barcode text
make text PRIMARY KEY
model text PRIMARY KEY

Index - Schema public


Table: menu_acl

Provides access control list entries for menu nodes.

menu_acl Structure
F-Key Name Type Description
id serial NOT NULL
role_name character varying PRIMARY KEY
acl_type character varying

Nodes are hidden unless a role is found of which the user is a member, and where the acl_type for that role type and node is set to 'allow' and no acl is found for any role of which the user is a member, where the acl_type is set to 'deny'.
menu_node.id node_id integer PRIMARY KEY

 

menu_acl Constraints
Name Constraint
menu_acl_acl_type_check CHECK ((((acl_type)::text = 'allow'::text) OR ((acl_type)::text = 'deny'::text)))

Index - Schema public


View: menu_friendly

A nice human-readable view for investigating the menu tree. Does not show menu attributes or acls.

menu_friendly Structure
F-Key Name Type Description
level integer
path text
label text
id integer
position integer
 WITH RECURSIVE tree
(path
     , id
     , parent
     , level
     , positions
) AS 
(
         
SELECT (menu_node.id)::text AS path
     ,
            menu_node.id
     ,
            menu_node.parent
     ,
            0 AS level
     ,
            
     (menu_node."position")::text AS "position"
           
  FROM menu_node
          
 WHERE (menu_node.parent IS NULL)
        
 UNION
         
SELECT (
           (t_1.path || 
               ','::text
           ) || 
           (n_1.id)::text
     )
     ,
            n_1.id
     ,
            n_1.parent
     ,
            
     (t_1.level + 1)
     ,
            
     (
           (t_1.positions || 
               ','::text
           ) || n_1."position"
     )
           
  FROM (menu_node n_1
             
        JOIN tree t_1 
          ON (
                 (t_1.id = n_1.parent)
           )
     )
        
)
 
SELECT t.level
,
    t.path
,
    
(repeat
     (' '::text
           , (2 * t.level)
     ) || 
     (n.label)::text
) AS label
,
    n.id
,
    n."position"
   
FROM (tree t
     
  JOIN menu_node n 
 USING (id)
)
  
ORDER BY (string_to_array
     (t.positions
           ,','::text
     )
)::integer[];

Index - Schema public


Table: menu_node

This table stores the tree structure of the menu.

menu_node Structure
F-Key Name Type Description
id serial PRIMARY KEY
label character varying NOT NULL
menu_node.id parent integer UNIQUE#1
position integer UNIQUE#1 NOT NULL
url text
standalone boolean
menu boolean

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: mfg_lot

This tracks assembly restocks. This is designed to work with old code and may change as we refactor the parts.

mfg_lot Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
lot_number text UNIQUE NOT NULL DEFAULT (nextval('lot_tracking_number'::regclass))::text
parts.id parts_id integer NOT NULL
qty numeric NOT NULL
stock_date date NOT NULL DEFAULT (now())::date

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: mfg_lot_item

This tracks items used in assembly restocking.

mfg_lot_item Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
mfg_lot.id mfg_lot_id integer NOT NULL
parts.id parts_id integer NOT NULL
qty numeric NOT NULL

Index - Schema public


Table: mime_type

This is a lookup table for storing MIME types.

mime_type Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
mime_type text PRIMARY KEY
invoice_include boolean DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: new_shipto

Tracks ship_to information for orders and invoices.

new_shipto Structure
F-Key Name Type Description
id serial PRIMARY KEY
transactions.id trans_id integer
oe.id oe_id integer
location.id location_id integer

Index - Schema public


Table: note

This is an abstract table which should have zero rows. It is inherited by other tables for specific notes.

note Structure
F-Key Name Type Description
id serial PRIMARY KEY
note_class.id note_class integer NOT NULL
note text NOT NULL

Body of note.
vector tsvector NOT NULL DEFAULT ''::tsvector

tsvector for full text indexing, requires both setting up tsearch dictionaries and adding triggers to use at present.
created timestamp without time zone NOT NULL DEFAULT now()
created_by text DEFAULT SESSION_USER
ref_key integer NOT NULL

Subclassed tables use this column as a foreign key against the table storing the record a note is attached to.
subject text

 

note Constraints
Name Constraint
note_check CHECK (false) NO INHERIT

Index - Schema public


Table: note_class

Coordinate with others before adding entries.

note_class Structure
F-Key Name Type Description
id serial PRIMARY KEY
class text NOT NULL

 

note_class Constraints
Name Constraint
note_class_class_check CHECK ((class ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: oe

Header information for: * Sales orders * Purchase Orders * Quotations * Requests for Quotation

oe Structure
F-Key Name Type Description
id serial PRIMARY KEY
ordnumber text
transdate date DEFAULT CURRENT_DATE
entity.id entity_id integer
amount_tc numeric
netamount_tc numeric
reqdate date
taxincluded boolean
shippingpoint text
notes text
currency.curr curr character(3)
person.entity_id person_id integer
closed boolean DEFAULT false
quotation boolean DEFAULT false
quonumber text
intnotes text
shipvia text
language_code character varying(6)
ponumber text
terms smallint
entity_credit_account.id entity_credit_account integer NOT NULL
oe_class.id oe_class_id integer NOT NULL
workflow.workflow_id workflow_id integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: oe_class

Hardwired classifications for orders and quotations. Coordinate before adding.

oe_class Structure
F-Key Name Type Description
id smallint UNIQUE
oe_class text PRIMARY KEY

 

oe_class Constraints
Name Constraint
oe_class_id_check CHECK ((id = ANY (ARRAY[1, 2, 3, 4])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: open_forms

This is our primary anti-xsrf measure, as this allows us to require a full round trip to the web server in order to save data.

open_forms Structure
F-Key Name Type Description
id serial PRIMARY KEY
session.session_id session_id integer
form_name character varying(100)
last_used timestamp without time zone

Index - Schema public


Table: orderitems

Line items for sales/purchase orders and quotations.

orderitems Structure
F-Key Name Type Description
id serial PRIMARY KEY
trans_id integer
parts.id parts_id integer
description text
qty numeric
sellprice numeric
precision integer
discount numeric
unit character varying(5)
reqdate date
ship numeric
serialnumber text
notes text

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: overpayments

overpayments Structure
F-Key Name Type Description
payment_id integer
payment_reference text
payment_class integer
payment_closed boolean
payment_date date
chart_id integer
accno text
chart_description text
available numeric
legal_name text
entity_credit_id integer
entity_id integer
discount numeric
meta_number character varying(32)
SELECT p.id AS payment_id
,
    p.reference AS payment_reference
,
    p.payment_class
,
    p.closed AS payment_closed
,
    p.payment_date
,
    ac.chart_id
,
    c.accno
,
    c.description AS chart_description
,
    
(sum
     (ac.amount_bc) * 
     (
        CASE
            WHEN 
           (eca.entity_class = 1) THEN '-1'::integer
            ELSE 1
        END
     )::numeric
) AS available
,
    cmp.legal_name
,
    eca.id AS entity_credit_id
,
    eca.entity_id
,
    eca.discount
,
    eca.meta_number
   
FROM (
     (
           (
                 (
                       (
                             (payment p
     
                                JOIN payment_links pl 
                                  ON (
                                         (pl.payment_id = p.id)
                                   )
                             )
     
                          JOIN acc_trans ac 
                            ON (
                                   (ac.entry_id = pl.entry_id)
                             )
                       )
     
                    JOIN account c 
                      ON (
                             (c.id = ac.chart_id)
                       )
                 )
     
              JOIN account_link l 
                ON (
                       (l.account_id = c.id)
                 )
           )
     
        JOIN entity_credit_account eca 
          ON (
                 (eca.id = p.entity_credit_id)
           )
     )
     
  JOIN company cmp 
    ON (
           (cmp.entity_id = eca.entity_id)
     )
)
  
WHERE (
     (p.gl_id IS NOT NULL)
   AND (
           (pl.type = 2)
          OR (pl.type = 0)
     )
   AND (l.description ~~ '%overpayment'::text)
)
  
GROUP BY p.id
, c.accno
, p.reference
, p.payment_class
, p.closed
, p.payment_date
, ac.chart_id
, c.description
, cmp.legal_name
, eca.id
, eca.entity_id
, eca.discount
, eca.meta_number
, eca.entity_class;

Index - Schema public


Table: parts

This stores detail information about goods and services. The type of part is currently defined according to the following rules: * If assembly is true, then an assembly * If inventory_accno_id, income_accno_id, and expense_accno_id are not null then a part. * If inventory_accno_id is null but the other two are not, then a service. * Otherwise, a labor/overhead entry.

parts Structure
F-Key Name Type Description
id serial PRIMARY KEY
partnumber text
description text
unit character varying(5)
listprice numeric
sellprice numeric
lastcost numeric
priceupdate date DEFAULT CURRENT_DATE
weight numeric
onhand numeric
notes text
makemodel boolean DEFAULT false
assembly boolean DEFAULT false
alternate boolean DEFAULT false
rop numeric

Re-order point. Used to select parts for short inventory report.
account.id inventory_accno_id integer
account.id income_accno_id integer
account.id expense_accno_id integer
account.id returns_accno_id integer
bin text

Text identifier for where a part is stored.
obsolete boolean DEFAULT false
bom boolean DEFAULT false

Show on Bill of Materials.
image text

Hyperlink to product image.
drawing text
microfiche text
partsgroup.id partsgroup_id integer
avgcost numeric

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: parts_translation

Translation information for parts.

parts_translation Structure
F-Key Name Type Description
parts.id trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Table parts_translation Inherits translation,

Index - Schema public


Table: partscustomer

Tracks per-customer pricing. Discounts can be offered for periods of time and for pricegroups as well as per customer

partscustomer Structure
F-Key Name Type Description
parts.id parts_id integer
entity_credit_account.id credit_id integer
pricegroup.id pricegroup_id integer
pricebreak numeric
sellprice numeric
validfrom date
validto date
currency.curr curr character(3) NOT NULL
entry_id serial PRIMARY KEY
qty numeric

Index - Schema public


Table: partsgroup

Groups of parts for Point of Sale screen.

partsgroup Structure
F-Key Name Type Description
id serial PRIMARY KEY
partsgroup text
partsgroup.id parent integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: partsgroup_translation

Translation information for partsgroups.

partsgroup_translation Structure
F-Key Name Type Description
partsgroup.id trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Table partsgroup_translation Inherits translation,

Index - Schema public


Table: partstax

Mapping of parts to taxes.

partstax Structure
F-Key Name Type Description
parts.id parts_id integer PRIMARY KEY
account.id chart_id integer PRIMARY KEY
taxcategory.taxcategory_id taxcategory_id integer

Index - Schema public


Table: partsvendor

Tracks vendor's pricing, as well as vendor's part number, lead time required and currency.

partsvendor Structure
F-Key Name Type Description
entity_credit_account.id credit_id integer NOT NULL
parts_id integer
partnumber text
leadtime smallint
lastcost numeric
currency.curr curr character(3) NOT NULL
entry_id serial PRIMARY KEY

Index - Schema public


Table: payment

This table tracks header data for payments. Its purpose is to maintain references to a gl transaction (overpayments) and purchase/sales orders (prepayments). Together with the payment_links table, it assembles data spread across multiple transactions into a single payment for reconciliation purposes. This table exists to record as a single payment 'acc_trans' lines spread over multiple AR/AP transactions: payment lines use the same 'trans_id' as the AR/AP item which they are payments to. This means that there is a need for a secondary table to track the various parts of a payment. E.g. when a 30USD payment is entered for 2 invoices (INV1 @ 10USD, INV2 @ 20USD), one might expect it to be recorded as +----------+--------+-------+---------+------+ | trans_id | amount | DT/CR | account | ref | +----------+--------+-------+---------+------+ | 25 | 30.00 | DT | Bank | | | 25 | 10.00 | CR | AR | INV1 | | 25 | 20.00 | CR | AR | INV2 | +----------+--------+-------+---------+------+ However, the payment is actually recorded in the 'acc_trans' table as below (assuming INV1 has trans_id 1 and INV2 has trans_id 2): +----------+--------+-------+---------+------+ | trans_id | amount | DT/CR | account | ref | +----------+--------+-------+---------+------+ | 1 | 10.00 | DT | Bank | | | 1 | 10.00 | CR | AR | INV1 | +----------+--------+-------+---------+------+ | 2 | 20.00 | DT | Bank | | | 2 | 20.00 | CR | AR | INV2 | +----------+--------+-------+---------+------+ To track the lines spread across the two transactions into a single payment, the 'payment_links' table references each of the lines as well as a line in the 'payment' table. Each line in the 'payment_links' table has a 'type', which has one of the three values below. For non-overpayment lines, the type is always '1'. Overpayments are entered into the ledger as a GL transaction of two lines; one line posts against the cash account; the other against the overpayment account. The overpayment gets entered into this table with payment_links of type 2 to the associated acc_trans lines. E.g. +----------+---------+-------+---------+--------------+--------+ | trans_id | amount | DT/CR | account | payment type | eca_id | +----------+---------+-------+---------+--------------+--------+ | 15 | 40.00 | DT | Bank | 2 | 21 | | 15 | 40.00 | CR | Prepay | 2 | 21 | +----------+---------+-------+---------+--------------+--------+ Note that the 'payment type' is the 'type' field in the 'payment_links' table and that the 'eca_id' field is the 'entity_credit_id' from the 'payment' table. Allocations of overpayments to invoices are entered as 2 lines of an AR/AP transaction, linking the associated acc_trans lines to a payment using payment_links of type 0. In case the payment involves a foreign currency gain or loss, the allocation consists of 3 lines. All three lines are included in the payment_links. A 4th line may be involved when an early payment discount is in order. E.g. when an overpayment gets used to pay the two invoices from the example above, the transactions will look like this (when the payment is payment_id '20' and the overpayment is payment_id '15' -- as created above): +----------+--------+-------+---------+------+------------+--------+ | trans_id | amount | DT/CR | account | ref | payment_id | ovp_id | +----------+--------+-------+---------+------+------------+--------+ | 1 | 10.00 | DT | Prepay | | 20 | 15 | | 1 | 10.00 | CR | AR | INV1 | 20 | N/A | +----------+--------+-------+---------+------+------------+--------+ | 2 | 20.00 | DT | Prepay | | 20 | 15 | | 2 | 20.00 | CR | AR | INV2 | 20 | N/A | +----------+--------+-------+---------+------+------------+--------+ Note that 'payment_id' is the 'id' field from the 'payment' table for the payment currently being entered. The 'ovp_id' is the same 'id' field in the 'payment' table, but links to the overpayment-creation payment record. The links to the 'payment_id' and 'ovp_id' are created using 2 records in the 'payment_links' table for the same 'acc_trans' row, with values '1' and '0' for the payment and overpayment-use respectively.

payment Structure
F-Key Name Type Description
id serial PRIMARY KEY
reference text NOT NULL

This field will store the code for both receipts and payment order
gl.id gl_id integer

Pre- and overpayments are linked to the GL; AR/AP payment lines are directly connected to 'ar' or 'ap' records which means this field isn't used in those cases.
payment_class integer NOT NULL

1 = payment , 2 = receipt following the values of entity_class (1=vendor, 2=customer)
payment_date date DEFAULT CURRENT_DATE
closed boolean DEFAULT false

This will store the current state of a payment/receipt order
entity_credit_account.id entity_credit_id integer
person.id employee_id integer
currency character(3)
notes text
reversing integer

Indicates which payment.id the current record is reversing (or null if the current record isn''t a reversal)

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payment_links

Ties acc_trans lines in ar/ap/gl items to payments The key to linking transaction lines to payments is the 'type' column. See there for more documentation on how to use this table. Note that *all* lines resulting from a payment should be linked. This includes foreign currency difference lines on ar/ap items and means that the lines linked to a single payment must be balanced.

payment_links Structure
F-Key Name Type Description
payment.id payment_id integer
acc_trans.entry_id entry_id integer
type integer

* A type '0' means that the referenced `acc_trans` line is part of an allocation of an overpayment to invoices (or a reversal thereof). A 'type 0' link exists only on the acc_trans line which modifies the overpayment account. * A type '1' means that the referenced `acc_trans` line is part of an allocation to an invoice. This may either be a through an incoming payment/receipt or through an allocation of an overpayment. All lines in the payment/allocation have a 'type 1' link. * A type '2' means that the referenced `acc_trans` line is part of an overpayment creation transaction. With this idea in order we can do the following To get the payment amount on an invoice we will sum the entries on the AR/AP account given a specific `trans_id` with type = 1. To get the total received/paid amount, we will sum the entries on the cash account where type > 0. To get the used amount of an overpayment, we will sum the entries with type = 0 on the overpayment account, given a specific (over)payment id. The overpayment amount can be obtained from the entries with type = 2. Note that fx difference lines are expected to be included in the list of references for types 1. Note that an `entry_id` associated with type '2' can not also be in the table with a type '1' or '0'. Note that type '1' and '2' collectively link to all `acc_trans` lines involved in the payment. (But type '0' refers to lines of *other* payments.) Note that an `entry_id` associated with type '1' may also be in the table with a type '0': this happens when the payment originates from an overpayment balance (as opposed to originating from a direct payment). Note that type '2' cannot be used in reversal transactions, because '2' designates an opening position. To make sure the overpayment position will be considered closed, a type '0' should be used.

Index - Schema public


Table: payment_type

payment_type Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text PRIMARY KEY

Index - Schema public


Table: payroll_deduction

payroll_deduction Structure
F-Key Name Type Description
entry_id serial UNIQUE NOT NULL
entity.id entity_id integer PRIMARY KEY
payroll_deduction_type.id type_id integer PRIMARY KEY
rate numeric NOT NULL

Index - Schema public


Table: payroll_deduction_class

payroll_deduction_class Structure
F-Key Name Type Description
id integer UNIQUE#1 NOT NULL
country.id country_id integer UNIQUE#1 PRIMARY KEY
label text PRIMARY KEY
stored_proc_name name NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_deduction_type

payroll_deduction_type Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
account.id account_id integer NOT NULL
payroll_deduction_class.id#1 pdc_id integer NOT NULL
payroll_deduction_class.country_id#1 country_id integer NOT NULL
label text NOT NULL
unit text NOT NULL
default_amount numeric
calc_percent boolean NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_employee_class

payroll_employee_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_employee_class_to_income_type

payroll_employee_class_to_income_type Structure
F-Key Name Type Description
payroll_employee_class.id ec_id integer PRIMARY KEY
payroll_income_type.id it_id integer PRIMARY KEY

Index - Schema public


Table: payroll_income_category

payroll_income_category Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text

Index - Schema public


Table: payroll_income_class

payroll_income_class Structure
F-Key Name Type Description
id integer UNIQUE#1 NOT NULL
country.id country_id integer UNIQUE#1 PRIMARY KEY
label text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_income_type

payroll_income_type Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
account.id account_id integer NOT NULL
payroll_income_class.id#1 pic_id integer NOT NULL
payroll_income_class.country_id#1 country_id integer NOT NULL
label text NOT NULL
unit text NOT NULL
default_amount numeric

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_paid_timeoff

payroll_paid_timeoff Structure
F-Key Name Type Description
entity.id employee_id integer NOT NULL
payroll_pto_class.id pto_class_id integer NOT NULL
payroll_report.id report_id integer NOT NULL
amount numeric NOT NULL

Index - Schema public


Table: payroll_pto_class

payroll_pto_class Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
label text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_report

payroll_report Structure
F-Key Name Type Description
id serial PRIMARY KEY
payroll_employee_class.id ec_id integer NOT NULL
payment_date date NOT NULL
entity_employee.entity_id created_by integer
entity_employee.entity_id approved_by integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: payroll_report_line

payroll_report_line Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
payroll_report.id report_id integer PRIMARY KEY
entity.id employee_id integer PRIMARY KEY
payroll_income_type.id it_id integer PRIMARY KEY
qty numeric NOT NULL
rate numeric NOT NULL
description text

Index - Schema public


Table: payroll_wage

payroll_wage Structure
F-Key Name Type Description
entry_id serial UNIQUE NOT NULL
entity.id entity_id integer PRIMARY KEY
payroll_income_type.id type_id integer PRIMARY KEY
rate numeric NOT NULL

Index - Schema public


View: periods

periods Structure
F-Key Name Type Description
id text
label text
date_to date
date_from date
SELECT'ytd'::text AS id
,
    'Year to Date'::text AS label
,
    
(now
     ()
)::date AS date_to
,
    
(
     (
           (date_part
                 ('year'::text
                       , now
                       ()
                 )
           )::text || '-01-01'::text
     )
)::date AS date_from

UNION
 
SELECT'last_year'::text AS id
,
    'Last Year'::text AS label
,
    
(
     (
           (
                 (date_part
                       ('YEAR'::text
                             , now
                             ()
                       ) - 
                       (1)::double precision
                 )
           )::text || '-12-31'::text
     )
)::date AS date_to
,
    
(
     (
           (
                 (date_part
                       ('YEAR'::text
                             , now
                             ()
                       ) - 
                       (1)::double precision
                 )
           )::text || '-01-01'::text
     )
)::date AS date_from;

Index - Schema public


Table: person

Every person, must have an entity to derive a common or display name. The correct way to get class information on a person would be person.entity_id->entity_class_to_entity.entity_id.

person Structure
F-Key Name Type Description
id serial PRIMARY KEY
entity.id entity_id integer UNIQUE NOT NULL
salutation.id salutation_id integer
first_name text NOT NULL
middle_name text
last_name text NOT NULL
created date NOT NULL DEFAULT CURRENT_DATE
birthdate date
personal_id text

 

person Constraints
Name Constraint
person_first_name_check CHECK ((first_name ~ '[[:alnum:]_]'::text))
person_last_name_check CHECK ((last_name ~ '[[:alnum:]_]'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: person_to_company

currently unused in the front-end, but can be used to map persons to companies.

person_to_company Structure
F-Key Name Type Description
location.id location_id integer PRIMARY KEY
person.id person_id integer PRIMARY KEY
company.id company_id integer NOT NULL

Index - Schema public


Table: pricegroup

Pricegroups are groups of customers who are assigned prices and discounts together.

pricegroup Structure
F-Key Name Type Description
id serial PRIMARY KEY
pricegroup text
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: recon_payee

recon_payee Structure
F-Key Name Type Description
payee text
id bigint
report_id integer
scn text
their_balance numeric
our_balance numeric
user integer
clear_time date
insert_time timestamp with time zone
trans_type text
post_date date
cleared boolean
SELECT DISTINCT 
ON (rr.id) n.name AS payee
,
    rr.id
,
    rr.report_id
,
    rr.scn
,
    rr.their_balance
,
    rr.our_balance
,
    rr."user"
,
    rr.clear_time
,
    rr.insert_time
,
    rr.trans_type
,
    rr.post_date
,
    rr.cleared
   
FROM (
     (
           (
                 (cr_report_line rr
     
               LEFT JOIN cr_report_line_links rll 
                      ON (
                             (rr.id = rll.report_line_id)
                       )
                 )
     
         LEFT JOIN acc_trans ac 
                ON (
                       (rll.entry_id = ac.entry_id)
                 )
           )
     
   LEFT JOIN gl 
          ON (
                 (ac.trans_id = gl.id)
           )
     )
     
LEFT JOIN (
      SELECT ap.id
           ,
            e.name
           
        FROM (
                 (ap
             
                    JOIN entity_credit_account eca 
                      ON (
                             (ap.entity_credit_account = eca.id)
                       )
                 )
             
              JOIN entity e 
                ON (
                       (eca.entity_id = e.id)
                 )
           )
        
       UNION
         
      SELECT ar.id
           ,
            e.name
           
        FROM (
                 (ar
             
                    JOIN entity_credit_account eca 
                      ON (
                             (ar.entity_credit_account = eca.id)
                       )
                 )
             
              JOIN entity e 
                ON (
                       (eca.entity_id = e.id)
                 )
           )
        
       UNION
         
      SELECT gl_1.id
           ,
            gl_1.description
           
        FROM gl gl_1
     ) n 
    ON (
           (n.id = ac.trans_id)
     )
);

Index - Schema public


Table: recurring

Stores recurring information on transactions which will recur in the future. Note that this means that only fully posted transactions can recur. I would highly recommend depricating this table and working instead on extending the template transaction addon to handle recurring information.

recurring Structure
F-Key Name Type Description
transactions.id id integer UNIQUE NOT NULL
reference text
startdate date
nextdate date
enddate date
recurring_interval interval
howmany integer
payment boolean DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: recurringemail

Email to be sent out when recurring transaction is posted.

recurringemail Structure
F-Key Name Type Description
recurring.id id integer PRIMARY KEY
formname text PRIMARY KEY
format text
message text

Index - Schema public


Table: recurringprint

Template, printer etc. to print to when recurring transaction posts.

recurringprint Structure
F-Key Name Type Description
recurring.id id integer PRIMARY KEY
formname text PRIMARY KEY
format text
printer text

Index - Schema public


Table: robot

Every robot, must have an entity to derive a common or display name. The correct way to get class information on a robot would be robot.entity_id->entity_class_to_entity.entity_id.

robot Structure
F-Key Name Type Description
id serial PRIMARY KEY
entity.id entity_id integer UNIQUE NOT NULL
first_name text
middle_name text
last_name text NOT NULL
created date NOT NULL DEFAULT CURRENT_DATE

 

robot Constraints
Name Constraint
robot_first_name_check CHECK ((first_name ~ '[[:alnum:] _\-\.\*]?'::text))
robot_last_name_check CHECK ((last_name ~ '[[:alnum:] _\-\.\*]'::text))

Index - Schema public


Table: salutation

salutation Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
salutation text PRIMARY KEY

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: session

This table is used to track sessions on a database level across page requests (discretionary locks,open forms for anti-xsrf measures). Because of the way LedgerSMB authentication works currently we do not time out authentication when the session times out. We do time out highly pessimistic locks used for large batch payment workflows.

session Structure
F-Key Name Type Description
session_id serial PRIMARY KEY
token character varying(32)
last_used timestamp without time zone DEFAULT now()
ttl integer NOT NULL DEFAULT 3600
users.id users_id integer NOT NULL
notify_pasword interval NOT NULL DEFAULT '7 days'::interval

 

session Constraints
Name Constraint
session_token_check CHECK ((length((token)::text) = 32))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: sic

This can be used SIC codes or any equivalent, such as ISIC, NAICS, etc.

sic Structure
F-Key Name Type Description
code character varying(6) PRIMARY KEY
sictype character(1)
description text
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: status

Whether AR/AP transactions and invoices have been emailed and/or printed

status Structure
F-Key Name Type Description
transactions.id trans_id integer PRIMARY KEY
formname text PRIMARY KEY
printed boolean DEFAULT false
emailed boolean DEFAULT false
spoolfile text

Index - Schema public


Table: tax

Information on tax rates.

tax Structure
F-Key Name Type Description
account.id chart_id integer PRIMARY KEY
rate numeric
minvalue numeric
maxvalue numeric
taxnumber text
validto timestamp without time zone PRIMARY KEY DEFAULT 'infinity'::timestamp without time zone
pass integer NOT NULL

This is an integer indicating the pass of the tax. This is to support cumultative sales tax rules (for example, Quebec charging taxes on the federal taxes collected).
taxmodule.taxmodule_id taxmodule_id integer NOT NULL DEFAULT 1

Index - Schema public


Table: tax_extended

This stores extended information for manual tax calculations.

tax_extended Structure
F-Key Name Type Description
tax_basis numeric
rate numeric
acc_trans.entry_id entry_id integer PRIMARY KEY

Index - Schema public


Table: taxcategory

taxcategory Structure
F-Key Name Type Description
taxcategory_id serial PRIMARY KEY
taxcategoryname text NOT NULL
taxmodule.taxmodule_id taxmodule_id integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: taxmodule

This is used to store information on tax modules. the module name is used to determine the Perl class for the taxes.

taxmodule Structure
F-Key Name Type Description
taxmodule_id serial PRIMARY KEY
taxmodulename text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: template

template Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
template_name text UNIQUE#1 NOT NULL
language.code language_code character varying(6) UNIQUE#1
template text NOT NULL
format text UNIQUE#1 NOT NULL
last_modified timestamp without time zone NOT NULL DEFAULT now()

Index - Schema public


Table: trans_type

Documents the transaction type codes used in the 'gl' table. Please note that the codes in this table are hard-coded into other (SQL) parts of the application. As such, this table merely serves as documentation; do *not* modify its content other than inserting new codes.

trans_type Structure
F-Key Name Type Description
code character(2) PRIMARY KEY

Code of the transaction type. The 72 alphanumeric codes starting with 'x' or 'X' are reserved for custom internal extensions. For extensions distributed for wide(r) use, please request a code from the LedgerSMB development team.
description character varying(1000)

This column contains the full documentation as to the origin and purpose of the transaction type.

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: transactions

This table provides referential integrity between AR, AP, GL tables on one hand and acc_trans on the other, pending the refactoring of those tables. It also is used to provide discretionary locking of financial transactions across database connections, for example in batch payment workflows.

transactions Structure
F-Key Name Type Description
id integer PRIMARY KEY
table_name text NOT NULL
session.session_id locked_by integer

This should only be used in pessimistic locking measures as required by large batch work flows.
approved boolean NOT NULL
entity.id approved_by integer
approved_at timestamp without time zone
transdate date
workflow.workflow_id workflow_id integer
reversing integer

This transaction is a reversal transaction. This field holds the transactions.id of the transaction being reversed.
reference text

For GL transactions, this field contains gl.reference; For AR/AP transactions, this field contains the invnumber field.

 

transactions Constraints
Name Constraint
transactions_table_name_check CHECK ((table_name = ANY (ARRAY['gl'::text, 'ar'::text, 'ap'::text])))
transdate_nullity CHECK (((NOT approved) OR (transdate IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


View: transactions_reversal

transactions_reversal Structure
F-Key Name Type Description
id integer
table_name text
locked_by integer
approved boolean
approved_by integer
approved_at timestamp without time zone
transdate date
workflow_id integer
reversing integer
reference text
reversed_by integer
reversed_by_reference text
reversing_reference text
SELECT t.id
,
    t.table_name
,
    t.locked_by
,
    t.approved
,
    t.approved_by
,
    t.approved_at
,
    t.transdate
,
    t.workflow_id
,
    t.reversing
,
    t.reference
,
    i.id AS reversed_by
,
    i.reference AS reversed_by_reference
,
    j.reference AS reversing_reference
   
FROM (
     (transactions t
     
   LEFT JOIN transactions i 
          ON (
                 (t.id = i.reversing)
           )
     )
     
LEFT JOIN transactions j 
    ON (
           (t.reversing = j.id)
     )
);

Index - Schema public


Table: translation

abstract table for manual translation data. Should have zero rows.

translation Structure
F-Key Name Type Description
trans_id integer PRIMARY KEY
language_code character varying(6) PRIMARY KEY
description text

Index - Schema public


Table: trial_balance__yearend_types

trial_balance__yearend_types Structure
F-Key Name Type Description
type text PRIMARY KEY

Index - Schema public


View: user_listable

user_listable Structure
F-Key Name Type Description
id integer
username character varying(30)
created date
name text
control_code text
SELECT u.id
,
    u.username
,
    e.created
,
    e.name
,
    e.control_code
   
FROM (entity e
     
  JOIN users u 
    ON (
           (u.entity_id = e.id)
     )
);

Index - Schema public


Table: user_preference

This table lists user and global preferences, one preference per row. Global preferences are stored using a NULL `user_id` value, requiring a workaround to check uniqueness of the preference `name` column.

user_preference Structure
F-Key Name Type Description
id serial PRIMARY KEY

This column is a surrogate primary key. It compensates for the fact that we cannot require `user_id` to be not-null because global settings will be stored with a NULL `user_id`. It exists to allow interactive software (e.g. PgAdmin4) to edit rows in the table.
users.id user_id integer
name text NOT NULL
value text NOT NULL

 

user_preference Constraints
Name Constraint
user_preference_user_id_check CHECK (((user_id IS NULL) OR (user_id > 0)))

Index - Schema public


Table: users

This table maps lsmb entities to postgresql roles, which are used to authenticate lsmb users. The username field maps to the postgresql role name and is therefore subject to its limitations. A role name is considered an Identifier and as such must begin with a letter or an underscore and is limited by default to 63 bytes (could be fewer characters if unicode) as documented here: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS Lsmb restricts the length of username, but this is an arbitrary restriction beyond the postgresql role name limitations already described.

users Structure
F-Key Name Type Description
id serial UNIQUE NOT NULL
username character varying(30) PRIMARY KEY
notify_password interval NOT NULL DEFAULT '7 days'::interval
entity.id entity_id integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: voucher

Mapping transactions to batches for batch approval.

voucher Structure
F-Key Name Type Description
transactions.id trans_id integer NOT NULL
batch.id batch_id integer NOT NULL
id serial PRIMARY KEY

This is simply a surrogate key for easy reference.
batch_class.id batch_class integer NOT NULL

This is the authoritative class of the voucher.

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: warehouse

warehouse Structure
F-Key Name Type Description
id serial PRIMARY KEY
description text
last_updated timestamp without time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: warehouse_inventory

This table contains inventory mappings to warehouses, not general inventory management data.

warehouse_inventory Structure
F-Key Name Type Description
entity_employee.entity_id entity_id integer
warehouse_id integer
parts_id integer
trans_id integer
orderitems_id integer
qty numeric
shippingdate date
entry_id serial PRIMARY KEY

Index - Schema public


Table: workflow

workflow Structure
F-Key Name Type Description
workflow_id integer PRIMARY KEY
type character varying(50) NOT NULL
state character varying(30) NOT NULL
last_update timestamp without time zone DEFAULT CURRENT_TIMESTAMP

Tables referencing this one via Foreign Key Constraints:

Index - Schema public


Table: workflow_context

workflow_context Structure
F-Key Name Type Description
workflow.workflow_id workflow_id integer PRIMARY KEY
context jsonb

Index - Schema public


Table: workflow_history

workflow_history Structure
F-Key Name Type Description
workflow_hist_id integer PRIMARY KEY
workflow.workflow_id workflow_id integer NOT NULL
action character varying(25) NOT NULL
description character varying(255)
state character varying(30) NOT NULL
workflow_user character varying(50)
history_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP
entity.id workflow_entity_id integer

Index - Schema public


Table: yearend

An extension to the journal_entry table to track transactionsactions which close out the books at yearend.

yearend Structure
F-Key Name Type Description
gl.id trans_id integer PRIMARY KEY
reversed boolean DEFAULT false
transdate date

Index - Schema public


Function: _entity_location_save(in_entity_id integer, in_location_id integer, in_location_class integer, in_line_one text, in_line_two text, in_line_three text, in_city text, in_state text, in_mail_code text, in_country_id integer)

Returns: integer

Language: PLPGSQL

Private method for storing locations to an entity. Do not call directly. Returns the location id that was inserted or updated.


    DECLARE
        l_row location;
        l_id INT;
        t_company_id int;
    BEGIN
        SELECT id INTO t_company_id
        FROM company WHERE entity_id = in_entity_id;

        DELETE FROM entity_to_location
        WHERE entity_id = in_entity_id
                AND location_class = in_location_class
                AND location_id = in_location_id;

        SELECT location_save(NULL, in_line_one, in_line_two, in_line_three, in_city,
                in_state, in_mail_code, in_country_id)
        INTO l_id;

        INSERT INTO entity_to_location
                (entity_id, location_class, location_id)
        VALUES  (in_entity_id, in_location_class, l_id);

        RETURN l_id;
    END;


Function: account__all_headings()

Returns: SET OF account_heading

Language: SQL

SELECT * FROM account_heading ORDER BY accno;

Function: account__delete(in_id integer)

Returns: boolean

Language: PLPGSQL

This deletes an account with the id specified. If the account has transactions associated with it, it will fail and raise a foreign key constraint.

BEGIN
    /* We only allow deletion of unused accounts.
       Any account_checkpoint rows remaining will cause the final
       DELETE FROM account to fail and this operation to be rolled back.
     */
    DELETE FROM account_checkpoint
    WHERE account_id = in_id
    AND amount_bc = 0
    AND amount_tc = 0
    AND debits = 0
    AND credits = 0;

    DELETE FROM tax WHERE chart_id = in_id;
    DELETE FROM account_link WHERE account_id = in_id;
    DELETE FROM account WHERE id = in_id;
    RETURN FOUND;
END;

Function: account__delete_translation(in_id integer, in_language_code text)

Returns: void

Language: SQL

Deletes the translation for the account+language combination.

   DELETE FROM account_translation
    WHERE trans_id = $1
      AND language_code = $2;

Function: account__get_by_link_desc(in_description text)

Returns: SET OF account

Language: SQL

Gets a list of accounts with a specific link description set. For example, for a dropdown list.

SELECT * FROM account
WHERE id IN (SELECT account_id FROM account_link WHERE description = $1);

Function: account__get_from_accno(in_accno text)

Returns: account

Language: SQL

Returns the account where the accno field matches (excatly) the in_accno provided.

     select * from account where accno = $1;

Function: account__get_taxes()

Returns: SET OF account

Language: SQL

Returns set of accounts where the tax attribute is true.

SELECT * FROM account
 WHERE tax is true
ORDER BY accno;

Function: account__is_recon(in_accno text)

Returns: boolean

Language: SQL

Returns true if account is set up for reconciliation, false otherwise. Note that returns false on invalid account number too

 SELECT count(*) > 0
     FROM cr_coa_to_account c2a
     JOIN account ON account.id = c2a.chart_id
    WHERE accno = $1; 

Function: account__list_by_heading()

Returns: SET OF account

Language: SQL

SELECT * FROM account ORDER BY heading;

Function: account__list_translations(in_id integer)

Returns: SET OF account_translation

Language: SQL

Returns the list of translations for the given account.

   SELECT * FROM account_translation WHERE trans_id = $1;

Function: account__obtain_balance(in_transdate date, in_account_id integer)

Returns: numeric

Language: SQL

Returns the account balance at a given point in time, calculating forward from most recent check point. This function is inclusive of in_transdate. For an exclusive function see account__obtain_starting_balance below.

WITH cp AS (
  SELECT amount_bc, end_date, account_id
    FROM account_checkpoint
   WHERE account_id = in_account_id
     AND end_date <= in_transdate
ORDER BY end_date DESC LIMIT 1
),
ac AS (
  SELECT acc_trans.amount_bc
    FROM acc_trans
    JOIN (select id from transactions where approved) a
          on acc_trans.trans_id = a.id
  LEFT JOIN cp ON acc_trans.chart_id = cp.account_id
   WHERE (cp.end_date IS NULL OR transdate > cp.end_date)
     AND transdate <= in_transdate
     AND chart_id = in_account_id)

 SELECT coalesce((select sum(amount)
                    from (select sum(amount_bc) as amount from cp
                          union all
                          select sum(amount_bc) from ac) as a),
                 0);

Function: account__obtain_starting_balance(in_transdate date, in_account_id integer)

Returns: numeric

Language: SQL

SELECT account__obtain_balance($1 - 1, $2);

Function: account__save(in_id integer, in_accno text, in_description text, in_category bpchar, in_gifi_accno text, in_heading integer, in_heading_negative_balance integer, in_contra boolean, in_tax boolean, in_link text[], in_obsolete boolean, in_is_temp boolean)

Returns: integer

Language: PLPGSQL

This deletes existing account_link entries, where the account_link.description is not designated as a custom one in the account_link_description table. If no account heading is provided, the account heading which has an accno field closest to but prior (by collation order) is used. Then it saves the account information, and rebuilds the account_link records based on the in_link array.

DECLARE
        t_link record;
        t_id int;
        t_tax bool;
BEGIN

    SELECT count(*) > 0 INTO t_tax FROM tax WHERE in_id = chart_id;
    t_tax := t_tax OR in_tax;
        -- check to ensure summary accounts are exclusive
        -- necessary for proper handling by legacy code
    FOR t_link IN SELECT description FROM account_link_description
    WHERE summary='t'
        LOOP
                IF t_link.description = ANY (in_link) and array_upper(in_link, 1) > 1 THEN
                        RAISE EXCEPTION 'Invalid link settings:  Summary';
                END IF;
        END LOOP;

        -- Remove all links. Later we'll (re-)insert the ones we want.
        DELETE FROM account_link
        WHERE account_id = in_id;

        UPDATE account
        SET accno = in_accno,
            description = in_description,
            category = in_category,
            gifi_accno = in_gifi_accno,
            heading = in_heading,
            heading_negative_balance = in_heading_negative_balance,
            contra = in_contra,
            obsolete = coalesce(in_obsolete,'f'),
            tax = t_tax,
            is_temp = coalesce(in_is_temp,'f')
        WHERE id = in_id;

        IF FOUND THEN
                t_id := in_id;
        ELSE
                -- can't obsolete on insert, but this can be changed if users
                -- request it --CT
                INSERT INTO account (accno, description, category, gifi_accno,
                        heading, heading_negative_balance, contra, tax, is_temp)
                VALUES (in_accno, in_description, in_category, in_gifi_accno,
                        in_heading, in_heading_negative_balance, in_contra,
                        in_tax, coalesce(in_is_temp, 'f'));

                t_id := currval('account_id_seq');
        END IF;

        FOR t_link IN
                select in_link[generate_series] AS val
                FROM generate_series(array_lower(in_link, 1),
                        array_upper(in_link, 1))
        LOOP
                INSERT INTO account_link (account_id, description)
                VALUES (t_id, t_link.val);
        END LOOP;


        RETURN t_id;
END;

Function: account__save_tax(in_chart_id integer, in_validto date, in_rate numeric, in_minvalue numeric, in_maxvalue numeric, in_taxnumber text, in_pass integer, in_taxmodule_id integer, in_old_validto date)

Returns: boolean

Language: PLPGSQL

This saves tax rates.

BEGIN
        UPDATE tax SET validto = in_validto,
               rate = in_rate,
               minvalue = in_minvalue,
               maxvalue = in_maxvalue,
               taxnumber = in_taxnumber,
               pass = in_pass,
               taxmodule_id = in_taxmodule_id
         WHERE chart_id = in_chart_id and validto = in_old_validto;

         IF FOUND THEN
             return true;
         END IF;

         INSERT INTO tax(chart_id, validto, rate, minvalue, maxvalue, taxnumber,
                        pass, taxmodule_id)
         VALUES (in_chart_id, in_validto, in_rate, in_minvalue, in_maxvalue,
                in_taxnumber, in_pass, in_taxmodule_id);

         RETURN TRUE;

END;

Function: account__save_translation(in_id integer, in_language_code text, in_description text)

Returns: void

Language: PLPGSQL

Saves the translation for the given account, creating a new translation if none existed for the account+language combination.

BEGIN
   UPDATE account_translation
      SET description = in_description
    WHERE language_code = in_language_code
      AND trans_id = in_id;

   IF NOT FOUND THEN
      INSERT INTO account_translation
             (trans_id, language_code, description)
      VALUES (in_id, in_language_code, in_description);
   END IF;
   RETURN;
END;

Function: account_get(in_id integer)

Returns: account_config

Language: SQL

Returns an entry from the account table which matches the id requested, and which is an account, not a heading.

  select c.id, c.accno, c.description, c.is_temp, c.category, c.gifi_accno,
  c.heading, c.heading_negative_balance, c.contra, c.tax, c.obsolete,
  string_agg(l.description, ':') as link
  from account c
  left join account_link l
    ON (c.id = l.account_id)
  where  id = $1
group by c.id, c.accno, c.description, c.category,
         c.heading, c.heading_negative_balance, c.gifi_accno, c.contra, c.tax;

Function: account_has_transactions(in_id integer)

Returns: boolean

Language: PLPGSQL

Checks to see if any transactions use this account. If so, returns true. If not, returns false.

BEGIN
        PERFORM trans_id FROM acc_trans WHERE chart_id = in_id LIMIT 1;
        IF FOUND THEN
                RETURN true;
        ELSE
                RETURN false;
        END IF;
END;

Function: account_heading__check_tree()

Returns: trigger

Language: PLPGSQL

BEGIN

PERFORM * from (
  WITH RECURSIVE account_headings AS (
      SELECT id, accno, 1 as level, accno as path
        FROM account_heading
      UNION ALL
      SELECT ah.id, ah.accno, at.level + 1 as level, at.path  || '||||' || ah.accno
        FROM account_heading ah
        JOIN account_headings at ON ah.parent_id = at.id
       WHERE NOT ah.accno = ANY(string_to_array(path, '||||'))
  )
  SELECT *
    FROM account_heading ah
    JOIN account_headings at ON ah.parent_id = at.id
   WHERE NOT EXISTS (SELECT 1 FROM account_headings
                  WHERE path = at.path || '||||' || ah.accno)
) x;

IF found then
   RAISE EXCEPTION 'ACCOUNT_HEADING_LOOP';
END IF;

RETURN NEW;
end;

Function: account_heading__delete(in_id integer)

Returns: boolean

Language: PLPGSQL

This deletes an account heading with the id specified. If the heading has accounts associated with it, it will fail and raise a foreign key constraint.

BEGIN
DELETE FROM account_heading WHERE id = in_id;
RETURN FOUND;
END;

Function: account_heading__delete_translation(in_id integer, in_language_code text)

Returns: void

Language: SQL

Deletes the translation for the account+language combination.

   DELETE FROM account_heading_translation
    WHERE trans_id = $1
      AND language_code = $2;

Function: account_heading__list()

Returns: SET OF account_heading

Language: SQL

Returns a list of all account headings, currently ordered by account number.

 SELECT * FROM account_heading order by accno; 

Function: account_heading__list_translations(in_id integer)

Returns: SET OF account_heading_translation

Language: SQL

Returns the list of translations for the given account.

   SELECT * FROM account_heading_translation WHERE trans_id = $1;

Function: account_heading__save_translation(in_id integer, in_language_code text, in_description text)

Returns: void

Language: PLPGSQL

Saves the translation for the given account, creating a new translation if none existed for the account+language combination.

BEGIN
   UPDATE account_heading_translation
      SET description = in_description
    WHERE language_code = in_language_code
      AND trans_id = in_id;

   IF NOT FOUND THEN
      INSERT INTO account_heading_translation
             (trans_id, language_code, description)
      VALUES (in_id, in_language_code, in_description);
   END IF;
   RETURN;
END;

Function: account_heading_get(in_id integer)

Returns: account_heading

Language: SQL

Returns an entry from the account heading tablewhich matches the id requested, and which is a heading, not an account.

SELECT *
   from account_heading ah
  WHERE id = in_id;

Function: account_heading_list()

Returns: SET OF account_heading

Language: SQL

Lists all existing account headings.

SELECT * FROM account_heading order by accno;

Function: account_heading_save(in_id integer, in_accno text, in_description text, in_parent integer)

Returns: integer

Language: PLPGSQL

Saves an account heading.

BEGIN
        UPDATE account_heading
        SET accno = in_accno,
                description = in_description,
                parent_id = in_parent
        WHERE id = in_id;

        IF FOUND THEN
                RETURN in_id;
        END IF;
        INSERT INTO account_heading (accno, description, parent_id)
        VALUES (in_accno, in_description, in_parent);

        RETURN currval('account_heading_id_seq');
END;

Function: admin__add_user_to_role(in_username text, in_role text)

Returns: integer

Language: PLPGSQL


    declare
        stmt TEXT;
        a_role name;
        a_user name;
        t_userid int;
        t_in_role TEXT;
    BEGIN

        -- Issue the grant
        -- Make sure to evaluate the role once because the optimizer
        -- uses it as a filter on every row otherwise
        SELECT lsmb__role(in_role) INTO t_in_role;
        select rolname into a_role from pg_roles
          where rolname = t_in_role;
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions of a non-existant role.';
        END IF;

        select rolname into a_user from pg_roles
         where rolname = in_username;

        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions to a non-existant database user.';
        END IF;

        select id into t_userid from users where username = in_username;
        if not FOUND then
          RAISE EXCEPTION 'Cannot grant permissions to a non-existant application user.';
        end if;

        stmt := 'GRANT '|| quote_ident(a_role) ||' to '|| quote_ident(in_username);

        EXECUTE stmt;

        return 1;
    END;


Function: admin__delete_user(in_username text, in_drop_role boolean)

Returns: integer

Language: PLPGSQL

Drops the provided user, as well as deletes the user configuration data. It leaves the entity and person references. If in_drop_role is set, it drops the role too.


    DECLARE
        stmt text;
        a_user users;
    BEGIN

        select * into a_user from users where username = in_username;

        IF NOT FOUND THEN

            raise exception 'User not found.';
        ELSIF FOUND THEN
            IF in_drop_role IS TRUE then
                stmt := ' drop user ' || quote_ident(a_user.username);
                execute stmt;
            END IF;
            -- delete cascades into user_preference by schema definition
            delete from users where entity_id = a_user.entity_id;
            return 1;
        END IF;
    END;


Function: admin__drop_session(in_session_id integer)

Returns: boolean

Language: PLPGSQL

Drops the session identified, releasing all locks held.

BEGIN
        DELETE FROM "session" WHERE session_id = in_session_id;
        RETURN FOUND;
END;

Function: admin__get_roles()

Returns: SET OF pg_roles

Language: PLPGSQL

DECLARE
   u_role pg_roles;
   t_role_prefix TEXT;
begin
    -- Make sure to evaluate the role prefix once because the optimizer
    -- uses it as a filter on every row otherwise
     SELECT lsmb__role_prefix() INTO t_role_prefix;
     FOR u_role IN
        SELECT *
        FROM
            pg_roles
        WHERE
            rolname ~ ('^' || t_role_prefix)
            AND NOT rolcanlogin
        ORDER BY lsmb__global_role(rolname) ASC
     LOOP
        u_role.rolname = lsmb__global_role(u_role.rolname);

        RETURN NEXT u_role;
     END LOOP;
end;

Function: admin__get_roles_for_user(in_user_id integer)

Returns: SET OF text

Language: PLPGSQL

Returns a set of roles that a user is a part of. Note: this function can only be used by - super users - database admins (setup.pl users): - database owners - database users (roles) which were granted the database owner role - application users: - application admins (users with 'manage_users' role) - application users (roles) which query their own roles


    declare
        u_role record;
        a_user users;
        t_role_prefix TEXT;
    begin
        select * into a_user from admin__get_user(in_user_id);

        -- this function used to be security definer, but that hides the true
        -- CURRENT_USER, returning the DEFINER instead of the caller
        IF a_user.username != CURRENT_USER THEN
            -- super users and application users match the first criterion
            -- db owners and db owner group members match the second criterion
            IF pg_has_role(lsmb__role('users_manage'), 'USAGE') IS FALSE
               AND pg_has_role((select rolname
                                 from pg_database db inner join pg_roles rol
                                   on db.datdba = rol.oid
                                where db.datname = current_database()),
                               'USAGE') IS FALSE THEN
               RAISE EXCEPTION 'User % querying permissions for %, not authorised', CURRENT_USER, a_user.username;
            END IF;
        END IF;

        -- Make sure to evaluate the role prefix once because the optimizer
        -- uses it as a filter on every row otherwise
        SELECT lsmb__role_prefix() INTO t_role_prefix;
        FOR u_role IN
        select r.rolname
        from
            pg_roles r,
            (select
                m.roleid
             from
                pg_auth_members m, pg_roles b
             where
                m.member = b.oid
             and
                b.rolname = a_user.username
            ) as ar
         where
            r.oid = ar.roleid
            and position(t_role_prefix in r.rolname) = 1
         LOOP

            RETURN NEXT lsmb__global_role(u_role.rolname);

        END LOOP;
        RETURN;
    end;


Function: admin__get_roles_for_user_by_entity(in_entity_id integer)

Returns: SET OF text

Language: PLPGSQL

Returns a set of roles that a user is a part of. Note: this function can only be used by - super users - database admins (setup.pl users): - database owners - database users (roles) which were granted the database owner role - application users: - application admins (users with 'manage_users' role) - application users (roles) which query their own roles


    declare
        u_role record;
        a_user users;
        t_role_prefix TEXT;
    begin
        select * into a_user from admin__get_user_by_entity(in_entity_id);

        -- this function used to be security definer, but that hides the true
        -- CURRENT_USER, returning the DEFINER instead of the caller
        IF a_user.username != CURRENT_USER THEN
            -- super users and application users match the first criterion
            -- db owners and db owner group members match the second criterion
            IF pg_has_role(lsmb__role('users_manage'), 'USAGE') IS FALSE
               AND pg_has_role((select rolname
                                 from pg_database db inner join pg_roles rol
                                   on db.datdba = rol.oid
                                where db.datname = current_database()),
                               'USAGE') IS FALSE THEN
               RAISE EXCEPTION 'User % querying permissions for %, not authorised', CURRENT_USER, a_user.username;
            END IF;
        END IF;

        -- Make sure to evaluate the role prefix once because the optimizer
        -- uses it as a filter on every row otherwise
        SELECT lsmb__role_prefix() INTO t_role_prefix;
        FOR u_role IN
        select r.rolname
        from
            pg_roles r,
            (select
                m.roleid
             from
                pg_auth_members m, pg_roles b
             where
                m.member = b.oid
             and
                b.rolname = a_user.username
            ) as ar
         where
            r.oid = ar.roleid
            and position(t_role_prefix in r.rolname) = 1
         LOOP

            RETURN NEXT lsmb__global_role(u_role.rolname);

        END LOOP;
        RETURN;
    end;


Function: admin__get_user(in_id integer)

Returns: users

Language: SQL

Returns a set of (only one) user specified by the id.


        select * from users where id = in_id;


Function: admin__get_user_by_entity(in_entity_id integer)

Returns: users

Language: SQL

Returns a set of (only one) user specified by the entity_id.


        select * from users where entity_id = in_entity_id;


Function: admin__is_user(in_user text)

Returns: boolean

Language: PLPGSQL

Returns true if user is set up in LedgerSMB. False otherwise.

    BEGIN

        PERFORM * from users where username = in_user;
        RETURN found;

    END;


Function: admin__list_sessions()

Returns: SET OF session_result

Language: SQL

Lists all active sessions.

SELECT s.session_id, u.username, s.last_used, count(t.id)
FROM "session" s
JOIN users u ON (s.users_id = u.id)
LEFT JOIN transactions t ON (t.locked_by = s.session_id)
GROUP BY s.session_id, u.username, s.last_used
ORDER BY u.username;

Function: admin__remove_user_from_role(in_username text, in_role text)

Returns: integer

Language: PLPGSQL


    declare
        stmt TEXT;
        a_role name;
        a_user name;
        t_in_role TEXT;
    BEGIN

        -- Issue the grant
        -- Make sure to evaluate the role once because the optimizer
        -- uses it as a filter on every row otherwise
        SELECT lsmb__role(in_role) INTO t_in_role;
        select rolname into a_role from pg_roles
         where rolname = t_in_role;

        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions of a non-existant role.';
        END IF;

        select rolname into a_user from pg_roles
         where rolname = in_username;

        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions from a non-existant user.';
        END IF;

        stmt := 'REVOKE '|| quote_ident(a_role) ||' FROM '|| quote_ident(in_username);

        EXECUTE stmt;

        return 1;
    END;


Function: admin__save_user(in_id integer, in_entity_id integer, in_username text, in_password text, in_pls_import boolean)

Returns: integer

Language: PLPGSQL

Creates a user and relevant records in LedgerSMB and PostgreSQL.

    DECLARE

        a_user users;
        v_user_id int;
        p_id int;
        l_id int;
        stmt text;
        t_is_role bool;
        t_is_user bool;
    BEGIN
        -- WARNING TO PROGRAMMERS:  This function runs as the definer and runs
        -- utility statements via EXECUTE.
        -- PLEASE BE VERY CAREFUL ABOUT SQL-INJECTION INSIDE THIS FUNCTION.

       PERFORM rolname FROM pg_roles WHERE rolname = in_username;
       t_is_role := found;
       t_is_user := admin__is_user(in_username);

       IF t_is_role is true and t_is_user is false and in_pls_import is NOT TRUE THEN
          RAISE EXCEPTION 'Duplicate user';
        END IF;

        if t_is_role and in_password is not null then
                execute 'ALTER USER ' || quote_ident( in_username ) ||
                     ' WITH ENCRYPTED PASSWORD ' || quote_literal (in_password)
                     || $e$ valid until $e$ ||
                      quote_literal(now() + '1 day'::interval);
        elsif in_pls_import is false AND t_is_user is false
              AND in_password IS NULL THEN
                RAISE EXCEPTION 'No password';
        elsif  t_is_role is false and in_pls_import IS NOT TRUE THEN
            -- create an actual user
                execute 'CREATE USER ' || quote_ident( in_username ) ||
                     ' WITH ENCRYPTED PASSWORD ' || quote_literal (in_password)
                     || $e$ valid until $e$ || quote_literal(now() + '1 day'::interval);
       END IF;

        select * into a_user from users lu where lu.id = in_id;
        IF FOUND THEN
            PERFORM admin__add_user_to_role(a_user.username, 'base_user');
            return a_user.id;
        ELSE
            -- Insert cycle

            --- The entity is expected to already BE created. See admin.pm.

            PERFORM * FROM USERS where username = in_username;
            IF NOT FOUND THEN
                v_user_id := nextval('users_id_seq');
                insert into users (id, username, entity_id) VALUES (
                    v_user_id,
                    in_username,
                    in_entity_id
                );
            END IF;

            IF NOT exists(SELECT * FROM entity_employee WHERE entity_id = in_entity_id) THEN
                INSERT into entity_employee (entity_id) values (in_entity_id);
            END IF;
            -- Finally, issue the create user statement
            PERFORM admin__add_user_to_role(in_username, 'base_user');
            return v_user_id ;



        END IF;

    END;

Function: admin__search_users(in_username text, in_first_name text, in_last_name text, in_ssn text, in_dob date)

Returns: SET OF user_result

Language: SQL

Returns a list of users matching search criteria. Nulls match all values. only username is not an exact match.

                SELECT u.id, u.username, p.first_name, p.last_name, e.ssn, e.dob
                FROM users u
                JOIN person p ON (u.entity_id = p.entity_id)
                JOIN entity_employee e ON (e.entity_id = p.entity_id)
                WHERE u.username LIKE '%' || coalesce(in_username,'') || '%' AND
                        (p.first_name = in_first_name or in_first_name is null)
                        AND (p.last_name = in_last_name or in_last_name is null)
                        AND (in_ssn is NULL or in_ssn = e.ssn)
                        AND (e.dob = in_dob::date or in_dob is NULL)

Function: ar_ap__transaction_search(in_account_id integer, in_name_part text, in_meta_number text, in_invnumber text, in_ordnumber text, in_ponumber text, in_source text, in_description text, in_notes text, in_shipvia text, in_from_date date, in_to_date date, in_on_hold boolean, in_inc_open boolean, in_inc_closed boolean, in_as_of date, in_entity_class integer, in_approved boolean)

Returns: SET OF purchase_info

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
   SELECT gl.id, gl.invoice,
          gl.invnumber, gl.ordnumber, gl.ponumber, gl.transdate,
          e.name, eca.meta_number::text, e.id, gl.amount_bc,
          gl.amount_bc - sum(CASE WHEN l.description IN ('AR', 'AP')
                               THEN ac.amount_bc ELSE 0
                           END),
          gl.amount_bc - gl.netamount_bc, gl.curr, gl.duedate,
          gl.notes, gl.shippingpoint, gl.shipvia,
          compound_array(bua.business_units || bui.business_units)
     FROM (select id, invoice, invnumber, ordnumber, ponumber, transdate, duedate,
                  description, notes, shipvia, shippingpoint, amount_bc,
                  netamount_bc, curr, entity_credit_account, on_hold,
                  approved
             FROM ar WHERE $17 = 2
            UNION
           select id, invoice, invnumber, ordnumber, ponumber, transdate, duedate,
                  description, notes, shipvia, shippingpoint, amount_bc,
                  netamount_bc, curr, entity_credit_account, on_hold,
                  approved
             FROM ap WHERE $17 = 1) gl
     JOIN entity_credit_account eca ON gl.entity_credit_account = eca.id
     JOIN entity e ON e.id = eca.entity_id
     JOIN acc_trans ac ON gl.id = ac.trans_id
     JOIN account act ON act.id = ac.chart_id
LEFT JOIN account_link l ON l.account_id = act.id
                          AND l.description IN ('AR', 'AP')
LEFT JOIN invoice inv ON gl.id = inv.trans_id
LEFT JOIN (SELECT array_agg(ARRAY[buc.label, bu.control_code])
                  as business_units, entry_id
             FROM business_unit_class buc
             JOIN business_unit bu ON bu.class_id = buc.id
             JOIN business_unit_ac buac ON buac.bu_id = bu.id
         GROUP BY buac.entry_id) bua
                                 ON bua.entry_id = ac.entry_id
LEFT JOIN (SELECT array_agg(ARRAY[buc.label, bu.control_code])
                  as business_units, entry_id
             FROM business_unit_class buc
             JOIN business_unit bu ON bu.class_id = buc.id
             JOIN business_unit_inv buinv ON buinv.bu_id = bu.id
         GROUP BY buinv.entry_id) bui
                                 ON bui.entry_id = inv.id
    WHERE ($1 IS NULL OR ac.chart_id = $1)
          AND ($2 IS NULL
                OR to_tsvector(get_default_lang()::regconfig, e.name)
                   @@ plainto_tsquery(get_default_lang()::regconfig, $2))
          AND ($3 IS NULL
                OR eca.meta_number LIKE $3 || '%')
          AND ($4 IS NULL or gl.invnumber LIKE $4 || '%')
          AND ($5 IS NULL or gl.ordnumber LIKE $5 || '%')
          AND ($6 IS NULL or gl.ponumber LIKE $6 || '%')
          AND ($8 IS NULL
                or to_tsvector(get_default_lang()::regconfig, gl.description)
                  @@ plainto_tsquery(get_default_lang()::regconfig, $8))
          AND ($9 IS NULL OR
                to_tsvector(get_default_lang()::regconfig, gl.notes)
                 @@ plainto_tsquery(get_default_lang()::regconfig, $9))
          AND ($11 IS NULL OR $11 <= gl.transdate)
          AND ($12 IS NULL OR $12 >= gl.transdate)
          AND ($13 IS NULL OR $13 = gl.on_hold)
          AND ($16 IS NULL OR $16 >= ac.transdate)
          AND ($18 is null
               OR (gl.approved = $18 AND ac.approved = $18))
 GROUP BY gl.id, gl.invnumber, gl.ordnumber, gl.ponumber, gl.transdate,
          gl.duedate, e.name, eca.meta_number, gl.amount_bc,
          gl.netamount_bc, gl.curr, gl.duedate,
          gl.notes, gl.shippingpoint, gl.shipvia, e.id, gl.invoice
   HAVING $7 = ANY(array_agg(ac.source)) or $7 IS NULL
$sql$
USING in_account_id, in_name_part, in_meta_number, in_invnumber,
 in_ordnumber, in_ponumber, in_source, in_description,
 in_notes, in_shipvia, in_from_date, in_to_date,
 in_on_hold, in_inc_open, in_inc_closed, in_as_of,
 in_entity_class, in_approved;
END

Function: ar_ap__transaction_search_summary(in_account_id integer, in_name_part text, in_meta_number text, in_invnumber text, in_ordnumber text, in_ponumber text, in_source text, in_description text, in_notes text, in_shipvia text, in_from_date date, in_to_date date, in_on_hold boolean, in_inc_open boolean, in_inc_closed boolean, in_as_of date, in_entity_class integer, in_approved boolean)

Returns: SET OF purchase_info

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
       SELECT null::int, null::bool, null::text, null::text, null::text,
              null::date, entity_name, meta_number::text, entity_id, sum(amount_bc),
              sum(amount_paid), sum(tax), currency, null::date,
              null::text, null::text, null::text, null::text[]
         FROM ar_ap__transaction_search
              ($1, $2, $3, $4,
              $5, $6, $7, $8,
              $9, $10, $11, $12,
              $13, $14, $15, $16,
              $17, $18)
     GROUP BY entity_name, meta_number, entity_id, currency
$sql$
USING in_account_id, in_name_part, in_meta_number, in_invnumber,
 in_ordnumber, in_ponumber, in_source, in_description,
 in_notes, in_shipvia, in_from_date, in_to_date,
 in_on_hold, in_inc_open, in_inc_closed, in_as_of,
 in_entity_class, in_approved;
END

Function: array_endswith(elem anyelement, arr anyarray)

Returns: boolean

Language: SQL

   SELECT $2[array_upper($2,1)]=$1;

Function: array_splice_from(elem anyelement, arr anyarray)

Returns: anyarray

Language: SQL

    select $2[i:array_upper($2,1)]
      from generate_subscripts($2,1) as i
     where $2[i] = $1
     order by i
     limit 1;
  

Function: array_splice_to(element anyelement, arr anyarray)

Returns: anyarray

Language: SQL

   select $2[1:i]
     from generate_subscripts($2,1) as i
    where $2[i] = $1
   order by i
   limit 1;
 

Function: assembly__stock(in_parts_id integer, in_qty numeric)

Returns: numeric

Language: SQL

    INSERT INTO mfg_lot(parts_id, qty) VALUES ($1, $2);
    INSERT INTO mfg_lot_item(mfg_lot_id, parts_id, qty)
    SELECT currval('mfg_lot_id_seq')::int, parts_id, qty * $2
      FROM assembly WHERE id = $1;

    SELECT mfg_lot__commit(currval('mfg_lot_id_seq')::int);

Function: asset__get(in_id integer, in_tag text)

Returns: asset_item

Language: SQL

Retrieves a given asset either by id or tag. Both are complete matches. Note that the behavior is undefined if both id and tag are provided.

        SELECT * from asset_item WHERE id = in_id OR in_tag = tag
        ORDER BY id desc limit 1;

Function: asset__import_from_disposal(in_id integer)

Returns: boolean

Language: PLPGSQL

Imports items from partial disposal reports. This function should not be called dirctly by programmers but rather through the other disposal approval api's.

DECLARE t_report asset_report;
        t_import asset_report;
BEGIN

    SELECT * INTO t_report from asset_report where id = in_id;

    if t_report.report_class <> 4 THEN RETURN FALSE;
    END IF;

    SELECT *
      INTO t_import
      FROM  asset_report__begin_import
            (t_report.asset_class::int, t_report.report_date);

    PERFORM asset_report__import(
        ai.description,
        ai.tag,
        ai.purchase_value * ( 100 - rld.percent_disposed ) / 100,
        ai.salvage_value * ( 100 - rld.percent_disposed ) / 100,
        ai.usable_life,
        ai.purchase_date,
        ai.start_depreciation,
        ai.location_id,
        ai.department_id,
        ai.asset_account_id,
        ai.dep_account_id,
        ai.exp_account_id,
        ai.asset_class_id,
        ai.invoice_id,
        t_import.id,
        r.accum_depreciation * ( 100 - rld.percent_disposed ) / 100,
        TRUE)
    FROM asset_item ai
    JOIN asset_report__get_disposal(t_report.id) r  ON (ai.id = r.id)
    JOIN asset_report_line rl ON (rl.asset_id = ai.id AND rl.report_id = in_id)
    join asset_rl_to_disposal_method rld
         ON (rl.report_id = rld.report_id and ai.id = rld.asset_id)
   where (rld.percent_disposed is null or percent_disposed < 100)
         and ai.obsolete_by is null;
   RETURN TRUE;
END;

Function: asset__save(in_id integer, in_asset_class integer, in_description text, in_tag text, in_purchase_date date, in_purchase_value numeric, in_usable_life numeric, in_salvage_value numeric, in_start_depreciation date, in_warehouse_id integer, in_department_id integer, in_invoice_id integer, in_asset_account_id integer, in_dep_account_id integer, in_exp_account_id integer, in_obsolete_by integer)

Returns: asset_item

Language: PLPGSQL

Saves the asset with the information provided. If the id is provided, overwrites the record with the id. Otherwise, or if that record is not found, inserts. Returns the row inserted or updated.

DECLARE ret_val asset_item;
BEGIN
        UPDATE asset_item
        SET asset_class_id = in_asset_class,
                description = in_description,
                tag = in_tag,
                purchase_date = in_purchase_date,
                purchase_value = in_purchase_value,
                usable_life = in_usable_life,
                location_id = in_warehouse_id,
                department_id = in_department_id,
                invoice_id = in_invoice_id,
                salvage_value = in_salvage_value,
                asset_account_id = in_asset_account_id,
                exp_account_id = in_exp_account_id,
                start_depreciation =
                         coalesce(in_start_depreciation, in_purchase_date),
                dep_account_id = in_dep_account_id,
                obsolete_by = in_obsolete_by
        WHERE id = in_id;
        IF FOUND THEN
                SELECT * INTO ret_val FROM asset_item WHERE id = in_id;
                return ret_val;
        END IF;

        INSERT INTO asset_item (asset_class_id, description, tag, purchase_date,
                purchase_value, usable_life, salvage_value, department_id,
                location_id, invoice_id, asset_account_id, dep_account_id,
                start_depreciation, exp_account_id, obsolete_by)
        VALUES (in_asset_class, in_description, in_tag, in_purchase_date,
                in_purchase_value, in_usable_life, in_salvage_value,
                in_department_id, in_warehouse_id, in_invoice_id,
                in_asset_account_id, in_dep_account_id,
                coalesce(in_start_depreciation, in_purchase_date),
                in_exp_account_id, in_obsolete_by);

        SELECT * INTO ret_val FROM asset_item
        WHERE id = currval('asset_item_id_seq');
        RETURN ret_val;
END;

Function: asset__search(in_asset_class integer, in_description text, in_tag text, in_purchase_date date, in_purchase_value numeric, in_usable_life numeric, in_salvage_value numeric)

Returns: SET OF asset_item

Language: PLPGSQL

Searches for assets. Nulls match all records. Asset class is exact, as is purchase date, purchase value, and salvage value. Tag and description are partial matches.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT * FROM asset_item
                WHERE ($1 is null
                        or asset_class_id = $1)
                        AND ($2 is null or description
                                LIKE '%' || $2 || '%')
                        and ($3 is null or tag like '%'||$3||'%')
                        AND ($4 is null
                                or purchase_date = $4)
                        AND ($5 is null
                                or $5 = purchase_value)
                        AND ($6 is null
                                or $6 = usable_life)
                        AND ($7 is null
                                OR $7 = salvage_value)
$sql$
USING in_asset_class, in_description, in_tag, in_purchase_date,
 in_purchase_value, in_usable_life, in_salvage_value;
END

Function: asset_class__get(in_id integer)

Returns: asset_class

Language: SQL

returns the row from asset_class identified by in_id.

        SELECT * FROM asset_class WHERE id = in_id;

Function: asset_class__get_asset_accounts()

Returns: SET OF account

Language: SQL

Returns a list of fixed asset accounts, ordered by account number

SELECT * FROM account
WHERE id IN
        (select account_id from account_link where description = 'Fixed_Asset')
ORDER BY accno;

Function: asset_class__get_dep_accounts()

Returns: SET OF account

Language: SQL

Returns a list of asset depreciation accounts, ordered by account number

SELECT * FROM account
WHERE id IN
        (select account_id from account_link where description = 'Asset_Dep')
ORDER BY accno;

Function: asset_class__get_dep_method(in_asset_class integer)

Returns: asset_dep_method

Language: SQL

Returns the depreciation method associated with the asset class.

SELECT * from asset_dep_method
WHERE id = (select method from asset_class where id = in_asset_class);

Function: asset_class__get_dep_methods()

Returns: SET OF asset_dep_method

Language: SQL

Returns a set of asset_dep_methods ordered by the method label.

SELECT * FROM asset_dep_method ORDER BY method;

Function: asset_class__list()

Returns: SET OF asset_class

Language: SQL

Returns an alphabetical list of asset classes.

SELECT * FROM asset_class ORDER BY label;

Function: asset_class__save(in_id integer, in_asset_account_id integer, in_dep_account_id integer, in_method integer, in_label text, in_unit_label text)

Returns: asset_class

Language: PLPGSQL

Saves this data as an asset_class record. If in_id is NULL or is not found in the table, inserts a new row. Returns the row saved.

DECLARE ret_val asset_class;
BEGIN
        UPDATE asset_class
        SET asset_account_id = in_asset_account_id,
                dep_account_id = in_dep_account_id,
                method = in_method,
                label = in_label
        WHERE id = in_id;

        IF FOUND THEN
                SELECT * INTO ret_val FROM asset_class where id = in_id;
                RETURN ret_val;
        END IF;

        INSERT INTO asset_class (asset_account_id, dep_account_id, method,
                label)
        VALUES (in_asset_account_id, in_dep_account_id, in_method,
                in_label);

        SELECT * INTO ret_val FROM asset_class
        WHERE id = currval('asset_class_id_seq');

        RETURN ret_val;
END;

Function: asset_class__search(in_asset_account_id integer, in_dep_account_id integer, in_method integer, in_label text)

Returns: SET OF asset_class_result

Language: PLPGSQL

Returns a list of matching asset classes. The account id's are exact matches as is the method, but the label is a partial match. NULL's match all.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT ac.id, ac.asset_account_id, aa.accno, aa.description,
                        ac.dep_account_id, ad.accno, ad.description,
                        m.method, ac.method,
                        ac.label
                FROM asset_class ac
                LEFT JOIN account aa ON (aa.id = ac.asset_account_id)
                LEFT JOIN account ad ON (ad.id = ac.dep_account_id)
                JOIN asset_dep_method m ON (ac.method = m.id)
                WHERE
                        ($1 is null
                                or $1 = ac.asset_account_id)
                        AND ($2 is null OR
                                $2 = ac.dep_account_id)
                        AND ($3 is null OR $3 = ac.method)
                        AND ($4 IS NULL OR ac.label LIKE
                                '%' || $4 || '%')
               ORDER BY label
$sql$
USING in_asset_account_id, in_dep_account_id, in_method, in_label;
END

Function: asset_dep__straight_line_base(in_base_life numeric, in_used numeric, in_basis numeric, in_dep_to_date numeric)

Returns: numeric

Language: SQL

This function is a basic function which does the actual calculation for straight line depreciation.

SELECT CASE WHEN in_used/in_base_life * in_basis < in_basis - in_dep_to_date
                 THEN in_used/in_base_life * in_basis
            ELSE in_basis - in_dep_to_date
            END;

Function: asset_dep__used_months(in_last_dep date, in_dep_date date, in_usable_life numeric)

Returns: numeric

Language: SQL

This checks the interval between the two dates, and if longer than the usable life, returns the months in that interval. Otherwise returns the usable life.

select CASE WHEN extract('MONTHS' FROM (date_trunc('day', in_dep_date) - date_trunc('day', in_last_dep)))
                 > in_usable_life
            THEN in_usable_life
            ELSE extract('MONTHS' FROM (date_trunc('day', in_dep_date) - date_trunc('day', in_last_dep)))::numeric
            END;

Function: asset_dep_get_usable_life_yr(in_usable_life numeric, in_start_date date, in_dep_date date)

Returns: numeric

Language: SQL

If the interval is less than 0 then 0. If the interval is greater than the usable life, then the usable life. Otherwise, return the interval as a fractional year.

   SELECT CASE WHEN in_dep_date IS NULL
                    or get_fractional_year(in_start_date, in_dep_date) > in_usable_life
               then in_usable_life
               WHEN get_fractional_year(in_start_date, in_dep_date) < 0
               THEN 0
               ELSE get_fractional_year(in_start_date, in_dep_date)
          END;

Function: asset_dep_straight_line_month(in_asset_ids integer[], in_report_date date, in_report_id integer)

Returns: boolean

Language: SQL

Performs straight line depreciation, selecting depreciation amounts, etc. into a report for further review and approval. Usable life is in months, and depreciation is an equal amount every month.

     INSERT INTO asset_report_line (asset_id, report_id, amount, department_id,
                                   warehouse_id)
     SELECT ai.id, in_report_id,
            asset_dep__straight_line_base(
                  ai.usable_life, --months
                  months_passed(coalesce(max(report_date),
                                         start_depreciation),
                                in_report_date),
                  purchase_value - salvage_value,
                  coalesce(sum(l.amount), 0)),
            ai.department_id, ai.location_id
       FROM asset_item ai
  LEFT JOIN asset_report_line l ON (l.asset_id = ai.id and l.amount > 0)
  LEFT JOIN asset_report r ON (l.report_id = r.id)
      WHERE ai.id = ANY(in_asset_ids)
   GROUP BY ai.id, ai.start_depreciation, ai.purchase_date, ai.purchase_value,
            ai.salvage_value, ai.department_id, ai.location_id, ai.usable_life;

    UPDATE asset_report SET report_class = 1 WHERE id = in_report_id;

    select true;

Function: asset_dep_straight_line_yr_d(in_asset_ids integer[], in_report_date date, in_report_id integer)

Returns: boolean

Language: SQL

     INSERT INTO asset_report_line (asset_id, report_id, amount, department_id,
                                   warehouse_id)
     SELECT ai.id, in_report_id,
            asset_dep__straight_line_base(
                  ai.usable_life, -- years
                  get_fractional_year(coalesce(max(report_date),
                                               start_depreciation),
                                      in_report_date),
                  purchase_value - salvage_value,
                  coalesce(sum(l.amount), 0)),
            ai.department_id, ai.location_id
       FROM asset_item ai
  LEFT JOIN asset_report_line l ON (l.asset_id = ai.id and l.amount > 0)
  LEFT JOIN asset_report r ON (l.report_id = r.id)
      WHERE ai.id = ANY(in_asset_ids)
   GROUP BY ai.id, ai.start_depreciation, ai.purchase_date, ai.purchase_value,
            ai.salvage_value, ai.department_id, ai.location_id, ai.usable_life;

    UPDATE asset_report SET report_class = 1 WHERE id = in_report_id;

    select true;

Function: asset_dep_straight_line_yr_m(in_asset_ids integer[], in_report_date date, in_report_id integer)

Returns: boolean

Language: SQL

Performs straight line depreciation on a set of selected assets, selecting the depreciation values into a report. Assumes the usable life is measured in years, and is depreciated eavenly every month.

     INSERT INTO asset_report_line (asset_id, report_id, amount, department_id,
                                   warehouse_id)
     SELECT ai.id, in_report_id,
            asset_dep__straight_line_base(
                  ai.usable_life * 12, --months
                  months_passed(coalesce(max(report_date),
                                         start_depreciation),
                                in_report_date),
                  purchase_value - salvage_value,
                  coalesce(sum(l.amount), 0)),
            ai.department_id, ai.location_id
       FROM asset_item ai
  LEFT JOIN asset_report_line l ON (l.asset_id = ai.id and l.amount > 0)
  LEFT JOIN asset_report r ON (l.report_id = r.id)
      WHERE ai.id = ANY(in_asset_ids)
   GROUP BY ai.id, ai.start_depreciation, ai.purchase_date, ai.purchase_value,
            ai.salvage_value, ai.department_id, ai.location_id, ai.usable_life;

    UPDATE asset_report SET report_class = 1 WHERE id = in_report_id;

    select true;

Function: asset_disposal__approve(in_id integer, in_gain_acct integer, in_loss_acct integer, in_asset_acct integer)

Returns: asset_report

Language: PLPGSQL

This approves the asset_report for disposals, creating relevant GL drafts. If the report is a partial disposal report, imports remaining percentages as new asset items.

DECLARE
   retval asset_report;
   iter record;
   t_disposed_percent numeric;
begin
-- this code is fairly opaque and needs more documentation that would be
-- otherwise optimal. This is mostly due to the fact that we have fairly
-- repetitive insert/select routines and the fact that the accounting
-- requirements are not immediately intuitive.  Inserts marked functionally along
-- with typical debit/credit designations.  Note debits are always negative.


retval := asset_report__record_approve(in_id);
if retval.report_class = 2 then
     t_disposed_percent := 100;
end if;

INSERT INTO gl (reference, description, approved, transdate, trans_type_code)
select 'Asset Report ' || in_id, 'Asset Disposal Report for ' || report_date,
       false, report_date, 'fd'
 FROM asset_report where id = in_id;

-- REMOVING ASSETS FROM ACCOUNT (Credit)
insert into acc_trans (trans_id, chart_id, amount_bc, curr, amount_tc,
                       approved, transdate)
SELECT currval('id'), a.asset_account_id,
       a.purchase_value
       * (coalesce(t_disposed_percent, m.percent_disposed)/100),
       defaults_get_defaultcurrency(),
       a.purchase_value
       * (coalesce(t_disposed_percent, m.percent_disposed)/100),
       true, r.report_date
 FROM  asset_item a
 JOIN  asset_report_line l ON (l.asset_id = a.id)
 JOIN  asset_report r ON (r.id = l.report_id)
 JOIN  asset_rl_to_disposal_method m
        ON (l.report_id = m.report_id and l.asset_id = m.asset_id)
 WHERE r.id = in_id;

-- REMOVING ACCUM DEP. (Debit)
INSERT into acc_trans (trans_id, chart_id, amount_bc, curr, amount_tc,
                       approved, transdate)
SELECT currval('id'), a.dep_account_id,
       sum(dl.amount) * -1
       * (coalesce(t_disposed_percent, m.percent_disposed)/100),
       defaults_get_defaultcurrency(),
       sum(dl.amount) * -1
       * (coalesce(t_disposed_percent, m.percent_disposed)/100),
       true, r.report_date
 FROM  asset_item a
 JOIN  asset_report_line l ON (l.asset_id = a.id)
 JOIN  asset_report r ON (r.id = l.report_id)
 JOIN  asset_report_line dl ON (l.asset_id = dl.asset_id)
 JOIN  asset_rl_to_disposal_method m
        ON (l.report_id = m.report_id and l.asset_id = m.asset_id)
 JOIN  asset_report dr ON (dl.report_id = dr.id
                           and dr.report_class = 1
                           and dr.approved_at is not null)
 WHERE r.id = in_id
group by a.dep_account_id, m.percent_disposed, r.report_date;

-- INSERT asset/proceeds (Debit, credit for negative values)
INSERT INTO acc_trans (trans_id, chart_id, amount_bc, curr, amount_tc,
                       approved, transdate)
SELECT currval('id'), in_asset_acct, coalesce(l.amount, 0) * -1,
       defaults_get_defaultcurrency(), coalesce(l.amount, 0) * -1,
       true, r.report_date
 FROM  asset_item a
 JOIN  asset_report_line l ON (l.asset_id = a.id)
 JOIN  asset_report r ON (r.id = l.report_id)
 JOIN  asset_rl_to_disposal_method m
        ON (l.report_id = m.report_id and l.asset_id = m.asset_id)
 WHERE r.id = in_id;

-- INSERT GAIN/LOSS (Credit for gain, debit for loss)
INSERT INTO acc_trans(trans_id, chart_id, amount_bc, curr, amount_tc,
                      approved, transdate)
select currval('id'),
            CASE WHEN sum(amount_bc) > 0 THEN in_loss_acct
            else in_gain_acct
        END,
        sum(amount_bc) * -1, defaults_get_defaultcurrency(),
        sum(amount_tc) * -1 , true,
        retval.report_date
  FROM acc_trans
  WHERE trans_id = currval('id');

IF retval.report_class = 4 then
   PERFORM asset__import_from_disposal(retval.id);
end if;

return retval;
end;

Function: asset_item__add_note(in_id integer, in_subject text, in_note text)

Returns: asset_note

Language: SQL

Adds a note to an asset item

INSERT INTO asset_note (ref_key, subject, note)
                values (in_id, in_subject, in_note);
SELECT * FROM asset_note WHERE id = currval('note_id_seq');

Function: asset_item__search(in_id integer, in_asset_class integer, in_description text, in_tag text, in_purchase_date date, in_purchase_value numeric, in_usable_life numeric, in_salvage_value numeric, in_start_depreciation date, in_warehouse_id integer, in_department_id integer, in_invoice_id integer, in_asset_account_id integer, in_dep_account_id integer)

Returns: SET OF asset_item

Language: PLPGSQL

Returns a list of matching asset items. Nulls match all records. Tag and description allow for partial match. All other matches are exact.

BEGIN
RETURN QUERY EXECUTE $sql$
         SELECT * FROM asset_item
          WHERE (id = $1 or $1 is null)
                and (asset_class_id = $2 or $2 is null)
                and (description like '%'||$3||'%'
                     or $3 is null)
                and (tag like '%' || $4 || '%' or $4 is null)
                and (purchase_value = $6
                    or $6 is null)
                and ($5 = purchase_date
                    or $5 is null)
                and (start_depreciation = $9
                    or $9 is null)
                and ($10 = location_id OR $10 is null)
                and (department_id = $11
                    or $11 is null)
                and ($12 = invoice_id OR $12 IS NULL)
                and (asset_account_id = $13
                    or $13 is null)
                and (dep_account_id = $14
                    or $14 is null)
$sql$
USING in_id, in_asset_class, in_description, in_tag,
in_purchase_date, in_purchase_value, in_usable_life, in_salvage_value,
in_start_depreciation, in_warehouse_id, in_department_id, in_invoice_id,
in_asset_account_id, in_dep_account_id;
END

Function: asset_nbv_report()

Returns: SET OF asset_nbv_line

Language: PLPGSQL

Returns the current net book value report.

BEGIN
RETURN QUERY EXECUTE $sql$
   SELECT ai.id, ai.tag, ai.description, ai.start_depreciation,
          adm.short_name, ai.usable_life
           - months_passed(ai.start_depreciation,
                                  coalesce(max(r.report_date),
                                    ai.start_depreciation))/(case when adm.unit_label='in years' then 12 else 1 end),
          ai.purchase_value - ai.salvage_value, ai.salvage_value, max(r.report_date),
          sum(rl.amount), ai.purchase_value - coalesce(sum(rl.amount), 0)
     FROM asset_item ai
     JOIN asset_class ac ON (ai.asset_class_id = ac.id)
     JOIN asset_dep_method adm ON (adm.id = ac.method)
LEFT JOIN (select arl.*
             from asset_report_line arl
             join asset_report ar on arl.report_id = ar.id
            where approved_at is not null) rl ON (ai.id = rl.asset_id)
LEFT JOIN asset_report r on (rl.report_id = r.id)
    WHERE r.id IS NULL OR r.approved_at IS NOT NULL
 GROUP BY ai.id, ai.tag, ai.description, ai.start_depreciation, ai.purchase_date,
          adm.short_name, adm.unit_label, ai.usable_life, ai.purchase_value, salvage_value
   HAVING (NOT 2 = ANY(array_agg(r.report_class)))
          AND (NOT 4 = ANY(array_agg(r.report_class)))
          OR max(r.report_class) IS NULL
 ORDER BY ai.id, ai.tag, ai.description
$sql$;
END

Function: asset_report__approve(in_id integer, in_expense_acct integer, in_gain_acct integer, in_loss_acct integer, in_cash_acct integer)

Returns: asset_report

Language: PLPGSQL

This function approves an asset report (whether depreciation or disposal). Also generates relevant GL drafts for review and posting.

DECLARE ret_val asset_report;
BEGIN
        UPDATE asset_report
           SET approved_at = now(),
               approved_by = person__get_my_entity_id()
         where id = in_id;
        SELECT * INTO ret_val FROM asset_report WHERE id = in_id;
        if ret_val.dont_approve is not true then
                if ret_val.report_class = 1 THEN
                    PERFORM asset_report__generate_gl(in_id, in_expense_acct);
                ELSIF ret_val.report_class = 2 THEN
                    PERFORM asset_report__disposal_gl(
                                 in_id, in_gain_acct, in_loss_acct, in_cash_acct);
                ELSIF ret_val.report_class = 4 THEN
                    PERFORM asset_disposal__approve(in_id, in_gain_acct, in_loss_acct, (select asset_account_id from asset_class
                                                                                         where id = ret_val.asset_class)
                                                   );
                ELSE RAISE EXCEPTION 'Invalid report class';
                END IF;
        end if;
        SELECT * INTO ret_val FROM asset_report WHERE id = in_id;
        RETURN ret_val;
end;

Function: asset_report__begin_disposal(in_asset_class integer, in_report_date date, in_report_class integer)

Returns: asset_report

Language: PLPGSQL

Creates the asset report record for the asset disposal report.

DECLARE retval asset_report;

begin

INSERT INTO asset_report (asset_class, report_date, entered_at, entered_by,
            report_class)
     VALUES (in_asset_class, in_report_date, now(), person__get_my_entity_id(),
            in_report_class);

SELECT * INTO retval FROM asset_report where id = currval('asset_report_id_seq');

return retval;

end;


Function: asset_report__begin_import(in_asset_class integer, in_report_date date)

Returns: asset_report

Language: SQL

Creates the outline of an asset import report

INSERT INTO asset_report (asset_class, report_date, entered_at, entered_by,
            report_class, dont_approve)
     VALUES (in_asset_class, in_report_date, now(), person__get_my_entity_id(),
            3, true);

SELECT * FROM asset_report where id = currval('asset_report_id_seq');


Function: asset_report__disposal_gl(in_id integer, in_gain_acct integer, in_loss_acct integer, in_cash_acct integer)

Returns: boolean

Language: SQL

Generates GL transactions for ful disposal reports.

  INSERT INTO gl (reference, description, transdate, approved, trans_type_code)
  SELECT setting_increment('glnumber'), 'Asset Report ' || asset_report.id,
                report_date, false, 'fd'
    FROM asset_report
  WHERE asset_report.id = in_id;

  -- Clear cumulative depreciation account
  INSERT
    INTO acc_trans (chart_id, trans_id, amount_bc, curr, amount_tc,
                    approved, transdate)
  SELECT a.dep_account_id, currval('id')::int, sum(r.accum_depreciation) * -1,
         defaults_get_defaultcurrency(), sum(r.accum_depreciation) * -1,
         TRUE, r.disposed_on
    FROM asset_report__get_disposal(in_id) r
    JOIN asset_item a ON (r.id = a.id)
   GROUP BY a.dep_account_id, r.disposed_on
  HAVING sum(r.accum_depreciation) <> 0;

  -- Add cash from sale(=disposal)
  INSERT
    INTO acc_trans (chart_id, trans_id, amount_bc, curr, amount_tc,
                    approved, transdate)
  SELECT in_cash_acct, currval('id')::int, sum(r.disposal_amt) * -1,
         defaults_get_defaultcurrency(), sum(r.disposal_amt) * -1,
         TRUE, r.disposed_on
    FROM asset_report__get_disposal(in_id) r
    JOIN asset_item ai ON (r.id = ai.id)
   GROUP BY r.disposed_on
  HAVING sum(r.disposal_amt) <> 0;

  -- GAIN is negative since it is a debit
  INSERT
    INTO acc_trans (chart_id, trans_id, amount_bc, curr, amount_tc,
                    approved, transdate)
  SELECT case when sum(r.gain_loss) > 0 THEN in_loss_acct else in_gain_acct end,
         currval('id')::int, sum(r.gain_loss), defaults_get_defaultcurrency(),
         sum(r.gain_loss),
         TRUE, r.disposed_on
    FROM asset_report__get_disposal(in_id) r
    JOIN asset_item ai ON (r.id = ai.id)
   GROUP BY r.disposed_on;

  -- Clear asset from asset account
  INSERT
    INTO acc_trans (chart_id, trans_id, amount_bc, curr, amount_tc,
                    approved, transdate)
  SELECT a.asset_account_id, currval('id')::int, sum(r.purchase_value),
         defaults_get_defaultcurrency(), sum(r.purchase_value),
         TRUE, r.disposed_on
    FROM asset_report__get_disposal(in_id) r
    JOIN asset_item a ON (r.id = a.id)
   GROUP BY a.asset_account_id, r.disposed_on
  HAVING sum(r.purchase_value) <> 0;


  SELECT TRUE;

Function: asset_report__dispose(in_id integer, in_asset_id integer, in_amount numeric, in_dm integer, in_percent_disposed numeric)

Returns: boolean

Language: PLPGSQL

Disposes of an asset. in_dm is the disposal method id.

BEGIN
    INSERT
      INTO asset_report_line (report_id, asset_id, amount)
    values (in_id, in_asset_id, in_amount);

    INSERT
      INTO asset_rl_to_disposal_method
           (report_id, asset_id, disposal_method_id, percent_disposed)
    VALUES (in_id, in_asset_id, in_dm, in_percent_disposed);

    RETURN TRUE;
    END;

Function: asset_report__generate(in_depreciation boolean, in_asset_class integer, in_report_date date)

Returns: SET OF asset_item

Language: SQL

Generates lines to select/deselect for the asset report (depreciation or disposal).

   SELECT ai.*
     FROM asset_item ai
     JOIN asset_class ac ON (ai.asset_class_id = ac.id)
LEFT JOIN asset_report_line arl ON (arl.asset_id = ai.id)
LEFT JOIN asset_report ar ON (arl.report_id = ar.id)
    WHERE ai.start_depreciation <= in_report_date AND ac.id = in_asset_class
          AND obsolete_by IS NULL
 GROUP BY ai.id, ai.tag, ai.description, ai.purchase_value, ai.usable_life,
          ai.purchase_date, ai.location_id, ai.invoice_id, ai.asset_account_id,
          ai.dep_account_id, ai.asset_class_id, ai.start_depreciation,
          ai.salvage_value, ai.department_id, ai.exp_account_id, ai.obsolete_by
   HAVING (count(ar.report_class) = 0 OR
          (2 <> ALL(array_agg(ar.report_class))
          and 4 <> ALL(array_agg(ar.report_class))))
          AND ((ai.purchase_value - coalesce(sum(arl.amount), 0)
               > ai.salvage_value) and ai.obsolete_by is null)
               OR in_depreciation is not true;

Function: asset_report__generate_gl(in_report_id integer, in_accum_account_id integer)

Returns: integer

Language: PLPGSQL

Generates a GL transaction when the Asset report is approved. Create approved transactions, unless the value of the setting_key 'debug_fixed_assets' evaluates to false

DECLARE
        t_report_dept record;
        t_dep_amount numeric;

Begin
        INSERT INTO gl (reference, description, transdate,
                        approved, trans_type_code)
        SELECT setting_increment('glnumber'),
               'Asset Report ' || asset_report.id,
                report_date,
                coalesce((select value::boolean from defaults
                           where setting_key = 'debug_fixed_assets'), true),
                'fa'
        FROM asset_report
        JOIN asset_report_line
                ON (asset_report.id = asset_report_line.report_id)
        JOIN asset_item
                ON (asset_report_line.asset_id = asset_item.id)
        WHERE asset_report.id = in_report_id
        GROUP BY asset_report.id, asset_report.report_date;

        INSERT INTO acc_trans (trans_id, chart_id, transdate, approved,
                              amount_bc, curr, amount_tc)
        SELECT gl.id, a.exp_account_id, r.report_date, true, sum(amount) * -1,
               defaults_get_defaultcurrency(), sum(amount) * -1
        FROM asset_report r
        JOIN asset_report_line l ON (r.id = l.report_id)
        JOIN asset_item a ON (l.asset_id = a.id)
        JOIN gl ON (gl.description = 'Asset Report ' || l.report_id)
        WHERE r.id = in_report_id
        GROUP BY gl.id, r.report_date, a.exp_account_id;

        INSERT INTO acc_trans (trans_id, chart_id, transdate, approved,
                               amount_bc, curr, amount_tc)
        SELECT gl.id, a.dep_account_id, r.report_date, true, sum(amount),
               defaults_get_defaultcurrency(), sum(amount)
        FROM asset_report r
        JOIN asset_report_line l ON (r.id = l.report_id)
        JOIN asset_item a ON (l.asset_id = a.id)
        JOIN gl ON (gl.description = 'Asset Report ' || l.report_id)
        WHERE r.id = in_report_id
        GROUP BY gl.id, a.dep_account_id, r.report_date, a.tag, a.description;

        RETURN in_report_id;
END;

Function: asset_report__get(in_id integer)

Returns: asset_report

Language: SQL

Returns the asset_report line identified by id.

select * from asset_report where id = in_id;

Function: asset_report__get_cash_accts()

Returns: SET OF account

Language: SQL

  SELECT * FROM account where not obsolete;

Function: asset_report__get_disposal(in_id integer)

Returns: SET OF asset_disposal_report_line

Language: SQL

Returns a set of lines of disposed assets in a disposal report, specified by the report id.

   SELECT ai.id, ai.tag, ai.description, ai.start_depreciation, r.report_date,
          dm.short_label, ai.purchase_value,
          sum (CASE WHEN pr.report_class in (1,3) THEN prl.amount ELSE 0 END)
          as accum_dep,
          l.amount,
          ai.purchase_value - sum(CASE WHEN pr.report_class in (1,3)
                                       THEN prl.amount
                                       ELSE 0
                                   END) as adjusted_basis,
          l.amount - ai.purchase_value + sum(CASE WHEN pr.report_class in (1,3)
                                                  THEN prl.amount
                                                  ELSE 0
                                              END) as gain_loss
     FROM asset_item ai
     JOIN asset_report_line l   ON (l.report_id = in_id AND ai.id = l.asset_id)
     JOIN asset_report r        ON (l.report_id = r.id)
LEFT JOIN asset_rl_to_disposal_method adm
                             USING (report_id, asset_id)
     JOIN asset_disposal_method dm
                                ON (adm.disposal_method_id = dm.id)
LEFT JOIN asset_report_line prl ON (prl.report_id <> in_id
                                   AND ai.id = prl.asset_id)
LEFT JOIN asset_report pr       ON (prl.report_id = pr.id)
 GROUP BY ai.id, ai.tag, ai.description, ai.start_depreciation, r.report_date,
          ai.purchase_value, l.amount, dm.short_label
 ORDER BY ai.id, ai.tag;

Function: asset_report__get_disposal_methods()

Returns: SET OF asset_disposal_method

Language: SQL

Returns a list of asset_disposal_method items ordered by label.

SELECT * FROM asset_disposal_method order by label;

Function: asset_report__get_expense_accts()

Returns: SET OF account

Language: SQL

Lists all asset expense reports.

    SELECT * FROM account__get_by_link_desc('asset_expense');

Function: asset_report__get_gain_accts()

Returns: SET OF account

Language: SQL

Returns a list of gain accounts for asset depreciation and disposal reports.

    SELECT * FROM account__get_by_link_desc('asset_gain');

Function: asset_report__get_lines(in_id integer)

Returns: SET OF asset_report_line_result

Language: SQL

Returns the lines of an asset depreciation report.

   select ai.tag, ai.start_depreciation, ai.purchase_value, m.short_name,
          ai.usable_life,
          ai.purchase_value - ai.salvage_value, max(pr.report_date),
          sum(case when pr.report_date < r.report_date then prl.amount
                   else 0
                end),
          rl.amount,
          sum (case when extract(year from pr.report_date)
                         = extract(year from r.report_date)
                         AND pr.report_date < r.report_date
                    then prl.amount
                    else 0
                end),
          sum(prl.amount),
          ai.description, ai.purchase_date
     FROM asset_item ai
     JOIN asset_class c ON (ai.asset_class_id = c.id)
     JOIN asset_dep_method m ON (c.method = m.id)
     JOIN asset_report_line rl ON (rl.asset_id = ai.id)
     JOIN asset_report r ON (rl.report_id = r.id)
LEFT JOIN asset_report_line prl ON (prl.asset_id = ai.id)
LEFT JOIN asset_report pr ON (prl.report_id = pr.id)
    WHERE rl.report_id = in_id
 GROUP BY ai.tag, ai.start_depreciation, ai.purchase_value, m.short_name,
          ai.usable_life, ai.salvage_value, r.report_date, rl.amount,
          ai.description, ai.purchase_date;

Function: asset_report__get_loss_accts()

Returns: SET OF account

Language: SQL

Returns a list of loss accounts for asset depreciation and disposal reports.

    SELECT * FROM account__get_by_link_desc('asset_loss');

Function: asset_report__import(in_description text, in_tag text, in_purchase_value numeric, in_salvage_value numeric, in_usable_life numeric, in_purchase_date date, in_start_depreciation date, in_location_id integer, in_department_id integer, in_asset_account_id integer, in_dep_account_id integer, in_exp_account_id integer, in_asset_class_id integer, in_invoice_id integer, in_dep_report_id integer, in_accum_dep numeric, in_obsolete_other boolean)

Returns: boolean

Language: SQL

Imports an asset with the supplied information. If in_obsolete_other is false, this creates a new depreciable asset. If it is true, it sets up the other asset as obsolete. This is the way partial disposal reports are handled.


INSERT
  INTO asset_report_line
       (report_id, asset_id, amount, department_id, warehouse_id)
select in_dep_report_id, id, in_accum_dep, department_id, location_id
  from asset__save
       (NULL, in_asset_class_id, in_description, in_tag, in_purchase_date, in_purchase_value, in_usable_life, coalesce(in_salvage_value, 0), in_start_depreciation, in_location_id, in_department_id, in_invoice_id, in_asset_account_id,
        in_dep_account_id, in_exp_account_id, (select min(id) from asset_item where tag = in_tag));
      -- use 'min(id)' because the first record in the series will be deprecated
      -- by by another one; chances are nil that it's actually deprecat*ing* one

UPDATE asset_item
   SET obsolete_by = currval('asset_item_id_seq')
 WHERE tag = in_tag and in_obsolete_other is true
       and id = (select min(id) from asset_item where tag = in_tag);

UPDATE asset_item
   SET obsolete_by = NULL
 WHERE tag = in_tag and in_obsolete_other is true
       and id = currval('asset_item_id_seq');

SELECT true;

Function: asset_report__record_approve(in_id integer)

Returns: asset_report

Language: SQL

Marks the asset_report record approved. Not generally recommended to call directly.

UPDATE asset_report
   set approved_by = person__get_my_entity_id(),
       approved_at = now()
 where id = in_id;

select * from asset_report where id = in_id;


Function: asset_report__save(in_id integer, in_report_date date, in_report_class integer, in_asset_class integer, in_submit boolean)

Returns: asset_report

Language: PLPGSQL

Creates or updates an asset report with the information presented. Note that approval values are not set here, and that one cannot unsubmit a report though this function.

DECLARE
        ret_val asset_report;
        item record;
        method_text text;
BEGIN
        UPDATE asset_report
        set asset_class = in_asset_class,
                report_class = in_report_class,
                report_date = in_report_date,
                submitted = (in_submit or submitted)
        WHERE id = in_id;

        IF FOUND THEN
                SELECT * INTO ret_val FROM asset_report WHERE id = in_id;
        ELSE
                INSERT INTO asset_report(report_class, asset_class, report_date,
                        submitted)
                values (in_report_class, in_asset_class, in_report_date,
                        coalesce(in_submit, true));

                SELECT * INTO ret_val FROM asset_report
                WHERE id = currval('asset_report_id_seq');

        END IF;
        RETURN ret_val;

END;

Function: asset_report__search(in_start_date date, in_end_date date, in_asset_class integer, in_approved boolean, in_entered_by integer)

Returns: SET OF asset_report_result

Language: SQL

Searches for asset reports. Nulls match all rows. Approved, asset class, and entered_by are exact matches. Start_date and end_date define the beginning and end of the search date.


  SELECT r.id, r.report_date, r.gl_id, r.asset_class, r.report_class,
         r.entered_by, r.approved_by, r.entered_at, r.approved_at,
         r.depreciated_qty, r.dont_approve, r.submitted, sum(l.amount)
    FROM asset_report r
    JOIN asset_report_line l ON (l.report_id = r.id)
   where (in_start_date is null or in_start_date <= report_date)
         and (in_end_date is null or in_end_date >= report_date)
         and (in_asset_class is null or in_asset_class = asset_class)
         and (in_approved is null
              or (in_approved is true and approved_by is not null)
              or (in_approved is false and approved_by is null))
         and (in_entered_by is null or in_entered_by = entered_by)
GROUP BY r.id, r.report_date, r.gl_id, r.asset_class, r.report_class,
         r.entered_by, r.approved_by, r.entered_at, r.approved_at,
         r.depreciated_qty, r.dont_approve, r.submitted;

Function: asset_report_partial_disposal_details(in_id integer)

Returns: SET OF partial_disposal_line

Language: SQL

Returns the partial disposal details for a partial disposal report.

SELECT ai.id, ai.tag, ai.description, ai.start_depreciation, ai.purchase_value,
       ar.report_date, arld.percent_disposed,
       (arld.percent_disposed / 100) * ai.purchase_value,
       100 - arld.percent_disposed,
       ((100 - arld.percent_disposed)/100) * ai.purchase_value
  FROM asset_item ai
  JOIN asset_report_line l ON (ai.id = l.asset_id)
  JOIN asset_report ar ON (ar.id = l.report_id)
  JOIN asset_rl_to_disposal_method arld
       ON  ((arld.report_id, arld.asset_id) = (l.report_id, l.asset_id))
 WHERE ar.id = in_id;

Function: avgcost(integer)

Returns: double precision

Language: PLPGSQL


DECLARE

v_cost float;
v_qty float;
v_parts_id alias for $1;

BEGIN

  SELECT INTO v_cost, v_qty SUM(i.sellprice * i.qty), SUM(i.qty)
  FROM invoice i
  JOIN ap a ON (a.id = i.trans_id)
  WHERE i.parts_id = v_parts_id;

  IF v_cost IS NULL THEN
    v_cost := 0;
  END IF;

  IF NOT v_qty IS NULL THEN
    IF v_qty = 0 THEN
      v_cost := 0;
    ELSE
      v_cost := v_cost/v_qty;
    END IF;
  END IF;

RETURN v_cost;
END;

Function: batch__lock(in_batch_id integer)

Returns: boolean

Language: SQL

UPDATE batch SET locked_by = (select max(session_id)
                                FROM "session" where users_id = (
                                        select id from users
                                         WHERE username = SESSION_USER))
 WHERE locked_by IS NULL
RETURNING true;

Function: batch__lock_for_update(in_batch_id integer)

Returns: batch

Language: SQL

Locks a batch for the duration of the running transaction. To be used when adding vouchers to the batch to prevent others from hitting the batch for other purposes (e.g. approval)

SELECT * FROM batch WHERE id = $1 FOR UPDATE;

Function: batch__search(in_class_id integer, in_description text, in_created_by_eid integer, in_date_from date, in_date_to date, in_amount_gt numeric, in_amount_lt numeric, in_approved boolean)

Returns: SET OF batch_list_item

Language: SQL

Returns a list of batches and amounts processed on the batch. Nulls match all values. in_date_from and in_date_to specify date ranges. in_description is a partial match. All other criteria are exact matches.

                SELECT b.id, c.class, b.control_code, b.description, u.username,
                        b.created_on, b.default_date,
                        sum(
                                CASE WHEN vc.id = 5 AND al.amount_bc < 0 -- GL
                                     THEN al.amount_bc
                                     WHEN vc.id  = 1
                                     THEN ap.amount_bc
                                     WHEN vc.id = 2
                 THEN ar.amount_bc
                                     ELSE 0
                                END) AS transaction_total,
                        sum(
                                CASE WHEN alc.description = 'AR' AND vc.id IN (6, 7)
                                     THEN al.amount_bc
                                     WHEN alc.description = 'AP' AND vc.id IN (3, 4)
                                     THEN al.amount_bc * -1
                                     ELSE 0
                                END
                           ) AS payment_total,
                     batch__lock(b.id)
                FROM batch b
                JOIN batch_class c ON (b.batch_class_id = c.id)
                LEFT JOIN users u ON (u.entity_id = b.created_by)
                LEFT JOIN voucher v ON (v.batch_id = b.id)
                LEFT JOIN batch_class vc ON (v.batch_class = vc.id)
                LEFT JOIN ar ON (vc.id = 2 AND v.trans_id = ar.id)
                LEFT JOIN ap ON (vc.id = 1 AND v.trans_id = ap.id)
                LEFT JOIN acc_trans al ON
                        ((vc.id = 5 AND v.trans_id = al.trans_id) OR
                                (vc.id IN (3, 4, 6, 7)
                                        AND al.voucher_id = v.id))
                LEFT JOIN account_link alc ON (al.chart_id = alc.account_id)
                WHERE (c.id = in_class_id OR in_class_id IS NULL) AND
                        (b.description LIKE
                                '%' || in_description || '%' OR
                                in_description IS NULL) AND
                        (in_created_by_eid = b.created_by OR
                                in_created_by_eid IS NULL) AND
                        (
                          (in_approved = false AND approved_on IS NULL)
                          OR (in_approved = true AND approved_on IS NOT NULL)
                          OR in_approved IS NULL
                        )
                        and (in_date_from IS NULL
                                or b.default_date >= in_date_from)
                        and (in_date_to IS NULL
                                or b.default_date <= in_date_to)
                GROUP BY b.id, c.class, b.description, u.username, b.created_on,
                        b.control_code, b.default_date
                HAVING
                        (in_amount_gt IS NULL OR
                        sum(coalesce(ar.amount_bc, ap.amount_bc,
                                al.amount_bc))
                        >= in_amount_gt)
                        AND
                        (in_amount_lt IS NULL OR
                        sum(coalesce(ar.amount_bc, ap.amount_bc,
                                al.amount_bc))
                        <= in_amount_lt)
                ORDER BY b.control_code, b.description


Function: batch__unlock(in_batch_id integer)

Returns: boolean

Language: SQL

UPDATE batch SET locked_by = NULL
 WHERE id = $1 AND locked_by IN (select session_id
                                   from "session" s
                                   join users u on (u.id = s.users_id)
                                  where username = SESSION_USER)
RETURNING true;

Function: batch_create(in_batch_number text, in_description text, in_batch_class text, in_batch_date date)

Returns: integer

Language: SQL

Inserts the batch into the table.

        INSERT INTO
                batch (batch_class_id, default_date, description, control_code,
                        created_by)
        VALUES ((SELECT id FROM batch_class WHERE class = in_batch_class),
                in_batch_date, in_description, in_batch_number,
                        (select entity_id FROM users WHERE username = session_user))
        RETURNING id;


Function: batch_delete(in_batch_id integer)

Returns: integer

Language: PLPGSQL

If the batch is found and unapproved, deletes it and returns 1. Otherwise raises an exception.

DECLARE
        t_transaction_ids int[];
        t_payment_ids int[];
BEGIN
        -- Adjust AR/AP tables for payment and payment reversal vouchers
        -- voucher_id is only set in acc_trans on payment/receipt vouchers and
        -- their reversals. -CT
        perform * from batch where id = in_batch_id and approved_on IS NULL;
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Batch not found';
        END IF;

        DELETE FROM ac_tax_form WHERE entry_id IN
               (select entry_id from acc_trans where voucher_id in
                       (select id from voucher where batch_id = in_batch_id)
               );

        WITH deleted_payment_ids AS (
           DELETE FROM payment_links p
            WHERE EXISTS (select 1 from acc_trans a
                           where p.entry_id = a.entry_id
                                 and a.voucher_id IN (select id from voucher
                                                       where batch_id = in_batch_id))
         RETURNING p.payment_id
        )
        SELECT array_agg(payment_id) INTO t_payment_ids
          FROM deleted_payment_ids;

        DELETE FROM payment
         WHERE id = any(t_payment_ids)
               AND NOT EXISTS (select 1 from payment_links
                                where payment_id = id);

        DELETE FROM acc_trans WHERE voucher_id IN
                (select id FROM voucher where batch_id = in_batch_id);

        -- The rest of this function involves the deletion of actual
        -- transactions, vouchers, and batches, and jobs which are in progress.
        -- -CT
        SELECT array_agg(trans_id) INTO t_transaction_ids
        FROM voucher WHERE batch_id = in_batch_id AND batch_class IN (1, 2, 5, 8, 9);

        DELETE FROM ac_tax_form WHERE entry_id in
               (select entry_id from acc_trans
                 where trans_id = any(t_transaction_ids));
        DELETE FROM invoice_tax_form WHERE invoice_id in
               (select id from invoice
                 where trans_id = any(t_transaction_ids));

        DELETE FROM invoice WHERE trans_id = ANY(t_transaction_ids);
        DELETE FROM acc_trans WHERE trans_id = ANY(t_transaction_ids);
        DELETE FROM voucher WHERE batch_id = in_batch_id;
        DELETE FROM batch WHERE id = in_batch_id;
        DELETE FROM ar WHERE id = ANY(t_transaction_ids);
        DELETE FROM ap WHERE id = ANY(t_transaction_ids);
        DELETE FROM gl WHERE id = ANY(t_transaction_ids);
        DELETE FROM transactions WHERE id = ANY(t_transaction_ids);

        RETURN 1;
END;

Function: batch_get_class_id(in_type text)

Returns: integer

Language: SQL

returns the batch class id associated with the in_type label provided.

SELECT id FROM batch_class WHERE class = $1;

Function: batch_get_class_name(in_class_id integer)

Returns: text

Language: SQL

returns the batch class name associated with the in_class_id id provided.

SELECT class FROM batch_class WHERE id = $1;

Function: batch_get_users()

Returns: SET OF users

Language: SQL

Returns a sim[ple set of user objects. This should be renamed so that it is more obvious it is a general purpose function.

                SELECT * from users WHERE entity_id IN (select created_by from batch)

Function: batch_list_classes()

Returns: SET OF batch_class

Language: PLPGSQL

Returns a list of all batch classes.

DECLARE out_val record;
BEGIN
        FOR out_val IN select * from batch_class order by id
        LOOP
                return next out_val;
        END LOOP;
END;

Function: batch_post(in_batch_id integer)

Returns: date

Language: SQL

Posts the specified batch to the books. Only posted batches should show up on standard financial reports.

        UPDATE ar SET approved = true
        WHERE id IN (select trans_id FROM voucher
                WHERE batch_id = in_batch_id
                AND batch_class = 2);

        UPDATE ap SET approved = true
        WHERE id IN (select trans_id FROM voucher
                WHERE batch_id = in_batch_id
                AND batch_class = 1);

        UPDATE gl SET approved = true
        WHERE id IN (select trans_id FROM voucher
                WHERE batch_id = in_batch_id);

        -- When approving the AR/AP batch import,
        -- we need to approve the acc_trans line also.
        UPDATE acc_trans SET approved = true
        WHERE trans_id IN (select trans_id FROM voucher
                WHERE batch_id = in_batch_id
                AND batch_class IN (1, 2));

        UPDATE acc_trans SET approved = true
        WHERE voucher_id IN (select id FROM voucher
                WHERE batch_id = in_batch_id
                AND batch_class IN (3, 4, 6, 7));

        UPDATE batch
        SET approved_on = now(),
                approved_by = (select entity_id FROM users
                        WHERE username = SESSION_USER)
        WHERE id = in_batch_id;

        SELECT now()::date;

Function: batch_search_empty(in_class_id integer, in_description text, in_created_by_eid integer, in_amount_gt numeric, in_amount_lt numeric, in_approved boolean)

Returns: SET OF batch_list_item

Language: SQL

This is a full search for the batches, listing them by amount processed. in_amount_gt and in_amount_lt provide a range to search for. in_description is a partial match field. Other fields are exact matches. NULLs match all values.

               SELECT b.id, c.class, b.control_code, b.description, u.username,
                        b.created_on, b.default_date, 0::numeric, 0::numeric, false
                FROM batch b
                JOIN batch_class c ON (b.batch_class_id = c.id)
                JOIN users u ON (u.entity_id = b.created_by)
                LEFT JOIN voucher v ON (v.batch_id = b.id)
               where v.id is null
                     and(u.entity_id = in_created_by_eid
                     or in_created_by_eid is null) and
                     (in_description is null or b.description
                     like '%'  || in_description || '%') and
                     (in_class_id is null or c.id = in_class_id)
            GROUP BY b.id, c.class, b.description, u.username, b.created_on,
                     b.control_code, b.default_date
            ORDER BY b.control_code, b.description



Function: batch_search_mini(in_class_id integer, in_description text, in_created_by_eid integer, in_approved boolean)

Returns: SET OF batch_list_item

Language: SQL

This performs a simple search of open batches created by the entity_id in question. This is used to pull up batches that were currently used so that they can be picked up and more vouchers added. NULLs match all values. in_description is a partial match All other inouts are exact matches.

                SELECT b.id, c.class, b.control_code, b.description, u.username,
                        b.created_on, b.default_date, NULL::NUMERIC, NULL::numeric, false
                FROM batch b
                JOIN batch_class c ON (b.batch_class_id = c.id)
                LEFT JOIN users u ON (u.entity_id = b.created_by)
                WHERE (c.id = in_class_id OR in_class_id IS NULL) AND
                        (b.description LIKE
                                '%' || in_description || '%' OR
                                in_description IS NULL) AND
                        (in_created_by_eid = b.created_by OR
                                in_created_by_eid IS NULL) AND
                        ((in_approved = false OR in_approved IS NULL AND
                                approved_on IS NULL) OR
                                (in_approved = true AND approved_on IS NOT NULL)
                        )
                GROUP BY b.id, c.class, b.description, u.username, b.created_on,
                        b.control_code, b.default_date

Function: budget__approve(in_id integer)

Returns: budget_info_ext

Language: SQL

UPDATE budget_info
   set approved_at = now(), approved_by = person__get_my_entity_id()
 WHERE id = $1;

SELECT budget__get_info($1);

Function: budget__get_business_units(in_id integer)

Returns: SET OF business_unit

Language: SQL

 select bu.*
     FROM business_unit bu
     JOIN budget_to_business_unit b2bu ON b2bu.bu_id = bu.id
     JOIN budget_info bi ON bi.id = b2bu.budget_id
    WHERE bi.id = $1
 ORDER BY bu.class_id;

Function: budget__get_details(in_id integer)

Returns: SET OF budget_line_details

Language: SQL

This retrieves the budget lines associated with a budget.

  SELECT l.budget_id, l.account_id, l.description, l.amount,
         a.accno, a.description,
         CASE WHEN l.amount < 0 THEN l.amount * -1 ELSE NULL END,
         CASE WHEN l.amount > 0 THEN l.amount ELSE NULL END
    FROM budget_line l
    JOIN account a ON a.id = l.account_id
   where budget_id = $1;

Function: budget__get_info(in_id integer)

Returns: budget_info_ext

Language: SQL

Selects the budget info.

select bi.id, bi.start_date, bi.end_date, bi.reference, bi.description,
       bi.entered_by, bi.approved_by, bi.obsolete_by, bi.entered_at,
       bi.approved_at, bi.obsolete_at,
       ee.name, ae.name, oe.name
  from budget_info bi
  JOIN entity ee ON bi.entered_by = ee.id
  LEFT JOIN entity ae ON bi.approved_by = ae.id
  LEFT JOIN entity oe ON bi.obsolete_by = oe.id
 where bi.id = $1;

Function: budget__get_notes(in_id integer)

Returns: SET OF budget_note

Language: SQL

Returns all notes associated with a budget, by default in the order they were created.

SELECT * FROM budget_note WHERE ref_key = $1
 ORDER BY created;

Function: budget__mark_obsolete(in_id integer)

Returns: budget_info_ext

Language: SQL

Marks a budget as obsolete

UPDATE budget_info
   set obsolete_by = person__get_my_entity_id(), obsolete_at = now()
 WHERE id = $1 and approved_by is not null;
SELECT budget__get_info($1)

Function: budget__reject(in_id integer)

Returns: boolean

Language: PLPGSQL

Deletes unapproved budgets only.

BEGIN

DELETE FROM budget_line
 WHERE budget_id IN (SELECT id from budget_info
                      WHERE id = in_id AND approved_by IS NULL);

DELETE FROM budget_to_project
 WHERE budget_id IN (SELECT id from budget_info
                      WHERE id = in_id AND approved_by IS NULL);

DELETE FROM budget_to_department
 WHERE budget_id IN (SELECT id from budget_info
                      WHERE id = in_id AND approved_by IS NULL);

DELETE FROM budget_info WHERE id = in_id AND approved_by IS NULL;

RETURN FOUND;
END;

Function: budget__save_details(in_id integer, in_details text[])

Returns: budget_info_ext

Language: PLPGSQL

This saves the line items for the budget. in_details is an array n long where each entry is {int account_id, text description, numeric amount}. The in_id parameter is the budget_id.

DECLARE
   loop_count int;
   retval budget_info_ext;
BEGIN
    FOR loop_count in
        array_lower(in_details, 1) ..
        array_upper(in_details, 1)
    LOOP
        INSERT INTO budget_line
                    (budget_id,
                     account_id,
                     description,
                     amount)
             VALUES (in_id,
                     in_details[loop_count][1]::int,
                     in_details[loop_count][2],
                     in_details[loop_count][3]::numeric);
    END LOOP;
    retval := budget__get_info(in_id);
    return retval;
END;

Function: budget__save_info(in_id integer, in_start_date date, in_end_date date, in_reference text, in_description text, in_business_units integer[])

Returns: budget_info_ext

Language: PLPGSQL

Saves the extended budget info passed through to the function. See the comment on type budget_info_ext for more information.

DECLARE
   retval budget_info_ext;
   t_id int;
BEGIN

   PERFORM * FROM budget_info WHERE id = in_id and approved_by is not null;
   IF FOUND THEN
       RAISE EXCEPTION 'report approved';
   END IF;

  UPDATE budget_info
     SET start_date = in_start_date,
         end_date = in_end_date,
         reference = in_reference,
         description = in_description
   WHERE id = in_id and approved_by is null;
  IF FOUND THEN
      t_id := in_id;
  ELSE
       INSERT INTO budget_info (start_date, end_date, reference, description)
            VALUES (in_start_date, in_end_date, in_reference, in_description);
       t_id = currval('budget_info_id_seq');

       INSERT INTO budget_to_business_unit(budget_id, bu_id, bu_class)
       SELECT t_id, id, class_id
         FROM business_unit
        WHERE id = ANY(in_business_units);
  END IF;
  retval := budget__get_info(t_id);
  return retval;
END;

Function: budget__save_note(in_id integer, in_subject text, in_note text)

Returns: budget_note

Language: SQL

Saves a note attached to a budget.

INSERT INTO budget_note (subject, note, ref_key)
     values ($2, $3, $1);

SELECT * FROM budget_note WHERE id = currval('note_id_seq'::regclass);

Function: budget__search(in_start_date date, in_end_date date, in_includes_date date, in_reference text, in_description text, in_entered_by integer, in_approved_by integer, in_obsolete_by integer, in_business_units integer[], in_is_approved boolean, in_is_obsolete boolean)

Returns: SET OF budget_info_ext

Language: SQL

This is a general search for budgets

select bi.id, bi.start_date, bi.end_date, bi.reference, bi.description,
       bi.entered_by, bi.approved_by, bi.obsolete_by, bi.entered_at,
       bi.approved_at, bi.obsolete_at,
       ee.name, ae.name, oe.name
  from budget_info bi
  JOIN entity ee ON bi.entered_by = ee.id
  LEFT JOIN entity ae ON bi.approved_by = ae.id
  LEFT JOIN entity oe ON bi.obsolete_by = oe.id
 WHERE (start_date = $1 or $1 is null) AND ($2 = end_date or $2 is null)
       AND ($3 BETWEEN start_date AND end_date or $2 is null)
       AND ($4 ilike reference || '%' or $4 is null)
       AND (bi.description @@ plainto_tsquery($5) or $5 is null)
       AND ($6 = entered_by or $6 is null)
       AND ($7 = approved_by or $7 is null)
       AND ($8 = obsolete_by or $8 is null)
       AND ($10 IS NULL OR ($10 = (approved_by IS NOT NULL)))
       AND ($11 IS NULL OR ($11 = (obsolete_by IS NOT NULL)))
 ORDER BY reference;

Function: budget__variance_report(in_id integer)

Returns: SET OF budget_variance_report

Language: SQL

Retrieves a variance report for budget with an id of in_id.

   WITH agg_account (amount, id, transdate)
        AS ( SELECT ac.amount_bc *
                    CASE WHEN a.contra THEN -1 ELSE 1 END *
                    CASE WHEN a.category IN ('A', 'E') THEN -1 ELSE 1 END
                    AS amount,
                    ac.chart_id, ac.transdate
               FROM acc_trans ac
               JOIN account a ON ac.chart_id = a.id
           )
   SELECT act.accno, act.description, act.id, b.description, b.amount,
          coalesce(sum(a.amount), 0),
          b.amount - coalesce(sum(a.amount), 0) AS variance
     FROM budget_info bi
     JOIN budget_line b ON bi.id = b.budget_id
     JOIN account act ON act.id = b.account_id
LEFT JOIN agg_account a ON a.transdate BETWEEN bi.start_date and bi.end_date
                           AND a.id = b.account_id
    WHERE bi.id = $1
 GROUP BY act.accno, act.description, act.id, b.description, b.amount
 ORDER BY act.accno;

Function: business_type__list()

Returns: SET OF business

Language: SQL

Returns a list of all business types. Ordered by description by default.

        SELECT * FROM business ORDER BY description;

Function: business_unit__get(in_id integer)

Returns: business_unit

Language: SQL

 SELECT * FROM business_unit where id = $1; 

Function: business_unit__get_tree_for(in_id integer)

Returns: SET OF business_unit_short

Language: PLPGSQL

This function returns tree-related records with the root of the tree being the business unit of in_id.

BEGIN
RETURN QUERY EXECUTE $sql$
WITH RECURSIVE tree  (id, control_code, description,  start_date, end_date,
                      parent_id, path, level)
AS (
   SELECT id, control_code, description, start_date, end_date, parent_id,
          ARRAY[parent_id] AS path, 1 as level
     FROM business_unit WHERE $1 = id
    UNION
   SELECT t.id, t.control_code, t.description, t.start_date, t.end_date,
          t.parent_id,
          t.path || bu.id AS path, t.level + 1 as level
     FROM business_unit bu JOIN tree t ON t.parent_id = bu.id
)
SELECT * FROM tree ORDER BY path;
$sql$
USING in_id;
END

Function: business_unit__list_by_class(in_business_unit_class_id integer, in_active_on date, in_credit_id integer, in_strict_credit boolean)

Returns: SET OF business_unit

Language: PLPGSQL

This function retUrns a list of all units (projects, departments, funds, etc) active on the in_active_on date, where in_credit_id matches the credit id of the customer or vendor requested, and where in_business_uni_class_id is the class id of the class of business units (1 for department, 2 for project, etc). With the exception of in_business_unit_class_id, the null matches all records.

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT * FROM business_unit
              WHERE ($2 BETWEEN coalesce(start_date, $2)
                                      AND coalesce(end_date, $2)
                      OR $2 IS NULL)
                    AND ($3 = credit_id
                        OR (credit_id IS NULL and $4 IS NOT TRUE)
                        OR ($3 IS NULL))
                    AND class_id = $1
           ORDER BY control_code
$sql$
USING in_business_unit_class_id, in_active_on, in_credit_id, in_strict_credit;
END

Function: business_unit__list_classes(in_active boolean, in_module text)

Returns: SET OF business_unit_class

Language: PLPGSQL

This function lists all business unit clases. If in_active is true, then only active classes are listed. If it is false then only inactive classes are listed. If it is null, then all classes are listed.

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT bc.*
  FROM business_unit_class bc
 WHERE     (active = $1 OR $1 IS NULL)
       AND (id IN (select bu_class_id
                     FROM bu_class_to_module bcm
                     JOIN lsmb_module mod ON mod.id = bcm.module_id
                    WHERE lower(label) = lower($2))
            OR $2 is null)
ORDER BY ordering
$sql$
USING in_active, in_module;
END

Function: business_unit__save(in_id integer, in_class_id integer, in_control_code text, in_description text, in_start_date date, in_end_date date, in_parent_id integer, in_credit_id integer)

Returns: business_unit

Language: PLPGSQL

DECLARE retval business_unit;
        t_id int;

BEGIN

UPDATE business_unit
   SET class_id = in_class_id,
       control_code = in_control_code,
       description = in_description,
       start_date = in_start_date,
       end_date = in_end_date,
       credit_id = in_credit_id
 WHERE id = in_id;


IF FOUND THEN
   t_id := in_id;
ELSE
   INSERT INTO business_unit
          (class_id, control_code, description, start_date, end_date, parent_id,
           credit_id)
   VALUES (in_class_id, in_control_code, in_description, in_start_date,
           in_end_date, in_parent_id, in_credit_id);
    t_id := currval('business_unit_id_seq');
END IF;

SELECT * INTO retval FROM business_unit WHERE id = t_id;

RETURN retval;
END;

Function: business_unit_class__delete(in_id integer)

Returns: business_unit_class

Language: SQL

  DELETE FROM business_unit_class WHERE id = in_id
  RETURNING *;

Function: business_unit_class__get_modules(in_id integer)

Returns: SET OF lsmb_module

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
 SELECT * FROM lsmb_module
    WHERE id IN (select module_id from bu_class_to_module where bu_class_id = $1)
 ORDER BY id
$sql$
USING in_id;
END

Function: business_unit_class__save(in_id integer, in_label text, in_active boolean, in_ordering integer)

Returns: business_unit_class

Language: PLPGSQL

DECLARE retval business_unit_class;
        t_id int;
BEGIN

t_id := in_id;
UPDATE business_unit_class
   SET label = in_label,
       active = in_active,
       ordering = in_ordering
 WHERE id = in_id;

IF NOT FOUND THEN

   INSERT INTO business_unit_class (label, active, ordering)
   VALUES (in_label, in_active, in_ordering);

   t_id := currval('business_unit_class_id_seq');

END IF;

SELECT * INTO retval FROM business_unit_class WHERE id = t_id;

RETURN retval;

END;


Function: business_unit_class__save_modules(in_id integer, in_mod_ids integer[])

Returns: boolean

Language: SQL

DELETE FROM bu_class_to_module WHERE bu_class_id = $1;

INSERT INTO bu_class_to_module (bu_class_id, module_id)
SELECT $1, unnest
  FROM unnest($2);

SELECT true;

Function: business_unit_get(in_id integer)

Returns: business_unit

Language: SQL

 SELECT * FROM business_unit WHERE id = $1; 

Function: cdc_update_last_updated()

Returns: trigger

Language: PLPGSQL

BEGIN
  IF TG_OP <> 'DELETE' THEN
    NEW.last_updated := NOW();
  END IF;
  RETURN NEW;
END;

Function: chart_get_ar_ap(in_account_class integer)

Returns: SET OF account

Language: PLPGSQL

This function returns the cash account according with in_account_class which must be 1 or 2. If in_account_class is 1 then it returns a list of AP accounts, and if in_account_class is 2, then a list of AR accounts.

DECLARE out_row account%ROWTYPE;
BEGIN
        IF in_account_class NOT IN (1, 2) THEN
                RAISE EXCEPTION 'Bad Account Type';
        END IF;
       FOR out_row IN
               SELECT * FROM account
               WHERE id in (select account_id from account_link
                               where description = CASE WHEN in_account_class = 1 THEN 'AP'
                               WHEN in_account_class = 2 THEN 'AR'
                               END)
               ORDER BY accno
       LOOP
               RETURN NEXT out_row;
       END LOOP;
END;

Function: chart_list_all()

Returns: SET OF account

Language: SQL

SELECT * FROM account ORDER BY accno;

Function: chart_list_cash(in_account_class integer)

Returns: SET OF account

Language: PLPGSQL

This function returns the overpayment accounts acording with in_account_class which must be 1 or 2. If in_account_class is 1 it returns a list of AP cash accounts and if 2, AR cash accounts.

 DECLARE resultrow record;
         link_string text;
 BEGIN
         IF in_account_class = 1 THEN
            link_string := 'AP_paid';
         ELSE
            link_string := 'AR_paid';
         END IF;

         FOR resultrow IN
          SELECT *  FROM account
          WHERE id in (select account_id from account_link where description = link_string)
          ORDER BY accno
           LOOP
           return next resultrow;
         END LOOP;
 END;

Function: chart_list_discount(in_account_class integer)

Returns: SET OF account

Language: PLPGSQL

This function returns the discount accounts acording with in_account_class which must be 1 or 2. If in_account_class is 1, returns AP discount accounts, if 2, AR discount accounts.

DECLARE resultrow record;
        link_string text;
BEGIN
        IF in_account_class = 1 THEN
           link_string := 'AP_discount';
        ELSE
           link_string := 'AR_discount';
        END IF;

        FOR resultrow IN
          SELECT *  FROM account
          WHERE id in (select account_id from account_link where description = link_string)
          ORDER BY accno
          LOOP
          return next resultrow;
        END LOOP;
END;

Function: chart_list_overpayment(in_account_class integer)

Returns: SET OF account

Language: PLPGSQL

Returns a list of AP_overpayment accounts if in_account_class is 1 Otherwise it returns a list of AR_overpayment accounts.

DECLARE resultrow record;
        link_string text;
BEGIN
        IF in_account_class = 1 THEN
           link_string := 'AP_overpayment';
        ELSE
           link_string := 'AR_overpayment';
        END IF;

        FOR resultrow IN
          SELECT *  FROM account
          WHERE id in (select account_id from account_link where description = link_string)
          ORDER BY accno
          LOOP
          return next resultrow;
        END LOOP;
END;

Function: chart_list_search(in_search text, in_link_desc text)

Returns: SET OF account

Language: SQL

This returns a list of account entries where the description or account number begins with in_search. If in_link_desc is provided, the list is further filtered by which accounts are set to an account_link.description equal to that provided.

                SELECT * FROM account
                 WHERE (accno ~* ('^'||in_search)
                       OR description ~* ('^'||in_search))
                       AND (in_link_desc IS NULL
                           or id in
                          (select account_id from account_link
                            where description = in_link_desc))
                       AND not obsolete
              ORDER BY accno

Function: check_expiration()

Returns: boolean

Language: PLPGSQL

This checks whether the user needs to be notified of a pending expiration of his/her password. Returns true if needed, false if not. The function also records the next time when the notification will again need to be displayed.

DECLARE test_result BOOL;
        expires_in interval;
        notify_again interval;
BEGIN
        expires_in := user__check_my_expiration();

        SELECT expires_in < notify_password INTO test_result
        FROM users WHERE username = SESSION_USER;

        IF test_result THEN
                IF expires_in < '1 week' THEN
                        notify_again := '1 hour';
                ELSE
                        notify_again := '1 day';
                END IF;

                UPDATE users
                SET notify_password = expires_in - notify_again
                WHERE username = SESSION_USER;
        END IF;
        RETURN test_result;
END;

Function: cogs__add_for_ap(in_parts_id integer, in_qty numeric, in_lastcost numeric)

Returns: numeric

Language: PLPGSQL

DECLARE t_alloc numeric := 0;
        t_cogs numeric := 0;
        t_inv invoice;
        t_cp account_checkpoint;
        t_transdate date;
        t_avail numeric;
BEGIN

IF in_qty > 0 THEN
   return (cogs__reverse_ap(in_parts_id, in_qty * -1))[1] * in_lastcost;
END IF;

SELECT * INTO t_cp FROM account_checkpoint ORDER BY end_date DESC LIMIT 1;

FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (select id, approved, transdate from ar
             union
            select id, approved, transdate from gl) a
           ON a.id = i.trans_id AND a.approved
     WHERE qty + allocated > 0 and parts_id  = in_parts_id
  ORDER BY a.transdate, a.id, i.id
LOOP
   t_avail := t_inv.qty + t_inv.allocated;
   SELECT transdate INTO t_transdate FROM transactions
    WHERE id = t_inv.trans_id;
   IF t_alloc < in_qty THEN
       RAISE EXCEPTION 'TOO MANY ALLOCATED';
   ELSIF t_alloc = in_qty THEN
       return t_alloc;
   ELSIF (in_qty + t_alloc) * -1 <=  t_avail  THEN
       UPDATE invoice SET allocated = allocated + (in_qty + t_alloc)
        WHERE id = t_inv.id;

       INSERT INTO acc_trans
              (chart_id, transdate, amount_bc, curr, amount_tc, invoice_id, approved, trans_id)
       SELECT expense_accno_id,
              CASE WHEN t_transdate > coalesce(t_cp.end_date, t_transdate - 1)
                   THEN t_transdate
                   ELSE t_cp.end_date + '1 day'::interval
               END,
               (in_qty + t_alloc) * in_lastcost,
               defaults_get_defaultcurrency(),
               (in_qty + t_alloc) * in_lastcost,
               t_inv.id, true,
              t_inv.trans_id
         FROM parts
       WHERE  id = t_inv.parts_id AND inventory_accno_id IS NOT NULL
              AND expense_accno_id IS NOT NULL
       UNION
       SELECT inventory_accno_id,
              CASE WHEN t_transdate > coalesce(t_cp.end_date, t_transdate - 1)
                   THEN t_transdate
                   ELSE t_cp.end_date + '1 day'::interval
               END,
               -1*(in_qty + t_alloc) * in_lastcost,
               defaults_get_defaultcurrency(),
               -1*(in_qty + t_alloc) * in_lastcost,
               t_inv.id, true,
              t_inv.trans_id
         FROM parts
       WHERE  id = t_inv.parts_id AND inventory_accno_id IS NOT NULL
              AND expense_accno_id IS NOT NULL;

       t_cogs := t_cogs + (in_qty + t_alloc) * in_lastcost;
       return in_qty * -1;
   ELSE
       UPDATE invoice SET allocated = qty * -1
        WHERE id = t_inv.id;
       t_cogs := t_cogs + t_avail * in_lastcost;

       INSERT INTO acc_trans
              (chart_id, transdate, amount_bc, curr, amount_tc, invoice_id, approved, trans_id)
       SELECT expense_accno_id,
              CASE WHEN t_transdate > coalesce(t_cp.end_date, t_transdate - 1)
                   THEN t_transdate
                   ELSE t_cp.end_date + '1 day'::interval
               END,
               -1*t_avail * in_lastcost,
               defaults_get_defaultcurrency(),
               -1*t_avail * in_lastcost,
              t_inv.id, true, t_inv.trans_id
         FROM parts
       WHERE  id = t_inv.parts_id AND inventory_accno_id IS NOT NULL
              AND expense_accno_id IS NOT NULL
       UNION
       SELECT inventory_accno_id,
              CASE WHEN t_transdate > coalesce(t_cp.end_date, t_transdate - 1)
                   THEN t_transdate
                   ELSE t_cp.end_date + '1 day'::interval
               END,
               t_avail * in_lastcost,
               defaults_get_defaultcurrency(),
               t_avail * in_lastcost,
               t_inv.id, true, t_inv.trans_id
         FROM parts
       WHERE  id = t_inv.parts_id AND inventory_accno_id IS NOT NULL
              AND expense_accno_id IS NOT NULL;
       t_alloc := t_alloc + t_avail;
       t_cogs := t_cogs + t_avail * in_lastcost;
   END IF;

END LOOP;

RETURN t_alloc;
END;

Function: cogs__add_for_ap_line(in_invoice_id integer)

Returns: numeric

Language: PLPGSQL

DECLARE retval numeric;
        r_cogs numeric[];
        t_inv invoice;
        t_adj numeric;
        t_transdate date;
BEGIN

SELECT * INTO t_inv FROM invoice
 WHERE id = in_invoice_id;

IF t_inv.qty + t_inv.allocated = 0 THEN
   return 0;
END IF;

PERFORM 1 FROM parts
         WHERE t_inv.parts_id = parts.id
               AND parts.inventory_accno_id IS NOT NULL;

IF NOT FOUND THEN
   -- the part doesn't have an associated inventory account: it's a service.
   return 0;
END IF;

IF t_inv.qty < 0 THEN -- normal COGS

    SELECT cogs__add_for_ap(i.parts_id, i.qty + i.allocated, i.sellprice)
      INTO retval
      FROM invoice i
      JOIN parts p ON p.id = i.parts_id
     WHERE i.id = $1;

    UPDATE invoice
       SET allocated = allocated + retval
     WHERE id = $1;
ELSE -- reversal

   r_cogs := cogs__reverse_ap(t_inv.parts_id, t_inv.qty + t_inv.allocated);

   UPDATE invoice
      SET allocated = allocated + r_cogs[1]
    WHERE id = in_invoice_id;

   t_adj := t_inv.sellprice * r_cogs[1] + r_cogs[2];

   SELECT transdate INTO t_transdate FROM transactions
    WHERE id = t_inv.trans_id;

   INSERT INTO acc_trans
          (chart_id, trans_id, approved,  amount_bc, curr, amount_tc, transdate, invoice_id)
   SELECT p.inventory_accno_id, t_inv.trans_id, true, t_adj,
          defaults_get_defaultcurrency(), t_adj, t_transdate,
          in_invoice_id
     FROM parts p
    WHERE id = t_inv.parts_id
    UNION
   SELECT p.expense_accno_id, t_inv.trans_id, true, t_adj * -1,
          defaults_get_defaultcurrency(), t_adj * -1, t_transdate,
          in_invoice_id
     FROM parts p
    WHERE id = t_inv.parts_id;
   retval := r_cogs[1];

END IF;

RETURN retval;

END;


Function: cogs__add_for_ar(in_parts_id integer, in_qty numeric)

Returns: numeric[]

Language: PLPGSQL

This function accepts a parts_id and a quantity, and iterates through AP records in order, calculating COGS on a FIFO basis and returning it to the application to attach to the current transaction. Modifies the `invoice` records `allocated` values. Return values are an array of {allocated, cogs}.

DECLARE t_alloc numeric := 0;
        t_cogs numeric := 0;
        t_inv invoice;
        t_avail numeric;
BEGIN


FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (select id, approved, transdate from ap
             union
            select id, approved, transdate from gl) a ON a.id = i.trans_id
     WHERE qty + allocated < 0 AND i.parts_id = in_parts_id AND a.approved
  ORDER BY a.transdate asc, a.id asc, i.id asc
LOOP
   t_avail := (t_inv.qty + t_inv.allocated) * -1;
   IF t_alloc > in_qty THEN
       RAISE EXCEPTION 'TOO MANY ALLOCATED';
   ELSIF t_alloc = in_qty THEN
       return ARRAY[t_alloc, t_cogs];
   ELSIF (in_qty - t_alloc) <= t_avail THEN
       UPDATE invoice SET allocated = allocated + (in_qty - t_alloc)
        WHERE id = t_inv.id;
       t_cogs := t_cogs + (in_qty - t_alloc) * t_inv.sellprice;
       t_alloc := in_qty;
       return ARRAY[t_alloc, t_cogs];
   ELSE
       UPDATE invoice SET allocated = qty * -1
        WHERE id = t_inv.id;
       t_cogs := t_cogs + (t_avail * t_inv.sellprice);
       t_alloc := t_alloc + t_avail;
   END IF;
END LOOP;

RETURN ARRAY[t_alloc, t_cogs];

END;

Function: cogs__add_for_ar_line(in_invoice_id integer)

Returns: numeric

Language: PLPGSQL

DECLARE
   t_cogs numeric[];
   t_inv invoice;
   t_part parts;
   t_ar ar;
   t_transdate date;
   t_override_cogs int;
BEGIN

SELECT * INTO t_inv FROM invoice WHERE id = in_invoice_id;
SELECT * INTO t_part FROM parts WHERE id = t_inv.parts_id;
SELECT * INTO t_ar FROM ar WHERE id = t_inv.trans_id;
SELECT transdate INTO t_transdate FROM transactions WHERE id = t_inv.trans_id;

IF t_ar.is_return THEN
   t_override_cogs = (setting_get('ar_return_account_id')).value::int;
END IF;

IF t_part.inventory_accno_id IS NULL THEN
   RETURN 0;
END IF;

IF t_inv.qty + t_inv.allocated = 0 THEN
   return 0;
END IF;

IF t_inv.qty > 0 THEN
   t_cogs := cogs__add_for_ar(t_inv.parts_id, t_inv.qty + t_inv.allocated);
ELSE
   t_cogs := cogs__reverse_ar(t_inv.parts_id, t_inv.qty + t_inv.allocated);
END IF;


UPDATE invoice set allocated = allocated - t_cogs[1]
 WHERE id = in_invoice_id;

SELECT CASE WHEN t_transdate > coalesce(max(end_date), t_transdate - 1)
            THEN t_transdate
            ELSE max(end_date) + '1 day'::interval
        END
  INTO t_transdate
  from account_checkpoint td;


INSERT INTO acc_trans
       (trans_id, chart_id, approved, amount_bc,
        curr, amount_tc, transdate,  invoice_id)
VALUES (t_inv.trans_id, COALESCE(t_override_cogs,
                                 CASE WHEN t_inv.qty < 0 AND t_ar.is_return
                                      THEN t_part.returns_accno_id
                                      ELSE t_part.expense_accno_id
                                      END), TRUE, t_cogs[2] * -1,
        defaults_get_defaultcurrency(), t_cogs[2] * -1, t_transdate, t_inv.id),
       (t_inv.trans_id, t_part.inventory_accno_id, TRUE, t_cogs[2],
        defaults_get_defaultcurrency(), t_cogs[2], t_transdate, t_inv.id);

RETURN t_cogs[1];

END;


Function: cogs__reverse_ap(in_parts_id integer, in_qty numeric)

Returns: numeric[]

Language: PLPGSQL

This function iterates through invoice rows attached to ap transactions and allocates to them on a first-in first-out basis. The sort of pseudo-"COGS" value is returned to the application for further handling.

DECLARE t_alloc numeric :=0;
        t_realloc numeric;
        t_reversed numeric;
        t_inv invoice;
        t_cogs numeric :=0;
        retval numeric[];
BEGIN

-- Move allocation to other purchase lines
FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (select id, approved, transdate from ap
             union
            select id, approved, transdate from gl) a
           ON a.id = i.trans_id
     WHERE qty + allocated < 0 AND parts_id = in_parts_id AND a.approved
  ORDER BY a.transdate, a.id, i.id
LOOP
   t_realloc := least(in_qty - t_alloc, -1 * (t_inv.allocated + t_inv.qty));
   UPDATE invoice SET allocated = allocated + t_realloc
    WHERE id = t_inv.id;
   t_alloc := t_alloc + t_realloc;
   t_cogs := t_cogs + t_realloc * t_inv.sellprice;

   IF t_alloc > in_qty THEN
       RAISE EXCEPTION 'TOO MANY ALLOCATED';
   ELSIF t_alloc = in_qty THEN
       return ARRAY[-1 * t_alloc, t_cogs];
   END IF;
END LOOP;

-- No more items in stock to move allocation to?
-- * Put AR invoices in back-order
FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (select id, approved, transdate from ar
            union
            select id, approved, transdate from gl) a ON a.id = i.trans_id
     WHERE allocated < 0 and a.approved and parts_id = in_parts_id
  ORDER BY a.transdate, a.id, i.id
LOOP
   t_reversed := least(in_qty - t_alloc, -1 * t_inv.allocated);
   UPDATE invoice SET allocated = allocated + t_reversed
    WHERE id = t_inv.id;
   t_alloc := t_alloc + t_reversed;

   IF t_alloc > in_qty THEN
       RAISE EXCEPTION 'TOO MANY ALLOCATED';
   ELSIF t_alloc = in_qty THEN
       RETURN ARRAY[-1 * t_alloc, t_cogs];
   END IF;
END LOOP;


RAISE EXCEPTION 'TOO FEW TO ALLOCATE';
END;

Function: cogs__reverse_ar(in_parts_id integer, in_qty numeric)

Returns: numeric[]

Language: PLPGSQL

This function accepts a part id and quantity to reverse. It then iterates backwards over AP related records, calculating COGS. This does not save COGS but rather returns it to the application to save. It does however, modify the `invoice` records. Return values are an array of {allocated, cogs}.

DECLARE t_alloc numeric := 0; -- qty to reverse (negative)
        t_cogs numeric := 0;
        t_inv invoice;
        t_reversed numeric;
        t_reallocated numeric;
BEGIN

IF in_qty = 0 THEN
   RETURN ARRAY[0, 0];
END IF;

-- First satisfy invoices in back-order
FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (SELECT id, approved, transdate FROM ar
            UNION
            SELECT id, approved, transdate FROM gl) a ON a.id = i.trans_id
     WHERE qty + allocated > 0 and a.approved and parts_id = in_parts_id
   ORDER BY a.transdate ASC, a.id ASC, i.id ASC
LOOP
   t_reallocated := least(t_alloc - in_qty, t_inv.qty + t_inv.allocated);
   UPDATE invoice
      SET allocated = allocated - t_reallocated
    WHERE id = t_inv.id;
   t_alloc := t_alloc - t_reallocated;

   IF t_alloc < in_qty THEN
      RAISE EXCEPTION 'TOO MANY ALLOCATED (1)';
   ELSIF t_alloc = in_qty THEN
      RETURN ARRAY[t_alloc, 0];
   END IF;
END LOOP;

-- No (more) invoices in back-order?
-- * Reverse allocation from AP invoices
FOR t_inv IN
    SELECT i.*
      FROM invoice i
      JOIN (select id, approved, transdate from ap
            union
            select id, approved, transdate from gl) a ON a.id = i.trans_id
     WHERE allocated > 0 and a.approved and parts_id = in_parts_id
           -- the sellprice check is here because of github issue #4791:
           -- when a negative number of assemblies has been "stocked",
           -- reversal of a sales invoice for that part, fails.
           and sellprice is not null
  ORDER BY a.transdate DESC, a.id DESC, i.id DESC
LOOP
   t_reversed := least((in_qty - t_alloc) * -1, t_inv.allocated);
   UPDATE invoice SET allocated = allocated - t_reversed
    WHERE id = t_inv.id;
   t_cogs := t_cogs - t_reversed * t_inv.sellprice;
   t_alloc := t_alloc - t_reversed;

   IF t_alloc < in_qty THEN
       RAISE EXCEPTION 'TOO MANY ALLOCATED';
   ELSIF t_alloc = in_qty THEN
       RETURN ARRAY[t_alloc, t_cogs];
   END IF;
END LOOP;

RAISE EXCEPTION 'TOO FEW TO ALLOCATE';
END;

Function: company__get(in_entity_id integer)

Returns: company_entity

Language: SQL

Returns all attributes for the company attached to the entity.

        SELECT c.entity_id, e.entity_class, c.legal_name, c.tax_id, c.sales_tax_id,
               c.license_number, c.sic_code, e.control_code, e.country_id
          FROM company c
          JOIN entity e ON e.id = c.entity_id
         WHERE entity_id = $1;

Function: company__get_all_accounts(in_entity_id integer, in_entity_class integer)

Returns: SET OF entity_credit_account

Language: SQL

Returns a list of all entity credit accounts attached to that entity.


    SELECT *
      FROM entity_credit_account
     WHERE entity_id = $1
       AND entity_class = $2;


Function: company__get_by_cc(in_control_code text)

Returns: company_entity

Language: SQL

Returns the entity/company row attached to the control code.

        SELECT c.entity_id, e.entity_class, c.legal_name, c.tax_id, c.sales_tax_id,
               c.license_number, c.sic_code, e.control_code, e.country_id
          FROM company c
          JOIN entity e ON e.id = c.entity_id
         WHERE e.control_code = $1;

Function: company__next_id()

Returns: bigint

Language: SQL


    select nextval('company_id_seq');


Function: company__save(in_control_code text, in_entity_class integer, in_legal_name text, in_tax_id text, in_entity_id integer, in_sic_code text, in_country_id integer, in_sales_tax_id text, in_license_number text)

Returns: company

Language: PLPGSQL

Saves a company. Returns the id number of the record stored.

DECLARE t_entity_id INT;
        t_control_code TEXT;
        t_retval COMPANY;
BEGIN

        IF in_control_code IS NULL THEN
                t_control_code := setting_increment('entity_control');
        ELSE
                t_control_code := in_control_code;
        END IF;

        UPDATE entity
        SET name = in_legal_name,
                entity_class = in_entity_class,
                control_code = t_control_code,
                country_id   = in_country_id
        WHERE id = in_entity_id;

        IF FOUND THEN
                t_entity_id = in_entity_id;
        ELSE
                INSERT INTO entity (name, entity_class, control_code,country_id)
                VALUES (in_legal_name, in_entity_class, t_control_code,in_country_id);
                t_entity_id := currval('entity_id_seq');
        END IF;

        UPDATE company
        SET legal_name = in_legal_name,
                tax_id = in_tax_id,
                sic_code = in_sic_code,
                sales_tax_id = in_sales_tax_id,
                license_number = in_license_number
        WHERE entity_id = t_entity_id;


        IF NOT FOUND THEN
                INSERT INTO company(entity_id, legal_name, tax_id, sic_code,
                                    sales_tax_id, license_number)
                VALUES (t_entity_id, in_legal_name, in_tax_id, in_sic_code,
                        in_sales_tax_id, in_license_number);

        END IF;
        SELECT * INTO t_retval FROM company WHERE entity_id = t_entity_id;
        RETURN t_retval;
END;

Function: company_get_billing_info(in_id integer)

Returns: company_billing_info

Language: SQL

Returns billing information (billing name and address) for a given credit account.

        select coalesce(eca.pay_to_name, c.legal_name), eca.meta_number,
                e.control_code, eca.cash_account_id, c.tax_id,
                a.line_one, a.line_two, a.line_three,
                a.city, a.state, a.mail_code, cc.name
        FROM (select legal_name, tax_id, entity_id
                FROM company
               UNION ALL
              SELECT last_name || ', ' || first_name, null, entity_id
                FROM person) c
        JOIN entity e ON (c.entity_id = e.id)
        JOIN entity_credit_account eca ON (eca.entity_id = e.id)
        LEFT JOIN eca_to_location cl ON (eca.id = cl.credit_id)
        LEFT JOIN location a ON (a.id = cl.location_id)
        LEFT JOIN country cc ON (cc.id = a.country_id)
        WHERE eca.id = in_id AND (location_class = 1 or location_class is null);


Function: compound_array(anyarray)

Returns: anyarray

Language: INTERNAL

Returns an n dimensional array.

aggregate_dummy

Function: compound_array(ary anyarray, elm anyarray)

Returns: anyarray

Language: SQL

PostgreSQL 14 vs pre-14 compatibility measure.

   SELECT array_cat(ary, elm);

Function: config_currency__delete(in_code text)

Returns: boolean

Language: SQL

   DELETE FROM currency WHERE curr = in_code
   RETURNING true;

Function: config_currency__save(in_code text, in_description text)

Returns: currency

Language: SQL

TODO

   INSERT INTO currency (curr, description)
        VALUES (in_code, in_description)
   ON CONFLICT (curr) DO UPDATE
      SET description = in_description
   RETURNING *;

Function: config_gifi__delete(in_code text)

Returns: boolean

Language: SQL

   DELETE FROM gifi WHERE accno = in_code
   RETURNING true;

Function: config_gifi__save(in_code text, in_description text)

Returns: gifi

Language: SQL

TODO

   INSERT INTO gifi (accno, description)
            VALUES (in_code, in_description)
   ON CONFLICT (accno) DO UPDATE
        SET description = in_description
   RETURNING *;

Function: config_sic__delete(in_code character varying)

Returns: boolean

Language: SQL

   DELETE FROM sic WHERE code = in_code
   RETURNING true;

Function: config_sic__save(in_code character varying, in_sictype bpchar, in_description text)

Returns: sic

Language: SQL

TODO

   INSERT INTO sic (code,    sictype,    description)
            VALUES (in_code, in_sictype, in_description)
   ON CONFLICT (code) DO UPDATE
        SET sictype = in_sictype,
            description = in_description
   RETURNING *;

Function: contact__search(in_entity_class integer, in_contact text, in_contact_info text[], in_meta_number text, in_address text, in_city text, in_state text, in_mail_code text, in_country text, in_active_date_from date, in_active_date_to date, in_business_id integer, in_name_part text, in_control_code text, in_notes text, in_users boolean)

Returns: SET OF contact_search_result

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$

   WITH entities_matching_name AS (
                      SELECT legal_name, sic_code, entity_id
                        FROM company
                       WHERE $13 IS NULL
             OR legal_name @@ plainto_tsquery($13)
             OR legal_name ilike $13 || '%'
                      UNION ALL
                     SELECT coalesce(first_name, '') || ' '
             || coalesce(middle_name, '')
             || ' ' || coalesce(last_name, ''), null, entity_id
                       FROM person
       WHERE $13 IS NULL
             OR coalesce(first_name, '') || ' ' || coalesce(middle_name, '')
                || ' ' || coalesce(last_name, '')
                             @@ plainto_tsquery($13)
   ),
   matching_eca_contacts AS (
       SELECT credit_id
         FROM eca_to_contact
        WHERE ($3 IS NULL
               OR contact = ANY($3))
                        AND ($2 IS NULL
                   OR description @@ plainto_tsquery($2))
   ),
   matching_entity_contacts AS (
       SELECT entity_id
                                           FROM entity_to_contact
        WHERE ($3 IS NULL
               OR contact = ANY($3))
              AND ($2 IS NULL
                   OR description @@ plainto_tsquery($2))
   ),
   matching_locations AS (
       SELECT id
         FROM location
        WHERE ($5 IS NULL
               OR line_one @@ plainto_tsquery($5)
               OR line_two @@ plainto_tsquery($5)
               OR line_three @@ plainto_tsquery($5))
              AND ($6 IS NULL
                   OR city ILIKE '%' || $6 || '%')
              AND ($7 IS NULL
                   OR state ILIKE '%' || $7 || '%')
              AND ($8 IS NULL
                   OR mail_code ILIKE $8 || '%')
              AND ($9 IS NULL
                   OR EXISTS (select 1 from country
                               where name ilike '%' || $9 || '%'
                                  or short_name ilike '%' || $9 || '%'))
                       )
   SELECT e.id, e.control_code, ec.id, ec.meta_number::text,
          ec.description, ec.entity_class,
          c.legal_name, c.sic_code::text, b.description , ec.curr::text
     FROM entity e
     JOIN entities_matching_name c ON c.entity_id = e.id
LEFT JOIN entity_credit_account ec ON (ec.entity_id = e.id)
LEFT JOIN business b ON (ec.business_id = b.id)
    WHERE ($1 is null
           OR coalesce(ec.entity_class, e.entity_class) = $1)
          AND ($14 IS NULL
               OR e.control_code like $14 || '%')
          AND (($3 IS NULL AND $2 IS NULL)
                OR EXISTS (select 1
                             from matching_eca_contacts mec
                            where mec.credit_id = ec.id)
                OR EXISTS (select 1
                             from matching_entity_contacts mec
                            where mec.entity_id = e.id))
           AND (($5 IS NULL AND $6 IS NULL
                 AND $7 IS NULL AND $8 IS NULL
                 AND $9 IS NULL)
                OR EXISTS (select 1
                             from matching_locations m
                             join eca_to_location etl ON m.id = etl.location_id
                            where etl.credit_id = ec.id)
                OR EXISTS (select 1
                             from matching_locations m
                             join entity_to_location etl
                                  ON m.id = etl.location_id
                            where etl.entity_id = e.id))
           AND ($12 IS NULL
                OR ec.business_id = $12)
           AND ($11 IS NULL
                OR ec.startdate <= $11)
           AND ($10 IS NULL
                OR ec.enddate >= ec.enddate)
           AND ($4 IS NULL
                OR ec.meta_number like $4 || '%')
           AND ($15 IS NULL
                OR EXISTS (select 1 from entity_note n
                            where e.id = n.entity_id
                                  and note @@ plainto_tsquery($15))
                OR EXISTS (select 1 from eca_note n
                            where ec.id = n.ref_key
                                  and note @@ plainto_tsquery($15)))
           AND ($16 IS NULL OR NOT $16
                OR EXISTS (select 1 from users where entity_id = e.id))
               ORDER BY legal_name
$sql$
USING in_entity_class, in_contact, in_contact_info, in_meta_number,
 in_address, in_city, in_state, in_mail_code,
 in_country, in_active_date_from, in_active_date_to, in_business_id,
 in_name_part, in_control_code, in_notes, in_users;
END

Function: contact_class__list()

Returns: SET OF contact_class

Language: SQL

Returns a list of contact classes ordered by ID.

                SELECT * FROM contact_class ORDER BY id;

Function: cr_coa_to_account_save(in_accno text, in_description text)

Returns: void

Language: PLPGSQL

Provides default rules for setting reconciliation labels. Currently saves a label of accno ||'--' || description.

    DECLARE
       v_chart_id int;
    BEGIN
        -- Check for existence of the account already
        PERFORM * FROM cr_coa_to_account cr
        JOIN account a on cr.chart_id = a.id
        WHERE accno = in_accno;

        IF NOT FOUND THEN
           -- This is a new account. Insert the relevant data.
           SELECT id INTO v_chart_id FROM account WHERE accno = in_accno;
           INSERT INTO cr_coa_to_account (chart_id, account) VALUES (v_chart_id, in_accno||'--'||in_description);
        END IF;
        -- Already found, no need to do anything. =)
    END;

Function: cr_report_block_changing_approved()

Returns: trigger

Language: PLPGSQL

This is a simple filter that prevents updating or deleting reconciliation reports that have already been approved. To purge old reconciliations you must disable the block_change_when_approved trigger on cr_report.

BEGIN
   IF OLD.approved IS TRUE THEN
       RAISE EXCEPTION 'Report is approved.  Cannot change!';
   END IF;
   IF TG_OP = 'DELETE' THEN
       RETURN OLD;
   ELSE
      RETURN NEW;
   END IF;
END;

Function: cr_report_line_cleared_update()

Returns: trigger

Language: PLPGSQL

BEGIN
  UPDATE cr_report_line_links rll
     SET cleared = NEW.cleared and r.submitted
    FROM cr_report r
   WHERE rll.report_line_id = NEW.id
         AND r.id = NEW.report_id;

  RETURN NEW;
END;

Function: cr_report_line_link_insert()

Returns: trigger

Language: PLPGSQL

BEGIN
  NEW.cleared = (select r.submitted and rl.cleared
                   from cr_report_line rl
                   join cr_report r on rl.report_id = r.id
                  where rl.id = NEW.report_line_id);

  RETURN NEW;
END;

Function: cr_report_submitted_update()

Returns: trigger

Language: PLPGSQL

BEGIN
  UPDATE cr_report_line_links rll
     SET cleared = rl.cleared and NEW.submitted
    FROM cr_report_line rl
   WHERE rll.report_line_id = rl.id
         AND rl.report_id = NEW.id;

  RETURN NEW;
END;

Function: credit_limit__used(in_eca integer)

Returns: numeric

Language: PLPGSQL

This function returns the amount outstanding with the entity credit account passed as the argument, accounted in the entity_credit_account's indicated preferred currency - using the exchange rates for the server's concept of "today". The "amount outstanding" is defined as the total of all unpaid invoice amounts and all open (interpreted as unfulfilled) orders. In case the required exchange rate(s) are missing, the function returns 'NaN'::numeric. In case there is no outstanding balance, the amount returned may be either 0 (zero) or NULL.

DECLARE
  _used_tc numeric;
BEGIN
  EXECUTE $sql$
    SELECT sum(total.used)     /
         CASE WHEN (SELECT curr
                     FROM entity_credit_account
                    WHERE id = $1) =
                   (SELECT value
                      FROM defaults
                     WHERE setting_key = 'curr') THEN
             1
         ELSE
             (SELECT coalesce(rate, 'NaN'::numeric) AS rate
                FROM exchangerate__get(
                      (SELECT curr FROM entity_credit_account
                      WHERE id = $1),
                      1,
                      current_date))
         END
   FROM (
     SELECT sum(ac.amount_bc *
                CASE WHEN al.description = 'AR' THEN -1 ELSE 1 END) AS used
       FROM (select id, entity_credit_account from ap union
             select id, entity_credit_account from ar) a
       JOIN acc_trans ac ON ac.trans_id = a.id
       JOIN account_link al ON al.account_id = ac.chart_id
      WHERE al.description IN ('AR', 'AP')
        AND ac.approved
        AND a.entity_credit_account = $1
      UNION ALL
     SELECT sum(o.amount_tc * coalesce(e.rate, 'NaN'::numeric))
       FROM oe o
       LEFT JOIN (SELECT rate, curr
                    FROM exchangerate_default
                   WHERE rate_type = 1
                     AND current_date BETWEEN valid_from AND valid_to ) e
            ON o.curr = e.curr
      WHERE o.entity_credit_account = $1
   ) total;
   $sql$
   INTO _used_tc
   USING in_eca;
   RETURN _used_tc;
END

Function: currency__delete(in_curr bpchar)

Returns: void

Language: PLPGSQL

Removes the indicated currency, if it''s not the default currency or subject to other integrity constraints.

BEGIN
   IF defaults_get_defaultcurrency() = in_curr THEN
      RAISE EXCEPTION 'Unable to delete default currency %', in_curr;
   END IF;

   -- defer the rest of the checks to the available integrity constraints
   DELETE FROM currency WHERE curr = in_curr;
END;

Function: currency__get(in_curr text)

Returns: currency

Language: SQL

Retrieves a currency and its description using the currency indicator.

   SELECT * FROM currency WHERE curr = $1;

Function: currency__is_used(in_curr text)

Returns: boolean

Language: PLPGSQL

Returns true if currency 'in_curr' is used within the current commpany database. Returns false otherwise.

BEGIN
   RETURN EXISTS (SELECT 1 FROM acc_trans WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM account_checkpoint WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM ap WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM ar WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM budget_line WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM entity_credit_account WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM exchangerate_default WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM jcitems WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM journal_line WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM oe WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM partscustomer WHERE curr = in_curr)
       OR EXISTS (SELECT 1 FROM partsvendor WHERE curr = in_curr);
END;

Function: currency__list(in_check_use boolean)

Returns: SET OF currency_list

Language: SQL

Returns all currencies, default currency first.

  select c.curr, c.description,
         case when in_check_use then currency__is_used(c.curr)
              else null end as is_used
    from currency c
    left join (select value as curr from defaults where setting_key = 'curr') d
         on c.curr = d.curr
   order by case when c.curr = d.curr then 1 else 2 end, c.curr;

Function: currency__save(in_curr text, in_description text)

Returns: text

Language: PLPGSQL

Creates a new currency if 'in_curr' doesn''t exist yet; otherwise, updates the description.

BEGIN
   UPDATE currency
      SET description = in_description
    WHERE curr = in_curr;

   IF NOT FOUND THEN
     INSERT INTO currency (curr, description)
          VALUES (in_curr, in_description);
   END IF;

   RETURN in_curr;
END;

Function: customer_location_save(in_entity_id integer, in_location_class integer, in_line_one text, in_line_two text, in_line_three text, in_city text, in_state text, in_mail_code text, in_country_id integer)

Returns: integer

Language: PLPGSQL

    BEGIN
    return _entity_location_save(
        in_entity_id, NULL,
        in_location_class, in_line_one, in_line_two, in_line_three,
        in_city, in_state, in_mail_code, in_country_id);
    END;


Function: date_get_all_years()

Returns: SET OF integer

Language: SQL

This function return each year inside transdate in transactions. Currently it uses a sparse index scan because the number of rows returned is very small and the table can be very large.

WITH RECURSIVE max_dates AS (
    SELECT max(transdate) AS max_date
      FROM acc_trans
    WHERE transdate IS NOT NULL

 UNION ALL
    SELECT (SELECT max(transdate)
              FROM acc_trans
                   -- the index acc_trans_transdate_year_idx uses the
                   -- date_part function with the exact syntax and capitals
                   -- below; changing the 'YEAR' capitals will stop the
                   -- query optimizer from using the index
             WHERE date_part('YEAR', transdate) < date_part('YEAR', max_date))
      FROM max_dates
     WHERE max_date IS NOT NULL)

SELECT date_part('YEAR', max_date)::int
  FROM max_dates
 WHERE max_date IS NOT NULL;

Function: days_in_month(in_date date)

Returns: integer

Language: SQL

Returns the number of days in the month that includes in_date.

SELECT (extract(DOM FROM date_trunc('month', $1)
                         + '1 month - 1 second'::interval)
      )::int;


Function: deduction__list_for_entity(in_entity_id integer)

Returns: SET OF payroll_deduction

Language: SQL

SELECT * FROM payroll_deduction WHERE entity_id = $1;

Function: deduction__list_types(in_country_id integer)

Returns: SET OF payroll_deduction_type

Language: SQL

SELECT * FROM payroll_deduction_type where country_id = $1

Function: deduction__save(in_rate numeric, in_entity_id integer, in_type_id integer)

Returns: payroll_deduction

Language: PLPGSQL

DECLARE return_ded payroll_deduction;
BEGIN

UPDATE payroll_deduction
   SET rate = in_rate
 WHERE entity_id = in_entity_id and in_type_id;


IF NOT FOUND THEN
    INSERT INTO payroll_deduction (entity_id, type_id, rate)
    VALUES (in_entity_id, in_type_id, in_rate);
END IF;

SELECT * INTO return_ded FROM payroll_deduction
             WHERE entity_id = in_entity_id and in_type_id;
RETURN return_ded;
END;

Function: defaults__get_contra_accounts(in_category bpchar)

Returns: SET OF account

Language: SQL

SELECT * FROM account WHERE contra AND category = $1;

Function: defaults_get_defaultcurrency()

Returns: bpchar

Language: SQL

This function return the default currency asigned by the program.

           SELECT substr(value,1,3)
           FROM defaults
           WHERE setting_key = 'curr';

Function: draft__search(in_type text, in_reference text, in_from_date date, in_to_date date, in_amount_lt numeric, in_amount_gt numeric)

Returns: SET OF draft_search_result

Language: PLPGSQL

Searches for drafts. in_type may be any of 'ar', 'ap', or 'gl'.

BEGIN
RETURN QUERY EXECUTE $sql$
        SELECT id, transdate, invoice, reference, eca_name, description,
               type, amount FROM (
            SELECT id, transdate, reference, null::text as eca_name,
                   description, false as invoice,
                   (SELECT SUM(line.amount_bc)
                      FROM acc_trans line
                     WHERE line.amount_bc > 0
                           and line.trans_id = gl.id) as amount,
                   'gl' as type
              from gl
             WHERE (lower($1) = 'gl' or $1 is null)
                  AND NOT approved
                  AND NOT EXISTS (SELECT 1
                                    FROM voucher v
                                   WHERE v.trans_id = gl.id)
            UNION
            SELECT id, transdate, invnumber as reference,
                (SELECT name FROM eca__get_entity(entity_credit_account)) as eca_name,
                description, invoice, amount_bc as amount, 'ap' as type
              FROM ap
             WHERE (lower($1) = 'ap' or $1 is null)
                   AND NOT approved
                   AND NOT EXISTS (SELECT 1
                                     FROM voucher v
                                    WHERE v.trans_id = ap.id)
            UNION
            SELECT id, transdate, invnumber as reference,
                (SELECT name FROM eca__get_entity(entity_credit_account)) as eca_name,
                description, invoice, amount_bc as amount, 'ar' as type
              FROM ar
             WHERE (lower($1) = 'ar' or $1 is null)
                   AND NOT approved
                   AND NOT EXISTS (SELECT 1
                                     FROM voucher v
                                    WHERE v.trans_id = ar.id)) trans
        WHERE ($3 IS NULL or trans.transdate >= $3)
          AND ($4 IS NULL or trans.transdate <= $4)
          AND ($6 IS NULL or amount >= $6)
          AND ($5 IS NULL or amount <= $5)
          AND ($2 IS NULL or trans.reference = $2)
        ORDER BY trans.reference
$sql$
USING in_type, in_reference, in_from_date, in_to_date, in_amount_lt, in_amount_gt;
END

Function: draft_approve(in_id integer)

Returns: boolean

Language: PLPGSQL

Posts draft to the books. in_id is the id from the ar, ap, or gl table.

declare
        t_table text;
begin
        SELECT table_name into t_table FROM transactions where id = in_id;

        IF (t_table = 'ar') THEN
                PERFORM cogs__add_for_ar_line(id) FROM invoice
                  WHERE trans_id = in_id;
                UPDATE ar set approved = true where id = in_id;
        ELSIF (t_table = 'ap') THEN
                PERFORM cogs__add_for_ap_line(id) FROM invoice
                  WHERE trans_id = in_id;
                UPDATE ap set approved = true where id = in_id;
        ELSIF (t_table = 'gl') THEN
                UPDATE gl set approved = true where id = in_id;
        ELSE
                raise exception 'Invalid table % in draft_approve for transaction %', t_table, in_id;
        END IF;

        IF NOT FOUND THEN
                RETURN FALSE;
        END IF;

        UPDATE transactions
        SET approved_by =
                        (select entity_id FROM users
                        WHERE username = SESSION_USER),
                approved_at = now()
        WHERE id = in_id;

        UPDATE acc_trans
        SET approved = 't'::boolean
        WHERE trans_id = in_id;

        RETURN TRUE;
END;

Function: draft_delete(in_id integer)

Returns: boolean

Language: PLPGSQL

Deletes the draft from the book. Only will delete unapproved transactions. Otherwise an exception is raised and the transaction terminated.

declare
        t_table text;
begin
        DELETE FROM ac_tax_form atf
         WHERE EXISTS (SELECT 1 FROM acc_trans
                        WHERE entry_id = atf.entry_id
                              AND trans_id = in_id);

        DELETE FROM payment_links pl
         WHERE EXISTS (SELECT 1 FROM acc_trans
                        WHERE entry_id = pl.entry_id
                              AND trans_id = in_id)
               AND (SELECT count(distinct ac.trans_id)
                      FROM payment p
                      JOIN payment_links pli ON p.id = pli.payment_id
                      JOIN acc_trans ac ON pli.entry_id = ac.entry_id
                     WHERE pl.payment_id = p.id) <= 1;

        DELETE FROM acc_trans WHERE trans_id = in_id;
        DELETE FROM invoice_tax_form itf
           WHERE EXISTS (select 1 from invoice i
                          where i.trans_id = in_id and itf.invoice_id = i.id);
        DELETE FROM invoice WHERE trans_id = in_id;
        SELECT lower(table_name) into t_table
          FROM transactions where id = in_id;

        IF t_table = 'ar' THEN
                DELETE FROM ar WHERE id = in_id AND approved IS FALSE;
        ELSIF t_table = 'ap' THEN
                DELETE FROM ap WHERE id = in_id AND approved IS FALSE;
        ELSIF t_table = 'gl' THEN
                DELETE FROM gl WHERE id = in_id AND approved IS FALSE;
        ELSE
                raise exception 'Invalid table % in draft_delete for transaction %', t_table, in_id;
        END IF;
        IF NOT FOUND THEN
                RAISE EXCEPTION 'Invalid transaction id %', in_id;
        END IF;
        RETURN TRUE;
END;

Function: eca__delete_contact(in_credit_id integer, in_class_id integer, in_contact text)

Returns: boolean

Language: PLPGSQL

Returns true if at least one record was deleted. False if no records were affected.

BEGIN

DELETE FROM eca_to_contact
 WHERE credit_id = in_credit_id and contact_class_id = in_class_id
       and contact= in_contact;
RETURN FOUND;

END;


Function: eca__delete_location(in_credit_id integer, in_id integer, in_location_class integer)

Returns: boolean

Language: PLPGSQL

Deletes the record identified. Returns true if successful, false if no record found.

BEGIN

DELETE FROM eca_to_location
 WHERE credit_id = in_credit_id AND location_id = in_id
       AND location_class = in_location_class;

RETURN FOUND;

END;

Function: eca__delete_pricematrix(in_credit_id integer, in_entry_id integer)

Returns: boolean

Language: PLPGSQL

DECLARE retval bool;

BEGIN

retval := false;

DELETE FROM partsvendor
 WHERE entry_id = in_entry_id
       AND credit_id = in_credit_id;

retval := FOUND;

DELETE FROM partscustomer
 WHERE entry_id = in_entry_id
       AND credit_id = in_credit_id;

RETURN FOUND or retval;

END;

Function: eca__get_by_meta_number(in_meta_number text, in_entity_class integer)

Returns: entity_credit_account

Language: PLPGSQL

DECLARE
  t_retval entity_credit_account;
BEGIN
EXECUTE $sql$
SELECT * FROM entity_credit_account
 WHERE entity_class = $2 AND meta_number = $1
$sql$
INTO t_retval
USING in_meta_number, in_entity_class;
RETURN t_retval;
END

Function: eca__get_entity(in_credit_id integer)

Returns: SET OF entity

Language: SQL

Returns a set of (only one) entity to which the entity credit account is attached.


    SELECT entity.*
      FROM entity_credit_account
      JOIN entity ON entity_credit_account.entity_id = entity.id
     WHERE entity_credit_account.id = in_credit_id;


Function: eca__get_pricematrix(in_credit_id integer)

Returns: SET OF eca__pricematrix

Language: SQL

This returns the pricematrix for the customer or vendor (entity_credit_account identified by in_id), orderd by partnumber, validfrom


SELECT pc.parts_id, p.partnumber, p.description, pc.credit_id, pc.pricebreak,
       pc.sellprice, NULL, NULL::int, NULL, pc.validfrom, pc.validto, pc.curr,
       pc.entry_id, pc.qty
  FROM partscustomer pc
  JOIN parts p on pc.parts_id = p.id
  JOIN entity_credit_account eca ON pc.credit_id = eca.id
 WHERE pc.credit_id = $1 AND eca.entity_class = 2
 UNION
SELECT pv.parts_id, p.partnumber, p.description, pv.credit_id, NULL, NULL,
       pv.lastcost, pv.leadtime::int, pv.partnumber, NULL, NULL, pv.curr,
       pv.entry_id, null
  FROM partsvendor pv
  JOIN parts p on pv.parts_id = p.id
  JOIN entity_credit_account eca ON pv.credit_id = eca.id
 WHERE pv.credit_id = $1 and eca.entity_class = 1
 ORDER BY partnumber, validfrom


Function: eca__get_pricematrix_by_pricegroup(in_credit_id integer)

Returns: SET OF eca__pricematrix

Language: SQL

SELECT pc.parts_id, p.partnumber, p.description, pc.credit_id, pc.pricebreak,
       pc.sellprice, NULL::numeric, NULL::int, NULL::text, pc.validfrom,
       pc.validto, pc.curr, pc.entry_id, pc.qty
  FROM partscustomer pc
  JOIN parts p on pc.parts_id = p.id
  JOIN entity_credit_account eca ON pc.pricegroup_id = eca.pricegroup_id
 WHERE eca.id = $1 AND eca.entity_class = 2

Function: eca__get_taxes(in_id integer)

Returns: SET OF eca_tax

Language: SQL

Returns a set of taxable account id's.

select * from eca_tax where eca_id = $1;

Function: eca__history(in_name_part text, in_meta_number text, in_contact_info text, in_address_line text, in_city text, in_state text, in_zip text, in_salesperson text, in_notes text, in_country_id integer, in_from_date date, in_to_date date, in_type bpchar, in_start_from date, in_start_to date, in_entity_class integer, in_inc_open boolean, in_inc_closed boolean)

Returns: SET OF eca_history_result

Language: PLPGSQL

This produces a history detail report, i.e. a list of all products purchased by a customer over a specific date range. meta_number is an exact match, as are in_open and inc_closed. All other fields allow for partial matches. NULL matches all values.

BEGIN
RETURN QUERY EXECUTE $sql$
     WITH arap AS (
       select  invnumber, ar.curr, ar.transdate, entity_credit_account, id,
                   person_id, notes
             FROM ar
             JOIN acc_trans ON ar.id  = acc_trans.trans_id
             JOIN account_link l ON acc_trans.chart_id = l.account_id
                  and l.description = 'AR'
            where $16 = 2 and $13 = 'i'
       GROUP BY 1, 2, 3, 4, 5, 6, 7
                  having (($17 and sum(acc_trans.amount_bc) = 0)
                      or ($18 and 0 <> sum(acc_trans.amount_bc)))
            UNION ALL
           select invnumber, ap.curr, ap.transdate, entity_credit_account, id,
                  person_id, notes
             FROM ap
             JOIN acc_trans ON ap.id  = acc_trans.trans_id
             JOIN account_link l ON acc_trans.chart_id = l.account_id
                  and l.description = 'AP'
            where $16 = 1 and $13 = 'i'
       GROUP BY 1, 2, 3, 4, 5, 6, 7
                  having (($17 and sum(acc_trans.amount_bc) = 0)
                      or ($18 and 0 <> sum(acc_trans.amount_bc)))
     )
     SELECT eca.id, e.name, eca.meta_number::text,
            a.id as invoice_id, a.invnumber, a.curr::text,
            p.id AS parts_id, p.partnumber,
            a.description,
            a.qty * case when eca.entity_class = 1 THEN -1 ELSE 1 END,
            a.unit::text, a.sellprice, a.discount,
            a.deliverydate,
            a.serialnumber,
            null::numeric as exchange_rate,
            ee.id as salesperson_id,
            ep.last_name || ', ' || ep.first_name as salesperson_name,
            a.transdate
     FROM (select * from entity_credit_account
            where ($2 is null or meta_number = $2)) eca
     join entity e on eca.entity_id = e.id
     JOIN (
           SELECT a.*, i.parts_id, i.qty, i.description, i.unit,
                  i.discount, i.deliverydate, i.serialnumber, i.sellprice
             FROM arap a
             JOIN invoice i ON a.id = i.trans_id
           union
           select o.ordnumber, o.curr, o.transdate, o.entity_credit_account,
                  o.id, o.person_id, o.notes, oi.parts_id, oi.qty,
                  oi.description, oi.unit, oi.discount, oi.reqdate,
                  oi.serialnumber, oi.sellprice
             from oe o
             join orderitems oi on o.id = oi.trans_id
            where (($13 = 'o' and quotation is not true)
                   or ($13 = 'q' and quotation is true))
              and (($16 = 1 and o.oe_class_id IN (2, 4))
                   or ($16 = 2 and o.oe_class_id IN (1, 3)))
              and (($17 and not closed)
                   or ($18 and closed))
          ) a ON (a.entity_credit_account = eca.id)
     JOIN parts p ON (p.id = a.parts_id)
LEFT JOIN entity ee ON (a.person_id = ee.id)
LEFT JOIN person ep ON (ep.entity_id = ee.id)
    WHERE (e.name ilike '%' || $1 || '%' or $1 is null)
      and ($3 is null
           or exists (select 1 from eca_to_contact
                       where credit_id = eca.id
                         and contact ilike '%' || $3 || '%'))
      and (($4 is null
            and $5 is null
            and $6 is null
            and $7 is null
            and $10 is null)
           or exists (select 1 from eca_to_location etl
                       where etl.credit_id = eca.id
                         and exists (select 1 from location l
                                      where l.id = etl.location_id
                                        and ($4 is null
                                             or line_one ilike '%' || $4 || '%'
                                             or line_two ilike '%' || $4 || '%')
                                        and ($5 is null
                                             or city ilike '%' || $5 || '%')
                                        and ($6 is null
                                             or state ilike '%' || $6 || '%')
                                        and ($7 is null
                                             or mail_code ilike '%' || $7 || '%')
                                        and ($10 is null
                                             or country_id = $10))
                     )
          )
          and (a.transdate >= $11 or $11 is null)
          and (a.transdate <= $12 or $12 is null)
          and (eca.startdate >= $14 or $14 is null)
          and (eca.startdate <= $15 or $15 is null)
          and (a.notes @@ plainto_tsquery($9) or $9 is null)
 ORDER BY eca.meta_number, p.partnumber
$sql$
USING in_name_part, in_meta_number, in_contact_info, in_address_line,
 in_city, in_state, in_zip, in_salesperson,
 in_notes, in_country_id, in_from_date, in_to_date,
 in_type, in_start_from, in_start_to, in_entity_class,
 in_inc_open, in_inc_closed;
END

Function: eca__history_summary(in_name_part text, in_meta_number text, in_contact_info text, in_address_line text, in_city text, in_state text, in_zip text, in_salesperson text, in_notes text, in_country_id integer, in_from_date date, in_to_date date, in_type bpchar, in_start_from date, in_start_to date, in_entity_class integer, in_inc_open boolean, in_inc_closed boolean)

Returns: SET OF eca_history_result

Language: PLPGSQL

Creates a summary account (no quantities, just parts group by invoice). meta_number must match exactly or be NULL. inc_open and inc_closed are exact matches too. All other values specify ranges or may match partially.

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT id, name, meta_number::text, null::int, null::text, curr, parts_id, partnumber,
       description, sum(qty), unit, null::numeric, null::numeric, null::date,
       null::text, null::numeric,
       null::int, null::text, null::date
FROM   eca__history($1, $2, $3, $4, $5, $6, $7, $8, $9,
                   $10, $11, $12, $13, $14, $15, $16, $17, $18)
 group by id, name, meta_number, curr, parts_id, partnumber, description, unit,
          sellprice
 order by meta_number
$sql$
USING in_name_part, in_meta_number, in_contact_info, in_address_line,
 in_city, in_state, in_zip, in_salesperson,
 in_notes, in_country_id, in_from_date, in_to_date,
 in_type, in_start_from, in_start_to, in_entity_class,
 in_inc_open, in_inc_closed;
END

Function: eca__list_contacts(in_credit_id integer)

Returns: SET OF contact_list

Language: PLPGSQL

Returns a list of contact info attached to the entity credit account.

DECLARE out_row contact_list;
BEGIN
        FOR out_row IN
                SELECT cl.class, cl.id, c.description, c.contact
                FROM eca_to_contact c
                JOIN contact_class cl ON (c.contact_class_id = cl.id)
                WHERE credit_id = in_credit_id
        LOOP
                return next out_row;
        END LOOP;
END;

Function: eca__list_locations(in_credit_id integer)

Returns: SET OF location_result

Language: SQL

Returns a list of locations attached to the credit account.

                SELECT l.id, l.line_one, l.line_two, l.line_three, l.city,
                        l.state, l.mail_code, c.id, c.name, lc.id, lc.class
                FROM location l
                JOIN eca_to_location ctl ON (ctl.location_id = l.id)
                JOIN location_class lc ON (ctl.location_class = lc.id)
                JOIN country c ON (c.id = l.country_id)
                WHERE ctl.credit_id = in_credit_id
                ORDER BY lc.id, l.id, c.name

Function: eca__list_notes(in_credit_id integer)

Returns: SET OF note

Language: PLPGSQL

Returns a list of notes attached to the entity credit account.

DECLARE out_row record;
        t_entity_id int;
BEGIN
        -- ALERT: security definer function.  Be extra careful about EXECUTE
        -- in here. --CT
        SELECT entity_id INTO t_entity_id
        FROM entity_credit_account
        WHERE id = in_credit_id;

        FOR out_row IN
                SELECT *
                FROM note
                WHERE (note_class = 3 and ref_key = in_credit_id) or
                        (note_class = 1 and ref_key = t_entity_id)
                ORDER BY created
        LOOP
                RETURN NEXT out_row;
        END LOOP;
END;

Function: eca__location_save(in_credit_id integer, in_id integer, in_location_class integer, in_line_one text, in_line_two text, in_line_three text, in_city text, in_state text, in_mail_code text, in_country_id integer, in_old_location_class integer)

Returns: integer

Language: PLPGSQL

Saves a location to an entity credit account. Returns id of saved record.


    DECLARE
        l_row location;
        l_id INT;
        l_orig_id INT;
    BEGIN

        UPDATE eca_to_location
           SET location_class = in_location_class
         WHERE credit_id = in_credit_id
           AND location_class = in_old_location_class
           AND location_id = in_id;

         IF FOUND THEN
            SELECT location_save(
                in_id,
                in_line_one,
                in_line_two,
                in_line_three,
                in_city,
                in_state,
                in_mail_code,
                in_country_id
            )
                INTO l_id;
        ELSE
            SELECT location_save(
                NULL,
                in_line_one,
                in_line_two,
                in_line_three,
                in_city,
                in_state,
                in_mail_code,
                in_country_id
            )
                INTO l_id;
            INSERT INTO eca_to_location
                        (credit_id, location_class, location_id)
                VALUES  (in_credit_id, in_location_class, l_id);

        END IF;

        RETURN l_id;
    END;


Function: eca__save(in_id integer, in_entity_class integer, in_entity_id integer, in_description text, in_discount numeric, in_taxincluded boolean, in_creditlimit numeric, in_discount_terms integer, in_terms integer, in_meta_number character varying, in_business_id integer, in_language_code character varying, in_pricegroup_id integer, in_curr bpchar, in_startdate date, in_enddate date, in_threshold numeric, in_ar_ap_account_id integer, in_cash_account_id integer, in_pay_to_name text, in_taxform_id integer, in_discount_account_id integer)

Returns: integer

Language: PLPGSQL

Saves an entity credit account. Returns the id of the record saved.


    DECLARE
        t_entity_class int;
        l_id int;
        t_meta_number text;
        t_mn_default_key text;
    BEGIN
        -- TODO:  Move to mapping table.
            IF in_entity_class = 1 THEN
               t_mn_default_key := 'vendornumber';
            ELSIF in_entity_class = 2 THEN
               t_mn_default_key := 'customernumber';
            END IF;
            IF in_meta_number IS NULL THEN
                t_meta_number := setting_increment(t_mn_default_key);
            ELSE
                t_meta_number := in_meta_number;
            END IF;
            update entity_credit_account SET
                discount = in_discount,
                taxincluded = in_taxincluded,
                creditlimit = in_creditlimit,
                description = in_description,
                terms = in_terms,
                ar_ap_account_id = in_ar_ap_account_id,
                cash_account_id = in_cash_account_id,
                discount_account_id = in_discount_account_id,
                meta_number = t_meta_number,
                business_id = in_business_id,
                language_code = in_language_code,
                pricegroup_id = in_pricegroup_id,
                curr = in_curr,
                startdate = in_startdate,
                enddate = in_enddate,
                threshold = in_threshold,
                discount_terms = in_discount_terms,
                pay_to_name = in_pay_to_name,
                taxform_id = in_taxform_id
            where id = in_id;

         IF FOUND THEN
            RETURN in_id;
         ELSE
            INSERT INTO entity_credit_account (
                entity_id,
                entity_class,
                discount,
                description,
                taxincluded,
                creditlimit,
                terms,
                meta_number,
                business_id,
                language_code,
                pricegroup_id,
                curr,
                startdate,
                enddate,
                discount_terms,
                threshold,
                ar_ap_account_id,
                pay_to_name,
                taxform_id,
                cash_account_id,
                discount_account_id
            )
            VALUES (
                in_entity_id,
                in_entity_class,
                in_discount,
                in_description,
                in_taxincluded,
                in_creditlimit,
                in_terms,
                t_meta_number,
                in_business_id,
                in_language_code,
                in_pricegroup_id,
                in_curr,
                in_startdate,
                in_enddate,
                in_discount_terms,
                in_threshold,
                in_ar_ap_account_id,
                in_pay_to_name,
                in_taxform_id,
                in_cash_account_id,
                in_discount_account_id
            );
            RETURN currval('entity_credit_account_id_seq');
       END IF;

    END;


Function: eca__save_contact(in_credit_id integer, in_class_id integer, in_description text, in_contact text, in_old_contact text, in_old_class_id integer)

Returns: eca_to_contact

Language: PLPGSQL

Saves the contact record at the entity credit account level. Returns 1.

DECLARE out_contact eca_to_contact;
BEGIN

    PERFORM *
       FROM eca_to_contact
      WHERE credit_id = in_credit_id
        AND contact_class_id = in_old_class_id
        AND contact = in_old_contact;

    IF FOUND THEN
        UPDATE eca_to_contact
           SET contact = in_contact,
               description = in_description,
               contact_class_id = in_class_id
         WHERE credit_id = in_credit_id
           AND contact_class_id = in_old_class_id
           AND contact = in_old_contact
        returning * INTO out_contact;
        return out_contact;
    END IF;
        INSERT INTO eca_to_contact(credit_id, contact_class_id,
                description, contact)
        VALUES (in_credit_id, in_class_id, in_description, in_contact)
        RETURNING * into out_contact;
        return out_contact;

END;

Function: eca__save_notes(in_credit_id integer, in_note text, in_subject text)

Returns: eca_note

Language: SQL

Saves an entity credit account-level note. Such a note is valid for only one credit account. Returns the id of the note.

        -- TODO, change this to create vector too
        INSERT INTO eca_note (ref_key, note_class, note, vector, subject)
        VALUES (in_credit_id, 3, in_note, '', in_subject)
        RETURNING *;


Function: eca__save_pricematrix(in_parts_id integer, in_credit_id integer, in_pricebreak numeric, in_price numeric, in_lead_time smallint, in_partnumber text, in_validfrom date, in_validto date, in_curr bpchar, in_entry_id integer)

Returns: eca__pricematrix

Language: PLPGSQL

DECLARE
   retval eca__pricematrix;
   t_insert bool;

BEGIN

t_insert := false;

PERFORM * FROM entity_credit_account
  WHERE id = in_credit_id AND entity_class = 1;

IF FOUND THEN -- VENDOR
    UPDATE partsvendor
       SET lastcost = in_price,
           leadtime = in_lead_time,
           partnumber = in_partnumber,
           curr = in_curr
     WHERE credit_id = in_credit_id AND entry_id = in_entry_id;

    IF NOT FOUND THEN
        INSERT INTO partsvendor
               (parts_id, credit_id, lastcost, leadtime, partnumber, curr)
        VALUES (in_parts_id, in_credit_id, in_price, in_lead_time::int2,
               in_partnumber, in_curr);
    END IF;

    SELECT pv.parts_id, p.partnumber, p.description, pv.credit_id, NULL, NULL,
           pv.lastcost, pv.leadtime::int, pv.partnumber, NULL, NULL, pv.curr,
           pv.entry_id, null
      INTO retval
      FROM partsvendor pv
      JOIN parts p ON p.id = pv.parts_id
     WHERE parts_id = in_parts_id and credit_id = in_credit_id;

    RETURN retval;
END IF;

PERFORM * FROM entity_credit_account
  WHERE id = in_credit_id AND entity_class = 2;

IF FOUND THEN -- CUSTOMER
    UPDATE partscustomer
       SET pricebreak = in_pricebreak,
           sellprice  = in_price,
           validfrom  = in_validfrom,
           validto    = in_validto,
           qty        = in_qty,
           curr       = in_curr
     WHERE entry_id = in_entry_id and credit_id = in_credit_id;

    IF NOT FOUND THEN
        INSERT INTO partscustomer
               (parts_id, credit_id, sellprice, validfrom, validto, curr, qty)
        VALUES (in_parts_id, in_credit_id, in_price, in_validfrom, in_validto,
                in_curr, in_qty);

        t_insert := true;
    END IF;

    SELECT pc.parts_id, p.partnumber, p.description, pc.credit_id,
           pc.pricebreak, pc.sellprice, NULL, NULL, NULL, pc.validfrom,
           pc.validto, pc.curr, pc.entry_id, pc.qty
      INTO retval
      FROM partscustomer pc
      JOIN parts p on pc.parts_id = p.id
     WHERE entry_id = CASE WHEN t_insert
                           THEN currval('partscustomer_entry_id_seq')
                           ELSE in_entry_id
                      END;

    RETURN retval;

END IF;

RAISE EXCEPTION 'No valid entity credit account found';

END;

Function: eca__set_taxes(in_id integer, in_tax_ids integer[])

Returns: boolean

Language: SQL

Sets the tax values for the customer or vendor. The entity credit account must exist before calling this function, and must have a type of either 1 or 2.

     DELETE FROM eca_tax WHERE eca_id = $1;
     INSERT INTO eca_tax (eca_id, chart_id)
     SELECT $1, tax_id
       FROM unnest($2) tax_id;
     SELECT TRUE;

Function: eca_bu_trigger()

Returns: trigger

Language: PLPGSQL

BEGIN
  IF TG_OP = 'INSERT' THEN
      INSERT INTO business_unit(class_id, control_code, description, credit_id)
      SELECT 7 - NEW.entity_class, NEW.meta_number,  e.name, NEW.id
             FROM entity e WHERE e.id = NEW.entity_id;
  ELSIF TG_OP = 'UPDATE' THEN
      IF new.meta_number <> old.meta_number THEN
         UPDATE business_unit SET control_code = new.meta_number
          WHERE class_id = 7 - NEW.entity_class
                AND credit_id = new.id;
      END IF;
  ELSIF TG_OP = 'DELETE'THEN
      DELETE FROM business_unit WHERE class_id = 7 - OLD.entity_class
                  AND credit_id = OLD.id;
      RETURN OLD;
  END IF;
  RETURN NEW;
END;

Function: employee__all_managers()

Returns: SET OF employee_result

Language: SQL

   SELECT p.entity_id, e.control_code, p.id, s.salutation, s.id,
          p.first_name, p.middle_name, p.last_name, ee.is_manager,
          ee.startdate, ee.enddate, ee.role, ee.ssn, ee.sales, ee.manager_id,
          mp.first_name, mp.last_name, ee.employeenumber, ee.dob, e.country_id
     FROM person p
     JOIN entity_employee ee on (ee.entity_id = p.entity_id)
     JOIN entity e ON (p.entity_id = e.id)
LEFT JOIN salutation s on (p.salutation_id = s.id)
LEFT JOIN person mp ON ee.manager_id = mp.entity_id
    WHERE ee.is_manager
 ORDER BY ee.employeenumber;

Function: employee__all_salespeople()

Returns: SET OF employee_result

Language: SQL

   SELECT p.entity_id, e.control_code, p.id, s.salutation, s.id,
          p.first_name, p.middle_name, p.last_name, ee.is_manager,
          ee.startdate, ee.enddate, ee.role, ee.ssn, ee.sales, ee.manager_id,
          mp.first_name, mp.last_name, ee.employeenumber, ee.dob, e.country_id
     FROM person p
     JOIN entity_employee ee on (ee.entity_id = p.entity_id)
     JOIN entity e ON (p.entity_id = e.id)
LEFT JOIN salutation s on (p.salutation_id = s.id)
LEFT JOIN person mp ON ee.manager_id = p.entity_id
    WHERE ee.sales
 ORDER BY ee.employeenumber;

Function: employee__get(in_entity_id integer)

Returns: employee_result

Language: SQL

Returns an employee_result tuple with information specified by the entity_id.

   SELECT p.entity_id, e.control_code, p.id, s.salutation, s.id,
          p.first_name, p.middle_name, p.last_name, ee.is_manager,
          ee.startdate, ee.enddate, ee.role, ee.ssn, ee.sales, ee.manager_id,
          mp.first_name, mp.last_name, ee.employeenumber, ee.dob, e.country_id
     FROM person p
     JOIN entity_employee ee on (ee.entity_id = p.entity_id)
     JOIN entity e ON (p.entity_id = e.id)
LEFT JOIN salutation s on (p.salutation_id = s.id)
LEFT JOIN person mp ON ee.manager_id = p.entity_id
    WHERE p.entity_id = $1;

Function: employee__get_user(in_entity_id integer)

Returns: users

Language: SQL

Returns username, user_id, etc. information if the employee is a user.

SELECT * FROM users WHERE entity_id = $1;

Function: employee__list_managers(in_id integer)

Returns: SET OF employees

Language: SQL

Returns a list of managers, that is employees with the 'manager' role set.

                SELECT
                    s.salutation,
                    p.first_name,
                    p.last_name,
                    ee.*
                FROM entity_employee ee
                JOIN entity e on e.id = ee.entity_id
                JOIN person p ON p.entity_id = e.id
                JOIN salutation s ON s.id = p.salutation_id
                WHERE ee.sales = 't'::bool AND ee.role='manager'
                        AND ee.entity_id <> coalesce(in_id, -1)
                ORDER BY name

Function: employee__save(in_entity_id integer, in_start_date date, in_end_date date, in_dob date, in_role text, in_ssn text, in_sales boolean, in_manager_id integer, in_employeenumber text, in_is_manager boolean)

Returns: integer

Language: PLPGSQL

Saves an employeerecord with the specified information.

DECLARE out_id INT;
BEGIN
        UPDATE entity_employee
        SET startdate = coalesce(in_start_date, now()::date),
                enddate = in_end_date,
                dob = in_dob,
                role = in_role,
                ssn = in_ssn,
                manager_id = in_manager_id,
                employeenumber = in_employeenumber,
                is_manager = coalesce(in_is_manager, false),
                sales = in_sales
        WHERE entity_id = in_entity_id;

        out_id = in_entity_id;

        IF NOT FOUND THEN
                INSERT INTO entity_employee
                        (startdate, enddate, dob, role, ssn, manager_id,
                                employeenumber, entity_id, is_manager, sales)
                VALUES
                        (coalesce(in_start_date, now()::date), in_end_date,
                                in_dob, in_role, in_ssn,
                                in_manager_id, in_employeenumber,
                                in_entity_id, in_is_manager, in_sales);
                RETURN in_entity_id;
        END IF;
        RETURN out_id;
END;

Function: employee__search(in_employeenumber text, in_startdate_from date, in_startdate_to date, in_first_name text, in_middle_name text, in_last_name text, in_notes text, in_is_user boolean)

Returns: SET OF employee_result

Language: SQL

Returns a list of employee_result records matching the search criteria. employeenumber is an exact match. stardate_from and startdate_to specify the start dates for employee searches All others are partial matches. NULLs match all values.

SELECT p.entity_id, e.control_code, p.id, s.salutation, s.id,
          p.first_name, p.middle_name, p.last_name, ee.is_manager,
          ee.startdate, ee.enddate, ee.role, ee.ssn, ee.sales, ee.manager_id,
          mp.first_name, mp.last_name, ee.employeenumber, ee.dob, e.country_id
     FROM person p
     JOIN entity e ON p.entity_id = e.id
     JOIN entity_employee ee on (ee.entity_id = p.entity_id)
LEFT JOIN salutation s on (p.salutation_id = s.id)
LEFT JOIN person mp ON ee.manager_id = p.entity_id
    WHERE ($7 is null or p.entity_id in (select ref_key from entity_note
                                          WHERE note ilike '%' || $7 || '%'))
          and ($1 is null or $1 = ee.employeenumber)
          and ($2 is null or $2 <= ee.startdate)
          and ($3 is null or $3 >= ee.startdate)
          and ($4 is null or p.first_name ilike '%' || $4 || '%')
          and ($5 is null or p.middle_name ilike '%' || $5 || '%')
          and ($6 is null or p.last_name ilike '%' || $6 || '%')
          and ($8 is null
               or $8 = (exists (select 1 from users where entity_id = e.id)));

Function: employee_search(in_startdatefrom date, in_startdateto date, in_name character varying, in_notes text, in_enddateto date, in_enddatefrom date, in_sales boolean)

Returns: SET OF employee_search

Language: SQL

                SELECT * FROM employee_search
                WHERE coalesce(startdate, 'infinity'::timestamp)
                        >= coalesce(in_startdateto, '-infinity'::timestamp)
                        AND coalesce(startdate, '-infinity'::timestamp) <=
                                coalesce(in_startdatefrom,
                                                'infinity'::timestamp)
                        AND coalesce(enddate, '-infinity'::timestamp) <=
                                coalesce(in_enddateto, 'infinity'::timestamp)
                        AND coalesce(enddate, 'infinity'::timestamp) >=
                                coalesce(in_enddatefrom, '-infinity'::timestamp)
                        AND (name ilike '%' || in_name || '%'
                            OR note ilike '%' || in_notes || '%')
                        AND (sales = 't' OR coalesce(in_sales, 'f') = 'f')

Function: entity__delete_bank_account(in_entity_id integer, in_id integer)

Returns: boolean

Language: PLPGSQL

Deletes the bank account identitied by in_id if it is attached to the entity identified by entity_id. Returns true if a record is deleted, false if not.

BEGIN

UPDATE entity_credit_account SET bank_account = NULL
 WHERE entity_id = in_entity_id AND bank_account = in_id;

DELETE FROM entity_bank_account
 WHERE id = in_id AND entity_id = in_entity_id;

RETURN FOUND;

END;

Function: entity__delete_contact(in_entity_id integer, in_class_id integer, in_contact text)

Returns: boolean

Language: PLPGSQL

Returns true if at least one record was deleted. False if no records were affected.

BEGIN

DELETE FROM entity_to_contact
 WHERE entity_id = in_entity_id
       and contact_class_id = in_class_id
       and contact= in_contact;
RETURN FOUND;

END;


Function: entity__delete_location(in_entity_id integer, in_id integer, in_location_class integer)

Returns: boolean

Language: PLPGSQL

Deletes the record identified. Returns true if successful, false if no record found.

BEGIN

DELETE FROM entity_to_location
 WHERE entity_id = in_entity_id AND location_id = in_id
       AND location_class = in_location_class;

RETURN FOUND;

END;

Function: entity__get(in_entity_id integer)

Returns: SET OF entity

Language: SQL

Returns a set of (only one) entity record with the entity id.

    SELECT * FROM entity WHERE id = in_entity_id;

Function: entity__get_bank_account(in_id integer)

Returns: entity_bank_account

Language: SQL

SELECT * FROM  entity_bank_account WHERE id = $1;

Function: entity__list_bank_account(in_entity_id integer)

Returns: SET OF entity_bank_account

Language: SQL

Lists all bank accounts for the entity.

SELECT * from entity_bank_account where entity_id = in_entity_id;

Function: entity__list_classes()

Returns: SET OF entity_class

Language: PLPGSQL

Returns a list of entity classes, ordered by assigned ids

DECLARE out_row entity_class;
BEGIN
        FOR out_row IN
                SELECT * FROM entity_class
                WHERE active and pg_has_role(SESSION_USER,
                                   lsmb__role_prefix()
                                   || 'contact_class_'
                                   || lower(regexp_replace(class, '( |\-)', '_')),
                                   'USAGE')
                ORDER BY id
        LOOP
                RETURN NEXT out_row;
        END LOOP;
END;

Function: entity__list_contacts(in_entity_id integer)

Returns: SET OF contact_list

Language: SQL

Lists all contact info for the entity.

                SELECT cl.class, cl.id, c.description, c.contact
                FROM entity_to_contact c
                JOIN contact_class cl ON (c.contact_class_id = cl.id)
                WHERE c.entity_id = in_entity_id

Function: entity__list_credit(in_entity_id integer, in_entity_class integer)

Returns: SET OF entity_credit_retrieve

Language: PLPGSQL

Returns a list of entity credit account entries for the entity and of the entity class.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT  ec.id, e.id, ec.entity_class, ec.discount,
                        ec.discount_terms,
                        ec.taxincluded, ec.creditlimit, ec.terms,
                        ec.meta_number::text, ec.description, ec.business_id,
                        ec.language_code::text,
                        ec.pricegroup_id, ec.curr::text, ec.startdate,
                        ec.enddate, ec.ar_ap_account_id, ec.cash_account_id,
                        ec.discount_account_id,
                        ec.threshold, e.control_code, ec.id, ec.pay_to_name,
                        ec.taxform_id
                FROM entity e
                JOIN entity_credit_account ec ON (e.id = ec.entity_id)
                WHERE e.id = $1
                       AND (ec.entity_class = $2
                            or $2 is null)
$sql$
USING in_entity_id, in_entity_class;
END

Function: entity__list_locations(in_entity_id integer)

Returns: SET OF location_result

Language: SQL

Lists all locations for an entity.

                SELECT l.id, l.line_one, l.line_two, l.line_three, l.city,
                        l.state, l.mail_code, c.id, c.name, lc.id, lc.class
                FROM location l
                JOIN entity_to_location ctl ON (ctl.location_id = l.id)
                JOIN location_class lc ON (ctl.location_class = lc.id)
                JOIN country c ON (c.id = l.country_id)
                WHERE ctl.entity_id = in_entity_id
                ORDER BY lc.id, l.id, c.name;

Function: entity__list_notes(in_entity_id integer)

Returns: SET OF entity_note

Language: SQL

Returns a set of notes (including content) attached to the entity.

                SELECT *
                FROM entity_note
                WHERE ref_key = in_entity_id
                ORDER BY created

Function: entity__location_save(in_entity_id integer, in_id integer, in_location_class integer, in_line_one text, in_line_two text, in_line_three text, in_city text, in_state text, in_mail_code text, in_country_id integer, in_created date)

Returns: integer

Language: PLPGSQL

Saves a location to a company. Returns the location id.

    BEGIN
    return _entity_location_save(
        in_entity_id, in_id,
        in_location_class, in_line_one, in_line_two,
        in_line_three, in_city , in_state, in_mail_code, in_country_id);
    END;


Function: entity__save_bank_account(in_entity_id integer, in_credit_id integer, in_bic text, in_iban text, in_remark text, in_bank_account_id integer)

Returns: entity_bank_account

Language: PLPGSQL

Saves bank account to the credit account.

DECLARE out_bank entity_bank_account;
BEGIN
        UPDATE entity_bank_account
           SET bic = coalesce(in_bic,''),
               iban = in_iban,
               remark = in_remark
         WHERE id = in_bank_account_id;

        IF FOUND THEN
             SELECT * INTO out_bank from entity_bank_account WHERE id = in_bank_account_id;

        ELSE
                INSERT INTO entity_bank_account(entity_id, bic, iban, remark)
                VALUES(in_entity_id, in_bic, in_iban, in_remark);
                SELECT * INTO out_bank from entity_bank_account WHERE id = CURRVAL('entity_bank_account_id_seq');
        END IF;

        IF in_credit_id IS NOT NULL THEN
                UPDATE entity_credit_account SET bank_account = out_bank.id
                WHERE id = in_credit_id;
        END IF;
        return out_bank;

END;

Function: entity__save_contact(in_entity_id integer, in_class_id integer, in_description text, in_contact text, in_old_contact text, in_old_class_id integer)

Returns: entity_to_contact

Language: SQL

Saves company contact information. The return value is meaningless.

        DELETE FROM entity_to_contact
         WHERE entity_id = in_entity_id AND contact = in_old_contact
               AND contact_class_id = in_old_class_id;

        INSERT INTO entity_to_contact
               (entity_id, contact_class_id, description, contact)
        VALUES (in_entity_id, in_class_id, in_description, in_contact)
         RETURNING *;

Function: entity__save_notes(in_entity_id integer, in_note text, in_subject text)

Returns: entity_note

Language: SQL

Saves an entity-level note. Such a note is valid for all credit accounts attached to that entity. Returns the id of the note.

        -- TODO, change this to create vector too
        INSERT INTO entity_note (ref_key, note_class, entity_id, note, vector, subject)
        VALUES (in_entity_id, 1, in_entity_id, in_note, '', in_subject)
        RETURNING *;


Function: entity_credit__get(in_id integer)

Returns: entity_credit_account

Language: SQL

Returns the entity credit account info.

SELECT * FROM entity_credit_account WHERE id = $1;

Function: entity_credit_get_id(in_entity_id integer, in_entity_class integer, in_meta_number text)

Returns: integer

Language: SQL

Returns an entity credit id, based on entity_id, entity_class, and meta_number. This is the preferred way to locate an account if all three of these are known

        SELECT id FROM entity_credit_account
        WHERE entity_id = in_entity_id
                AND in_entity_class = entity_class
                AND in_meta_number = meta_number;


Function: entity_credit_get_id_by_meta_number(in_meta_number text, in_account_class integer)

Returns: integer

Language: SQL

Returns the credit id from the meta_number and entity_class.

        SELECT id
        FROM entity_credit_account
        WHERE meta_number = in_meta_number
                AND entity_class = in_account_class;


Function: entity_save(in_entity_id integer, in_name text, in_entity_class integer)

Returns: integer

Language: PLPGSQL

Currently unused. Left in because it is believed it may be helpful. This saves an entity, with the control code being the next available via the defaults table.


    DECLARE
        e entity;
        e_id int;

    BEGIN

        select * into e from entity where id = in_entity_id;

        update
            entity
        SET
            name = in_name,
            entity_class = in_entity_class
        WHERE
            id = in_entity_id;
        IF NOT FOUND THEN
            -- do the insert magic.
            e_id = nextval('entity_id_seq');
            insert into entity (id, name, entity_class) values
                (e_id,
                in_name,
                in_entity_class
                );
            return e_id;
        END IF;
        return in_entity_id;

    END;


Function: eoy__latest_checkpoint()

Returns: account_checkpoint

Language: SQL

This returns a single checkpoint from the latest set. Which account and info is returned is non-determinative and so only the end date shoudl be relied on.

   SELECT * FROM account_checkpoint ORDER BY end_date DESC LIMIT 1;

Function: eoy__reopen_books_at(in_reopen_date date)

Returns: boolean

Language: SQL


  WITH eoy_dates AS (
      SELECT end_date
              FROM account_checkpoint
             WHERE end_date >= $1
             GROUP BY end_date
    ORDER BY end_date DESC
    )
    SELECT eoy_reopen_books(end_date)
      FROM eoy_dates;

SELECT CASE WHEN (SELECT count(*) > 0 from account_checkpoint
                   where end_date = $1 - 1)
            THEN true
            ELSE eoy_create_checkpoint($1 - 1) > 0
       END;


Function: eoy_close_books(in_end_date date, in_reference text, in_description text, in_retention_acc_id integer)

Returns: boolean

Language: PLPGSQL

Zeroes accounts and then creates a checkpoint. in_end_date is the date when the books are to be closed, in_reference and in_description become the reference and description of the gl transaction, and in_retention_acc_id is the retained earnings account id.

BEGIN
        IF eoy_zero_accounts(in_end_date, in_reference, in_description, in_retention_acc_id) > 0 THEN
                PERFORM eoy_create_checkpoint(in_end_date);
                RETURN TRUE;
        ELSE
                RETURN FALSE;
        END IF;
END;

Function: eoy_create_checkpoint(in_end_date date)

Returns: integer

Language: PLPGSQL

Creates checkpoints for each account at a specific date. Books are considered closed when they occur before the latest checkpoint timewise. This means that balances (and credit/debit amounts) can be calculated starting at a checkpoint and moving forward (thus providing a mechanism for expunging old data while keeping balances correct at some future point).

DECLARE ret_val int;
        approval_check int;
        cp_date        date;
BEGIN
        IF in_end_date > now()::date THEN
                RAISE EXCEPTION 'Invalid date:  Must be earlier than present';
        END IF;

        SELECT count(*) into approval_check
        FROM acc_trans ac
        JOIN (select id, approved, transdate from transactions
             ) gl ON (gl.id = ac.trans_id)
        WHERE (ac.approved IS NOT TRUE AND ac.transdate <= in_end_date)
                OR (gl.approved IS NOT TRUE AND gl.transdate <= in_end_date);

        if approval_check > 0 THEN
                RAISE EXCEPTION 'Unapproved transactions in closed period';
        END IF;

        SELECT coalesce(max(end_date),(select min(transdate)-1
                                         from acc_trans)) INTO cp_date
          FROM account_checkpoint
         WHERE end_date < in_end_date;

        INSERT INTO
        account_checkpoint (end_date, account_id, amount_bc,
                            amount_tc, curr, debits, credits)
        SELECT in_end_date, account.id,
            COALESCE(a.amount_bc,0) + COALESCE(cp.amount_bc, 0),
            COALESCE(a.amount_tc,0) + COALESCE(cp.amount_tc, 0),
            COALESCE(a.curr, cp.curr, defaults_get_defaultcurrency()),
            COALESCE(a.debits, 0) + COALESCE(cp.debits, 0),
            COALESCE(a.credits, 0) + COALESCE(cp.credits, 0)
        FROM (SELECT
                chart_id, curr,
                SUM(amount_bc) as amount_bc,
                SUM(amount_tc) as amount_tc,
                SUM(CASE WHEN (amount_bc < 0) THEN amount_bc
                                           ELSE 0 END) as debits,
                SUM(CASE WHEN (amount_bc > 0) THEN amount_bc
                                           ELSE 0 END) as credits
                  FROM acc_trans
                 WHERE transdate <= in_end_date
                       AND transdate > cp_date
                 GROUP BY chart_id, curr) a
        FULL OUTER JOIN (
              SELECT account_id, curr,
                     end_date, amount_bc, amount_tc, debits, credits
                FROM account_checkpoint
                WHERE end_date = cp_date) cp
           ON (a.chart_id = cp.account_id) and (a.curr = cp.curr)
        RIGHT JOIN account
           ON account.id = a.chart_id
              OR account.id = cp.account_id;

        SELECT count(*) INTO ret_val FROM account_checkpoint
        where end_date = in_end_date;

        return ret_val;
END;

Function: eoy_earnings_accounts()

Returns: SET OF account

Language: SQL

Lists equity accounts for the retained earnings dropdown.

    SELECT *
      FROM account
     WHERE category = 'Q'
     ORDER BY accno;

Function: eoy_reopen_books(in_end_date date)

Returns: boolean

Language: PLPGSQL

Removes checkpoints and reverses yearend transactions on in_end_date

BEGIN
  PERFORM count(*) FROM account_checkpoint WHERE end_date = in_end_date;
  IF NOT FOUND THEN
    RETURN FALSE;
  END IF;

  PERFORM * FROM account_checkpoint WHERE end_date > in_end_date;
  IF FOUND THEN
    RAISE EXCEPTION 'Only last closed period can be reopened';
  END IF;

  DELETE FROM account_checkpoint WHERE end_date = in_end_date;

  PERFORM count(*) FROM yearend
    WHERE transdate = in_end_date and reversed is not true;

  IF FOUND THEN
    DECLARE
      t_new_trans_id int;
    BEGIN
      INSERT INTO gl (transdate, reference, description, approved, trans_type_code)
      SELECT in_end_date, 'Reversing ' || reference, 'Reversing ' || description, true, 'ye'
        FROM gl
       WHERE id = (select trans_id from yearend
                    where transdate = in_end_date
                      and reversed is not true)
             RETURNING id INTO t_new_trans_id;

      UPDATE transactions
         SET reversing = (select trans_id
                            from yearend
                           where transdate = in_end_date
                             and reversed is not true)
       WHERE id = t_new_trans_id;

      INSERT INTO acc_trans (chart_id, amount_bc, curr, amount_tc,
                             transdate, trans_id, approved)
      SELECT chart_id, amount_bc * -1, curr, amount_tc * -1,
             in_end_date, t_new_trans_id, true
        FROM acc_trans where trans_id = (select trans_id
                                           from yearend
                                          where transdate = in_end_date
                                            and reversed is not true);

      UPDATE yearend
         SET reversed = true
       where transdate = in_end_date
                        and reversed is not true;
    END;
  END IF;

  DELETE FROM account_checkpoint WHERE end_date = in_end_date;
  RETURN TRUE;
END;

Function: eoy_zero_accounts(in_end_date date, in_reference text, in_description text, in_retention_acc_id integer)

Returns: integer

Language: PLPGSQL

Posts a transaction which zeroes the income and expense accounts, moving the net balance there into a retained earnings account identified by in_retention_acc_id.

DECLARE
   ret_val int;
   cp_date date;
BEGIN
        INSERT INTO gl (transdate, reference, description, approved,
                        trans_type_code)
        VALUES (in_end_date, in_reference, in_description, true, 'ye');

        INSERT INTO yearend (trans_id, transdate)
             VALUES (currval('id'), in_end_date);

        SELECT coalesce(max(end_date),
                        (SELECT min(transdate)-1 FROM acc_trans)) INTO cp_date
          FROM account_checkpoint;

        INSERT INTO acc_trans (transdate, chart_id, trans_id,
                               amount_bc, curr, amount_tc)
        SELECT in_end_date, a.chart_id, currval('id'),
               (coalesce(a.amount_bc, 0) + coalesce(cp.amount_bc, 0)) * -1,
               coalesce(a.curr,cp.curr),
               (coalesce(a.amount_tc, 0) + coalesce(cp.amount_tc, 0)) * -1
        FROM (SELECT chart_id, sum(amount_bc) as amount_bc, curr,
                     sum(amount_tc) as amount_tc
                FROM acc_trans a
        JOIN account acc ON (acc.id = a.chart_id)
               WHERE transdate <= in_end_date
                     AND transdate > cp_date
                AND (acc.category IN ('I', 'E')
                      OR acc.category = 'Q' AND acc.is_temp)
               GROUP BY chart_id, curr) a
        LEFT JOIN (
                SELECT account_id, end_date, amount_bc, curr, amount_tc
                  FROM account_checkpoint
                 WHERE end_date = (select max(end_date) from account_checkpoint
                                    where end_date < in_end_date)
                ) cp
           ON (a.chart_id = cp.account_id) AND a.curr = cp.curr;

        INSERT INTO acc_trans (transdate, trans_id, chart_id,
                               amount_bc, curr, amount_tc)
        SELECT in_end_date, currval('id'), in_retention_acc_id,
               coalesce(sum(amount_bc) * -1, 0),
               -- post only default currency in retained earnings
               defaults_get_defaultcurrency(),
               coalesce(sum(amount_tc) * -1, 0)
        FROM acc_trans WHERE trans_id = currval('id');


        SELECT count(*) INTO ret_val from acc_trans
        where trans_id = currval('id');

        RETURN ret_val;
end;

Function: exchangerate__delete(in_curr text, in_rate_type numeric, in_valid_from date)

Returns: void

Language: PLPGSQL

Removes the indicated exchangerate.

BEGIN
   DELETE FROM exchangerate_default
         WHERE curr = in_curr
               AND rate_type = in_rate_type
               AND valid_from = in_valid_from;
END;

Function: exchangerate__get(in_curr text, in_type numeric, in_date date)

Returns: exchangerate_default

Language: SQL

Retrieves an exchangerate of currency in_curr and rate type in_type applicable on date in_date. Note: the returned record''s 'valid_from' may not be equal to the requested date because of rates being applicable in intervals and not solely on a single day.

   SELECT * FROM exchangerate_default
     WHERE curr = $1
           AND rate_type = $2
           AND ($3 >= valid_from AND $3 < valid_to)
   ORDER BY valid_from DESC
   LIMIT 1;

Function: exchangerate__list(in_curr text, in_rate_type numeric, in_valid_from_start date, in_valid_from_end date, in_offset numeric, in_limit numeric)

Returns: SET OF exchangerate_default

Language: SQL

Returns all exchangerates of currency in_curr and rate type in_rate_type optionally restricting records with a valid_from betwer in_valid_from_start and in_valid_from_end and skipping the first in_offset records and limiting the number of returned records to in_limit.

   SELECT * FROM exchangerate_default
    WHERE ($1 IS NULL OR curr = $1)
          AND ($2 IS NULL OR rate_type = $2)
          AND ($3 IS NULL OR valid_from >= $3)
          AND ($4 IS NULL OR valid_from <= $4)
   ORDER BY valid_from DESC
   OFFSET coalesce( $5, 0 )
   LIMIT $6;

Function: exchangerate__save(in_curr text, in_rate_type numeric, in_valid_from date, in_rate numeric)

Returns: exchangerate_default

Language: PLPGSQL

Creates a new exchangerate if one keyed on (curr,type,valid_from) doesn''t exist yet; otherwise, updates the rate.

DECLARE
   t_row exchangerate_default;
BEGIN
   UPDATE exchangerate_default
      SET rate = in_rate
    WHERE curr = in_curr
          AND rate_type = in_rate_type
          AND valid_from = in_valid_from
   RETURNING * INTO t_row;

   IF NOT FOUND THEN
      INSERT INTO exchangerate_default (curr, rate_type, valid_from, rate)
          VALUES (in_curr, in_rate_type, in_valid_from, in_rate)
      RETURNING * INTO t_row;
   END IF;

   RETURN t_row;
END;

Function: exchangerate_type__delete(in_id numeric)

Returns: void

Language: PLPGSQL

Removes the indicated exchangerate type.

BEGIN
   DELETE FROM exchangerate_type WHERE id = in_id;
END;

Function: exchangerate_type__get(in_id numeric)

Returns: exchangerate_type

Language: SQL

Retrieves an exchangerate type and its description.

   SELECT * FROM exchangerate_type WHERE id = $1;

Function: exchangerate_type__is_used(in_id integer)

Returns: boolean

Language: PLPGSQL

Returns true if exchangerate_type with id 'in_id' is used within the current commpany database. Returns false otherwise.

BEGIN
   RETURN EXISTS (SELECT 1 FROM exchangerate_default WHERE rate_type = in_id);
END;

Function: exchangerate_type__list()

Returns: SET OF exchangerate_type_list

Language: SQL

Returns all exchangerate types.

   SELECT id, description, builtin, exchangerate_type__is_used(id)
   FROM exchangerate_type;

Function: exchangerate_type__save(in_id numeric, in_description text)

Returns: text

Language: PLPGSQL

Creates a new exchangerate type if in_id is null doesn''t exist yet; otherwise, updates the description.

DECLARE
   t_id numeric;
BEGIN
   t_id := in_id;

   IF in_id IS NOT NULL THEN
      UPDATE exchangerate_type
         SET description = in_description
       WHERE id = in_id;
   END IF;

   IF in_id IS NULL OR NOT FOUND THEN
      INSERT INTO exchangerate_type (description)
          VALUES (in_description)
      RETURNING id INTO t_id;
   ELSE
      RAISE EXCEPTION 'Unable to update unknown exchangerate_type (%)', in_id;
   END IF;

   RETURN t_id;
END;

Function: file__attach_to_eca(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to a good or service. in_content OR id can be set. Setting both raises an exception. Note that currently links (setting id) is NOT supported because we dont have a use case of linking files to entity credit accounts.

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       RAISE EXCEPTION 'links not implemented';
       RETURN retval;
   ELSE
       INSERT INTO file_eca
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_email(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to an e-mail. in_content OR id can be set. Setting both raises an exception. Note that currently links (setting id) is NOT supported because we dont have a use case of linking files to e-mails

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       RAISE EXCEPTION 'links not implemented';
       RETURN retval;
   ELSE
       INSERT INTO file_email
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_entity(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to a contact or entity. in_content OR id can be set. Setting both raises an exception. Note that currently links (setting id) is NOT supported because we dont have a use case of linking files to entities

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       RAISE EXCEPTION 'links not implemented';
       RETURN retval;
   ELSE
       INSERT INTO file_entity
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_order(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to an order. in_content OR id can be set. Setting both raises an exception.

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Conflicting options file_id and content$e$;
       END IF;
       IF in_file_class = 1 THEN
           INSERT INTO file_tx_to_order
                  (file_id, source_class, ref_key, dest_class, attached_by,
                  attached_at)
           VALUES (in_id, 1, in_ref_key, 2, person__get_my_entity_id(), now());
       ELSIF in_file_class = 2 THEN
           INSERT INTO file_order_to_order
                  (file_id, source_class, ref_key, dest_class, attached_by,
                  attached_at)
           VALUES (in_id, 2, in_ref_key, 2, person__get_my_entity_id(), now());
       ELSE
           RAISE EXCEPTION $E$Invalid file class$E$;
       END IF;
       SELECT * INTO retval FROM file_base where id = in_id;
       RETURN retval;
   ELSE
       INSERT INTO file_order
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_part(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to a good or service. in_content OR id can be set. Setting both raises an exception. Note that currently links (setting id) is NOT supported because we dont have a use case of linking files to parts

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       RAISE EXCEPTION 'links not implemented';
       RETURN retval;
   ELSE
       INSERT INTO file_part
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_reconciliation(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to an e-mail. in_content OR id can be set. Setting both raises an exception. Note that currently links (setting id) is NOT supported because we dont have a use case of linking files to e-mails

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       RAISE EXCEPTION 'links not implemented';
       RETURN retval;
   ELSE
       INSERT INTO file_reconciliation
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__attach_to_tx(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text, in_id integer, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: PLPGSQL

Attaches or links a file to a transaction. in_content OR id can be set. Setting both raises an exception.

DECLARE retval file_base;
BEGIN
   IF in_id IS NOT NULL THEN
       IF in_content THEN
          RAISE EXCEPTION $e$Can't specify id and content in attachment$e$;--'
       END IF;
       INSERT INTO file_order_to_tx
              (file_id, source_class, ref_key, dest_class, attached_by,
              attached_at)
       VALUES (in_id, 2, in_ref_key, 1, person__get_my_entity_id(), now());

       SELECT * INTO retval FROM file_base where id = in_id;
       RETURN retval;
   ELSE
       INSERT INTO file_transaction
                   (content, mime_type_id, file_name, description, ref_key,
                   file_class, uploaded_by, uploaded_at)
            VALUES (in_content, in_mime_type_id, in_file_name, in_description,
                   in_ref_key, in_file_class, person__get_my_entity_id(),
                   now());
        SELECT * INTO retval FROM file_base
         where id = currval('file_base_id_seq');

        RETURN retval;
    END IF;
END;

Function: file__delete(in_id integer, in_file_class integer)

Returns: void

Language: SQL

Deletes the file identified by in_id and in_file_class.

DELETE FROM file_base where id = in_id and file_class = in_file_class;

Function: file__get(in_id integer, in_file_class integer)

Returns: file_base

Language: SQL

Retrieves the file information specified including content.

SELECT * FROM file_base where id = $1 and file_class = $2;

Function: file__get_by_name(in_file_name text, in_ref_key integer, in_file_class integer)

Returns: file_base

Language: SQL

Retrieves the file information specified including content.

SELECT * FROM file_base where file_name = in_file_name
                              and ref_key = in_ref_key
                              and file_class = in_file_class;

Function: file__get_for_template(in_ref_key integer, in_file_class integer)

Returns: SET OF file_list_item

Language: SQL


SELECT m.mime_type, CASE WHEN f.file_class = 3 THEN ref_key ||'-'|| f.file_name
                         ELSE f.file_name END,
       f.description, f.uploaded_by, e.name,
       f.uploaded_at, f.id, f.ref_key, f.file_class,  f.content
  FROM mime_type m
  JOIN file_base f ON f.mime_type_id = m.id
  JOIN entity e ON f.uploaded_by = e.id
 WHERE f.ref_key = $1 and f.file_class = $2
       AND m.invoice_include
       OR f.id IN (SELECT max(fb.id)
                   FROM file_base fb
                   JOIN mime_type m ON fb.mime_type_id = m.id
                        AND m.mime_type ilike 'image%'
                   JOIN invoice i ON i.trans_id = $1
                        AND i.parts_id = fb.ref_key
                  WHERE fb.file_class = 3
               GROUP BY ref_key)

Function: file__get_mime_type(in_mime_type_id integer, in_mime_type_text text)

Returns: mime_type

Language: PLPGSQL

Retrieves mime type reference data or creates it. Note that the reference data isn''t created when in_mime_type_id is not null or that in_mime_type_text is null.

DECLARE
   r mime_type;
BEGIN
  select * into r from mime_type
   where ($1 IS NULL OR id = $1) AND ($2 IS NULL OR mime_type = $2);

  if not found and in_mime_type_id is null and in_mime_type_text is not null then
    insert into mime_type (mime_type_text) values (in_mime_type_text)
    returning * into r;
  end if;

  return r;
END;

Function: file__list_by(in_ref_key integer, in_file_class integer)

Returns: SET OF file_list_item

Language: SQL

Returns a list of files attached to a database object. No content is retrieved.


SELECT m.mime_type, f.file_name, f.description, f.uploaded_by, e.name,
       f.uploaded_at, f.id, f.ref_key, f.file_class,
       case when m.mime_type = 'text/x-uri' THEN f.content ELSE NULL END
  FROM mime_type m
  JOIN file_base f ON f.mime_type_id = m.id
  JOIN entity e ON f.uploaded_by = e.id
 WHERE f.ref_key = $1 and f.file_class = $2;


Function: file__list_links(in_ref_key integer, in_file_class integer)

Returns: SET OF file_links

Language: SQL

This function retrieves a list of file attachments on a specified object.

 select * from file_links where ref_key = $1 and dest_class = $2;

Function: file__save_incoming(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text)

Returns: file_base

Language: SQL

If the file_name is not unique, a unique constraint violation will be thrown.

INSERT INTO file_incoming(content, mime_type_id, file_name, description,
                          ref_key, file_class, uploaded_by)
SELECT $1, $2, $3, $4, 0, 7, entity_id
  FROM users where username = SESSION_USER
 RETURNING *;

Function: file__save_internal(in_content bytea, in_mime_type_id integer, in_file_name text, in_description text)

Returns: file_base

Language: SQL

If the file_name is not unique, this will overwrite the previous stored file.

WITH up AS (
    UPDATE file_internal
       SET content = $1, uploaded_at = now(),
           uploaded_by = (select entity_id from users
                           where username = session_user)
     WHERE file_name = $3
 RETURNING true as found_it
)
INSERT INTO file_internal (content, mime_type_id, file_name, description,
                          ref_key, file_class, uploaded_by)
SELECT $1, $2, $3, $4, 0, 6, entity_id
  FROM users
 where username = SESSION_USER
       AND NOT EXISTS (select 1 from up)
RETURNING *;

Function: file_links_vrebuild()

Returns: boolean

Language: PLPGSQL

DECLARE
   viewline file_view_catalog%rowtype;
   stmt text;
BEGIN
   stmt := '';
   FOR viewline IN
       select * from file_view_catalog
   LOOP
       IF stmt = '' THEN
           stmt := 'SELECT * FROM ' || quote_ident(viewline.view_name) || '
';
       ELSE
           stmt := stmt || ' UNION
SELECT * FROM '|| quote_ident(viewline.view_name) || '
';
       END IF;
   END LOOP;
   EXECUTE 'CREATE OR REPLACE VIEW file_links AS
' || stmt;
   RETURN TRUE;
END;

Function: form_check(in_session_id integer, in_form_id integer)

Returns: boolean

Language: SQL

This checks to see if an open form (record in open_forms) exists with the form_id and session_id provided. Returns true if exists, false if not.

SELECT count(*) = 1
  FROM open_forms f
  JOIN "session" s USING (session_id)
  JOIN users u ON (s.users_id = u.id)
 WHERE f.session_id = $1 and f.id = $2 and u.username = SESSION_USER;

Function: form_close(in_session_id integer, in_form_id integer)

Returns: boolean

Language: PLPGSQL

Closes out the form by deleting it from the open_forms table. Returns true if found, false if not.

DECLARE form_test bool;
BEGIN
        form_test := form_check(in_session_id, in_form_id);

        IF form_test IS TRUE THEN
                DELETE FROM open_forms
                WHERE session_id = in_session_id AND id = in_form_id;

                RETURN TRUE;

        ELSE
            RETURN FALSE;
        END IF;
END;

Function: form_open(in_session_id integer)

Returns: integer

Language: PLPGSQL

This opens a form, and returns the id of the form opened.

DECLARE usertest bool;
    form_id int;
BEGIN
        SELECT count(*) = 1 INTO usertest FROM session
         WHERE session_id = in_session_id
               AND users_id IN (select id from users
                                WHERE username = SESSION_USER);

        IF usertest is not true THEN
            RAISE EXCEPTION 'Invalid session';
        END IF;

        INSERT INTO open_forms (session_id,last_used)
                        VALUES (in_session_id,now())
        RETURNING id INTO form_id;

        RETURN form_id;
END;

Function: get_default_lang()

Returns: text

Language: SQL

 SELECT coalesce((select description FROM language
    WHERE code = (SELECT substring(value, 1, 2) FROM defaults
                   WHERE setting_key = 'default_language')), 'english');

Function: get_fractional_month(in_date_first date, in_date_second date)

Returns: numeric

Language: SQL

Returns the number of months between two dates in numeric form.

SELECT CASE WHEN is_same_month($1, $2)
            THEN ($2 - $1)::numeric
                 / days_in_month($1)
            ELSE (get_fractional_month(
                   $1, (date_trunc('MONTH', $1)
                       + '1 month - 1 second'::interval)::date)
                 + get_fractional_month(date_trunc('MONTH', $2)::date, $2)
                 + (extract ('YEAR' from $2) - extract ('YEAR' from $1) * 12)
                 + extract ('MONTH' from $1) - extract ('MONTH' from $2)
                 - 1)::numeric
            END;

Function: get_fractional_year(in_date_from date, in_date_to date)

Returns: numeric

Language: SQL

Returns the decimal representation of the fractional year.

   select ($2 - $1
            - leap_days(next_leap_year_calc($1, false),
                       next_leap_year_calc($2, true)))
            /365::numeric;

Function: get_link_descriptions(in_summary boolean, in_custom boolean)

Returns: SET OF account_link_description

Language: SQL

Gets the set of possible account_link descriptions, optionally filtered by their `custom` or `summary` attributes.

    SELECT * FROM account_link_description
    WHERE (in_custom IS NULL OR custom = in_custom)
    AND (in_summary IS NULL OR summary = in_summary);

Function: gifi__list()

Returns: SET OF gifi

Language: SQL

SELECT * FROM gifi ORDER BY accno;

Function: gl_audit_trail_append()

Returns: trigger

Language: PLPGSQL

This provides centralized support for insertions into audittrail.

DECLARE
   t_reference text;
   t_row RECORD;
BEGIN

IF TG_OP = 'INSERT' then
   t_row := NEW;
ELSE
   t_row := OLD;
END IF;

IF TG_TABLE_NAME IN ('ar', 'ap') THEN
    t_reference := t_row.invnumber;
ELSE
    t_reference := t_row.reference;
END IF;

INSERT INTO audittrail (trans_id,tablename,reference, action, person_id)
values (t_row.id,TG_TABLE_NAME,t_reference, TG_OP, person__get_my_entity_id());

return null; -- AFTER TRIGGER ONLY, SAFE
END;

Function: goods__history(in_date_from date, in_date_to date, in_partnumber text, in_description text, in_serialnumber text, in_inc_po boolean, in_inc_so boolean, in_inc_quo boolean, in_inc_rfq boolean, in_inc_is boolean, in_inc_ir boolean)

Returns: SET OF parts_history_result

Language: SQL

  SELECT p.id, p.partnumber, o.transdate, p.description, p.bin,
         o.id as ord_id, o.ordnumber, o.oe_class, eca.meta_number::text, e.name,
         i.sellprice, i.qty, i.discount, i.serialnumber
    FROM parts p
    JOIN (select id, trans_id, parts_id, sellprice, qty, discount, serialnumber,
                 'o' as i_type
            FROM orderitems
           UNION
          SELECT id, trans_id, parts_id, sellprice, qty, discount, serialnumber,
                 'i' as i_type
            FROM invoice) i ON p.id = i.parts_id
    JOIN (select o.id, 'oe' as o_table, ordnumber as ordnumber, c.oe_class,
                 o.oe_class_id, o.transdate, o.entity_credit_account, 'o' as expected_line
            FROM oe o
            JOIN oe_class c ON o.oe_class_id = c.id
           UNION
          SELECT id, 'ar' as o_table, invnumber as ordnumber, 'is' as oe_class,
                 null, transdate, entity_credit_account, 'i' as expected_line
            FROM ar
           UNION
          SELECT id, 'ap' as o_table, invnumber as ordnumber, 'ir' as oe_class,
                 null, transdate, entity_credit_account, 'i' as expected_line
            FROM ap) o ON o.id = i.trans_id
                          AND o.expected_line = i.i_type
    JOIN entity_credit_account eca ON o.entity_credit_account = eca.id
    JOIN entity e ON e.id = eca.entity_id
   WHERE (in_partnumber is null or p.partnumber like in_partnumber || '%')
         AND (in_description IS NULL
              OR p.description @@ plainto_tsquery(in_description))
         AND (in_date_from is null or in_date_from <= o.transdate)
         and (in_date_to is null or in_date_to >= o.transdate)
         AND (in_serialnumber is null or i.serialnumber = in_serialnumber)
         AND ((in_inc_po IS NULL AND in_inc_so IS NULL
                AND in_inc_quo IS NULL AND in_inc_rfq IS NULL
                AND in_inc_ir IS NULL AND in_inc_is IS NULL)
              OR (
                 (in_inc_po is true and o.oe_class = 'Purchase Order')
                 OR (in_inc_so is true and o.oe_class = 'Sales Order')
                 OR (in_inc_quo is true and o.oe_class = 'Quotation')
                 OR (in_inc_rfq is true and o.oe_class = 'RFQ')
                 OR (in_inc_ir is true and o.oe_class = 'ir')
                 OR (in_inc_is is true and o.oe_class = 'is')
             ))
ORDER BY o.transdate desc, o.id desc;

Function: goods__search(in_parttype text, in_partnumber text, in_description text, in_partsgroup_id integer, in_serialnumber text, in_make text, in_model text, in_drawing text, in_microfiche text, in_status text, in_date_from date, in_date_to date)

Returns: SET OF goods_search_result

Language: SQL

       SELECT p.partnumber,
              p.id, p.description, p.onhand, p.unit::text, p.priceupdate,
              pg.partsgroup,
              p.listprice, p.sellprice, p.lastcost, p.avgcost,
              CASE WHEN p.lastcost = 0 THEN NULL
                   ELSE ((p.sellprice / p.lastcost) - 1) * 100
              END as markup,
              p.bin, p.rop, p.weight, p.notes, p.image, p.drawing, p.microfiche,
              m.make, m.model
         FROM parts p
    LEFT JOIN makemodel m ON m.parts_id = p.id
    LEFT JOIN partsgroup pg ON p.partsgroup_id = pg.id
        WHERE (in_partnumber is null or p.partnumber ilike in_partnumber || '%')
              AND (in_description is null
                  or p.description @@ plainto_tsquery(in_description))
              AND (in_partsgroup_id is null
                  or p.partsgroup_id = in_partsgroup_id )
              AND (in_make is null or m.make ilike in_make || '%')
              AND (in_model is null or m.model  ilike in_model || '%')
              AND (in_drawing IS NULL OR p.drawing ilike in_drawing || '%')
              AND (in_microfiche IS NULL
                  OR p.microfiche ilike in_microfiche || '%')
              AND (in_serialnumber IS NULL OR p.id IN
                      (select parts_id from invoice
                        where serialnumber = in_serialnumber))
              AND (in_parttype IS NULL
                   OR in_parttype = 'all'
                   OR (in_parttype = 'assemblies' and p.assembly)
                   OR (in_parttype = 'services'
                       and p.inventory_accno_id IS NULL)
                   OR (in_parttype = 'overhead'
                       and p.inventory_accno_id IS NOT NULL
                       and p.income_accno_id IS NULL)
                   OR (in_parttype = 'parts'
                       and p.inventory_accno_id IS NOT NULL
                       and p.expense_accno_id IS NOT NULL
                       and p.income_accno_id IS NOT NULL))
              AND ((in_status = 'active' and not p.obsolete)
                   OR (in_status = 'onhand' and p.onhand > 0)
                   OR (in_status = 'obsolete' and p.obsolete)
                   OR (in_status = 'short' and p.onhand <= p.rop)
                   OR (in_status = 'unused'
                      AND NOT EXISTS (select 1 FROM invoice
                                       WHERE parts_id = p.id
                                       UNION
                                      SELECT 1 FROM orderitems
                                       WHERE parts_id = p.id)));

Function: in_tree(in_node_id integer, in_search_array public.tree_record[])

Returns: boolean

Language: SQL

SELECT CASE WHEN count(*) > 0 THEN true ELSE false END
  FROM unnest($2) r
 WHERE t @> array[$1];

Function: in_tree(in_node_id integer[], in_search_array public.tree_record[])

Returns: boolean

Language: SQL

SELECT bool_and(in_tree(e, $2))
  FROM unnest($1) e;

Function: inventory__activity(in_from_date date, in_to_date date, in_partnumber text, in_description text)

Returns: SET OF inv_activity_line

Language: SQL

    SELECT p.id, p.description, p.partnumber,
           SUM(CASE WHEN transtype = 'ar' THEN i.qty ELSE 0 END) AS sold,
           SUM(CASE WHEN transtype = 'ar' THEN i.sellprice * i.qty ELSE 0 END)
           AS revenue,
           SUM(CASE WHEN transtype = 'ap' THEN i.qty * -1 ELSE 0 END)
           AS purchased,
           SUM(CASE WHEN transtype = 'ap' THEN -1 * i.sellprice * i.qty ELSE 0
                END) AS cost,
           SUM(CASE WHEN transtype = 'as' AND i.qty > 0 THEN i.qty ELSE 0 END)
           AS used,
           SUM(CASE WHEN transtype = 'as' AND i.qty < 0 then -1*i.qty ELSE 0 END)
           AS assembled,
           SUM(CASE WHEN transtype = 'ia' THEN -1 * i.qty ELSE 0 END)
           AS adjusted
      FROM invoice i
      JOIN parts p ON (i.parts_id = p.id)
      JOIN (select id, approved, transdate, 'ar' as transtype FROM ar
             UNION
            SELECT id, approved, transdate, 'ap' as transtype FROM ap
             UNION
            SELECT id, approved, transdate, trans_type_code as transtype
              FROM gl) a
            ON (a.id = i.trans_id AND a.approved)
     WHERE ($1 IS NULL OR a.transdate >= $1)
           AND ($2 IS NULL OR a.transdate <= $2)
           AND ($3 IS NULL OR p.partnumber ilike $3 || '%')
           AND ($4 IS NULL OR p.description @@ plainto_tsquery($4))
  GROUP BY p.id, p.description, p.partnumber

Function: inventory__get_item_by_id(in_id integer)

Returns: parts

Language: SQL

SELECT * FROM parts WHERE id = $1;

Function: inventory__get_item_by_partnumber(in_partnumber text)

Returns: parts

Language: SQL

SELECT * FROM parts WHERE obsolete is not true AND partnumber = $1;

Function: inventory__search_part(in_parts_id integer, in_partnumber text, in_counted_date date)

Returns: part_at_date

Language: SQL

WITH RECURSIVE assembly_comp (a_id, parts_id, qty) AS (
     SELECT id, parts_id, qty FROM assembly
      UNION ALL
     SELECT ac.a_id, a.parts_id, ac.qty * a.qty
       FROM assembly a JOIN assembly_comp ac ON a.parts_id = ac.parts_id
),
invoice_sum AS (
SELECT a.transdate, sum(i.qty) as qty, i.parts_id
  FROM invoice i
  JOIN (select id, transdate from transactions
         where approved) a ON i.trans_id = a.id
 GROUP BY a.transdate, i.parts_id
),
order_sum AS (
SELECT oe.transdate,
       sum(oi.ship * case when oe_class_id = 1 THEN 1 ELSE -1 END) as qty,
       oi.parts_id
  FROM orderitems oi
  JOIN oe ON oe.closed is false and oe_class_id in (1, 2)
 GROUP BY oe.transdate, oi.parts_id
)
     SELECT p.id, p.partnumber,
            coalesce(sum((coalesce(i.qty, 0) + coalesce(oi.qty, 0)) * a.qty ),0)
       FROM parts p
  LEFT JOIN assembly_comp a ON a.a_id = p.id
  LEFT JOIN invoice_sum i ON i.parts_id = p.id OR a.parts_id = i.parts_id
  LEFT JOIN order_sum oi ON oi.parts_id = p.id OR a.parts_id = i.parts_id
      WHERE p.id = $1 OR p.partnumber = $2
            OR (p.id IN (select parts_id FROM makemodel WHERE barcode = $2)
               AND NOT EXISTS (select id from parts
                                where partnumber = $2 AND NOT obsolete
            ))
            and (i.transdate is null or i.transdate <= $3)
            AND (oi.transdate IS NULL OR oi.transdate <= $3)
   GROUP BY p.id, p.partnumber;

Function: inventory_adjust__approve(in_id integer)

Returns: inventory_report

Language: PLPGSQL

DECLARE inv inventory_report;
        t_trans_id int;
BEGIN

SELECT * INTO inv FROM inventory_report where id = in_id;

IF inv.trans_id IS NOT NULL THEN
   -- already approved
   RETURN inv;
END IF;

INSERT INTO gl (description, transdate, reference, approved, trans_type_code)
        VALUES ('Transaction due to approval of inventory adjustment',
                inv.transdate, 'invadj-' || in_id, true, 'ia')
    RETURNING id INTO t_trans_id;

UPDATE inventory_report
   set trans_id = t_trans_id
 WHERE id = in_id;

-- When the count is lower than expected, we need to write-off
-- some of our inventory. To do so, we post COGS to the expense
-- account and reduce the inventory by the amount. There is no
-- income associated with the transaction, so 'discount' == 100%
--
-- When the count is higher than expected, we need to increase
-- some of our inventory. To do so, we stock at reverse COGS, taking
-- the cost of the inventory increase out of COGS (to re-add on sale)
INSERT INTO invoice (trans_id, parts_id, description, qty, allocated,
                     sellprice, precision, discount)
SELECT t_trans_id, p.id, p.description, l.variance * -1, 0,
       0, 3, 1
  FROM parts p
  JOIN inventory_report_line l ON p.id = l.parts_id
 WHERE l.adjust_id = in_id;

-- cogs for AR manipulates the tip of the FIFO buffer
--  record shortage as a regular FIFO allocation, without income aspects
--  record overage as a regular FIFO reversal, similarly without income aspects
PERFORM cogs__add_for_ar_line(id)
   FROM invoice
  WHERE trans_id = t_trans_id;


UPDATE parts p
   SET onhand = onhand + (select variance
                            from inventory_report_line l
                           where p.id = l.parts_id
                             and l.adjust_id = in_id)
 WHERE id IN (select parts_id
                from inventory_report_line
               where adjust_id = in_id);


SELECT * INTO inv FROM inventory_report where id = in_id;

RETURN inv;

END;

Function: inventory_adjust__create(in_transdate date)

Returns: inventory_report

Language: SQL

        INSERT INTO inventory_report(transdate) values (in_transdate)
        RETURNING *;

Function: inventory_adjust__delete(in_id integer)

Returns: boolean

Language: PLPGSQL

DECLARE inv inventory_report;
BEGIN
SELECT * INTO inv FROM inventory_report where id = in_id;
IF NOT FOUND THEN
   RETURN FALSE;
ELSIF inv.trans_id IS NOT NULL THEN
   RAISE EXCEPTION 'Set is Already Approved!';
END IF;

DELETE FROM inventory_report_line where adjust_id = in_id;
DELETE FROM inventory_report where id = in_id;

RETURN TRUE;

END;

Function: inventory_adjust__get(in_id integer)

Returns: inventory_report

Language: SQL

SELECT * FROM inventory_report WHERE id = $1;

Function: inventory_adjust__get_lines(in_id integer)

Returns: SET OF inventory_report_line

Language: SQL

SELECT * FROM inventory_report_line l WHERE adjust_id = $1
 ORDER BY parts_id;

Function: inventory_adjust__list(in_from_date date, in_to_date date, in_approved boolean)

Returns: SET OF inventory_report

Language: SQL


SELECT * FROM inventory_report
 WHERE ($1 is null or transdate >= $1)
       AND ($2 IS NULL OR transdate <= $2)
       AND ($3 IS NULL OR $3 = (trans_id IS NOT NULL));


Function: inventory_adjust__save_info(in_transdate date, in_source text)

Returns: inventory_report

Language: SQL

INSERT INTO inventory_report(transdate, source)
VALUES ($1, $2)
RETURNING *;

Function: inventory_adjust__save_line(in_adjust_id integer, in_parts_id integer, in_counted numeric, in_expected numeric, in_variance numeric)

Returns: inventory_report_line

Language: SQL

INSERT INTO inventory_report_line
       (adjust_id, parts_id, counted, expected, variance)
VALUES ($1, $2, $3, $4, $5)
RETURNING *;

Function: inventory_adjust__search(in_from_date date, in_to_date date, in_partnumber text, in_source text)

Returns: SET OF inventory_report

Language: SQL


   SELECT r.id, r.transdate, r.source, r.trans_id
     FROM inventory_report r
  LEFT JOIN inventory_report_line l ON l.adjust_id = r.id
     JOIN parts p ON l.parts_id = p.id
    WHERE ($1 is null or $1 <= r.transdate) AND
          ($2 is null OR $2 >= r.transdate) AND
          ($3 IS NULL OR plainto_tsquery($3) @@ tsvector(p.partnumber)) AND
          ($4 IS NULL OR source LIKE $4 || '%')
 GROUP BY r.id, r.transdate, r.source, r.trans_id;

Function: inventory_get_item_at_day(in_transdate date, in_partnumber text)

Returns: parts

Language: PLPGSQL

DECLARE out_row RECORD;
        t_parts_id int;
        int_outrow RECORD;
BEGIN
        SELECT id INTO t_parts_id
        FROM parts
        WHERE (partnumber like in_partnumber|| ' %'
                or partnumber = in_partnumber)
                and obsolete is not true
                and assembly is not true;

        SELECT * INTO out_row FROM parts WHERE id = t_parts_id;

        WITH RECURSIVE c AS (
             SELECT 1::numeric as multiplier, t_parts_id  as part_used,
                    t_parts_id as current_part_id
             UNION ALL
             SELECT c.multiplier * a.qty, t_parts_id as part_used,
                    a.parts_id as current_part_id
               FROM assembly a
               JOIN c ON c.current_part_id = a.id
        )
        SELECT  sum(coalesce(c.multiplier, 1) * i.qty) * -1
                AS onhand
        INTO int_outrow
        FROM parts p
        LEFT JOIN c ON c.part_used = t_parts_id
        JOIN invoice i ON (i.parts_id = p.id OR i.parts_id = c.current_part_id)
        JOIN (select id, transdate from transactions) a
             ON (i.trans_id = a.id)

        WHERE (p.partnumber = in_partnumber
                or p.partnumber like in_partnumber || ' %')
                AND a.transdate <= in_transdate
                AND assembly IS FALSE AND obsolete IS NOT TRUE
        GROUP BY p.id, p.partnumber, p.description, p.unit, p.listprice,
                p.sellprice, p.lastcost, p.priceupdate, p.weight,
                p.onhand, p.notes, p.makemodel, p.assembly, p.alternate,
                p.rop, p.inventory_accno_id, p.income_accno_id, p.expense_accno_id,
                p.bin, p.obsolete, p.bom, p.image, p.microfiche, p.partsgroup_id,
                p.avgcost;

        out_row.onhand := int_outrow.onhand;
        RETURN out_row;
END;

Function: invoice__get_by_vendor_number(in_meta_number text, in_invoice_number text)

Returns: ap

Language: SQL

        SELECT * FROM ap WHERE entity_credit_account =
                (select id from entity_credit_account where entity_class = 1
                AND meta_number = in_meta_number)
                AND invnumber = in_invoice_number;

Function: is_leapyear(in_date date)

Returns: boolean

Language: SQL

Returns true if date is in a leapyear. False if not. Uses the built-in PostgreSQL date handling, and no direct detection is done in our code.

    select extract('day' FROM (
                           (extract('year' FROM $1)::text
                           || '-02-28')::date + '1 day'::interval)::date)
           = 29;

Function: is_same_month(in_date1 date, in_date2 date)

Returns: boolean

Language: SQL

Returns true if the two dates are in the same month and year. False otherwise.

SELECT is_same_year($1, $2)
       and extract ('MONTH' from $1) = extract ('MONTH' from $2);

Function: is_same_year(in_date1 date, in_date2 date)

Returns: boolean

Language: SQL

Returns true if the two dates are in the same year, false otherwise.

SELECT  extract ('YEAR' from $1) = extract ('YEAR' from $2);

Function: journal__add(in_reference text, in_description text, in_journal integer, in_post_date date, in_approved boolean, in_is_template boolean, in_currency text)

Returns: journal_entry

Language: SQL

        INSERT INTO journal_entry (reference, description, journal, post_date,
                        approved, is_template, effective_start, effective_end,
                        currency, entered_by)
        VALUES (coalesce($1, ''), $2, $3, $4,
                        coalesce($5 , false),
                        coalesce($6, false),
               $4, $4, $7, person__get_my_entity_id()) RETURNING *;

Function: journal__add_line(in_account_id integer, in_journal_id integer, in_amount numeric, in_amount_fx numeric, in_curr text, in_cleared boolean, in_memo text, in_business_units integer[])

Returns: journal_line

Language: SQL

        INSERT INTO journal_line(account_id, journal_id, amount,
          amount_tc, curr, cleared)
        VALUES (in_account_id, in_journal_id, in_amount, in_amount_fx,
           in_curr, coalesce(in_cleared, false));

        INSERT INTO business_unit_jl(entry_id, bu_class, bu_id)
        SELECT currval('journal_line_id_seq'), class_id, id
          FROM business_unit
         WHERE id = any(in_business_units);

        SELECT * FROM journal_line where id = currval('journal_line_id_seq');

Function: journal__delete(in_journal_id integer)

Returns: void

Language: SQL

  DELETE FROM eca_invoice WHERE journal_id = in_journal_id;
  DELETE FROM journal_line WHERE journal_id = in_journal_id;
  DELETE FROM journal_entry WHERE id = in_journal_id;

Function: journal__get_entry(in_id integer)

Returns: journal_entry

Language: SQL

SELECT * FROM journal_entry where id = $1;

Function: journal__get_invoice(in_id integer)

Returns: eca_invoice

Language: SQL

SELECT * FROM eca_invoice where journal_id = $1;

Function: journal__lines(in_id integer)

Returns: SET OF journal_line

Language: SQL

select * from journal_line where journal_id = $1;

Function: journal__make_invoice(in_order_id integer, in_journal_id integer, in_on_hold boolean, in_reverse boolean, in_credit_id integer, in_language_code character varying)

Returns: eca_invoice

Language: PLPGSQL

DECLARE retval eca_invoice;
BEGIN
        INSERT INTO eca_invoice (order_id, journal_id, on_hold, reverse,
                credit_id, language_code, due)
        VALUES (in_order_id, in_journal_id, coalesce(in_on_hold, false),
                in_reverse, in_credit_id, in_language_code, 'today');

        SELECT * INTO retval FROM eca_invoice WHERE journal_id = in_journal_id;

        RETURN retval;
END;

Function: journal__save_recurring_print(in_id integer, in_formname text, in_printer text)

Returns: recurringprint

Language: SQL

insert into recurringprint (id, formname, format, printer)
values ($1, $2, 'PDF', $3)
returning *;

Function: journal__search(in_reference text, in_description text, in_entry_type integer, in_transaction_date date, in_approved boolean, in_is_template boolean, in_meta_number text, in_entity_class integer, in_recurring boolean)

Returns: SET OF journal_search_result

Language: SQL

    SELECT  j.id, j.reference, j.description, j.journal,
            j.post_date, j.approved,
            j.is_template, eca.meta_number,
            e.name, ec.class,
            coalesce(
              r.startdate + 0, -- r.recurring_interval,
              j.post_date )
    FROM journal_entry j
    LEFT JOIN eca_invoice i ON (i.journal_id = j.id)
    LEFT JOIN entity_credit_account eca ON (eca.id = credit_id)
    LEFT JOIN entity e ON (eca.entity_id = e.id)
    LEFT JOIN entity_class ec ON (eca.entity_class = ec.id)
    LEFT JOIN recurring r ON j.id = r.id
    WHERE (in_reference IS NULL OR in_reference = j.reference) AND
            (in_description IS NULL
                    or in_description = j.description) AND
            (in_entry_type is null or in_entry_type = j.journal)
            and (in_transaction_date is null
                    or in_transaction_date = j.post_date) and
            j.approved = coalesce(in_approved, true) and
            j.is_template = coalesce(in_is_template, false) and
            (in_meta_number is null
                    or eca.meta_number = in_meta_number) and
            (in_entity_class is null
                    or eca.entity_class = in_entity_class) AND
            (in_recurring IS NOT TRUE OR
                    coalesce(r.startdate, r.nextdate) <= now()::date
            );

Function: journal__validate_entry(in_id integer)

Returns: boolean

Language: SQL

        SELECT sum(amount) = 0 FROM journal_line WHERE journal_id = $1;

Function: lastcost(integer)

Returns: double precision

Language: PLPGSQL


DECLARE

v_cost float;
v_parts_id alias for $1;

BEGIN

  SELECT INTO v_cost sellprice FROM invoice i
  JOIN ap a ON (a.id = i.trans_id)
  WHERE i.parts_id = v_parts_id
  ORDER BY a.transdate desc, a.id desc
  LIMIT 1;

  IF v_cost IS NULL THEN
    v_cost := 0;
  END IF;

RETURN v_cost;
END;

Function: leap_days(in_year_from integer, in_year_to integer)

Returns: integer

Language: SQL

Returns the number of leap years between the two year inputs, inclusive.

   SELECT count(*)::int
   FROM generate_series($1, $2)
   WHERE is_leapyear((generate_series::text || '-01-01')::date);

Function: limit_summary_account_links()

Returns: trigger

Language: PLPGSQL

Called as a constraint trigger function on the account_link table. Raises an exception if the operation would create more than one Summary account_link descriptors for the relevant account.

BEGIN
    IF (
        SELECT COUNT(*) > 1
        FROM account_link
        JOIN account_link_description ON (
            account_link.description = account_link_description.description
        )
        WHERE account_id = NEW.account_id
        AND account_link_description.summary IS TRUE
    )
    THEN
        RAISE EXCEPTION 'Account cannot have more than one summary account_link descriptor';
    END IF;

    RETURN NEW;
END;

Function: list_taxforms(in_entity_id integer)

Returns: SET OF country_tax_form

Language: PLPGSQL

Returns a list of tax forms for the entity's country.

DECLARE t_country_tax_form country_tax_form;
BEGIN

        FOR t_country_tax_form IN

                      SELECT *
                            FROM country_tax_form where country_id in(SELECT country_id from entity where id=in_entity_id)
        LOOP

        RETURN NEXT t_country_tax_form;

        END LOOP;

END;

Function: location__get(in_id integer)

Returns: location

Language: SQL

Returns the location specified by in_id.

        SELECT * FROM location WHERE id = in_id;

Function: location_delete(in_id integer)

Returns: void

Language: SQL

DELETES the location specified by in_id. Does not return a value.

        DELETE FROM location WHERE id = in_id;

Function: location_list_class()

Returns: SET OF location_class_item

Language: SQL

Lists location classes, by default in order entered.

                SELECT l.*, array_agg(e.entity_class)
                  FROM location_class l
                  JOIN location_class_to_entity_class e
                       ON (l.id = e.location_class)
              GROUP BY l.id, l.class, l.authoritative
              ORDER BY l.id;

Function: location_list_country()

Returns: SET OF country

Language: SQL

Lists countries, by default in alphabetical order.

                SELECT * FROM country ORDER BY name;

Function: location_save(in_location_id integer, in_address1 text, in_address2 text, in_address3 text, in_city text, in_state text, in_zipcode text, in_country integer)

Returns: integer

Language: PLPGSQL

Note that this does NOT override the data in the database unless in_location_id is specified. Instead we search for locations matching the desired specifications and if none are found, we insert one. Either way, the return value of the location can be used for mapping to other things. This is necessary because locations are only loosly coupled with entities, etc.

DECLARE
        location_id integer;
        location_row RECORD;
BEGIN

        IF in_location_id IS NULL THEN
            SELECT id INTO location_id FROM location
            WHERE line_one = in_address1 AND line_two = in_address2
                  AND line_three = in_address3 AND in_city = city
                  AND in_state = state AND in_zipcode = mail_code
                  AND in_country = country_id
            LIMIT 1;

            IF NOT FOUND THEN
            -- Straight insert.
            location_id = nextval('location_id_seq');
            INSERT INTO location (
                id,
                line_one,
                line_two,
                line_three,
                city,
                state,
                mail_code,
                country_id)
            VALUES (
                location_id,
                in_address1,
                in_address2,
                in_address3,
                in_city,
                in_state,
                in_zipcode,
                in_country
                );
            END IF;
            return location_id;
        ELSE
            RAISE NOTICE 'Overwriting location id %', in_location_id;
            -- Test it.
            SELECT * INTO location_row FROM location WHERE id = in_location_id;
            IF NOT FOUND THEN
                -- Tricky users are lying to us.
                RAISE EXCEPTION 'location_save called with nonexistant location ID %', in_location_id;
            ELSE
                -- Okay, we're good.

                UPDATE location SET
                    line_one = in_address1,
                    line_two = in_address2,
                    line_three = in_address3,
                    city = in_city,
                    state = in_state,
                    mail_code = in_zipcode,
                    country_id = in_country
                WHERE id = in_location_id;
                return in_location_id;
            END IF;
        END IF;
END;

Function: lock_record(in_id integer, in_session_id integer)

Returns: boolean

Language: PLPGSQL

This function seeks to lock a record with an id of in_id to a session with an id of in_session_id. If possible, it returns true. If it is already locked, false. These are not hard locks and the application is free to disregard or not even ask. They time out when the session is destroyed.

declare
   locked int;
begin
   SELECT locked_by into locked from transactions where id = $1;
   IF NOT FOUND THEN
        RETURN FALSE;
   ELSEIF locked is not null AND locked <> $2 THEN
        RETURN FALSE;
   END IF;
   UPDATE transactions set locked_by = $2 where id = $1;
   RETURN TRUE;
end;

Function: lsmb__backup_roles()

Returns: boolean

Language: PLPGSQL

This function creates two tables, dropping them if they exist previously: * lsmb_role_grants * lsmb_password_backups These contain sensitive security information and should only be used when creating customer-ready backups from shared hosting environments.

BEGIN

PERFORM lsmb__clear_role_backup();

CREATE TABLE lsmb_role_grants AS
SELECT u.id, rm.rolname
  FROM users u
  JOIN pg_authid r ON r.rolname = u.username
  JOIN pg_auth_members m ON m.member = r.oid
  JOIN pg_authid rm ON rm.oid = m.roleid;

CREATE TABLE lsmb_password_backups AS
SELECT u.id, rolpassword, rolvaliduntil
  FROM users u
  JOIN pg_authid r ON r.rolname = u.username;

RETURN FOUND;

END;

Function: lsmb__clear_role_backup()

Returns: boolean

Language: PLPGSQL

This functon drops the backup tables. It is also called on the successful completion of lsmb__restore_roles().

BEGIN

DROP TABLE IF EXISTS lsmb_role_grants CASCADE;
DROP TABLE IF EXISTS lsmb_password_backups CASCADE;

RETURN TRUE;

END;


Function: lsmb__decompose_timestamp(in_timestamp timestamp with time zone)

Returns: lsmb_date_fields

Language: SQL

SELECT extract('century' from $1) as century,
       extract('decade' from $1) as decade,
       extract('year' from $1) as year,
       extract('month' from $1) as month,
       extract('day' from $1) as day,
       extract('hour' from $1) as hour,
       extract('minute' from $1) as minute,
       extract('second' from $1) as second,
       extract('quarter' from $1) as quarter,
       extract('doy' from $1) as doy,
       extract('dow' from $1) as dow,
       extract('week' from $1) as week,
       extract('epoch' from $1) as epoch,
       $1::date as as_date,
       $1::time as as_time;

Function: lsmb__global_role(role text)

Returns: text

Language: SQL

Strips the role prefix from the role turning it into a global role identifier, or returns NULL if the string does not start with the role prefix.

select case when position(lsmb__role_prefix() in role) = 1
              then substring(role from length(lsmb__role_prefix())+1)
       else null
       end;

Function: lsmb__max_date()

Returns: date

Language: SQL

 SELECT max(transdate) FROM acc_trans; 

Function: lsmb__min_date()

Returns: date

Language: SQL

 SELECT min(transdate) from acc_trans; 

Function: lsmb__restore_roles()

Returns: boolean

Language: PLPGSQL

This file restores the roles from lsmb__backup_roles() and then cleares the role backup. If the role backup/restore did not work properly one can always restore the backup tables only from the backup again but this reduces security disclosure.

DECLARE temp_rec RECORD;

BEGIN

FOR temp_rec IN
    select u.username, l.*
      FROM users u
      JOIN lsmb_password_backups l ON u.id = l.id
LOOP
    PERFORM 1 FROM pg_authid WHERE rolname = temp_rec.username;

    IF FOUND THEN
        EXECUTE $e$ ALTER USER $e$ || quote_ident(temp_rec.username) ||
        $e$ WITH ENCRYPTED PASSWORD $e$ || quote_literal(temp_rec.rolpassword) ||
        $e$ VALID UNTIL $e$ || coalesce(quote_literal(temp_rec.rolvaliduntil),
                                         'NULL');
    ELSE
        EXECUTE $e$ CREATE USER $e$ || quote_ident(temp_rec.username) ||
        $e$ WITH ENCRYPTED PASSWORD $e$ || quote_literal(temp_rec.rolpassword) ||
        $e$ VALID UNTIL $e$ || coalesce(quote_literal(temp_rec.rolvaliduntil),
                                         'NULL');
    END IF;
END LOOP;

PERFORM admin__add_user_to_role(u.username, r.rolname)
   FROM users u
   JOIN lsmb_role_grants r ON u.id = r.id
   JOIN pg_authid a ON r.rolname = a.rolname;

RETURN lsmb__clear_role_backup();

END;

Function: lsmb__role(global_role text)

Returns: text

Language: SQL

Prepends the role prefix to a role name. E.g. 'contact_edit' is converted to 'lsmb_mycompany__contact_edit'

 select lsmb__role_prefix() || $1; 

Function: lsmb__role_prefix()

Returns: text

Language: SQL

Returns the prefix text to be used for roles. E.g. 'lsmb__mycompany_'

 select coalesce((setting_get('role_prefix')).value,
                   'lsmb_' || current_database() || '__'); 

Function: lsmb_module__get(in_id integer)

Returns: lsmb_module

Language: SQL

Retrieves a single module's info by id.

 SELECT * FROM lsmb_module where id = $1; 

Function: lsmb_module__list()

Returns: SET OF lsmb_module

Language: SQL

Returns a list of all defined modules, ordered by id.

 SELECT * FROM lsmb_module ORDER BY id 

Function: menu_generate()

Returns: SET OF menu_item

Language: SQL

This function returns the complete menu tree. It is used to generate nested menus for the web interface.

WITH RECURSIVE tree (path, id, parent, level, positions)
AS (
   select id::text as path, id, parent,
          0 as level, position::text
     from menu_node where parent is null
    UNION
   select path || ',' || n.id::text, n.id,
          n.parent, t.level + 1, t.positions || ',' || n.position
     from menu_node n
     JOIN tree t ON t.id = n.parent
)
SELECT n.position, n.id, c.level, n.label, c.path, n.parent,
       n.standalone, n.menu, n.url
  FROM tree c
  JOIN menu_node n USING(id)
 WHERE n.id IN (select node_id
                  FROM menu_acl acl
             LEFT JOIN pg_roles pr on pr.rolname = acl.role_name
                 WHERE CASE WHEN role_name ilike 'public' THEN true
                            WHEN rolname IS NULL THEN FALSE
                            ELSE pg_has_role(rolname, 'USAGE')
                       END
                 GROUP BY node_id
                   HAVING bool_and(CASE WHEN acl_type ilike 'DENY' THEN FALSE
                                        WHEN acl_type ilike 'ALLOW' THEN TRUE
                                   END))
       OR exists (select cn.id, cc.path
                    FROM tree cc
                    JOIN menu_node cn USING(id)
                   WHERE cn.id IN (select node_id
                                     FROM menu_acl acl
                                LEFT JOIN pg_roles pr
                                          on pr.rolname = acl.role_name
                                    WHERE CASE WHEN rolname ilike 'public'
                                                    THEN true
                                               WHEN rolname IS NULL
                                                    THEN FALSE
                                               ELSE pg_has_role(rolname, 'USAGE')
                                          END
                                     GROUP BY node_id
                                       HAVING bool_and(CASE WHEN acl_type
                                                                 ilike 'DENY'
                                                            THEN false
                                                            WHEN acl_type
                                                                 ilike 'ALLOW'
                                                            THEN TRUE
                                                      END))
                         and cc.path::text like c.path::text || ',%')
 ORDER BY string_to_array(c.positions, ',')::int[]

Function: menu_insert(in_parent_id integer, in_position integer, in_label text)

Returns: integer

Language: PLPGSQL

This function inserts menu items at arbitrary positions. The arguments are, in order: parent, position, label. The return value is the id number of the menu item created.

DECLARE
        new_id int;
BEGIN
        UPDATE menu_node
            -- prevent duplicates by setting negative as a first step
           SET position = -1 * (position + 1)
         WHERE parent = in_parent_id
               AND position >= in_position;

        UPDATE menu_node
            -- negate again now that all numbers are final
           SET position = -1 * position
         WHERE parent = in_parent_id
               AND position < 0;

        INSERT INTO menu_node (parent, position, label)
        VALUES (in_parent_id, in_position, in_label);

        RETURN currval('menu_node_id_seq');
END;

Function: mfg_lot__commit(in_id integer)

Returns: numeric

Language: PLPGSQL

DECLARE t_mfg_lot mfg_lot;
BEGIN
    SELECT * INTO t_mfg_lot FROM mfg_lot WHERE id = $1;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Lot not found';
    END IF;

    UPDATE parts SET onhand = onhand
                              - (select qty from mfg_lot_item
                                  WHERE parts_id = parts.id AND
                                        mfg_lot_id = $1)
     WHERE id in (select parts_id from mfg_lot_item
                   WHERE mfg_lot_id = $1);

    UPDATE parts SET onhand = onhand + t_mfg_lot.qty
     where id = t_mfg_lot.parts_id;

    INSERT INTO gl (reference, description, transdate, approved,
                   trans_type_code)
    values ('mfg-' || $1::TEXT, 'Manufacturing lot',
            now(), true, 'as');

    INSERT INTO invoice (trans_id, parts_id, qty, allocated)
    SELECT currval('id')::int, parts_id, qty, 0
      FROM mfg_lot_item WHERE mfg_lot_id = $1;

    PERFORM cogs__add_for_ar_line(id) FROM invoice
      WHERE trans_id = currval('id')::int;


    PERFORM * FROM invoice
      WHERE qty + allocated <> 0 AND trans_id = currval('id')::int;

    IF FOUND THEN
       RAISE EXCEPTION 'Not enough parts in stock';
    END IF;

    INSERT INTO invoice (trans_id, parts_id, qty, allocated, sellprice)
    SELECT currval('id')::int, t_mfg_lot.parts_id, t_mfg_lot.qty * -1, 0,
           sum(amount_bc) / t_mfg_lot.qty
      FROM acc_trans
     WHERE amount_bc < 0 and trans_id = currval('id')::int;

    PERFORM cogs__add_for_ap_line(currval('invoice_id_seq')::int);

    -- move from reverse COGS.
    INSERT INTO acc_trans(trans_id, chart_id, transdate,
                          amount_bc, curr, amount_tc)
    SELECT trans_id, chart_id, transdate, amount_bc * -1, curr, amount_tc * -1
      FROM acc_trans
     WHERE amount_bc < 0 and trans_id = currval('id')::int;

    -- difference goes into inventory
    INSERT INTO acc_trans(trans_id, transdate, amount_bc, curr, amount_tc,
                          chart_id)
    SELECT trans_id, now(), sum(amount_bc) * -1, curr, sum(amount_tc) * -1,
           (select inventory_accno_id from parts where id = t_mfg_lot.parts_id)
      FROM acc_trans
     WHERE trans_id = currval('id')::int
  GROUP BY trans_id, curr;


    RETURN t_mfg_lot.qty;
END;

Function: months_passed(in_start timestamp without time zone, in_end timestamp without time zone)

Returns: integer

Language: SQL

Returns the number of months between in_start and in_end.


-- The addition of one day is so that it will return '1' when run on the end
-- day of consecutive months.

select (extract (months from age(in_end + '1 day', in_start + '1 day'))
       + extract (years from age(in_end, in_start)) * 12)::int;

Function: next_leap_year_calc(in_date date, is_end boolean)

Returns: integer

Language: SQL

Next relevant leap year calculation for a daily depreciation calculation

SELECT
          (CASE WHEN extract('doy' FROM $1) < 59
          THEN extract('year' FROM $1)
          ELSE extract('year' FROM $1) + 1
          END)::int
          -
          CASE WHEN $2 THEN 1 ELSE 0 END;

Function: order__combine(in_ids integer[])

Returns: SET OF oe

Language: PLPGSQL


DECLARE retval oe;
        ordercount int;
        ids int[];
        loop_info record;
        settings text[];
        my_person_id int;
BEGIN

SELECT id INTO my_person_id
  FROM person
 WHERE entity_id = person__get_my_entity_id();

settings := ARRAY['sonumber', 'ponumber', 'sqnumber', 'rfqnumber'];
ids := array[]::int[];

-- This approach of looping through insert/select operations will break down
-- if overly complex order consolidation jobs are run (think, hundreds of
-- combined orders in the *output*
--
-- The tradeoff is that if we address the huge complex runs here, then we have
-- the possibility of having to lock the whole table which poses other issues.
-- For that reason, I am going with this approach for now. --CT

FOR loop_info IN
       SELECT max(id) as id, taxincluded, entity_credit_account, oe_class_id,
              curr
         FROM oe WHERE id = any(in_ids)
     GROUP BY taxincluded, entity_credit_account, oe_class_id, curr
LOOP

INSERT INTO oe
       (ordnumber, transdate,   amount_tc,     netamount_tc,
        reqdate,   taxincluded, shippingpoint, notes,
        curr,      person_id,   closed,        quotation,
        quonumber, intnotes,    shipvia,       language_code,
        ponumber,  terms,       oe_class_id,   entity_credit_account)
SELECT CASE WHEN oe_class_id IN (1, 2)
            THEN setting_increment(settings[oe_class_id])
            ELSE NULL
        END,          now()::date,        sum(amount_tc),  sum(netamount_tc),
        min(reqdate), taxincluded,        min(shippingpoint), '',
        curr,         my_person_id, false, false,
        CASE WHEN oe_class_id IN (3, 4)
            THEN setting_increment(settings[oe_class_id])
            ELSE NULL
        END,          NULL,      NULL,          NULL,
        null,       min(terms),  oe_class_id,  entity_credit_account
  FROM oe
 WHERE id = any (in_ids)
       AND taxincluded = loop_info.taxincluded
       AND entity_credit_account = loop_info.entity_credit_account
       AND oe_class_id = loop_info.oe_class_id
 GROUP BY curr, taxincluded, oe_class_id, entity_credit_account;


INSERT INTO orderitems
       (trans_id,      parts_id,        description,         qty,
        sellprice,     precision,       discount,            unit,
        reqdate,       ship,            serialnumber,        notes)
SELECT currval('oe_id_seq'), oi.parts_id, oi.description,     oi.qty,
       oi.sellprice,   oi.precision,    oi.discount,         oi.unit,
       oi.reqdate,     oi.ship,         oi.serialnumber,     oi.notes
  FROM orderitems oi
  JOIN oe ON oi.trans_id = oe.id
 WHERE oe.id = any (in_ids)
       AND taxincluded = loop_info.taxincluded
       AND entity_credit_account = loop_info.entity_credit_account
       AND oe_class_id = loop_info.oe_class_id;

ids := ids || currval('oe_id_seq')::int;

END LOOP;

UPDATE oe SET closed = true WHERE id = any(in_ids);

FOR retval IN select * from oe WHERE id =any(ids)
LOOP
   RETURN NEXT retval;
END LOOP;

END;

Function: order__search(in_oe_class_id integer, in_meta_number text, in_legal_name text, in_ponumber text, in_ordnumber text, in_open boolean, in_closed boolean, in_shipvia text, in_description text, in_date_from date, in_date_to date, in_shippable boolean, in_buisness_units integer[])

Returns: SET OF order_search_line

Language: SQL


       SELECT o.id,
              CASE WHEN oe_class_id IN (1, 2) THEN o.ordnumber
                   WHEN oe_class_id IN (3, 4) THEN o.quonumber
                   ELSE NULL
               END as ordnumber, o.transdate, o.reqdate,
              o.amount_tc, c.name, o.netamount_tc,
              o.entity_credit_account, o.closed, o.quonumber, o.shippingpoint,
              o.shipvia, pe.first_name || ' ' || pe.last_name
              AS employee, pm.first_name || ' ' || pm.last_name AS manager,
              o.curr, o.ponumber, ct.meta_number, c.id
         FROM oe o
         JOIN entity_credit_account ct ON (o.entity_credit_account = ct.id)
         JOIN entity c ON (c.id = ct.entity_id)
    LEFT JOIN person pe ON (o.person_id = pe.id)
    LEFT JOIN entity_employee e ON (pe.entity_id = e.entity_id)
    LEFT JOIN person pm ON (e.manager_id = pm.id)
    LEFT JOIN entity_employee m ON (pm.entity_id = m.entity_id)
        WHERE o.oe_class_id = in_oe_class_id
             AND (in_meta_number IS NULL
                   or ct.meta_number ILIKE in_meta_number || '%')
             AND (in_legal_name IS NULL OR
                     c.name @@ plainto_tsquery(in_legal_name))
             AND (in_ponumber IS NULL OR o.ponumber ILIKE in_ponumber || '%')
             AND (in_ordnumber IS NULL
                  OR (oe_class_id IN (1, 2) AND o.ordnumber ILIKE in_ordnumber || '%')
                  OR oe_class_id IN (3, 4) AND o.quonumber ILIKE in_ordnumber || '%')
             AND ((in_open is true and o.closed is false)
                 OR (in_closed is true and o.closed is true))
             AND (in_shipvia IS NULL
                      OR o.shipvia @@ plainto_tsquery(in_shipvia))
             AND (in_description IS NULL AND in_shippable IS NULL OR
                     EXISTS (SELECT 1
                               FROM orderitems oi
                               JOIN parts p ON p.id = oi.parts_id
                              WHERE trans_id = o.id
                                    AND (in_description IS NULL OR
                                        oi.description
                                        @@ plainto_tsquery(in_description))
                                    AND (in_shippable IS NULL OR
                                         p.assembly OR
                                         p.inventory_accno_id IS NOT NULL))
                 )
             AND (in_date_from IS NULL OR o.transdate >= in_date_from)
             AND (in_date_to IS NULL OR o.transdate <= in_date_to);


Function: overpayment__reverse(in_id integer, in_transdate date, in_batch_id integer, in_account_class integer)

Returns: boolean

Language: PLPGSQL

declare t_id int;
        in_cash_accno text;
BEGIN

-- reverse overpayment gl

INSERT INTO gl (transdate, reference, description, approved, trans_type_code)
SELECT transdate, reference || '-reversal',
       'reversal of ' || description, '0', 'op'
  FROM gl WHERE id = (select gl_id from payment where id = in_id);

IF NOT FOUND THEN
   RETURN FALSE;
END IF;

t_id := currval('id');

UPDATE transactions SET reversing = in_id WHERE id = t_id;

INSERT INTO voucher (batch_id, trans_id, batch_class)
VALUES (in_batch_id, t_id, CASE WHEN in_account_class = 1 THEN 4 ELSE 7 END);

INSERT INTO acc_trans (transdate, trans_id, chart_id,
                       amount_bc, curr, amount_tc)
SELECT in_transdate, t_id, chart_id, amount_bc * -1, curr, amount_tc * -1
  FROM acc_trans
 WHERE trans_id = in_id;

PERFORM * FROM payment__overpayments_list(null, null, null, null, null)
    WHERE available<>amount and payment_id = in_id;

IF FOUND THEN
   RAISE 'Cannot reverse used overpayment: reverse payments first';
END IF;

-- reverse overpayment usage
--
-- The query below will automatically do what the above simply bails out on.
-- However, it doesn't work and I don't understand it enough - right now -
-- to fix it.
-- PERFORM payment__reverse(ac.source, ac.transdate, eca.id, at.accno,
--         in_transdate, eca.entity_class, in_batch_id, null,
--         in_exchangerate, in_curr)
--   FROM acc_trans ac
--   JOIN account at ON ac.chart_id = at.id
--   JOIN account_link al ON at.id = al.account_id AND al.description like 'A%paid'
--   JOIN (select id, entity_credit_account FROM ar UNION
--         select id, entity_credit_account from ap) a ON a.id = ac.trans_id
--   JOIN entity_credit_account eca ON a.entity_credit_account = eca.id
--   JOIN payment_links pl ON pl.entry_id = ac.entry_id
--   JOIN overpayments op ON op.payment_id = pl.payment_id
--   JOIN payment p ON p.id = op.payment_id
--  WHERE p.gl_id = in_id
-- GROUP BY ac.source, ac.transdate, eca.id, eca.entity_class,
--          at.accno, al.description;

RETURN TRUE;
END;

Function: parse_date(in_date date)

Returns: date

Language: SQL

Simple way to cast a Perl string to a date format of known type.

 select $1; 

Function: part__get_by_id(in_id integer)

Returns: parts

Language: SQL

select * from parts where id = $1;

Function: parts__get_by_id(in_id integer)

Returns: parts

Language: SQL

SELECT * FROM parts WHERE id = $1;

Function: parts__get_by_partnumber(in_partnumber text)

Returns: parts

Language: SQL

SELECT * FROM parts WHERE partnumber = $1 and obsolete is NOT TRUE;

Function: parts__search_lite(in_partnumber text, in_description text)

Returns: SET OF parts

Language: SQL

SELECT *
  FROM parts
 WHERE ($1 IS NULL OR (partnumber ilike '%' || $1 || '%'))
       AND ($2 IS NULL
            OR description ilike '%' || $2 || '%'
            OR plainto_tsquery(get_default_lang()::regconfig, $2)
               =
               plainto_tsquery(get_default_lang()::regconfig, '')
            OR (description
                @@
                plainto_tsquery(get_default_lang()::regconfig, $2)))
       AND not obsolete
ORDER BY partnumber;

Function: partsgroup__search(in_pricegroup text)

Returns: SET OF partsgroup

Language: SQL

  SELECT * FROM partsgroup
   WHERE $1 is null or partsgroup ilike $1 || '%'
ORDER BY partsgroup;

Function: payment__get_gl(in_payment_id integer)

Returns: gl

Language: SQL

SELECT * FROM gl WHERE id = (select id from payment where id = $1);

Function: payment__overpayments_list(in_date_from date, in_date_to date, in_control_code text, in_meta_number text, in_name_part text)

Returns: SET OF overpayment_list_item

Language: SQL

-- I don't like the subquery below but we are looking for the first line, and
-- I can't think of a better way to do that. --CT

-- This should never hit an income statement-side account but I have handled it
-- in case of configuration error. --CT
SELECT o.payment_id, e.name, o.available, g.transdate,
       (select amount_bc * CASE WHEN c.category in ('A', 'E') THEN -1 ELSE 1 END
          from acc_trans
         where g.id = trans_id
               AND chart_id = o.chart_id ORDER BY entry_id ASC LIMIT 1) as amount
  FROM overpayments o
  JOIN payment p ON o.payment_id = p.id
  JOIN gl g ON g.id = p.gl_id
  JOIN account c ON c.id = o.chart_id
  JOIN entity_credit_account eca ON eca.id = o.entity_credit_id
  JOIN entity e ON eca.entity_id = e.id
 WHERE ($1 IS NULL OR $1 <= g.transdate) AND
       ($2 IS NULL OR $2 >= g.transdate) AND
       ($3 IS NULL OR $3 = e.control_code) AND
       ($4 IS NULL OR $4 = eca.meta_number) AND
       ($5 IS NULL OR e.name @@ plainto_tsquery($5));

Function: payment__reverse(in_payment_id integer, in_payment_date date, in_approved boolean, in_batch_id integer)

Returns: integer

Language: PLPGSQL

Reverses the payment identified by `in_payment_id`, adding the resulting transactions into `in_batch_id` if that''s not null. Returns the `id` of the reversal payment generated.

DECLARE
  t_payment_id int;
BEGIN
  -- check against being an overpayment??
  INSERT INTO payment (reference, gl_id, payment_class,
                       payment_date, closed, entity_credit_id,
                       employee_id, currency, reversing, notes)
    SELECT reference, gl_id, payment_class,
           in_payment_date, closed, entity_credit_id,
           person__get_my_id(), currency, in_payment_id,
           'This payment reverses ' || in_payment_id
      FROM payment
     WHERE id = in_payment_id
  RETURNING id INTO t_payment_id;

  IF in_batch_id IS NOT NULL THEN
    -- Note that we're using the original payment to derive the
    -- value for 'trans_id', because the reversal inserts into the
    -- same trans_id (see 'new_entries' query below for determination
    -- of the value of 'trans_id' in the new acc_trans lines)
    INSERT INTO voucher (trans_id, batch_id, batch_class)
    select trans_id, in_batch_id,
         (select case when payment_class = 1 then 4
                      else 7 end
            from payment c where c.id = t_payment_id)
      from acc_trans a join payment_links pl on a.entry_id = pl.entry_id
     where pl.payment_id = in_payment_id
     group by trans_id;
  END IF;

  -- Using a CTE because we can use the returned result to fill
  -- the payment_links table without further temporary tables
  WITH new_entries AS (
    INSERT INTO acc_trans (trans_id, chart_id, transdate, source,
                           cleared, memo, invoice_id, approved,
                           amount_bc, amount_tc, curr,
                           voucher_id)
     SELECT trans_id, chart_id, in_payment_date, source,
            false, memo, null, coalesce(in_approved, true),
            -1 * amount_bc, -1 * amount_tc, curr,
            (select id from voucher v
              where a.trans_id = v.trans_id
                    and v.batch_id = in_batch_id) as voucher_id
       FROM acc_trans a
      WHERE exists (select 1 from payment_links pl
                     where pl.payment_id = in_payment_id
                           and a.entry_id = pl.entry_id)
    RETURNING entry_id
  )
  INSERT INTO payment_links (payment_id, entry_id)
  SELECT t_payment_id, entry_id
    FROM new_entries;

  RETURN t_payment_id;
END;

Function: payment__search(in_source text, in_from_date date, in_to_date date, in_credit_id integer, in_cash_accno text, in_entity_class integer, in_currency bpchar, in_meta_number text)

Returns: SET OF payment_record

Language: PLPGSQL

This searches for payments. in_date_to and _date_from specify the acceptable date range. All other matches are exact except that null matches all values. Currently (and to support earlier data) we define a payment as a collection of acc_trans records against the same credit account and cash account, on the same day with the same source number, and optionally the same voucher id.

BEGIN
RETURN QUERY EXECUTE $sql$
   select p.id, sum(case when c.entity_class = 1 then a.amount_bc
                    else -1*a.amount_bc end),
          c.meta_number::text, c.id, e.name,
          array_agg(array[act.id::text, act.accno,
                                     act.description]),
          a.source, b.control_code, b.description,
          v.id, p.payment_date,
          (select r.id from payment r where r.reversing = p.id)
     from payment p
     join payment_links l on p.id = l.payment_id
     join entity_credit_account c on p.entity_credit_id = c.id
     join entity e on e.id = c.entity_id
     join acc_trans a on l.entry_id = a.entry_id
     join account act on a.chart_id = act.id
     left join voucher v on a.voucher_id = v.id
     left join batch b on v.batch_id = b.id
    where ($2 is null
           or $2 <= p.payment_date)
          and ($3 is null
               or $3 >= p.payment_date)
          and ($4 is null
               or c.id = $4)
          and ($6 is null
               or c.entity_class = $6)
          and ($7 is null
               or p.currency = $7)
          and ($8 is null
               or c.meta_number = $8)
          and ($1 is null
               or a.source = $1)
          and (($5 is null
                and exists (select 1
                             from account_link al
                            where al.description in ('AR_paid', 'AP_paid')
                              and al.account_id = act.id))
               or a.chart_id = (select id from account
                                 where accno = $5))
        group by p.id, c.meta_number, c.id, e.name,
                 a.source, b.control_code, b.description,
                 v.id, p.payment_date
$sql$
USING in_source, in_from_date, in_to_date, in_credit_id,
 in_cash_accno, in_entity_class, in_currency, in_meta_number;
END

Function: payment_bulk_post(in_transactions numeric[], in_batch_id integer, in_source text, in_ar_ap_accno text, in_cash_accno text, in_payment_date date, in_account_class integer, in_exchangerate numeric, in_currency text)

Returns: integer

Language: PLPGSQL

This posts the payments for large batch workflows. Note that in_transactions is a two-dimensional numeric array. Of each sub-array, the first element is the (integer) transaction id, and the second is the amount for that transaction.

DECLARE
        out_count int;
        t_voucher_id int;
        t_trans_id int;
        t_amount numeric;
        t_ar_ap_id int;
        t_cash_id int;
        t_defaultcurr text;
        t_exchangerate numeric;
        t_cash_sign int;
        t_batch batch;
BEGIN
        t_exchangerate := in_exchangerate;

        IF in_batch_id IS NULL THEN
                -- t_voucher_id := NULL;
                RAISE EXCEPTION 'Bulk Post Must be from Batch!';
        ELSE
                SELECT * INTO t_batch FROM batch WHERE in_batch_id = id;
                IF t_batch.approved_by IS NOT NULL THEN
                    RAISE EXCEPTION 'Approved Batch';
                ELSIF t_batch.locked_by IS NOT NULL THEN
                    PERFORM * FROM session
                       JOIN users ON (session.users_id = users.id)
                      WHERE session_id = t_batch.locked_by
                            AND users.username = SESSION_USER;

                    IF NOT FOUND THEN
                        -- locked by someone else
                        RAISE EXCEPTION 'batch locked by %, I am %', t_batch.locked_by, session_user;
                    END IF;
                END IF;
                INSERT INTO voucher (batch_id, batch_class, trans_id)
                values (in_batch_id,
                (SELECT batch_class_id FROM batch WHERE id = in_batch_id),
                in_transactions[1][1]);

                t_voucher_id := currval('voucher_id_seq');
        END IF;

        SELECT * INTO t_defaultcurr
          FROM defaults_get_defaultcurrency();


        IF in_account_class = 1 THEN
            t_cash_sign := 1;
        ELSE
            t_cash_sign := -1;
        END IF;

        IF (in_currency IS NULL OR in_currency = t_defaultcurr) THEN
                t_exchangerate := 1;
        END IF;
        IF t_exchangerate IS NULL THEN
            RAISE EXCEPTION 'No exchangerate provided and not default currency';
        END IF;

        CREATE TEMPORARY TABLE bulk_payments_in (
            id int,                   -- AR/AP id
            payment_id int,           -- payment.id
            eca_id int,               -- entity_credit_account.id
            entry_id int,             -- acc_trans.entry_id
            amount_bc numeric,        -- amount in local currency (current rate)
            amount_tc numeric,        -- amount in foreign currency
            disc_amount_bc numeric,   -- discount amount in
            disc_amount_tc numeric,
            fxrate numeric,
            gain_loss_accno int,
            want_gain_loss_accno boolean,
            invoice_date date);

        FOR out_count IN
                        array_lower(in_transactions, 1) ..
                        array_upper(in_transactions, 1)
        LOOP
            -- Fill the bulk payments table
            IF in_transactions[out_count][2] <> 0 THEN
               INSERT INTO bulk_payments_in(id, amount_tc)
            VALUES (in_transactions[out_count][1],
                    in_transactions[out_count][2]);
            END IF;
        END LOOP;

        UPDATE bulk_payments_in bpi
           SET eca_id =
                  (SELECT entity_credit_account FROM ar
                            WHERE in_account_class = 2
                              AND bpi.id = ar.id
                            UNION
                           SELECT entity_credit_account FROM ap
                            WHERE in_account_class = 1
                              AND bpi.id = ap.id);

        CREATE TEMPORARY TABLE eca_payments_in AS
        SELECT eca_id, nextval('payment_id_seq') as payment_id,
                       -- this logic is reversed, but mirrors what's been
                       -- incorrect in payment post since 12 years...
                       case when in_account_class = 1
                                 then setting_increment('rcptnumber')
                            else setting_increment('paynumber')
                       end as reference
          FROM bulk_payments_in
         GROUP BY eca_id;

        UPDATE bulk_payments_in bpi
           SET payment_id = (select payment_id from eca_payments_in ep
                              where bpi.eca_id = ep.eca_id);


        UPDATE bulk_payments_in bpi
           SET invoice_date = (select transdate from transactions trn
                                where trn.id = bpi.id);

        IF (in_currency IS NULL OR in_currency = t_defaultcurr) THEN
            UPDATE bulk_payments_in
               SET fxrate = 1;
        ELSE
            UPDATE bulk_payments_in
               SET fxrate =
                (SELECT fxrate

                   FROM (SELECT id, CASE WHEN amount_tc<>0
                                      THEN amount_bc/amount_tc
                                    ELSE NULL END as fxrate
                         FROM ar
                         UNION
                         SELECT id, CASE WHEN amount_tc<>0
                                      THEN amount_bc/amount_tc
                                    ELSE NULL END as fxrate
                         FROM ap) a
                   WHERE a.id = bulk_payments_in.id);

            UPDATE bulk_payments_in
               SET want_gain_loss_accno = true,
                   gain_loss_accno =
                (SELECT value::int FROM defaults
                  WHERE setting_key = 'fxgain_accno_id')
             WHERE ((t_exchangerate - bulk_payments_in.fxrate)
                    * t_cash_sign) < 0;

            UPDATE bulk_payments_in
               SET want_gain_loss_accno = true,
                   gain_loss_accno = (SELECT value::int FROM defaults
                  WHERE setting_key = 'fxloss_accno_id')
             WHERE ((t_exchangerate - bulk_payments_in.fxrate)
                    * t_cash_sign) > 0;
            -- explicitly leave zero gain/loss accno_id entries at NULL
            -- so we have an easy check later
        END IF;

        PERFORM * FROM bulk_payments_in
                  WHERE want_gain_loss_accno AND gain_loss_accno IS NULL;
        IF FOUND THEN
           RAISE 'Missing gain/loss account while posting FX difference';
        END IF;

        UPDATE bulk_payments_in bpi
           SET disc_amount_tc = coalesce(
                  (SELECT bpi.amount_tc
                          / (100 - eca.discount::numeric)
                          * eca.discount::numeric
                     FROM entity_credit_account eca
                    WHERE age(in_payment_date, bpi.invoice_date)
                                  < (eca.discount_terms||' days')::interval
                          AND eca.discount_terms IS NOT NULL
                          AND eca.discount IS NOT NULL
                          AND eca.discount_account_id IS NOT NULL
                          AND eca.id = bpi.eca_id),
                  0);

        UPDATE bulk_payments_in
           SET amount_bc = amount_tc * t_exchangerate,
               disc_amount_bc = disc_amount_tc * t_exchangerate;


        select id into t_ar_ap_id from account where accno = in_ar_ap_accno;
        select id into t_cash_id from account where accno = in_cash_accno;



-- Given an open item, created on an earlier date, at an FX rate of '2',
-- the code below inserts this transaction (for each line in bulk_payments_in),
-- with the FX rate of the current transaction being '3' and the terms of the
-- transaction being 10/30 and the transaction being paid within the term.

--                   |   Credits       |   Debits        |   FX  |
--                   |   BC   |   TC   |   BC   |   TC   |  rate |
--   ----------------+--------+--------+--------+--------+-------|
--   Current account |     81 |     27 |        |        |  curr |
--   Discounts       |      9 |      3 |        |        |  curr |
--   ----------------+--------+--------+--------+--------+-------|
--   Accounts rec    |        |        |     60 |     30 |  orig |
--   FX effects      |        |        |     30 |      0 |   na  |
--   ----------------+--------+--------+--------+--------+-------|
--   Total           |     90 |     30 |     90 |     30 |       |
--   ----------------+--------+--------+--------+--------|-------|

-- Note: due to the fact that the discount is valued at the current rate,
--   the revaluation is based on the accounts receivable amount.
--   If the discount amount were to be valued at the original rate,
--   the FX effect should be calculated based on the current payment amount

        -- The 'id' values were allocated above
        INSERT INTO payment (id, reference, payment_class, payment_date,
                             entity_credit_id, employee_id, currency, notes)
        SELECT epi.payment_id, epi.reference, in_account_class,
               in_payment_date, epi.eca_id, person__get_my_id(), in_currency,
               'generated from bulk payment'
          FROM eca_payments_in epi;

        -- Insert cash side @ current fx rate
        UPDATE bulk_payments_in
           SET entry_id = nextval('acc_trans_entry_id_seq')
         WHERE amount_tc <> 0;
        INSERT INTO acc_trans
             (trans_id, chart_id, amount_bc, curr, amount_tc, approved,
              voucher_id, transdate, source, entry_id)
           SELECT id, t_cash_id, amount_bc * t_cash_sign,
                  in_currency, amount_tc * t_cash_sign,
                  CASE WHEN t_voucher_id IS NULL THEN true
                       ELSE false END,
                  t_voucher_id, in_payment_date, in_source, entry_id
             FROM bulk_payments_in  where amount_tc <> 0;
        INSERT INTO payment_links (payment_id, entry_id, type)
        SELECT payment_id, entry_id, 1 FROM bulk_payments_in
         WHERE amount_tc <> 0;

        -- Insert discount @ current fx rate
        UPDATE bulk_payments_in
           SET entry_id = nextval('acc_trans_entry_id_seq')
         WHERE disc_amount_bc <> 0;
        INSERT INTO acc_trans
               (trans_id, chart_id, amount_bc, curr, amount_tc, approved,
               voucher_id, transdate, source, entry_id)
        SELECT bpi.id, eca.discount_account_id,
               disc_amount_bc * t_cash_sign, in_currency,
               disc_amount_tc * t_cash_sign,
               CASE WHEN t_voucher_id IS NULL THEN true
                       ELSE false END,
               t_voucher_id, in_payment_date, in_source, entry_id
          FROM bulk_payments_in bpi
          JOIN entity_credit_account eca ON bpi.eca_id = eca.id
         WHERE bpi.disc_amount_bc <> 0;
        INSERT INTO payment_links (payment_id, entry_id, type)
        SELECT payment_id, entry_id, 1 FROM bulk_payments_in
         WHERE disc_amount_bc <> 0;

        -- Insert AR/AP amount @ orginal rate
        UPDATE bulk_payments_in
           SET entry_id = nextval('acc_trans_entry_id_seq');
        INSERT INTO acc_trans
               (trans_id, chart_id, amount_bc, curr, amount_tc, approved,
               voucher_id, transdate, source, entry_id)
        SELECT bpi.id, t_ar_ap_id,
               (bpi.amount_tc + bpi.disc_amount_tc)
                  * t_cash_sign * -1 * bpi.fxrate, in_currency,
               (bpi.amount_tc + bpi.disc_amount_tc)
                  * t_cash_sign * -1,
               CASE WHEN t_voucher_id IS NULL THEN true
                       ELSE false END,
               t_voucher_id, in_payment_date, in_source, entry_id
          FROM bulk_payments_in bpi
          JOIN entity_credit_account eca ON bpi.eca_id = eca.id;
        INSERT INTO payment_links (payment_id, entry_id, type)
        SELECT payment_id, entry_id, 1 FROM bulk_payments_in;

        -- Insert fx gain/loss effects, if applicable
        UPDATE bulk_payments_in
           SET entry_id = nextval('acc_trans_entry_id_seq')
         WHERE gain_loss_accno IS NOT NULL;
        INSERT INTO acc_trans
             (trans_id, chart_id, amount_bc, curr, amount_tc, approved,
              voucher_id, transdate, source, entry_id)
           SELECT id, gain_loss_accno,
                  amount_tc * t_cash_sign *
                     (t_exchangerate - fxrate),
                  in_currency, 0,
                  CASE WHEN t_voucher_id IS NULL THEN true
                       ELSE false END,
                  t_voucher_id, in_payment_date, in_source, entry_id
             FROM bulk_payments_in
            WHERE gain_loss_accno IS NOT NULL;
        INSERT INTO payment_links (payment_id, entry_id, type)
        SELECT payment_id, entry_id, 1 FROM bulk_payments_in
         WHERE gain_loss_accno IS NOT NULL;

        DROP TABLE bulk_payments_in;
        DROP TABLE eca_payments_in;
        perform unlock_all();
        return out_count;
END;

Function: payment_gather_header_info(in_account_class integer, in_payment_id integer)

Returns: SET OF payment_header_item

Language: PLPGSQL

This function finds a payment based on the id and retrieves the record, it is usefull for printing payments :)

BEGIN
RETURN QUERY EXECUTE $sql$
   SELECT p.id as payment_id, p.reference as payment_reference, p.payment_date,
          c.legal_name as legal_name, am.amount_bc as amount, em.first_name, em.last_name, p.currency, p.notes
   FROM payment p
   JOIN entity_employee ent_em ON (ent_em.entity_id = p.employee_id)
   JOIN person em ON (ent_em.entity_id = em.entity_id)
   JOIN entity_credit_account eca ON (eca.id = p.entity_credit_id)
   JOIN company c ON   (c.entity_id  = eca.entity_id)
   JOIN payment_links pl ON (p.id = pl.payment_id)
   LEFT JOIN (  SELECT sum(a.amount_bc) as amount_bc
                FROM acc_trans a
                JOIN account acc ON (a.chart_id = acc.id)
                JOIN account_link al ON (acc.id =al.account_id)
                JOIN payment_links pl ON (pl.entry_id=a.entry_id)
                WHERE al.description in
                       ('AP_paid', 'AP_discount', 'AR_paid', 'AR_discount')
                       and (($1 = 1 AND al.description like 'AP%')
                       or ($1 = 2 AND al.description like 'AR%'))
             ) am ON (true)
   WHERE p.id = $2
$sql$
USING in_account_class, in_payment_id;
END

Function: payment_gather_line_info(in_account_class integer, in_payment_id integer)

Returns: SET OF payment_line_item

Language: PLPGSQL

This function finds a payment based on the id and retrieves all the line records, it is usefull for printing payments and build reports :)

BEGIN
RETURN QUERY EXECUTE $sql$
     SELECT pl.payment_id, ac.entry_id, pl.type as link_type, ac.trans_id, a.invnumber as invoice_number,
     ac.chart_id, ch.accno as chart_accno, ch.description as chart_description,
     ac.amount_bc, ac.transdate as trans_date, ac.source, ac.cleared,
     ac.memo, ac.invoice_id, ac.approved
     FROM acc_trans ac
     JOIN payment_links pl ON (pl.entry_id = ac.entry_id )
     JOIN account ch ON (ch.id = ac.chart_id)
     LEFT JOIN (SELECT id,invnumber
                 FROM ar WHERE $1 = 2
                 UNION
                 SELECT id,invnumber
                 FROM ap WHERE $1 = 1
                ) a ON (ac.trans_id = a.id)
     WHERE pl.payment_id = $2
$sql$
USING in_account_class, in_payment_id;
END

Function: payment_get_all_accounts(in_account_class integer)

Returns: SET OF payment_open_account

Language: PLPGSQL

This function takes a single argument (1 for vendor, 2 for customer as always) and returns all entities with accounts of the appropriate type.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT  ec.id,
                        e.name, ec.entity_class
                FROM entity e
                JOIN entity_credit_account ec ON (ec.entity_id = e.id)
                                WHERE e.entity_class = $1
$sql$
USING in_account_class;
END

Function: payment_get_all_contact_invoices(in_account_class integer, in_business_id integer, in_currency bpchar, in_date_from date, in_date_to date, in_batch_id integer, in_ar_ap_accno text, in_meta_number text, in_contact_name text, in_payment_date date)

Returns: SET OF payment_contact_invoice

Language: PLPGSQL

This function takes the following arguments (all prefaced with in_ in the db): account_class: 1 for vendor, 2 for customer business_type: integer of business.id. currency: char(3) of currency (for example 'USD') date_from, date_to: These dates are inclusive. batch_id: For payment batches, where fees are concerned. ar_ap_accno: The AR/AP account number. This then returns a set of contact information with a 2 dimensional array cnsisting of outstanding invoices. Note that the payment selection logic is that this returns all invoices which are either approved or in the batch_id specified. It also locks the invoices using the LedgerSMB discretionary locking framework, and if not possible, returns the username of the individual who has the lock.

BEGIN
RETURN QUERY EXECUTE $sql$
                  SELECT c.id AS contact_id, e.control_code as econtrol_code,
                        c.description as eca_description,
                        e.name AS contact_name,
                         c.meta_number::text AS account_number,
                         sum( case when u.username IS NULL or
                                       u.username = SESSION_USER
                             THEN
                              coalesce(p.due::numeric, 0) -
                              CASE WHEN (c.discount_terms||' days')::interval
                                        > age(coalesce($10, current_date), a.transdate)
                                   THEN 0
                                   ELSE (coalesce(p.due::numeric, 0)) *
                                        coalesce(c.discount::numeric, 0) / 100
                              END
                             ELSE 0::numeric
                             END) AS total_due,
                         array_agg(ARRAY[
                              a.id::text, a.invnumber, a.transdate::text,
                              a.amount_bc::text, (a.amount_bc - p.due)::text,
                              (CASE WHEN (c.discount_terms||' days')::interval
                                        < age(coalesce($10, current_date), a.transdate)
                                   THEN 0
                                   ELSE (coalesce(p.due, 0) * coalesce(c.discount, 0) / 100)
                              END)::text,
                              (coalesce(p.due, 0) -
                              (CASE WHEN (c.discount_terms||' days')::interval
                                        < age(coalesce($10, current_date), a.transdate)
                                   THEN 0
                                   ELSE (coalesce(p.due, 0)) * coalesce(c.discount, 0) / 100
                              END))::text,
                                case when u.username IS NOT NULL
                                          and u.username <> SESSION_USER
                                     THEN 0::text
                                     ELSE 1::text
                                END,
                                COALESCE(u.username, 0::text)
                                ]),
                              sum(case when a.batch_id = $6 then 1
                                  else 0 END),
                              bool_and(lock_record(a.id, (select max(session_id)
                                FROM "session" where users_id = (
                                        select id from users WHERE username =
                                        SESSION_USER))))

                    FROM entity e
                    JOIN entity_credit_account c ON (e.id = c.entity_id)
                    JOIN (SELECT ap.id, invnumber, transdate, amount_bc,
                                 curr, 1 as invoice_class,
                                 entity_credit_account, on_hold, v.batch_id,
                                 approved
                            FROM ap
                       LEFT JOIN (select * from voucher where batch_class = 1) v
                                 ON (ap.id = v.trans_id)
                           WHERE $1 = 1
                                 AND (v.batch_class = 1 or v.batch_id IS NULL)
                           UNION
                          SELECT ar.id, invnumber, transdate, amount_bc,
                                 curr, 2 as invoice_class,
                                 entity_credit_account, on_hold, v.batch_id,
                                 approved
                            FROM ar
                       LEFT JOIN (select * from voucher where batch_class = 2) v
                                 ON (ar.id = v.trans_id)
                           WHERE $1 = 2
                                 AND (v.batch_class = 2 or v.batch_id IS NULL)
                        ORDER BY transdate
                         ) a ON (a.entity_credit_account = c.id)
                    JOIN transactions t ON (a.id = t.id)
                    JOIN (SELECT acc_trans.trans_id,
                                 sum(CASE WHEN $1 = 1 THEN amount_bc
                                          WHEN $1 = 2
                                          THEN amount_bc * -1
                                     END) AS due
                            FROM acc_trans
                            JOIN account coa ON (coa.id = acc_trans.chart_id)
                            JOIN account_link al ON (al.account_id = coa.id)
                       LEFT JOIN voucher v ON (acc_trans.voucher_id = v.id)
                           WHERE ((al.description = 'AP' AND $1 = 1)
                                 OR (al.description = 'AR' AND $1 = 2))
                           AND (approved IS TRUE or v.batch_class IN (3, 6))
                        GROUP BY acc_trans.trans_id) p ON (a.id = p.trans_id)
                LEFT JOIN "session" s ON (s."session_id" = t.locked_by)
                LEFT JOIN users u ON (u.id = s.users_id)
                   WHERE (a.batch_id = $6
                          OR (a.invoice_class = $1
                             AND a.approved
                         AND due <> 0
                         AND NOT a.on_hold
                         AND a.curr = $3
                         AND EXISTS (select trans_id FROM acc_trans
                                      WHERE trans_id = a.id AND
                                            chart_id = (SELECT id from account
                                                         WHERE accno
                                                               = $7)
                                    )))
                         AND ($8 IS NULL OR
                              $8 = c.meta_number)
                         AND ($9 IS NULL OR
                              e.name ilike '%' || $9 || '%')
                GROUP BY c.id, e.name, c.meta_number, c.threshold,
                        e.control_code, c.description
                  HAVING  c.threshold is null or (sum(p.due) >= c.threshold
                        OR sum(case when a.batch_id = $6 then 1
                                  else 0 END) > 0)
        ORDER BY c.meta_number ASC
$sql$
USING in_account_class, in_business_id, in_currency, in_date_from,
 in_date_to, in_batch_id, in_ar_ap_accno, in_meta_number,
 in_contact_name, in_payment_date;
END

Function: payment_get_available_overpayment_amount(in_account_class integer, in_entity_credit_id integer)

Returns: SET OF payment_overpayments_available_amount

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
              SELECT chart_id, accno,   chart_description, available
              FROM overpayments
              WHERE payment_class  = $1
              AND entity_credit_id = $2
              AND available <> 0;
$sql$
USING in_account_class, in_entity_credit_id;
END

Function: payment_get_entity_account_payment_info(in_entity_credit_id integer)

Returns: payment_vc_info

Language: PLPGSQL

Returns payment information on the entity credit account as required to for discount calculations and payment processing.

DECLARE
  t_retval payment_vc_info;
BEGIN
EXECUTE $sql$
 SELECT ec.id, coalesce(ec.pay_to_name, cp.name  || coalesce(':' || ec.description, ''), '') as name,
        e.entity_class, ec.discount_account_id, ec.meta_number
 FROM entity_credit_account ec
 JOIN entity e ON (ec.entity_id = e.id)
 JOIN (
   select entity_id, legal_name as name
   from company
   union all
   select entity_id, first_name || coalesce(' ' || middle_name || ' ', '') || last_name
   from person
 ) cp ON (cp.entity_id = e.id)
 WHERE ec.id = $1
$sql$
INTO t_retval
USING in_entity_credit_id;
RETURN t_retval;
END

Function: payment_get_entity_accounts(in_account_class integer, in_vc_name text, in_vc_idn text, in_datefrom date, in_dateto date)

Returns: SET OF payment_vc_info

Language: PLPGSQL

Returns a minimal set of information about customer or vendor accounts as needed for discount calculations and the like.

BEGIN
RETURN QUERY EXECUTE $sql$
              SELECT ec.id, coalesce(ec.pay_to_name, e.name ||
                     coalesce(':' || ec.description,'')) as name,
                     e.entity_class, ec.discount_account_id, ec.meta_number
                FROM entity_credit_account ec
                JOIN entity e ON (ec.entity_id = e.id)
                WHERE ec.entity_class = $1
                AND (e.name ilike coalesce('%'||$2||'%','%%')
                    OR EXISTS (select 1 FROM company
                                WHERE entity_id = e.id AND tax_id = $3))
                AND (coalesce(ec.enddate, now()::date)
                     >= coalesce($4, now()::date))
                AND (coalesce(ec.startdate, now()::date)
                     <= coalesce($5, now()::date))
$sql$
USING in_account_class, in_vc_name, in_vc_idn, in_datefrom, in_dateto;
END

Function: payment_get_open_accounts(in_account_class integer, in_datefrom date, in_dateto date)

Returns: SET OF payment_open_account

Language: PLPGSQL

This function takes a single argument (1 for vendor, 2 for customer as always) and returns all entities with open accounts of the appropriate type.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT ec.id, e.name, ec.entity_class
                FROM entity e
                JOIN entity_credit_account ec ON (ec.entity_id = e.id)
                        WHERE ec.entity_class = $1
                        AND (coalesce(ec.enddate, now()::date)
                             <= coalesce($3, now()::date))
                        AND (coalesce(ec.startdate, now()::date)
                             >= coalesce($2, now()::date))
                        AND CASE WHEN $1 = 1 THEN
                                ec.id IN
                                (SELECT entity_credit_account
                                   FROM acc_trans
                                   JOIN account_link l ON (acc_trans.chart_id = l.account_id)
                                   JOIN ap ON (acc_trans.trans_id = ap.id)
                                   WHERE l.description = 'AP'
                                   GROUP BY chart_id,
                                         trans_id, entity_credit_account
                                   HAVING SUM(acc_trans.amount_bc) <> 0)
                               WHEN $1 = 2 THEN
                                ec.id IN (SELECT entity_credit_account
                                   FROM acc_trans
                                   JOIN account_link l ON (acc_trans.chart_id = l.account_id)
                                   JOIN ar ON (acc_trans.trans_id = ar.id)
                                   WHERE l.description = 'AR'
                                   GROUP BY chart_id,
                                         trans_id, entity_credit_account
                                   HAVING SUM(acc_trans.amount_bc) <> 0)
                          END
$sql$
USING in_account_class, in_datefrom, in_dateto;
END

Function: payment_get_open_invoice(in_account_class integer, in_entity_credit_id integer, in_curr bpchar, in_datefrom date, in_dateto date, in_amountfrom numeric, in_amountto numeric, in_invnumber text, in_datepaid date)

Returns: SET OF payment_invoice

Language: PLPGSQL

This function is based on payment_get_open_invoices and returns only one invoice if the in_invnumber is set. if no in_invnumber is passed this function behaves the same as payment_get_open_invoices

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT * from payment_get_open_invoices($1, $2, $3, $4, $5, $6,
                $7, $9)
                WHERE (invnumber like $8 OR $8 IS NULL)
$sql$
USING in_account_class, in_entity_credit_id, in_curr, in_datefrom,
 in_dateto, in_amountfrom, in_amountto, in_invnumber,
 in_datepaid;
END

Function: payment_get_open_invoices(in_account_class integer, in_entity_credit_id integer, in_curr bpchar, in_datefrom date, in_dateto date, in_amountfrom numeric, in_amountto numeric, in_datepaid date)

Returns: SET OF payment_invoice

Language: PLPGSQL

This function is the base for get_open_invoice and returns all open invoices for the entity_credit_id it has a lot of options to enable filtering and use the same logic for entity_class_id and currency.

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT a.id AS invoice_id, a.invnumber AS invnumber,a.invoice AS invoice,
                       a.transdate AS invoice_date, a.amount_bc AS amount,
                       a.amount_tc,
                       (CASE WHEN (c.discount_terms||' days')::interval < age(coalesce($8, current_date), a.transdate)
                        THEN 0
                        ELSE (coalesce(ac.due, a.amount_bc)) * coalesce(c.discount, 0) / 100
                        END) AS discount,
                       (CASE WHEN (c.discount_terms||' days')::interval < age(coalesce($8, current_date), a.transdate)
                        THEN 0
                        ELSE (coalesce(ac.due_fx, a.amount_tc)) * coalesce(c.discount, 0) / 100
                        END) AS discount_tc,
                       ac.due - (CASE WHEN (c.discount_terms||' days')::interval < age(coalesce($8, current_date), a.transdate)
                        THEN 0
                        ELSE (coalesce(ac.due, a.amount_bc)) * coalesce(c.discount, 0) / 100
                        END) AS due,
                       ac.due_fx - (CASE WHEN (c.discount_terms||' days')::interval < age(coalesce($8, current_date), a.transdate)
                        THEN 0
                        ELSE (coalesce(ac.due_fx, a.amount_tc)) * coalesce(c.discount, 0) / 100
                         END) AS due_fx,
                        null::numeric AS exchangerate,
                        a.description
                 --TODO HV prepare drop entity_id from ap,ar
                 --FROM  (SELECT id, invnumber, transdate, amount, entity_id,
                 FROM  (SELECT id, invnumber, invoice, transdate, amount_bc,
                       amount_tc,
                               1 as invoice_class, curr,
                               entity_credit_account, approved, description
                          FROM ap
                         UNION
                         --SELECT id, invnumber, transdate, amount, entity_id,
                         SELECT id, invnumber, invoice, transdate, amount_bc,
                      amount_tc,
                               2 AS invoice_class, curr,
                               entity_credit_account, approved, description
                         FROM ar
                         ) a
                JOIN (SELECT trans_id, chart_id,
                             sum(CASE WHEN $1 = 1 THEN amount_bc
                                      WHEN $1 = 2 THEN amount_bc * -1
                                  END) as due,
                             sum(CASE WHEN $1 = 1 THEN amount_tc
                                      WHEN $1 = 2 THEN amount_tc * -1
                                 END) as due_fx
                        FROM acc_trans
                      GROUP BY trans_id, chart_id) ac ON (ac.trans_id = a.id)
                        JOIN account_link l ON (l.account_id = ac.chart_id)
                        JOIN entity_credit_account c ON (c.id = a.entity_credit_account)
                --        OR (a.entity_credit_account IS NULL and a.entity_id = c.entity_id))
                        WHERE ((l.description = 'AP' AND $1 = 1)
                              OR (l.description = 'AR' AND $1 = 2))
                        AND a.invoice_class = $1
                        AND c.entity_class = $1
                        AND c.id = $2
                        --### short term: ignore fractional cent differences
                        AND a.curr = $3
                        AND (a.transdate >= $5
                             OR $5 IS NULL)
                        AND (a.transdate <= $5
                             OR $5 IS NULL)
                        AND (a.amount_bc >= $6
                             OR $6 IS NULL)
                        AND (a.amount_bc <= $7
                             OR $7 IS NULL)
                        AND due <> 0
                        AND a.approved = true
                        GROUP BY a.invnumber, a.transdate, a.amount_bc, amount_tc,
              discount, discount_tc, ac.due, ac.due_fx, a.id, c.discount_terms,
              a.curr, a.invoice, a.description
$sql$
USING in_account_class, in_entity_credit_id, in_curr, in_datefrom,
 in_dateto, in_amountfrom, in_amountto, in_datepaid;
END

Function: payment_get_open_overpayment_entities(in_account_class integer)

Returns: SET OF payment_vc_info

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT DISTINCT entity_credit_id, legal_name, e.entity_class, null::int, o.meta_number
                FROM overpayments o
                JOIN entity e ON (e.id=o.entity_id)
                WHERE available <> 0 AND $1 = payment_class
$sql$
USING in_account_class;
END

Function: payment_get_unused_overpayment(in_account_class integer, in_entity_credit_id integer, in_chart_id integer)

Returns: SET OF overpayments

Language: PLPGSQL

Returns a list of available overpayments

BEGIN
RETURN QUERY EXECUTE $sql$
              SELECT DISTINCT *
              FROM overpayments
              WHERE payment_class  = $1
              AND entity_credit_id = $2
              AND available <> 0
              AND ($3 IS NULL OR chart_id = $3 )
              ORDER BY payment_date
$sql$
USING in_account_class, in_entity_credit_id, in_chart_id;
END

Function: payment_get_vc_info(in_entity_credit_id integer, in_location_class_id integer)

Returns: SET OF payment_location_result

Language: PLPGSQL

This function returns vendor or customer info

BEGIN
RETURN QUERY EXECUTE $sql$
                SELECT l.id, l.line_one, l.line_two, l.line_three, l.city,
                       l.state, l.mail_code, c.name, lc.class
                FROM location l
                JOIN entity_to_location ctl ON (ctl.location_id = l.id)
                JOIN entity cp ON (ctl.entity_id = cp.id)
                JOIN location_class lc ON (ctl.location_class = lc.id)
                JOIN country c ON (c.id = l.country_id)
                JOIN entity_credit_account ec ON (ec.entity_id = cp.id)
                WHERE ec.id = $1 AND
                      lc.id = $2
                ORDER BY lc.id, l.id, c.name
$sql$
USING in_entity_credit_id, in_location_class_id;
END

Function: payment_post(in_datepaid date, in_account_class integer, in_entity_credit_id integer, in_curr bpchar, in_exchangerate numeric, in_notes text, in_gl_description text, in_cash_account_id integer[], in_amount numeric[], in_source text[], in_memo text[], in_transaction_id integer[], in_op_amount numeric[], in_op_cash_account_id integer[], in_op_source text[], in_op_memo text[], in_op_account_id integer[], in_ovp_payment_id integer[], in_approved boolean)

Returns: integer

Language: PLPGSQL

Posts a payment. in_op_* arrays are cross-indexed with eachother. Other arrays are cross-indexed with eachother. The 'in_cash_account_id's are the "cash side" of the payment; i.e. this can be a bank current account, overpayment account or a suspense account associated with a bank current account.

DECLARE var_payment_id int;
DECLARE var_gl_id int;
DECLARE var_entry record;
DECLARE var_entry_id int[];
DECLARE out_count int;
DECLARE coa_id record;
DECLARE var_employee int;
DECLARE var_account_id int;
DECLARE default_currency char(3);
DECLARE current_exchangerate numeric;
DECLARE old_exchangerate numeric;
DECLARE fx_gain_loss_amount numeric;
DECLARE gain_loss_accno_id int;
DECLARE sign int;
BEGIN
      IF array_upper(in_amount, 1) <> array_upper(in_cash_account_id, 1) THEN
          RAISE EXCEPTION 'Wrong number of accounts';
      END IF;

   current_exchangerate := in_exchangerate;
   IF in_account_class = 1 THEN
      sign := 1;
   ELSE
      sign := -1;
   END IF;
   SELECT * INTO default_currency  FROM defaults_get_defaultcurrency();

        SELECT INTO var_employee p.id
        FROM users u
        JOIN person p ON (u.entity_id=p.entity_id)
    WHERE username = SESSION_USER
    LIMIT 1;

        -- WE HAVE TO INSERT THE PAYMENT, USING THE GL INFORMATION
        -- THE ID IS GENERATED BY payment_id_seq
        INSERT INTO payment (reference, payment_class, payment_date,
                              employee_id, currency, notes, entity_credit_id)
    VALUES (-- the rcptnumber and paynumber are reversed; have been for 12 years
            (CASE WHEN in_account_class = 1 THEN setting_increment('rcptnumber')
                                 ELSE setting_increment('paynumber')
                                     END),
                 in_account_class, in_datepaid, var_employee,
                 in_curr, in_notes, in_entity_credit_id);

  -- Assuming a transaction with foreign currency being recorded,
  -- at an exchangerate of 3 upon AR creation and an exchangerate of 2
  -- upon payment. The owed (and paid) amount is 20 in the foreign currency.

  -- 5000 = 'AR' account
  -- 5100 = 'Cash' account
  -- 9999 = fx gain/loss account

  -- +-------+----------+----------+----------+----------+
  -- | accno | Deb (bc) | Deb (tc) | Cre (bc) | Cre (tc) |
  -- +-------+----------+----------+----------+----------+
  -- | 5000  |          |          |    60.00 |    20.00 |
  -- +-------+----------+----------+----------+----------+
  -- | 5100  |    40.00 |    20.00 |          |          |
  -- +-------+----------+----------+----------+----------+
  -- | 9999  |    20.00 |    00.00 |          |          |
  -- +-------+----------+----------+----------+----------+

  -- +-------+----------+----------+----------+----------+
  -- | Total |    60.00 |    20.00 |    60.00 |    20.00 |
  -- +-------+----------+----------+----------+----------+


   SELECT currval('payment_id_seq') INTO var_payment_id;
   IF (array_upper(in_cash_account_id, 1) > 0) THEN
      FOR out_count IN
                      array_lower(in_cash_account_id, 1) ..
                      array_upper(in_cash_account_id, 1)
      LOOP
        -- Insert cash account side of the payment
        -- Each payment can have its own cash account set through the UI
        INSERT INTO acc_trans
               (chart_id, amount_bc, curr, amount_tc, trans_id,
                transdate, approved, source, memo)
              VALUES (in_cash_account_id[out_count],
                      in_amount[out_count]*current_exchangerate*sign,
                      in_curr,
                      in_amount[out_count]*sign,
                      in_transaction_id[out_count],
                      in_datepaid,
                      coalesce(in_approved, true),
                      in_source[out_count],
                      in_memo[out_count]);
        -- Link the ledger line to the payment record
        INSERT INTO payment_links
             VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 1);
        IF (in_ovp_payment_id IS NOT NULL
           AND in_ovp_payment_id[out_count] IS NOT NULL) THEN
          -- mark the current transaction as being the consequence of an overpayment
          -- (lowering the customer account balance)
          INSERT INTO payment_links
                VALUES (in_ovp_payment_id[out_count],
                        currval('acc_trans_entry_id_seq'), 0);
       END IF;
      END LOOP;

      -- HANDLE THE AR/AP ACCOUNTS
      -- OBTAIN THE ACCOUNT AND EXCHANGERATE FROM THERE
      FOR out_count IN
                   array_lower(in_transaction_id, 1) ..
                   array_upper(in_transaction_id, 1)
      LOOP
        SELECT chart_id, amount_bc/amount_tc
               INTO var_account_id, old_exchangerate
          FROM acc_trans as ac
          JOIN account_link as l ON (l.account_id = ac.chart_id)
         WHERE trans_id = in_transaction_id[out_count]
               AND ( l.description in ('AR', 'AP'));

        -- Now we post the AP/AR transaction
        INSERT INTO acc_trans (chart_id, amount_bc, curr, amount_tc,
                               trans_id, transdate, approved, source, memo)
              VALUES (var_account_id,
                      in_amount[out_count]*old_exchangerate*sign*-1,
                      in_curr,
                      in_amount[out_count]*sign*-1,
                      in_transaction_id[out_count],
                      in_datepaid,
                      coalesce(in_approved, true),
                      in_source[out_count],
                      in_memo[out_count]);
        -- Link the ledger line to the payment record
        INSERT INTO payment_links
              VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 1);

        -- Calculate the gain/loss on the transaction
        -- everything above depends on this being an AR/AP posting
        -- the PNL posting and decision to post a gain or loss does not
        --  --> incorporate sign here instead of when posting.
        fx_gain_loss_amount :=
            in_amount[out_count]*sign*(old_exchangerate-current_exchangerate);

        IF (fx_gain_loss_amount > 0) THEN
          SELECT value::int INTO gain_loss_accno_id
            FROM defaults
           WHERE setting_key = 'fxgain_accno_id';
        ELSIF (fx_gain_loss_amount < 0) THEN
          SELECT value::int INTO gain_loss_accno_id
            FROM defaults
           WHERE setting_key = 'fxloss_accno_id';
        END IF;
        IF fx_gain_loss_amount <> 0.00 THEN
          INSERT INTO acc_trans
                   (chart_id, amount_bc, curr, amount_tc,
                    trans_id, transdate, approved, source)
                -- In this transaction we can't use the default currency,
                -- because by definition the tc and bc amounts are the same.
                VALUES (gain_loss_accno_id,
                  fx_gain_loss_amount,
                  in_curr,
                  0, -- the transaction currency side is zero by definition
                  in_transaction_id[out_count],
                  in_datepaid,
                  coalesce(in_approved, true),
                  in_source[out_count]);

          INSERT INTO payment_links
                VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 1);
        END IF;
      END LOOP;
   END IF;


   --
   -- HANDLE THE OVERPAYMENTS NOW
   IF (array_upper(in_op_cash_account_id, 1) > 0) THEN
       INSERT INTO gl (reference, description, transdate,
                       person_id, notes, approved, trans_type_code)
              VALUES (setting_increment('glnumber'),
                      in_gl_description, in_datepaid, var_employee,
                      in_notes, in_approved, 'op');
       SELECT currval('id') INTO var_gl_id;

       UPDATE payment SET gl_id = var_gl_id
        WHERE id = var_payment_id;

       FOR out_count IN
                        array_lower(in_op_cash_account_id, 1) ..
                        array_upper(in_op_cash_account_id, 1)
       LOOP
         -- Cash account side of the transaction
         INSERT INTO acc_trans (chart_id, amount_bc, curr, amount_tc,
                               trans_id, transdate, approved, source, memo)
                VALUES (in_op_cash_account_id[out_count],
                     in_op_amount[out_count]*current_exchangerate*sign,
                     in_curr,
                     in_op_amount[out_count]*sign,
                     var_gl_id,
                     in_datepaid,
                     coalesce(in_approved, true),
                     in_op_source[out_count],
                     in_op_memo[out_count]);
         INSERT INTO payment_links
              VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 2);

       END LOOP;

       -- NOW LETS HANDLE THE OVERPAYMENT ACCOUNTS
       FOR out_count IN
                     array_lower(in_op_account_id, 1) ..
                     array_upper(in_op_account_id, 1)
       LOOP
         INSERT INTO acc_trans (chart_id, amount_bc, curr, amount_tc, trans_id,
                               transdate, approved, source, memo)
                VALUES (in_op_account_id[out_count],
                     in_op_amount[out_count]*current_exchangerate*sign*-1,
                     in_curr,
                     in_op_amount[out_count]*sign*-1,
                     var_gl_id,
                     in_datepaid,
                     coalesce(in_approved, true),
                     in_op_source[out_count],
                     in_op_memo[out_count]);
         INSERT INTO payment_links
                VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 2);
       END LOOP;
 END IF;
 return var_payment_id;
END;

Function: payment_type__get_label(in_payment_type_id integer)

Returns: SET OF payment_type

Language: SQL

Returns all information on a payment type by the id. This should be renamed to account for its behavior in future versions.

SELECT * FROM payment_type where id=in_payment_type_id;

Function: payment_type__list()

Returns: SET OF payment_type

Language: SQL

SELECT * FROM payment_type;

Function: payments_get_open_currencies(in_account_class integer)

Returns: SET OF bpchar

Language: PLPGSQL

This does a sparse scan to find currencies attached to open invoices. It should scale per the number of currencies used rather than the size of the ar or ap tables.

DECLARE result char(3);
BEGIN
select min(curr) into result from ar WHERE in_account_class = 2
union
select min(curr) from ap WHERE in_account_class = 1;


LOOP
   EXIT WHEN result IS NULL;
   return next result;

   SELECT min(curr) INTO result from ar
    where in_account_class = 2 and curr > result
            union
   select min(curr) from ap
    WHERE in_account_class = 1 and curr > result
    LIMIT 1;

END LOOP;
END;

Function: payroll_deduction_type__search(in_account_id integer, in_pdc_id integer, in_country_id integer, in_label text, in_unit text)

Returns: SET OF payroll_deduction_type

Language: SQL

SELECT *
  FROM payroll_deduction_type
 where (account_id = $1 OR $1 IS NULL) AND
       (pdc_id = $2 OR $2 IS NULL) AND
       (country_id = $3 OR $3 IS NULL) AND
       ($4 IS NULL OR label LIKE $4 || '%') AND
       (unit = $5 or $5 IS NULL);

Function: payroll_income_category__list()

Returns: SET OF payroll_income_category

Language: SQL

SELECT * FROM payroll_income_category order by id;

Function: payroll_income_class__for_country(in_country_id integer)

Returns: SET OF payroll_income_class

Language: SQL

SELECT * FROM payroll_income_class where country_id = $1
ORDER BY label;

Function: payroll_income_type__get(in_id integer)

Returns: payroll_income_type

Language: SQL

SELECT * FROM payroll_income_type WHERE id  = $1;

Function: payroll_income_type__save(in_id integer, in_account_id integer, in_pic_id integer, in_country_id integer, in_label text, in_unit text, in_default_amount numeric)

Returns: payroll_income_type

Language: PLPGSQL


   DECLARE retval payroll_income_type;

BEGIN
   UPDATE payroll_income_type
      SET account_id = in_account_id,
          pic_id = in_pic_id,
          country_id = in_country_id,
          label = in_label,
          unit = in_unit,
          default_amount = in_default_amount
    WHERE id = in_id;

   IF FOUND THEN
       retval := payroll_income_type__get(in_id);
       RETURN retval;
   END IF;

   INSERT INTO payroll_income_type
          (account_id, pic_id, country_id, label, unit, default_amount)
   VALUES (in_account_id, in_pic_id, in_country_id, in_label, in_unit,
           in_default_amount);

   retval := payroll_income_type__get(currval('payroll_income_type_id_seq')::int);
   RETURN retval;

END;

Function: payroll_income_type__search(in_account_id integer, in_pic_id integer, in_country_id integer, in_label text, in_unit text)

Returns: SET OF payroll_income_type

Language: SQL

SELECT *
  FROM payroll_income_type
 where (account_id = $1 OR $1 IS NULL) AND
       (pic_id = $2 OR $2 IS NULL) AND
       (country_id = $3 OR $3 IS NULL) AND
       ($4 IS NULL OR label LIKE $4 || '%') AND
       (unit = $5 or $5 IS NULL);

Function: periods_get()

Returns: SET OF periods

Language: SQL

Returns dates for year to date, and last year.

SELECT * FROM periods ORDER BY id

Function: person__delete_contact(in_person_id integer, in_contact_class_id integer, in_contact text)

Returns: boolean

Language: SQL

Deletes a contact record specified for the person. Returns true if a record was found and deleted, false if not.

DELETE FROM entity_to_contact
 WHERE entity_id = (SELECT entity_id FROM person WHERE id = in_person_id)
       and contact_class_id = in_contact_class_id
       and contact= in_contact
RETURNING TRUE;

Function: person__delete_location(in_person_id integer, in_location_id integer, in_location_class integer)

Returns: boolean

Language: SQL

Deletes a location mapping to a person. Returns true if found, false if no data deleted.


DELETE FROM entity_to_location
 WHERE entity_id = (select entity_id from person where id = in_person_id)
       AND location_id = in_location_id
       AND location_class = in_location_class
RETURNING TRUE;


Function: person__get(in_entity_id integer)

Returns: person_entity

Language: SQL

SELECT e.id, e.control_code, e.name, e.country_id, c.name,
       p.first_name, p.middle_name, p.last_name, e.entity_class,
       p.birthdate, p.personal_id
  FROM entity e
  JOIN country c ON c.id = e.country_id
  JOIN person p ON p.entity_id = e.id
 WHERE e.id = $1;

Function: person__get_by_cc(in_control_code text)

Returns: person_entity

Language: SQL

SELECT e.id, e.control_code, e.name, e.country_id, c.name,
       p.first_name, p.middle_name, p.last_name, e.entity_class,
       p.birthdate, p.personal_id
  FROM entity e
  JOIN country c ON c.id = e.country_id
  JOIN person p ON p.entity_id = e.id
 WHERE e.control_code = $1;

Function: person__get_my_entity_id()

Returns: integer

Language: SQL

Returns the entity_id of the current, logged in user.

        SELECT entity_id from users where username = SESSION_USER;

Function: person__get_my_id()

Returns: integer

Language: SQL

Returns the person id of the current, logged in user.

        SELECT p.id from person p
        JOIN users u ON u.entity_id = p.entity_id
        WHERE username = SESSION_USER;

Function: person__list_bank_account(in_entity_id integer)

Returns: SET OF entity_bank_account

Language: SQL

Lists bank accounts for a person

SELECT * from entity_bank_account where entity_id = in_entity_id

Function: person__list_contacts(in_entity_id integer)

Returns: SET OF contact_list

Language: SQL

Returns a list of contacts attached to the function.

                SELECT cc.class, cc.id, c.description, c.contact
                FROM entity_to_contact c
                JOIN contact_class cc ON (c.contact_class_id = cc.id)
                JOIN person p ON (c.entity_id = p.entity_id)
                WHERE p.entity_id = in_entity_id

Function: person__list_languages()

Returns: SET OF language

Language: SQL

Returns a list of languages ordered by code

 SELECT * FROM language ORDER BY description ASC 

Function: person__list_locations(in_entity_id integer)

Returns: SET OF location_result

Language: PLPGSQL

Returns a list of locations specified attached to the person.

DECLARE out_row RECORD;
BEGIN
        FOR out_row IN
                SELECT l.id, l.line_one, l.line_two, l.line_three, l.city,
                        l.state, l.mail_code, c.id, c.name, lc.id, lc.class
                FROM location l
                JOIN entity_to_location ctl ON (ctl.location_id = l.id)
                JOIN person p ON (ctl.entity_id = p.entity_id)
                JOIN location_class lc ON (ctl.location_class = lc.id)
                JOIN country c ON (c.id = l.country_id)
                WHERE p.entity_id = in_entity_id
                ORDER BY lc.id, l.id, c.name
        LOOP
                RETURN NEXT out_row;
        END LOOP;
END;

Function: person__list_notes(in_entity_id integer)

Returns: SET OF entity_note

Language: SQL

Returns a list of notes attached to a person.

                SELECT *
                FROM entity_note
                WHERE ref_key = in_entity_id
                ORDER BY created

Function: person__list_salutations()

Returns: SET OF salutation

Language: SQL

Returns a list of salutations ordered by id.

 SELECT * FROM salutation ORDER BY id ASC 

Function: person__save(in_entity_id integer, in_salutation_id integer, in_first_name text, in_middle_name text, in_last_name text, in_country_id integer, in_birthdate date, in_personal_id text)

Returns: integer

Language: PLPGSQL

Saves the person with the information specified. Returns the entity_id of the record saved.


    DECLARE
        e_id int;
        e entity;
        loc location;
        l_id int;
        p_id int;
    BEGIN

    select * into e from entity where id = in_entity_id and entity_class = 3;
    e_id := in_entity_id;

    IF FOUND THEN
        UPDATE entity
           SET name = in_first_name || ' ' || in_last_name,
               country_id = in_country_id
         WHERE id = in_entity_id;
    ELSE
        INSERT INTO entity (name, entity_class, country_id)
        values (in_first_name || ' ' || in_last_name, 3, in_country_id);
        e_id := currval('entity_id_seq');

    END IF;


    UPDATE person SET
            salutation_id = in_salutation_id,
            first_name = in_first_name,
            last_name = in_last_name,
            middle_name = in_middle_name,
            birthdate = in_birthdate,
            personal_id = in_personal_id
    WHERE
            entity_id = in_entity_id;
    IF FOUND THEN
        RETURN in_entity_id;
    ELSE
        -- Do an insert

        INSERT INTO person (salutation_id, first_name, last_name, entity_id,
                           birthdate, personal_id)
        VALUES (in_salutation_id, in_first_name, in_last_name, e_id,
                in_birthdate, in_personal_id);

        RETURN e_id;

    END IF;
END;

Function: person__save_contact(in_entity_id integer, in_contact_class integer, in_old_contact text, in_contact_new text, in_description text, in_old_contact_class integer)

Returns: integer

Language: PLPGSQL

Saves saves contact info. Returns 1 if a row was inserted, 0 if it was updated.

DECLARE
    out_id int;
    v_orig entity_to_contact;
BEGIN

    SELECT cc.* into v_orig
      FROM entity_to_contact cc
      JOIN person p ON (p.entity_id = cc.entity_id)
     WHERE p.entity_id = in_entity_id
    and cc.contact_class_id = in_old_contact_class
    AND cc.contact = in_old_contact;

    IF NOT FOUND THEN

        -- create
        INSERT INTO entity_to_contact
               (entity_id, contact_class_id, contact, description)
        VALUES (in_entity_id, in_contact_class, in_contact_new, in_description);

        return 1;
    ELSE
        -- edit.
        UPDATE entity_to_contact
           SET contact = in_contact_new, description = in_description
         WHERE contact = in_old_contact
               AND entity_id = in_entity_id
               AND contact_class_id = in_old_contact_class;
        return 0;
    END IF;

END;

Function: person__save_location(in_entity_id integer, in_location_id integer, in_location_class integer, in_line_one text, in_line_two text, in_line_three text, in_city text, in_state text, in_mail_code text, in_country_code integer, in_old_location_class integer)

Returns: integer

Language: PLPGSQL

Saves a location mapped to the person with the specified information. Returns the location id saved.


    DECLARE
        l_row location;
        l_id INT;
            t_person_id int;
    BEGIN
        SELECT id INTO t_person_id
        FROM person WHERE entity_id = in_entity_id;

    UPDATE entity_to_location
       SET location_class = in_location_class
     WHERE entity_id = in_entity_id
           AND location_class = in_old_location_class
           AND location_id = in_location_id;


    IF NOT FOUND THEN
        -- Create a new one.
        l_id := location_save(
            in_location_id,
            in_line_one,
            in_line_two,
            in_line_three,
            in_city,
                in_state,
                in_mail_code,
                in_country_code);

        INSERT INTO entity_to_location
                (entity_id, location_id, location_class)
        VALUES  (in_entity_id, l_id, in_location_class);
    ELSE
        l_id := location_save(
            in_location_id,
            in_line_one,
            in_line_two,
            in_line_three,
            in_city,
                in_state,
                in_mail_code,
                in_country_code);
        -- Update the old one.
    END IF;
    return l_id;
    END;

Function: pnl__customer(in_id integer, in_from_date date, in_to_date date, in_language text)

Returns: SET OF financial_statement_line

Language: SQL

WITH acc_meta AS (
  SELECT a.id, a.accno,
         coalesce(at.description, a.description) as description,
         CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
         ELSE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
         END AS path,
         a.category, 'A'::char as account_type, contra, a.gifi_accno,
         gifi.description as gifi_description
     FROM account a
    INNER JOIN account_heading_tree aht on a.heading = aht.id
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
   WHERE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
                          IS NOT NULL
         -- legacy: earn_id not configured (yet)
         OR (NOT EXISTS (SELECT 1 FROM defaults
                         WHERE setting_key = 'earn_id'
                           AND value IS NOT NULL)
             AND category IN ('E', 'I'))
),
hdr_meta AS (
   SELECT aht.id, aht.accno,
          coalesce(at.description, aht.description) as description,
          CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
          ELSE array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
          END AS path,
          ahc.derived_category as category,
          'H'::char as account_type, 'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
    WHERE ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NOT NULL
           AND array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
                           IS NOT NULL)
          -- legacy: earn_id not configured; select headings belonging to
          --    selected accounts
          OR ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NULL
              AND EXISTS (SELECT 1 FROM acc_meta
                                  WHERE aht.id = ANY(acc_meta.path)))
),
acc_balance AS (
WITH gl (id) AS
 ( SELECT id FROM ap WHERE approved is true AND entity_credit_account = in_id
UNION ALL
   SELECT id FROM ar WHERE approved is true AND entity_credit_account = in_id
)
SELECT ac.chart_id AS id, sum(ac.amount_bc) AS balance
  FROM acc_trans ac
  JOIN gl ON ac.trans_id = gl.id
 WHERE ac.approved is true
          AND (in_from_date IS NULL OR ac.transdate >= in_from_date)
          AND (in_to_date IS NULL OR ac.transdate <= in_to_date)
 GROUP BY ac.chart_id
   HAVING sum(ac.amount_bc) <> 0.00
 ),
hdr_balance AS (
   select ahd.id, sum(balance) as balance
     FROM acc_balance ab
    INNER JOIN account acc ON ab.id = acc.id
    INNER JOIN account_heading_descendant ahd
            ON acc.heading = ahd.descendant_id
    GROUP BY ahd.id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi, null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
   UNION
   SELECT am.id, am.accno, am.description, am.account_type, am.category,
          gifi_accno as gifi, gifi_description, am.contra, ab.balance, am.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: pnl__income_statement_accrual(in_from_date date, in_to_date date, in_business_units integer[], in_language text)

Returns: SET OF financial_statement_line

Language: SQL

WITH acc_meta AS (
  SELECT a.id, a.accno,
         coalesce(at.description, a.description) as description,
         CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
         ELSE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
         END AS path,
         a.category, 'A'::char as account_type, contra, a.gifi_accno,
         gifi.description as gifi_description
     FROM account a
    INNER JOIN account_heading_tree aht on a.heading = aht.id
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
   WHERE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
                          IS NOT NULL
         -- legacy: earn_id not configured (yet)
         OR (NOT EXISTS (SELECT 1 FROM defaults
                         WHERE setting_key = 'earn_id'
                               AND value IS NOT NULL)
             AND category IN ('E', 'I'))
),
hdr_meta AS (
   SELECT aht.id, aht.accno,
          coalesce(at.description, aht.description) as description,
          CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
          ELSE array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
          END AS path,
          ahc.derived_category as category,
          'H'::char as account_type, 'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
    WHERE ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NOT NULL
           AND array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
                           IS NOT NULL)
          -- legacy: earn_id not configured; select headings belonging to
          --    selected accounts
          OR ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NULL
              AND EXISTS (SELECT 1 FROM acc_meta
                                  WHERE aht.id = ANY(acc_meta.path)))
),
acc_balance AS (
   WITH RECURSIVE bu_tree (id, parent, path) AS (
      SELECT id, null, row(array[id])::tree_record FROM business_unit
       WHERE id = any(in_business_units)
      UNION ALL
      SELECT bu.id, parent, row((path).t || bu.id)::tree_record
        FROM business_unit bu
        JOIN bu_tree ON bu.parent_id = bu_tree.id
   )
   SELECT ac.chart_id AS id, sum(ac.amount_bc) AS balance
     FROM acc_trans ac
    INNER JOIN transactions gl ON ac.trans_id = gl.id AND gl.approved
     LEFT JOIN (SELECT array_agg(path) AS bu_ids, entry_id
                  FROM business_unit_ac buac
                 INNER JOIN bu_tree ON bu_tree.id = buac.bu_id
                 GROUP BY buac.entry_id) bu
          ON (ac.entry_id = bu.entry_id)
    WHERE ac.approved
      AND (in_from_date IS NULL OR ac.transdate >= in_from_date)
      AND (in_from_date IS NULL OR in_to_date IS NULL
           OR NOT EXISTS (select 1 from yearend ye
                           where in_from_date < ye.transdate
                             and ye.transdate < in_to_date
                             and not ye.reversed
                             and ac.trans_id = ye.trans_id))
      AND (in_to_date IS NULL OR ac.transdate <= in_to_date)
      AND (in_business_units IS NULL OR in_business_units = '{}'
           OR in_tree(in_business_units, bu_ids))
      AND (in_to_date is null
           OR (ac.transdate <= in_to_date
               AND ac.trans_id IS DISTINCT FROM (SELECT trans_id
                                                   FROM yearend
                                                  WHERE transdate = in_to_date
                                                    AND NOT reversed)))
   GROUP BY ac.chart_id
     HAVING sum(ac.amount_bc) <> 0.00
 ),
hdr_balance AS (
   select ahd.id, sum(balance) as balance
     FROM acc_balance ab
    INNER JOIN account acc ON ab.id = acc.id
    INNER JOIN account_heading_descendant ahd
            ON acc.heading = ahd.descendant_id
    GROUP BY ahd.id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi, null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
    UNION
   SELECT am.id, am.accno, am.description, am.account_type, am.category,
          gifi_accno as gifi, gifi_description, am.contra, ab.balance, am.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: pnl__income_statement_cash(in_from_date date, in_to_date date, in_business_units integer[], in_language text)

Returns: SET OF financial_statement_line

Language: SQL

WITH acc_meta AS (
  SELECT a.id, a.accno,
         coalesce(at.description, a.description) as description,
         CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
         ELSE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
         END AS path,
         a.category, 'A'::char as account_type, contra, a.gifi_accno,
         gifi.description as gifi_description
     FROM account a
    INNER JOIN account_heading_tree aht on a.heading = aht.id
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
   WHERE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
                          IS NOT NULL
         -- legacy: earn_id not configured (yet)
         OR (NOT EXISTS (SELECT 1 FROM defaults
                         WHERE setting_key = 'earn_id'
                           AND value IS NOT NULL)
             AND category IN ('E', 'I'))
),
hdr_meta AS (
   SELECT aht.id, aht.accno,
          coalesce(at.description, aht.description) as description,
          CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
          ELSE array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
          END AS path,
          ahc.derived_category as category,
          'H'::char as account_type, 'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
    WHERE ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NOT NULL
           AND array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
                           IS NOT NULL)
          -- legacy: earn_id not configured; select headings belonging to
          --    selected accounts
          OR ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NULL
              AND EXISTS (SELECT 1 FROM acc_meta
                                  WHERE aht.id = ANY(acc_meta.path)))
),
acc_balance AS (
WITH RECURSIVE bu_tree (id, parent, path) AS (
      SELECT id, null, row(array[id])::tree_record FROM business_unit
       WHERE id = any(in_business_units)
      UNION ALL
      SELECT bu.id, parent, row((path).t || bu.id)::tree_record
        FROM business_unit bu
        JOIN bu_tree ON bu.parent_id = bu_tree.id
)
   SELECT ac.chart_id AS id, sum(ac.amount_bc * ca.portion) AS balance
     FROM acc_trans ac
     JOIN transactions gl ON ac.trans_id = gl.id AND gl.approved
     JOIN (SELECT id, sum(portion) as portion
             FROM cash_impact ca
            WHERE (in_from_date IS NULL OR ca.transdate >= in_from_date)
                  AND (in_to_date IS NULL OR ca.transdate <= in_to_date)
           GROUP BY id
          ) ca ON gl.id = ca.id
LEFT JOIN (select array_agg(path) as bu_ids, entry_id
             FROM business_unit_ac buac
             JOIN bu_tree ON bu_tree.id = buac.bu_id
         GROUP BY entry_id) bu
          ON (ac.entry_id = bu.entry_id)
    WHERE ac.approved
      AND (in_business_units = '{}'
           OR in_business_units is null or in_tree(in_business_units, bu_ids))
      AND (in_from_date IS NULL OR in_to_date IS NULL
           OR NOT EXISTS (select 1 from yearend ye
                           where in_from_date < ye.transdate
                             and ye.transdate < in_to_date
                             and not ye.reversed
                             and ac.trans_id = ye.trans_id))
      AND (in_to_date is null
           OR (ac.transdate <= in_to_date
               AND ac.trans_id IS DISTINCT FROM (SELECT trans_id
                                                   FROM yearend
                                                  WHERE transdate = in_to_date
                                                    AND NOT reversed)))
 GROUP BY ac.chart_id
   HAVING sum(ac.amount_bc * ca.portion) <> 0.00
 ),
hdr_balance AS (
   select ahd.id, sum(balance) as balance
     FROM acc_balance ab
    INNER JOIN account acc ON ab.id = acc.id
    INNER JOIN account_heading_descendant ahd
            ON acc.heading = ahd.descendant_id
    GROUP BY ahd.id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi, null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
   UNION
   SELECT am.id, am.accno, am.description, am.account_type, am.category,
          gifi_accno as gifi, gifi_description, am.contra, ab.balance, am.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: pnl__invoice(in_id integer, in_language text)

Returns: SET OF financial_statement_line

Language: SQL

WITH acc_meta AS (
  SELECT a.id, a.accno,
         coalesce(at.description, a.description) as description,
         CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
         ELSE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
         END AS path,
         a.category, 'A'::char as account_type, contra, a.gifi_accno,
         gifi.description as gifi_description
     FROM account a
    INNER JOIN account_heading_tree aht on a.heading = aht.id
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
   WHERE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
                          IS NOT NULL
         -- legacy: earn_id not configured (yet)
         OR (NOT EXISTS (SELECT 1 FROM defaults
                         WHERE setting_key = 'earn_id'
                           AND value IS NOT NULL)
             AND category IN ('E', 'I'))
),
hdr_meta AS (
   SELECT aht.id, aht.accno,
          coalesce(at.description, aht.description) as description,
          CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
          ELSE array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
          END AS path,
          ahc.derived_category as category,
          'H'::char as account_type, 'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
    WHERE ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NOT NULL
           AND array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
                           IS NOT NULL)
          -- legacy: earn_id not configured; select headings belonging to
          --    selected accounts
          OR ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NULL
              AND EXISTS (SELECT 1 FROM acc_meta
                                  WHERE aht.id = ANY(acc_meta.path)))
),
acc_balance AS (
SELECT ac.chart_id AS id, sum(ac.amount_bc) AS balance
  FROM acc_trans ac
 WHERE ac.approved AND ac.trans_id = in_id
 GROUP BY ac.chart_id
 ),
hdr_balance AS (
   select ahd.id, sum(balance) as balance
     FROM acc_balance ab
    INNER JOIN account acc ON ab.id = acc.id
    INNER JOIN account_heading_descendant ahd
            ON acc.heading = ahd.descendant_id
    GROUP BY ahd.id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi, null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
   UNION
   SELECT am.id, am.accno, am.description, am.account_type, am.category,
          gifi_accno as gifi, gifi_description, am.contra, ab.balance, am.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: pnl__product(in_from_date date, in_to_date date, in_parts_id integer, in_business_units integer[], in_language text)

Returns: SET OF financial_statement_line

Language: SQL

WITH acc_meta AS (
  SELECT a.id, a.accno,
         coalesce(at.description, a.description) as description,
         CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
         ELSE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
         END AS path,
         a.category, 'A'::char as account_type, contra, a.gifi_accno,
         gifi.description as gifi_description
     FROM account a
    INNER JOIN account_heading_tree aht on a.heading = aht.id
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
   WHERE array_splice_from((SELECT value::int FROM defaults
                             WHERE setting_key = 'earn_id'),aht.path)
                          IS NOT NULL
         -- legacy: earn_id not configured (yet)
         OR (NOT EXISTS (SELECT 1 FROM defaults
                         WHERE setting_key = 'earn_id'
                           AND value IS NOT NULL)
             AND category IN ('E', 'I'))
),
hdr_meta AS (
   SELECT aht.id, aht.accno,
          coalesce(at.description, aht.description) as description,
          CASE WHEN (SELECT value::int FROM defaults where setting_key = 'earn_id') IS NULL THEN aht.path
          ELSE array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
          END AS path,
          ahc.derived_category as category,
          'H'::char as account_type, 'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
    WHERE ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NOT NULL
           AND array_splice_from((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id'),aht.path)
                           IS NOT NULL)
          -- legacy: earn_id not configured; select headings belonging to
          --    selected accounts
          OR ((SELECT value::int FROM defaults
                              WHERE setting_key = 'earn_id') IS NULL
              AND EXISTS (SELECT 1 FROM acc_meta
                                  WHERE aht.id = ANY(acc_meta.path)))
),
acc_balance AS (
   WITH RECURSIVE bu_tree (id, parent, path) AS (
      SELECT id, null, row(array[id])::tree_record FROM business_unit
       WHERE id = any(in_business_units)
      UNION ALL
      SELECT bu.id, parent, row((path).t || bu.id)::tree_record
        FROM business_unit bu
        JOIN bu_tree ON bu.parent_id = bu_tree.id
   )
SELECT ac.chart_id AS id, sum(ac.amount_bc) AS balance
     FROM acc_trans ac
     JOIN invoice i ON i.id = ac.invoice_id
     JOIN account_link l ON l.account_id = ac.chart_id
     JOIN ar ON ar.id = ac.trans_id
LEFT JOIN (select array_agg(bu.path) as bu_ids, entry_id
             from business_unit_inv bui
             JOIN bu_tree bu ON bui.bu_id = bu.id
         GROUP BY entry_id) bui ON bui.entry_id = i.id
    WHERE i.parts_id = in_parts_id
          AND (ac.transdate >= in_from_date OR in_from_date IS NULL)
          AND (ac.transdate <= in_to_date OR in_to_date IS NULL)
          AND ar.approved
          AND l.description = 'IC_expense'
          AND (in_business_units is null or in_business_units = '{}' OR in_tree(in_business_units, bu_ids))
 GROUP BY ac.chart_id
   HAVING sum(ac.amount_bc) <> 0.00
    UNION
   SELECT ac.chart_id,
          sum(i.sellprice * i.qty * (1 - coalesce(i.discount, 0)))
     FROM invoice i
     JOIN acc_trans ac ON ac.invoice_id = i.id
     JOIN ar ON ar.id = ac.trans_id
LEFT JOIN (select array_agg(bu.path) as bu_ids, entry_id
             from business_unit_inv bui
             JOIN bu_tree bu ON bui.bu_id = bu.id
         GROUP BY entry_id) bui ON bui.entry_id = i.id
    WHERE i.parts_id = in_parts_id
          AND (ac.transdate >= in_from_date OR in_from_date IS NULL)
          AND (ac.transdate <= in_to_date OR in_to_date IS NULL)
          AND ar.approved
          AND (in_business_units is null or in_business_units = '{}' OR in_tree(in_business_units, bu_ids))
 GROUP BY ac.chart_id
   HAVING sum(i.sellprice * i.qty * (1 - coalesce(i.discount, 0))) <> 0.00
 ),
hdr_balance AS (
   select ahd.id, sum(balance) as balance
     FROM acc_balance ab
    INNER JOIN account acc ON ab.id = acc.id
    INNER JOIN account_heading_descendant ahd
            ON acc.heading = ahd.descendant_id
    GROUP BY ahd.id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi, null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
   UNION
   SELECT am.id, am.accno, am.description, am.account_type, am.category,
          gifi_accno as gifi, gifi_description, am.contra, ab.balance, am.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: preference__get(in_name text)

Returns: text

Language: SQL

Returns the value of the setting in the defaults table.

  SELECT "value" FROM user_preference
   WHERE "name" = in_name
         AND (user_id is null
              OR user_id = (select id from users
                             where username = session_user)
                             )
  order by user_id
  limit 1

Function: preference__set(in_name text, in_value text, in_global boolean)

Returns: boolean

Language: PLPGSQL

sets a value in the defaults thable and returns true if successful.

BEGIN
  IF in_global THEN
    IF in_value IS NULL THEN
      DELETE FROM user_preference
       WHERE "name" = in_name AND user_id IS NULL;

      RETURN true;
    END IF;

    INSERT INTO user_preference (user_id, "name", "value")
         VALUES (NULL, in_name, in_value)
      ON CONFLICT (coalesce(user_id, 0), "name")
    DO
      UPDATE SET "value" = in_value;

    RETURN true;
  END IF;

  IF in_value IS NULL THEN
     DELETE FROM user_preference
      WHERE user_id = (select id from users where username=SESSION_USER)
            AND "name" = in_name;
     RETURN true;
  END IF;

  INSERT INTO user_preference (user_id, "name", "value")
       VALUES ((select id from users
                 where username=SESSION_USER), in_name, in_value)
    ON CONFLICT (coalesce(user_id, 0), "name")
  DO
    UPDATE SET "value" = in_value;

  RETURN true;
END;

Function: prevent_closed_transactions()

Returns: trigger

Language: PLPGSQL

DECLARE t_end_date date;
BEGIN
SELECT max(end_date) into t_end_date FROM account_checkpoint;
IF new.transdate <= t_end_date THEN
    RAISE EXCEPTION 'Transaction entered into closed period.  Transdate: %',
                   new.transdate;
END IF;
RETURN new;
END;

Function: pricegroup__list()

Returns: SET OF pricegroup

Language: SQL

Returns an alphabetically ordered pricegroup list.

SELECT * FROM pricegroup ORDER BY pricegroup;

Function: pricegroup__search(in_pricegroup text)

Returns: SET OF pricegroup

Language: SQL

  SELECT * FROM pricegroup
   WHERE $1 IS NULL OR pricegroup ilike $1 || '%'
ORDER BY pricegroup;

Function: pricegroups__list()

Returns: SET OF pricegroup

Language: SQL

SELECT * FROM pricegroup;

Function: pricelist__delete(in_entry_id integer, in_credit_id integer)

Returns: boolean

Language: SQL

delete from partscustomer where entry_id = $1 and credit_id = $2;
delete from partsvendor where entry_id = $1 and credit_id = $2;
select true;

Function: pricelist__save(in_parts_id integer, in_credit_id integer, in_pricebreak numeric, in_price numeric, in_lead_time smallint, in_partnumber text, in_validfrom date, in_validto date, in_curr bpchar, in_entry_id integer, in_qty numeric)

Returns: eca__pricematrix

Language: PLPGSQL

DECLARE
   retval eca__pricematrix;
   t_insert bool;
   t_entity_class int;

BEGIN

t_insert := false;

SELECT entity_class INTO t_entity_class FROM entity_credit_account
  WHERE id = in_credit_id;

IF t_entity_class = 1 THEN -- VENDOR
    UPDATE partsvendor
       SET lastcost = in_price,
           leadtime = in_lead_time,
           partnumber = in_partnumber,
           curr = in_curr
     WHERE credit_id = in_credit_id AND entry_id = in_entry_id;

    IF NOT FOUND THEN
        INSERT INTO partsvendor
               (parts_id, credit_id, lastcost, leadtime, partnumber, curr)
        VALUES (in_parts_id, in_credit_id, in_price, in_leadtime::int2,
               in_partnumber, in_curr);
    END IF;

    SELECT pv.parts_id, p.partnumber, p.description, pv.credit_id, NULL, NULL,
           pv.lastcost, pv.leadtime::int, pv.partnumber, NULL, NULL, pv.curr,
           pv.entry_id
      INTO retval
      FROM partsvendor pv
      JOIN parts p ON p.id = pv.parts_id
     WHERE parts_id = in_parts_id and credit_id = in_credit_id;

    RETURN retval;

ELSIF t_entity_class = 2 THEN -- CUSTOMER
    UPDATE partscustomer
       SET pricebreak = in_pricebreak,
           sellprice  = in_price,
           validfrom  = in_validfrom,
           validto    = in_validto,
           qty        = in_qty,
           curr       = in_curr
     WHERE entry_id = in_entry_id and credit_id = in_credit_id;

    IF NOT FOUND THEN
        INSERT INTO partscustomer
               (parts_id, credit_id, sellprice, validfrom, validto, curr, qty)
        VALUES (in_parts_id, in_credit_id, in_price, in_validfrom, in_validto,
                in_curr, in_qty);

        t_insert := true;
    END IF;

    SELECT pc.parts_id, p.partnumber, p.description, pc.credit_id,
           pc.pricebreak, pc.sellprice, NULL, NULL, NULL, pc.validfrom,
           pc.validto, pc.curr, pc.entry_id, qty
      INTO retval
      FROM partscustomer pc
      JOIN parts p on pc.parts_id = p.id
     WHERE entry_id = CASE WHEN t_insert
                           THEN currval('partscustomer_entry_id_seq')
                           ELSE in_entry_id
                      END;

    RETURN retval;

ELSE

RAISE EXCEPTION 'No valid entity credit account found';

END IF;
END;

Function: pricematrix__for_customer(in_credit_id integer, in_parts_id integer, in_transdate date, in_qty numeric, in_currency text)

Returns: SET OF partscustomer

Language: SQL

   SELECT p.*
     FROM partscustomer p
     JOIN entity_credit_account eca ON eca.id = in_credit_id
LEFT JOIN pricegroup pg ON eca.pricegroup_id = pg.id
    WHERE p.parts_id = in_parts_id
        AND coalesce(p.validfrom, in_transdate) <=
            in_transdate
        AND coalesce(p.validto, in_transdate) >=
            in_transdate
        AND (p.credit_id = eca.id OR p.pricegroup_id = pg.id
             OR (p.credit_id is null and p.pricegroup_id is null))
        AND coalesce(qty, 0) <= coalesce(in_qty, 0)
        AND coalesce(p.curr, defaults_get_defaultcurrency()) =
            coalesce(in_currency, defaults_get_defaultcurrency())
  ORDER BY case WHEN p.credit_id = eca.id THEN 1
                WHEN p.pricegroup_id = pg.id THEN 2
                ELSE 3
            end asc, qty desc;


Function: pricematrix__for_vendor(in_credit_id integer, in_parts_id integer)

Returns: SET OF partsvendor

Language: SQL

SELECT *
  FROM partsvendor
 WHERE parts_id = in_parts_id
       AND credit_id = in_credit_id;

Function: reconciliation__account_list()

Returns: SET OF recon_accounts

Language: SQL

returns set of accounts set up for reconciliation. Currently we pull the account number and description from the account table.

    SELECT DISTINCT
        coa.accno || ' ' || coa.description as name,
        coa.accno, coa.id as id
    FROM account coa
         JOIN cr_coa_to_account cta ON cta.chart_id = coa.id
    ORDER BY coa.accno;

Function: reconciliation__add_entry(in_report_id integer, in_scn text, in_type text, in_date timestamp without time zone, in_amount numeric)

Returns: integer

Language: PLPGSQL

This function is used for automatically matching entries from an external source like a bank-produced csv file. This function is very sensitive to ordering of inputs. NULL or empty in_scn values MUST be submitted after meaningful scns. It is also highly recommended that within each category, one submits in order of amount. We should therefore wrap it in another function which can operate on a set, perhaps in 1.4.... It returns the ID of the inserted/updated entry


    DECLARE
        in_account int;
        la RECORD;
        t_errorcode INT;
        lid INT;
        in_count int;
        t_scn TEXT;
        t_uid int;
        t_prefix text;
        t_amount numeric;
    BEGIN
        SELECT CASE WHEN a.category in ('A', 'E') THEN in_amount * -1
                                                  ELSE in_amount
               END into t_amount
          FROM cr_report r JOIN account a ON r.chart_id = a.id
         WHERE r.id = in_report_id;

        SELECT value into t_prefix FROM defaults WHERE setting_key = 'check_prefix';

        t_uid := person__get_my_entity_id();
        IF t_uid IS NULL THEN
                t_uid = robot__get_my_entity_id();
        END IF;
        IF in_scn = '' THEN
                t_scn := NULL;
        ELSIF in_scn !~ '^[0-9]+$' THEN
                t_scn := in_scn;
        ELSE
                t_scn := t_prefix || in_scn;
        END IF;
        IF t_scn IS NOT NULL THEN
                -- could this be changed to update, if not found insert?
                SELECT count(*) INTO in_count FROM cr_report_line
                WHERE scn ilike t_scn AND report_id = in_report_id
                        AND their_balance = 0 AND post_date = in_date;

                IF in_count = 0 THEN
                        -- YLA - Where does our_balance comes from?
                        INSERT INTO cr_report_line
                        (report_id, scn, their_balance, our_balance, clear_time,
                                "user", trans_type)
                        VALUES
                        (in_report_id, t_scn, t_amount, 0, in_date, t_uid,
                                in_type)
                        RETURNING id INTO lid;
                ELSIF in_count = 1 THEN
                        SELECT id INTO lid FROM cr_report_line
                        WHERE t_scn = scn AND report_id = in_report_id
                                AND their_balance = 0 AND post_date = in_date;
                        UPDATE cr_report_line
                        SET their_balance = t_amount, clear_time = in_date,
                                cleared = true
                        WHERE id = lid;
                ELSE
                        SELECT count(*) INTO in_count FROM cr_report_line
                        WHERE t_scn ilike scn AND report_id = in_report_id
                                AND our_balance = t_amount and their_balance = 0
                                AND post_date = in_date;

                        IF in_count = 0 THEN -- no match among many of values
                                SELECT id INTO lid FROM cr_report_line
                                WHERE t_scn ilike scn
                                      AND report_id = in_report_id
                                      AND post_date = in_date
                                ORDER BY our_balance ASC limit 1;

                                UPDATE cr_report_line
                                SET their_balance = t_amount,
                                        clear_time = in_date,
                                        trans_type = in_type,
                                        cleared = true
                                WHERE id = lid;

                        ELSIF in_count = 1 THEN -- EXECT MATCH
                                SELECT id INTO lid FROM cr_report_line
                                WHERE t_scn = scn AND report_id = in_report_id
                                        AND our_balance = t_amount
                                        AND their_balance = 0
                                        AND post_date = in_date;
                                UPDATE cr_report_line
                                SET their_balance = t_amount,
                                        trans_type = in_type,
                                        clear_time = in_date,
                                        cleared = true
                                WHERE id = lid;
                        ELSE -- More than one match
                                SELECT id INTO lid FROM cr_report_line
                                WHERE t_scn ilike scn AND report_id = in_report_id
                                        AND our_balance = t_amount
                                        AND post_date = in_date
                                ORDER BY id ASC limit 1;

                                UPDATE cr_report_line
                                SET their_balance = t_amount,
                                        trans_type = in_type,
                                        cleared = true,
                                        clear_time = in_date
                                WHERE id = lid;

                        END IF;
                END IF;
        ELSE -- scn IS NULL, check on amount instead
                SELECT count(*) INTO in_count FROM cr_report_line
                WHERE report_id = in_report_id AND our_balance = t_amount
                        AND their_balance = 0 AND post_date = in_date
                        and scn NOT LIKE t_prefix || '%';

                IF in_count = 0 THEN -- no match
                        INSERT INTO cr_report_line
                        (report_id, scn, their_balance, our_balance, clear_time,
                        "user", trans_type)
                        VALUES
                        (in_report_id, t_scn, t_amount, 0, in_date, t_uid,
                        in_type)
                        RETURNING id INTO lid;
                ELSIF in_count = 1 THEN -- perfect match
                        SELECT id INTO lid FROM cr_report_line
                        WHERE report_id = in_report_id
                                AND our_balance = t_amount
                                AND their_balance = 0
                                AND post_date = in_date
                                AND in_scn NOT LIKE t_prefix || '%';
                        UPDATE cr_report_line SET their_balance = t_amount,
                                        trans_type = in_type,
                                        clear_time = in_date,
                                        cleared = true
                        WHERE id = lid;
                ELSE -- more than one match
                        SELECT min(id) INTO lid FROM cr_report_line
                        WHERE report_id = in_report_id AND our_balance = t_amount
                                AND their_balance = 0 AND post_date = in_date
                                AND scn NOT LIKE t_prefix || '%'
                        LIMIT 1;

                        UPDATE cr_report_line SET their_balance = t_amount,
                                        trans_type = in_type,
                                        clear_time = in_date,
                                        cleared = true
                        WHERE id = lid;

                END IF;
        END IF;
        return lid;

    END;

Function: reconciliation__check(in_end_date date, in_chart_id integer)

Returns: SET OF defaults

Language: SQL

Checks whether there are unapproved transactions on or before the end date and unapproved reports before the end date provided. Note that the check for unapproved transactions should include the end date, because having unapproved transactions on the end date influences the outcome of the balance to be verified by a report. Also note that the unapproved reports check can't include the end date, because that would mean that if a report were in progress while this function is being called, that report would be included in the count.

WITH unapproved_tx as (
     SELECT 'unapproved_transactions'::text, sum(c)::text
       FROM (SELECT count(*) as c FROM transactions
              WHERE approved IS FALSE AND transdate <= $1
      UNION  SELECT count(DISTINCT source) FROM acc_trans
              WHERE approved IS FALSE AND transdate <= $1 AND chart_id = $2
            ) tx
),
     unapproved_cr as (
     SELECT 'unapproved_reports'::text, count(*)::text
       FROM cr_report
      WHERE end_date < $1 AND approved IS NOT TRUE AND chart_id = $2
)
SELECT * FROM unapproved_tx
UNION SELECT * FROM unapproved_cr;

Function: reconciliation__delete_my_report(in_report_id integer)

Returns: boolean

Language: SQL

This function allows a user to delete his or her own unsubmitted, unapproved reconciliation reports only. This is designed to allow a user to back out of the reconciliation process without cluttering up the search results for others.

    DELETE FROM cr_report_line
     WHERE report_id = in_report_id
           AND report_id IN (SELECT id FROM cr_report
                              WHERE entered_username = SESSION_USER
                                    AND submitted IS NOT TRUE
                                    and approved IS NOT TRUE);
    DELETE FROM cr_report
     WHERE id = in_report_id AND entered_username = SESSION_USER
           AND submitted IS NOT TRUE AND approved IS NOT TRUE
    RETURNING TRUE;

Function: reconciliation__delete_unapproved(in_report_id integer)

Returns: boolean

Language: SQL

This function deletes any specified unapproved transaction.

    DELETE FROM cr_report_line
     WHERE report_id = in_report_id
           AND report_id IN (SELECT id FROM cr_report
                              WHERE approved IS NOT TRUE);
    DELETE FROM cr_report
     WHERE id = in_report_id AND approved IS NOT TRUE
    RETURNING TRUE;

Function: reconciliation__get_cleared_balance(in_chart_id integer, in_report_date date)

Returns: numeric

Language: SQL

Gets the cleared balance of the account specified by chart_id, as cleared by reports on and before in_report_date. Please note that the cleared balance amount as at a sperific date may differ from the value returned by this function, if transactions prior to in_report_date are cleared using reports on a date later than in_report_date. The returned value is specified in normal format (i.e. positive numbers for debits for asset and expense accounts, and positive numbers for credits in other accounts. Note that currently contra accounts will show negative balances.

  SELECT sum(ac.amount_bc) * CASE WHEN c.category in('A', 'E') THEN -1 ELSE 1 END
    FROM account c
           JOIN acc_trans ac ON ac.chart_id = c.id
           JOIN transactions g ON g.id = ac.trans_id
   WHERE g.approved
     AND c.id = in_chart_id
     AND ac.approved
     -- cleared using a report on or before in_report_date:
     AND EXISTS (select 1
                   from cr_report cr
                          join cr_report_line crl on cr.id = crl.report_id
                          join cr_report_line_links crll on crl.id = crll.report_line_id
                  where cr.approved
                    and cr.chart_id = in_chart_id
                    and cr.end_date <= in_report_date
                    and crl.cleared
                    and crll.entry_id = ac.entry_id)
    GROUP BY c.id, c.category;

Function: reconciliation__get_current_balance(in_account_id integer, in_date date)

Returns: numeric

Language: SQL

Gets the current balance of all approved transactions against a specific account. For asset and expense accounts this is the debit balance, for others this is the credit balance.

        SELECT CASE WHEN (select category FROM account WHERE id = in_account_id)
                        IN ('A', 'E') THEN sum(a.amount_bc) * -1
                ELSE sum(a.amount_bc) END
        FROM acc_trans a
        JOIN ( SELECT id FROM transactions WHERE approved IS true
             ) gl ON a.trans_id = gl.id
        WHERE a.approved IS TRUE
                AND a.chart_id = in_account_id
                AND a.transdate <= in_date;


Function: reconciliation__new_report_id(in_chart_id integer, in_total numeric, in_end_date date, in_recon_fx boolean)

Returns: integer

Language: SQL

Inserts creates a new report and returns the id.


    INSERT INTO cr_report(chart_id, their_total, end_date, recon_fx)
    values (in_chart_id, in_total, in_end_date, in_recon_fx);
    SELECT currval('cr_report_id_seq')::int;


Function: reconciliation__pending_transactions(in_report_id integer, in_their_total numeric)

Returns: integer

Language: PLPGSQL

Ensures that the list of pending transactions in the report is up to date.


    DECLARE
        t_row            record;
        t_recon_fx       BOOL;
        t_chart_id       integer;
        t_end_date       date;
        t_report_line_id integer;
        t_uid int;
    BEGIN
        SELECT end_date, recon_fx, chart_id
         INTO t_end_date, t_recon_fx, t_chart_id
         FROM cr_report
        WHERE id = in_report_id;

        SELECT entity_id INTO t_uid
        FROM users
        WHERE username = CURRENT_USER;

        /*

        Approach in 4 steps:
         1. Identify lines to be added *somewhere*
            That is: all lines before the reconcilation date which
            are not yet part of any other reconciliation; lines come
            from two sources: payment transactions and others (the second
            are usually GL transactions)
         2. Identify lines part of a payment
            Lines in this category are grouped by payment and added as a
            single reconciliation line, irrespective of the number of lines
            identified, *unless* lines have explicitly different 'Source'
            values - which is weird and unexpected, but possible when the
            user sets a specific value on each payment line separately - in
            which case, the lines in the payment will be grouped by the value
            of the Source field
         3. Identify non-payment lines that adjust payments
            When a payment has been entered wrongly or the bank has withheld
            transaction fees, the payment of the invoice does not correspond
            to the actual amount on the bank statement - meaning adjustment
            is required; GL transactions can be used to enter adjustments by
            listing the same date and the same source as used for the payment
            transaction. The lines in this category will be added as an
            adjustment to the existing (coming from the payment) reconciliation
            line
         4. Remaining lines added as new lines, either by source (if they
            have one) or as individual ones.
            Note that the lines in this category - by logical reasoning - can
            **not** be payments lines, because those were handled in step 2.
            Also note that it's not an option to lump all lines without a source
            into a single line, because that way all lines without a Source
            would end up as a single reconciliation line, while unknowing users
            are expected to post GL lines without Source numbers; to help these
            users, we present lines from non-payment (GL) transactions as
            individual lines
         */

        -- step 1: identify lines to be added somehow
        create temporary table lines_to_be_added as
        select entry_id, null::int as report_line_id
         from acc_trans ac
         join transactions tr on ac.trans_id = tr.id
         where tr.approved
               and ac.approved
               and not ac.cleared
               and ac.chart_id = t_chart_id
               and ac.transdate <= t_end_date
               and not exists (select 1 from cr_report_line_links rll
                                        join cr_report_line rl
                                          on rl.id = rll.report_line_id
                                where ac.entry_id = rll.entry_id
                                      and rl.report_id = in_report_id);

        -- step 2: add lines part of a payment one line per payment
        for t_row in
           select payment_id, array_agg(ac.entry_id) as entries,
                  sum(case when t_recon_fx then amount_tc
                           else amount_bc end) as our_balance,
                  payment_date, source
             from payment_links pl
             join acc_trans ac on pl.entry_id = ac.entry_id
             join payment p on p.id = pl.payment_id
            where ac.chart_id = t_chart_id
                  and pl.entry_id in (select entry_id from lines_to_be_added)
           group by payment_id, payment_date, source
        loop
            insert into cr_report_line (report_id, scn, their_balance,
                                       our_balance, post_date, "user")
            values (in_report_id, t_row.source, 0, t_row.our_balance, t_row.payment_date, t_uid)
           returning id into t_report_line_id;

           update lines_to_be_added
              set report_line_id = t_report_line_id
            where entry_id = any(t_row.entries);
        end loop;

        -- step 3: add new ledger lines to existing recon lines
        with matched_entries as (
           update lines_to_be_added la
              set report_line_id =
                     (select id
                        from cr_report_line rl
                        join acc_trans ac on ac.source = rl.scn
                                             and ac.transdate = rl.post_date
                       where la.entry_id = ac.entry_id
                             and rl.report_id = in_report_id
                             -- exclude 'scn' values associated with more than one
                             -- report line: the gl line can't be unambiguously
                             -- combined with a payment; hence it can't serve as
                             -- a correction...
                             and not exists (select 1 from cr_report_line rli
                                              where rl.post_date = rli.post_date
                                                    and rl.report_id = rli.report_id
                                                    and rli.scn = rl.scn
                                                    and rl.id <> rli.id)
                             and not exists (select 1 from payment_links pl
                                              where pl.entry_id = ac.entry_id))
            where la.report_line_id is null
           returning report_line_id, entry_id
        )
        update cr_report_line rl
           set our_balance = (select sum(case when t_recon_fx then ac.amount_tc
                                              else ac.amount_bc end)
                                from (
                                     -- lines that were already there
                                     select report_line_id, entry_id
                                       from cr_report_line_links
                                     union all
                                     -- lines identified in step (2)
                                     -- (does not include 'matched_entries', because
                                     -- the default transaction isolation [read
                                     -- committed] freezes our view at query start,
                                     -- which means lines_to_be_added isn't updated
                                     -- by 'matched_entries', as we see it)
                                     select report_line_id, entry_id
                                       from lines_to_be_added
                                     union all
                                     -- lines identidief in this step
                                     select report_line_id, entry_id
                                       from matched_entries
                                ) rll
                                join acc_trans ac on rll.entry_id = ac.entry_id
                                where rl.id = rll.report_line_id)
         where rl.id in (select report_line_id from matched_entries);

        -- step 4: add new lines not part of payments
        for t_row in
           select source, array_agg(entry_id) as entries,
                  sum(case when t_recon_fx then amount_tc
                           else amount_bc end) as our_balance,
                  transdate
             from acc_trans ac
            where ac.chart_id = t_chart_id
                  and ac.entry_id in (select entry_id from lines_to_be_added
                                       where report_line_id is null)
           group by source, transdate,
                    case when source is null then entry_id else null end
        loop
           insert into cr_report_line (report_id, scn, their_balance,
                                      our_balance, post_date, "user")
            values (in_report_id, t_row.source, 0,
                      t_row.our_balance, t_row.transdate, t_uid)
           returning id into t_report_line_id;

           update lines_to_be_added
              set report_line_id = t_report_line_id
            where entry_id = any(t_row.entries);
        end loop;

        perform * from lines_to_be_added where report_line_id is null;
        if found then
          drop table lines_to_be_added;
          raise exception 'Unhandled entries %', (select array_agg(entry_id) from lines_to_be_added where report_line_id is null)::int[];
        end if;

        insert into cr_report_line_links (report_line_id, entry_id)
        select report_line_id, entry_id from lines_to_be_added;

        drop table lines_to_be_added;

        UPDATE cr_report
           set updated = date_trunc('second', now()),
               their_total = coalesce(in_their_total, their_total)
         where id = in_report_id;

        RETURN in_report_id;
    END;

Function: reconciliation__previous_report_date(in_chart_id integer, in_end_date date)

Returns: SET OF cr_report

Language: SQL

Returns the submitted reconciliation report before in_end_date for the in_chart_id account

                SELECT r.* FROM cr_report r
                  JOIN account c ON r.chart_id = c.id
                 WHERE in_end_date > end_date
                   AND in_chart_id = chart_id
                   AND submitted
                   AND NOT r.deleted
                 ORDER BY end_date DESC
                 LIMIT 1

Function: reconciliation__reject_set(in_report_id integer)

Returns: boolean

Language: SQL

Sets the reconciliation report identified by in_report_id as not approved, providing it is not already submitted. Used in the reconciliation workflow to reject approval.

     UPDATE cr_report set submitted = false
      WHERE id = in_report_id
            AND approved is not true
     RETURNING true;

Function: reconciliation__report_approve(in_report_id integer)

Returns: integer

Language: PLPGSQL

Marks the report approved and marks all cleared transactions in it cleared.


    BEGIN
        UPDATE cr_report SET approved = 't',
                approved_by = person__get_my_entity_id(),
                approved_username = SESSION_USER
         WHERE id = in_report_id;
        IF NOT FOUND THEN
            RAISE EXCEPTION 'No report at %.', $1;
        END IF;

        UPDATE acc_trans ac
           SET cleared = TRUE
         WHERE exists (select 1
                         from cr_report_line_links rll
                         join cr_report_line rl on rll.report_line_id = rl.id
                        where rll.entry_id = ac.entry_id
                              and rl.cleared
                              and rl.report_id = in_report_id);
        return 1;
    END;


Function: reconciliation__report_details(in_report_id integer)

Returns: SET OF cr_report_line

Language: SQL

Returns the details of the report.


                select * from cr_report_line where report_id = in_report_id
                order by scn, post_date

Function: reconciliation__report_details_payee(in_report_id integer)

Returns: SET OF recon_payee

Language: SQL

                select * from recon_payee where report_id = in_report_id
                order by scn, post_date

Function: reconciliation__report_details_payee_with_days(in_report_id integer, in_end_date date)

Returns: SET OF recon_payee_days

Language: PLPGSQL

Pulls the payee information for the reconciliation report.

BEGIN
            RETURN QUERY
                SELECT rp.id,
                        CASE WHEN in_end_date IS NULL THEN NULL
                        ELSE      in_end_date - clear_time
                        END AS d
                FROM recon_payee rp
                WHERE rp.report_id = in_report_id;

RETURN;
END;

Function: reconciliation__report_summary(in_report_id integer)

Returns: cr_report

Language: SQL

        select * from cr_report where id = in_report_id;

Function: reconciliation__save_set(in_report_id integer, in_line_ids integer[])

Returns: boolean

Language: SQL

Sets which lines of the report are cleared.

        UPDATE cr_report_line SET cleared = (id = ANY(in_line_ids))
         WHERE report_id = in_report_id;

        SELECT TRUE;

Function: reconciliation__search(in_date_from date, in_date_to date, in_balance_from numeric, in_balance_to numeric, in_account_id integer, in_submitted boolean, in_approved boolean)

Returns: SET OF cr_report

Language: SQL

Searches for reconciliation reports. NULLs match all values. in_date_to and in_date_from give a range of reports. All other inputs are exact matches.

                SELECT r.* FROM cr_report r
                JOIN account c ON (r.chart_id = c.id)
                WHERE
                        (in_date_from IS NULL OR in_date_from <= end_date) and
                        (in_date_to IS NULL OR in_date_to >= end_date) AND
                        (in_balance_from IS NULL
                                or in_balance_from <= their_total ) AND
                        (in_balance_to IS NULL
                                OR in_balance_to >= their_total) AND
                        (in_account_id IS NULL OR in_account_id = chart_id) AND
                        (in_submitted IS NULL or in_submitted = submitted) AND
                        (in_approved IS NULL OR in_approved = approved) AND
                        (r.deleted IS FALSE)
                ORDER BY c.accno, end_date, their_total

Function: reconciliation__submit_set(in_report_id integer)

Returns: boolean

Language: PLPGSQL

Submits a reconciliation report for approval. in_line_ids is used to specify which report lines are cleared, finalizing the report.

BEGIN
        UPDATE cr_report set submitted = true where id = in_report_id;

        RETURN FOUND;
END;

Function: report__aa_outstanding(in_entity_class integer, in_account_id integer, in_entity_name text, in_meta_number text, in_employee_id integer, in_business_units integer[], in_ship_via text, in_on_hold boolean, in_from_date date, in_to_date date, in_partnumber text, in_parts_id integer)

Returns: SET OF aa_transactions_line

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$

SELECT null::int as id, null::bool as invoice, entity_id, meta_number::text,
       entity_name, null::date as transdate, count(*)::text as invnumber,
       null::text as ordnumber, null::text as ponumber, curr,
       sum(amount) as amount, sum(netamount) as netamount, sum(tax) as tax,
       sum(paid) as paid, sum(due) as due, max(last_payment) as last_payment,
       null::date as duedate, null::text as notes,
       null::text as salesperson, null::text as manager,
       null::text as shipping_point, null::text as ship_via,
       null::text[] as business_units
  FROM report__aa_outstanding_details($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
    $11, $12)
 GROUP BY meta_number, entity_name, entity_id, curr
$sql$
USING in_entity_class, in_account_id, in_entity_name, in_meta_number,
 in_employee_id, in_business_units, in_ship_via, in_on_hold,
 in_from_date, in_to_date, in_partnumber, in_parts_id;
END

Function: report__aa_outstanding_details(in_entity_class integer, in_account_id integer, in_entity_name text, in_meta_number text, in_employee_id integer, in_business_units integer[], in_ship_via text, in_on_hold boolean, in_from_date date, in_to_date date, in_partnumber text, in_parts_id integer)

Returns: SET OF aa_transactions_line

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$

SELECT a.id, a.invoice, eeca.id, eca.meta_number::text, eeca.name, a.transdate,
       a.invnumber, a.ordnumber, a.ponumber, a.curr, a.amount_bc, a.netamount_bc,
       a.amount_bc - a.netamount_bc as tax,
       a.amount_bc - p.due as paid, p.due, p.last_payment, a.duedate, a.notes,
       ee.name, me.name, a.shippingpoint, a.shipvia,
       '{}'::text[] as business_units -- TODO
  FROM (select id, transdate, invnumber, curr, amount_bc, netamount_bc, duedate,
               notes, person_id, entity_credit_account, invoice,
               shippingpoint, shipvia, ordnumber, ponumber, description,
               on_hold, force_closed
          FROM ar
         WHERE $1 = 2 and approved
         UNION
        SELECT id, transdate, invnumber, curr, amount_bc, netamount_bc, duedate,
               notes, person_id, entity_credit_account, invoice,
               shippingpoint, shipvia, ordnumber, ponumber, description,
               on_hold, force_closed
          FROM ap
         WHERE $1 = 1 and approved) a
  LEFT
  JOIN (SELECT trans_id, sum(amount_bc) *
               CASE WHEN $1 = 1 THEN 1 ELSE -1 END AS due,
               max(transdate) as last_payment
          FROM acc_trans ac
          JOIN account_link al ON ac.chart_id = al.account_id
         WHERE approved AND al.description IN ('AR', 'AP')
               AND ($10 is null or transdate <= $10)
      GROUP BY trans_id) p ON p.trans_id = a.id
  JOIN entity_credit_account eca ON a.entity_credit_account = eca.id
  JOIN entity eeca ON eca.entity_id = eeca.id
  LEFT
  JOIN entity_employee ON entity_employee.entity_id = a.person_id
  LEFT
  JOIN entity ee ON entity_employee.entity_id = ee.id
  LEFT
  JOIN entity me ON entity_employee.manager_id = me.id
 WHERE ($2 IS NULL
          OR EXISTS (select 1 FROM acc_trans
                      WHERE trans_id = a.id and chart_id = $2))
       AND ($3 IS NULL
           OR eeca.name @@ plainto_tsquery($3)
           OR eeca.name ilike '%' || $3 || '%')
       AND ($4 IS NULL
          OR eca.meta_number ilike $4 || '%')
       AND ($5 IS NULL OR ee.id = $5)
       AND ($7 IS NULL
          OR a.shipvia @@ plainto_tsquery($7))
       AND ($8 IS NULL OR $8 = a.on_hold)
       AND ($9 IS NULL OR a.transdate >= $9)
       AND ($10 IS NULL OR a.transdate <= $10)
       AND p.due::numeric(100,2) <> 0
       AND a.force_closed IS NOT TRUE
       AND ($11 IS NULL
          OR EXISTS(SELECT 1 FROM invoice inv
                      JOIN parts ON inv.parts_id = parts.id
                     WHERE inv.trans_id = a.id))
       AND ($12 IS NULL
          OR EXISTS (select 1 FROM invoice
                      WHERE parts_id = $12 AND trans_id = a.id))
$sql$
USING in_entity_class, in_account_id, in_entity_name, in_meta_number,
 in_employee_id, in_business_units, in_ship_via, in_on_hold,
 in_from_date, in_to_date, in_partnumber, in_parts_id;
END

Function: report__aa_transactions(in_entity_class integer, in_account_id integer, in_entity_name text, in_meta_number text, in_employee_id integer, in_manager_id integer, in_invnumber text, in_ordnumber text, in_ponumber text, in_source text, in_description text, in_notes text, in_shipvia text, in_from_date date, in_to_date date, in_on_hold boolean, in_taxable boolean, in_tax_account_id integer, in_open boolean, in_closed boolean, in_approved boolean, in_voided boolean, in_partnumber text)

Returns: SET OF aa_transactions_line

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$

SELECT a.id, a.invoice, eeca.id, eca.meta_number::text, eeca.name,
       a.transdate, a.invnumber, a.ordnumber, a.ponumber, a.curr,
       a.amount_bc as amount, a.netamount_bc as netamount,
       a.amount_bc - a.netamount_bc as tax, a.amount_bc - p.due,
       p.due, p.last_payment,
       a.duedate, a.notes,
       eee.name as employee, mee.name as manager, a.shippingpoint,
       a.shipvia, '{}'::text[]

  FROM (select id, transdate, invnumber, curr, amount_bc, netamount_bc, duedate,
               notes,
               person_id, entity_credit_account, invoice, shippingpoint,
               shipvia, ordnumber, ponumber, description, on_hold, force_closed
          FROM ar
         WHERE $1 = 2
               and ($21 is null or ($21 = approved))
         UNION
        SELECT id, transdate, invnumber, curr, amount_bc, netamount_bc, duedate,
               notes,
               person_id, entity_credit_account, invoice, shippingpoint,
               shipvia, ordnumber, ponumber, description, on_hold, force_closed
          FROM ap
         WHERE $1 = 1
               and ($21 is null or ($21 = approved))) a
  LEFT
  JOIN (select sum(amount_bc) * case when $1 = 1 THEN 1 ELSE -1 END
               as due, trans_id, max(transdate) as last_payment
          FROM acc_trans ac
          JOIN account_link l ON ac.chart_id = l.account_id
         WHERE l.description IN ('AR', 'AP')
      GROUP BY ac.trans_id
       ) p ON p.trans_id = a.id
  LEFT
  JOIN entity_employee ee ON ee.entity_id = a.person_id
  LEFT
  JOIN entity eee ON eee.id = ee.entity_id
  JOIN entity_credit_account eca ON a.entity_credit_account = eca.id
  JOIN entity eeca ON eca.entity_id = eeca.id
  LEFT
  JOIN entity mee ON ee.manager_id = mee.id
 WHERE ($2 IS NULL OR
       EXISTS (select * from acc_trans
               where trans_id = a.id AND chart_id = $2))
       AND ($3 IS NULL
           OR eeca.name ilike '%' || $3 || '%'
           OR eeca.name @@ plainto_tsquery($3))
       AND ($4 IS NULL OR eca.meta_number ilike $4)
       AND ($5 = ee.entity_id OR $5 IS NULL)
       AND ($6 = mee.id OR $6 IS NULL)
       AND (a.invnumber ilike $7 || '%' OR $7 IS NULL)
       AND (a.ordnumber ilike $8 || '%' OR $8 IS NULL)
       AND (a.ponumber ilike $9 || '%' OR $9 IS NULL)
       AND ($10 IS NULL OR
           EXISTS (
              SELECT * from acc_trans where trans_id = a.id
                     AND source ilike $10 || '%'
           ))
       AND ($11 IS NULL
              OR a.description @@ plainto_tsquery($11))
       AND ($12 IS NULL OR a.notes @@ plainto_tsquery($12))
       AND ($13 IS NULL OR a.shipvia @@ plainto_tsquery($13))
       AND ($14 IS NULL OR a.transdate >= $14)
       AND ($15 IS NULL OR a.transdate <= $15)
       AND ($16 IS NULL OR $16 = a.on_hold)
       AND ($17 IS NULL
            OR ($17
              AND ($18 IS NULL
                 OR EXISTS (SELECT 1 FROM acc_trans
                             WHERE trans_id = a.id
                                   AND chart_id = $18)
            ))
            OR (NOT $17
                  AND NOT EXISTS (SELECT 1
                                    FROM acc_trans ac
                                    JOIN account_link al
                                      ON al.account_id = ac.chart_id
                                   WHERE ac.trans_id = a.id
                                         AND al.description ilike '%tax'))
            )
            AND ( -- open/closed handling
              ($19 IS TRUE AND ( a.force_closed IS NOT TRUE AND
                 abs(p.due) > 0.005))                  -- threshold due to
                                                       -- impossibility to
                                                       -- collect below -CT
               OR ($20 IS TRUE AND ( a.force_closed IS NOT TRUE AND
                 abs(p.due) > 0.005) IS NOT TRUE)
            )
            AND (
              $22 IS NULL
              OR $22 IS NOT DISTINCT FROM (EXISTS (SELECT 1
                                                     FROM transactions t
                                                    WHERE t.approved
                                                      AND t.reversing = a.id))
            )
            AND  -- by partnumber
              ($23 IS NULL
                 OR a.id IN (
                    select i.trans_id
                      FROM invoice i JOIN parts p ON i.parts_id = p.id
                     WHERE p.partnumber = $23))
$sql$
USING in_entity_class, in_account_id, in_entity_name, in_meta_number,
 in_employee_id, in_manager_id, in_invnumber, in_ordnumber,
 in_ponumber, in_source, in_description, in_notes,
 in_shipvia, in_from_date, in_to_date, in_on_hold,
 in_taxable, in_tax_account_id, in_open, in_closed,
 in_approved, in_voided, in_partnumber;
END

Function: report__balance_sheet(in_to_date date, in_language text, in_timing text)

Returns: SET OF financial_statement_line

Language: SQL

This produces a balance sheet and the paths (acount numbers) of all headings necessary; output is generated in the language requested, or in the users default language if not available.

WITH chkpoint_date AS (
   SELECT coalesce(max(end_date),
                   (select min(transdate)-'1 day'::interval
                     from acc_trans)) AS end_date
     FROM account_checkpoint
    WHERE (in_to_date IS NULL
           OR (end_date < in_to_date)
           OR ((end_date = in_to_date)
               and (in_timing is null or in_timing='ultimo')
               and not exists (select 1 from yearend
                                where transdate = in_to_date
                                      and not reversed)))
),
hdr_meta AS (
   SELECT aht.id, aht.accno, coalesce(at.description, aht.description) as description,
          aht.path,
          ahc.derived_category as category, 'H'::char as account_type,
          'f'::boolean as contra
     FROM account_heading_tree aht
    INNER JOIN account_heading_derived_category ahc ON aht.id = ahc.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation
                WHERE language_code =
                       coalesce(in_language, preference__get('language'))) at
              ON aht.id = at.trans_id
     WHERE array_endswith((SELECT value::int FROM defaults
                            WHERE setting_key = 'earn_id'), aht.path)
           -- legacy (no earn_id) returns all headers
           OR (NOT aht.path @> ARRAY[(SELECT value::int FROM defaults
                                      WHERE setting_key = 'earn_id')])
),
acc_meta AS (
  SELECT a.id, a.accno, coalesce(at.description, a.description) as description,
         a.category, 'A'::char as account_type, contra,
         a.gifi_accno, gifi.description as gifi_description
     FROM account a
     LEFT JOIN gifi ON a.gifi_accno = gifi.accno
     LEFT JOIN (SELECT trans_id, description
                  FROM account_translation
                 WHERE language_code =
                        coalesce(in_language, preference__get('language'))) at
               ON a.id = at.trans_id
),
acc_balance AS (
  SELECT b.id,
         case when a.heading_negative_balance is not null
           then (
             case when ((b.balance > 0 and a.category = 'A')
                        or (b.balance < 0 and a.category = 'L'))
               then a.heading_negative_balance
             else a.heading
             end)
         else a.heading
         end as heading,
         case when a.heading_negative_balance is not null
           then (
             case when ((b.balance > 0 and a.category = 'A')
                        or (b.balance < 0 and a.category = 'L'))
               then nht.path
             else aht.path
             end)
         else aht.path
         end as path,
         case when a.heading_negative_balance is not null
           then (
             case when (b.balance > 0 and a.category = 'A')
               then 'L'
             when (b.balance < 0 and a.category = 'L')
               then 'A'
             else a.category
             end)
         else a.category
         end as category,
         balance
    FROM (
      SELECT bal.id, sum(bal.balance) as balance
        FROM (
          SELECT account_id as id, amount_bc as balance
            FROM account_checkpoint
           WHERE end_date = (select end_date from chkpoint_date)

           UNION ALL
          SELECT ac.chart_id as id, ac.amount_bc as balance
            FROM acc_trans ac
                   JOIN transactions t ON t.approved AND t.id = ac.trans_id
           WHERE t.approved AND
                 ac.transdate > (select end_date from chkpoint_date) AND
                 (in_to_date is null
                 OR ((in_timing is null OR in_timing='ultimo')
                     AND ac.transdate <= in_to_date
                     AND ac.trans_id IS DISTINCT FROM (SELECT trans_id
                                                         FROM yearend
                                                        WHERE transdate = in_to_date
                                                          AND NOT reversed))
                                                          OR (in_timing='primo'
                                                              AND ac.transdate < in_to_date))
        ) bal
       GROUP BY bal.id
      HAVING sum(bal.balance) <> 0.00
    ) b
           INNER JOIN account a
               ON b.id = a.id
           INNER JOIN account_heading_tree aht on a.heading = aht.id
           LEFT JOIN account_heading_tree nht on a.heading_negative_balance = nht.id
     WHERE array_endswith((SELECT value::int FROM defaults
                            WHERE setting_key = 'earn_id'), aht.path)
           -- legacy (no earn_id) returns all accounts; bug?
           OR (NOT aht.path @> ARRAY[(SELECT value::int FROM defaults
                                      WHERE setting_key = 'earn_id')])
),
hdr_balance AS (
   select id, sum(balance) as balance
     FROM (
       select UNNEST(path) as id, balance from acc_balance ab
     ) a
    GROUP BY id
)
   SELECT hm.id, hm.accno, hm.description, hm.account_type, hm.category,
          null::text as gifi_accno,
          null::text as gifi_description, hm.contra,
          hb.balance, hm.path
     FROM hdr_meta hm
    INNER JOIN hdr_balance hb ON hm.id = hb.id
   UNION
   SELECT am.id, am.accno, am.description, am.account_type, ab.category,
          am.gifi_accno, am.gifi_description, am.contra,
          ab.balance, ab.path
     FROM acc_meta am
    INNER JOIN acc_balance ab on am.id = ab.id

Function: report__coa()

Returns: SET OF coa_entry

Language: SQL

WITH ac (chart_id, amount_bc) AS (
     SELECT chart_id, sum(amount_bc)
       FROM acc_trans
       JOIN (select id, approved from transactions) gl
             ON gl.id = acc_trans.trans_id
      WHERE acc_trans.approved and gl.approved
      GROUP BY chart_id
),
l(account_id, link) AS (
     SELECT account_id, array_to_string(array_agg(description), ':')
       FROM account_link
   GROUP BY account_id
),
hh(parent_id) AS (
     SELECT DISTINCT parent_id
       FROM account_heading
),
ha(heading) AS (
     SELECT heading
       FROM account
),
eca(account_id) AS (
    SELECT DISTINCT discount_account_id
      FROM entity_credit_account
    UNION
    SELECT DISTINCT ar_ap_account_id
      FROM entity_credit_account
    UNION
    SELECT DISTINCT cash_account_id
      FROM entity_credit_account
),
ta(account_id) AS (
    SELECT chart_id
      FROM eca_tax
  GROUP BY 1
)
SELECT a.id, a.is_heading, a.accno, a.description, a.gifi_accno,
       CASE WHEN sum(ac.amount_bc) < 0 THEN sum(amount_bc) * -1
            ELSE null::numeric END,
       CASE WHEN sum(ac.amount_bc) > 0 THEN sum(amount_bc)
            ELSE null::numeric END,
       count(ac.*)+count(hh.*)+count(ha.*)+count(eca.*)+count(ta.*), l.link
  FROM (SELECT id, heading, false as is_heading, accno, description, gifi_accno
          FROM account
         UNION
        SELECT id, parent_id, true, accno, description, null::text
          FROM account_heading) a

 LEFT JOIN ac ON ac.chart_id = a.id AND not a.is_heading
 LEFT JOIN l ON l.account_id = a.id AND NOT a.is_heading
 LEFT JOIN hh ON hh.parent_id = a.id AND a.is_heading
 LEFT JOIN ha ON ha.heading = a.id AND a.is_heading
 LEFT JOIN eca ON eca.account_id = a.id AND NOT a.is_heading
 LEFT JOIN ta ON ta.account_id = a.id AND NOT a.is_heading
  GROUP BY a.id, a.is_heading, a.accno, a.description, a.gifi_accno, l.link
  ORDER BY a.accno;


Function: report__general_balance(in_from_date date, in_to_date date)

Returns: SET OF general_balance_line

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT a.id, a.accno, a.description,
      sum(CASE WHEN ac.transdate < $1 THEN abs(amount_bc) ELSE null END),
      sum(CASE WHEN ac.transdate >= $1 AND ac.amount_bc < 0
               THEN ac.amount_bc * -1 ELSE null END),
      SUM(CASE WHEN ac.transdate >= $1 AND ac.amount_bc > 0
               THEN ac.amount_bc ELSE null END),
      SUM(ABS(ac.amount_bc))
 FROM account a
 LEFT
 JOIN acc_trans ac ON ac.chart_id = a.id
 LEFT
 JOIN (select id, approved from transactions) gl
       ON ac.trans_id = gl.id
WHERE gl.approved and ac.approved
      and ac.transdate <= $2
GROUP BY a.id, a.accno, a.description
ORDER BY a.accno
$sql$
USING in_from_date, in_to_date;
END

Function: report__gl(in_reference text, in_accno text, in_category bpchar, in_source text, in_memo text, in_description text, in_from_date date, in_to_date date, in_approved boolean, in_voided boolean, in_from_amount numeric, in_to_amount numeric, in_business_units integer[])

Returns: SET OF gl_report_item

Language: PLPGSQL

DECLARE
         retval gl_report_item;
         t_balance numeric;
         t_chart_id int;
BEGIN

IF in_from_date IS NULL THEN
   t_balance := 0;
ELSIF in_accno IS NOT NULL THEN
   SELECT id INTO t_chart_id FROM account WHERE accno  = in_accno;
   t_balance :=
      account__obtain_balance((in_from_date - '1 day'::interval)::date,
                                       (select id from account
                                         where accno = in_accno));
ELSE
   t_balance := null;
END IF;

FOR retval IN
       WITH RECURSIVE bu_tree (id, path) AS (
            SELECT id, ARRAY[id]::int[] AS path
              FROM business_unit
             WHERE parent_id is null
            UNION
            SELECT bu.id, array_append(bu_tree.path, bu.id)
              FROM business_unit bu
              JOIN bu_tree ON bu_tree.id = bu.parent_id
            )
       SELECT g.id, g.type, g.invoice, g.reference, g.eca_name, g.description, ac.transdate,
              ac.source, ac.amount_bc, ac.curr, ac.amount_tc, c.accno, c.gifi_accno,
              ac.cleared, ac.memo, c.description AS accname,
              ac.chart_id, ac.entry_id,
              sum(ac.amount_bc) over (order by ac.transdate, ac.trans_id,
                                            c.accno, ac.entry_id)
                + t_balance
                as running_balance,
              array_agg(ARRAY[bac.class_id, bac.bu_id])
         FROM (select id, 'gl' as type, false as invoice, reference,
                      null::text as eca_name, description, approved
                 FROM gl
               UNION
               SELECT ar.id, 'ar', invoice, invnumber, e.name, ar.description, approved
                 FROM ar
                 JOIN entity_credit_account eca ON ar.entity_credit_account
                      = eca.id
                 JOIN entity e ON e.id = eca.entity_id
               UNION
               SELECT ap.id, 'ap', invoice, invnumber, e.name, ap.description, approved
                 FROM ap
                 JOIN entity_credit_account eca ON ap.entity_credit_account
                      = eca.id
                 JOIN entity e ON e.id = eca.entity_id) g
         JOIN acc_trans ac ON ac.trans_id = g.id
         JOIN account c ON ac.chart_id = c.id
    LEFT JOIN business_unit_ac bac ON ac.entry_id = bac.entry_id
    LEFT JOIN bu_tree ON bac.bu_id = bu_tree.id
        WHERE (g.reference ilike in_reference || '%' or in_reference is null)
              AND (c.accno = in_accno OR in_accno IS NULL)
              AND (ac.source ilike '%' || in_source || '%'
                   OR in_source is null)
              AND (ac.memo ilike '%' || in_memo || '%' OR in_memo is null)
             AND (in_description IS NULL OR
                  g.description
                  @@
                  plainto_tsquery(get_default_lang()::regconfig, in_description))
              AND (transdate BETWEEN in_from_date AND in_to_date
                   OR (transdate >= in_from_date AND  in_to_date IS NULL)
                   OR (transdate <= in_to_date AND in_from_date IS NULL)
                   OR (in_to_date IS NULL AND in_from_date IS NULL))
              AND (in_approved is null
                   or (in_approved is true
                       and g.approved is true
                       AND ac.approved is true)
                   or (in_approved is false
                       and (g.approved is false
                            or ac.approved is false)))
              AND (in_voided is null
                   or in_voided is not distinct from (exists (select 1
                                                                from transactions t
                                                               where t.approved
                                                                 and t.reversing = g.id)))
              AND (in_from_amount IS NULL
                   OR abs(ac.amount_bc) >= in_from_amount)
              AND (in_to_amount IS NULL
                   OR abs(ac.amount_bc) <= in_to_amount)
              AND (in_category = c.category OR in_category IS NULL)
     GROUP BY g.id, g.type, g.invoice, g.reference, g.eca_name, g.description, ac.transdate,
              ac.source, ac.amount_bc, c.accno, c.gifi_accno,
              ac.cleared, ac.memo, c.description,
              ac.chart_id, ac.entry_id, ac.trans_id
       HAVING in_business_units is null
              or in_business_units <@ compound_array(bu_tree.path)
     ORDER BY ac.transdate, ac.trans_id, c.accno, ac.entry_id
LOOP
   RETURN NEXT retval;
END LOOP;
END;

Function: report__incoming_cogs_line(in_date_from date, in_date_to date, in_partnumber text, in_parts_description text)

Returns: SET OF incoming_lot_cogs_line

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT i.id, a.id, a.invnumber, a.transdate, i.parts_id, p.partnumber,
       i.description, i.qty * -1, i.allocated, p.onhand,
       i.sellprice, i.qty * i.sellprice * -1, i.allocated * i.sellprice
  FROM ap a
  JOIN invoice i ON a.id = i.trans_id
  JOIN parts p ON i.parts_id = p.id
 WHERE p.income_accno_id IS NOT NULL AND p.expense_accno_id IS NOT NULL
       AND (a.transdate >= $1 OR $1 IS NULL)
       AND (a.transdate <= $2 OR $2 IS NULL)
       AND (p.partnumber like $3 || '%' OR $3 IS NULL)
       AND (p.description @@ plainto_tsquery($4)
            OR p.description LIKE '%' || $4 || '%'
            OR $4 IS NULL)
 ORDER BY p.partnumber, a.invnumber
$sql$
USING in_date_from, in_date_to, in_partnumber, in_parts_description;
END

Function: report__invoice_aging_detail(in_entity_id integer, in_entity_class integer, in_credit_id integer, in_accno text, in_to_date date, in_business_units integer[], in_use_duedate boolean, in_name_part text)

Returns: SET OF report_aging_item

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
     WITH RECURSIVE bu_tree (id, path) AS (
                SELECT id, ARRAY[id]::int[] AS path
                  FROM business_unit
                 WHERE id = any($6)
                       OR $6 IS NULL
                 UNION
                SELECT bu.id, array_append(bu_tree.path, bu.id)
                  FROM business_unit bu
                  JOIN bu_tree ON bu_tree.id = bu.parent_id
                       )
                SELECT c.entity_id, c.meta_number::text, e.name,
                       e.name as contact_name, c.language_code::text as "language",
                       a.invnumber, a.transdate, a.ordnumber,
                       a.ponumber, a.notes,
                       CASE WHEN a.age/30 = 0
                                 THEN (a.sign * sum(ac.amount_bc))
                            ELSE 0 END
                            as c0,
                       CASE WHEN a.age/30 = 1
                                 THEN (a.sign * sum(ac.amount_bc))
                            ELSE 0 END
                            as c30,
                       CASE WHEN a.age/30 = 2
                            THEN (a.sign * sum(ac.amount_bc))
                            ELSE 0 END
                            as c60,
                       CASE WHEN a.age/30 > 2
                            THEN (a.sign * sum(ac.amount_bc))
                            ELSE 0 END
                            as c90,
                       a.duedate, a.id, a.curr,
                       null::numeric AS exchangerate,
                        (SELECT array_agg(ARRAY[p.partnumber,
                                        i.description, i.qty::text])
                                FROM parts p
                                JOIN invoice i ON (i.parts_id = p.id)
                                WHERE i.trans_id = a.id) AS line_items,
                   (coalesce($5, now())::date - a.transdate) as age
                  FROM (select id, invnumber, ordnumber, amount_bc, duedate,
                               curr, ponumber, notes, entity_credit_account,
                               -1 AS sign, transdate, force_closed,
                               CASE WHEN $7
                                    THEN coalesce($5, now())::date
                                         - duedate
                                    ELSE coalesce($5, now())::date
                                         - transdate
                               END as age
                          FROM ar
                         WHERE $2 = 2
                         UNION
                        SELECT id, invnumber, ordnumber, amount_bc, duedate,
                               curr, ponumber, notes, entity_credit_account,
                               1 as sign, transdate, force_closed,
                               CASE WHEN $7
                                    THEN coalesce($5, now())::date
                                         - duedate
                                    ELSE coalesce($5, now())::date
                                         - transdate
                               END as age
                          FROM ap
                         WHERE $2 = 1) a
                  JOIN acc_trans ac ON ac.trans_id = a.id
                  JOIN account acc ON ac.chart_id = acc.id
                  JOIN account_link acl ON acl.account_id = acc.id
                       AND (($2 = 1
                              AND acl.description = 'AP')
                           OR ($2 = 2
                              AND acl.description = 'AR'))
                  JOIN entity_credit_account c
                       ON a.entity_credit_account = c.id
                  JOIN entity e ON (e.id = c.entity_id)
             LEFT JOIN business_unit_ac buac ON ac.entry_id = buac.entry_id
             LEFT JOIN bu_tree ON buac.bu_id = bu_tree.id
             LEFT JOIN entity_to_location e2l
                       ON e.id = e2l.entity_id
                       AND e2l.location_class = 3
             LEFT JOIN location l ON l.id = e2l.location_id
             LEFT JOIN country ON (country.id = l.country_id)
                 WHERE (e.id = $1 OR $1 IS NULL)
                       AND ($3 IS NULL or c.id = $3)
                       AND ($4 IS NULL or acc.accno = $4)
                       AND a.force_closed IS NOT TRUE
                       AND ($8 IS NULL
                            OR e.name like '%' || $8 || '%')
              GROUP BY c.entity_id, c.meta_number, e.name, c.language_code,
                       l.line_one, l.line_two, l.line_three,
                       l.city, l.state, l.mail_code, country.name,
                       a.invnumber, a.transdate, a.ordnumber,
                       a.ponumber, a.notes, a.amount_bc, a.sign,
                       a.duedate, a.id, a.curr, a.age
                HAVING ($6 is null
                        or $6 <@ compound_array(bu_tree.path))
                       AND sum(ac.amount_bc::numeric(20,2)) <> 0
              ORDER BY entity_id, meta_number, curr, transdate, invnumber
$sql$
USING in_entity_id, in_entity_class, in_credit_id, in_accno, in_to_date,
 in_business_units, in_use_duedate, in_name_part;
END

Function: report__invoice_aging_summary(in_entity_id integer, in_entity_class integer, in_credit_id integer, in_accno text, in_to_date date, in_business_units integer[], in_use_duedate boolean, in_name_part text)

Returns: SET OF report_aging_item

Language: PLPGSQL

BEGIN
RETURN QUERY EXECUTE $sql$
SELECT entity_id, account_number, name, contact_name, "language",
       null::text, null::date,
       null::text, null::text, null::text,
       sum(c0), sum(c30), sum(c60), sum(c90), null::date, null::int, curr,
       null::numeric, null::text[], null::int
  FROM report__invoice_aging_detail($1, $2, $3, $4, $5, $6, $7, $8)
 GROUP BY entity_id, account_number, name, contact_name, "language", curr
 ORDER BY account_number
$sql$
USING in_entity_id, in_entity_class, in_credit_id, in_accno, in_to_date,
 in_business_units, in_use_duedate, in_name_part;
END

Function: report_trial_balance(in_datefrom date, in_dateto date, in_department_id integer, in_project_id integer, in_gifi boolean)

Returns: SET OF trial_balance_line

Language: PLPGSQL

This is a simple routine to generate trial balances for the full company, for a project, or for a department.

DECLARE out_row trial_balance_line;
BEGIN
        IF in_department_id IS NULL THEN
                FOR out_row IN
                        SELECT c.id, c.accno, c.description,
                                SUM(CASE WHEN ac.transdate < in_datefrom
                                              AND c.category IN ('I', 'L', 'Q')
                                    THEN ac.amount_bc
                                    ELSE ac.amount_bc * -1
                                    END),
                                SUM(CASE WHEN ac.transdate >= in_date_from
                                              AND ac.amount_bc > 0
                                    THEN ac.amount_bc
                                    ELSE 0 END),
                                SUM(CASE WHEN ac.transdate >= in_date_from
                                              AND ac.amount_bc < 0
                                    THEN ac.amount_bc
                                    ELSE 0 END) * -1,
                                SUM(CASE WHEN ac.transdate >= in_date_from
                                        AND c.charttype IN ('I')
                                    THEN ac.amount_bc
                                    WHEN ac.transdate >= in_date_from
                                              AND c.category IN ('I', 'L', 'Q')
                                    THEN ac.amount_bc
                                    ELSE ac.amount_bc * -1
                                    END)
                                FROM acc_trans ac
                                JOIN (select id, approved FROM transactions) g
                                        ON (g.id = ac.trans_id)
                                JOIN account c ON (c.id = ac.chart_id)
                                WHERE ac.transdate <= in_date_to
                                        AND ac.approved AND g.approved
                                        AND (in_project_id IS NULL
                                                OR in_project_id = ac.project_id)
                                GROUP BY c.id, c.accno, c.description
                                ORDER BY c.accno

                LOOP
                        RETURN NEXT out_row;
                END LOOP;
        ELSE
                FOR out_row IN
                        SELECT 1
                LOOP
                        RETURN NEXT out_row;
                END LOOP;
        END IF;
END;

Function: robot__get(in_entity_id integer)

Returns: robot_entity

Language: SQL

SELECT e.id, e.control_code, e.name, e.country_id, c.name,
       p.first_name, p.middle_name, p.last_name, e.entity_class
  FROM entity e
  JOIN country c ON c.id = e.country_id
  JOIN robot p ON p.entity_id = e.id
 WHERE e.id = $1;

Function: robot__get_by_cc(in_control_code text)

Returns: robot_entity

Language: SQL

SELECT e.id, e.control_code, e.name, e.country_id, c.name,
       p.first_name, p.middle_name, p.last_name, e.entity_class
  FROM entity e
  JOIN country c ON c.id = e.country_id
  JOIN robot p ON p.entity_id = e.id
 WHERE e.control_code = $1;

Function: robot__get_my_entity_id()

Returns: integer

Language: SQL

Returns the entity_id of the current, logged in user.

        SELECT entity_id from users where username = SESSION_USER OR username = 'Migrator';

Function: robot__list_notes(in_entity_id integer)

Returns: SET OF entity_note

Language: SQL

Returns a list of notes attached to a robot.

                SELECT *
                FROM entity_note
                WHERE ref_key = in_entity_id
                ORDER BY created

Function: robot__save(in_entity_id integer, in_first_name text, in_middle_name text, in_last_name text, in_country_id integer)

Returns: integer

Language: PLPGSQL

Saves the robot with the information specified. Returns the entity_id of the record saved.


    DECLARE
        e_id int;
        e entity;
        loc location;
        l_id int;
        p_id int;
    BEGIN

    select * into e from entity where id = in_entity_id and entity_class = 3;
    e_id := in_entity_id;

    IF FOUND THEN
        UPDATE entity
           SET name = in_first_name || ' ' || in_last_name,
               country_id = in_country_id
         WHERE id = in_entity_id;
    ELSE
        INSERT INTO entity (name, entity_class, country_id)
        values (in_first_name || ' ' || in_last_name, 3, in_country_id);
        e_id := currval('entity_id_seq');

    END IF;


    UPDATE robot SET
            first_name = in_first_name,
            last_name = in_last_name,
            middle_name = in_middle_name
    WHERE
            entity_id = in_entity_id;
    IF FOUND THEN
        RETURN in_entity_id;
    ELSE
        -- Do an insert

        INSERT INTO robot (first_name, last_name, entity_id)
        VALUES (in_first_name, in_last_name, e_id);

        RETURN e_id;

    END IF;
END;

Function: save_taxform(in_country_code integer, in_taxform_name text)

Returns: boolean

Language: SQL

Saves tax form information. Returns true or raises exception.

        INSERT INTO country_tax_form(country_id, form_name)
        values (in_country_code, in_taxform_name);

        SELECT true;

Function: sequence__delete(in_label text)

Returns: lsmb_sequence

Language: SQL

DELETE FROM lsmb_sequence where label = $1;

SELECT NULL::lsmb_sequence;

Function: sequence__get(in_label text)

Returns: lsmb_sequence

Language: SQL

SELECT * FROM lsmb_sequence WHERE label = $1;

Function: sequence__increment(in_label text)

Returns: defaults

Language: PLPGSQL

DECLARE t_seq lsmb_sequence;
        new_value text;
        retval    defaults;
BEGIN

   SELECT * INTO t_seq FROM lsmb_sequence WHERE label = in_label
          FOR UPDATE;

   new_value := setting__increment_base(t_seq.sequence);

   UPDATE lsmb_sequence SET sequence = new_value WHERE label = in_label;

   retval := row(t_seq.setting_key, t_seq.prefix || t_seq.sequence || t_seq.suffix);
   return retval;

END;

Function: sequence__list()

Returns: SET OF lsmb_sequence

Language: SQL

SELECT * FROM lsmb_sequence order by label;

Function: sequence__list_by_key(in_setting_key text)

Returns: SET OF lsmb_sequence

Language: SQL

SELECT * FROM lsmb_sequence where setting_key = $1 order by label;

Function: sequence__save(in_label text, in_setting_key text, in_prefix text, in_suffix text, in_sequence text, in_accept_input boolean)

Returns: lsmb_sequence

Language: PLPGSQL

DECLARE retval lsmb_sequence;
BEGIN
UPDATE lsmb_sequence
   SET prefix = coalesce(in_prefix, ''),
       suffix = coalesce(in_suffix, ''),
       sequence = coalesce(in_sequence, '1'),
       setting_key = in_setting_key,
       accept_input = coalesce(in_accept_input, false)
 WHERE label = in_label;

IF FOUND THEN
   retval := sequence__get(in_label);
   RETURN retval;
END IF;

INSERT INTO lsmb_sequence(label, setting_key, prefix, suffix, sequence,
                          accept_input)
VALUES (in_label, in_setting_key,
        coalesce(in_prefix, ''),
        coalesce(in_suffix, ''),
        coalesce(in_sequence, '1'),
        coalesce(in_accept_input, false)
);

retval := sequence__get(in_label);
RETURN retval;

end;

Function: session_check(in_session_id integer, in_token text)

Returns: session

Language: PLPGSQL

Returns a session row. If no session exists, it returns null

DECLARE out_row session%ROWTYPE;
BEGIN
        DELETE FROM session
         WHERE last_used < now() - coalesce((SELECT value FROM defaults
                                              WHERE setting_key = 'session_timeout')::interval,
                                            '90 minutes'::interval);
        UPDATE session
           SET last_used = now()
         WHERE session_id = in_session_id
               AND token = in_token
               AND users_id = (select id from users
                        where username = SESSION_USER)
        RETURNING * INTO out_row;

        -- if there is no matching row, return NULL values
        -- note: there is also a failing match when the token doesn't
        -- match; which might mean a replay attack!
        RETURN out_row;
END;

Function: session_create()

Returns: session

Language: PLPGSQL

Creates a session for the current session user and returns it. When no user is found by name of the session user, returns a row with NULL values.

DECLARE
    out_row session%ROWTYPE;
    users_id int;
BEGIN
    SELECT id INTO users_id
      FROM users WHERE username = SESSION_USER;

    IF NOT FOUND THEN
       RETURN out_row;
    END IF;

    INSERT INTO session (users_id, token, last_used)
    VALUES (users_id, md5(random()::text), now())
    RETURNING * INTO out_row;

    RETURN out_row;
END;

Function: session_delete(in_session_id integer)

Returns: boolean

Language: PLPGSQL

Removes the session with the id given in the argument. Returns TRUE on success. Note: only users owning a session may delete that session.

BEGIN
   DELETE FROM session
     WHERE session_id = in_session_id
       AND users_id = (select id from users
                       where username = SESSION_USER);

   RETURN FOUND;
END;

Function: setting__increment_base(in_raw_var text)

Returns: character varying

Language: PLPGSQL

declare raw_value VARCHAR;
       base_value VARCHAR;
       increment  INTEGER;
       inc_length INTEGER;
       new_value VARCHAR;
begin
    raw_value := in_raw_var;
    base_value := substring(raw_value from
                                '(' || E'\\' || 'd*)(' || E'\\' || 'D*|<'
                                    || E'\\' || '?lsmb [^<>] ' || E'\\'
                                    || '?>)*$');
    IF base_value like '0%' THEN
         increment := base_value::integer + 1;
         inc_length := char_length(increment::text);
         new_value := overlay(base_value placing increment::varchar
                              from (char_length(base_value)
                                    - inc_length + 1)
                              for inc_length);
    ELSE
         new_value := base_value::integer + 1;
    END IF;
    return regexp_replace(raw_value, base_value, new_value);
end;

Function: setting__set(in_setting_key character varying, in_value character varying)

Returns: boolean

Language: PLPGSQL

sets a value in the defaults thable and returns true if successful.

BEGIN
        UPDATE defaults SET value = in_value WHERE setting_key = in_setting_key;
        IF NOT FOUND THEN
             INSERT INTO defaults (setting_key, value)
                  VALUES (in_setting_key, in_value);
        END IF;
        RETURN TRUE;
END;

Function: setting_get(in_key character varying)

Returns: defaults

Language: SQL

Returns the value of the setting in the defaults table.

SELECT * FROM defaults WHERE setting_key = $1;

Function: setting_get_default_accounts()

Returns: SET OF defaults

Language: SQL

Returns a set of settings for default accounts.

                SELECT * FROM defaults
                WHERE setting_key like '%accno_id'
                ORDER BY setting_key

Function: setting_increment(in_key character varying)

Returns: character varying

Language: SQL

This function takes a value for a sequence in the defaults table and increments it. Leading zeroes and spaces are preserved as placeholders. Currently <?lsmb parsing is not supported in this routine though it may be added at a later date.

        UPDATE defaults SET value = setting__increment_base(value)
        WHERE setting_key = in_key
  RETURNING (
    -- return the old value
    -- note: only works at the 'read committed' isolation or lower
    select value from defaults where setting_key = in_key
  );


Function: sic__list()

Returns: SET OF sic

Language: SQL

SELECT * FROM sic ORDER BY code;

Function: tax_form__get(in_form_id integer)

Returns: country_tax_form

Language: SQL

Retrieves specified tax form information from the database.

SELECT * FROM country_tax_form where id = $1;

Function: tax_form__list_all()

Returns: SET OF tax_form_list

Language: SQL

Returns a set of all tax forms, ordered by country_id and id

SELECT tf.id, tf.form_name, c.name, tf.default_reportable, tf.is_accrual
  FROM country c
  JOIN country_tax_form tf ON c.id = tf.country_id
 ORDER BY country_id, form_name;

Function: tax_form__list_ext()

Returns: SET OF taxform_list

Language: SQL

Returns a list of tax forms with an added field, country_name, to specify the name of the country.

SELECT t.id, t.form_name, t.country_id, c.name, t.default_reportable,
       t.is_accrual
  FROM country_tax_form t
  JOIN country c ON c.id = t.country_id
 ORDER BY c.name, t.form_name;

Function: tax_form__save(in_id integer, in_country_id integer, in_form_name text, in_default_reportable boolean, in_is_accrual boolean)

Returns: integer

Language: PLPGSQL

Saves tax form information to the database.

BEGIN
        UPDATE country_tax_form
           SET country_id = in_country_id,
               form_name =in_form_name,
               default_reportable = coalesce(in_default_reportable,false),
               is_accrual = coalesce(in_is_accrual, false)
         WHERE id = in_id;

        IF FOUND THEN
           RETURN in_id;
        END IF;

        insert into country_tax_form(country_id,form_name, default_reportable,
                                     is_accrual)
        values (in_country_id, in_form_name,
                coalesce(in_default_reportable, false),
                coalesce(in_is_accrual, false));

        RETURN currval('country_tax_form_id_seq');
END;

Function: tax_form_details_report(in_tax_form_id integer, in_from_date date, in_to_date date, in_meta_number text)

Returns: SET OF tax_form_report_detail_item

Language: SQL

This provides a list of invoices and transactions that a report hits. This is intended to allow an organization to adjust what is reported on the 1099 before printing them.

              SELECT entity_credit_account.id,
                     company.legal_name, company.entity_id,
                     entity_credit_account.entity_class, entity.control_code,
                     entity_credit_account.meta_number,
                     company.tax_id, company.sales_tax_id,
                     sum(CASE WHEN gl.amount_bc = 0 then 0
                              when relation = 'acc_trans'
                          THEN ac.reportable_amount_bc * pmt.amount_bc
                                / gl.amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 then 0
                              WHEN relation = 'invoice'
                          THEN ac.reportable_amount_bc * pmt.amount_bc
                               / gl.amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ar' THEN -1 else 1 end),
                     SUM(CASE WHEN gl.amount_bc = 0 THEN 0
                              ELSE ac.reportable_amount_bc * pmt.amount_bc
                               / gl.amount_bc
                              END
                         * CASE WHEN gl.class = 'ap' THEN -1 else 1 end
                         * CASE WHEN relation = 'invoice' THEN -1 ELSE 1 END),
                     gl.invnumber, gl.duedate::text, gl.id
                FROM (select id, entity_credit_account, invnumber, duedate,
                             amount_bc, transdate, 'ar' as class
                        FROM ar
                       UNION
                      select id, entity_credit_account, invnumber, duedate,
                             amount_bc, transdate, 'ap' as class
                        FROM ap
                     ) gl
                JOIN (select trans_id, 'acc_trans' as relation,
                             sum(amount_bc) as amount_bc,
                             sum(case when atf.reportable then amount_bc else 0
                                 end) as reportable_amount_bc
                        FROM  acc_trans
                   LEFT JOIN ac_tax_form atf
                          ON (acc_trans.entry_id = atf.entry_id)
                       GROUP BY trans_id
                       UNION
                      select trans_id, 'invoice' as relation,
                             sum(sellprice * qty) as amount_bc,
                             sum(case when itf.reportable
                                      then sellprice * qty
                                      else 0
                                 end) as reportable_amount_bc
                        FROM invoice
                   LEFT JOIN invoice_tax_form itf
                          ON (invoice.id = itf.invoice_id)
                       GROUP BY trans_id
                     ) ac ON (ac.trans_id = gl.id)
                JOIN entity_credit_account ON (gl.entity_credit_account = entity_credit_account.id)
                JOIN entity ON (entity.id = entity_credit_account.entity_id)
                JOIN company ON (entity.id = company.entity_id)
                JOIN country_tax_form ON (entity_credit_account.taxform_id = country_tax_form.id)
                JOIN (SELECT ac.trans_id, sum(ac.amount_bc) as amount_bc,
                             array_agg(entry_id) as entry_ids,
                             array_agg(chart_id) as chart_ids,
                             count(*) as num
                        FROM acc_trans ac
                       where chart_id in (select account_id
                                            from account_link
                                           where description like '%paid')
                          AND transdate BETWEEN in_from_date AND in_to_date
                     group by ac.trans_id
                     ) pmt ON  (pmt.trans_id = gl.id)
                WHERE country_tax_form.id = in_tax_form_id AND meta_number = in_meta_number
                GROUP BY legal_name, meta_number, company.entity_id, company.tax_id, company.sales_tax_id, entity_credit_account.entity_class, entity.control_code, gl.invnumber, gl.duedate, gl.id, entity_credit_account.id

Function: tax_form_details_report_accrual(in_tax_form_id integer, in_from_date date, in_to_date date, in_meta_number text)

Returns: SET OF tax_form_report_detail_item

Language: SQL

This provides a list of invoices and transactions that a report hits. This is intended to allow an organization to adjust what is reported on the 1099 before printing them.

              SELECT entity_credit_account.id,
                     company.legal_name, company.entity_id,
                     entity_credit_account.entity_class, entity.control_code,
                     entity_credit_account.meta_number,
                     company.tax_id, company.sales_tax_id,
                     sum(CASE WHEN gl.amount_bc = 0 then 0
                              when relation = 'acc_trans'
                          THEN ac.reportable_amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 then 0
                              WHEN relation = 'invoice'
                          THEN ac.reportable_amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ar' THEN -1 else 1 end),
                     SUM(CASE WHEN gl.amount_bc = 0
                                   THEN 0
                              ELSE ac.reportable_amount_bc
                              END
                         * CASE WHEN gl.class = 'ap' THEN -1 else 1 end
                         * CASE WHEN relation = 'invoice' THEN -1 ELSE 1 END),
                     gl.invnumber, gl.duedate::text, gl.id
                FROM (select id, entity_credit_account, invnumber, duedate,
                             amount_bc, transdate, 'ar' as class
                        FROM ar
                       WHERE transdate BETWEEN in_from_date AND in_to_date
                       UNION
                      select id, entity_credit_account, invnumber, duedate,
                             amount_bc, transdate, 'ap' as class
                        FROM ap
                       WHERE transdate BETWEEN in_from_date AND in_to_date
                     ) gl
                JOIN (select trans_id, 'acc_trans' as relation,
                             sum(amount_bc) as amount_bc,
                             sum(case when atf.reportable then amount_bc else 0
                                 end) as reportable_amount_bc
                        FROM  acc_trans
                   LEFT JOIN ac_tax_form atf
                          ON (acc_trans.entry_id = atf.entry_id)
                       GROUP BY trans_id
                       UNION
                      select trans_id, 'invoice' as relation,
                             sum(sellprice * qty) as amount_bc,
                             sum(case when itf.reportable
                                      then sellprice * qty
                                      else 0
                                 end) as reportable_amount_bc
                        FROM invoice
                   LEFT JOIN invoice_tax_form itf
                          ON (invoice.id = itf.invoice_id)
                       GROUP BY trans_id
                     ) ac ON (ac.trans_id = gl.id)
                JOIN entity_credit_account ON (gl.entity_credit_account = entity_credit_account.id)
                JOIN entity ON (entity.id = entity_credit_account.entity_id)
                JOIN company ON (entity.id = company.entity_id)
                JOIN country_tax_form ON (entity_credit_account.taxform_id = country_tax_form.id)
                WHERE country_tax_form.id = in_tax_form_id AND meta_number = in_meta_number
                GROUP BY legal_name, meta_number, company.entity_id, company.tax_id, company.sales_tax_id, entity_credit_account.entity_class, entity.control_code, gl.invnumber, gl.duedate, gl.id, entity_credit_account.id

Function: tax_form_summary_report(in_tax_form_id integer, in_from_date date, in_to_date date)

Returns: SET OF tax_form_report_item

Language: SQL

This provides the total reportable value per vendor. As per 1099 forms, these are cash-basis documents and show amounts paid.

              SELECT entity_credit_account.id,
                     company.legal_name, company.entity_id,
                     entity_credit_account.entity_class, entity.control_code,
                     entity_credit_account.meta_number,
                     company.tax_id, company.sales_tax_id,
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                              WHEN relation = 'acc_trans'
                          THEN ac.reportable_amount_bc * pmt.amount_bc
                                / gl.amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                              WHEN relation = 'invoice'
                          THEN ac.reportable_amount_bc * pmt.amount_bc
                               / gl.amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ar' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                          ELSE ac.reportable_amount_bc * pmt.amount_bc
                                / gl.amount_bc
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end
                      * CASE WHEN ac.relation = 'invoice' then -1 else 1 end)

                FROM (select id, transdate, entity_credit_account, invoice,
                             amount_bc, 'ar' as class FROM ar
                       UNION
                      select id, transdate, entity_credit_account, invoice,
                              amount_bc, 'ap' as class from ap
                     ) gl
               JOIN (select trans_id, 'acc_trans' as relation,
                             sum(amount_bc) as amount_bc,
                             sum(case when atf.reportable then amount_bc else 0
                                 end) as reportable_amount_bc
                        FROM  acc_trans
                    LEFT JOIN ac_tax_form atf
                          ON (acc_trans.entry_id = atf.entry_id)
                       GROUP BY trans_id
                       UNION
                      select trans_id, 'invoice' as relation,
                             sum(sellprice * qty) as amount_bc,
                             sum(case when itf.reportable
                                      then sellprice * qty
                                      else 0
                                 end) as reportable_amount_bc
                        FROM invoice
                    LEFT JOIN invoice_tax_form itf
                          ON (invoice.id = itf.invoice_id)
                       GROUP BY trans_id
                     ) ac ON (ac.trans_id = gl.id
                             AND ((gl.invoice is true and ac.relation='invoice')
                                  OR (gl.invoice is false
                                     and ac.relation='acc_trans')))
                JOIN (SELECT ac.trans_id, sum(ac.amount_bc) as amount_bc,
                             array_agg(entry_id) as entry_ids,
                             array_agg(chart_id) as chart_ids,
                             count(*) as num
                        FROM acc_trans ac
                       where chart_id in (select account_id
                                            from account_link
                                           where description like '%paid')
                          AND transdate BETWEEN in_from_date AND in_to_date
                     group by ac.trans_id
                     ) pmt ON  (pmt.trans_id = gl.id)
                JOIN entity_credit_account
                  ON (gl.entity_credit_account = entity_credit_account.id)
                JOIN entity ON (entity.id = entity_credit_account.entity_id)
                JOIN company ON (entity.id = company.entity_id)
                JOIN country_tax_form ON (entity_credit_account.taxform_id = country_tax_form.id)
               WHERE country_tax_form.id = in_tax_form_id
             GROUP BY legal_name, meta_number, company.tax_id, company.sales_tax_id, company.entity_id, entity_credit_account.entity_class, entity.control_code, entity_credit_account.id

Function: tax_form_summary_report_accrual(in_tax_form_id integer, in_from_date date, in_to_date date)

Returns: SET OF tax_form_report_item

Language: SQL

This provides the total reportable value per vendor. As per 1099 forms, these are cash-basis documents and show amounts paid.

              SELECT entity_credit_account.id,
                     company.legal_name, company.entity_id,
                     entity_credit_account.entity_class, entity.control_code,
                     entity_credit_account.meta_number,
                     company.tax_id, company.sales_tax_id,
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                              WHEN relation = 'acc_trans'
                          THEN ac.reportable_amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                              WHEN relation = 'invoice'
                          THEN ac.reportable_amount_bc
                          ELSE 0
                      END * CASE WHEN gl.class = 'ar' THEN -1 else 1 end),
                     sum(CASE WHEN gl.amount_bc = 0 THEN 0
                          ELSE ac.reportable_amount_bc
                      END * CASE WHEN gl.class = 'ap' THEN -1 else 1 end
                      * CASE WHEN ac.relation = 'invoice' then -1 else 1 end)

                FROM (select id, transdate, entity_credit_account, invoice,
                             amount_bc, 'ar' as class FROM ar
                       WHERE transdate BETWEEN in_from_date AND in_to_date
                       UNION
                      select id, transdate, entity_credit_account, invoice,
                              amount_bc, 'ap' as class from ap
                       WHERE transdate BETWEEN in_from_date AND in_to_date
                     ) gl
               JOIN (select trans_id, 'acc_trans' as relation,
                             sum(amount_bc) as amount_bc,
                             sum(case when atf.reportable then amount_bc else 0
                                 end) as reportable_amount_bc
                        FROM  acc_trans
                    LEFT JOIN ac_tax_form atf
                          ON (acc_trans.entry_id = atf.entry_id)
                       GROUP BY trans_id
                       UNION
                      select trans_id, 'invoice' as relation,
                             sum(sellprice * qty) as amount_bc,
                             sum(case when itf.reportable
                                      then sellprice * qty
                                      else 0
                                 end) as reportable_amount_bc
                        FROM invoice
                    LEFT JOIN invoice_tax_form itf
                          ON (invoice.id = itf.invoice_id)
                       GROUP BY trans_id
                     ) ac ON (ac.trans_id = gl.id
                             AND ((gl.invoice is true and ac.relation='invoice')
                                  OR (gl.invoice is false
                                     and ac.relation='acc_trans')))
                JOIN entity_credit_account
                  ON (gl.entity_credit_account = entity_credit_account.id)
                JOIN entity ON (entity.id = entity_credit_account.entity_id)
                JOIN company ON (entity.id = company.entity_id)
                JOIN country_tax_form ON (entity_credit_account.taxform_id = country_tax_form.id)
               WHERE country_tax_form.id = in_tax_form_id
             GROUP BY legal_name, meta_number, company.entity_id, company.tax_id, company.sales_tax_id, entity_credit_account.entity_class, entity.control_code, entity_credit_account.id

Function: template__get(in_template_name text, in_language_code character varying, in_format text)

Returns: template

Language: SQL

SELECT * FROM template
 WHERE template_name = $1 AND format = $3 AND
       language_code IS NOT DISTINCT FROM $2;

Function: template__get_by_id(in_id integer)

Returns: template

Language: SQL

SELECT * FROM template WHERE id = $1;

Function: template__list(in_language_code character varying)

Returns: SET OF template

Language: SQL


SELECT * FROM template WHERE language_code IS NOT DISTINCT FROM $1
ORDER BY template_name, format;


Function: template__save(in_template_name text, in_language_code character varying, in_template text, in_format text)

Returns: template

Language: PLPGSQL

DECLARE retval template;
BEGIN
   UPDATE template SET template = in_template,
                       last_modified = now()
    WHERE template_name = in_template_name AND format = in_format AND
          language_code IS NOT DISTINCT FROM in_language_code;

   IF FOUND THEN
      retval := template__get(in_template_name, in_language_code, in_format);
      RETURN retval;
   END IF;
   INSERT INTO template (template_name, language_code, template, format)
   VALUES (in_template_name, in_language_code, in_template, in_format);

   retval := template__get(in_template_name, in_language_code, in_format);
   RETURN retval;
END;

Function: timecard__allocate(in_id integer, in_amount numeric)

Returns: jcitems

Language: PLPGSQL


DECLARE retval jcitems;

BEGIN

UPDATE jcitems SET allocated = allocated + in_amount WHERE id = in_id;

IF NOT FOUND THEN
   RAISE EXCEPTION 'timecard not found';
END IF;

SELECT * INTO retval FROM jcitems WHERE id = in_id;

IF allocated > qty THEN
   RAISE EXCEPTION 'Too many allocated';
END IF;

RETURN retval;

END;

Function: timecard__bu_class(in_id integer)

Returns: business_unit_class

Language: SQL

SELECT * from business_unit_class
 where id in (select class_id from business_unit
               WHERE id in (select business_unit_id from jcitems
                             WHERE id = $1));

Function: timecard__get(id integer)

Returns: jcitems

Language: SQL

 SELECT * FROM jcitems WHERE id = $1; 

Function: timecard__parts(in_timecard boolean, in_service boolean, in_partnumber text)

Returns: SET OF parts

Language: SQL

SELECT *
  FROM parts
 WHERE not obsolete
       AND ($1 OR inventory_accno_id IS NULL)
       AND ($2 OR (income_accno_id IS NOT NULL
             AND inventory_accno_id IS NULL))
       AND ($3 IS NULL OR partnumber like $3 || '%')
 ORDER BY partnumber;

Function: timecard__report(in_business_units integer[], in_partnumber text, in_person_id integer, in_date_from date, in_date_to date, in_open boolean, in_closed boolean, in_jctype integer)

Returns: SET OF timecard_report_line

Language: SQL

WITH RECURSIVE bu_tree (id, path) AS (
     SELECT id, id::text AS path, control_code, description
       FROM business_unit
      WHERE id = any(in_business_units) OR (in_business_units = '{}' OR in_business_units IS NULL and parent_id IS NULL)
      UNION
     SELECT bu.id, bu_tree.path || ',' || bu.id, bu.control_code, bu.description
       FROM business_unit bu
       JOIN bu_tree ON bu_tree.id = bu.parent_id
)
SELECT j.id, j.description, j.qty, j.allocated, j.checkedin::time as checkedin,
       j.checkedout::time as checkedout, j.checkedin::date as transdate,
       extract('dow' from j.checkedin) as weekday,
       extract('week' from j.checkedin) as workweek,
       date_trunc('week', j.checkedin)::date as weekstarting,
       p.partnumber, bu.control_code as business_unit_code,
       bu.description AS businessunit_description,
       ee.employeenumber, e.name AS employee, j.parts_id, j.sellprice
  FROM jcitems j
  JOIN parts p ON p.id = j.parts_id
  JOIN person pr ON pr.id = j.person_id
  JOIN entity_employee ee ON ee.entity_id = pr.entity_id
  JOIN entity e ON ee.entity_id = e.id
  LEFT JOIN bu_tree bu ON bu.id = j.business_unit_id
 WHERE (p.partnumber = in_partnumber OR in_partnumber IS NULL)
       AND (j.person_id = in_person_id OR in_person_id IS NULL)
       AND (j.checkedin::date >= in_date_from OR in_date_from IS NULL)
       AND (j.checkedin::date <= in_date_to OR in_date_to IS NULL)
       AND (((j.qty > j.allocated or j.allocated is null)  AND in_open)
            OR (j.qty <= j.allocated AND in_closed))
       AND (j.jctype = in_jctype OR in_jctype is null)
       AND (bu.path IS NOT NULL OR in_business_units = '{}' OR in_business_units IS NULL)
  ORDER BY j.checkedin, bu.description, p.partnumber, e.name

Function: timecard__save(in_id integer, in_business_unit_id integer, in_parts_id integer, in_description text, in_qty numeric, in_allocated numeric, in_sellprice numeric, in_fxsellprice numeric, in_serialnumber text, in_checkedin timestamp with time zone, in_checkedout timestamp with time zone, in_person_id integer, in_notes text, in_total numeric, in_non_billable numeric, in_curr bpchar, in_jctype integer)

Returns: jcitems

Language: PLPGSQL

DECLARE retval jcitems;

BEGIN

UPDATE jcitems
   SET description = in_description,
       qty = in_qty,
       allocated = in_allocated,
       serialnumber = in_serialnumber,
       checkedin = in_checkedin,
       checkedout = in_checkedout,
       person_id = coalesce(in_person_id, person__get_my_id()),
       notes = in_notes,
       total = in_total,
       non_billable = in_non_billable
 WHERE id = in_id;

IF FOUND THEN
  SELECT * INTO retval WHERE id = in_id;
  return retval;
END IF;

INSERT INTO jcitems
(business_unit_id, parts_id, description, qty, allocated, sellprice,
  fxsellprice, serialnumber, checkedin, checkedout, person_id, notes,
  total, non_billable, jctype, curr)
VALUES
(in_business_unit_id, in_parts_id, in_description, in_qty, in_allocated,
  in_sellprice, in_fxsellprice, in_serialnumber, in_checkedin, in_checkedout,
  coalesce(in_person_id, person__get_my_id()), in_notes, in_total,
  in_non_billable, in_jctype, in_curr);

SELECT * INTO retval FROM jcitems WHERE id = currval('jcitems_id_seq')::int;

RETURN retval;

END;

Function: timecard_type__get(in_id integer)

Returns: jctype

Language: SQL


SELECT * FROM jctype where id = $1;


Function: timecard_type__list()

Returns: SET OF jctype

Language: SQL


SELECT * FROM jctype ORDER BY label;


Function: track_global_sequence()

Returns: trigger

Language: PLPGSQL

This trigger is used to track the id sequence entries across the transactions table, and with the ar, ap, and gl tables. This is necessary because these have not been properly refactored yet.

  DECLARE
  t_new_reference text;
  t_old_reference text;
BEGIN
  if tg_relname in ('ar','ap') then
    t_new_reference := new.invnumber;
    t_old_reference := old.invnumber;
  else
    t_new_reference := new.reference;
    t_old_reference := old.reference;
  end if;
  IF tg_op = 'INSERT' THEN
    INSERT INTO transactions (id, table_name, transdate, approved, reference)
    VALUES (new.id, TG_RELNAME, new.transdate, new.approved, t_new_reference);
  ELSEIF tg_op = 'UPDATE' THEN
    IF new.id <> old.id
      OR new.approved <> old.approved
      OR new.transdate <> old.transdate
      OR t_new_reference <> t_old_reference THEN
        UPDATE transactions
           SET id = new.id,
               approved = new.approved,
               transdate = new.transdate,
               reference = t_new_reference
         WHERE id = old.id;
    END IF;
  ELSE
    DELETE FROM transactions WHERE id = old.id;
  END IF;
  RETURN new;
END;

Function: trial_balance__generate(in_from_date date, in_to_date date, in_heading integer, in_accounts integer[], in_business_units integer[], in_balance_sign integer, in_all_accounts boolean, in_approved boolean)

Returns: SET OF tb_row

Language: PLPGSQL

Returns a row for each account which has transactions or a starting or ending balance over the indicated period, except when in_all_accounts is true, in which case a record is returned for all accounts, even ones unused over the reporting period.

DECLARE
        out_row         tb_row;
        t_roll_forward  date;
        t_cp            account_checkpoint;
        ignore_trans    int[];
        t_start_date    date;
        t_end_date      date;
        t_balance_sign  int;
BEGIN
    IF in_balance_sign IS NULL OR in_balance_sign = 0 THEN
       t_balance_sign = null;
    ELSIF in_balance_sign = -1 OR in_balance_sign = 1 THEN
       t_balance_sign = in_balance_sign;
    ELSE
       RAISE EXCEPTION 'Invalid Balance Type';
    END IF;

     IF in_from_date IS NULL THEN
       SELECT max(end_date) INTO t_roll_forward
         FROM account_checkpoint
        WHERE end_date < (select max(gl.transdate)
                            FROM gl JOIN yearend y ON y.trans_id = gl.id
                           WHERE y.transdate < coalesce(in_to_date, gl.transdate)
                         );
    ELSE
      SELECT max(end_date) INTO t_roll_forward
         FROM account_checkpoint
        WHERE end_date < in_from_date;
    END IF;

    IF t_roll_forward IS NULL
       OR array_upper(in_business_units, 1) > 0
    THEN
       SELECT min(transdate) - '1 day'::interval
         INTO t_roll_forward
         FROM acc_trans;
    END IF;

    SELECT ARRAY[trans_id] INTO ignore_trans FROM yearend
     ORDER BY transdate DESC LIMIT 1;

    IF in_to_date IS NULL THEN
        SELECT max(transdate) INTO t_end_date FROM acc_trans;
    ELSE
        t_end_date := in_to_date;
    END IF;


    RETURN QUERY
       WITH ac (transdate, amount_bc, chart_id) AS (
           WITH RECURSIVE bu_tree (id, path) AS (
            SELECT id, id::text AS path
              FROM business_unit
             WHERE parent_id = any(in_business_units)
            UNION
            SELECT bu.id, bu_tree.path || ',' || bu.id
              FROM business_unit bu
              JOIN bu_tree ON bu_tree.id = bu.parent_id
           )
       SELECT ac.transdate, ac.amount_bc, ac.chart_id
         FROM (select * from acc_trans
                where in_business_units = '{}' OR in_business_units IS NULL
                      OR EXISTS (
                            select 1 from business_unit_ac buac
                              join bu_tree on bu_tree.id = buac.bu_id
                             where buac.entry_id = acc_trans.entry_id
                           )) ac
         JOIN (SELECT id, approved FROM transactions
                WHERE (in_approved is null OR approved = in_approved)) gl
              ON ac.trans_id = gl.id
        WHERE ac.transdate BETWEEN t_roll_forward + '1 day'::interval AND t_end_date
              AND (in_approved is null or ac.approved or in_approved is false)
              AND (ignore_trans is null or ac.trans_id <> ALL(ignore_trans))
       )
       SELECT a.id, a.accno,
         COALESCE(at.description, a.description) as description, a.gifi_accno,
         CASE WHEN in_from_date IS NULL THEN 0 ELSE
              COALESCE(t_balance_sign,
                      CASE WHEN a.category IN ('A', 'E') THEN -1 ELSE 1 END )
              * (COALESCE(cp.amount_bc, 0)
              + SUM(CASE WHEN ac.transdate < coalesce(in_from_date,
                                                      t_roll_forward)
                         THEN ac.amount_bc ELSE 0 END)) end,
         SUM(CASE WHEN ac.transdate BETWEEN coalesce(in_from_date,
                                                     t_roll_forward)
                                        AND coalesce(in_to_date, ac.transdate)
                                    AND ac.amount_bc < 0 THEN ac.amount_bc * -1
                                                      ELSE 0 END)
            - CASE WHEN in_from_date IS NULL THEN COALESCE(cp.debits, 0)
                                             ELSE 0 END,
         SUM(CASE WHEN ac.transdate BETWEEN COALESCE(in_from_date,
                                                         t_roll_forward)
                                            AND COALESCE(in_to_date,
                                                         ac.transdate)
                                    AND ac.amount_bc > 0 THEN ac.amount_bc
                                                      ELSE 0 END) +
              CASE WHEN in_from_date IS NULL THEN COALESCE(cp.credits, 0)
                                             ELSE 0 END,
         COALESCE(t_balance_sign,
                  CASE WHEN a.category IN ('A', 'E') THEN -1 ELSE 1 END)
            * (COALESCE(cp.amount_bc, 0) + SUM(COALESCE(ac.amount_bc, 0))),
         CASE WHEN SUM(ac.amount_bc) + COALESCE(cp.amount_bc, 0) < 0
                 THEN (SUM(ac.amount_bc) + COALESCE(cp.amount_bc, 0)) * -1
              ELSE NULL END,
         CASE WHEN SUM(ac.amount_bc) + COALESCE(cp.amount_bc, 0) > 0
                   THEN sum(ac.amount_bc) + COALESCE(cp.amount_bc, 0)
              ELSE NULL END
         FROM account a
    LEFT JOIN ac ON ac.chart_id = a.id
    LEFT JOIN (
         select account_id, sum(amount_bc) as amount_bc,
                sum(debits) as debits, sum(credits) as credits
         from account_checkpoint
          where end_date = t_roll_forward
        group by account_id) cp ON cp.account_id = a.id
    LEFT JOIN (SELECT trans_id, description
                 FROM account_translation at
               WHERE language_code = preference__get('language')) at
           ON a.id = at.trans_id
        WHERE (in_accounts IS NULL OR in_accounts = '{}'
               OR a.id = ANY(in_accounts))
              AND (in_heading IS NULL OR in_heading = a.heading)
     GROUP BY a.id, a.accno, COALESCE(at.description, a.description),
              a.category, a.gifi_accno, cp.account_id,
              cp.amount_bc, cp.debits, cp.credits
       HAVING ABS(cp.amount_bc) > 0 or COUNT(ac) > 0 or in_all_accounts
     ORDER BY a.accno;
END;

Function: trial_balance__heading_accounts(in_accounts integer[])

Returns: SET OF account

Language: SQL

    SELECT * FROM account WHERE id in (SELECT unnest($1));

Function: trial_balance__list_headings()

Returns: SET OF trial_balance__heading

Language: SQL

    SELECT id, accno, description, ARRAY( SELECT id FROM account where heading = ah.id) FROM account_heading ah;

Function: trigger_parts_short()

Returns: trigger

Language: PLPGSQL

BEGIN
  IF NEW.onhand >= NEW.rop THEN
    NOTIFY parts_short;
  END IF;
  RETURN NEW;
END;

Function: trigger_workflow_user()

Returns: trigger

Language: PLPGSQL

BEGIN
  IF TG_OP <> 'DELETE' THEN
    NEW.workflow_user = CURRENT_USER;
    NEW.workflow_entity_id = person__get_my_entity_id();
  END IF;
  RETURN NEW;
END;

Function: unlock(in_id integer)

Returns: boolean

Language: PLPGSQL

Releases a pessimistic locks against a transaction, if that transaciton, as identified by in_id exists, and if it is locked by the current session. These locks are again only advisory, and the application may choose to handle them or not. Returns true if the transaction was unlocked by this routine, false otherwise.

BEGIN
    UPDATE transactions SET locked_by = NULL WHERE id = in_id
           AND locked_by IN (SELECT session_id FROM session WHERE users_id =
                (SELECT id FROM users WHERE username = SESSION_USER));
    RETURN FOUND;
END;

Function: unlock_all()

Returns: boolean

Language: PLPGSQL

Releases all pessimistic locks against transactions. These locks are again only advisory, and the application may choose to handle them or not. Returns true if any transactions were unlocked, false otherwise.

BEGIN
    UPDATE transactions SET locked_by = NULL
    where locked_by IN
          (select session_id from session WHERE users_id =
                  (SELECT id FROM users WHERE username = SESSION_USER));

    RETURN FOUND;
END;

Function: user__change_password(in_new_password text)

Returns: integer

Language: PLPGSQL

Allows a user to change his or her own password. The password is set to expire setting_get('password_duration') days after the password change.

DECLARE
        t_expires timestamp;
        t_password_duration text;
BEGIN
    SELECT value INTO t_password_duration FROM defaults
     WHERE setting_key = 'password_duration';
    IF t_password_duration IS NULL or t_password_duration='' THEN
        t_expires := 'infinity';
    ELSE
        t_expires := now()
                     + (t_password_duration::numeric::text || ' days')::interval;
    END IF;


    UPDATE users SET notify_password = DEFAULT where username = SESSION_USER;

    EXECUTE 'ALTER USER ' || quote_ident(SESSION_USER) ||
            ' with ENCRYPTED password ' || quote_literal(in_new_password) ||
                 ' VALID UNTIL '|| quote_literal(t_expires);
    return 1;
END;

Function: user__check_my_expiration()

Returns: interval

Language: PLPGSQL

Returns the time when password of the current logged in user is set to expire.

DECLARE
    outval interval;
BEGIN
    SELECT CASE WHEN isfinite(rolvaliduntil) is not true THEN '1 year'::interval
                ELSE rolvaliduntil - now() END AS expiration INTO outval
    FROM pg_roles WHERE rolname = SESSION_USER;
    RETURN outval;
end;

Function: user__get_all_users()

Returns: SET OF user_listable

Language: SQL


    select * from user_listable;


Function: user__get_preferences(in_user_id integer)

Returns: user_preferences

Language: PLPGSQL

Returns the preferences row for the user.


declare
    v_row user_preferences;
BEGIN
    --TODO This is a workaround waiting to be replaced with something more
    -- appropriate for returning a flexible set of preference values
    select preference__get('dateformat'),
           preference__get('numberformat'),
           preference__get('language'),
           preference__get('stylesheet'),
           preference__get('printer')
       into v_row;

    return v_row;
END;

Function: user__save_preferences(in_dateformat text, in_numberformat text, in_language text, in_stylesheet text, in_printer text)

Returns: boolean

Language: PLPGSQL

Saves user preferences. Returns true if successful, false if no preferences were found to update.

BEGIN
    perform preference__set('dateformat', in_dateformat);
    perform preference__set('numberformat', in_numberformat);
    perform preference__set('language', in_language);
    perform preference__set('stylesheet', in_stylesheet);
    perform preference__set('printer', in_printer);

    RETURN true;
END;

Function: voucher__delete(in_voucher_id integer)

Returns: integer

Language: PLPGSQL

Deletes the specified voucher from the batch.

DECLARE
        voucher_row RECORD;
BEGIN
    SELECT * INTO voucher_row FROM voucher WHERE id = in_voucher_id;
    IF voucher_row.batch_class IN (1, 2, 5) THEN -- GL/AR/AP voucher
        -- Delete *all* lines from acc_trans (in the transaction)
        -- /even/ if not explicitly linked to the voucher
        DELETE FROM ac_tax_form WHERE entry_id IN (
               SELECT entry_id
                 FROM acc_trans
               WHERE trans_id = voucher_row.trans_id);

        -- Note that this query *looks* duplicated with the next section
        -- but it's not! Notably, the WHERE clause in the EXISTS subquery
        -- has a different condition (trans_id vs voucher_id!)
        WITH deleted_links AS (
             DELETE FROM payment_links pl WHERE
                   EXISTS (select 1 from acc_trans a
                            where pl.entry_id    = a.entry_id
                                  and a.trans_id = voucher_row.trans_id)
             RETURNING *
        )
        DELETE FROM payment p
         WHERE id IN (select payment_id from deleted_links)
                AND NOT EXISTS (select 1 from payment_links pl
                                 where pl.payment_id = p.id);
        DELETE FROM acc_trans WHERE trans_id = voucher_row.trans_id;

        -- deletion of the ar/ap/gl row causes removal of the `transactions`
        -- row, which fails if the voucher isn't deleted...
        DELETE FROM voucher WHERE id = voucher_row.id;
        DELETE FROM ar WHERE id = voucher_row.trans_id;
        DELETE FROM ap WHERE id = voucher_row.trans_id;
        DELETE FROM gl WHERE id = voucher_row.trans_id;
    ELSE
        -- Delete only the lines in the transaction which are explicitly
        -- linked to the voucher
        DELETE FROM ac_tax_form WHERE entry_id IN
               (select entry_id from acc_trans
                 where voucher_id = voucher_row.id);

        WITH deleted_links AS (
             DELETE FROM payment_links pl WHERE
                   EXISTS (select 1 from acc_trans a
                            where pl.entry_id    = a.entry_id
                                  and a.voucher_id = voucher_row.id)
             RETURNING *
        )
        DELETE FROM payment p
         WHERE id IN (select payment_id from deleted_links)
                AND NOT EXISTS (select 1 from payment_links pl
                                 where pl.payment_id = p.id);
        DELETE FROM acc_trans where voucher_id = voucher_row.id;
        DELETE FROM voucher WHERE id = voucher_row.id;
    END IF;

    RETURN 1;
END;

Function: voucher__list(in_batch_id integer)

Returns: SET OF voucher_list

Language: SQL

Retrieves a list of vouchers and amounts attached to the batch.

                SELECT v.id, a.invoice, a.invnumber,
                        eca.meta_number || '--' || e.name,
                        v.batch_id, v.trans_id,
                        a.amount_bc, a.transdate, 'Payable', v.batch_class
                FROM voucher v
                JOIN ap a ON (v.trans_id = a.id)
                JOIN entity_credit_account eca
                        ON (eca.id = a.entity_credit_account)
                JOIN entity e ON (eca.entity_id = e.id)
                WHERE v.batch_id = in_batch_id
                        AND v.batch_class = (select id from batch_class
                                        WHERE class = 'ap')
                UNION
                SELECT v.id, a.invoice, a.invnumber,
                        eca.meta_number || '--' || e.name,
                        v.batch_id, v.trans_id,
                        a.amount_bc, a.transdate, 'Receivable', v.batch_class
                FROM voucher v
                JOIN ar a ON (v.trans_id = a.id)
                JOIN entity_credit_account eca
                        ON (eca.id = a.entity_credit_account)
                JOIN entity e ON (eca.entity_id = e.id)
                WHERE v.batch_id = in_batch_id
                        AND v.batch_class = (select id from batch_class
                                        WHERE class = 'ar')
                UNION ALL
                -- TODO:  Add the class labels to the class table.
                SELECT v.id, ap.invoice, a.source,
                        eca.meta_number || '--'  || e.name,
                        v.batch_id, v.trans_id,
                        sum(CASE WHEN bc.class LIKE 'payment%' THEN a.amount_bc * -1
                             ELSE a.amount_bc  END), a.transdate,
                        CASE WHEN bc.class = 'payment' THEN 'Payment'
                             WHEN bc.class = 'payment_reversal'
                             THEN 'Payment Reversal'
                        END, v.batch_class
                FROM voucher v
                JOIN acc_trans a ON (v.id = a.voucher_id)
                JOIN batch_class bc ON (bc.id = v.batch_class)
                JOIN account_link l ON (a.chart_id = l.account_id)
                JOIN ap ON (ap.id = a.trans_id)
                JOIN entity_credit_account eca
                        ON (ap.entity_credit_account = eca.id)
                JOIN entity e ON (eca.entity_id = e.id)
                WHERE v.batch_id = in_batch_id
                        AND a.voucher_id = v.id
                        AND (bc.class like 'payment%' AND l.description = 'AP')
                GROUP BY v.id, ap.invoice, a.source, eca.meta_number, e.name,
                        v.batch_id, v.trans_id, a.transdate, bc.class

                UNION ALL
                SELECT v.id, ar.invoice, a.source,
                        eca.meta_number || '--'  || e.name,
                        v.batch_id, v.trans_id,
                        CASE WHEN bc.class LIKE 'receipt%' THEN sum(a.amount_bc) * -1
                             ELSE sum(a.amount_bc)  END, a.transdate,
                        CASE WHEN bc.class = 'receipt' THEN 'Receipt'
                             WHEN bc.class = 'receipt_reversal'
                             THEN 'Receipt Reversal'
                        END, v.batch_class
                FROM voucher v
                JOIN acc_trans a ON (v.id = a.voucher_id)
                JOIN batch_class bc ON (bc.id = v.batch_class)
                JOIN account_link l ON (a.chart_id = l.account_id)
                JOIN ar ON (ar.id = a.trans_id)
                JOIN entity_credit_account eca
                        ON (ar.entity_credit_account = eca.id)
                JOIN entity e ON (eca.entity_id = e.id)
                WHERE v.batch_id = in_batch_id
                        AND a.voucher_id = v.id
                        AND (bc.class like 'receipt%' AND l.description = 'AR')
                GROUP BY v.id, ar.invoice, a.source, eca.meta_number, e.name,
                        v.batch_id, v.trans_id, a.transdate, bc.class
                UNION ALL
                SELECT v.id, false, g.reference, g.description,
                        v.batch_id, v.trans_id,
                        sum(a.amount_bc), g.transdate, 'GL', v.batch_class
                FROM voucher v
                JOIN gl g ON (g.id = v.trans_id)
                JOIN acc_trans a ON (v.trans_id = a.trans_id)
                WHERE a.amount_bc > 0
                        AND v.batch_id = in_batch_id
                        AND v.batch_class IN (select id from batch_class
                                        where class = 'gl')
                GROUP BY v.id, g.reference, g.description, v.batch_id,
                        v.trans_id, g.transdate
                ORDER BY 7, 1

Function: voucher_get_batch(in_batch_id integer)

Returns: batch

Language: PLPGSQL

Retrieves basic batch information based on batch_id.

DECLARE
        batch_out batch%ROWTYPE;
BEGIN
        SELECT * INTO batch_out FROM batch b WHERE b.id = in_batch_id;
        RETURN batch_out;
END;

Function: wage__list_for_entity(in_entity_id integer)

Returns: SET OF payroll_wage

Language: SQL

SELECT * FROM payroll_wage WHERE entity_id = $1;

Function: wage__list_types(in_country_id integer)

Returns: SET OF payroll_income_type

Language: SQL

SELECT * FROM payroll_income_type where country_id = $1

Function: wage__save(in_rate numeric, in_entity_id integer, in_type_id integer)

Returns: payroll_wage

Language: PLPGSQL

DECLARE
  return_wage payroll_wage;
BEGIN

UPDATE payroll_wage
   SET rate = in_rate
 WHERE entity_id = in_entity_id and in_type_id;


IF NOT FOUND THEN
    INSERT INTO payroll_wage (entity_id, type_id, rate)
    VALUES (in_entity_id, in_type_id, in_rate);
END IF;

SELECT * INTO return_wage FROM payroll_wage
             WHERE entity_id = in_entity_id and in_type_id;

RETURN return_wage;
END;

Function: warehouse__list()

Returns: SET OF warehouse

Language: SQL

SELECT * FROM warehouse ORDER BY DESCRIPTION;

Function: warehouse__list_all()

Returns: SET OF warehouse

Language: SQL

SELECT * FROM warehouse order by description;

Generated by PostgreSQL Autodoc

W3C HTML 4.01 Strict