From 2c757d7db4cb6a7b9655de13206fcc84fb7ce61f Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 14 May 2006 16:47:31 +0000 Subject: [PATCH] first part of ACL and re-skinning work and some other small stuff --- CREDITS | 6 + Changes.1.7.0 | 39 ++ FS/FS/AccessRight.pm | 77 +++ FS/FS/CGI.pm | 5 +- FS/FS/Schema.pm | 87 ++- FS/FS/UI/Web.pm | 14 +- FS/FS/access_group.pm | 121 +++++ FS/FS/access_groupagent.pm | 124 +++++ FS/FS/access_right.pm | 127 +++++ FS/FS/access_user.pm | 167 ++++++ FS/FS/access_user_pref.pm | 127 +++++ FS/FS/access_usergroup.pm | 144 +++++ FS/FS/agent_type.pm | 3 +- FS/FS/cust_bill.pm | 5 + FS/FS/m2m_Common.pm | 110 ++++ FS/FS/part_pkg/billoneday.pm | 48 -- FS/FS/payby.pm | 3 +- FS/FS/svc_domain.pm | 6 +- FS/MANIFEST | 1 + FS/bin/freeside-addoutsourceuser | 4 +- FS/t/AccessRight.t | 5 + FS/t/access_group.t | 5 + FS/t/access_groupagent.t | 5 + FS/t/access_right.t | 5 + FS/t/access_user.t | 5 + FS/t/access_user_pref.t | 5 + FS/t/access_usergroup.t | 5 + htetc/handler.pl | 2 + httemplate/autohandler | 10 +- httemplate/browse/access_group.html | 33 ++ httemplate/browse/access_user.html | 63 +++ httemplate/browse/agent_type.cgi | 108 ++-- httemplate/browse/cust_main_county.cgi | 185 +++---- httemplate/browse/msgcat.cgi | 20 +- httemplate/browse/part_pkg.cgi | 14 +- httemplate/edit/access_group.html | 10 + httemplate/edit/access_user.html | 37 ++ httemplate/edit/agent_type.cgi | 52 +- httemplate/edit/cust_bill_pay.cgi | 87 ++- httemplate/edit/cust_credit.cgi | 71 ++- httemplate/edit/cust_credit_bill.cgi | 90 ++-- httemplate/edit/cust_main.cgi | 105 ++-- httemplate/edit/cust_pkg.cgi | 136 +++-- httemplate/edit/elements/edit.html | 36 +- httemplate/edit/part_referral.cgi | 36 +- httemplate/edit/part_virtual_field.cgi | 19 +- httemplate/edit/process/access_group.html | 5 + httemplate/edit/process/access_user.html | 8 + httemplate/edit/process/agent_type.cgi | 39 +- httemplate/edit/process/cust_bill_pay.cgi | 16 +- httemplate/edit/process/cust_credit.cgi | 17 +- httemplate/edit/process/cust_credit_bill.cgi | 16 +- httemplate/edit/process/elements/process.html | 19 +- httemplate/edit/svc_domain.cgi | 55 +- httemplate/elements/checkboxes-table.html | 110 ++++ httemplate/elements/cssexpr.js | 66 +++ httemplate/elements/footer.html | 3 + httemplate/elements/header.html | 289 +++++++++- httemplate/elements/menubar.html | 1 + httemplate/elements/select-access_group.html | 15 + httemplate/elements/tr-select-access_group.html | 22 + httemplate/elements/xmenu.css | 185 +++++++ httemplate/elements/xmenu.js | 668 ++++++++++++++++++++++++ httemplate/index.html | 12 +- httemplate/misc/batch-cust_pay.html | 2 - httemplate/misc/payment.cgi | 38 +- httemplate/search/cust_bill.cgi | 165 ------ httemplate/search/cust_main-otaker.cgi | 47 +- httemplate/search/cust_main-payinfo.html | 20 - httemplate/search/cust_main-quickpay.html | 44 -- httemplate/search/cust_main.cgi | 50 +- httemplate/search/cust_pay.html | 18 - httemplate/search/cust_pkg_report.cgi | 39 +- httemplate/search/report_cust_bill.html | 54 +- httemplate/search/report_cust_credit.html | 62 +-- httemplate/search/report_cust_pay.html | 81 +-- httemplate/search/report_prepaid_income.html | 26 +- httemplate/search/report_tax.html | 73 ++- httemplate/search/sqlradius.html | 52 +- httemplate/search/svc_acct.html | 19 - httemplate/search/svc_domain.cgi | 2 +- httemplate/search/svc_domain.html | 19 - httemplate/search/svc_external.cgi | 21 +- httemplate/view/cust_main/packages.html | 4 +- httemplate/view/cust_main/payment_history.html | 26 +- 85 files changed, 3577 insertions(+), 1198 deletions(-) create mode 100644 Changes.1.7.0 create mode 100644 FS/FS/AccessRight.pm create mode 100644 FS/FS/access_group.pm create mode 100644 FS/FS/access_groupagent.pm create mode 100644 FS/FS/access_right.pm create mode 100644 FS/FS/access_user.pm create mode 100644 FS/FS/access_user_pref.pm create mode 100644 FS/FS/access_usergroup.pm create mode 100644 FS/FS/m2m_Common.pm delete mode 100644 FS/FS/part_pkg/billoneday.pm create mode 100644 FS/t/AccessRight.t create mode 100644 FS/t/access_group.t create mode 100644 FS/t/access_groupagent.t create mode 100644 FS/t/access_right.t create mode 100644 FS/t/access_user.t create mode 100644 FS/t/access_user_pref.t create mode 100644 FS/t/access_usergroup.t create mode 100644 httemplate/browse/access_group.html create mode 100644 httemplate/browse/access_user.html create mode 100644 httemplate/edit/access_group.html create mode 100644 httemplate/edit/access_user.html create mode 100644 httemplate/edit/process/access_group.html create mode 100644 httemplate/edit/process/access_user.html create mode 100644 httemplate/elements/checkboxes-table.html create mode 100644 httemplate/elements/cssexpr.js create mode 100644 httemplate/elements/select-access_group.html create mode 100644 httemplate/elements/tr-select-access_group.html create mode 100644 httemplate/elements/xmenu.css create mode 100644 httemplate/elements/xmenu.js delete mode 100755 httemplate/search/cust_bill.cgi delete mode 100755 httemplate/search/cust_main-payinfo.html delete mode 100755 httemplate/search/cust_main-quickpay.html delete mode 100755 httemplate/search/cust_pay.html delete mode 100755 httemplate/search/svc_acct.html delete mode 100755 httemplate/search/svc_domain.html diff --git a/CREDITS b/CREDITS index 930b4f351..72b049129 100644 --- a/CREDITS +++ b/CREDITS @@ -157,5 +157,11 @@ Perl backend version (c) copyright 2005 Nathan Schmidt Scott Edwards contributed magic for XMLHTTP error handling, and other patches. +Contains XMenu +by Erik Arvidsson, licensed under the terms of the GNU GPL. + +Contains public domain artwork from openclipart.org by mimooh and other +authors. + Everything else is my (Ivan Kohler ) fault. diff --git a/Changes.1.7.0 b/Changes.1.7.0 new file mode 100644 index 000000000..8dcc36e08 --- /dev/null +++ b/Changes.1.7.0 @@ -0,0 +1,39 @@ +- tax report overhaul +- package classes +- UI overhaul. No more Apache::ASP support, HTML::Mason only +- lots of CDR/telephony work +- inventory classes and inventory (better description from directleap/specs.txt) +- vonage click2call bs :) +- zip code report +- sales/credit/receipt summary report now has options for agent, 12mo cumulative totals +- gross sales report/graph broken down by agent and package class +- config switch to base tax off shipping address if present (warning: tax reports can take a long time with this switch on) +- plesk provisioning + +-------- some of the above, nicely: + +- Charge taxes based on shipping address if present. Note that tax + reports can take a bit longer than they used to. + +- Per-agent A/R Aging + - Bookeeping/Collections | Accounts Receivable Aging Summary + +- Per-agent Sales/Credit/Receipt Summary and "pre-selection" of agent + and time period as you requested. + - Bookeeping/Collections | Sales, Credits and Receipts Summary + +- Package classes + - go to Sysadmin | View/Edit package classes and create some classes + - go to Sysadmin | View/Edit package definitions, edit the existing + package defs and put them into classes + +- The sales report you requested, broken down by agent and + package class. This works fine right now, but it will show more + information once you enter some package classes. + - Bookeeping/Collections | Sales report (by agent, package class ... + +-------- + +and... +- ACLs +- Agent virtualization diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm new file mode 100644 index 000000000..01d63e35d --- /dev/null +++ b/FS/FS/AccessRight.pm @@ -0,0 +1,77 @@ +package FS::AccessRight; + +use strict; +user vars qw(@rights %rights); +use Tie::IxHash; + +=head1 NAME + +FS::AccessRight - Access control rights. + +=head1 SYNOPSIS + + use FS::AccessRight; + +=head1 DESCRIPTION + +Access control rights - Permission to perform specific actions that can be +assigned to users and/or groups. + +=cut + +@rights = ( + 'Reports' => [ + '_desc' => 'Access to high-level reporting', + ], + 'Configuration' => [ + '_desc' => 'Access to configuration', + + 'Settings' => {}, + + 'agent' => [ + '_desc' => 'Master access to reseller configuration', + 'agent_type' => {}, + 'agent' => {}, + ], + + 'export_svc_pkg' => [ + '_desc' => 'Access to export, service and package configuration', + 'part_export' => {}, + 'part_svc' => {}, + 'part_pkg' => {}, + 'pkg_class' => {}, + ], + + 'billing' => [ + '_desc' => 'Access to billing configuration', + 'payment_gateway' => {}, + 'part_bill_event' => {}, + 'prepay_credit' => {}, + 'rate' => {}, + 'cust_main_county' => {}, + ], + + 'dialup' => [ + '_desc' => 'Access to dialup configuraiton', + 'svc_acct_pop' => {}, + ], + + 'broadband' => [ + '_desc' => 'Access to broadband configuration', + 'router' => {}, + 'addr_block' => {}, + ], + + 'misc' => [ + 'part_referral' => {}, + 'part_virtual_field' => {}, + 'msgcat' => {}, + 'inventory_class' => {}, + ], + + }, + +); + +#turn it into a more hash-like structure, but ordered via IxHash + diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm index f1f2a3dca..d598f5218 100644 --- a/FS/FS/CGI.pm +++ b/FS/FS/CGI.pm @@ -62,9 +62,9 @@ sub header { - $title +
$title
-

+
END $x .= $menubar. "

" if $menubar; $x; @@ -115,6 +115,7 @@ sub menubar { #$menubar=menubar('Main Menu', '../', 'Item', 'url', ... ); my($item,$url,@html); while (@_) { ($item,$url)=splice(@_,0,2); + next if $item =~ /^\s*Main\s+Menu\s*$/i; push @html, qq!$item!; } join(' | ',@html); diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 9125758d0..e81185666 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -244,6 +244,8 @@ sub tables_hashref { my $username_len = 32; #usernamemax config file + # name type nullability length default local + return { 'agent' => { @@ -445,6 +447,9 @@ sub tables_hashref { 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ], [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ], [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ], + [ 'ship_last' ], [ 'ship_company' ], + [ 'payby' ], [ 'paydate' ], + ], }, @@ -1444,16 +1449,94 @@ sub tables_hashref { 'inventory_class' => { 'columns' => [ - 'classnum', 'serial', '', '', '', '', - 'classname', 'varchar', $char_d, '', '', '', + 'classnum', 'serial', '', '', '', '', + 'classname', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'classnum', 'unique' => [], 'index' => [], }, + 'access_user' => { + 'columns' => [ + 'usernum', 'serial', '', '', '', '', + 'username', 'varchar', '', $char_d, '', '', + '_password', 'varchar', '', $char_d, '', '', + 'last', 'varchar', '', $char_d, '', '', + 'first', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'usernum', + 'unique' => [ [ 'username' ] ], + 'index' => [], + }, + + 'access_user_pref' => { + 'columns' => [ + 'prefnum', 'serial', '', '', '', '', + 'usernum', 'int', '', '', '', '', + 'prefname', 'varchar', '', $char_d, '', '', + 'prefvalue', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'prefnum', + 'unique' => [], + 'index' => [ [ 'usernum' ] ], + }, + + 'access_group' => { + 'columns' => [ + 'groupnum', 'serial', '', '', '', '', + 'groupname', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'groupnum', + 'unique' => [ [ 'groupname' ] ], + 'index' => [], + }, + + 'access_usergroup' => { + 'columns' => [ + 'usergroupnum', 'serial', '', '', '', '', + 'usernum', 'int', '', '', '', '', + 'groupnum', 'int', '', '', '', '', + ], + 'primary_key' => 'usergroupnum', + 'unique' => [ [ 'usernum', 'groupnum' ] ], + 'index' => [ [ 'usernum' ] ], + }, + + 'access_groupagent' => { + 'columns' => [ + 'groupagentnum', 'serial', '', '', '', '', + 'groupnum', 'int', '', '', '', '', + 'agentnum', 'int', '', '', '', '', + ], + 'primary_key' => 'groupagentnum', + 'unique' => [ [ 'groupnum', 'agentnum' ] ], + 'index' => [ [ 'groupnum' ] ], + }, + + 'access_right' => { + 'columns' => [ + 'rightnum', 'serial', '', '', '', '', + 'righttype', 'varchar', '', $char_d, '', '', + 'rightobjnum', 'int', '', '', '', '', + 'rightname', 'varchar', '', '', '', '', + ], + 'primary_key' => 'rightnum', + 'unique' => [ [ 'righttype', 'rightobjnum', 'rightname' ] ], + 'index' => [], + }, + }; + #'new_table' => { + # 'columns' => [ + # 'num', 'serial', '', '', '', '', + # ], + # 'primary_key' => 'num', + # 'unique' => [], + # 'index' => [], + #}, + } =back diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index dc45e0188..10ddbf33f 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -184,6 +184,10 @@ sub process { $self->job_status(@args); + } else { + + die "unknown sub $sub"; + } } @@ -228,11 +232,19 @@ sub start_job { my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) ); if ( $error ) { + + warn "job not inserted: $error\n" + if $DEBUG; + $error; #this doesn't seem to be handled well, # will trigger "illegal jobnum" below? # (should never be an error inserting the job, though, only thing # would be Pg f%*kage) } else { + + warn "job inserted successfully with jobnum ". $job->jobnum. "\n" + if $DEBUG; + $job->jobnum; } @@ -253,7 +265,7 @@ sub job_status { my @return; if ( $job && $job->status ne 'failed' ) { @return = ( 'progress', $job->statustext ); - } elsif ( !$job ) { #handle job gone case : job sucessful + } elsif ( !$job ) { #handle job gone case : job successful # so close popup, redirect parent window... @return = ( 'complete' ); } else { diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm new file mode 100644 index 000000000..9d870e57f --- /dev/null +++ b/FS/FS/access_group.pm @@ -0,0 +1,121 @@ +package FS::access_group; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_group - Object methods for access_group records + +=head1 SYNOPSIS + + use FS::access_group; + + $record = new FS::access_group \%hash; + $record = new FS::access_group { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_group object represents an example. FS::access_group inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item groupnum - primary key + +=item groupname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_group'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('groupnum') + || $self->ut_text('groupname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_groupagent.pm b/FS/FS/access_groupagent.pm new file mode 100644 index 000000000..6b5def1a3 --- /dev/null +++ b/FS/FS/access_groupagent.pm @@ -0,0 +1,124 @@ +package FS::access_groupagent; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_groupagent - Object methods for access_groupagent records + +=head1 SYNOPSIS + + use FS::access_groupagent; + + $record = new FS::access_groupagent \%hash; + $record = new FS::access_groupagent { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_groupagent object represents an example. FS::access_groupagent inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item groupagentnum - primary key + +=item groupnum - + +=item agentnum - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_groupagent'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('groupagentnum') + || $self->ut_number('groupnum') + || $self->ut_number('agentnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm new file mode 100644 index 000000000..67200f245 --- /dev/null +++ b/FS/FS/access_right.pm @@ -0,0 +1,127 @@ +package FS::access_right; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_right - Object methods for access_right records + +=head1 SYNOPSIS + + use FS::access_right; + + $record = new FS::access_right \%hash; + $record = new FS::access_right { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_right object represents an example. FS::access_right inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item rightnum - primary key + +=item righttype - + +=item rightobjnum - + +=item rightname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_right'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('rightnum') + || $self->ut_text('righttype') + || $self->ut_text('rightobjnum') + || $self->ut_text('rightname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm new file mode 100644 index 000000000..ca311d3b8 --- /dev/null +++ b/FS/FS/access_user.pm @@ -0,0 +1,167 @@ +package FS::access_user; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::m2m_Common; +use FS::access_usergroup; + +@ISA = qw( FS::m2m_Common FS::Record ); + +=head1 NAME + +FS::access_user - Object methods for access_user records + +=head1 SYNOPSIS + + use FS::access_user; + + $record = new FS::access_user \%hash; + $record = new FS::access_user { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_user object represents an example. FS::access_user inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item usernum - primary key + +=item username - + +=item _password - + +=item last - + +=item first - + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_user'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('usernum') + || $self->ut_text('username') + || $self->ut_text('_password') + || $self->ut_text('last') + || $self->ut_text('first') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item name + +Returns a name string for this user: "Last, First". + +=cut + +sub name { + my $self = shift; + $self->get('last'). ', '. $self->first; +} + +=item access_usergroup + +=cut + +sub access_usergroup { + my $self = shift; + qsearch( 'access_usergroup', { 'usernum' => $self->usernum } ); +} + +#=item access_groups +# +#=cut +# +#sub access_groups { +# +#} +# +#=item access_groupnames +# +#=cut +# +#sub access_groupnames { +# +#} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_user_pref.pm b/FS/FS/access_user_pref.pm new file mode 100644 index 000000000..ff957f2a1 --- /dev/null +++ b/FS/FS/access_user_pref.pm @@ -0,0 +1,127 @@ +package FS::access_user_pref; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_user_pref - Object methods for access_user_pref records + +=head1 SYNOPSIS + + use FS::access_user_pref; + + $record = new FS::access_user_pref \%hash; + $record = new FS::access_user_pref { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_user_pref object represents an example. FS::access_user_pref inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item prefnum - primary key + +=item usernum - + +=item prefname - + +=item prefvalue - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_user_pref'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('prefnum') + || $self->ut_number('usernum') + || $self->ut_text('prefname') + || $self->ut_textn('prefvalue') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_usergroup.pm b/FS/FS/access_usergroup.pm new file mode 100644 index 000000000..4d8836c15 --- /dev/null +++ b/FS/FS/access_usergroup.pm @@ -0,0 +1,144 @@ +package FS::access_usergroup; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::access_user; +use FS::access_group; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_usergroup - Object methods for access_usergroup records + +=head1 SYNOPSIS + + use FS::access_usergroup; + + $record = new FS::access_usergroup \%hash; + $record = new FS::access_usergroup { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_usergroup object represents an example. FS::access_usergroup inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item usergroupnum - primary key + +=item usernum - + +=item groupnum - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_usergroup'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('usergroupnum') + || $self->ut_number('usernum') + || $self->ut_number('groupnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item access_user + +=cut + +sub access_user { + my $self = shift; + qsearchs( 'access_user', { 'usernum' => $self->usernum } ); +} + +=item access_group + +=cut + +sub access_group { + my $self = shift; + qsearchs( 'access_group', { 'groupnum' => $self->groupnum } ); +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm index 968b3b72e..b28c57285 100644 --- a/FS/FS/agent_type.pm +++ b/FS/FS/agent_type.pm @@ -3,10 +3,11 @@ package FS::agent_type; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch ); +use FS::m2m_Common; use FS::agent; use FS::type_pkgs; -@ISA = qw( FS::Record ); +@ISA = qw( FS::m2m_Common FS::Record ); =head1 NAME diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index cce028b2c..bcae4d646 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2463,6 +2463,7 @@ use Data::Dumper; use MIME::Base64; sub process_re_X { my( $method, $job ) = ( shift, shift ); + warn "process_re_X $method for job $job\n" if $DEBUG; my $param = thaw(decode_base64(shift)); warn Dumper($param) if $DEBUG; @@ -2478,6 +2479,10 @@ sub process_re_X { sub re_X { my($method, $job, %param ) = @_; # [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], + if ( $DEBUG ) { + warn "re_X $method for job $job with param:\n". + join( '', map { " $_ => ". $param{$_}. "\n" } keys %param ); + } #some false laziness w/search/cust_bill.html my $distinct = ''; diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm new file mode 100644 index 000000000..fd8700a2d --- /dev/null +++ b/FS/FS/m2m_Common.pm @@ -0,0 +1,110 @@ +package FS::m2m_Common; + +use strict; +use vars qw( @ISA $DEBUG ); +use FS::Schema qw( dbdef ); +use FS::Record qw( qsearch qsearchs ); #dbh ); + +@ISA = qw( FS::Record ); + +$DEBUG = 0; + +=head1 NAME + +FS::m2m_Common - Base class for classes in a many-to-many relationship + +=head1 SYNOPSIS + +use FS::m2m_Common; + +@ISA = qw( FS::m2m_Common ); + +=head1 DESCRIPTION + +FS::m2m_Common is intended as a base class for classes which have a +many-to-many relationship with another table (via a linking table). + +Note: It is currently assumed that the link table contains two fields +named the same as the primary keys of ths base and target tables. + +=head1 METHODS + +=over 4 + +=item process_m2m + +=cut + +sub process_m2m { + my( $self, %opt ) = @_; + + my $self_pkey = $self->dbdef_table->primary_key; + + my $link_table = $self->_load_table($opt{'link_table'}); + + my $target_table = $self->_load_table($opt{'target_table'}); + my $target_pkey = dbdef->table($target_table)->primary_key; + + foreach my $target_obj ( qsearch($target_table, {} ) ) { + + my $targetnum = $target_obj->$target_pkey(); + + my $link_obj = qsearchs( $link_table, { + $self_pkey => $self->$self_pkey(), + $target_pkey => $targetnum, + }); + + if ( $link_obj && ! $opt{'params'}->{"$target_pkey$targetnum"} ) { + + my $d_link_obj = $link_obj; #need to save $link_obj for below. + my $error = $d_link_obj->delete; + die $error if $error; + + } elsif ( $opt{'params'}->{"$target_pkey$targetnum"} && ! $link_obj ) { + + #ok to clobber it now (but bad form nonetheless?) + #$link_obj = new "FS::$link_table" ( { + $link_obj = "FS::$link_table"->new( { + $self_pkey => $self->$self_pkey(), + $target_pkey => $targetnum, + }); + my $error = $link_obj->insert; + die $error if $error; + } + + } + + ''; +} + +sub _load_table { + my( $self, $table ) = @_; + eval "use FS::$table"; + die $@ if $@; + $table; +} + +#=item target_table +# +#=cut +# +#sub target_table { +# my $self = shift; +# my $target_table = $self->_target_table; +# eval "use FS::$target_table"; +# die $@ if $@; +# $target_table; +#} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L + +=cut + +1; + diff --git a/FS/FS/part_pkg/billoneday.pm b/FS/FS/part_pkg/billoneday.pm deleted file mode 100644 index 8740547a3..000000000 --- a/FS/FS/part_pkg/billoneday.pm +++ /dev/null @@ -1,48 +0,0 @@ -package FS::part_pkg::billoneday; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'charge a full month every (selectable) billing day', - 'fields' => { - 'setup_fee' => { 'name' => 'Setup fee for this package', - 'default' => 0, - }, - 'recur_fee' => { 'name' => 'Recurring fee for this package', - 'default' => 0, - }, - 'cutoff_day' => { 'name' => 'billing day', - 'default' => 1, - }, - - }, - 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', - 'freq' => 'm', - 'weight' => 30, -); - -sub calc_recur { - my($self, $cust_pkg, $sdate ) = @_; - - my $mnow = $$sdate; - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); - my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - - if($mday > $self->option('cutoff_date') and $mstart != $mnow ) { - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - } - else{ - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year); - } - $self->option('recur_fee'); -} -1; diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 4425df040..9f8b68918 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -115,7 +115,8 @@ sub cust_payby2longname { =head1 BUGS -This should eventually be an actual database table. +This should eventually be an actual database table, and all tables that +currently have a char payby field should have a foreign key into here instead. =head1 SEE ALSO diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 191d85604..bdaf79b2f 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -230,7 +230,11 @@ sub delete { my $error = $domain_record->delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "can't delete DNS entry: ". + join(' ', map $domain_record->$_(), + qw( reczone recaf rectype recdata ) + ). + ":$error"; } } diff --git a/FS/MANIFEST b/FS/MANIFEST index c309251ff..bd810a8db 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -339,3 +339,4 @@ FS/access_groupagent.pm t/access_groupagent.t FS/access_right.pm t/access_right.t +FS/m2m_Common.pm diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser index cad07f1fd..02a435141 100644 --- a/FS/bin/freeside-addoutsourceuser +++ b/FS/bin/freeside-addoutsourceuser @@ -3,6 +3,7 @@ username=$1 domain=$2 password=$3 +realdomain=$4 freeside-adduser -h /usr/local/etc/freeside/htpasswd \ -s conf.DBI:Pg:dbname=$domain/secrets \ @@ -10,6 +11,5 @@ freeside-adduser -h /usr/local/etc/freeside/htpasswd \ $username $password 2>/dev/null [ -e /usr/local/etc/freeside/dbdef.DBI:Pg:dbname=$domain ] \ - || ( freeside-setup -s $username 2>/dev/null; \ - /home/ivan/freeside/bin/populate-msgcat $username 2>/dev/null ) + || ( freeside-setup -d $realdomain $username 2>/dev/null ) diff --git a/FS/t/AccessRight.t b/FS/t/AccessRight.t new file mode 100644 index 000000000..a96684224 --- /dev/null +++ b/FS/t/AccessRight.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::AccessRight; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_group.t b/FS/t/access_group.t new file mode 100644 index 000000000..be141099b --- /dev/null +++ b/FS/t/access_group.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_group; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_groupagent.t b/FS/t/access_groupagent.t new file mode 100644 index 000000000..aff1f2524 --- /dev/null +++ b/FS/t/access_groupagent.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_groupagent; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_right.t b/FS/t/access_right.t new file mode 100644 index 000000000..66cd362e8 --- /dev/null +++ b/FS/t/access_right.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_right; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_user.t b/FS/t/access_user.t new file mode 100644 index 000000000..cab679d8d --- /dev/null +++ b/FS/t/access_user.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_user; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_user_pref.t b/FS/t/access_user_pref.t new file mode 100644 index 000000000..282209830 --- /dev/null +++ b/FS/t/access_user_pref.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_user_pref; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_usergroup.t b/FS/t/access_usergroup.t new file mode 100644 index 000000000..383a7cf9c --- /dev/null +++ b/FS/t/access_usergroup.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_usergroup; +$loaded=1; +print "ok 1\n"; diff --git a/htetc/handler.pl b/htetc/handler.pl index e8cd3333b..cbe2dd35d 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -179,6 +179,8 @@ sub handler use FS::inventory_class; use FS::inventory_item; use FS::pkg_class; + use FS::access_user; + use FS::access_group; if ( %%%RT_ENABLED%%% ) { eval ' diff --git a/httemplate/autohandler b/httemplate/autohandler index a3f7eb008..ad0ab8ba6 100644 --- a/httemplate/autohandler +++ b/httemplate/autohandler @@ -9,7 +9,15 @@ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { if ( lc($r->content_type) eq 'text/html' ) { - $profile = '
'. encode_entities(dbh->sprintProfile()).
+    # barely worth it, just in case someone tries to use profiling on a
+    # non-RT install
+    eval "use Text::Wrapper;";
+    die $@ if $@;
+
+    my $wrapper = new Text::Wrapper( columns => 80 );
+
+    $profile = '
'.
+               encode_entities( $wrapper->wrap( dbh->sprintProfile() ) ).
                #"\n\n". &sprintAutoProfile(). '
'; "\n\n". '
'; } diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html new file mode 100644 index 000000000..6ba89ea81 --- /dev/null +++ b/httemplate/browse/access_group.html @@ -0,0 +1,33 @@ +<% + +my $html_init = + "Internal access groups control access to the back-office interface.

". + qq!Add an internal access group

!; + +my $count_query = 'SELECT COUNT(*) FROM access_group'; + +my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Internal Access Groups', + 'menubar' => [ # 'Main menu' => $p, + 'Internal users' => $p.'browse/access_user.html', + ], + 'html_init' => $html_init, + 'name' => 'internal access groups', + 'query' => { 'table' => 'access_group', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY groupname', #?? + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Group name', + ], + 'fields' => [ 'groupnum', + 'groupname', + ], + 'links' => [ $link, + $link, + ], + ) +%> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html new file mode 100644 index 000000000..38d5430b1 --- /dev/null +++ b/httemplate/browse/access_user.html @@ -0,0 +1,63 @@ +<% + +my $html_init = + "Internal users have access to the back-office interface. Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees. It is highly recommended to add a separate account for each person rather than using role accounts.

". + qq!Add an internal user

!; + +#false laziness w/agent_type.cgi +my $groups_sub = sub { + my $access_user = shift; + + [ map { + my $access_usergroup = $_; + my $access_group = $access_usergroup->access_group; + [ + { + 'data' => $access_group->groupname, + 'align' => 'left', + 'link' => + $p. 'edit/access_group.html?'. $access_usergroup->groupnum, + }, + ]; + } + grep { $_->access_group # and ! $_->access_group->disabled + } + $access_user->access_usergroup, + + ]; + +}; + +my $count_query = 'SELECT COUNT(*) FROM access_user'; + +my $link = [ $p.'edit/access_user.html?', 'usernum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Internal Users', + 'menubar' => [ #'Main menu' => $p, + 'Internal access groups' => $p.'browse/access_group.html', + ], + 'html_init' => $html_init, + 'name' => 'internal users', + 'query' => { 'table' => 'access_user', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY last, first', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Username', + 'Full name', + 'Groups' + ], + 'fields' => [ 'usernum', + 'username', + 'name', # sub { shift->name }, + $groups_sub, + ], + 'links' => [ $link, + $link, + $link, + '' + ], + ) +%> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi index 2e1bdad42..a5ffb1048 100755 --- a/httemplate/browse/agent_type.cgi +++ b/httemplate/browse/agent_type.cgi @@ -1,60 +1,62 @@ - -<%= include("/elements/header.html","Agent Type Listing", menubar( - 'Main Menu' => $p, - 'Agents' => $p. 'browse/agent.cgi', -)) %> -Agent types define groups of packages that you can then assign to particular -agents.

-Add a new agent type

+<% -<%= table() %> - - Agent Type - Packages - +my $html_init = + 'Agent types define groups of packages that you can then assign to'. + ' particular agents.

'. + qq!Add a new agent type

!; -<% -foreach my $agent_type ( sort { - $a->getfield('typenum') <=> $b->getfield('typenum') -} qsearch('agent_type',{}) ) { - my $hashref = $agent_type->hashref; - #more efficient to do this with SQL... - my @type_pkgs = grep { $_->part_pkg and ! $_->part_pkg->disabled } - qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} }); - my $rowspan = scalar(@type_pkgs); - $rowspan = int($rowspan/2+0.5) ; - print < - - $hashref->{typenum} - - $hashref->{atype} -END +my $count_query = 'SELECT COUNT(*) FROM agent_type'; - my($type_pkgs); - my($tdcount) = -1 ; - foreach $type_pkgs ( @type_pkgs ) { - my($pkgpart)=$type_pkgs->getfield('pkgpart'); - my($part_pkg) = qsearchs('part_pkg',{'pkgpart'=> $pkgpart }); - print qq!! if ($tdcount == 0) ; - $tdcount = 0 if ($tdcount == -1) ; - print qq!!, - $part_pkg->getfield('pkg'),""; - $tdcount ++ ; - if ($tdcount == 2) - { - print qq!\n! ; - $tdcount = 0 ; - } - } +#false laziness w/access_user.html +my $packages_sub = sub { + my $agent_type = shift; - print ""; -} + [ map { + my $type_pkgs = $_; + my $part_pkg = $type_pkgs->part_pkg; + [ + { + 'data' => $part_pkg->pkg. ' - '. $part_pkg->comment, + 'align' => 'left', + 'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart, + }, + ]; + } + #sort { + # } + grep { + $_->part_pkg and ! $_->part_pkg->disabled + } + $agent_type->type_pkgs #XXX the method should order itself by something + ]; -print < - - -END +}; +my $link = [ $p.'edit/agent_type.cgi?', 'typenum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Agent Types', + 'menubar' => [ #'Main menu' => $p, + 'Agents' =>"${p}browse/agent.cgi", + ], + 'html_init' => $html_init, + 'name' => 'agent types', + 'query' => { 'table' => 'agent_type', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY typenum', # 'ORDER BY atype', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Agent Type', + 'Packages', + ], + 'fields' => [ 'typenum', + 'atype', + $packages_sub, + ], + 'links' => [ $link, + $link, + '', + ], + ) %> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index 1e0e0880c..9e3feb8f3 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -1,33 +1,34 @@ - -<% +<%= include('/elements/header.html', "Tax Rate Listing", menubar( + 'Edit tax rates' => $p. "edit/cust_main_county.cgi", +)) %> + + Click on expand country to specify a country's tax rates by state. +
Click on expand state to specify a state's tax rates by county. +<% my $conf = new FS::Conf; my $enable_taxclasses = $conf->exists('enable_taxclasses'); -print header("Tax Rate Listing", menubar( - 'Main Menu' => $p, - 'Edit tax rates' => $p. "edit/cust_main_county.cgi", -)),<expand country to specify a country's tax rates by state. -
Click on expand state to specify a state's tax rates by county. -END +if ( $enable_taxclasses ) { %> -if ( $enable_taxclasses ) { - print '
Click on expand taxclasses to specify tax classes'; -} +
Click on expand taxclasses to specify tax classes -print '

'. &table(). < - Country - State - County - Taxclass
(per-package classification) - Tax name
(printed on invoices) - Tax - Exemption - -END +<% } %> + +

+<%= table() %> + + + Country + State + County + Taxclass
(per-package classification) + Tax name
(printed on invoices) + Tax + Exemption + +<% my @regions = sort { $a->country cmp $b->country or $a->state cmp $b->state or $a->county cmp $b->county @@ -39,10 +40,12 @@ my $sup=0; for ( my $i=0; $i<@regions; $i++ ) { my $cust_main_county = $regions[$i]; my $hashref = $cust_main_county->hashref; - print < - $hashref->{country} -END + <%= $hashref->{country} %> + + <% my $j; if ( $sup ) { @@ -74,69 +77,73 @@ END $j = 1; } - print "{state} + %> + + <%= + $hashref->{state} ? ' BGCOLOR="#ffffff">'. $hashref->{state} : qq! BGCOLOR="#cccccc">(ALL) !. qq!expand country!; - - print qq! collapse state! if $j>1; - - print ""; - } - -# $sup=$newsup; - - print "{county} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{county}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $hashref->{state} ) { - print qq!!. - qq!expand state!; - } - } - print ""; - - print "{taxclass} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxclass}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $enable_taxclasses ) { - print qq!!. - qq!expand taxclasses!; - } - - } - print ""; - - print "{taxname} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxname}; - } else { - print ' BGCOLOR="#cccccc">Tax'; - } - print ""; - - print "$hashref->{tax}%". - ''; - print '$'. sprintf("%.2f", $hashref->{exempt_amount} ). - ' per month
' - if $hashref->{exempt_amount} > 0; - print 'Setup fee
' if $hashref->{setuptax} =~ /^Y$/i; - print 'Recurring fee
' if $hashref->{recurtax} =~ /^Y$/i; - print ''; - -} - -print < - - -END - -%> + qq!">expand country
! + %> + <% if ( $j>1 ) { %> + collapse state + <% } %> + + + <% } %> + +<% # $sup=$newsup; %> + + {county} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{county} %> + <% } else { + %> BGCOLOR="#cccccc">(ALL) + <% if ( $hashref->{state} ) { %> + expand state + <% } %> + <% } %> + + + {taxclass} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{taxclass} %> + <% } else { + %> BGCOLOR="#cccccc">(ALL) + <% if ( $enable_taxclasses ) { %> + expand taxclasses + <% } %> + <% } %> + + + {taxname} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{taxname} %> + <% } else { + %> BGCOLOR="#cccccc">Tax + <% } %> + + + <%= $hashref->{tax} %>% + + + + <% if ( $hashref->{exempt_amount} > 0 ) { %> + $<%= sprintf("%.2f", $hashref->{exempt_amount} ) %> per month
+ <% } %> + + <% if ( $hashref->{setuptax} =~ /^Y$/i ) { %> + Setup fee
+ <% } %> + + <% if ( $hashref->{recurtax} =~ /^Y$/i ) { %> + Recurring fee
+ <% } %> + + + + + +<% } %> + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi index d4adf9f1a..318ebfdff 100755 --- a/httemplate/browse/msgcat.cgi +++ b/httemplate/browse/msgcat.cgi @@ -1,10 +1,6 @@ - -<% - -print header("View Message catalog", menubar( - 'Main Menu' => $p, +<%= include('/elements/header.html', "View Message catalog", menubar( 'Edit message catalog' => $p. "edit/msgcat.cgi", -)), '
'; +)) %><% my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => 'en_US', @@ -38,13 +34,7 @@ my $widget = new HTML::Widgets::SelectLayers( }, ); - -print $widget->html; - -print < - - -END - %> + +<%= $widget->html %> +<%= include('/elements/footer.html') %> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 0afa54750..41d86358c 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -11,8 +11,8 @@ my $select = '*'; my $orderby = 'pkgpart'; if ( $cgi->param('active') ) { - $orderby = 'num_active'; - + $orderby = 'num_active DESC'; +} $select = " *, @@ -33,13 +33,13 @@ if ( $cgi->param('active') ) { "; -} +#} my $conf = new FS::Conf; my $taxclasses = $conf->exists('enable_taxclasses'); my $html_init; -unless ( $cgi->param('active') ) { +#unless ( $cgi->param('active') ) { $html_init = qq! One or more service definitions are grouped together into a package definition and given pricing information. Customers purchase packages @@ -47,7 +47,7 @@ unless ( $cgi->param('active') ) { Add a new package definition

!; -} +#} my $posttotal; if ( $cgi->param('showdisabled') ) { @@ -85,7 +85,7 @@ unless ( 0 ) { #already showing only one class or something? $align .= 'l'; } -if ( $cgi->param('active') ) { +#if ( $cgi->param('active') ) { push @header, 'Customer
packages'; my %col = ( 'active' => '00CC00', @@ -117,7 +117,7 @@ if ( $cgi->param('active') ) { } (qw( active suspended cancelled )) ]; }; $align .= 'r'; -} +#} push @header, 'Frequency'; push @fields, sub { shift->freq_pretty; }; diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html new file mode 100644 index 000000000..11b8df7bc --- /dev/null +++ b/httemplate/edit/access_group.html @@ -0,0 +1,10 @@ +<%= include( 'elements/edit.html', + 'name' => 'Internal Access Group', + 'table' => 'access_group', + 'labels' => { + 'groupnum' => 'Group number', + 'groupname' => 'Group name', + }, + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html new file mode 100644 index 000000000..2b19dbf7b --- /dev/null +++ b/httemplate/edit/access_user.html @@ -0,0 +1,37 @@ +<%= include( 'elements/edit.html', + 'name' => 'Internal User', + 'table' => 'access_user', + 'fields' => [ + 'username', + { field=>'_password', type=>'password' }, + 'last', + 'first', + ], + 'labels' => { + 'usernum' => 'User number', + 'username' => 'Username', + '_password' => 'Password', + 'last' => 'Last name', + 'first' => 'First name', + }, + 'viewall_dir' => 'browse', + 'html_bottom' => + sub { + my $access_user = shift; + + '
Internal Access Groups
'. + ntable("#cccccc",2). + ''. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_user, + 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + 'name_col' => 'groupname', + 'target_link' => $p.'edit/access_group.html?', + #'disable-able' => 1, + ). + '' + ; + }, + ) +%> diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi index 944ddd0d0..f5afd3a96 100755 --- a/httemplate/edit/agent_type.cgi +++ b/httemplate/edit/agent_type.cgi @@ -14,9 +14,7 @@ if ( $cgi->param('error') ) { } my $action = $agent_type->typenum ? 'Edit' : 'Add'; -%> - -<%= include("/elements/header.html","$action Agent Type", menubar( +%><%= include("/elements/header.html","$action Agent Type", menubar( 'Main Menu' => "$p", 'View all agent types' => "${p}browse/agent_type.cgi", )) @@ -29,47 +27,29 @@ my $action = $agent_type->typenum ? 'Edit' : 'Add';
Agent Type #<%= $agent_type->typenum || "(NEW)" %> -

+
Agent Type

Select which packages agents of this type may sell to customers
- -<% foreach my $part_pkg ( - qsearch({ 'table' => 'part_pkg', - 'hashref' => { 'disabled' => '' }, - 'select' => 'part_pkg.*', - 'addl_from' => 'LEFT JOIN type_pkgs USING ( pkgpart )', - 'extra_sql' => ( $agent_type->typenum - ? 'OR typenum = '. $agent_type->typenum - : '' - ), - }) - ) { +<%= ntable("#cccccc", 2) %> +<%= include('/elements/checkboxes-table.html', + 'source_obj' => $agent_type, + 'link_table' => 'type_pkgs', + 'target_table' => 'part_pkg', + 'name_callback' => sub { $_[0]->pkg. ' - '. $_[0]->comment; }, + 'target_link' => $p.'edit/part_pkg.cgi?', + 'disable-able' => 1, + + ) %> - -
- $agent_type->typenum, - 'pkgpart' => $part_pkg->pkgpart, - }) - ? 'CHECKED ' - : '' - %> VALUE="ON"> - - <%= $part_pkg->pkgpart %>: - <%= $part_pkg->pkg %> - <%= $part_pkg->comment %> - <%= $part_pkg->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> - -<% } %> - -

+ +
">
- - + +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi index 24bce308a..9d3bdd8cb 100755 --- a/httemplate/edit/cust_bill_pay.cgi +++ b/httemplate/edit/cust_bill_pay.cgi @@ -1,4 +1,3 @@ - <% my($paynum, $amount, $invnum); @@ -18,78 +17,76 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Payment", ''); -print qq!Error: !, $cgi->param('error'), - "

" - if $cgi->param('error'); -print < -END +%><%= header("Apply Payment", '') %> +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

+<% } %> + +
+ +<% my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); die "payment $paynum not found!" unless $cust_pay; my $unapplied = $cust_pay->unapplied; +%> + +Payment #<%= $paynum %> + -print "Payment # $paynum". - qq!!. - '
Date: '. time2str("%D", $cust_pay->_date). ''. - '
Amount: $'. $cust_pay->paid. ''. - "
Unapplied amount: \$$unapplied" - ; +
Date: <%= time2str("%D", $cust_pay->_date) %> +
Amount: $<%= $cust_pay->paid %> + +
Unapplied amount: $<%= $unapplied %> + +<% my @cust_bill = grep $_->owed != 0, qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } ); -print < + -END - -print qq!
Invoice #"; -print qq!
Amount \$!; +
Invoice # + +
Amount $ -print < - -END +
-print < - - + -END - -%> diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi index aae0df2fc..946b1087b 100755 --- a/httemplate/edit/cust_credit.cgi +++ b/httemplate/edit/cust_credit.cgi @@ -1,4 +1,3 @@ - <% my $conf = new FS::Conf; @@ -25,39 +24,57 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Post Credit", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); -print <config('countrydefault')); -
- - - - - - -END - -print '

