','Chart of Accounts (AU)'
+AT,' Kontoplan für Österreich Ferdinand Gassauer\, Tue\, 5 Feb 2002 checked and completed\, Thu\, 7 Feb 2002\, Dieter Simader
','Chart of Accounts (AT)'
+ID,' sample only insert into chart (accno\,description\,charttype\,gifi_accno\,category\,link) values ('2190'\,'Federal Income Tax Payable'\,'A'\,''\,'L'\,''); insert into chart (accno\,description\,charttype\,gifi_accno\,category\,link) values ('2210'\,'Workers Comp Payable'\,'A'\,''\,'L'\,''); insert into chart (accno\,description\,charttype\,gifi_accno\,category\,link) values ('2220'\,'Vacation Pay Payable'\,'A'\,''\,'L'\,''); insert into chart (accno\,description\,charttype\,gifi_accno\,category\,link) values ('2320'\,'VAT (14%)'\,'A'\,''\,'L'\,'AR_tax:AP_tax:IC_taxpart:IC_taxservice'); insert into chart (accno\,description\,charttype\,gifi_accno\,category\,link) values ('2330'\,'VAT (30%)'\,'A'\,''\,'L'\,'AR_tax:AP_tax:IC_taxpart:IC_taxservice');
insert into tax (chart_id\,rate) values ((select id from chart where accno = '2320')\,0.14); insert into tax (chart_id\,rate) values ((select id from chart where accno = '2330')\,0.3);
','Default chart of accounts (ID)'
+BE,' selon PLAN COMPTABLE MINIMUM NORMALISE BELGE contribué par Jens-Ingo Brodesser\, jens-ingo@all2all.org\, Moving Art Studio ASBL\, ALL2ALL The Independent Network le 15/07/2003 CHARGES - KOSTEN'\,'H'\,'E'\,''\,''); PRODUITS - OPBRENGSTEN'\,'H'\,'I'\,''\,''); CAPITAL PROPRE\, ... - EIGEN VERMOGEN\, ...'\,'H'\,'L'\,''\,''); FRAIS D''ETABLISSEMENT\, ACTIFS IMMOBILISES\, ... - OPRICHTINGSKOSTEN\, VASTE ACTIVA\, ...'\,'H'\,'A'\,''\,''); STOCK ET COMMANDES EN COURS - GRONDSTOFFEN'\,'H'\,'A'\,''\,''); CREANCES ET DETTES A UN AN AU PLUS - VORDERINGEN EN SCHULDEN OP TEN HOOGTE EEN JAAR'\,'H'\,'A'\,''\,''); PLACEMENTS DE TRESORIERIE - LIQUIDE MIDDELEN / GELDBELEGGINGEN'\,'H'\,'Q'\,''\,'');','Chart of Accounts (BE)'
+BR,' sample only
','General Brazilien Portuguese COA (BR)'
+en_CA,' sample only
','General Canadian COA (CA)'
+fr_CA,' sample only translated and adapted from the General Canadian COA\, with the help of the Grand Dictionnaire Terminologique: http://granddictionnaire.com/ Some provisions have been made for Québec-specifics\, namely: TVQ/TPS terminology\, CSST\, Assurance-emploi\, RRQ
','General French-Canadian (AKA Québécois) COA (CA)'
+CO,'
Plan único de Cuentas de Colombia\, elaborado por Rene Real Hernandez (SDI S.A.)\, Francico Padilla (SDI S.A.)\, Silfredo Godoy Chavez (CaribeNet S.A.)\, Lourdes Mejía Martinez (CaribeNet S.A.) y Dirk Enrique Seiffert (CaribeNet S.A.) Si quieres aportar: Contactenos www.caribenet.com - info@caribenet.com
','Chart of Accounts (CO)'
+CZ,' Tomas.Fencl@centrum.cz Kompletní úètová osnova platná v roce 2001 Osnova má 2 místa pro tvorbu analytik
','Czech chart of accounts (CZ)'
+DK,' sample only
','Default chart of accounts (DK)'
+US,' sample only
','Default chart of accounts (US)'
+NL,'
AJ Hettema\, Mon\, 20 Aug 2001
','Dutch Chart of Accounts (NL)'
+NL_standard,' Nederlandstalig Rekeningschema conform het Decimaal Stelsel. PDF Tammes\, Fri\, 29 Mar 2002 ( remarks or questions to finance@bermuda-holding.com ) Just delete any accounts not needed after importing the scheme\, beats entering all manually (or add -- at the beginning) Account groups (first number in accountnumber defines to which group it belongs) 0 : Vaste Activa\, Eigen Vermogen\, Voorzieningen en lang vreemd vermogen => Fixed Assets\, Capital\, Accruals and long term loans 1 : Financiele Rekeningen => Financial Accounts 2 : Tussenrekeningen => Intermediate Accounts 3 : Voorraad grond- en hulpstoffen => Stocks (minerals\, parts not yet assembled etc) 4 : Kostenrekeningen => Costs Accounts 5 : Verdeling Directe kosten => Accounts to recharge several Direct Costs to separate departments 6 : Fabricagekosten => Assembly Costs 7 : Voorraad gereed produkt en product in bewerking => Stocks (Trade Articles\, half-finished assemblies\, projects under construction) 8 : Rekeningen voor vaststelling van het verkoopresultaat => Accounts for dediding Sales Result 9 : Rekeningen voor vaststelling van de resultatenrekening => Accounts for dediding Profit & Loss (P/L) The general idea is to allocate all costs to the 4/5/6 accounts\, all stock transactions to 7 and sales related to 8. At the end of the year you clean out the seperate accounts via x999 and kick the result to the relevant 9xxx series. That way you have a specialized P/L in the seperate accounts and an overview/shirt version of the P/L in the 9 series\, where also various results not related to normal operations is recorded (tax\, donations\, that kind of thing. Finally the 9999 account is used to kick the result to retained earnings and related accounts in the balance sheet\, and we are ready for the next year
','Dutch Chart of Accounts following the Decimal Standards as set by the famous Philips Accounting Department (NL)'
+EG,' ','Chart of Accounts (EG)'
+FR,' From: Oscar Buijten Mon\, 6 Aug 2001
insert taxes
update defaults
','Chart of Accounts for France (FR)'
+DE,' Einfacher Deutscher Kontenrahmen => Very Easy German Default Chart Vorbereitet von / Prepared by Paul Tammes May 9th\, 2002. Kommentar / Comments : finance@bermuda-holding.com Englische Texte für eigene Zwecke und um Refernz in SQL-Ledger Dokumentation zu erleichtern. English terms used mostly for my own reference and to make lookup in SQL-Ledger documentation easier. GIFI-codes werden benutzt/misbraucht um die art der Rechnung zu deuten\, Fehler nicht ausgeschlossen denn Ich bin kein Deutscher Steuerberater ;-( GIFI field codes re-used for following specs: Link: Achtung\, sehr wenig benutzt da mir die Kentnisse zum Deutschen System fehlen. Sehr gut aufpassen und wenn Ihr Fehler oder Praktische TIPS hat: gerne! Link: used to a minimum\, update and customization may well be needed! A0 = Anlagevermögen / Fixed Assets A1-1 = Warenbestand / Inventory A1-2 = Forderungen / Liabilities A1-3 = Liquide Mittel / Assets A1-4 = Aktive Rechnungsabgrenzung / Closing Account results E = Erträge / Income K0 = Wareneinsatz / COGS K1 = Personalkosten / Salaries etc K2 = Raumkosten / Rental etc K3 = Sonstige Kosten / Various costs NA = Neutrale Aufwendungen / Neutral Costs NE - Neutrale Erlöse / Neutral Income P0 = Eigenkapital / Equity P1 = Rückstellungen / Reserves P2 = Fremdkapital Langfristig / Liabilities Long Term P3 = Fremdkapital Kurzfristig / Liabilities Short Term P4 = Passive Rechnungsabgrenzung / Closing Account results
A0 A1-1 A1-2 A1-3 A1-4 P0 P1 P2 P3 P4 K0 K1 K2 K3 E NA NE
Default settings
','Chart of Accounts (DE)'
+DE_DATEV,' DATEV SKR03
','Chart of Accounts (DE)'
+DE_SKR03,'
','General COA (DE)'
+HU,' Magyar fõkönyvi számlák\, amelyek csak példaként szolgálnak
','Hungarian chart of accounts (HU)'
+IT_cc2424,'
From: Daniele Giacomini 13 ottobre 2003 05 novembre 2003
Il codice GIFI viene usato per rappresentare il codice corrispondente al bilancio riclassificato\, come da codice civile\, art. 2424. Il codice in questione e' rappresentato separando i vari elementi con un punto\, aggiungendo inizialmente un numero: 1 sta per attivo\, 2 sta per passivo\, 3 sta per conto economico.
L'abbinamento tra il piano dei conti e il codice GIFI non e' perfetto e richiede un controllo ulteriore; inoltre\, non sono stati risolti i problemi relativi alle sottoclassificazioni previste dal codice civile\, che pero' non hanno un codice standard corrispondente.
La codifica GIFI è contenuta in un file separato.
Questo file e' scritto usando soltanto la codifica ASCII\, per evitare problemi di qualunque genere nella scelta della codifica. Pertanto\, le vocali accentate sono seguite da un apostrofo.
E' disponibile un cliente\, un fornitore e un articolo di prova.
update defaults ','Chart of Accounts for Italy (IT)'
+IT,'
From: Luca Venturini 9 Oct 2001
('2001101'5 Conto ('6470005' diventa tassa (negativa) ('2001102'5 IVA su acquisti diventa Iva su acq. (20%) ('2001102'5 Introdotto un conto per ogni aliquota IVA ('2001102'7 Modificato numero di conto per la Ritenuta d'acconto ('2001103'1 Inseriti Fornitore-test e Consulente-test ('2001111'5 Invertito i ruoli di ('6470005' e ('6470010' (erano sbagliati) ('2001111'5 Eliminata l'applicabilita' della RA al cliente test ('2001112'0 Aggiunto IC_expense al conto ('7005005' (mancava un conto di default per i servizi)
','Chart of Accounts for Italy (IT)'
+LV,' prepared by Kaspars Melkis Sept. 14\, 2003 checked and edited Sept. 20\, 2003\, D. Simader
','Latvian COA (LV)'
+NO,' charset: ISO-8859-1 http://www.nif.idrett.no/ftp/Lover/doc/kontoplan.htm http://www.legemiddelverket.no/rundskriv/frahtil/1999/ik-1499.htm sample only
','Default chart of accounts (NO)'
+PY,'
Version: 2.4.6 Submitted by: Mario L. Epp Date: 2005-01-03
','SQL-Ledger Sample COA - Paraguay (PY)'
+PL,' From: Peter Dabrowski Sun\, 23 March 2003
','Chart of Account for Poland (PL)'
+HK,'
exchange rate
','Default chart of accounts -- sample only (HK)'
+ES,' From: Federico Montesino Pouzols 23 Apr 2002
Taxes in Spain
IVA: 4\, 7 or 16% IVA soportado Recargo equivalente: 0.5\, 1 or 4%
IVA repercutido Recargo equivalente: 0.5\, 1 or 4%
update defaults
','Chart of Accounts for Spain (Cuadro del Plan de Contabilidad español) (ES)'
+SE,'no comments from sql-ledger chart.sql file.','Chart of Accounts (SE)'
+CH,' adapted to numeric representation of chart no.
','Swiss chart of accounts (CH)'
+TW,'
exchange rate
','Default chart of accounts -- sample only (TW)'
+GB,'
','sample COA for UK (GB)'
+US,' modify as needed
','US_General COA (US)'
+US_mfg,' modify as needed
','US_Manufacturing COA (US)'
+US_service,' modify as needed
','US_Service_Company COA (US)'
+VE,' modify as needed
+ General Ledger provides services to other packages and provides a basic UI for directly managing a ledger and accounts.
+
+
+ Features
+
+
Converted:
+
+Data model for Postgresql including preloading chart templates and language translations when installing.
+
+Num2text in SL, lc_number_to_text in accounts-payables package
+
+
Planned:
+
+Basic general ledger posting via web and service (and/or callback?), and editing chart templates. One ledger per package instance. See sql-ledger's features and What's new pages.
+
+
+notes
+
+sql-ledger package-key table name
+
+<table_name> qal_<table_name>
+
+
+
+Need to add package_key to data model.
+
+porting notes and guidelines
+
+The locale data has been extracted using a custom program located in the accounts-ledger/info directory of this package. Configuration data is set in convert.tcl. Be sure to remove xml language files in accounts-ledger/catalog/ before running the program. An error while running convert-SL-charts.tcl may be caused by multiple line queries in the chart files. The program should handle it now, but it has not been tested.
+
+Any functions dependent on either AP or AR data are moved to those packages. When something is dependent on two or more accounting packages, it is moved into the "full accounting features" accounts-desk package.
+
+
+Each package has a set of Model-View-Control features and services. Most any of these packages should provide a basic level of features without requiring other packages. Optional (integrated) features are built into the packages where appropriate.
+
+Table of package dependencies
+
+
+
package-key
depends on
+
+
accounts-ledger
online-catalog (and maybe inventory-control) for parts sales management), contacts?
+
+
accounts-payable
accounts-ledger, contacts
+
+
accounts-receivables
accounts-ledger, contacts
+
+
accounts-desk
all (a catch-all for cross and multiple denpendencies)
+lc_number_to_text proc is made from the the SL Num2text procedure which has localized cases in the locale directories, and the default Num2text.pm in sql-ledger/SL/
+
+
+SQL
+
+The Oracle SQL will be added after the PG SQL has settled a bit.
+
Some of the SQL has been changed to the OpenACS standards. See http://openacs.org/doc/current/eng-standards-plsql.html and http://openacs.org/wiki
+
+for Postgresql:
+
+INT changed to INTEGER
+some of the TEXT types were changed to VARCHAR so that they get indexed
+FLOAT changed to NUMERIC
+
+
Index: openacs-4/packages/accounts-ledger/www/doc/index.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-ledger/www/doc/index.tcl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-ledger/www/doc/index.tcl 11 Nov 2005 05:32:39 -0000 1.1
@@ -0,0 +1,14 @@
+ad_page_contract {
+
+ Documentation
+
+ @author Torben Brosten torben@kappacorp.com
+ @creation-date 2005-09-27
+} {
+}
+
+
+set package_instance_name [apm_instance_name_from_id [apm_package_id_from_key accounts-ledger]]
+
+ad_return_template
+
Index: openacs-4/packages/accounts-payables/accounts-payables.info
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/accounts-payables.info,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/accounts-payables.info 11 Nov 2005 05:33:40 -0000 1.1
@@ -0,0 +1,28 @@
+
+
+
+
+ Accounts Payables
+ Accounts Payables
+ f
+ f
+
+
+ Torben Brosten
+ OpenACS community
+ Accounts Payables manages and tracks purchase orders, vendor invoices and related payments
+ Dekka Corp of Oregon
+ Accounts Payables provides a basic UI for creating and managing purchase orders, vendor invoices and related payments. Also provides related services to other packages.
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
Index: openacs-4/packages/accounts-payables/sql/postgresql/accounts-payables-create.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/sql/postgresql/accounts-payables-create.sql,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/sql/postgresql/accounts-payables-create.sql 11 Nov 2005 05:33:40 -0000 1.1
@@ -0,0 +1,72 @@
+--
+CREATE TABLE qap_ap (
+ id integer DEFAULT nextval ( 'qal_id' ),
+ invnumber varchar(100),
+ transdate date DEFAULT current_date,
+ vendor_id integer,
+ taxincluded bool DEFAULT 'f',
+ amount numeric,
+ netamount numeric,
+ paid numeric,
+ datepaid date,
+ duedate date,
+ invoice bool DEFAULT 'f',
+ ordnumber varchar(100),
+ curr varchar(3),
+ notes text,
+ employee_id integer,
+ till varchar(20),
+ quonumber varchar(100),
+ intnotes text,
+ department_id integer DEFAULT 0,
+ shipvia varchar(100),
+ language_code varchar(6),
+ ponumber text,
+ shippingpoint text,
+ terms integer DEFAULT 0
+);
+--
+create index qap_ap_id_key on qap_ap (id);
+create index qap_ap_transdate_key on qap_ap (transdate);
+create index qap_ap_invnumber_key on qap_ap (invnumber);
+create index qap_ap_ordnumber_key on qap_ap (ordnumber);
+create index qap_ap_vendor_id_key on qap_ap (vendor_id);
+create index qap_ap_employee_id_key on qap_ap (employee_id);
+create index qap_ap_quonumber_key on qap_ap (quonumber);
+
+-- accounts-ledger maintenance
+
+CREATE TRIGGER qap_del_department AFTER DELETE ON qap_ap FOR EACH ROW EXECUTE PROCEDURE qal_del_department();
+-- end trigger
+
+CREATE TRIGGER qap_del_exchangerate BEFORE DELETE ON qap_ap FOR EACH ROW EXECUTE PROCEDURE qal_del_exchangerate();
+-- end trigger
+
+CREATE TRIGGER qap_check_department AFTER INSERT OR UPDATE ON qap_ap FOR EACH ROW EXECUTE PROCEDURE qal_check_department();
+-- end trigger
+
+--
+CREATE FUNCTION qap_lastcost(integer) RETURNS FLOAT AS '
+
+DECLARE
+
+v_cost numeric;
+v_parts_id alias for $1;
+
+BEGIN
+
+ SELECT INTO v_cost sellprice FROM qal_invoice i
+ JOIN qap_ap a ON (a.id = i.trans_id)
+ WHERE i.parts_id = v_parts_id
+ ORDER BY a.transdate desc
+ LIMIT 1;
+
+ IF v_cost IS NULL THEN
+ v_cost := 0;
+ END IF;
+
+RETURN v_cost;
+END;
+' language 'plpgsql';
+-- end function
+--
Index: openacs-4/packages/accounts-payables/tcl/CP.pm
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/tcl/Attic/CP.pm,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/tcl/CP.pm 11 Nov 2005 05:33:41 -0000 1.1
@@ -0,0 +1,623 @@
+#=====================================================================
+# SQL-Ledger Accounting
+# Copyright (C) 2003
+#
+# Author: DWS Systems Inc.
+# Web: http://www.sql-ledger.org
+#
+# Contributors:
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#======================================================================
+#
+# Check and receipt printing payment module backend routines
+# Number to text conversion routines are in
+# locale/{countrycode}/Num2text
+#
+#======================================================================
+
+package CP;
+
+
+sub new {
+ my ($type, $countrycode) = @_;
+
+ $self = {};
+
+ if ($countrycode) {
+ if (-f "locale/$countrycode/Num2text") {
+ require "locale/$countrycode/Num2text";
+ } else {
+ use SL::Num2text;
+ }
+ } else {
+ use SL::Num2text;
+ }
+
+ bless $self, $type;
+
+}
+
+
+sub paymentaccounts {
+ my ($self, $myconfig, $form) = @_;
+
+ # connect to database
+ my $dbh = $form->dbconnect($myconfig);
+
+ my $query = qq|SELECT accno, description, link
+ FROM chart
+ WHERE link LIKE '%$form->{ARAP}%'
+ ORDER BY accno|;
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $form->dberror($query);
+
+ $form->{PR}{$form->{ARAP}} = ();
+ $form->{PR}{"$form->{ARAP}_paid"} = ();
+
+ while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
+ foreach my $item (split /:/, $ref->{link}) {
+ if ($item eq $form->{ARAP}) {
+ push @{ $form->{PR}{$form->{ARAP}} }, $ref;
+ }
+ if ($item eq "$form->{ARAP}_paid") {
+ push @{ $form->{PR}{"$form->{ARAP}_paid"} }, $ref;
+ }
+ }
+ }
+ $sth->finish;
+
+ # get currencies and closedto
+ $query = qq|SELECT curr, closedto, current_date
+ FROM defaults|;
+ ($form->{currencies}, $form->{closedto}, $form->{datepaid}) = $dbh->selectrow_array($query);
+
+ if ($form->{payment} eq 'payments') {
+ # get language codes
+ $query = qq|SELECT *
+ FROM language
+ ORDER BY 2|;
+ $sth = $dbh->prepare($query);
+ $sth->execute || $self->dberror($query);
+
+ $form->{all_language} = ();
+ while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
+ push @{ $form->{all_language} }, $ref;
+ }
+ $sth->finish;
+
+ $form->all_departments($myconfig, $dbh, $form->{vc});
+ }
+
+ $dbh->disconnect;
+
+}
+
+
+sub get_openvc {
+ my ($self, $myconfig, $form) = @_;
+
+ my $dbh = $form->dbconnect($myconfig);
+
+ my $arap = ($form->{vc} eq 'customer') ? 'ar' : 'ap';
+ my $query = qq|SELECT count(*)
+ FROM $form->{vc} ct, $arap a
+ WHERE a.$form->{vc}_id = ct.id
+ AND a.amount != a.paid|;
+ my ($count) = $dbh->selectrow_array($query);
+
+ my $sth;
+ my $ref;
+
+ # build selection list
+ if ($count < $myconfig->{vclimit}) {
+ $query = qq|SELECT DISTINCT ct.id, ct.name
+ FROM $form->{vc} ct, $arap a
+ WHERE a.$form->{vc}_id = ct.id
+ AND a.amount != a.paid
+ ORDER BY name|;
+ $sth = $dbh->prepare($query);
+ $sth->execute || $form->dberror($query);
+
+ while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
+ push @{ $form->{"all_$form->{vc}"} }, $ref;
+ }
+
+ $sth->finish;
+
+ }
+
+ $form->all_departments($myconfig, $dbh, $form->{vc});
+
+ # get language codes
+ $query = qq|SELECT *
+ FROM language
+ ORDER BY 2|;
+ $sth = $dbh->prepare($query);
+ $sth->execute || $self->dberror($query);
+
+ $form->{all_language} = ();
+ while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
+ push @{ $form->{all_language} }, $ref;
+ }
+ $sth->finish;
+
+ # get currency for first name
+ if (@{ $form->{"all_$form->{vc}"} }) {
+ $query = qq|SELECT curr FROM $form->{vc}
+ WHERE id = $form->{"all_$form->{vc}"}->[0]->{id}|;
+ ($form->{currency}) = $dbh->selectrow_array($query);
+ }
+
+ $dbh->disconnect;
+
+}
+
+
+sub get_openinvoices {
+ my ($self, $myconfig, $form) = @_;
+
+ my $null;
+ my $department_id;
+
+ # connect to database
+ my $dbh = $form->dbconnect($myconfig);
+
+ my $where = qq|WHERE a.$form->{vc}_id = $form->{"$form->{vc}_id"}
+ AND a.curr = '$form->{currency}'
+ AND a.amount != a.paid|;
+
+ my $sortorder = "transdate, invnumber";
+
+ my ($buysell);
+ if ($form->{vc} eq 'customer') {
+ $buysell = "buy";
+ } else {
+ $buysell = "sell";
+ }
+
+ if ($form->{payment} eq 'payments') {
+ $where = qq|WHERE a.curr = '$form->{currency}'
+ AND a.amount != a.paid|;
+
+ if ($form->{duedatefrom}) {
+ $where .= qq|
+ AND a.duedate >= '$form->{duedatefrom}'|;
+ }
+ if ($form->{duedateto}) {
+ $where .= qq|
+ AND a.duedate <= '$form->{duedateto}'|;
+ }
+ $sortorder = "name, transdate";
+ }
+
+
+ ($null, $department_id) = split /--/, $form->{department};
+ if ($department_id) {
+ $where .= qq|
+ AND a.department_id = $department_id|;
+ }
+
+ my $query = qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid,
+ a.curr, c.name, a.$form->{vc}_id, c.language_code
+ FROM $form->{arap} a
+ JOIN $form->{vc} c ON (c.id = a.$form->{vc}_id)
+ $where
+ ORDER BY $sortorder|;
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $form->dberror($query);
+
+ $query = qq|SELECT s.spoolfile
+ FROM status s
+ WHERE s.formname = '$form->{formname}'
+ AND s.trans_id = ?|;
+ my $vth = $dbh->prepare($query);
+
+ my $spoolfile;
+ while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
+ # if this is a foreign currency transaction get exchangerate
+ $ref->{exchangerate} = $form->get_exchangerate($dbh, $ref->{curr}, $ref->{transdate}, $buysell) if ($form->{currency} ne $form->{defaultcurrency});
+
+ $vth->execute($ref->{id});
+ $ref->{queue} = "";
+ while (($spoolfile) = $vth->fetchrow_array) {
+ $ref->{queued} .= "$form->{formname} $spoolfile ";
+ }
+ $vth->finish;
+ $ref->{queued} =~ s/ +$//g;
+
+ push @{ $form->{PR} }, $ref;
+ }
+
+ $sth->finish;
+ $dbh->disconnect;
+
+}
+
+
+
+sub post_payment {
+ my ($self, $myconfig, $form) = @_;
+
+ # connect to database, turn AutoCommit off
+ my $dbh = $form->dbconnect_noauto($myconfig);
+
+ my $sth;
+
+ my ($paymentaccno) = split /--/, $form->{account};
+
+ # if currency ne defaultcurrency update exchangerate
+ if ($form->{currency} ne $form->{defaultcurrency}) {
+ $form->{exchangerate} = $form->parse_amount($myconfig, $form->{exchangerate});
+
+ if ($form->{vc} eq 'customer') {
+ $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, $form->{exchangerate}, 0);
+ } else {
+ $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, 0, $form->{exchangerate});
+ }
+ } else {
+ $form->{exchangerate} = 1;
+ }
+
+ my $query = qq|SELECT fxgain_accno_id, fxloss_accno_id
+ FROM defaults|;
+ my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query);
+
+ my ($buysell);
+
+ if ($form->{vc} eq 'customer') {
+ $buysell = "buy";
+ } else {
+ $buysell = "sell";
+ }
+
+ my $ml;
+ my $where;
+
+ if ($form->{ARAP} eq 'AR') {
+ $ml = 1;
+ $where = qq|
+ (c.link = 'AR'
+ OR c.link LIKE 'AR:%')
+ |;
+ } else {
+ $ml = -1;
+ $where = qq|
+ (c.link = 'AP'
+ OR c.link LIKE '%:AP'
+ OR c.link LIKE '%:AP:%')
+ |;
+ }
+
+ my $paymentamount = $form->parse_amount($myconfig, $form->{amount});
+
+ # query to retrieve paid amount
+ $query = qq|SELECT paid FROM $form->{arap}
+ WHERE id = ?
+ FOR UPDATE|;
+ my $pth = $dbh->prepare($query) || $form->dberror($query);
+
+ my %audittrail;
+
+ # go through line by line
+ for my $i (1 .. $form->{rowcount}) {
+
+ $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"});
+ $form->{"due_$i"} = $form->parse_amount($myconfig, $form->{"due_$i"});
+
+ if ($form->{"checked_$i"} && $form->{"paid_$i"}) {
+
+ $paymentamount -= $form->{"paid_$i"};
+
+ # get exchangerate for original
+ $query = qq|SELECT $buysell
+ FROM exchangerate e
+ JOIN $form->{arap} a ON (a.transdate = e.transdate)
+ WHERE e.curr = '$form->{currency}'
+ AND a.id = $form->{"id_$i"}|;
+ my ($exchangerate) = $dbh->selectrow_array($query);
+
+ $exchangerate = 1 unless $exchangerate;
+
+ $query = qq|SELECT c.id
+ FROM chart c
+ JOIN acc_trans a ON (a.chart_id = c.id)
+ WHERE $where
+ AND a.trans_id = $form->{"id_$i"}|;
+ my ($id) = $dbh->selectrow_array($query);
+
+ $amount = $form->round_amount($form->{"paid_$i"} * $exchangerate, 2);
+
+ # add AR/AP
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount)
+ VALUES ($form->{"id_$i"}, $id, '$form->{datepaid}',
+ $amount * $ml)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ # add payment
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, source, memo)
+ VALUES ($form->{"id_$i"},
+ (SELECT id FROM chart
+ WHERE accno = '$paymentaccno'),
+ '$form->{datepaid}', $form->{"paid_$i"} * $ml * -1, |
+ .$dbh->quote($form->{source}).qq|, |
+ .$dbh->quote($form->{memo}).qq|)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ # add exchangerate difference if currency ne defaultcurrency
+ $amount = $form->round_amount($form->{"paid_$i"} * ($form->{exchangerate} - 1), 2);
+
+ if ($amount) {
+ # exchangerate difference
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, cleared, fx_transaction, source, memo)
+ VALUES ($form->{"id_$i"},
+ (SELECT id FROM chart
+ WHERE accno = '$paymentaccno'),
+ '$form->{datepaid}', $amount * $ml * -1, '0', '1', |
+ .$dbh->quote($form->{source}).qq|, |
+ .$dbh->quote($form->{memo}).qq|)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ # gain/loss
+ $amount = $form->round_amount($form->{"paid_$i"} * ($exchangerate - $form->{exchangerate}) * $ml * -1, 2);
+ if ($amount) {
+ my $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id;
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, cleared, fx_transaction)
+ VALUES ($form->{"id_$i"}, $accno_id,
+ '$form->{datepaid}', $amount, '0', '1')|;
+ $dbh->do($query) || $form->dberror($query);
+ }
+ }
+
+ $form->{"paid_$i"} = $form->round_amount($form->{"paid_$i"} * $exchangerate, 2);
+
+ $pth->execute($form->{"id_$i"}) || $form->dberror;
+ ($amount) = $pth->fetchrow_array;
+ $pth->finish;
+
+ $amount += $form->{"paid_$i"};
+
+ # update AR/AP transaction
+ $query = qq|UPDATE $form->{arap} set
+ paid = $amount,
+ datepaid = '$form->{datepaid}'
+ WHERE id = $form->{"id_$i"}|;
+ $dbh->do($query) || $form->dberror($query);
+
+ %audittrail = ( tablename => $form->{arap},
+ reference => $form->{source},
+ formname => $form->{formname},
+ action => 'posted',
+ id => $form->{"id_$i"} );
+
+ $form->audittrail($dbh, "", \%audittrail);
+
+ }
+ }
+
+
+ # record a AR/AP with a payment
+ if ($form->round_amount($paymentamount, 2)) {
+ $form->{invnumber} = "";
+ OP::overpayment("", $myconfig, $form, $dbh, $paymentamount, $ml, 1);
+ }
+
+ my $rc = $dbh->commit;
+ $dbh->disconnect;
+
+ $rc;
+
+}
+
+
+sub post_payments {
+ my ($self, $myconfig, $form) = @_;
+
+ # connect to database, turn AutoCommit off
+ my $dbh = $form->dbconnect_noauto($myconfig);
+
+ my $sth;
+
+ my ($paymentaccno) = split /--/, $form->{account};
+
+ # if currency ne defaultcurrency update exchangerate
+ if ($form->{currency} ne $form->{defaultcurrency}) {
+ $form->{exchangerate} = $form->parse_amount($myconfig, $form->{exchangerate});
+
+ if ($form->{vc} eq 'customer') {
+ $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, $form->{exchangerate}, 0);
+ } else {
+ $form->update_exchangerate($dbh, $form->{currency}, $form->{datepaid}, 0, $form->{exchangerate});
+ }
+ } else {
+ $form->{exchangerate} = 1;
+ }
+
+ my $query = qq|SELECT fxgain_accno_id, fxloss_accno_id
+ FROM defaults|;
+ my ($fxgain_accno_id, $fxloss_accno_id) = $dbh->selectrow_array($query);
+
+ my ($buysell);
+
+ if ($form->{vc} eq 'customer') {
+ $buysell = "buy";
+ } else {
+ $buysell = "sell";
+ }
+
+ my $ml;
+ my $where;
+
+ if ($form->{ARAP} eq 'AR') {
+ $ml = 1;
+ $where = qq|
+ (c.link = 'AR'
+ OR c.link LIKE 'AR:%')
+ |;
+ } else {
+ $ml = -1;
+ $where = qq|
+ (c.link = 'AP'
+ OR c.link LIKE '%:AP'
+ OR c.link LIKE '%:AP:%')
+ |;
+ }
+
+ # query to retrieve paid amount
+ $query = qq|SELECT paid FROM $form->{arap}
+ WHERE id = ?
+ FOR UPDATE|;
+ my $pth = $dbh->prepare($query) || $form->dberror($query);
+
+ my %audittrail;
+
+ my $overpayment = 0;
+ my $accno_id;
+
+ # go through line by line
+ for my $i (1 .. $form->{rowcount}) {
+
+ $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"});
+ $form->{"due_$i"} = $form->parse_amount($myconfig, $form->{"due_$i"});
+
+ if ($form->{"$form->{vc}_id_$i"} ne $sameid) {
+ # record a AR/AP with a payment
+ if ($overpayment > 0) {
+ $form->{invnumber} = "";
+ OP::overpayment("", $myconfig, $form, $dbh, $overpayment, $ml, 1);
+ }
+ $overpayment = 0;
+ $form->{"$form->{vc}_id"} = $form->{"$form->{vc}_id_$i"};
+ for (qw(source memo)) { $form->{$_} = $form->{"${_}_$i"} }
+ }
+
+ if ($form->{"checked_$i"} && $form->{"paid_$i"}) {
+
+ $overpayment += ($form->{"paid_$i"} - $form->{"due_$i"});
+
+ # get exchangerate for original
+ $query = qq|SELECT $buysell
+ FROM exchangerate e
+ JOIN $form->{arap} a ON (a.transdate = e.transdate)
+ WHERE e.curr = '$form->{currency}'
+ AND a.id = $form->{"id_$i"}|;
+ my ($exchangerate) = $dbh->selectrow_array($query);
+
+ $exchangerate ||= 1;
+
+ $query = qq|SELECT c.id
+ FROM chart c
+ JOIN acc_trans a ON (a.chart_id = c.id)
+ WHERE $where
+ AND a.trans_id = $form->{"id_$i"}|;
+ my ($id) = $dbh->selectrow_array($query);
+
+ $paid = ($form->{"paid_$i"} > $form->{"due_$i"}) ? $form->{"due_$i"} : $form->{"paid_$i"};
+ $amount = $form->round_amount($paid * $exchangerate, 2);
+
+ # add AR/AP
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount)
+ VALUES ($form->{"id_$i"}, $id, '$form->{datepaid}',
+ $amount * $ml)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ $query = qq|SELECT id
+ FROM chart
+ WHERE accno = '$paymentaccno'|;
+ ($accno_id) = $dbh->selectrow_array($query);
+
+ # add payment
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, source, memo)
+ VALUES ($form->{"id_$i"}, $accno_id, '$form->{datepaid}',
+ $paid * $ml * -1, |
+ .$dbh->quote($form->{source}).qq|, |
+ .$dbh->quote($form->{memo}).qq|)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ # add exchangerate difference if currency ne defaultcurrency
+ $amount = $form->round_amount($paid * ($form->{exchangerate} - 1) * $ml * -1, 2);
+
+ if ($amount) {
+ # exchangerate difference
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, source, memo)
+ VALUES ($form->{"id_$i"}, $accno_id, '$form->{datepaid}',
+ $amount, |
+ .$dbh->quote($form->{source}).qq|, |
+ .$dbh->quote($form->{memo}).qq|)|;
+ $dbh->do($query) || $form->dberror($query);
+
+ # gain/loss
+ $amount = $form->round_amount($paid * ($exchangerate - $form->{exchangerate}) * $ml * -1, 2);
+ if ($amount) {
+ $accno_id = ($amount > 0) ? $fxgain_accno_id : $fxloss_accno_id;
+ $query = qq|INSERT INTO acc_trans (trans_id, chart_id, transdate,
+ amount, fx_transaction)
+ VALUES ($form->{"id_$i"}, $accno_id,
+ '$form->{datepaid}', $amount, '1')|;
+ $dbh->do($query) || $form->dberror($query);
+ }
+ }
+
+ $paid = $form->round_amount($paid * $exchangerate, 2);
+
+ $pth->execute($form->{"id_$i"}) || $form->dberror;
+ ($amount) = $pth->fetchrow_array;
+ $pth->finish;
+
+ $amount += $paid;
+
+ # update AR/AP transaction
+ $query = qq|UPDATE $form->{arap} set
+ paid = $amount,
+ datepaid = '$form->{datepaid}'
+ WHERE id = $form->{"id_$i"}|;
+ $dbh->do($query) || $form->dberror($query);
+
+ %audittrail = ( tablename => $form->{arap},
+ reference => $form->{source},
+ formname => $form->{formname},
+ action => 'posted',
+ id => $form->{"id_$i"} );
+
+ $form->audittrail($dbh, "", \%audittrail);
+
+ }
+
+ $sameid = $form->{"$form->{vc}_id_$i"};
+
+ }
+
+ # record a AR/AP with a payment
+ if ($overpayment > 0) {
+ $form->{invnumber} = "";
+ OP::overpayment("", $myconfig, $form, $dbh, $overpayment, $ml, 1);
+ }
+
+ my $rc = $dbh->commit;
+ $dbh->disconnect;
+
+ $rc;
+
+}
+
+
+1;
+
Index: openacs-4/packages/accounts-payables/tcl/accounts-payables-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/tcl/accounts-payables-procs.tcl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/tcl/accounts-payables-procs.tcl 11 Nov 2005 05:33:40 -0000 1.1
@@ -0,0 +1,105 @@
+ad_library {
+ procedures for the Accounts Payables package
+
+ @author ported by Torben Brosten (torben@kappacorp.com)
+ @creation-date October 2005
+
+}
+
+ad_proc integer_to_text -public {
+ -number
+ -locale
+} {
+ @param number Currently only translates whole numbers.
+ @param locale
+ @return a written language representation of a number
+ @author ported by Torben Brosten
+
+ Returns the number unchanged if the locale is not represented. This is ported from
+ sql-ledger, which has many more locales represented. Read the source comments of this
+ procedure for details in adding locales not yet supported.
+} {
+
+ # Num2text is fragmented into separate files in sql-ledger. Each language has a
+ # directory with language specific files in a subdirectory of directory sql-ledger/locale
+ # For example, consider the French language modifications:
+ # perl num2text code is in file: sql-ledger/locale/fr/Num2text
+ # Language proper name is in file: sql-ledger/locale/fr/LANGUAGE
+ # Contact info about the procedure is in: sql-ledger/locale/fr/COPYING
+
+ # isolate the integer part
+ set decimal_loc [string first "." number]
+
+ if {$decimal_loc > 0} {
+ set integer [string range 0 $decimal_loc]
+ } elseif { $decimal_loc == 0 } {
+ set integer 0
+ } elseif { $decimal_loc == -1 } {
+ set integer $number
+ }
+
+ set digit_count [string length $integer]
+
+ # e_plus means e+ (tcl notation for power of ten), ie ones, thousands, millions, billions, trillions, quadrillions, quintillions
+ #set e_plus_list [list 0 3 6 9 12 15 18]
+
+ # sets e_plus_array into groups of NNN where N is a digit between 0 and 9
+ for {set i 0} {$i < $digit_count} {incr i 3} {
+ # prepending zeros for possible hanging left most digit(s) of number
+ set e_plus_array($i) [string range "000[string range $integer [expr $digit_count - $i - 3 ] [expr $digit_count - $i - 1]]" end-2 end]
+ }
+
+ # make substitutions based on grammatical rules.
+ # There are locale specific exceptions (see switch conditions below).
+ # Each loop expands an e_plus NNN grouping into a string of translation-keys
+ foreach e_plus [array names e_plus_array] {
+
+ switch -exact -- $locale {
+
+
+ default {
+ set first_digit [string range $e_plus_array($e_plus) end-2 end-2]
+ set middle_digit [string range $e_plus_array($e_plus) end-1 end-1]
+ set last_digit [string range $e_plus_array($e_plus) end end]
+
+ if { ![string equal $first_digit "0"] } {
+ # must be something like 100 ie. one hundred
+ set written_array($e_plus) "#accounts-ledger.${first_digit}# #accounts-ledger.10__2#"
+ }
+ if { $middle_digit > 1 } {
+ if { [string equal $last_digit "0"] } {
+ # 20, 30, 40, 50, 60, 70, 80 and 90 have specific keys
+ append written_array($e_plus) " #accounts-ledger.${middle_digit}0#"
+ } else {
+ # 21..
+ append written_array($e_plus) " #accounts-ledger.${middle_digit}0# #accounts-ledger.${last_digit}#"
+ }
+ } elseif { [string equal $middle_digit "1"] } {
+ # 10 .. 19 do not use grammar because there are so many locale variations
+ append written_array($e_plus) " #accounts-ledger.${middle_digit}${last_digit}#"
+ } elseif { ![string equal $last_digit "0"] } {
+ # 1 .. 9
+ append written_array($e_plus) " #accounts-ledger.${last_digit}#"
+ }
+ # if NNN was 000 then written_array might not exist..
+ if { $e_plus > 0 && [info exists written_array($e_plus)] } {
+ append written_array($e_plus) " #accounts-ledger.10__${e_plus}#"
+ }
+ }
+
+ }
+ }
+ # combine string for return
+ set written_number ""
+ for {set iii 0} {$iii < $digit_count} {incr iii 3} {
+ if { [info exists written_array($iii)] } {
+ set written_number "$written_array($iii) ${written_number}"
+ }
+ }
+ if {[string length [string trim $written_number]] == 0 } {
+ # must be zero
+ return "#accounts-ledger.0#"
+ } else {
+ return [string trim $written_number]
+ }
+}
\ No newline at end of file
Index: openacs-4/packages/accounts-payables/tcl/ap.pl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/tcl/Attic/ap.pl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/tcl/ap.pl 11 Nov 2005 05:33:40 -0000 1.1
@@ -0,0 +1,40 @@
+#=====================================================================
+# SQL-Ledger Accounting
+# Copyright (C) 2000
+#
+# Author: DWS Systems Inc.
+# Web: http://www.sql-ledger.org
+#
+# Contributors:
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#======================================================================
+#
+# Accounts Payable
+#
+#======================================================================
+
+use SL::PE;
+use SL::IR;
+
+require "$form->{path}/arap.pl";
+require "$form->{path}/arapprn.pl";
+require "$form->{path}/aa.pl";
+
+$form->{vc} = 'vendor';
+$form->{ARAP} = 'AP';
+
+1;
+# end of main
+
Index: openacs-4/packages/accounts-payables/tcl/cp.pl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/accounts-payables/tcl/Attic/cp.pl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/accounts-payables/tcl/cp.pl 11 Nov 2005 05:33:41 -0000 1.1
@@ -0,0 +1,1264 @@
+#=====================================================================
+# SQL-Ledger Accounting
+# Copyright (c) 2002
+#
+# Author: DWS Systems Inc.
+# Web: http://www.sql-ledger.org
+#
+# Contributors:
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#======================================================================
+#
+# Payment module
+#
+#======================================================================
+
+
+use SL::CP;
+use SL::OP;
+use SL::IS;
+use SL::IR;
+
+require "$form->{path}/arap.pl";
+
+1;
+# end of main
+
+
+sub payment {
+
+ if ($form->{type} eq 'receipt') {
+ $form->{ARAP} = "AR";
+ $form->{arap} = "ar";
+ $form->{vc} = "customer";
+ $form->{formname} = "receipt";
+ }
+ if ($form->{type} eq 'check') {
+ $form->{ARAP} = "AP";
+ $form->{arap} = "ap";
+ $form->{vc} = "vendor";
+ $form->{formname} = "check";
+ }
+
+ $form->{payment} = "payment";
+
+ $form->{callback} = "$form->{script}?action=payment&path=$form->{path}&login=$form->{login}&sessionid=$form->{sessionid}&all_vc=$form->{all_vc}&type=$form->{type}";
+
+ # setup customer/vendor selection for open invoices
+ if ($form->{all_vc}) {
+ $form->all_vc(\%myconfig, $form->{vc}, $form->{ARAP}, undef, $form->{datepaid});
+ } else {
+ CP->get_openvc(\%myconfig, \%$form);
+ }
+
+ $form->{"select$form->{vc}"} = "";
+ if (@{ $form->{"all_$form->{vc}"} }) {
+ $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
+ for (@{ $form->{"all_$form->{vc}"} }) { $form->{"select$form->{vc}"} .= qq|