Credit'. ntable("#cccccc", 2). - 'Date'. - time2str("%D",$_date). ''; - -print qq!Amount\$!; +%> + +<%= header("Post Credit", '') %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

+<% } %> + + + + + + + + + + + +Credit + +<%= ntable("#cccccc", 2) %> + + Date + <%= time2str("%D",$_date) %> + + + + Amount + $ + + +<% #print qq! Also post refund!; +%> -print qq!Reason!; + + Reason + + -print qq!Auto-apply
to invoices!; + + Auto-apply
to invoices + + -print < +
- +
-END - -%> diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi index 1a97e1312..409ea3c25 100755 --- a/httemplate/edit/cust_credit_bill.cgi +++ b/httemplate/edit/cust_credit_bill.cgi @@ -1,4 +1,3 @@ - <% my($crednum, $amount, $invnum); @@ -23,79 +22,78 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Credit", ''); -print qq!Error: !, $cgi->param('error'), - "

" - if $cgi->param('error'); -print < -END +%><%= header("Apply Credit", '') %> +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

+<% } %> + +
+ +<% my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); die "credit $crednum not found!" unless $cust_credit; my $credited = $cust_credit->credited; +%> + +Credit #<%= $crednum %> + + +
Date: <%= time2str("%D", $cust_credit->_date) %> -print "Credit # $crednum". - qq!!. - '
Date: '. time2str("%D", $cust_credit->_date). ''. - '
Amount: $'. $cust_credit->amount. ''. - "
Unapplied amount: \$$credited". - '
Reason: '. $cust_credit->reason. '' - ; +
Amount: $<%= $cust_credit->amount %> +
Unapplied amount: $<%= $credited %> + +
Reason: <%= $cust_credit->reason %> + +<% my @cust_bill = grep $_->owed != 0, qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } ); -print < + -END - -print qq!
Invoice #"; -print qq!
Amount \$!; +
Invoice # + +
Amount $ -print < - -END +
-print < - - + -END - -%> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 80fec9359..bb2a8618e 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -397,49 +397,66 @@ unless ( $custnum ) { if ( @part_pkg ) { -# print "

First package", &itable("#cccccc", "0 ALIGN=LEFT"), -#apiabuse & undesirable wrapping - print "
First package", &ntable("#cccccc"), - qq!"; - - #false laziness: (mostly) copied from edit/svc_acct.cgi - #$ulen = $svc_acct->dbdef_table->column('username')->length; - my $ulen = dbdef->table('svc_acct')->column('username')->length; - my $ulen2 = $ulen+2; - my $passwordmax = $conf->config('passwordmax') || 8; - my $pmax2 = $passwordmax + 2; - print <Username - -Password - -(blank to generate) -END - - print 'Access number' - . - &FS::svc_acct_pop::popselector($popnum). - '' - ; - } -} + # print "

First package", &itable("#cccccc", "0 ALIGN=LEFT"), + #apiabuse & undesirable wrapping + + %> +
First package + <%= ntable("#cccccc") %> + + + + + + + + <% + #false laziness: (mostly) copied from edit/svc_acct.cgi + #$ulen = $svc_acct->dbdef_table->column('username')->length; + my $ulen = dbdef->table('svc_acct')->column('username')->length; + my $ulen2 = $ulen+2; + my $passwordmax = $conf->config('passwordmax') || 8; + my $pmax2 = $passwordmax + 2; + %> + + + Username + + MAXLENGTH=<%= $ulen %>> + + + + + Password + + MAXLENGTH=<%= $passwordmax %>> + (blank to generate) + + + + + Access number + <%= FS::svc_acct_pop::popselector($popnum) %> + + + + <% } %> + +<% } %> -my $otaker = $cust_main->otaker; -print qq!!, - qq!

!, - "", -; + +
+"> +
+ + +<%= include('/elements/footer.html') %> -%> diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi index ce1c86612..174d4dde1 100755 --- a/httemplate/edit/cust_pkg.cgi +++ b/httemplate/edit/cust_pkg.cgi @@ -1,4 +1,3 @@ - <% my %pkg = (); @@ -29,48 +28,62 @@ if ( $cgi->param('error') ) { } my $p1 = popurl(1); -print header("Add/Edit Packages", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); +%><%= include('/elements/header.html', "Add/Edit Packages", '') %> -print qq!
!; +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> -print qq!!; + + + +<% #current packages -my @cust_pkg = qsearch('cust_pkg',{ 'custnum' => $custnum, 'cancel' => '' } ); +my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); if (@cust_pkg) { - print < - - Pkg # - Package description - -

-END +%> + + Current packages - select to remove (services are moved to a new package below) + + + + + +

- foreach (sort { $all_pkg{$a->getfield('pkgpart')} cmp $all_pkg{$b->getfield('pkgpart')} } @cust_pkg) { + <% + + foreach ( sort { $all_pkg{ $a->getfield('pkgpart') } + cmp $all_pkg{ $b->getfield('pkgpart') } + } + @cust_pkg + ) + { my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') ); my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : ''; - print < - - \n - - -END - } - print qq!
Pkg #Package description
$pkgnum:$all_pkg{$pkgpart} - $all_comment{$pkgpart}


!; -} -print <
-END + %> + + + > + <%= $pkgnum %>: + <%= $all_pkg{$pkgpart} %> - <%= $all_comment{$pkgpart} %> + + + <% } %> + + +

+<% } %> + +Order new packages +

+ +<% my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); @@ -79,13 +92,15 @@ my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) } my $count = 0; my $pkgparts = 0; -print < + -END + +<% #foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} } keys(%agent_pkgs) ) { @@ -93,38 +108,43 @@ foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} } next unless exists $pkg{$pkgpart}; #skip disabled ones #print qq!! if ( $count == 0 ); my $value = $cgi->param("pkg$pkgpart") || 0; - print < + - - - + + + -END + +<% $count ++ ; #if ( $count == 2 ) { # print qq!\n! ; # $count = 0; #} } -print qq!
Qty. Package Description
$pkgpart:$pkg{$pkgpart} - $comment{$pkgpart} + " VALUE="<%= $value %>" SIZE="2" MAXLENGTH="2"> + <%= $pkgpart %>:<%= $pkg{$pkgpart} %> - <%= $comment{$pkgpart}%>
!; - -unless ( $pkgparts ) { - my $p2 = popurl(2); - my $typenum = $agent->typenum; - my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); - my $atype = $agent_type->atype; - print <package definitions, or agent type -$atype not allowed to purchase -any packages.) -END -} +%> -#submit -print < - - - -END + + +<% unless ( $pkgparts ) { + my $p2 = popurl(2); + my $typenum = $agent->typenum; + my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); + my $atype = $agent_type->atype; %> + + (No package definitions, + or agent type + <%= $atype %> + is not allowed to purchase any packages.) + +<% } %> + +

+ + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 5486b4b00..120c03a3c 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -17,6 +17,13 @@ # 'menubar' => '', #menubar arrayref # # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' + # + # 'html_bottom' => '', #string + # 'html_bottom' => sub { + # my $object = shift; + # # ... + # "html_string"; + # }, my(%opt) = @_; @@ -27,6 +34,7 @@ my $fields = $opt{'fields'} #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; || [ grep { $_ ne $pkey } fields($table) ]; + #my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields; my $object; if ( $cgi->param('error') ) { @@ -63,10 +71,7 @@ ); } -%> - - -<%= include("/elements/header.html", $title, +%><%= include("/elements/header.html", $title, include( '/elements/menubar.html', @menubar ) ) %> @@ -86,7 +91,18 @@ <%= ntable("#cccccc",2) %> -<% foreach my $field ( @$fields ) { %> +<% foreach my $f ( @$fields ) { + + my( $field, $type); + if ( ref($f) ) { + $field = $f->{'field'}, + $type = $f->{'type'} || 'text', + } else { + $field = $f; + $type = 'text'; + } + +%> @@ -98,12 +114,11 @@ <% - #just text in one size for now... eventually more options for - # uneditable, hidden, , etc. fields %> - + @@ -112,6 +127,11 @@ +<%= ref( $opt{'html_bottom'} ) + ? &{ $opt{'html_bottom'} }( $object ) + : $opt{'html_bottom'} +%> +
"> diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi index f784dfa3e..dce1e6394 100755 --- a/httemplate/edit/part_referral.cgi +++ b/httemplate/edit/part_referral.cgi @@ -1,4 +1,3 @@ - <% my $part_referral; @@ -17,32 +16,29 @@ my $action = $part_referral->refnum ? 'Edit' : 'Add'; my $hashref = $part_referral->hashref; my $p1 = popurl(1); -print header("$action Advertising source", menubar( + +%><%= include('/elements/header.html', "$action Advertising source", menubar( 'Main Menu' => popurl(2), 'View all advertising sources' => popurl(2). "browse/part_referral.cgi", -)); +)) %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); +

-print qq!!; + -print qq!!; +<% #print "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)"; +%> -print < -END +Advertising source -print qq!
!; +
+"> -print < - - -END + -%> +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi index fb10321e8..7b2c768a7 100644 --- a/httemplate/edit/part_virtual_field.cgi +++ b/httemplate/edit/part_virtual_field.cgi @@ -1,4 +1,3 @@ - <% my ($vfieldpart, $part_virtual_field); @@ -21,12 +20,14 @@ if ( $cgi->param('error') ) { my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add'; my $p1 = popurl(1); -print header("$action Virtual Field Definition", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); -%> +%><%= include('/elements/header.html', "$action Virtual Field Definition") %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

+<% } %> +
@@ -83,10 +84,8 @@ Field #<%=$vfieldpart or "(NEW)"%>

-

+
If you don't understand what check_block and list_source mean, LEAVE THEM BLANK. We mean it. - - - +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html new file mode 100644 index 000000000..e8c6d07b1 --- /dev/null +++ b/httemplate/edit/process/access_group.html @@ -0,0 +1,5 @@ +<%= include( 'elements/process.html', + 'table' => 'access_group', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html new file mode 100644 index 000000000..a6c2a36b1 --- /dev/null +++ b/httemplate/edit/process/access_user.html @@ -0,0 +1,8 @@ +<%= include( 'elements/process.html', + 'table' => 'access_user', + 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + }, + ) +%> diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi index 516594573..fd8ca8833 100755 --- a/httemplate/edit/process/agent_type.cgi +++ b/httemplate/edit/process/agent_type.cgi @@ -11,43 +11,24 @@ my $new = new FS::agent_type ( { my $error; if ( $typenum ) { - $error=$new->replace($old); + $error = $new->replace($old); } else { - $error=$new->insert; - $typenum=$new->getfield('typenum'); + $error = $new->insert; + $typenum = $new->getfield('typenum'); } +#$error ||= $new->process_m2m( ); if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ); } else { - #false laziness w/ edit/process/part_svc.cgi - foreach my $part_pkg (qsearch('part_pkg',{})) { - my($pkgpart)=$part_pkg->getfield('pkgpart'); - - my($type_pkgs)=qsearchs('type_pkgs',{ - 'typenum' => $typenum, - 'pkgpart' => $pkgpart, - }); - if ( $type_pkgs && ! $cgi->param("pkgpart$pkgpart") ) { - my($d_type_pkgs)=$type_pkgs; #need to save $type_pkgs for below. - $error=$d_type_pkgs->delete; - die $error if $error; - - } elsif ( $cgi->param("pkgpart$pkgpart") - && ! $type_pkgs - ) { - #ok to clobber it now (but bad form nonetheless?) - $type_pkgs=new FS::type_pkgs ({ - 'typenum' => $typenum, - 'pkgpart' => $pkgpart, - }); - $error= $type_pkgs->insert; - die $error if $error; - } - - } + my $error = $new->process_m2m( + 'link_table' => 'type_pkgs', + 'target_table' => 'part_pkg', + 'params' => scalar($cgi->Vars) + ); + die $error if $error; print $cgi->redirect(popurl(3). "browse/agent_type.cgi"); } diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi index 0025b16b5..fc668bb07 100755 --- a/httemplate/edit/process/cust_bill_pay.cgi +++ b/httemplate/edit/process/cust_bill_pay.cgi @@ -33,11 +33,19 @@ if ($cgi->param('invnum') =~ /^Refund$/) { my $error = $new->insert; if ( $error ) { + $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ); + %><%= $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %><% + } else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Payment application sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi index 85bfd4489..6a4ef194a 100755 --- a/httemplate/edit/process/cust_credit.cgi +++ b/httemplate/edit/process/cust_credit.cgi @@ -13,14 +13,23 @@ my $error = $new->insert; if ( $error ) { $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ); + + %><%= $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ) %><% + } else { + if ( $cgi->param('apply') eq 'yes' ) { my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum }) or die "unknown custnum $custnum"; $cust_main->apply_credits; } - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Credit sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi index 28f892f62..3b759536f 100755 --- a/httemplate/edit/process/cust_credit_bill.cgi +++ b/httemplate/edit/process/cust_credit_bill.cgi @@ -34,11 +34,19 @@ if ($cgi->param('invnum') =~ /^Refund$/) { my $error = $new->insert; if ( $error ) { + $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ); + %><%= $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %><% + } else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Credit application sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 83ff6f728..59ad35ee4 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -2,10 +2,21 @@ # options example... # + ### + ##req + ## # 'table' => + # # #? 'primary_key' => #required when the dbdef doesn't know...??? # #? 'fields' => [] + # + ### + ##opt + ### # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' + # 'process_m2m' => { 'link_table' => 'link_table_name', + # 'target_table' => 'target_table_name', + # }. my(%opt) = @_; @@ -31,12 +42,16 @@ if ( $pkeyvalue ) { $error = $new->replace($old); } else { - warn $new; $error = $new->insert; - warn $error; $pkeyvalue = $new->getfield($pkey); } + if ( !$error && $opt{'process_m2m'} ) { + $error = $new->process_m2m( %{ $opt{'process_m2m'} }, + 'params' => scalar($cgi->Vars), + ); + } + if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "$table.html?". $cgi->query_string ); diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi index ca0e3398f..f47ba0a8f 100755 --- a/httemplate/edit/svc_domain.cgi +++ b/httemplate/edit/svc_domain.cgi @@ -1,4 +1,3 @@ - <% my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, @@ -66,33 +65,31 @@ my $otaker = getotaker; my $domain = $svc_domain->domain; my $p1 = popurl(1); -print header("$action $svc", ''); - -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); - -print < - - - -END - -print qq!New!; -print qq!
Transfer!; - -print <Domain -
Purpose/Description: -

- - - -END %> + +<%= include('/elements/header.html', "$action $svc", '') %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> + +

+ + + + +>New +
+ +>Transfer + +

Domain + +
Purpose/Description: + +

+ +

+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html new file mode 100644 index 000000000..d26ebef35 --- /dev/null +++ b/httemplate/elements/checkboxes-table.html @@ -0,0 +1,110 @@ +<% + + ## + # required + ## + # 'target_table' => 'table_name', + # 'link_table' => 'table_name', + # + # 'name_col' => 'name_column', + # #or + # 'name_callback' => sub { }, + # + ## + # recommended (required?) + ## + # 'source_obj' => $obj, + # #or? + # #'source_table' => 'table_name', + # #'sourcenum' => '4', #current value of primary key in source_table + # # # (none is okay, just pass it if you have it) + ## + # optional + ## + # 'disable-able' => 1, + + my( %opt ) = @_; + + my $target_pkey = dbdef->table($opt{'target_table'})->primary_key; + + my( $source_pkey, $sourcenum, $source_obj ); + if ( $opt{'source_obj'} ) { + + $source_obj = $opt{'source_obj'}; + #$source_table = $source_obj->dbdef_table->table; + $source_pkey = $source_obj->dbdef_table->primary_key; + $sourcenum = $source_obj->$source_pkey(); + + } else { + + #$source_obj? + $source_pkey = $opt{'source_table'} + ? dbdef->table($opt{'source_table'})->primary_key + : ''; + $sourcenum = $opt{'sourcenum'}; + } + + my $hashref = $opt{'hashref'} || {}; + + my $extra_sql = ''; + + if ( $opt{'disable-able'} ) { + $hashref->{'disabled'} = ''; + + $extra_sql .= ( $sourcenum && $source_pkey ) + ? "OR $source_pkey = $sourcenum" + : ''; + } + +%> + +<% foreach my $target_obj ( + qsearch({ 'table' => $opt{'target_table'}, + 'hashref' => $hashref, + 'select' => $opt{'target_table'}. '.*', + 'addl_from' => "LEFT JOIN $opt{'link_table'} USING ( $target_pkey )", + 'extra_sql' => $extra_sql, + }) + ) { + + my $targetnum = $target_obj->$target_pkey(); +%> + + $sourcenum, + $target_pkey => $targetnum, + }) + ? 'CHECKED ' + : '' + %> VALUE="ON"> + + <% if ( $opt{'target_link'} ) { %> + + <% + + } + %><%= $targetnum %>: + + <% if ( $opt{'name_callback'} ) { %> + + <%= &{ $opt{'name_callback'} }( $target_obj ) %><%= $opt{'target_link'} ? '' : '' %> + + <% } else { + my $name_col = $opt{'name_col'}; + %> + + <%= $target_obj->$name_col() %><%= $opt{'target_link'} ? '' : '' %> + + <% } %> + + <% if ( $opt{'disable-able'} ) { %> + + <%= $target_obj->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> + + <% } %> + +
+ +<% } %> + diff --git a/httemplate/elements/cssexpr.js b/httemplate/elements/cssexpr.js new file mode 100644 index 000000000..c434d8da0 --- /dev/null +++ b/httemplate/elements/cssexpr.js @@ -0,0 +1,66 @@ +function constExpression(x) { + return x; +} + +function simplifyCSSExpression() { + try { + var ss,sl, rs, rl; + ss = document.styleSheets; + sl = ss.length + + for (var i = 0; i < sl; i++) { + simplifyCSSBlock(ss[i]); + } + } + catch (exc) { + //alert("Got an error while processing css. The page should still work but might be a bit slower"); + throw exc; + } +} + +function simplifyCSSBlock(ss) { + var rs, rl; + + for (var i = 0; i < ss.imports.length; i++) + simplifyCSSBlock(ss.imports[i]); + + if (ss.cssText.indexOf("expression(constExpression(") == -1) + return; + + rs = ss.rules; + rl = rs.length; + for (var j = 0; j < rl; j++) + simplifyCSSRule(rs[j]); + +} + +function simplifyCSSRule(r) { + var str = r.style.cssText; + var str2 = str; + var lastStr; + do { + lastStr = str2; + str2 = simplifyCSSRuleHelper(lastStr); + } while (str2 != lastStr) + + if (str2 != str) + r.style.cssText = str2; +} + +function simplifyCSSRuleHelper(str) { + var i, i2; + i = str.indexOf("expression(constExpression("); + if (i == -1) return str; + i2 = str.indexOf("))", i); + var hd = str.substring(0, i); + var tl = str.substring(i2 + 2); + var exp = str.substring(i + 27, i2); + var val = eval(exp) + return hd + val + tl; +} + +if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) { + window.attachEvent("onload", function () { + simplifyCSSExpression(); + }); +} diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html index 6029d7637..32d121996 100644 --- a/httemplate/elements/footer.html +++ b/httemplate/elements/footer.html @@ -1,2 +1,5 @@ + + + diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 10e4e40f1..49814577e 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -2,20 +2,285 @@ my($title, $menubar) = ( shift, shift ); my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. my $head = @_ ? shift : ''; #$head is for things that go in the section + my $conf = new FS::Conf; %> - - - - <%= $title %> - - - - - <%= $head %> - - > + + + + + <%= $title %> + + + + + + + + <% + + tie my %report_menu, 'Tie::IxHash', + 'Report one' => [ 'there', 'theretip' ], + 'Report too' => [ 'here', 'heretip' ], + ; + + tie my %config_employees, 'Tie::IxHash', + 'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ], + 'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ], + ; + + tie my %config_export_svc_pkg, 'Tie::IxHash', + 'View/Edit exports' => [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ], + 'View/Edit service definitions' => [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ], + 'View/Edit package definitions' => [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ], + 'View/Edit package classes' => [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ], + ; + + tie my %config_agent, 'Tie::IxHash', + 'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ], + 'View/Edit agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ], + ; + + tie my %config_billing, 'Tie::IxHash', + 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ], + 'View/Edit invoice events' => [ $fsurl.'browse/part_bill_event.cgi', 'Actions for overdue invoices' ], + 'View/Edit prepaid cards' => [ $fsurl.'browse/prepay_credit.html', 'View outstanding cards, generate new cards' ], + 'View/Edit call rates and regions' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans, regions and prefixes for VoIP and call billing' ], + 'View/Edit locales and tax rates' => [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ], + ; + + tie my %config_dialup, 'Tie::IxHash', + 'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ], + ; + + tie my %config_broadband, 'Tie::IxHash', + 'View/Edit routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ], + 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ], + ; + + tie my %config_misc, 'Tie::IxHash', + 'View/Edit advertising sources' => [ $fsurl.'browse/part_referral.cgi', 'Where a customer heard about your service. Tracked for informational purposes' ], + 'View/Edit virtual fields' => [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ], + 'View/Edit message catalog' => [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ], + 'View/Edit inventory classes and inventory' => [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ], + ; + + tie my %config_menu, 'Tie::IxHash', + 'Settings' => [ $fsurl.'config/config-view.cgi', 'XXXconfigittip' ], + 'separator' => '', #its a separator! + 'Employees' => [ \%config_employees, 'XXXtooltip' ], + 'Provisioning, services and packages' + => [ \%config_export_svc_pkg, 'XXXtootip' ], + 'Resellers' => [ \%config_agent, 'XXXtootip' ], + 'Billing' => [ \%config_billing, 'XXXtootip' ], + 'Dialup' => [ \%config_dialup, 'XXXtootip' ], + 'Fixed (username-less) broadband' + => [ \%config_broadband, 'XXXtootip' ], + 'Miscellaneous' => [ \%config_misc, 'XXXtootip' ], + ; + + tie my %menu, 'Tie::IxHash', + 'Home' => [ $fsurl, 'hometip', ], + 'Top item one' => [ 'nowhere_yet', 'nowheretip', ], + 'Top item too' => [ 'nowhere_yet_either', 'eithertip', ], + 'Reports' => [ \%report_menu, 'reportmenutip' ], + 'Configuration' => [ \%config_menu, 'configmenutip' ], + ; + + use vars qw($gmenunum); + $gmenunum = 0; + + sub submenu { + my($submenu, $title) = @_; + my $menunum = $gmenunum++; + + #return two args: html, menuname + + "var myMenu$menunum = new WebFXMenu;\n". + #"myMenu$menunum.useAutoPosition = true;\n". + "myMenu$menunum.emptyText = '$title';\n". + + ( + join("\n", map { + + if ( !ref( $submenu->{$_} ) ) { + + "myMenu$menunum.add(new WebFXMenuSeparator());"; + + } else { + + my($url_or_submenu, $tooltip ) = @{ $submenu->{$_} }; + if ( ref($url_or_submenu) ) { + + my($subhtml, $submenuname ) = submenu($url_or_submenu, $_); #mmm, recursion + + "$subhtml\n". + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", null, \"$tooltip\", $submenuname ));"; + + } else { + + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", \"$url_or_submenu\", \"$tooltip\" ));"; + + } + + } + + } keys %$submenu ) + ). "\n". + "myMenu$menunum.width = 224\n", + + "myMenu$menunum"; + + } + + %> + + + + + <%= $head %> + + + STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"> + + + + + + + + + +
+ freeside + + <%= $conf->config('company_name') %> Billing + Logged in as <%= getotaker %> 
Preferences 

+
+ + + + + <% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { %> + <% eval "use RT;"; %> + + + <% } %> + + +
+ + Freeside v<%= $FS::VERSION %>
+ Documentation
+
+
+ + RT v<%= $RT::VERSION %>
+
Documentation
+
+
+ +
+ + + + + + + + + + + +
+ +
+ +
+
+
+ + +
+
+
+ + +
+
+ + + + + + + + + + + + + + +<% } %> diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css new file mode 100644 index 000000000..5bb8a0deb --- /dev/null +++ b/httemplate/elements/xmenu.css @@ -0,0 +1,185 @@ + +.webfx-menu, .webfx-menu * { + /* + Set the box sizing to content box + in the future when IE6 supports box-sizing + there will be an issue to fix the sizes + + There is probably an issue with IE5 mac now + because IE5 uses content-box but the script + assumes all versions of IE uses border-box. + + At the time of this writing mozilla did not support + box-sizing for absolute positioned element. + + Opera only supports content-box + */ + box-sizing: content-box; + -moz-box-sizing: content-box; +} + +.webfx-menu { + position: absolute; + z-index: 100; + visibility: hidden; + width: 154px; + border: 1px solid black; + padding: 1px; + background: white; + filter: progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4) + alpha(Opacity=95); + -moz-opacity: 0.95; + /* a drop shadow would be nice in moz/others too... */ +} + +.webfx-menu-empty { + display: block; + border: 1px solid white; + padding: 2px 5px 2px 5px; + font-size: 11px; + /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */ + color: black; +} + +.webfx-menu a { + display: block; + width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */ + height: expression(constExpression("1px")); + overflow: visible; + padding: 2px 0px 2px 5px; + font-size: 11px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + vertical-align: center; + color: black; + border: 1px solid white; +} + +.webfx-menu a:visited, +.webfx-menu a:visited:hover { + color: black; +} + +.webfx-menu a:hover { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + /* background: #ffe6fe; */ + /* background: #ffc2fe; */ + background: #fff2fe; + border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ +} + +.webfx-menu a .arrow { + float: right; + border: 0; + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +/* separtor */ +.webfx-menu div { + height: 0; + height: expression(constExpression(ieBox ? "2px" : "0")); + border-top: 1px solid #7e0079; /* rgb(120,172,255); */ + border-bottom: 1px solid rgb(234,242,255); + overflow: hidden; + margin: 2px 0px 2px 0px; + font-size: 0mm; +} + +.webfx-menu-bar { + /* i want a vertical bar */ + display: block; + + /* background: rgb(120,172,255);/*rgb(255,128,0);*/ + /* background: #a097ed; */ + background: #000000; + /* border: 1px solid #7E0079; */ + /* border: 1px solid #000000; */ + /* border: none */ + + padding: 2px; + + font-family: Verdana, Helvetica, Sans-Serif; + font-size: 11px; + + /* IE5.0 has the wierdest box model for inline elements */ + padding: expression(constExpression(ie50 ? "0px" : "2px")); +} + +.webfx-menu-bar a, +.webfx-menu-bar a:visited { + /* i want a vertical bar */ + display: block; + + /* border: 1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/ + /* border: 1px solid black; /* #ffffff; */ + /* border-bottom: 1px solid black; */ + border-bottom: 1px solid white; + /* border-bottom: 1px solid rgb(0,66,174); + /* border-bottom: 1px solid black; + border-bottom: 1px solid black; + border-bottom: 1px solid black; */ + + padding: 1px 5px 1px 5px; + + /* color: black; */ + color: white; + text-decoration: none; + + /* IE5.0 Does not paint borders and padding on inline elements without a height/width */ + height: expression(constExpression(ie50 ? "17px" : "auto")); +} + +.webfx-menu-bar a:hover { + /* color: black; */ + color: white; + /* background: rgb(120,172,255); */ + /* background: #BC79B8; */ + background: #7E0079; + /* border-left: 1px solid rgb(234,242,255); + border-right: 1px solid rgb(0,66,174); + border-top: 1px solid rgb(234,242,255); + border-bottom: 1px solid rgb(0,66,174); */ +} + +.webfx-menu-bar a .arrow { + border: 0; + float: right; +/* vertical-align: top; */ + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +.webfx-menu-bar a:active, .webfx-menu-bar a:focus { + -moz-outline: none; + outline: none; + /* + ie does not support outline but ie55 can hide the outline using + a proprietary property on HTMLElement. Did I say that IE sucks at CSS? + */ + ie-dummy: expression(this.hideFocus=true); + + border-left: 1px solid rgb(0,66,174); + border-right: 1px solid rgb(234,242,255); + border-top: 1px solid rgb(0,66,174); + border-bottom: 1px solid rgb(234,242,255); +} + +.webfx-menu-title { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + background: #7e0079; +/* border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ + padding: 3px 1px 3px 6px; + display: block; + font-size: 13px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + color: white; +/* border: 1px solid white; */ + border-bottom: 1px solid white; +} + diff --git a/httemplate/elements/xmenu.js b/httemplate/elements/xmenu.js new file mode 100644 index 000000000..134265f53 --- /dev/null +++ b/httemplate/elements/xmenu.js @@ -0,0 +1,668 @@ +// + + + + <% #include( '/elements/table.html', '#cccccc' ) %> <%= ntable('#cccccc') %> @@ -112,7 +102,7 @@ function achclose() { @@ -173,7 +163,7 @@ function achclose() { @@ -205,5 +195,5 @@ function achclose() {
- - + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/cust_bill.cgi b/httemplate/search/cust_bill.cgi deleted file mode 100755 index 5b0538ca3..000000000 --- a/httemplate/search/cust_bill.cgi +++ /dev/null @@ -1,165 +0,0 @@ -<% - -my $conf = new FS::Conf; -my $maxrecords = $conf->config('maxsearchrecordsperpage'); - -my $orderby = ''; #removeme - -my $limit = ''; -$limit .= "LIMIT $maxrecords" if $maxrecords; - -my $offset = $cgi->param('offset') || 0; -$limit .= " OFFSET $offset" if $offset; - -my($total, $tot_amount, $tot_balance); - -my(@cust_bill); -if ( $cgi->keywords ) { - my($query) = $cgi->keywords; - my $owed = "charged - ( select coalesce(sum(amount),0) from cust_bill_pay - where cust_bill_pay.invnum = cust_bill.invnum ) - - ( select coalesce(sum(amount),0) from cust_credit_bill - where cust_credit_bill.invnum = cust_bill.invnum )"; - my @where; - if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { - my($open, $days, $field) = ($1, $2, $3); - $field = "_date" if $field eq 'date'; - $orderby = "ORDER BY cust_bill.$field"; - push @where, "0 != $owed" if $open; - push @where, "cust_bill._date < ". (time-86400*$days) if $days; - } else { - die "unknown query string $query"; - } - - my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; - - my $statement = "SELECT COUNT(*), sum(charged), sum($owed) - FROM cust_bill $extra_sql"; - my $sth = dbh->prepare($statement) or die dbh->errstr. " doing $statement"; - $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; - - ( $total, $tot_amount, $tot_balance ) = @{$sth->fetchrow_arrayref}; - - @cust_bill = qsearch( - 'cust_bill', - {}, - "cust_bill.*, $owed as owed", - "$extra_sql $orderby $limit" - ); -} else { - $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/; - my $invnum = $2; - @cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum } ); - $total = scalar(@cust_bill); -} - -#if ( scalar(@cust_bill) == 1 ) { -if ( $total == 1 ) { - my $invnum = $cust_bill[0]->invnum; - print $cgi->redirect(popurl(2). "view/cust_bill.cgi?$invnum"); #redirect -} elsif ( scalar(@cust_bill) == 0 ) { -%> - -<% - eidiot("Invoice not found."); -} else { -%> - -<% - - #begin pager - my $pager = ''; - if ( $total != scalar(@cust_bill) && $maxrecords ) { - unless ( $offset == 0 ) { - $cgi->param('offset', $offset - $maxrecords); - $pager .= 'Previous '; - } - my $poff; - my $page; - for ( $poff = 0; $poff < $total; $poff += $maxrecords ) { - $page++; - if ( $offset == $poff ) { - $pager .= qq!$page !; - } else { - $cgi->param('offset', $poff); - $pager .= qq!$page !; - } - } - unless ( $offset + $maxrecords > $total ) { - $cgi->param('offset', $offset + $maxrecords); - $pager .= 'Next '; - } - } - #end pager - - print header("Invoice Search Results", menubar( - 'Main Menu', popurl(2) - )). - "$total matching invoices found
". - "\$$tot_balance total balance
". - "\$$tot_amount total amount
". - "
$pager". table(). < - - - - - - - -END - - foreach my $cust_bill ( @cust_bill ) { - my($invnum, $owed, $charged, $date ) = ( - $cust_bill->invnum, - sprintf("%.2f", $cust_bill->getfield('owed')), - sprintf("%.2f", $cust_bill->charged), - $cust_bill->_date, - ); - my $pdate = time2str("%b %d %Y", $date); - - my $rowspan = 1; - - my $view = popurl(2). "view/cust_bill.cgi?$invnum"; - print < - - - - -END - my $custnum = $cust_bill->custnum; - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); - if ( $cust_main ) { - my $cview = popurl(2). "view/cust_main.cgi?". $cust_main->custnum; - my ( $name, $company ) = ( - $cust_main->last. ', '. $cust_main->first, - $cust_main->company, - ); - print <$name - -END - } else { - print <WARNING: couldn't find cust_main.custnum $custnum (cust_bill.invnum $invnum) -END - } - - print ""; - } - $tot_balance = sprintf("%.2f", $tot_balance); - $tot_amount = sprintf("%.2f", $tot_amount); - print "
+ +
+ + +
+ <%= $title %> +

- <%= $menubar ? "$menubar

" : '' %> + <%= $menubar !~ /^\s*$/ ? "$menubar

" : '' %> diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html index 87a50312c..29facb6b6 100644 --- a/httemplate/elements/menubar.html +++ b/httemplate/elements/menubar.html @@ -2,6 +2,7 @@ my($item, $url, @html); while (@_) { ($item, $url) = splice(@_,0,2); + next if $item =~ /^\s*Main\s+Menu\s*$/i; push @html, qq!$item!; } %> diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html new file mode 100644 index 000000000..b05f565ea --- /dev/null +++ b/httemplate/elements/select-access_group.html @@ -0,0 +1,15 @@ +<% + my( $groupnum, %opt ) = @_; + + %opt{'records'} = delete $opt{'access_group'} + if $opt{'access_group'}; + +%><%= include( '/elements/select-table.html', + 'table' => 'access_group', + 'name_col' => 'groupname', + 'value' => $groupnum, + 'empty_label' => '(none)', + #'hashref' => { 'disabled' => '' }, + %opt, + ) +%> diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html new file mode 100644 index 000000000..0beec0842 --- /dev/null +++ b/httemplate/elements/tr-select-access_group.html @@ -0,0 +1,22 @@ +<% + my( $groupnum, %opt ) = @_; + + $opt{'access_group'} ||= [ qsearch( 'access_group', {} ) ]; # { disabled=>'' } ) + + #warn "***** tr-select-access_group: \n". Dumper(%opt); +%> + +<% if ( scalar(@{ $opt{'access_group'} }) == 0 ) { %> + + + +<% } else { %> + +
<%= $opt{'label'} || 'Access group' %> + <%= include( '/elements/select-access_group.html', $groupnum, %opt ) %> +
CVV2 - (help) + (help)
ABA/Routing number - (help) + (help)
BalanceAmountDateContact nameCompany
$invnum\$$owed\$$charged$pdate$company
$pager
". table(). <       Total
BalanceTotal
Amount - \$$tot_balance\$$tot_amount - - - -END - -} - -%> diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi index 03c2619af..6ac0bde18 100755 --- a/httemplate/search/cust_main-otaker.cgi +++ b/httemplate/search/cust_main-otaker.cgi @@ -1,28 +1,23 @@ - - - Customer Search - - - - Customer Search - -
-
- Search for Order taker: - - <% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") - or die dbh->errstr; - $sth->execute() or die $sth->errstr; -# my @otakers = map { $_->[0] } @{$sth->selectall_arrayref}; - %> - -

+<%= include('/elements/header.html', 'Customer Search' ) %> -

- - +
+Search for Order taker: + + +<% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") + or die dbh->errstr; + $sth->execute() or die $sth->errstr; + #my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; +%> + + +

+ +

+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/cust_main-payinfo.html b/httemplate/search/cust_main-payinfo.html deleted file mode 100755 index b82b610d8..000000000 --- a/httemplate/search/cust_main-payinfo.html +++ /dev/null @@ -1,20 +0,0 @@ - - - Customer Search - - - - Customer Search - -
-
- Search for Credit card #: - - - -

- -

- - - diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html deleted file mode 100755 index 154a64199..000000000 --- a/httemplate/search/cust_main-quickpay.html +++ /dev/null @@ -1,44 +0,0 @@ - - - Quick payment entry - - - - Quick payment entry - -

- Main Menu

-
- - Search for last name: - - using search method: - -

Search for company: - - using search method: - -

- -

- -
Explanation of search methods: -
    -
  • All - Try all search methods. -
  • Fuzzy - Searches for matches that are close to your text. -
  • Substring - Searches for matches that contain your text. -
  • Exact - Finds exact matches only, but much faster than the other search methods. -
- - - diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 36ad39da8..8b70ff490 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -220,14 +220,13 @@ if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { eidiot "No matching customers found!\n"; } else { %> - -<% +<%= include('/elements/header.html', "Customer Search Results", '' ) %> - $total ||= scalar(@cust_main); - print header("Customer Search Results",menubar( - 'Main Menu', popurl(2) - )), "$total matching customers found "; + <% $total ||= scalar(@cust_main); %> + <%= $total %> matching customers found + + <% #begin pager my $pager = ''; if ( $total != scalar(@cust_main) && $maxrecords ) { @@ -368,12 +367,14 @@ END my $pcompany = $company ? qq!$company! : ' '; - print < + - $custnum - $last, $first - $pcompany -END + ><%= $custnum %> + ><%= "$last, $first" %> + ><%= $pcompany %> + + <% if ( defined dbdef->table('cust_main')->column('ship_last') ) { my($ship_last,$ship_first,$ship_company)=( $cust_main->ship_last || $cust_main->getfield('last'), @@ -383,15 +384,18 @@ END my $pship_company = $ship_company ? qq!$ship_company! : ' '; - print <$ship_last, $ship_first - $pship_company -END - } + %> - foreach my $addl_col ( @addl_cols ) { - print ""; - if ( $addl_col eq 'tickets' ) { + ><%= "$ship_last, $ship_first" %> + ><%= $pship_company %> + + <% } + + foreach my $addl_col ( @addl_cols ) { %> + + ALIGN=right> + + <% if ( $addl_col eq 'tickets' ) { if ( @custom_priorities ) { print &itable('', 0); foreach my $priority ( @custom_priorities, '' ) { @@ -461,10 +465,14 @@ END } print ""; } + + %> - print "$pager"; + <%= $pager %> -} + <%= include('/elements/footer.html') %> + +<% } #undef $cache; #does this help? diff --git a/httemplate/search/cust_pay.html b/httemplate/search/cust_pay.html deleted file mode 100755 index 6414cf771..000000000 --- a/httemplate/search/cust_pay.html +++ /dev/null @@ -1,18 +0,0 @@ - - - Check # Search - - - - Check # Search - -

-
- Search for check #: - - -

-
- - - diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi index 412c3f79d..d9aada5f4 100755 --- a/httemplate/search/cust_pkg_report.cgi +++ b/httemplate/search/cust_pkg_report.cgi @@ -1,23 +1,22 @@ - - - Packages - - -

Packages

-
- - Return packages with next bill date:

- - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - ) - %> -
-
+<%= include('/elements/header.html', 'Packages' ) %> -
+
+ - - +Return packages with next bill date: +

+ + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + ) + %> +
+ +
+ + +
+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html index a7be76689..f1b7bfa14 100644 --- a/httemplate/search/report_cust_bill.html +++ b/httemplate/search/report_cust_bill.html @@ -1,28 +1,28 @@ - - Invoice report criteria - - -

Invoice report criteria

-
- - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'Invoices for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - - - - - - - - -
Show only open invoices
Show only the single most recent invoice per-customer
-
-
- - +<%= include('/elements/header.html', 'Invoice report criteria' ) %> +
+ + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'Invoices for agent: ', + ) + %> + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + + + + + + + +
Show only open invoices
Show only the single most recent invoice per-customer
+ +
+ + +
+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html index 56bbd0ac0..8ca52dc9a 100644 --- a/httemplate/search/report_cust_credit.html +++ b/httemplate/search/report_cust_credit.html @@ -1,36 +1,38 @@ - - - Credit report criteria - - -

Credit report criteria

-
- - - - +<%= include('/elements/header.html', 'Credit report' ) %> + + + + +
Credits by employee:
+ + + <% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_credit") or die dbh->errstr; $sth->execute or die $sth->errstr; my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; %> - - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> -
Credits by employee: -
-
-
- - + + + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + +
+ + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html index 5d8b74e77..8adf7dc13 100644 --- a/httemplate/search/report_cust_pay.html +++ b/httemplate/search/report_cust_pay.html @@ -1,38 +1,43 @@ - - - Payment report criteria - - -

Payment report criteria

-
- - - - - - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> -
Payments of type: -
-
-
- - +<%= include('/elements/header.html', 'Payment report' ) %> + +
+ + + + + + + + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + +
Payments of type: + +
+ +
+ + +
+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html index 57c318eba..4359918f9 100644 --- a/httemplate/search/report_prepaid_income.html +++ b/httemplate/search/report_prepaid_income.html @@ -1,13 +1,13 @@ - - - Prepaid Income (Unearned Revenue) Report - - - - - - -

Prepaid Income (Unearned Revenue) Report

+<%= include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report', + '', + '', + ' + + + + ' +) %> +
@@ -32,8 +32,6 @@ }); - - - -
+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index 7a8ecd4f0..bdeb8e237 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -1,40 +1,35 @@ - - - Tax Report Criteria - - -

Tax Report Criteria

- - -
- - <%= include( '/elements/tr-select-agent.html' ) %> - - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - - <% my $conf = new FS::Conf; - if ( $conf->exists('enable_taxclasses') ) { - %> - - - - - <% } %> - - <% my @pkg_class = qsearch('pkg_class', {}); - if ( @pkg_class ) { - %> - - - - - <% } %> - -
Show tax classes
Show package classes
- -
-
- - - +<%= include('/elements/header.html', 'Tax Report' ) %> +
+ + + + <%= include( '/elements/tr-select-agent.html' ) %> + + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + <% my $conf = new FS::Conf; + if ( $conf->exists('enable_taxclasses') ) { + %> + + + + + <% } %> + + <% my @pkg_class = qsearch('pkg_class', {}); + if ( @pkg_class ) { + %> + + + + + <% } %> + +
Show tax classes
Show package classes
+ +
+ +
+ +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html index 8f4878dbc..645505101 100644 --- a/httemplate/search/sqlradius.html +++ b/httemplate/search/sqlradius.html @@ -1,9 +1,5 @@ -<%= include( '/elements/header.html', 'Search RADIUS sessions', '', '', ' - - - - -') %> +<%= include( '/elements/header.html', 'Search RADIUS sessions' ) %> +
<% #include( '/elements/table.html' ) %> <%= ntable('#cccccc') %> @@ -47,48 +43,10 @@ <% } %> - - From: - - - - - - - - m/d/y - - - To: - - - - - - - - m/d/y -
(leave one or both dates blank for an open-ended search) - - +<%= include( '/elements/tr-input-beginning_ending.html' ) %> +
- - - +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/svc_acct.html b/httemplate/search/svc_acct.html deleted file mode 100755 index c504c2f34..000000000 --- a/httemplate/search/svc_acct.html +++ /dev/null @@ -1,19 +0,0 @@ - - - Account Search - - - - Account Search - -

-
- Search for username: - - -

- -

- - - diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index f261ea9f3..b02eea8bd 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -61,7 +61,7 @@ my $link_cust = sub { $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; }; -%><%= include ('elements/search.html', +%><%= include( 'elements/search.html', 'title' => "Domain Search Results", 'name' => 'domains', 'query' => $sql_query, diff --git a/httemplate/search/svc_domain.html b/httemplate/search/svc_domain.html deleted file mode 100755 index b759102f4..000000000 --- a/httemplate/search/svc_domain.html +++ /dev/null @@ -1,19 +0,0 @@ - - - Domain Search - - - - Domain Search - -

-
- Search for domain: - - -

- -

- - - diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index 8dbb949c8..7968f3c43 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -38,17 +38,18 @@ if ( $query eq 'svcnum' ) { } if ( scalar(@svc_external) == 1 ) { - print $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum); - #exit; + + %><%= $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %><% + } elsif ( scalar(@svc_external) == 0 ) { -%> - -<% - eidiot "No matching external services found!\n"; -} else { -%> - -<%= include("/elements/header.html","External Search Results",'') %> + + %><%= include('/elements/header.html', 'External Search Results' ) %> + + No matching external services found + +<% } else { + + %><%= include('/elements/header.html', 'External Search Results', '') %> <%= scalar(@svc_external) %> matching external services found diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index ece1b62bb..32e0ee1fc 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -67,7 +67,7 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { - -
> + > <%=$pkg->{pkgnum}%>: <%=$pkg->{pkg}%> - <%=$pkg->{comment}%>
<% unless ($pkg->{cancel}) { %> @@ -75,7 +75,7 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { ( <%=pkg_dates_link($pkg)%> | <%=pkg_customize_link($pkg,$cust_main->custnum)%> ) <% } %>
> + > <% diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index a0bec3906..f0cd993ff 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -58,7 +58,10 @@ <% } %> -
Post credit +
+ +Post credit +
<% @@ -115,8 +118,10 @@ foreach my $cust_pay ($cust_main->cust_pay) { #completely unapplied $pre = 'Unapplied '; $post = ''; - $apply = qq! (apply)'; + $apply = qq! (apply)!; + } elsif ( scalar(@cust_bill_pay) == 1 && scalar(@cust_pay_refund) == 0 && $cust_pay->unapplied == 0 ) { @@ -151,8 +156,9 @@ foreach my $cust_pay ($cust_main->cust_pay) { $desc .= '  '. '$'. $cust_pay->unapplied. ' unapplied'. - qq! (apply)'. + qq! (apply)!. '
'; } } @@ -264,8 +270,9 @@ foreach my $cust_credit ($cust_main->cust_credit) { #completely unapplied $pre = 'Unapplied '; $post = ''; - $apply = qq! (apply)'; + $apply = qq! (apply)!; } elsif ( scalar(@cust_credit_bill) == 1 && scalar(@cust_credit_refund) == 0 && $cust_credit->credited == 0 ) { @@ -299,8 +306,9 @@ foreach my $cust_credit ($cust_main->cust_credit) { if ( $cust_credit->credited > 0 ) { $desc .= '  $'. $cust_credit->credited. ' unapplied'. - qq! (apply)'. + qq! (apply)!. '
'; } } -- 2.11.0