diff options
723 files changed, 49644 insertions, 23611 deletions
diff --git a/ANNOUNCE.1.5 b/ANNOUNCE.1.5 deleted file mode 100644 index 36c78e102..000000000 --- a/ANNOUNCE.1.5 +++ /dev/null @@ -1,54 +0,0 @@ -- broadband (dsl/wireless) tracking, etc etc -- Extended description on invoice for time/data charges -- Multiple, named taxes -- */*FIX -- extended reported and graphing -- integrated RT ticketing system -- one-time payments (in signup server too). DCRD and DCHK on-demand payment types -- credit report -- reseller interface - -1.5.0pre6: -- RADIUS session viewing -- Major updates for reseller interface -- Credit card and ACH refunds (w/supported processor module) -- Proper email payment receipts (not invoice copies) -- modular price plans, rewrote package add/edit page -- fixed up tax report - should be correct for edge cases with named taxes, - tax classes, etc. -- Documentation updates - -1.5.7: -- version numbering change, now even/odd like Perl or Linux -- fix bug that could cause mis-billing on upgrades! (new installs ok) -- updated install documentation -- historical late notice viewing in web interface -- VoIP billing for CDRs from RADIUS -- promotional codes for signup -- lots of RT integration, integrated RT upgraded to 3.2.2, preliminary RT - add-on docs -- one-time referral credits -- invoices now use history records (don't lose details) -- option to credit for remaining service upon package cancel/change - (peter bowen) -- one-time registration codes -- "selfservice_server-session_module" config value can be set to - "Cache::FileCache" on FreeBSD or elsewhere IPC::ShareLite has trouble. -- package changes don't re-charge setup fee -- per-agent payment and credit reports -- CSV and Excel export of most reports, others to be migrated to new report template -- prepaid card support updated: now includes a web generator, agent-specific - prepaid cards, and creates *payments*, not credits -- preliminary setup for Slony-1 PostgreSQL replication -- reformatted latex invoice templates w/Text::Template (khoff) and removed - some useless fields (quantity/unit price) -- simplified upgrade instructions -- add export to vpopmail SQL -- html invoices -- big self-service updates (recharge w/prepaid card, change info, more) -- significant freeside-daily speedup - -notyet (1.5.8?): -- account merging UI in exports (for example, to consolidate passwd files from - multiple servers) - @@ -157,5 +157,11 @@ Perl backend version (c) copyright 2005 Nathan Schmidt Scott Edwards <supadupa@gmail.com> contributed magic for XMLHTTP error handling, and other patches. +Contains XMenu <http://webfx.eae.net/dhtml/xmenu/xmenu.html> +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 <ivan@420.am>) fault. diff --git a/Changelog b/Changelog deleted file mode 100644 index 36c78e102..000000000 --- a/Changelog +++ /dev/null @@ -1,54 +0,0 @@ -- broadband (dsl/wireless) tracking, etc etc -- Extended description on invoice for time/data charges -- Multiple, named taxes -- */*FIX -- extended reported and graphing -- integrated RT ticketing system -- one-time payments (in signup server too). DCRD and DCHK on-demand payment types -- credit report -- reseller interface - -1.5.0pre6: -- RADIUS session viewing -- Major updates for reseller interface -- Credit card and ACH refunds (w/supported processor module) -- Proper email payment receipts (not invoice copies) -- modular price plans, rewrote package add/edit page -- fixed up tax report - should be correct for edge cases with named taxes, - tax classes, etc. -- Documentation updates - -1.5.7: -- version numbering change, now even/odd like Perl or Linux -- fix bug that could cause mis-billing on upgrades! (new installs ok) -- updated install documentation -- historical late notice viewing in web interface -- VoIP billing for CDRs from RADIUS -- promotional codes for signup -- lots of RT integration, integrated RT upgraded to 3.2.2, preliminary RT - add-on docs -- one-time referral credits -- invoices now use history records (don't lose details) -- option to credit for remaining service upon package cancel/change - (peter bowen) -- one-time registration codes -- "selfservice_server-session_module" config value can be set to - "Cache::FileCache" on FreeBSD or elsewhere IPC::ShareLite has trouble. -- package changes don't re-charge setup fee -- per-agent payment and credit reports -- CSV and Excel export of most reports, others to be migrated to new report template -- prepaid card support updated: now includes a web generator, agent-specific - prepaid cards, and creates *payments*, not credits -- preliminary setup for Slony-1 PostgreSQL replication -- reformatted latex invoice templates w/Text::Template (khoff) and removed - some useless fields (quantity/unit price) -- simplified upgrade instructions -- add export to vpopmail SQL -- html invoices -- big self-service updates (recharge w/prepaid card, change info, more) -- significant freeside-daily speedup - -notyet (1.5.8?): -- account merging UI in exports (for example, to consolidate passwd files from - multiple servers) - diff --git a/Changes.1.5.7 b/Changes.1.5.7 deleted file mode 100644 index 1407af62b..000000000 --- a/Changes.1.5.7 +++ /dev/null @@ -1,45 +0,0 @@ -Major new features and updates: -- Version numbering has been simplified. 1.5.7 is the version after - 1.5.0pre6. It is still a development version - releases with odd numbered - middle parts (NN in x.NN.x) are development versions, like Perl or Linux. -- VoIP rating and billing for CDRs from RADIUS -- Lots of additions to the RT integration, integrated RT upgraded to 3.2.2, - preliminary RT installation docs -- Self-service updates (recharge w/prepaid card, change info, UI updates) - -Signup additions: -- Prepaid card support updated: now includes a web generator and agent-specific - prepaid cards -- Promotional codes and one-time registration codes for signup, with - agent-specific option - -Invoicing: -- HTML formatting option for email invoices -- Retypeset and reformatted latex invoice templates w/Text::Template - (thanks to Kristian Hoffman) -- Option to credit a package for the prorated remaining service upon cancel - or change (thanks to Peter Bowen) -- Web interface now includes ability to view all late notices as the customer - sees them - -Reporting: -- Per-agent payment and credit reports -- New report template used for most reports includs CSV and Excel export. - Remaining reports will shortly be migrated to the new template. - -Documentation updates: -- Updated the install documentation -- Simplified upgrade instructions - -Miscellaneous: -- Added direct export to vpopmail SQL databases -- Preliminary setup for Slony-1 PostgreSQL replication -- "selfservice_server-session_module" config value can be set to - "Cache::FileCache" on FreeBSD or elsewhere IPC::ShareLite has trouble. - -Bugfixes and optimizations: -- Significant freeside-daily speedup -- Fix bug that could cause mis-billing on upgrades! (new installs ok) -- Invoices now use history records to retain account details -- Package changes don't re-charge setup fee - diff --git a/Changes.1.5.8 b/Changes.1.5.8 deleted file mode 100644 index ab3572103..000000000 --- a/Changes.1.5.8 +++ /dev/null @@ -1,28 +0,0 @@ -- move account search (httemplate/search/svc_acct.cgi) to new template -- cust-fields configuration value to control which customer fields are shown on reports -- add unlinked mail forward (svc_forward) report -- move cust_pkg search (httemplate/search/cust_pkg.cgi) to new template -- add active/suspended/cancelled customer packages to agent browse -- add export to everyone.net outsource mail service -- add native Radiator export -- added agent/taxclass/card type-specific gateway overrides for people with - multiple payment gateways for different resellers, taxclasses and/or card - types -- re-did billing section of customer edit and added maestro/switch/solo support -- add cpanel export -- added prepaid packages that set the RADIUS "Expiration" attribute and - auto-suspend on their next bill date -- added banned card table and option to send customer cards there on cancel -- moved to XMLHttpRequest instead of hidden iframe transport for progress bar, - should be more efficient and improve compatibility with Konq and maybe other - browsers? -- also use XMLHttpRequest for retreiving states rather than send a huge page - for customer add/edit, much faster -- redo account view and edit pages, add ability to edit uid/gid if conf options - for it are turned on -- redo quick payment entry page with ajax magic -- explicit payment types for cash and (optionally) western union -- bulk svcpart change -- tax report updated, per-agent option and most items now clickable -- direct radiator export -- added maximum "cap" options to RADIUS usage charges @@ -111,6 +111,8 @@ L<FS::cust_svc> - Service class L<FS::cust_pkg> - Customer package class +L<FS::cust_pkg_option> - Customer package option class + L<FS::cust_main> - Customer class L<FS::cust_main_invoice> - Invoice destination @@ -142,7 +144,9 @@ L<FS::cust_credit_bill> - Credit application to invoice class L<FS::cust_pay_refund> - Refund application to payment class -L<FS::cust_pay_batch> - Credit card transaction queue class +L<FS::pay_batch> - Credit card transaction queue class + +L<FS::cust_pay_batch> - Credit card transaction member queue class L<FS::prepay_credit> - Prepaid "calling card" credit class. diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm new file mode 100644 index 000000000..5194bd4d9 --- /dev/null +++ b/FS/FS/AccessRight.pm @@ -0,0 +1,202 @@ +package FS::AccessRight; + +use strict; +use 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 + +#well, this is what we have for now. could be ordered better, could be lots of +# things better, but this ACL system does 99% of what folks need and the UI +# isn't *that* bad +# +# okay, well it *really* needs some catgorization in the UI. badly. +@rights = ( + +## +# basic customer rights +## + 'New customer', + 'View customer', + #'View Customer | View tickets', + 'Edit customer', + 'Cancel customer', + 'Complimentary customer', #aka users-allow_comp + 'Delete customer', #aka. deletecustomers #Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customers' packages if they cancel service. + 'Add customer note', #NEW + 'Edit customer note', #NEW + +### +# customer package rights +### + 'View customer packages', #NEW + 'Order customer package', + 'One-time charge', + 'Change customer package', + 'Bulk change customer packages', + 'Edit customer package dates', + 'Customize customer package', + 'Suspend customer package', + 'Unsuspend customer package', + 'Cancel customer package immediately', + 'Cancel customer package later', + 'Add on-the-fly cancel reason', #NEW + 'Add on-the-fly suspend reason', #NEW + +### +# customer service rights +### + 'Edit usage', #NEW + 'View customer services', #NEW + 'Provision customer service', + 'Recharge customer service', #NEW + 'Unprovision customer service', + + 'View/link unlinked services', #not agent-virtualizable without more work + +### +# customer invoice/financial info rights +### + 'View invoices', + 'View customer tax exemptions', #yow + 'View customer batched payments', #NEW + +### +# customer payment rights +### + 'Post payment', + 'Post payment batch', + 'Unapply payment', #aka. unapplypayments Enable "unapplication" of unclosed payments. + 'Process payment', + 'Refund payment', + + 'Delete payment', #aka. deletepayments - Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted. + + 'Delete refund', #NEW + +### +# customer credit rights +### + 'Post credit', + #'Apply credit', + 'Unapply credit', #aka unapplycredits Enable "unapplication" of unclosed credits. + 'Delete credit', #aka. deletecredits Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted. + +### +# customer voiding rights.. +### + 'Credit card void', #aka. cc-void #Enable local-only voiding of echeck payments in addition to refunds against the payment gateway + 'Echeck void', #aka. echeck-void #Enable local-only voiding of echeck payments in addition to refunds against the payment gateway + 'Regular void', + 'Unvoid', #aka. unvoid #Enable unvoiding of voided payments + +### +# report/listing rights... +### + 'List customers', + 'List zip codes', #NEW + 'List invoices', + 'List packages', + 'List services', + + 'List rating data', # 'Usage reports', + 'Billing event reports', + 'Financial reports', + +### +# misc rights +### + 'Job queue', # these are not currently agent-virtualized + 'Process batches', # NEW + 'Reprocess batches', # NEW + 'Import', # + 'Export', # + +### +# misc misc rights +### + 'Raw SQL', #NEW + +### +# setup/config rights +### + 'Edit advertising sources', + 'Edit global advertising sources', + + 'Configuration', #most of the rest of the configuraiton is not + # agent-virtualized +); + +sub rights { + @rights; +} + diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm index 9dc635ad2..4c2693db8 100644 --- a/FS/FS/CGI.pm +++ b/FS/FS/CGI.pm @@ -9,7 +9,7 @@ use URI::URL; use FS::UID; @ISA = qw(Exporter); -@EXPORT_OK = qw(header menubar idiot eidiot popurl table itable ntable +@EXPORT_OK = qw(header menubar idiot eidiot popurl rooturl table itable ntable small_custview myexit http_header); =head1 NAME @@ -62,9 +62,9 @@ sub header { </HEAD> <BODY BGCOLOR="#e8e8e8"$etc> <FONT SIZE=6> - $title + <CENTER>$title</CENTER> </FONT> - <BR><BR> + <BR><!--<BR>--> END $x .= $menubar. "<BR><BR>" if $menubar; $x; @@ -79,14 +79,7 @@ Sets an http header. sub http_header { my ( $header, $value ) = @_; if (exists $ENV{MOD_PERL}) { - if ( defined $main::Response - && $main::Response->isa('Apache::ASP::Response') ) { #Apache::ASP - if ( $header =~ /^Content-Type$/ ) { - $main::Response->{ContentType} = $value; - } else { - $main::Response->AddHeader( $header => $value ); - } - } elsif ( defined $HTML::Mason::Commands::r ) { #Mason + if ( defined $HTML::Mason::Commands::r ) { #Mason ## is this the correct pacakge for $r ??? for 1.0x and 1.1x ? if ( $header =~ /^Content-Type$/ ) { $HTML::Mason::Commands::r->content_type($value); @@ -115,6 +108,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!<A HREF="$url">$item</A>!; } join(' | ',@html); @@ -185,12 +179,7 @@ If running under mod_perl, calles Apache::exit, otherwise, calls exit. sub myexit { if (exists $ENV{MOD_PERL}) { - if ( defined $main::Response - && $main::Response->isa('Apache::ASP::Response') ) { #Apache::ASP - $main::Response->End(); - require Apache; - Apache::exit(); - } elsif ( defined $HTML::Mason::Commands::m ) { #Mason + if ( defined $HTML::Mason::Commands::m ) { #Mason #$HTML::Mason::Commands::m->flush_buffer(); $HTML::Mason::Commands::m->abort(); die "shouldn't fall through to here (mason \$m->abort didn't)"; @@ -225,6 +214,40 @@ sub popurl { $x; } +=item rooturl + +=cut + +sub rooturl { + # better to start with the client-provided URL + my $cgi = &FS::UID::cgi; + my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url; + $url_string =~ s/\?.*//; + + #even though this is kludgy + $url_string =~ s{ / index\.html /? $ } + {/}x; + $url_string =~ + s{ + / + (browse|config|docs|edit|graph|misc|search|view|pref|rt|elements) + / + (process/)? + ([\w\-\.\/]+) + $ + } + {}x; + + #elements because of progress-popup.html... + #XXX remove anything from elements that is called directly & prevent + #those pages from being served up + + $url_string .= '/' unless $url_string =~ /\/$/; + + $url_string; + +} + =item table Returns HTML tag for beginning a table. @@ -277,7 +300,7 @@ sub ntable { } -=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT, NOBALANCE_FLAG +=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT, NOBALANCE_FLAG, URL Sheesh. I should just switch to Mason. @@ -290,12 +313,18 @@ sub small_custview { my $arg = shift; my $countrydefault = shift || 'US'; my $nobalance = shift; + my $url = shift; my $cust_main = ref($arg) ? $arg : qsearchs('cust_main', { 'custnum' => $arg } ) or die "unknown custnum $arg"; - my $html = 'Customer #<B>'. $cust_main->custnum. '</B></A>'. + my $html; + + $html = qq!View <A HREF="$url?! . $cust_main->custnum . '">' + if $url; + + $html .= 'Customer #<B>'. $cust_main->custnum. '</B></A>'. ' - <B><FONT COLOR="'. $cust_main->statuscolor. '">'. ucfirst($cust_main->status). '</FONT></B>'. ntable('#e8e8e8'). '<TR><TD VALIGN="top">'. ntable("#cccccc",2). diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 4b67f53af..bade103f2 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -8,9 +8,11 @@ use Date::Format; use Business::CreditCard; use Time::Duration; use FS::CGI qw(small_custview); #doh +use FS::UI::Web; use FS::Conf; use FS::Record qw(qsearch qsearchs); use FS::Msgcat qw(gettext); +use FS::Misc qw(card_types); use FS::ClientAPI_SessionCache; use FS::svc_acct; use FS::svc_domain; @@ -20,6 +22,15 @@ use FS::cust_main; use FS::cust_bill; use FS::cust_main_county; use FS::cust_pkg; +use HTML::Entities; + +#false laziness with FS::cust_main +BEGIN { + eval "use Time::Local;"; + die "Time::Local minimum version 1.05 required with Perl versions before 5.6" + if $] < 5.006 && !defined($Time::Local::VERSION); + eval "use Time::Local qw(timelocal_nocheck);"; +} use vars qw( @cust_main_editable_fields ); @cust_main_editable_fields = qw( @@ -127,7 +138,7 @@ sub customer_info { } if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { - $return{payinfo} = $cust_main->payinfo_masked; + $return{payinfo} = $cust_main->paymask; @return{'month', 'year'} = $cust_main->paydate_monthyear; } @@ -172,7 +183,7 @@ sub edit_info { if ( $p->{'payby'} =~ /^(CARD|DCRD)$/ ) { $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01'); - if ( $new->payinfo eq $cust_main->payinfo_masked ) { + if ( $new->payinfo eq $cust_main->paymask ) { $new->payinfo($cust_main->payinfo); } else { $new->paycvv($p->{'paycvv'}); @@ -204,33 +215,30 @@ sub payment_info { #generic ## - my $conf = new FS::Conf; - my %states = map { $_->state => 1 } - qsearch('cust_main_county', { - 'country' => $conf->config('countrydefault') || 'US' - } ); - use vars qw($payment_info); #cache for performance - $payment_info ||= { + unless ( $payment_info ) { - #list all counties/states/countries - 'cust_main_county' => - [ map { $_->hashref } qsearch('cust_main_county', {}) ], + my $conf = new FS::Conf; + my %states = map { $_->state => 1 } + qsearch('cust_main_county', { + 'country' => $conf->config('countrydefault') || 'US' + } ); - #shortcut for one-country folks - 'states' => - [ sort { $a cmp $b } keys %states ], + $payment_info = { - 'card_types' => { - 'VISA' => 'VISA card', - 'MasterCard' => 'MasterCard', - 'Discover' => 'Discover card', - 'American Express' => 'American Express card', - 'Switch' => 'Switch', - 'Solo' => 'Solo', - }, + #list all counties/states/countries + 'cust_main_county' => + [ map { $_->hashref } qsearch('cust_main_county', {}) ], - }; + #shortcut for one-country folks + 'states' => + [ sort { $a cmp $b } keys %states ], + + 'card_types' => card_types(), + + }; + + } ## #customer-specific @@ -253,7 +261,7 @@ sub payment_info { $return{payby} = $cust_main->payby; if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { - #warn $return{card_type} = cardtype($cust_main->payinfo); + $return{card_type} = cardtype($cust_main->payinfo); $return{payinfo} = $cust_main->payinfo; @return{'month', 'year'} = $cust_main->paydate_monthyear; @@ -318,17 +326,15 @@ sub process_payment { return { 'error' => gettext('unknown_card_type') } if cardtype($payinfo) eq "Unknown"; - if ( defined $cust_main->dbdef_table->column('paycvv') ) { - if ( length($p->{'paycvv'} ) ) { - if ( cardtype($payinfo) eq 'American Express card' ) { - $p->{'paycvv'} =~ /^(\d{4})$/ - or return { 'error' => "CVV2 (CID) for American Express cards is four digits." }; - $paycvv = $1; - } else { - $p->{'paycvv'} =~ /^(\d{3})$/ - or return { 'error' => "CVV2 (CVC2/CID) is three digits." }; - $paycvv = $1; - } + if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) { + if ( cardtype($payinfo) eq 'American Express card' ) { + $p->{'paycvv'} =~ /^\s*(\d{4})\s*$/ + or return { 'error' => "CVV2 (CID) for American Express cards is four digits." }; + $paycvv = $1; + } else { + $p->{'paycvv'} =~ /^\s*(\d{3})\s*$/ + or return { 'error' => "CVV2 (CVC2/CID) is three digits." }; + $paycvv = $1; } } @@ -380,18 +386,27 @@ sub process_prepay { my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) or return { 'error' => "unknown custnum $custnum" }; - my( $amount, $seconds ) = ( 0, 0 ); + my( $amount, $seconds, $upbytes, $downbytes, $totalbytes ) = ( 0, 0, 0, 0, 0 ); my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'}, \$amount, - \$seconds + \$seconds, + \$upbytes, + \$downbytes, + \$totalbytes, ); return { 'error' => $error } if $error; - return { 'error' => '', - 'amount' => $amount, - 'seconds' => $seconds, - 'duration' => duration_exact($seconds), + return { 'error' => '', + 'amount' => $amount, + 'seconds' => $seconds, + 'duration' => duration_exact($seconds), + 'upbytes' => $upbytes, + 'upload' => FS::UI::Web::bytecount_unexact($upbytes), + 'downbytes' => $downbytes, + 'download' => FS::UI::Web::bytecount_unexact($downbytes), + 'totalbytes'=> $totalbytes, + 'totalload' => FS::UI::Web::bytecount_unexact($totalbytes), }; } @@ -414,10 +429,37 @@ sub invoice { return { 'error' => '', 'invnum' => $invnum, 'invoice_text' => join('', $cust_bill->print_text ), + 'invoice_html' => $cust_bill->print_html, }; } +sub invoice_logo { + my $p = shift; + + #sessioning for this? how do we get the session id to the backend invoice + # template so it can add it to the link, blah + + my $templatename = $p->{'templatename'}; + + #false laziness-ish w/view/cust_bill-logo.cgi + + my $conf = new FS::Conf; + if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) { + $templatename = "_$1"; + } else { + $templatename = ''; + } + + my $filename = "logo$templatename.png"; + + return { 'error' => '', + 'logo' => $conf->config_binary($filename), + 'content_type' => 'image/png', #should allow gif, jpg too + }; +} + + sub list_invoices { my $p = shift; my $session = _cache->get($p->{'session_id'}) @@ -499,6 +541,141 @@ sub list_pkgs { } +sub list_svcs { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $search = { 'custnum' => $custnum }; + $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + my $cust_main = qsearchs('cust_main', $search ) + or return { 'error' => "unknown custnum $custnum" }; + + my @cust_svc = (); + #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) { + foreach my $cust_pkg ( $p->{'ncancelled'} + ? $cust_main->ncancelled_pkgs + : $cust_main->unsuspended_pkgs ) { + push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context + } + @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc + if $p->{'svcdb'}; + + #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username } + # @svc_x; + + { + #no#'svcnum' => $session->{'svcnum'}, + 'custnum' => $custnum, + 'svcs' => [ map { + my $svc_x = $_->svc_x; + my($label, $value) = $_->label; + my $part_pkg = $svc_x->cust_svc->cust_pkg->part_pkg; + + { 'svcnum' => $_->svcnum, + 'label' => $label, + 'value' => $value, + 'username' => $svc_x->username, + 'email' => $svc_x->email, + 'seconds' => $svc_x->seconds, + 'upbytes' => $svc_x->upbytes, + 'downbytes' => $svc_x->downbytes, + 'totalbytes'=> $svc_x->totalbytes, + 'recharge_amount' => $part_pkg->option('recharge_amount', 1), + 'recharge_seconds' => $part_pkg->option('recharge_seconds', 1), + 'recharge_upbytes' => $part_pkg->option('recharge_upbytes', 1), + 'recharge_downbytes' => $part_pkg->option('recharge_downbytes', 1), + 'recharge_totalbytes' => $part_pkg->option('recharge_totalbytes', 1), + # more... + }; + } + @cust_svc + ], + }; + +} + +sub list_svc_usage { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $search = { 'svcnum' => $p->{'svcnum'} }; + $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + my $svc_acct = qsearchs ( 'svc_acct', $search ); + return { 'error' => 'No service selected in list_svc_usage' } + unless $svc_acct; + + my $freq = $svc_acct->cust_svc->cust_pkg->part_pkg->freq; + my $start = $svc_acct->cust_svc->cust_pkg->setup; + #my $end = $svc_acct->cust_svc->cust_pkg->bill; # or time? + my $end = time; + + unless($p->{beginning}){ + $p->{beginning} = $svc_acct->cust_svc->cust_pkg->last_bill; + $p->{ending} = $end; + } + my @usage = (); + + foreach my $part_export ( + map { qsearch ( 'part_export', { 'exporttype' => $_ } ) } + qw (sqlradius sqlradius_withdomain') + ) { + + push @usage, @ { $part_export->usage_sessions($p->{beginning}, + $p->{ending}, + $svc_acct) + }; + } + + #kinda false laziness with FS::cust_main::bill, but perhaps + #we should really change this bit to DateTime and DateTime::Duration + # + #change this bit to use Date::Manip? CAREFUL with timezones (see + # mailing list archive) + my ($nsec,$nmin,$nhour,$nmday,$nmon,$nyear) = + (localtime($p->{ending}) )[0,1,2,3,4,5]; + my ($psec,$pmin,$phour,$pmday,$pmon,$pyear) = + (localtime($p->{beginning}) )[0,1,2,3,4,5]; + + if ( $freq =~ /^\d+$/ ) { + $nmon += $freq; + until ( $nmon < 12 ) { $nmon -= 12; $nyear++; } + $pmon -= $freq; + until ( $pmon >= 0 ) { $pmon += 12; $pyear--; } + } elsif ( $freq =~ /^(\d+)w$/ ) { + my $weeks = $1; + $nmday += $weeks * 7; + $pmday -= $weeks * 7; + } elsif ( $freq =~ /^(\d+)d$/ ) { + my $days = $1; + $nmday += $days; + $pmday -= $days; + } elsif ( $freq =~ /^(\d+)h$/ ) { + my $hours = $1; + $nhour += $hours; + $phour -= $hours; + } else { + return { 'error' => "unparsable frequency: ". $freq }; + } + + my $previous = timelocal_nocheck($psec,$pmin,$phour,$pmday,$pmon,$pyear); + my $next = timelocal_nocheck($nsec,$nmin,$nhour,$nmday,$nmon,$nyear); + + + { + 'error' => '', + 'svcnum' => $p->{svcnum}, + 'beginning' => $p->{beginning}, + 'ending' => $p->{ending}, + 'previous' => ($previous > $start) ? $previous : $start, + 'next' => ($next < $end) ? $next : $end, + 'usage' => \@usage, + }; +} + sub order_pkg { my $p = shift; @@ -535,7 +712,7 @@ sub order_pkg { $svcpart ||= $cust_pkg->part_pkg->svcpart($svcdb); my %fields = ( - 'svc_acct' => [ qw( username _password sec_phrase popnum ) ], + 'svc_acct' => [ qw( username domsvc _password sec_phrase popnum ) ], 'svc_domain' => [ qw( domain ) ], 'svc_external' => [ qw( id title ) ], ); @@ -581,12 +758,126 @@ sub order_pkg { my $conf = new FS::Conf; if ( $conf->exists('signup_server-realtime') ) { + my $bill_error = _do_bop_realtime( $cust_main ); + + if ($bill_error) { + $cust_pkg->cancel('quiet'=>1); + return $bill_error; + } else { + $cust_pkg->reexport; + } + + } else { + $cust_pkg->reexport; + } + + return { error => '', pkgnum => $cust_pkg->pkgnum }; + +} + +sub change_pkg { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $search = { 'custnum' => $custnum }; + $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + my $cust_main = qsearchs('cust_main', $search ) + or return { 'error' => "unknown custnum $custnum" }; + + my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $p->{pkgnum} } ) + or return { 'error' => "unknown package $p->{pkgnum}" }; + + my @newpkg; + my $error = FS::cust_pkg::order( $custnum, + [$p->{pkgpart}], + [$p->{pkgnum}], + \@newpkg, + ); + + my $conf = new FS::Conf; + if ( $conf->exists('signup_server-realtime') ) { + + my $bill_error = _do_bop_realtime( $cust_main ); + + if ($bill_error) { + $newpkg[0]->suspend; + return $bill_error; + } else { + $newpkg[0]->reexport; + } + + } else { + $newpkg[0]->reexport; + } + + return { error => '', pkgnum => $cust_pkg->pkgnum }; + +} + +sub order_recharge { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $search = { 'custnum' => $custnum }; + $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + my $cust_main = qsearchs('cust_main', $search ) + or return { 'error' => "unknown custnum $custnum" }; + + my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $p->{'svcnum'} } ) + or return { 'error' => "unknown service " . $p->{'svcnum'} }; + + my $svc_x = $cust_svc->svc_x; + my $part_pkg = $cust_svc->cust_pkg->part_pkg; + + my %vhash = + map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_, 1) } + qw ( recharge_seconds recharge_upbytes recharge_downbytes + recharge_totalbytes ); + my $amount = $part_pkg->option('recharge_amount', 1); + + my ($l, $v, $d) = $cust_svc->label; # blah + my $pkg = "Recharge $v"; + + my $bill_error = $cust_main->charge($amount, $pkg, + "time: $vhash{seconds}, up: $vhash{upbytes}," . + "down: $vhash{downbytes}, total: $vhash{totalbytes}", + $part_pkg->taxclass); #meh + + my $conf = new FS::Conf; + if ( $conf->exists('signup_server-realtime') && !$bill_error ) { + + $bill_error = _do_bop_realtime( $cust_main ); + + if ('bill_error') { + return $bill_error; + } else { + my $error = $svc_x->recharge (\%vhash); + return { 'error' => $error } if $error; + } + + } else { + my $error = $bill_error; + $error ||= $svc_x->recharge (\%vhash); + return { 'error' => $error } if $error; + } + + return { error => '', svc => $cust_svc->part_svc->svc }; + +} + +sub _do_bop_realtime { + my ($cust_main) = @_; + my $old_balance = $cust_main->balance; my $bill_error = $cust_main->bill; - $cust_main->apply_payments; - $cust_main->apply_credits; - $bill_error = $cust_main->collect; + + $cust_main->apply_payments_and_credits; + $bill_error = $cust_main->collect('realtime' => 1); if ( $cust_main->balance > $old_balance && $cust_main->balance > 0 @@ -596,18 +887,10 @@ sub order_pkg { 'self-service decline' ); $cust_main->apply_credits( 'order' => 'newest' ); - $cust_pkg->cancel('quiet'=>1); return { 'error' => '_decline', 'bill_error' => $bill_error }; - } else { - $cust_pkg->reexport; } - } else { - $cust_pkg->reexport; - } - - return { error => '', pkgnum => $cust_pkg->pkgnum }; - + ''; } sub cancel_pkg { @@ -772,6 +1055,45 @@ sub unprovision_svc { } +sub myaccount_passwd { + my $p = shift; + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + return { 'error' => "New passwords don't match." } + if $p->{'new_password'} ne $p->{'new_password2'}; + + return { 'error' => 'Enter new password' } + unless length($p->{'new_password'}); + + #my $search = { 'custnum' => $custnum }; + #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + $custnum =~ /^(\d+)$/ or die "illegal custnum"; + my $search = " AND custnum = $1"; + $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent'; + + my $svc_acct = qsearchs( { + 'table' => 'svc_acct', + 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '. + 'LEFT JOIN cust_pkg USING ( pkgnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => { 'svcnum' => $p->{'svcnum'}, }, + 'extra_sql' => $search, #important + } ) + or return { 'error' => "Service not found" }; + + $svc_acct->_password($p->{'new_password'}); + my $error = $svc_acct->replace(); + + my($label, $value) = $svc_acct->cust_svc->label; + + return { 'error' => $error, + 'label' => $label, + 'value' => $value, + }; + +} + #-- sub _custoragent_session_custnum { diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index ed71651fa..ac211ec27 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -5,6 +5,7 @@ use Tie::RefHash; use FS::Conf; use FS::Record qw(qsearch qsearchs dbdef); use FS::Msgcat qw(gettext); +use FS::Misc qw(card_types); use FS::ClientAPI_SessionCache; use FS::agent; use FS::cust_main_county; @@ -22,29 +23,21 @@ sub signup_info { my $conf = new FS::Conf; - use vars qw($signup_info); #cache for performance; - $signup_info ||= { - + use vars qw($signup_info_cache); #cache for performance; + $signup_info_cache ||= { 'cust_main_county' => [ map { $_->hashref } qsearch('cust_main_county', {}) ], 'agent' => [ map { $_->hashref } - qsearch('agent', dbdef->table('agent')->column('disabled') - ? { 'disabled' => '' } - : {} - ) + qsearch('agent', { 'disabled' => '' } ) ], 'part_referral' => [ map { $_->hashref } - qsearch('part_referral', - dbdef->table('part_referral')->column('disabled') - ? { 'disabled' => '' } - : {} - ) + qsearch('part_referral', { 'disabled' => '' }) ], 'agentnum2part_pkg' => @@ -53,28 +46,33 @@ sub signup_info { my $href = $_->pkgpart_hashref; $_->agentnum => [ - map { { 'payby' => [ $_->payby ], %{$_->hashref} } } + map { { 'payby' => [ $_->payby ], + 'freq_pretty' => $_->freq_pretty, + 'options' => { $_->options }, + %{$_->hashref} + } } grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } } qsearch( 'part_pkg', { 'disabled' => '' } ) ]; - } qsearch('agent', dbdef->table('agent')->column('disabled') - ? { 'disabled' => '' } - : {} - ) + } qsearch('agent', { 'disabled' => '' }) }, 'svc_acct_pop' => [ map { $_->hashref } qsearch('svc_acct_pop',{} ) ], + 'emailinvoiceonly' => $conf->exists('emailinvoiceonly'), + 'security_phrase' => $conf->exists('security_phrase'), 'payby' => [ $conf->config('signup_server-payby') ], - 'cvv_enabled' => defined dbdef->table('cust_main')->column('paycvv'), + 'card_types' => card_types(), + + 'cvv_enabled' => defined dbdef->table('cust_main')->column('paycvv'), # 1, - 'ship_enabled' => defined dbdef->table('cust_main')->column('ship_last'), + 'ship_enabled' => defined dbdef->table('cust_main')->column('ship_last'),#1, 'msgcat' => { map { $_=>gettext($_) } qw( - passwords_dont_match invalid_card unknown_card_type not_a empty_password + passwords_dont_match invalid_card unknown_card_type not_a empty_password illegal_or_empty_text ) }, 'statedefault' => $conf->config('statedefault') || 'CA', @@ -83,9 +81,39 @@ sub signup_info { 'refnum' => $conf->config('signup_server-default_refnum'), + 'default_pkgpart' => $conf->config('signup_server-default_pkgpart'), + }; - my $agentnum = $conf->config('signup_server-default_agentnum'); + my $signup_info = { %$signup_info_cache }; + + my @addl = qw( signup_server-classnum2 signup_server-classnum3 ); + + if ( grep { $conf->exists($_) } @addl ) { + + $signup_info->{optional_packages} = []; + + foreach my $addl ( @addl ) { + my $classnum = $conf->config($addl) or next; + + my @pkgs = map { { + 'freq_pretty' => $_->freq_pretty, + 'options' => { $_->options }, + %{ $_->hashref } + }; + } + qsearch( 'part_pkg', { classnum => $classnum } ); + + push @{$signup_info->{optional_packages}}, \@pkgs; + + } + + } + + my $agentnum = $packet->{'agentnum'} + || $conf->config('signup_server-default_agentnum'); + $agentnum =~ /^(\d*)$/ or die "illegal agentnum"; + $agentnum = $1; my $session = ''; if ( exists $packet->{'session_id'} ) { @@ -98,13 +126,31 @@ sub signup_info { } else { return { 'error' => "Can't resume session" }; #better error message } + }elsif( exists $packet->{'customer_session_id'} ) { + my $cache = new FS::ClientAPI_SessionCache( { + 'namespace' => 'FS::ClientAPI::MyAccount', + } ); + $session = $cache->get($packet->{'customer_session_id'}); + if ( $session ) { + my $custnum = $session->{'custnum'}; + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum }); + return { 'error' => "Can't find your customer record" } unless $cust_main; + $agentnum = $cust_main->agentnum; + } else { + return { 'error' => "Can't resume session" }; #better error message + } } $signup_info->{'part_pkg'} = []; if ( $packet->{'reg_code'} ) { $signup_info->{'part_pkg'} = - [ map { { 'payby' => [ $_->payby ], %{$_->hashref} } } + [ map { { 'payby' => [ $_->payby ], + 'freq_pretty' => $_->freq_pretty, + 'options' => { $_->options }, + %{$_->hashref} + }; + } grep { $_->svcpart('svc_acct') } map { $_->part_pkg } qsearchs( 'reg_code', { 'code' => $packet->{'reg_code'}, @@ -118,7 +164,11 @@ sub signup_info { } elsif ( $packet->{'promo_code'} ) { $signup_info->{'part_pkg'} = - [ map { { 'payby' => [ $_->payby ], %{$_->hashref} } } + [ map { { 'payby' => [ $_->payby ], + 'freq_pretty' => $_->freq_pretty, + 'options' => { $_->options }, + %{$_->hashref} + } } grep { $_->svcpart('svc_acct') } qsearch( 'part_pkg', { 'promo_code' => { op=>'ILIKE', @@ -133,12 +183,29 @@ sub signup_info { if ( $agentnum && ! @{ $signup_info->{'part_pkg'} } ) { $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}; + + $signup_info->{'part_referral'} = + [ + map { $_->hashref } + qsearch( { + 'table' => 'part_referral', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => "AND ( agentnum = $agentnum ". + " OR agentnum IS NULL ) ", + }, + ) + ]; + } # else { # delete $signup_info->{'part_pkg'}; #} - if ( $session ) { + $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} } # case? + @{ $signup_info->{'part_pkg'} } + ]; + + if ( exists $packet->{'session_id'} ) { my $agent_signup_info = { %$signup_info }; delete $agent_signup_info->{agentnum2part_pkg}; $agent_signup_info->{'agent'} = $session->{'agent'}; @@ -214,7 +281,9 @@ sub new_customer { $cust_main->payinfo($cust_main->daytime) if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo; - my @invoicing_list = split( /\s*\,\s*/, $packet->{'invoicing_list'} ); + my @invoicing_list = $packet->{'invoicing_list'} + ? split( /\s*\,\s*/, $packet->{'invoicing_list'} ) + : (); $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/; my $pkgpart = $1; @@ -299,10 +368,9 @@ sub new_customer { #warn "[fs_signup_server] error billing new customer: $bill_error" # if $bill_error; - $cust_main->apply_payments; - $cust_main->apply_credits; + $cust_main->apply_payments_and_credits; - $bill_error = $cust_main->collect; + $bill_error = $cust_main->collect('realtime' => 1); #warn "[fs_signup_server] error collecting from new customer: $bill_error" # if $bill_error; diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 88dbdf082..8db0b0c6a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1,9 +1,14 @@ package FS::Conf; -use vars qw($default_dir @config_items $DEBUG ); +use vars qw($default_dir $base_dir @config_items @card_types $DEBUG ); use IO::File; use File::Basename; use FS::ConfItem; +use FS::ConfDefaults; + +$base_dir = '%%%FREESIDE_CONF%%%'; +$default_dir = '%%%FREESIDE_CONF%%%'; + $DEBUG = 0; @@ -51,13 +56,15 @@ $FS::Conf::default_dir has not been set. sub new { my($proto,$dir) = @_; my($class) = ref($proto) || $proto; - my($self) = { 'dir' => $dir || $default_dir } ; + my($self) = { 'dir' => $dir || $default_dir, + 'base_dir' => $base_dir, + }; bless ($self, $class); } =item dir -Returns the directory. +Returns the conf directory. =cut @@ -72,6 +79,23 @@ sub dir { $1; } +=item base_dir + +Returns the base directory. By default this is /usr/local/etc/freeside. + +=cut + +sub base_dir { + my($self) = @_; + my $base_dir = $self->{base_dir}; + -e $base_dir or die "FATAL: $base_dir doesn't exist!"; + -d $base_dir or die "FATAL: $base_dir isn't a directory!"; + -r $base_dir or die "FATAL: Can't read $base_dir!"; + -x $base_dir or die "FATAL: $base_dir not searchable (executable)!"; + $base_dir =~ /^(.*)$/; + $1; +} + =item config KEY Returns the configuration value or values (depending on context) for key. @@ -283,6 +307,20 @@ httemplate/docs/config.html =cut +#Business::CreditCard +@card_types = ( + "VISA card", + "MasterCard", + "Discover card", + "American Express card", + "Diner's Club/Carte Blanche", + "enRoute", + "JCB", + "BankCard", + "Switch", + "Solo", +); + @config_items = map { new FS::ConfItem $_ } ( { @@ -412,6 +450,17 @@ httemplate/docs/config.html }, { + 'key' => 'date_format', + 'section' => 'UI', + 'description' => 'Format for displaying dates', + 'type' => 'select', + 'select_hash' => [ + '%m/%d/%Y' => 'MM/DD/YYYY', + '%Y/%m/%d' => 'YYYY/MM/DD', + ], + }, + + { 'key' => 'cyrus', 'section' => 'deprecated', 'description' => '<b>DEPRECATED</b>, add a <i>cyrus</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to integrate with <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>, three lines: IMAP server, admin username, and admin password. Cyrus::IMAP::Admin should be installed locally and the connection to the server secured.', @@ -434,29 +483,36 @@ httemplate/docs/config.html { 'key' => 'deletepayments', - 'section' => 'UI', - 'description' => 'Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.', + 'section' => 'billing', + 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.', 'type' => [qw( checkbox text )], }, { 'key' => 'deletecredits', - 'section' => 'UI', - 'description' => 'Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.', 'type' => [qw( checkbox text )], }, { + 'key' => 'deleterefunds', + 'section' => 'billing', + 'description' => 'Enable deletion of unclosed refunds. Be very careful! Only delete refunds that were data-entry errors, not adjustments.', + 'type' => 'checkbox', + }, + + { 'key' => 'unapplypayments', - 'section' => 'UI', - 'description' => 'Enable "unapplication" of unclosed payments.', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.', 'type' => 'checkbox', }, { 'key' => 'unapplycredits', - 'section' => 'UI', - 'description' => 'Enable "unapplication" of unclosed credits.', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to nable "unapplication" of unclosed credits.', 'type' => 'checkbox', }, @@ -503,6 +559,13 @@ httemplate/docs/config.html }, { + 'key' => 'emailinvoiceautoalways', + 'section' => 'billing', + 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses', + 'type' => 'checkbox', + }, + + { 'key' => 'exclude_ip_addr', 'section' => '', 'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)', @@ -988,6 +1051,13 @@ httemplate/docs/config.html }, { + 'key' => 'unsuspend-always_adjust_next_bill_date', + 'section' => 'billing', + 'description' => 'Global override that causes unsuspensions to always adjust the next bill date under any circumstances. This is now controlled on a per-package bases - probably best not to use this option unless you are a legacy installation that requires this behaviour.', + 'type' => 'checkbox', + }, + + { 'key' => 'usernamemin', 'section' => 'username', 'description' => 'Minimum username length (default 2)', @@ -1176,15 +1246,63 @@ httemplate/docs/config.html { 'key' => 'signup_server-default_agentnum', 'section' => '', - 'description' => 'Default agentnum for the signup server', - 'type' => 'text', + 'description' => 'Default agent for the signup server', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::agent; + map { $_->agentnum => $_->agent } + FS::Record::qsearch('agent', { disabled=>'' } ); + }, + 'option_sub' => sub { require FS::Record; + require FS::agent; + my $agent = FS::Record::qsearchs( + 'agent', { 'agentnum'=>shift } + ); + $agent ? $agent->agent : ''; + }, }, { 'key' => 'signup_server-default_refnum', 'section' => '', - 'description' => 'Default advertising source number for the signup server', - 'type' => 'text', + 'description' => 'Default advertising source for the signup server', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::part_referral; + map { $_->refnum => $_->referral } + FS::Record::qsearch( 'part_referral', + { 'disabled' => '' } + ); + }, + 'option_sub' => sub { require FS::Record; + require FS::part_referral; + my $part_referral = FS::Record::qsearchs( + 'part_referral', { 'refnum'=>shift } ); + $part_referral ? $part_referral->referral : ''; + }, + }, + + { + 'key' => 'signup_server-default_pkgpart', + 'section' => '', + 'description' => 'Default pakcage for the signup server', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::part_pkg; + map { $_->pkgpart => $_->pkg.' - '.$_->comment } + FS::Record::qsearch( 'part_pkg', + { 'disabled' => ''} + ); + }, + 'option_sub' => sub { require FS::Record; + require FS::part_pkg; + my $part_pkg = FS::Record::qsearchs( + 'part_pkg', { 'pkgpart'=>shift } + ); + $part_pkg + ? $part_pkg->pkg.' - '.$part_pkg->comment + : ''; + }, }, { @@ -1200,6 +1318,43 @@ httemplate/docs/config.html 'description' => 'Run billing for signup server signups immediately, and do not provision accounts which subsequently have a balance.', 'type' => 'checkbox', }, + { + 'key' => 'signup_server-classnum2', + 'section' => '', + 'description' => 'Package Class for first optional purchase', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::pkg_class; + map { $_->classnum => $_->classname } + FS::Record::qsearch('pkg_class', {} ); + }, + 'option_sub' => sub { require FS::Record; + require FS::pkg_class; + my $pkg_class = FS::Record::qsearchs( + 'pkg_class', { 'classnum'=>shift } + ); + $pkg_class ? $pkg_class->classname : ''; + }, + }, + + { + 'key' => 'signup_server-classnum3', + 'section' => '', + 'description' => 'Package Class for second optional purchase', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::pkg_class; + map { $_->classnum => $_->classname } + FS::Record::qsearch('pkg_class', {} ); + }, + 'option_sub' => sub { require FS::Record; + require FS::pkg_class; + my $pkg_class = FS::Record::qsearchs( + 'pkg_class', { 'classnum'=>shift } + ); + $pkg_class ? $pkg_class->classname : ''; + }, + }, { 'key' => 'backend-realtime', @@ -1301,6 +1456,42 @@ httemplate/docs/config.html }, { + 'key' => 'warning_email', + 'section' => '', + 'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>', + 'type' => 'textarea', + }, + + { + 'key' => 'warning_email-from', + 'section' => '', + 'description' => 'From: address header for warning email', + 'type' => 'text', + }, + + { + 'key' => 'warning_email-cc', + 'section' => '', + 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.', + 'type' => 'text', + }, + + { + 'key' => 'warning_email-subject', + 'section' => '', + 'description' => 'Subject: header for warning email', + 'type' => 'text', + }, + + { + 'key' => 'warning_email-mimetype', + 'section' => '', + 'description' => 'MIME type for warning email', + 'type' => 'select', + 'select_enum' => [ 'text/plain', 'text/html' ], + }, + + { 'key' => 'payby', 'section' => 'billing', 'description' => 'Available payment types.', @@ -1362,8 +1553,8 @@ httemplate/docs/config.html { 'key' => 'users-allow_comp', - 'section' => '', - 'description' => 'Usernames (Freeside users, created with <a href="../docs/man/bin/freeside-adduser.html">freeside-adduser</a>) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.', + 'section' => 'deprecated', + 'description' => '<b>DEPRECATED</b>, enable the <i>Complimentary customer</i> access right instead. Was: Usernames (Freeside users, created with <a href="../docs/man/bin/freeside-adduser.html">freeside-adduser</a>) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.', 'type' => 'textarea', }, @@ -1372,15 +1563,7 @@ httemplate/docs/config.html 'section' => 'billing', 'description' => 'Save CVV2 information after the initial transaction for the selected credit card types. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option for any credit card types.', 'type' => 'selectmultiple', - 'select_enum' => [ "VISA card", - "MasterCard", - "Discover card", - "American Express card", - "Diner's Club/Carte Blanche", - "enRoute", - "JCB", - "BankCard", - ], + 'select_enum' => \@card_types, }, { @@ -1455,9 +1638,9 @@ httemplate/docs/config.html { 'key' => 'global_unique-username', 'section' => 'username', - 'description' => 'Global username uniqueness control: none (usual setting - check uniqueness per exports), username (all usernames are globally unique, regardless of domain or exports), or username@domain (all username@domain pairs are globally unique, regardless of exports)', + 'description' => 'Global username uniqueness control: none (usual setting - check uniqueness per exports), username (all usernames are globally unique, regardless of domain or exports), or username@domain (all username@domain pairs are globally unique, regardless of exports). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.', 'type' => 'select', - 'select_enum' => [ 'none', 'username', 'username@domain' ], + 'select_enum' => [ 'none', 'username', 'username@domain', 'disabled' ], }, { @@ -1556,22 +1739,22 @@ httemplate/docs/config.html { 'key' => 'echeck-void', - 'section' => 'billing', - 'description' => 'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway', 'type' => 'checkbox', }, { 'key' => 'cc-void', - 'section' => 'billing', - 'description' => 'Enable local-only voiding of credit card payments in addition to refunds against the payment gateway', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of credit card payments in addition to refunds against the payment gateway', 'type' => 'checkbox', }, { 'key' => 'unvoid', - 'section' => 'billing', - 'description' => 'Enable unvoiding of voided payments', + 'section' => 'deprecated', + 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable unvoiding of voided payments', 'type' => 'checkbox', }, @@ -1605,32 +1788,30 @@ httemplate/docs/config.html { 'key' => 'svc_acct-usage_suspend', 'section' => 'billing', - 'description' => 'Suspends the package an account belongs to when svc_acct.seconds is decremented to 0 or below (accounts with an empty seconds value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.', + 'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.', 'type' => 'checkbox', }, { 'key' => 'svc_acct-usage_unsuspend', 'section' => 'billing', - 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds is incremented from 0 or below to a positive value (accounts with an empty seconds value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.', + 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.', 'type' => 'checkbox', }, { + 'key' => 'svc_acct-usage_threshold', + 'section' => 'billing', + 'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder. Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd. Defaults to 80.', + 'type' => 'text', + }, + + { 'key' => 'cust-fields', 'section' => 'UI', - 'description' => 'Which customer fields to display on reports', + 'description' => 'Which customer fields to display on reports by default', 'type' => 'select', - 'select_enum' => [ - 'Customer: Last, First</b> or</i> Company (Last, First)</b>', - 'Cust# | Customer: custnum | Last, First or Company (Last, First)', - 'Name | Company: Last, First | Company', - 'Cust# | Name | Company: custnum | Last, First | Company', - '(bill) Customer | (service) Customer: Last, First or Company (Last, First) | (same for service address if present)', - 'Cust# | (bill) Customer | (service) Customer: custnum | Last, First or Company (Last, First) | (same for service address if present)', - '(bill) Name | (bill) Company | (service) Name | (service) Company: Last, First | Company | (same for service address if present)', - 'Cust# | (bill) Name | (bill) Company | (service) Name | (service) Company: custnum | Last, First | Company | (same for service address if present)', - ], + 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ], }, { @@ -1661,6 +1842,209 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + #these should become per-user... + { + 'key' => 'vonage-username', + 'section' => '', + 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)', + 'type' => 'text', + }, + { + 'key' => 'vonage-password', + 'section' => '', + 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)', + 'type' => 'text', + }, + { + 'key' => 'vonage-fromnumber', + 'section' => '', + 'description' => 'Vonage Click2Call number (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)', + 'type' => 'text', + }, + + { + 'key' => 'echeck-nonus', + 'section' => 'billing', + 'description' => 'Disable ABA-format account checking for Electronic Check payment info', + 'type' => 'checkbox', + }, + + { + 'key' => 'voip-cust_cdr_spools', + 'section' => '', + 'description' => 'Enable the per-customer option for individual CDR spools.', + 'type' => 'checkbox', + }, + + { + 'key' => 'svc_forward-arbitrary_dst', + 'section' => '', + 'description' => "Allow forwards to point to arbitrary strings that don't necessarily look like email addresses. Only used when using forwards for weird, non-email things.", + 'type' => 'checkbox', + }, + + { + 'key' => 'tax-ship_address', + 'section' => 'billing', + 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead. Note: Tax reports can take a long time when enabled.', + 'type' => 'checkbox', + }, + + { + 'key' => 'batch-enable', + 'section' => 'billing', + 'description' => 'Enable credit card batching - leave disabled for real-time installations.', + 'type' => 'checkbox', + }, + + { + 'key' => 'batch-default_format', + 'section' => 'billing', + 'description' => 'Default format for batches.', + 'type' => 'select', + 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', + 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ] + }, + + { + 'key' => 'batch-fixed_format-CARD', + 'section' => 'billing', + 'description' => 'Fixed (unchangeable) format for credit card batches.', + 'type' => 'select', + 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' , + 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ] + }, + + { + 'key' => 'batch-fixed_format-CHEK', + 'section' => 'billing', + 'description' => 'Fixed (unchangeable) format for electronic check batches.', + 'type' => 'select', + 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ] + }, + + { + 'key' => 'batch-increment_expiration', + 'section' => 'billing', + 'description' => 'Increment expiration date years in batches until cards are current. Make sure this is acceptable to your batching provider before enabling.', + 'type' => 'checkbox' + }, + + { + 'key' => 'batchconfig-BoM', + 'section' => 'billing', + 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account', + 'type' => 'textarea', + }, + + { + 'key' => 'batchconfig-PAP', + 'section' => 'billing', + 'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account', + 'type' => 'textarea', + }, + + { + 'key' => 'batchconfig-csv-chase_canada-E-xactBatch', + 'section' => 'billing', + 'description' => 'Gateway ID for Chase Canada E-xact batching', + 'type' => 'text', + }, + + { + 'key' => 'payment_history-years', + 'section' => 'UI', + 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.', + 'type' => 'text', + }, + + { + 'key' => 'cust_main-use_comments', + 'section' => 'UI', + 'description' => 'Display free form comments on the customer edit screen. Useful as a scratch pad.', + 'type' => 'checkbox', + }, + + { + 'key' => 'cust_main-disable_notes', + 'section' => 'UI', + 'description' => 'Disable new style customer notes - timestamped and user identified customer notes. Useful in tracking who did what.', + 'type' => 'checkbox', + }, + + { + 'key' => 'cust_main_note-display_times', + 'section' => 'UI', + 'description' => 'Display full timestamps (not just dates) for customer notes.', + 'type' => 'checkbox', + }, + + { + 'key' => 'cust_main-ticket_statuses', + 'section' => 'UI', + 'description' => 'Show tickets with these statuses on the customer view page.', + 'type' => 'selectmultiple', + 'select_enum' => [qw( new open stalled resolved rejected deleted )], + }, + + { + 'key' => 'cust_main-max_tickets', + 'section' => 'UI', + 'description' => 'Maximum number of tickets to show on the customer view page.', + 'type' => 'text', + }, + + { + 'key' => 'cust_main-skeleton_tables', + 'section' => '', + 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.', + 'type' => 'textarea', + }, + + { + 'key' => 'cust_main-skeleton_custnum', + 'section' => '', + 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.', + 'type' => 'text', + }, + + { + 'key' => 'cust_main-enable_birthdate', + 'section' => 'UI', + 'descritpion' => 'Enable tracking of a birth date with each customer record', + 'type' => 'checkbox', + }, + + { + 'key' => 'support-key', + 'section' => '', + 'description' => 'A support key enables access to commercial services delivered over the network, such as the payroll module, access to the internal ticket system, priority support and optional backups.', + 'type' => 'text', + }, + + { + 'key' => 'card-types', + 'section' => 'billing', + 'description' => 'Select one or more card types to enable only those card types. If no card types are selected, all card types are available.', + 'type' => 'selectmultiple', + 'select_enum' => \@card_types, + }, + + { + 'key' => 'dashboard-toplist', + 'section' => 'UI', + 'description' => 'List of items to display on the top of the front page', + 'type' => 'textarea', + }, + + { + 'key' => 'impending_recur_template', + 'section' => 'billing', + 'description' => 'Template file for alerts about looming first time recurrant billing. See the <a href="http://search.cpan.org/~mjd/Text-Template.pm">Text::Template</a> documentation for details on the template substitition language. Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a> The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>', +# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse + 'type' => 'textarea', + }, + ); 1; diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm new file mode 100644 index 000000000..baee0bb08 --- /dev/null +++ b/FS/FS/ConfDefaults.pm @@ -0,0 +1,68 @@ +package FS::ConfDefaults; + +=head1 NAME + +FS::ConfDefaults - Freeside configuration default and available values + +=head1 SYNOPSIS + + use FS::ConfDefaults; + + @avail_cust_fields = FS::ConfDefaults->cust_fields_avail(); + +=head1 DESCRIPTION + +Just a small class to keep config default and available values + +=head1 METHODS + +=over 4 + +=item cust_fields_avail + +Returns a list, suitable for assigning to a hash, of available values and +labels for customer fields values. + +=cut + +# XXX should use msgcat for "Day phone" and "Night phone", but how? +sub cust_fields_avail { ( + + 'Cust. Status | Customer' => + 'Status | Last, First or Company (Last, First)', + 'Cust# | Cust. Status | Customer' => + 'custnum | Status | Last, First or Company (Last, First)', + + 'Cust. Status | Name | Company' => + 'Status | Last, First | Company', + 'Cust# | Cust. Status | Name | Company' => + 'custnum | Status | Last, First | Company', + + 'Cust. Status | (bill) Customer | (service) Customer' => + 'Status | Last, First or Company (Last, First) | (same for service contact if present)', + 'Cust# | Cust. Status | (bill) Customer | (service) Customer' => + 'custnum | Status | Last, First or Company (Last, First) | (same for service contact if present)', + + 'Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' => + 'Status | Last, First | Company | (same for service address if present)', + 'Cust# | Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' => + 'custnum | Status | Last, First | Company | (same for service address if present)', + + 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Invoicing email(s)' => + 'custnum | Status | Last, First | Company | (all address fields ) | Day phone | Night phone | Invoicing email(s)', + +); } + +=back + +=head1 BUGS + +Not yet. + +=head1 SEE ALSO + +L<FS::Conf> + +=cut + +1; diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm new file mode 100644 index 000000000..204069a12 --- /dev/null +++ b/FS/FS/Cron/backup.pm @@ -0,0 +1,43 @@ +package FS::Cron::backup; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +use FS::UID qw(driver_name datasrc); + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( backup_scp ); + +sub backup_scp { + my $conf = new FS::Conf; + my $dest = $conf->config('dump-scpdest'); + if ( $dest ) { + datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc; + my $database = $1; + eval "use Net::SCP qw(scp);"; + die $@ if $@; + if ( driver_name eq 'Pg' ) { + system("pg_dump $database >/var/tmp/$database.sql") + } else { + die "database dumps not yet supported for ". driver_name; + } + if ( $conf->config('dump-pgpid') ) { + eval 'use GnuPG;'; + die $@ if $@; + my $gpg = new GnuPG; + $gpg->encrypt( plaintext => "/var/tmp/$database.sql", + output => "/var/tmp/$database.gpg", + recipient => $conf->config('dump-pgpid'), + ); + chmod 0600, '/var/tmp/$database.gpg'; + scp("/var/tmp/$database.gpg", $dest); + unlink "/var/tmp/$database.gpg" or die $!; + } else { + chmod 0600, '/var/tmp/$database.sql'; + scp("/var/tmp/$database.sql", $dest); + } + unlink "/var/tmp/$database.sql" or die $!; + } +} + +1; diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm new file mode 100644 index 000000000..fb9e5499d --- /dev/null +++ b/FS/FS/Cron/bill.pm @@ -0,0 +1,118 @@ +package FS::Cron::bill; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +use Date::Parse; +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; + +@ISA = qw( Exporter ); +@EXPORT_OK = qw ( bill ); + +sub bill { + + my %opt = @_; + + $FS::cust_main::DEBUG = 1 if $opt{'v'}; + + my %search = (); + $search{'payby'} = $opt{'p'} if $opt{'p'}; + $search{'agentnum'} = $opt{'a'} if $opt{'a'}; + + #we're at now now (and later). + my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T; + $time += $opt{'y'} * 86400 if $opt{'y'}; + + # select * from cust_main where + my $where_pkg = <<"END"; + 0 < ( select count(*) from cust_pkg + where cust_main.custnum = cust_pkg.custnum + and ( cancel is null or cancel = 0 ) + and ( setup is null or setup = 0 + or bill is null or bill <= $time + or ( expire is not null and expire <= $^T ) + ) + ) +END + + # or + my $where_bill_event = <<"END"; + 0 < ( select count(*) from cust_bill + where cust_main.custnum = cust_bill.custnum + and 0 < charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) + ,0 + ) + and 0 < ( select count(*) from part_bill_event + where payby = cust_main.payby + and ( disabled is null or disabled = '' ) + and seconds <= $time - cust_bill._date + and 0 = ( select count(*) from cust_bill_event + where cust_bill.invnum = cust_bill_event.invnum + and part_bill_event.eventpart = cust_bill_event.eventpart + and status = 'done' + ) + + ) + ) +END + + my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "( $where_pkg OR $where_bill_event )"; + + my @cust_main; + if ( @ARGV ) { + @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV + } else { + @cust_main = qsearch('cust_main', \%search, '', $extra_sql ); + } + ; + + my($cust_main,%saw); + foreach $cust_main ( @cust_main ) { + + # $^T not $time because -d is for pre-printing invoices + foreach my $cust_pkg ( + grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs + ) { + my $error = $cust_pkg->cancel; + warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ". + $cust_main->custnum. ": $error" + if $error; + } + # $^T not $time because -d is for pre-printing invoices + foreach my $cust_pkg ( + grep { $_->part_pkg->is_prepaid + && $_->bill && $_->bill < $^T && ! $_->susp + } + $cust_main->ncancelled_pkgs + ) { + my $error = $cust_pkg->suspend; + warn "Error suspending package ". $cust_pkg->pkgnum. + " for custnum ". $cust_main->custnum. + ": $error" + if $error; + } + + my $error = $cust_main->bill( 'time' => $time, + 'resetup' => $opt{'s'}, + ); + warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error; + + $cust_main->apply_payments_and_credits; + + $error = $cust_main->collect( 'invoice_time' => $time, + 'freq' => $opt{'freq'}, + ); + warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error; + + } + +} diff --git a/FS/FS/Cron/notify.pm b/FS/FS/Cron/notify.pm new file mode 100644 index 000000000..371065094 --- /dev/null +++ b/FS/FS/Cron/notify.pm @@ -0,0 +1,136 @@ +package FS::Cron::notify; + +use strict; +use vars qw( @ISA @EXPORT_OK $DEBUG ); +use Exporter; +use FS::UID qw( dbh ); +use FS::Record qw(qsearch); +use FS::cust_main; +use FS::cust_pkg; + +@ISA = qw( Exporter ); +@EXPORT_OK = qw ( notify_flat_delay ); +$DEBUG = 0; + +sub notify_flat_delay { + + my %opt = @_; + + my $oldAutoCommit = $FS::UID::AutoCommit; + $DEBUG = 1 if $opt{'v'}; + + #we're at now now (and later). + my($time) = $^T; + + # select * from cust_pkg where + my $where_pkg = <<"END"; + where ( cancel is null or cancel = 0 ) + and ( bill > 0 ) + and + 0 < ( select count(*) from part_pkg + where cust_pkg.pkgpart = part_pkg.pkgpart + and part_pkg.plan = 'flat_delayed' + and 0 < ( select count (*) from part_pkg_option + where part_pkg.pkgpart = part_pkg_option.pkgpart + and part_pkg_option.optionname = 'recur_notify' + and part_pkg_option.optionvalue > 0 + and 0 <= $time + + cast(part_pkg_option.optionvalue as integer) + * 86400 + - cust_pkg.bill + and ( cust_pkg.expire is null + or cust_pkg.expire > $time + + cast(part_pkg_option.optionvalue as integer) + * 86400 + ) + ) + ) + and + 0 = ( select count(*) from cust_pkg_option + where cust_pkg.pkgnum = cust_pkg_option.pkgnum + and cust_pkg_option.optionname = 'impending_recur_notification_sent' + and cust_pkg_option.optionvalue = 1 + ) +END + + if ($opt{a}) { + $where_pkg .= <<END; + and 0 < ( select count(*) from cust_main + where cust_pkg.custnum = cust_main.custnum + and cust_main.agentnum = $opt{a} + ) +END + } + + my @cust_pkg; + if ( @ARGV ) { + $where_pkg .= "and ( " . join( "OR ", map { "custnum = $_" } @ARGV) . " )"; + } + + my $orderby = "order by custnum, bill"; + + my $extra_sql = "$where_pkg $orderby"; + + @cust_pkg = qsearch('cust_pkg', {}, '', $extra_sql ); + + my @packages = (); + my @recurdates = (); + my @cust_pkgs = (); + while ( scalar(@cust_pkg) ) { + my $cust_main = $cust_pkg[0]->cust_main; + my $custnum = $cust_pkg[0]->custnum; + warn "working on $custnum" if $DEBUG; + while (scalar(@cust_pkg)){ + last if ($cust_pkg[0]->custnum != $custnum); + warn "storing information on " . $cust_pkg[0]->pkgnum if $DEBUG; + push @packages, $cust_pkg[0]->part_pkg->pkg; + push @recurdates, $cust_pkg[0]->bill; + push @cust_pkgs, $cust_pkg[0]; + shift @cust_pkg; + } + my $error = + $cust_main->notify( 'impending_recur_template', + 'extra_fields' => { 'packages' => \@packages, + 'recurdates' => \@recurdates, + 'package' => $packages[0], + 'recurdate' => $recurdates[0], + }, + ); + warn "Error notifying, custnum ". $cust_main->custnum. ": $error" if $error; + + unless ($error) { + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + for (@cust_pkgs) { + my %options = ($_->options, 'impending_recur_notification_sent' => 1 ); + $error = $_->replace( $_, options => \%options ); + if ($error){ + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + die "Error updating package options for customer". $cust_main->custnum. + ": $error" if $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + } + + @packages = (); + @recurdates = (); + @cust_pkgs = (); + + } + + dbh->commit or die dbh->errstr if $oldAutoCommit; + +} + +1; diff --git a/FS/FS/Cron/vacuum.pm b/FS/FS/Cron/vacuum.pm new file mode 100644 index 000000000..075572d50 --- /dev/null +++ b/FS/FS/Cron/vacuum.pm @@ -0,0 +1,23 @@ +package FS::Cron::vacuum; + +use vars qw( @ISA @EXPORT_OK); +use Exporter; +use FS::UID qw(driver_name dbh); +use FS::Schema qw(dbdef); + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( vacuum ); + +sub vacuum { + + if ( driver_name eq 'Pg' ) { + dbh->{AutoCommit} = 1; #so we can vacuum + foreach my $table ( dbdef->tables ) { + my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr; + $sth->execute or die $sth->errstr; + } + } + +} + +1; diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm new file mode 100644 index 000000000..bcd337d2c --- /dev/null +++ b/FS/FS/CurrentUser.pm @@ -0,0 +1,67 @@ +package FS::CurrentUser; + +use vars qw($CurrentUser $upgrade_hack); + +#not at compile-time, circular dependancey causes trouble +#use FS::Record qw(qsearchs); +#use FS::access_user; + +$upgrade_hack = 0; + +=head1 NAME + +FS::CurrentUser - Package representing the current user + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=cut + +sub load_user { + my( $class, $user ) = @_; #, $pass + + if ( $upgrade_hack ) { + return $CurrentUser = new FS::CurrentUser::BootstrapUser; + } + + #return "" if $user =~ /^fs_(queue|selfservice)$/; + + #not the best thing in the world... + eval "use FS::Record qw(qsearchs);"; + die $@ if $@; + eval "use FS::access_user;"; + die $@ if $@; + + $CurrentUser = qsearchs('access_user', { + 'username' => $user, + #'_password' => + 'disabled' => '', + } ); + + die "unknown user: $user" unless $CurrentUser; # or bad password + + $CurrentUser; +} + +=head1 BUGS + +Creepy crawlies + +=head1 SEE ALSO + +=cut + +package FS::CurrentUser::BootstrapUser; + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); +} + +sub AUTOLOAD { 1 }; + +1; + diff --git a/FS/FS/Daemon.pm b/FS/FS/Daemon.pm index 3e64f79e9..7e0d45c20 100644 --- a/FS/FS/Daemon.pm +++ b/FS/FS/Daemon.pm @@ -5,6 +5,7 @@ use vars qw( $pid_dir $me $pid_file $sigint $sigterm $logfile ); use Exporter; use Fcntl qw(:flock); use POSIX qw(setsid); +use IO::File; use Date::Format; #this is a simple refactoring of the stuff from freeside-queued, just to diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index 2e383d549..a535ecebf 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -7,7 +7,10 @@ use Carp; use Data::Dumper; @ISA = qw( Exporter ); -@EXPORT_OK = qw( send_email send_fax ); +@EXPORT_OK = qw( send_email send_fax + states_hash counties state_label + card_types + ); $DEBUG = 0; @@ -185,6 +188,80 @@ sub send_email { } +#this kludges a "mysmtpsend" method into Mail::Internet for send_email above +package Mail::Internet; + +use Mail::Address; +use Net::SMTP; + +sub Mail::Internet::mysmtpsend { + my $src = shift; + my %opt = @_; + my $host = $opt{Host}; + my $envelope = $opt{MailFrom}; + my $noquit = 0; + my $smtp; + my @hello = defined $opt{Hello} ? (Hello => $opt{Hello}) : (); + + push(@hello, 'Port', $opt{'Port'}) + if exists $opt{'Port'}; + + push(@hello, 'Debug', $opt{'Debug'}) + if exists $opt{'Debug'}; + + if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP')) { + $smtp = $host; + $noquit = 1; + } + else { + #local $SIG{__DIE__}; + #$smtp = eval { Net::SMTP->new($host, @hello) }; + $smtp = new Net::SMTP $host, @hello; + } + + unless ( defined($smtp) ) { + my $err = $!; + $err =~ s/Invalid argument/Unknown host/; + return "can't connect to $host: $err" + } + + my $hdr = $src->head->dup; + + _prephdr($hdr); + + # Who is it to + + my @rcpt = map { ref($_) ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'}; + @rcpt = map { $hdr->get($_) } qw(To Cc Bcc) + unless @rcpt; + my @addr = map($_->address, Mail::Address->parse(@rcpt)); + + return 'No valid destination addresses found!' + unless(@addr); + + $hdr->delete('Bcc'); # Remove blind Cc's + + # Send it + + #warn "Headers: \n" . join('',@{$hdr->header}); + #warn "Body: \n" . join('',@{$src->body}); + + my $ok = $smtp->mail( $envelope ) && + $smtp->to(@addr) && + $smtp->data(join("", @{$hdr->header},"\n",@{$src->body})); + + if ( $ok ) { + $smtp->quit + unless $noquit; + return ''; + } else { + return $smtp->code. ' '. $smtp->message; + } + +} +package FS::Misc; +#eokludge + =item send_fax OPTION => VALUE ... Options: @@ -268,77 +345,128 @@ sub send_fax { } -package Mail::Internet; +=item states_hash COUNTRY -use Mail::Address; -use Net::SMTP; +Returns a list of key/value pairs containing state (or other sub-country +division) abbriviations and names. -sub Mail::Internet::mysmtpsend { - my $src = shift; - my %opt = @_; - my $host = $opt{Host}; - my $envelope = $opt{MailFrom}; - my $noquit = 0; - my $smtp; - my @hello = defined $opt{Hello} ? (Hello => $opt{Hello}) : (); +=cut - push(@hello, 'Port', $opt{'Port'}) - if exists $opt{'Port'}; +use FS::Record qw(qsearch); +use Locale::SubCountry; + +sub states_hash { + my($country) = @_; + + my @states = +# sort + map { s/[\n\r]//g; $_; } + map { $_->state; } + qsearch({ + 'select' => 'state', + 'table' => 'cust_main_county', + 'hashref' => { 'country' => $country }, + 'extra_sql' => 'GROUP BY state', + }); + + #it could throw a fatal "Invalid country code" error (for example "AX") + my $subcountry = eval { new Locale::SubCountry($country) } + or return ( '', '(n/a)' ); + + #"i see your schwartz is as big as mine!" + map { ( $_->[0] => $_->[1] ) } + sort { $a->[1] cmp $b->[1] } + map { [ $_ => state_label($_, $subcountry) ] } + @states; +} - push(@hello, 'Debug', $opt{'Debug'}) - if exists $opt{'Debug'}; +=item counties STATE COUNTRY - if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP')) { - $smtp = $host; - $noquit = 1; - } - else { - #local $SIG{__DIE__}; - #$smtp = eval { Net::SMTP->new($host, @hello) }; - $smtp = new Net::SMTP $host, @hello; - } +Returns a list of counties for this state and country. - unless ( defined($smtp) ) { - my $err = $!; - $err =~ s/Invalid argument/Unknown host/; - return "can't connect to $host: $err" - } +=cut - my $hdr = $src->head->dup; +sub counties { + my( $state, $country ) = @_; + + sort map { s/[\n\r]//g; $_; } + map { $_->county } + qsearch({ + 'select' => 'DISTINCT county', + 'table' => 'cust_main_county', + 'hashref' => { 'state' => $state, + 'country' => $country, + }, + }); +} - _prephdr($hdr); +=item state_label STATE COUNTRY_OR_LOCALE_SUBCOUNRY_OBJECT - # Who is it to +=cut - my @rcpt = map { ref($_) ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'}; - @rcpt = map { $hdr->get($_) } qw(To Cc Bcc) - unless @rcpt; - my @addr = map($_->address, Mail::Address->parse(@rcpt)); +sub state_label { + my( $state, $country ) = @_; - return 'No valid destination addresses found!' - unless(@addr); + unless ( ref($country) ) { + $country = eval { new Locale::SubCountry($country) } + or return'(n/a)'; - $hdr->delete('Bcc'); # Remove blind Cc's + } - # Send it + # US kludge to avoid changing existing behaviour + # also we actually *use* the abbriviations... + my $full_name = $country->country_code eq 'US' + ? '' + : $country->full_name($state); - #warn "Headers: \n" . join('',@{$hdr->header}); - #warn "Body: \n" . join('',@{$src->body}); + $full_name = '' if $full_name eq 'unknown'; + $full_name =~ s/\(see also.*\)\s*$//; + $full_name .= " ($state)" if $full_name; - my $ok = $smtp->mail( $envelope ) && - $smtp->to(@addr) && - $smtp->data(join("", @{$hdr->header},"\n",@{$src->body})); + $full_name || $state || '(n/a)'; - if ( $ok ) { - $smtp->quit - unless $noquit; - return ''; - } else { - return $smtp->code. ' '. $smtp->message; - } +} +=item card_types + +Returns a hash reference of the accepted credit card types. Keys are shorter +identifiers and values are the longer strings used by the system (see +L<Business::CreditCard>). + +=cut + +#$conf from above + +sub card_types { + my $conf = new FS::Conf; + + my %card_types = ( + #displayname #value (Business::CreditCard) + "VISA" => "VISA card", + "MasterCard" => "MasterCard", + "Discover" => "Discover card", + "American Express" => "American Express card", + "Diner's Club/Carte Blanche" => "Diner's Club/Carte Blanche", + "enRoute" => "enRoute", + "JCB" => "JCB", + "BankCard" => "BankCard", + "Switch" => "Switch", + "Solo" => "Solo", + ); + my @conf_card_types = grep { ! /^\s*$/ } $conf->config('card-types'); + if ( @conf_card_types ) { + #perhaps the hash is backwards for this, but this way works better for + #usage in selfservice + %card_types = map { $_ => $card_types{$_} } + grep { + my $d = $_; + grep { $card_types{$d} eq $_ } @conf_card_types + } + keys %card_types; + } + + \%card_types; } -package FS::Misc; =back diff --git a/FS/FS/Pony.pm b/FS/FS/Pony.pm new file mode 100644 index 000000000..c37dd7855 --- /dev/null +++ b/FS/FS/Pony.pm @@ -0,0 +1,23 @@ +package FS::Pony; + +=head1 NAME + +FS::Pony - A pony + +=head1 SYNOPSYS + +use FS::Pony; # <-- yours! + +=head1 DESCRIPTION + +We told you it came with a pony. + +=head1 BUGS + +=head1 SEE ALSO + +http://420.am/~ivan/nopony.jpg + +=cut + +1; diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 887c8dcd4..4efaeffdc 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2,7 +2,8 @@ package FS::Record; use strict; use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG - $me %virtual_fields_cache $nowarn_identical ); + $conf $me + %virtual_fields_cache $nowarn_identical ); use Exporter; use Carp qw(carp cluck croak confess); use File::CounterFile; @@ -10,6 +11,7 @@ use Locale::Country; use DBI qw(:sql_types); use DBIx::DBSchema 0.25; use FS::UID qw(dbh getotaker datasrc driver_name); +use FS::CurrentUser; use FS::Schema qw(dbdef); use FS::SearchCache; use FS::Msgcat qw(gettext); @@ -29,7 +31,6 @@ $me = '[FS::Record]'; $nowarn_identical = 0; -my $conf; my $rsa_module; my $rsa_loaded; my $rsa_encrypt; @@ -37,9 +38,10 @@ my $rsa_decrypt; FS::UID->install_callback( sub { $conf = new FS::Conf; - $File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc; + $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc; } ); + =head1 NAME FS::Record - Database record objects @@ -82,8 +84,11 @@ FS::Record - Database record objects $value = $record->unique('column'); $error = $record->ut_float('column'); + $error = $record->ut_floatn('column'); $error = $record->ut_number('column'); $error = $record->ut_numbern('column'); + $error = $record->ut_snumber('column'); + $error = $record->ut_snumbern('column'); $error = $record->ut_money('column'); $error = $record->ut_text('column'); $error = $record->ut_textn('column'); @@ -250,7 +255,7 @@ sub qsearch { my $table = $cache ? $cache->table : $stable; my $dbdef_table = dbdef->table($table) or die "No schema for table $table found - ". - "do you need to create it or run dbdef-create?"; + "do you need to run freeside-upgrade?"; my $pkey = $dbdef_table->primary_key; my @real_fields = grep exists($record->{$_}), real_fields($table); @@ -285,7 +290,7 @@ sub qsearch { if ( $op eq '=' ) { if ( driver_name eq 'Pg' ) { my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $column IS NULL )-; } else { qq-( $column IS NULL OR $column = '' )-; @@ -296,7 +301,7 @@ sub qsearch { } elsif ( $op eq '!=' ) { if ( driver_name eq 'Pg' ) { my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $column IS NOT NULL )-; } else { qq-( $column IS NOT NULL AND $column != '' )-; @@ -365,7 +370,7 @@ sub qsearch { grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields ) { if ( $record->{$field} =~ /^\d+(\.\d+)?$/ - && dbdef->table($table)->column($field)->type =~ /(int|serial)/i + && dbdef->table($table)->column($field)->type =~ /(int|(big)?serial)/i ) { $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } ); } else { @@ -389,7 +394,7 @@ sub qsearch { my %result; tie %result, "Tie::IxHash"; my @stuff = @{ $sth->fetchall_arrayref( {} ) }; - if($pkey) { + if ( $pkey && scalar(@stuff) && $stuff[0]->{$pkey} ) { %result = map { $_->{$pkey}, $_ } @stuff; } else { @result{@stuff} = @stuff; @@ -441,7 +446,11 @@ sub qsearch { } # Check for encrypted fields and decrypt them. - if ($conf->exists('encryption') && eval 'defined(@FS::'. $table . '::encrypted_fields)') { + ## only in the local copy, not the cached object + if ( $conf && $conf->exists('encryption') # $conf doesn't exist when doing + # the initial search for + # access_user + && eval 'defined(@FS::'. $table . '::encrypted_fields)') { foreach my $record (@return) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { # Set it directly... This may cause a problem in the future... @@ -554,6 +563,17 @@ sub dbdef_table { dbdef->table($table); } +=item primary_key + +Returns the primary key for the table. + +=cut + +sub primary_key { + my $self = shift; + my $pkey = $self->dbdef_table->primary_key; +} + =item get, getfield COLUMN Returns the value of the column/field/key COLUMN. @@ -668,6 +688,24 @@ sub modified { $self->{'modified'}; } +=item select_for_update + +Selects this record with the SQL "FOR UPDATE" command. This can be useful as +a mutex. + +=cut + +sub select_for_update { + my $self = shift; + my $primary_key = $self->primary_key; + qsearchs( { + 'select' => '*', + 'table' => $self->table, + 'hashref' => { $primary_key => $self->$primary_key() }, + 'extra_sql' => 'FOR UPDATE', + } ); +} + =item insert Inserts this record to the database. If there is an error, returns the error, @@ -679,6 +717,8 @@ sub insert { my $self = shift; my $saved = {}; + warn "$self -> insert" if $DEBUG; + my $error = $self->check; return $error if $error; @@ -695,7 +735,7 @@ sub insert { my $col = $self->dbdef_table->column($primary_key); $db_seq = - uc($col->type) eq 'SERIAL' + uc($col->type) =~ /^(BIG)?SERIAL\d?/ || ( driver_name eq 'Pg' && defined($col->default) && $col->default =~ /^nextval\(/i @@ -711,28 +751,34 @@ sub insert { # Encrypt before the database - if ($conf->exists('encryption') && defined(eval '@FS::'. $table . 'encrypted_fields')) { + if ($conf->exists('encryption') && defined(eval '@FS::'. $table . '::encrypted_fields')) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { $self->{'saved'} = $self->getfield($field); - $self->setfield($field, $self->enrypt($self->getfield($field))); + $self->setfield($field, $self->encrypt($self->getfield($field))); } } #false laziness w/delete my @real_fields = - grep defined($self->getfield($_)) && $self->getfield($_) ne "", + grep { defined($self->getfield($_)) && $self->getfield($_) ne "" } real_fields($table) ; my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields; #eslaf - my $statement = "INSERT INTO $table ( ". - join( ', ', @real_fields ). - ") VALUES (". - join( ', ', @values ). - ")" - ; + my $statement = "INSERT INTO $table "; + if ( @real_fields ) { + $statement .= + "( ". + join( ', ', @real_fields ). + ") VALUES (". + join( ', ', @values ). + ")" + ; + } else { + $statement .= 'DEFAULT VALUES'; + } warn "[debug]$me $statement\n" if $DEBUG > 1; my $sth = dbh->prepare($statement) or return dbh->errstr; @@ -757,7 +803,7 @@ sub insert { #my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?"; my $default = $self->dbdef_table->column($primary_key)->default; - unless ( $default =~ /^nextval\('"?([\w\.]+)"?'/i ) { + unless ( $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i ) { dbh->rollback if $FS::UID::AutoCommit; return "can't parse $table.$primary_key default value". " for sequence name: $default"; @@ -769,8 +815,7 @@ sub insert { dbh->rollback if $FS::UID::AutoCommit; return dbh->errstr; }; - #$i_sth->execute($oid) or do { - $i_sth->execute() or do { + $i_sth->execute() or do { #$i_sth->execute($oid) dbh->rollback if $FS::UID::AutoCommit; return $i_sth->errstr; }; @@ -959,24 +1004,17 @@ returns the error, otherwise returns false. =cut sub replace { - my $new = shift; - my $old = shift; - - if (!defined($old)) { - warn "[debug]$me replace called with no arguments; autoloading old record\n" - if $DEBUG; - my $primary_key = $new->dbdef_table->primary_key; - if ( $primary_key ) { - $old = qsearchs($new->table, { $primary_key => $new->$primary_key() } ) - or croak "can't find ". $new->table. ".$primary_key ". - $new->$primary_key(); - } else { - croak $new->table. " has no primary key; pass old record as argument"; - } - } + my ($new, $old) = (shift, shift); + + $old = $new->replace_old unless defined($old); warn "[debug]$me $new ->replace $old\n" if $DEBUG; + if ( $new->can('replace_check') ) { + my $error = $new->replace_check($old); + return $error if $error; + } + return "Records not in same table!" unless $new->table eq $old->table; my $primary_key = $old->dbdef_table->primary_key; @@ -990,8 +1028,9 @@ sub replace { return $error if $error; # Encrypt for replace + my $conf = new FS::Conf; my $saved = {}; - if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . 'encrypted_fields')) { + if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . '::encrypted_fields')) { foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') { $saved->{$field} = $new->getfield($field); $new->setfield($field, $new->encrypt($new->getfield($field))); @@ -1021,7 +1060,7 @@ sub replace { #false laziness w/qsearch if ( driver_name eq 'Pg' ) { my $type = $old->dbdef_table->column($_)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $_ IS NULL )-; } else { qq-( $_ IS NULL OR $_ = '' )-; @@ -1137,6 +1176,22 @@ sub replace { } +sub replace_old { + my( $self ) = shift; + warn "[$me] replace called with no arguments; autoloading old record\n" + if $DEBUG; + + my $primary_key = $self->dbdef_table->primary_key; + if ( $primary_key ) { + $self->by_key( $self->$primary_key() ) #this is what's returned + or croak "can't find ". $self->table. ".$primary_key ". + $self->$primary_key(); + } else { + croak $self->table. " has no primary key; pass old record as argument"; + } + +} + =item rep Depriciated (use replace instead). @@ -1187,9 +1242,15 @@ sub _h_statement { $time ||= time; my @fields = - grep defined($self->getfield($_)) && $self->getfield($_) ne "", + grep { defined($self->getfield($_)) && $self->getfield($_) ne "" } real_fields($self->table); ; + + # If we're encrypting then don't ever store the payinfo or CVV2 in the history.... + # You can see if it changed by the paymask... + if ($conf->exists('encryption') ) { + @fields = grep $_ ne 'payinfo' && $_ ne 'cvv2', @fields; + } my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields; "INSERT INTO h_". $self->table. " ( ". @@ -1259,11 +1320,28 @@ sub ut_float { $self->setfield($field,$1); ''; } +=item ut_floatn COLUMN + +Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10. May be +null. If there is an error, returns the error, otherwise returns false. + +=cut + +#false laziness w/ut_ipn +sub ut_floatn { + my( $self, $field ) = @_; + if ( $self->getfield($field) =~ /^()$/ ) { + $self->setfield($field,''); + ''; + } else { + $self->ut_float($field); + } +} =item ut_snumber COLUMN -Check/untaint signed numeric data (whole numbers). May not be null. If there -is an error, returns the error, otherwise returns false. +Check/untaint signed numeric data (whole numbers). If there is an error, +returns the error, otherwise returns false. =cut @@ -1275,6 +1353,25 @@ sub ut_snumber { ''; } +=item ut_snumbern COLUMN + +Check/untaint signed numeric data (whole numbers). If there is an error, +returns the error, otherwise returns false. + +=cut + +sub ut_snumbern { + my($self, $field) = @_; + $self->getfield($field) =~ /^(-?)\s*(\d*)$/ + or return "Illegal (numeric) $field: ". $self->getfield($field); + if ($1) { + return "Illegal (numeric) $field: ". $self->getfield($field) + unless $2; + } + $self->setfield($field, "$1$2"); + ''; +} + =item ut_number COLUMN Check/untaint simple numeric data (whole numbers). May not be null. If there @@ -1325,7 +1422,7 @@ sub ut_money { =item ut_text COLUMN Check/untaint text. Alphanumerics, spaces, and the following punctuation -symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = +symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] May not be null. If there is an error, returns the error, otherwise returns false. @@ -1336,9 +1433,10 @@ sub ut_text { #warn "msgcat ". \&msgcat. "\n"; #warn "notexist ". \¬exist. "\n"; #warn "AUTOLOAD ". \&AUTOLOAD. "\n"; - $self->getfield($field) =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]+)$/ - or return gettext('illegal_or_empty_text'). " $field: ". - $self->getfield($field); + $self->getfield($field) + =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/ + or return gettext('illegal_or_empty_text'). " $field: ". + $self->getfield($field); $self->setfield($field,$1); ''; } @@ -1353,8 +1451,9 @@ May be null. If there is an error, returns the error, otherwise returns false. sub ut_textn { my($self,$field)=@_; - $self->getfield($field) =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ - or return gettext('illegal_text'). " $field: ". $self->getfield($field); + $self->getfield($field) + =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ + or return gettext('illegal_text'). " $field: ". $self->getfield($field); $self->setfield($field,$1); ''; } @@ -1420,6 +1519,33 @@ sub ut_phonen { ''; } +=item ut_hex COLUMN + +Check/untaint hexadecimal values. + +=cut + +sub ut_hex { + my($self, $field) = @_; + $self->getfield($field) =~ /^([\da-fA-F]+)$/ + or return "Illegal (hex) $field: ". $self->getfield($field); + $self->setfield($field, uc($1)); + ''; +} + +=item ut_hexn COLUMN + +Check/untaint hexadecimal values. May be null. + +=cut + +sub ut_hexn { + my($self, $field) = @_; + $self->getfield($field) =~ /^([\da-fA-F]*)$/ + or return "Illegal (hex) $field: ". $self->getfield($field); + $self->setfield($field, uc($1)); + ''; +} =item ut_ip COLUMN Check/untaint ip addresses. IPv4 only for now. @@ -1489,16 +1615,27 @@ Check/untaint zip codes. =cut -my @zip_reqd_countries = qw( CA ); #US implicit... +my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit... sub ut_zip { my( $self, $field, $country ) = @_; + if ( $country eq 'US' ) { - $self->getfield($field) =~ /\s*(\d{5}(\-\d{4})?)\s*$/ + + $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/ + or return gettext('illegal_zip'). " $field for country $country: ". + $self->getfield($field); + $self->setfield($field, $1); + + } elsif ( $country eq 'CA' ) { + + $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i or return gettext('illegal_zip'). " $field for country $country: ". $self->getfield($field); - $self->setfield($field,$1); + $self->setfield($field, "$1 $2"); + } else { + if ( $self->getfield($field) =~ /^\s*$/ && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries ) ) @@ -1509,7 +1646,9 @@ sub ut_zip { or return gettext('illegal_zip'). " $field: ". $self->getfield($field); $self->setfield($field,$1); } + } + ''; } @@ -1593,6 +1732,36 @@ sub ut_foreign_keyn { : ''; } +=item ut_agentnum_acl + +Checks this column as an agentnum, taking into account the current users's +ACLs. + +=cut + +sub ut_agentnum_acl { + my( $self, $field, $null_acl ) = @_; + + my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum'); + return "Illegal agentnum: $error" if $error; + + my $curuser = $FS::CurrentUser::CurrentUser; + + if ( $self->$field() ) { + + return "Access deined" + unless $curuser->agentnum($self->$field()); + + } else { + + return "Access denied" + unless $curuser->access_right($null_acl); + + } + + ''; + +} =item virtual_fields [ TABLE ] @@ -1615,7 +1784,8 @@ sub virtual_fields { "WHERE dbtable = '$table'"; my $dbh = dbh; my $result = $dbh->selectcol_arrayref($query); - confess $dbh->errstr if $dbh->err; + confess "Error executing virtual fields query: $query: ". $dbh->errstr + if $dbh->err; $virtual_fields_cache{$table} = $result; } @@ -1699,14 +1869,12 @@ sub _quote { ( $nullable ? ' NULL' : ' NOT NULL' ). ")\n" if $DEBUG > 2; - if ( $value eq '' && $column_type =~ /^int/ ) { - if ( $nullable ) { - 'NULL'; - } else { - cluck "WARNING: Attempting to set non-null integer $table.$column null; ". - "using 0 instead"; - 0; - } + if ( $value eq '' && $nullable ) { + 'NULL' + } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) { + cluck "WARNING: Attempting to set non-null integer $table.$column null; ". + "using 0 instead"; + 0; } elsif ( $value =~ /^\d+(\.\d+)?$/ && ! $column_type =~ /(char|binary|text)$/i ) { $value; @@ -1764,10 +1932,22 @@ sub _dump { } (fields($self->table)) ); } +=item encrypt($value) + +Encrypts the credit card using a combination of PK to encrypt and uuencode to armour. + +Returns the encrypted string. + +You should generally not have to worry about calling this, as the system handles this for you. + +=cut + + sub encrypt { my ($self, $value) = @_; my $encrypted; + my $conf = new FS::Conf; if ($conf->exists('encryption')) { if ($self->is_encrypted($value)) { # Return the original value if it isn't plaintext. @@ -1787,25 +1967,42 @@ sub encrypt { return $encrypted; } +=item is_encrypted($value) + +Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status. + +=cut + + sub is_encrypted { my ($self, $value) = @_; # Possible Bug - Some work may be required here.... - if (length($value) > 80) { + if ($value =~ /^M/ && length($value) > 80) { return 1; } else { return 0; } } +=item decrypt($value) + +Uses the private key to decrypt the string. Returns the decryoted string or undef on failure. + +You should generally not have to worry about calling this, as the system handles this for you. + +=cut + sub decrypt { my ($self,$value) = @_; my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted. + my $conf = new FS::Conf; if ($conf->exists('encryption') && $self->is_encrypted($value)) { $self->loadRSA; if (ref($rsa_decrypt) =~ /::RSA/) { my $encrypted = unpack ("u*", $value); - $decrypted = unpack("Z*", $rsa_decrypt->decrypt($encrypted)); + $decrypted = unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)}); + if ($@) {warn "Decryption Failed"}; } } return $decrypted; @@ -1816,6 +2013,7 @@ sub loadRSA { #Initialize the Module $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default + my $conf = new FS::Conf; if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') { $rsa_module = $conf->config('encryptionmodule'); } diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 89d44f92f..145f2a85c 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -5,6 +5,7 @@ use vars qw( @ISA $expenses_kludge ); use Time::Local; use FS::UID qw( dbh ); use FS::Report::Table; +use FS::CurrentUser; @ISA = qw( FS::Report::Table ); @@ -24,6 +25,11 @@ FS::Report::Table::Monthly - Tables of report data, indexed monthly 'start_year' => 2000, 'end_month' => 4, 'end_year' => 2020, + #opt + 'agentnum' => 54 + 'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ... + 'remove_empty' => 1, #collapse empty rows, default 0 + 'item_labels' => [ ], #useful with remove_empty ); my $data = $report->data; @@ -41,10 +47,14 @@ Returns a hashref of data (!! describe) sub data { my $self = shift; + #use Data::Dumper; + #warn Dumper($self); + my $smonth = $self->{'start_month'}; my $syear = $self->{'start_year'}; my $emonth = $self->{'end_month'}; my $eyear = $self->{'end_year'}; + my $agentnum = $self->{'agentnum'}; my %data; @@ -58,64 +68,122 @@ sub data { my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear); push @{$data{eperiod}}, $eperiod; + my $col = 0; + my @row = (); foreach my $item ( @{$self->{'items'}} ) { - push @{$data{$item}}, $self->$item($speriod, $eperiod); + my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: (); + my $value = $self->$item($speriod, $eperiod, $agentnum, @param); + #push @{$data{$item}}, $value; + push @{$data{data}->[$col++]}, $value; + } + + } + + #these need to get generalized, sheesh + $data{'items'} = $self->{'items'}; + $data{'item_labels'} = $self->{'item_labels'} || $self->{'items'}; + $data{'colors'} = $self->{'colors'}; + $data{'links'} = $self->{'links'} || []; + + #use Data::Dumper; + #warn Dumper(\%data); + + if ( $self->{'remove_empty'} ) { + + #warn "removing empty rows\n"; + + my $col = 0; + #these need to get generalized, sheesh + my @newitems = (); + my @newlabels = (); + my @newdata = (); + my @newcolors = (); + my @newlinks = (); + foreach my $item ( @{$self->{'items'}} ) { + + if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) { + push @newitems, $data{'items'}->[$col]; + push @newlabels, $data{'item_labels'}->[$col]; + push @newdata, $data{'data'}->[$col]; + push @newcolors, $data{'colors'}->[$col]; + push @newlinks, $data{'links'}->[$col]; + } + + $col++; } + $data{'items'} = \@newitems; + $data{'item_labels'} = \@newlabels; + $data{'data'} = \@newdata; + $data{'colors'} = \@newcolors; + $data{'links'} = \@newlinks; + } + #use Data::Dumper; + #warn Dumper(\%data); + \%data; } sub invoiced { #invoiced - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $self->scalar_sql(" - SELECT SUM(charged) FROM cust_bill - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(charged) + FROM cust_bill + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); + } sub netsales { #net sales - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; my $credited = $self->scalar_sql(" SELECT SUM(cust_credit_bill.amount) - FROM cust_credit_bill, cust_bill - WHERE cust_bill.invnum = cust_credit_bill.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill') + FROM cust_credit_bill + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill') ); #horrible local kludge my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql(" SELECT SUM(cust_bill_pkg.setup) - FROM cust_bill_pkg, cust_bill, cust_pkg, part_pkg - WHERE cust_bill.invnum = cust_bill_pkg.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill'). " - AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum - AND cust_pkg.pkgpart = part_pkg.pkgpart - AND LOWER(part_pkg.pkg) LIKE 'expense _%' + FROM cust_bill_pkg + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill'). " + AND LOWER(part_pkg.pkg) LIKE 'expense _%' "); - $self->invoiced($speriod,$eperiod) - $credited - $expenses; + $self->invoiced($speriod,$eperiod,$agentnum) - $credited - $expenses; } #deferred revenue sub receipts { #cashflow - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; my $refunded = $self->scalar_sql(" - SELECT SUM(refund) FROM cust_refund - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(refund) + FROM cust_refund + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); #horrible local kludge that doesn't even really work right my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql(" SELECT SUM(cust_bill_pay.amount) - FROM cust_bill_pay, cust_bill - WHERE cust_bill_pay.invnum = cust_bill.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill_pay'). " + FROM cust_bill_pay + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill_pay'). " AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum @@ -125,40 +193,117 @@ sub receipts { #cashflow "); # my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'"; - $self->payments($speriod, $eperiod) - $refunded - $expenses; + $self->payments($speriod, $eperiod, $agentnum) - $refunded - $expenses; } sub payments { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT SUM(paid) FROM cust_pay - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(paid) + FROM cust_pay + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); } sub credits { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT SUM(amount) FROM cust_credit - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(amount) + FROM cust_credit + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); } +#these should be auto-generated or $AUTOLOADed or something +sub invoiced_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->invoiced($speriod, $eperiod, $agentnum); +} + +sub netsales_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->netsales($speriod, $eperiod, $agentnum); +} + +sub receipts_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->receipts($speriod, $eperiod, $agentnum); +} + +sub payments_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->payments($speriod, $eperiod, $agentnum); +} + +sub credits_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->credits($speriod, $eperiod, $agentnum); +} + +#not being too bad with the false laziness +use Time::Local qw(timelocal); +sub _subtract_11mo { + my($self, $time) = @_; + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5]; + $mon -= 11; + if ( $mon < 0 ) { $mon+=12; $year--; } + timelocal($sec,$min,$hour,$mday,$mon,$year); +} + +sub cust_bill_pkg { + my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_; + + my $where = ''; + if ( $opt{'classnum'} =~ /^(\d+)$/ ) { + if ( $1 == 0 ) { + $where = "classnum IS NULL"; + } else { + $where = "classnum = $1"; + } + } + + $agentnum ||= $opt{'agentnum'}; + + $self->scalar_sql(" + SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur) + FROM cust_bill_pkg + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + WHERE pkgnum != 0 + AND $where + AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) + ); + +} + +# NEEDS TO BE AGENTNUM-capable sub canceled { #active - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) - AND cust_pkg.cancel > $speriod AND cust_pkg.cancel < $eperiod + SELECT COUNT(*) + FROM cust_pkg + LEFT JOIN cust_main USING ( custnum ) + WHERE 0 = ( SELECT COUNT(*) + FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + ) + AND cust_pkg.cancel > $speriod AND cust_pkg.cancel < $eperiod "); } +# NEEDS TO BE AGENTNUM-capable sub newaccount { #newaccount - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.custnum = cust_main.custnum @@ -168,8 +313,9 @@ sub newaccount { #newaccount "); } +# NEEDS TO BE AGENTNUM-capable sub suspended { #suspended - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.custnum = cust_main.custnum @@ -182,10 +328,20 @@ sub suspended { #suspended "); } -sub in_time_period { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); +sub in_time_period_and_agent { + my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4); my $table = @_ ? shift().'.' : ''; - "${table}_date >= $speriod AND ${table}_date < $eperiod"; + + my $sql = "${table}_date >= $speriod AND ${table}_date < $eperiod"; + + #agent selection + $sql .= " AND agentnum = $agentnum" + if $agentnum; + + #agent virtualization + $sql .= ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; + + $sql; } sub scalar_sql { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 451ef2d2e..0d67834a0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3,9 +3,9 @@ package FS::Schema; use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache); use subs qw(reload_dbdef); use Exporter; -use DBIx::DBSchema 0.25; +use DBIx::DBSchema 0.30; use DBIx::DBSchema::Table; -use DBIx::DBSchema::Column; +use DBIx::DBSchema::Column 0.06; use DBIx::DBSchema::ColGroup::Unique; use DBIx::DBSchema::ColGroup::Index; use FS::UID qw(datasrc); @@ -16,13 +16,6 @@ use FS::UID qw(datasrc); $DEBUG = 0; $me = '[FS::Schema]'; -#ask FS::UID to run this stuff for us later -FS::UID->install_callback( sub { - #$conf = new FS::Conf; - &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc) - unless $setup_hack; #$setup_hack needed now? -} ); - =head1 NAME FS::Schema - Freeside database schema @@ -58,7 +51,7 @@ sub reload_dbdef { unless ( exists $dbdef_cache{$file} ) { warn "[debug]$me loading dbdef for $file\n" if $DEBUG; $dbdef_cache{$file} = DBIx::DBSchema->load( $file ) - or die "can't load database schema from $file"; + or die "can't load database schema from $file: $DBIx::DBSchema::errstr\n"; } else { warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG; } @@ -92,9 +85,18 @@ sub dbdef_dist { my $dbdef = new DBIx::DBSchema map { my @columns; while (@{$tables_hashref->{$_}{'columns'}}) { - my($name, $type, $null, $length) = - splice @{$tables_hashref->{$_}{'columns'}}, 0, 4; - push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length ); + #my($name, $type, $null, $length, $default, $local) = + my @coldef = + splice @{$tables_hashref->{$_}{'columns'}}, 0, 6; + my %hash = map { $_ => shift @coldef } + qw( name type null length default local ); + + unless ( defined $hash{'default'} ) { + warn "$_:\n". + join('', map "$_ => $hash{$_}\n", keys %hash) ;# $stop = <STDIN>; + } + + push @columns, new DBIx::DBSchema::Column ( \%hash ); } DBIx::DBSchema::Table->new( $_, @@ -235,19 +237,21 @@ sub tables_hashref { my $username_len = 32; #usernamemax config file + # name type nullability length default local + return { 'agent' => { 'columns' => [ - 'agentnum', 'serial', '', '', - 'agent', 'varchar', '', $char_d, - 'typenum', 'int', '', '', - 'freq', 'int', 'NULL', '', - 'prog', @perl_type, - 'disabled', 'char', 'NULL', 1, - 'username', 'varchar', 'NULL', $char_d, - '_password','varchar', 'NULL', $char_d, - 'ticketing_queueid', 'int', 'NULL', '', + 'agentnum', 'serial', '', '', '', '', + 'agent', 'varchar', '', $char_d, '', '', + 'typenum', 'int', '', '', '', '', + 'freq', 'int', 'NULL', '', '', '', + 'prog', @perl_type, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + 'username', 'varchar', 'NULL', $char_d, '', '', + '_password','varchar', 'NULL', $char_d, '', '', + 'ticketing_queueid', 'int', 'NULL', '', '', '', ], 'primary_key' => 'agentnum', 'unique' => [], @@ -256,8 +260,8 @@ sub tables_hashref { 'agent_type' => { 'columns' => [ - 'typenum', 'serial', '', '', - 'atype', 'varchar', '', $char_d, + 'typenum', 'serial', '', '', '', '', + 'atype', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'typenum', 'unique' => [], @@ -266,9 +270,9 @@ sub tables_hashref { 'type_pkgs' => { 'columns' => [ - 'typepkgnum', 'serial', '', '', - 'typenum', 'int', '', '', - 'pkgpart', 'int', '', '', + 'typepkgnum', 'serial', '', '', '', '', + 'typenum', 'int', '', '', '', '', + 'pkgpart', 'int', '', '', '', '', ], 'primary_key' => 'typepkgnum', 'unique' => [ ['typenum', 'pkgpart'] ], @@ -277,12 +281,12 @@ sub tables_hashref { 'cust_bill' => { 'columns' => [ - 'invnum', 'serial', '', '', - 'custnum', 'int', '', '', - '_date', @date_type, - 'charged', @money_type, - 'printed', 'int', '', '', - 'closed', 'char', 'NULL', 1, + 'invnum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'charged', @money_type, '', '', + 'printed', 'int', '', '', '', '', + 'closed', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'invnum', 'unique' => [], @@ -291,12 +295,12 @@ sub tables_hashref { 'cust_bill_event' => { 'columns' => [ - 'eventnum', 'serial', '', '', - 'invnum', 'int', '', '', - 'eventpart', 'int', '', '', - '_date', @date_type, - 'status', 'varchar', '', $char_d, - 'statustext', 'text', 'NULL', '', + 'eventnum', 'serial', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'eventpart', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'status', 'varchar', '', $char_d, '', '', + 'statustext', 'text', 'NULL', '', '', '', ], 'primary_key' => 'eventnum', #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ], @@ -306,15 +310,17 @@ sub tables_hashref { 'part_bill_event' => { 'columns' => [ - 'eventpart', 'serial', '', '', - 'payby', 'char', '', 4, - 'event', 'varchar', '', $char_d, - 'eventcode', @perl_type, - 'seconds', 'int', 'NULL', '', - 'weight', 'int', '', '', - 'plan', 'varchar', 'NULL', $char_d, - 'plandata', 'text', 'NULL', '', - 'disabled', 'char', 'NULL', 1, + 'eventpart', 'serial', '', '', '', '', + 'freq', 'varchar', 'NULL', $char_d, '', '', + 'payby', 'char', '', 4, '', '', + 'event', 'varchar', '', $char_d, '', '', + 'eventcode', @perl_type, '', '', + 'seconds', 'int', 'NULL', '', '', '', + 'weight', 'int', '', '', '', '', + 'plan', 'varchar', 'NULL', $char_d, '', '', + 'plandata', 'text', 'NULL', '', '', '', + 'reason', 'int', 'NULL', '', '', '', + 'disabled', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'eventpart', 'unique' => [], @@ -323,14 +329,14 @@ sub tables_hashref { 'cust_bill_pkg' => { 'columns' => [ - 'billpkgnum', 'serial', '', '', - 'pkgnum', 'int', '', '', - 'invnum', 'int', '', '', - 'setup', @money_type, - 'recur', @money_type, - 'sdate', @date_type, - 'edate', @date_type, - 'itemdesc', 'varchar', 'NULL', $char_d, + 'billpkgnum', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'setup', @money_type, '', '', + 'recur', @money_type, '', '', + 'sdate', @date_type, '', '', + 'edate', @date_type, '', '', + 'itemdesc', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'billpkgnum', 'unique' => [], @@ -339,10 +345,10 @@ sub tables_hashref { 'cust_bill_pkg_detail' => { 'columns' => [ - 'detailnum', 'serial', '', '', - 'pkgnum', 'int', '', '', - 'invnum', 'int', '', '', - 'detail', 'varchar', '', $char_d, + 'detailnum', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'detail', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'detailnum', 'unique' => [], @@ -351,13 +357,13 @@ sub tables_hashref { 'cust_credit' => { 'columns' => [ - 'crednum', 'serial', '', '', - 'custnum', 'int', '', '', - '_date', @date_type, - 'amount', @money_type, - 'otaker', 'varchar', '', 32, - 'reason', 'text', 'NULL', '', - 'closed', 'char', 'NULL', 1, + 'crednum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'amount', @money_type, '', '', + 'otaker', 'varchar', '', 32, '', '', + 'reason', 'text', 'NULL', '', '', '', + 'closed', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'crednum', 'unique' => [], @@ -366,102 +372,137 @@ sub tables_hashref { 'cust_credit_bill' => { 'columns' => [ - 'creditbillnum', 'serial', '', '', - 'crednum', 'int', '', '', - 'invnum', 'int', '', '', - '_date', @date_type, - 'amount', @money_type, + 'creditbillnum', 'serial', '', '', '', '', + 'crednum', 'int', '', '', '', '', + 'invnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'amount', @money_type, '', '', ], 'primary_key' => 'creditbillnum', 'unique' => [], 'index' => [ ['crednum'], ['invnum'] ], }, + 'cust_credit_bill_pkg' => { + 'columns' => [ + 'creditbillpkgnum', 'serial', '', '', '', '', + 'creditbillnum', 'int', '', '', '', '', + 'billpkgnum', 'int', '', '', '', '', + 'amount', @money_type, '', '', + 'setuprecur', 'varchar', '', $char_d, '', '', + 'sdate', @date_type, '', '', + 'edate', @date_type, '', '', + ], + 'primary_key' => 'creditbillpkgnum', + 'unique' => [], + 'index' => [ [ 'creditbillnum' ], [ 'billpkgnum' ], ], + }, + 'cust_main' => { 'columns' => [ - 'custnum', 'serial', '', '', - 'agentnum', 'int', '', '', -# 'titlenum', 'int', 'NULL', '', - 'last', 'varchar', '', $char_d, -# 'middle', 'varchar', 'NULL', $char_d, - 'first', 'varchar', '', $char_d, - 'ss', 'varchar', 'NULL', 11, - 'company', 'varchar', 'NULL', $char_d, - 'address1', 'varchar', '', $char_d, - 'address2', 'varchar', 'NULL', $char_d, - 'city', 'varchar', '', $char_d, - 'county', 'varchar', 'NULL', $char_d, - 'state', 'varchar', 'NULL', $char_d, - 'zip', 'varchar', 'NULL', 10, - 'country', 'char', '', 2, - 'daytime', 'varchar', 'NULL', 20, - 'night', 'varchar', 'NULL', 20, - 'fax', 'varchar', 'NULL', 12, - 'ship_last', 'varchar', 'NULL', $char_d, -# 'ship_middle', 'varchar', 'NULL', $char_d, - 'ship_first', 'varchar', 'NULL', $char_d, - 'ship_company', 'varchar', 'NULL', $char_d, - 'ship_address1', 'varchar', 'NULL', $char_d, - 'ship_address2', 'varchar', 'NULL', $char_d, - 'ship_city', 'varchar', 'NULL', $char_d, - 'ship_county', 'varchar', 'NULL', $char_d, - 'ship_state', 'varchar', 'NULL', $char_d, - 'ship_zip', 'varchar', 'NULL', 10, - 'ship_country', 'char', 'NULL', 2, - 'ship_daytime', 'varchar', 'NULL', 20, - 'ship_night', 'varchar', 'NULL', 20, - 'ship_fax', 'varchar', 'NULL', 12, - 'payby', 'char', '', 4, - 'payinfo', 'varchar', 'NULL', 512, - 'paycvv', 'varchar', 'NULL', 512, - 'paymask', 'varchar', 'NULL', $char_d, - #'paydate', @date_type, - 'paydate', 'varchar', 'NULL', 10, - 'paystart_month', 'int', 'NULL', '', - 'paystart_year', 'int', 'NULL', '', - 'payissue', 'varchar', 'NULL', 2, - 'payname', 'varchar', 'NULL', $char_d, - 'payip', 'varchar', 'NULL', 15, - 'tax', 'char', 'NULL', 1, - 'otaker', 'varchar', '', 32, - 'refnum', 'int', '', '', - 'referral_custnum', 'int', 'NULL', '', - 'comments', 'text', 'NULL', '', + 'custnum', 'serial', '', '', '', '', + 'agentnum', 'int', '', '', '', '', + 'agent_custid', 'varchar', 'NULL', $char_d, '', '', +# 'titlenum', 'int', 'NULL', '', '', '', + 'last', 'varchar', '', $char_d, '', '', +# 'middle', 'varchar', 'NULL', $char_d, '', '', + 'first', 'varchar', '', $char_d, '', '', + 'ss', 'varchar', 'NULL', 11, '', '', + 'birthdate' ,@date_type, '', '', + 'signupdate',@date_type, '', '', + 'company', 'varchar', 'NULL', $char_d, '', '', + 'address1', 'varchar', '', $char_d, '', '', + 'address2', 'varchar', 'NULL', $char_d, '', '', + 'city', 'varchar', '', $char_d, '', '', + 'county', 'varchar', 'NULL', $char_d, '', '', + 'state', 'varchar', 'NULL', $char_d, '', '', + 'zip', 'varchar', 'NULL', 10, '', '', + 'country', 'char', '', 2, '', '', + 'daytime', 'varchar', 'NULL', 20, '', '', + 'night', 'varchar', 'NULL', 20, '', '', + 'fax', 'varchar', 'NULL', 12, '', '', + 'ship_last', 'varchar', 'NULL', $char_d, '', '', +# 'ship_middle', 'varchar', 'NULL', $char_d, '', '', + 'ship_first', 'varchar', 'NULL', $char_d, '', '', + 'ship_company', 'varchar', 'NULL', $char_d, '', '', + 'ship_address1', 'varchar', 'NULL', $char_d, '', '', + 'ship_address2', 'varchar', 'NULL', $char_d, '', '', + 'ship_city', 'varchar', 'NULL', $char_d, '', '', + 'ship_county', 'varchar', 'NULL', $char_d, '', '', + 'ship_state', 'varchar', 'NULL', $char_d, '', '', + 'ship_zip', 'varchar', 'NULL', 10, '', '', + 'ship_country', 'char', 'NULL', 2, '', '', + 'ship_daytime', 'varchar', 'NULL', 20, '', '', + 'ship_night', 'varchar', 'NULL', 20, '', '', + 'ship_fax', 'varchar', 'NULL', 12, '', '', + 'payby', 'char', '', 4, '', '', + 'payinfo', 'varchar', 'NULL', 512, '', '', + 'paycvv', 'varchar', 'NULL', 512, '', '', + 'paymask', 'varchar', 'NULL', $char_d, '', '', + #'paydate', @date_type, '', '', + 'paydate', 'varchar', 'NULL', 10, '', '', + 'paystart_month', 'int', 'NULL', '', '', '', + 'paystart_year', 'int', 'NULL', '', '', '', + 'payissue', 'varchar', 'NULL', 2, '', '', + 'payname', 'varchar', 'NULL', $char_d, '', '', + 'payip', 'varchar', 'NULL', 15, '', '', + 'tax', 'char', 'NULL', 1, '', '', + 'otaker', 'varchar', '', 32, '', '', + 'refnum', 'int', '', '', '', '', + 'referral_custnum', 'int', 'NULL', '', '', '', + 'comments', 'text', 'NULL', '', '', '', + 'spool_cdr','char', 'NULL', 1, '', '', ], 'primary_key' => 'custnum', - 'unique' => [], + 'unique' => [ [ 'agentnum', 'agent_custid' ] ], #'index' => [ ['last'], ['company'] ], 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ], [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ], - [ 'county' ], [ 'state' ], [ 'country' ] + [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ], + [ 'ship_last' ], [ 'ship_company' ], + [ 'payby' ], [ 'paydate' ], + ], }, 'cust_main_invoice' => { 'columns' => [ - 'destnum', 'serial', '', '', - 'custnum', 'int', '', '', - 'dest', 'varchar', '', $char_d, + 'destnum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'dest', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'destnum', 'unique' => [], 'index' => [ ['custnum'], ], }, + 'cust_main_note' => { + 'columns' => [ + 'notenum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'otaker', 'varchar', '', 32, '', '', + 'comments', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'notenum', + 'unique' => [], + 'index' => [ [ 'custnum' ], [ '_date' ], ], + }, + 'cust_main_county' => { #county+state+country are checked off the #cust_main_county for validation and to provide # a tax rate. 'columns' => [ - 'taxnum', 'serial', '', '', - 'state', 'varchar', 'NULL', $char_d, - 'county', 'varchar', 'NULL', $char_d, - 'country', 'char', '', 2, - 'taxclass', 'varchar', 'NULL', $char_d, - 'exempt_amount', @money_type, - 'tax', 'real', '', '', #tax % - 'taxname', 'varchar', 'NULL', $char_d, - 'setuptax', 'char', 'NULL', 1, # Y = setup tax exempt - 'recurtax', 'char', 'NULL', 1, # Y = recur tax exempt + 'taxnum', 'serial', '', '', '', '', + 'state', 'varchar', 'NULL', $char_d, '', '', + 'county', 'varchar', 'NULL', $char_d, '', '', + 'country', 'char', '', 2, '', '', + 'taxclass', 'varchar', 'NULL', $char_d, '', '', + 'exempt_amount', @money_type, '', '', + 'tax', 'real', '', '', '', '', #tax % + 'taxname', 'varchar', 'NULL', $char_d, '', '', + 'setuptax', 'char', 'NULL', 1, '', '', # Y = setup tax exempt + 'recurtax', 'char', 'NULL', 1, '', '', # Y = recur tax exempt ], 'primary_key' => 'taxnum', 'unique' => [], @@ -471,16 +512,18 @@ sub tables_hashref { 'cust_pay' => { 'columns' => [ - 'paynum', 'serial', '', '', - #now cust_bill_pay #'invnum', 'int', '', '', - 'custnum', 'int', '', '', - 'paid', @money_type, - '_date', @date_type, - 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index into - # payment type table. - 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above - 'paybatch', 'varchar', 'NULL', $char_d, #for auditing purposes. - 'closed', 'char', 'NULL', 1, + 'paynum', 'serial', '', '', '', '', + #now cust_bill_pay #'invnum', 'int', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'paid', @money_type, '', '', + '_date', @date_type, '', '', + 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be + # index into payby table + # eventually + 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above + 'paymask', 'varchar', 'NULL', $char_d, '', '', + 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes. + 'closed', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'paynum', 'unique' => [], @@ -489,18 +532,20 @@ sub tables_hashref { 'cust_pay_void' => { 'columns' => [ - 'paynum', 'int', '', '', - 'custnum', 'int', '', '', - 'paid', @money_type, - '_date', @date_type, - 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index into - # payment type table. - 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above - 'paybatch', 'varchar', 'NULL', $char_d, #for auditing purposes. - 'closed', 'char', 'NULL', 1, - 'void_date', @date_type, - 'reason', 'varchar', 'NULL', $char_d, - 'otaker', 'varchar', '', 32, + 'paynum', 'int', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'paid', @money_type, '', '', + '_date', @date_type, '', '', + 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be + # index into payby table + # eventually + 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above + 'paymask', 'varchar', 'NULL', $char_d, '', '', + 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes. + 'closed', 'char', 'NULL', 1, '', '', + 'void_date', @date_type, '', '', + 'reason', 'varchar', 'NULL', $char_d, '', '', + 'otaker', 'varchar', '', 32, '', '', ], 'primary_key' => 'paynum', 'unique' => [], @@ -509,76 +554,147 @@ sub tables_hashref { 'cust_bill_pay' => { 'columns' => [ - 'billpaynum', 'serial', '', '', - 'invnum', 'int', '', '', - 'paynum', 'int', '', '', - 'amount', @money_type, - '_date', @date_type + 'billpaynum', 'serial', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'paynum', 'int', '', '', '', '', + 'amount', @money_type, '', '', + '_date', @date_type, '', '', ], 'primary_key' => 'billpaynum', 'unique' => [], 'index' => [ [ 'paynum' ], [ 'invnum' ] ], }, + 'cust_bill_pay_batch' => { + 'columns' => [ + 'billpaynum', 'serial', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'paybatchnum', 'int', '', '', '', '', + 'amount', @money_type, '', '', + '_date', @date_type, '', '', + ], + 'primary_key' => 'billpaynum', + 'unique' => [], + 'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ], + }, + + 'cust_bill_pay_pkg' => { + 'columns' => [ + 'billpaypkgnum', 'serial', '', '', '', '', + 'billpaynum', 'int', '', '', '', '', + 'billpkgnum', 'int', '', '', '', '', + 'amount', @money_type, '', '', + 'setuprecur', 'varchar', '', $char_d, '', '', + 'sdate', @date_type, '', '', + 'edate', @date_type, '', '', + ], + 'primary_key' => 'billpaypkgnum', + 'unique' => [], + 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ], + }, + + 'pay_batch' => { #batches of payments to an external processor + 'columns' => [ + 'batchnum', 'serial', '', '', '', '', + 'payby', 'char', '', 4, '', '', # CARD/CHEK + 'status', 'char', 'NULL', 1, '', '', + 'download', @date_type, '', '', + 'upload', @date_type, '', '', + ], + 'primary_key' => 'batchnum', + 'unique' => [], + 'index' => [], + }, + 'cust_pay_batch' => { #what's this used for again? list of customers #in current CARD batch? (necessarily CARD?) 'columns' => [ - 'paybatchnum', 'serial', '', '', - 'invnum', 'int', '', '', - 'custnum', 'int', '', '', - 'last', 'varchar', '', $char_d, - 'first', 'varchar', '', $char_d, - 'address1', 'varchar', '', $char_d, - 'address2', 'varchar', 'NULL', $char_d, - 'city', 'varchar', '', $char_d, - 'state', 'varchar', 'NULL', $char_d, - 'zip', 'varchar', 'NULL', 10, - 'country', 'char', '', 2, -# 'trancode', 'int', '', '', - 'cardnum', 'varchar', '', 16, - #'exp', @date_type, - 'exp', 'varchar', '', 11, - 'payname', 'varchar', 'NULL', $char_d, - 'amount', @money_type, + 'paybatchnum', 'serial', '', '', '', '', + 'batchnum', 'int', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'last', 'varchar', '', $char_d, '', '', + 'first', 'varchar', '', $char_d, '', '', + 'address1', 'varchar', '', $char_d, '', '', + 'address2', 'varchar', 'NULL', $char_d, '', '', + 'city', 'varchar', '', $char_d, '', '', + 'state', 'varchar', 'NULL', $char_d, '', '', + 'zip', 'varchar', 'NULL', 10, '', '', + 'country', 'char', '', 2, '', '', + # 'trancode', 'int', '', '', '', '' + 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be + 'payinfo', 'varchar', '', 512, '', '', + #'exp', @date_type, '', '' + 'exp', 'varchar', 'NULL', 11, '', '', + 'payname', 'varchar', 'NULL', $char_d, '', '', + 'amount', @money_type, '', '', + 'status', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'paybatchnum', 'unique' => [], - 'index' => [ ['invnum'], ['custnum'] ], + 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ], }, 'cust_pkg' => { 'columns' => [ - 'pkgnum', 'serial', '', '', - 'custnum', 'int', '', '', - 'pkgpart', 'int', '', '', - 'otaker', 'varchar', '', 32, - 'setup', @date_type, - 'bill', @date_type, - 'last_bill', @date_type, - 'susp', @date_type, - 'cancel', @date_type, - 'expire', @date_type, - 'manual_flag', 'char', 'NULL', 1, + 'pkgnum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'pkgpart', 'int', '', '', '', '', + 'otaker', 'varchar', '', 32, '', '', + 'setup', @date_type, '', '', + 'bill', @date_type, '', '', + 'last_bill', @date_type, '', '', + 'susp', @date_type, '', '', + 'cancel', @date_type, '', '', + 'expire', @date_type, '', '', + 'manual_flag', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'pkgnum', 'unique' => [], 'index' => [ ['custnum'], ['pkgpart'] ], }, + 'cust_pkg_option' => { + 'columns' => [ + 'optionnum', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'optionname', 'varchar', '', $char_d, '', '', + 'optionvalue', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'optionnum', + 'unique' => [], + 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ], + }, + + 'cust_pkg_reason' => { + 'columns' => [ + 'num', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'reasonnum','int', '', '', '', '', + 'otaker', 'varchar', '', 32, '', '', + 'date', @date_type, '', '', + ], + 'primary_key' => 'num', + 'unique' => [], + 'index' => [], + }, + 'cust_refund' => { 'columns' => [ - 'refundnum', 'serial', '', '', - #now cust_credit_refund #'crednum', 'int', '', '', - 'custnum', 'int', '', '', - '_date', @date_type, - 'refund', @money_type, - 'otaker', 'varchar', '', 32, - 'reason', 'varchar', '', $char_d, - 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index - # into payment type table. - 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above - 'paybatch', 'varchar', 'NULL', $char_d, - 'closed', 'char', 'NULL', 1, + 'refundnum', 'serial', '', '', '', '', + #now cust_credit_refund #'crednum', 'int', '', '', '', '', + 'custnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'refund', @money_type, '', '', + 'otaker', 'varchar', '', 32, '', '', + 'reason', 'varchar', '', $char_d, '', '', + 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should + # be index into payby + # table eventually + 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above + 'paymask', 'varchar', 'NULL', $char_d, '', '', + 'paybatch', 'varchar', 'NULL', $char_d, '', '', + 'closed', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'refundnum', 'unique' => [], @@ -587,11 +703,11 @@ sub tables_hashref { 'cust_credit_refund' => { 'columns' => [ - 'creditrefundnum', 'serial', '', '', - 'crednum', 'int', '', '', - 'refundnum', 'int', '', '', - 'amount', @money_type, - '_date', @date_type + 'creditrefundnum', 'serial', '', '', '', '', + 'crednum', 'int', '', '', '', '', + 'refundnum', 'int', '', '', '', '', + 'amount', @money_type, '', '', + '_date', @date_type, '', '', ], 'primary_key' => 'creditrefundnum', 'unique' => [], @@ -601,9 +717,9 @@ sub tables_hashref { 'cust_svc' => { 'columns' => [ - 'svcnum', 'serial', '', '', - 'pkgnum', 'int', 'NULL', '', - 'svcpart', 'int', '', '', + 'svcnum', 'serial', '', '', '', '', + 'pkgnum', 'int', 'NULL', '', '', '', + 'svcpart', 'int', '', '', '', '', ], 'primary_key' => 'svcnum', 'unique' => [], @@ -612,19 +728,22 @@ sub tables_hashref { 'part_pkg' => { 'columns' => [ - 'pkgpart', 'serial', '', '', - 'pkg', 'varchar', '', $char_d, - 'comment', 'varchar', '', $char_d, - 'promo_code', 'varchar', 'NULL', $char_d, - 'setup', @perl_type, - 'freq', 'varchar', '', $char_d, #billing frequency - 'recur', @perl_type, - 'setuptax', 'char', 'NULL', 1, - 'recurtax', 'char', 'NULL', 1, - 'plan', 'varchar', 'NULL', $char_d, - 'plandata', 'text', 'NULL', '', - 'disabled', 'char', 'NULL', 1, - 'taxclass', 'varchar', 'NULL', $char_d, + 'pkgpart', 'serial', '', '', '', '', + 'pkg', 'varchar', '', $char_d, '', '', + 'comment', 'varchar', '', $char_d, '', '', + 'promo_code', 'varchar', 'NULL', $char_d, '', '', + 'setup', @perl_type, '', '', + 'freq', 'varchar', '', $char_d, '', '', #billing frequency + 'recur', @perl_type, '', '', + 'setuptax', 'char', 'NULL', 1, '', '', + 'recurtax', 'char', 'NULL', 1, '', '', + 'plan', 'varchar', 'NULL', $char_d, '', '', + 'plandata', 'text', 'NULL', '', '', '', + 'disabled', 'char', 'NULL', 1, '', '', + 'taxclass', 'varchar', 'NULL', $char_d, '', '', + 'classnum', 'int', 'NULL', '', '', '', + 'pay_weight', 'real', 'NULL', '', '', '', + 'credit_weight', 'real', 'NULL', '', '', '', ], 'primary_key' => 'pkgpart', 'unique' => [], @@ -643,11 +762,11 @@ sub tables_hashref { 'pkg_svc' => { 'columns' => [ - 'pkgsvcnum', 'serial', '', '', - 'pkgpart', 'int', '', '', - 'svcpart', 'int', '', '', - 'quantity', 'int', '', '', - 'primary_svc','char', 'NULL', 1, + 'pkgsvcnum', 'serial', '', '', '', '', + 'pkgpart', 'int', '', '', '', '', + 'svcpart', 'int', '', '', '', '', + 'quantity', 'int', '', '', '', '', + 'primary_svc','char', 'NULL', 1, '', '', ], 'primary_key' => 'pkgsvcnum', 'unique' => [ ['pkgpart', 'svcpart'] ], @@ -656,9 +775,10 @@ sub tables_hashref { 'part_referral' => { 'columns' => [ - 'refnum', 'serial', '', '', - 'referral', 'varchar', '', $char_d, - 'disabled', 'char', 'NULL', 1, + 'refnum', 'serial', '', '', '', '', + 'referral', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + 'agentnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'refnum', 'unique' => [], @@ -667,10 +787,10 @@ sub tables_hashref { 'part_svc' => { 'columns' => [ - 'svcpart', 'serial', '', '', - 'svc', 'varchar', '', $char_d, - 'svcdb', 'varchar', '', $char_d, - 'disabled', 'char', 'NULL', 1, + 'svcpart', 'serial', '', '', '', '', + 'svc', 'varchar', '', $char_d, '', '', + 'svcdb', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'svcpart', 'unique' => [], @@ -679,11 +799,11 @@ sub tables_hashref { 'part_svc_column' => { 'columns' => [ - 'columnnum', 'serial', '', '', - 'svcpart', 'int', '', '', - 'columnname', 'varchar', '', 64, - 'columnvalue', 'varchar', 'NULL', $char_d, - 'columnflag', 'char', 'NULL', 1, + 'columnnum', 'serial', '', '', '', '', + 'svcpart', 'int', '', '', '', '', + 'columnname', 'varchar', '', 64, '', '', + 'columnvalue', 'varchar', 'NULL', $char_d, '', '', + 'columnflag', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'columnnum', 'unique' => [ [ 'svcpart', 'columnname' ] ], @@ -693,12 +813,12 @@ sub tables_hashref { #(this should be renamed to part_pop) 'svc_acct_pop' => { 'columns' => [ - 'popnum', 'serial', '', '', - 'city', 'varchar', '', $char_d, - 'state', 'varchar', '', $char_d, - 'ac', 'char', '', 3, - 'exch', 'char', '', 3, - 'loc', 'char', 'NULL', 4, #NULL for legacy purposes + 'popnum', 'serial', '', '', '', '', + 'city', 'varchar', '', $char_d, '', '', + 'state', 'varchar', '', $char_d, '', '', + 'ac', 'char', '', 3, '', '', + 'exch', 'char', '', 3, '', '', + 'loc', 'char', 'NULL', 4, '', '', #NULL for legacy purposes ], 'primary_key' => 'popnum', 'unique' => [], @@ -707,12 +827,12 @@ sub tables_hashref { 'part_pop_local' => { 'columns' => [ - 'localnum', 'serial', '', '', - 'popnum', 'int', '', '', - 'city', 'varchar', 'NULL', $char_d, - 'state', 'char', 'NULL', 2, - 'npa', 'char', '', 3, - 'nxx', 'char', '', 3, + 'localnum', 'serial', '', '', '', '', + 'popnum', 'int', '', '', '', '', + 'city', 'varchar', 'NULL', $char_d, '', '', + 'state', 'char', 'NULL', 2, '', '', + 'npa', 'char', '', 3, '', '', + 'nxx', 'char', '', 3, '', '', ], 'primary_key' => 'localnum', 'unique' => [], @@ -721,20 +841,27 @@ sub tables_hashref { 'svc_acct' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'username', 'varchar', '', $username_len, #unique (& remove dup code) - '_password', 'varchar', '', 72, #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60) - 'sec_phrase', 'varchar', 'NULL', $char_d, - 'popnum', 'int', 'NULL', '', - 'uid', 'int', 'NULL', '', - 'gid', 'int', 'NULL', '', - 'finger', 'varchar', 'NULL', $char_d, - 'dir', 'varchar', 'NULL', $char_d, - 'shell', 'varchar', 'NULL', $char_d, - 'quota', 'varchar', 'NULL', $char_d, - 'slipip', 'varchar', 'NULL', 15, #four TINYINTs, bah. - 'seconds', 'int', 'NULL', '', #uhhhh - 'domsvc', 'int', '', '', + 'svcnum', 'int', '', '', '', '', + 'username', 'varchar', '', $username_len, '', '', #unique (& remove dup code) + '_password', 'varchar', '', 72, '', '', #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60) + 'sec_phrase', 'varchar', 'NULL', $char_d, '', '', + 'popnum', 'int', 'NULL', '', '', '', + 'uid', 'int', 'NULL', '', '', '', + 'gid', 'int', 'NULL', '', '', '', + 'finger', 'varchar', 'NULL', $char_d, '', '', + 'dir', 'varchar', 'NULL', $char_d, '', '', + 'shell', 'varchar', 'NULL', $char_d, '', '', + 'quota', 'varchar', 'NULL', $char_d, '', '', + 'slipip', 'varchar', 'NULL', 15, '', '', #four TINYINTs, bah. + 'seconds', 'int', 'NULL', '', '', '', #uhhhh + 'seconds_threshold', 'int', 'NULL', '', '', '', + 'upbytes', 'bigint', 'NULL', '', '', '', + 'upbytes_threshold', 'bigint', 'NULL', '', '', '', + 'downbytes', 'bigint', 'NULL', '', '', '', + 'downbytes_threshold', 'bigint', 'NULL', '', '', '', + 'totalbytes','bigint', 'NULL', '', '', '', + 'totalbytes_threshold', 'bigint', 'NULL', '', '', '', + 'domsvc', 'int', '', '', '', '', ], 'primary_key' => 'svcnum', #'unique' => [ [ 'username', 'domsvc' ] ], @@ -754,38 +881,53 @@ sub tables_hashref { 'svc_domain' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'domain', 'varchar', '', $char_d, - 'catchall', 'int', 'NULL', '', + 'svcnum', 'int', '', '', '', '', + 'domain', 'varchar', '', $char_d, '', '', + 'suffix', 'varchar', 'NULL', $char_d, '', '', + 'catchall', 'int', 'NULL', '', '', '', + 'parent_svcnum', 'int', 'NULL', '', '', '', + 'registrarnum', 'int', 'NULL', '', '', '', + 'registrarkey', 'varchar', 'NULL', '', '', '', + 'setup_date', @date_type, '', '', + 'renewal_interval', 'int', 'NULL', '', '', '', + 'expiration_date', @date_type, '', '', ], 'primary_key' => 'svcnum', - 'unique' => [ ['domain'] ], - 'index' => [], + 'unique' => [ ], + 'index' => [ ['domain'] ], }, 'domain_record' => { 'columns' => [ - 'recnum', 'serial', '', '', - 'svcnum', 'int', '', '', - #'reczone', 'varchar', '', $char_d, - 'reczone', 'varchar', '', 255, - 'recaf', 'char', '', 2, - 'rectype', 'varchar', '', 5, - #'recdata', 'varchar', '', $char_d, - 'recdata', 'varchar', '', 255, + 'recnum', 'serial', '', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'reczone', 'varchar', '', 255, '', '', + 'recaf', 'char', '', 2, '', '', + 'rectype', 'varchar', '', 5, '', '', + 'recdata', 'varchar', '', 255, '', '', ], 'primary_key' => 'recnum', 'unique' => [], 'index' => [ ['svcnum'] ], }, + 'registrar' => { + 'columns' => [ + 'registrarnum', 'serial', '', '', '', '', + 'registrarname', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'registrarnum', + 'unique' => [], + 'index' => [], + }, + 'svc_forward' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'srcsvc', 'int', 'NULL', '', - 'src', 'varchar', 'NULL', 255, - 'dstsvc', 'int', 'NULL', '', - 'dst', 'varchar', 'NULL', 255, + 'svcnum', 'int', '', '', '', '', + 'srcsvc', 'int', 'NULL', '', '', '', + 'src', 'varchar', 'NULL', 255, '', '', + 'dstsvc', 'int', 'NULL', '', '', '', + 'dst', 'varchar', 'NULL', 255, '', '', ], 'primary_key' => 'svcnum', 'unique' => [], @@ -794,9 +936,9 @@ sub tables_hashref { 'svc_www' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'recnum', 'int', '', '', - 'usersvc', 'int', '', '', + 'svcnum', 'int', '', '', '', '', + 'recnum', 'int', '', '', '', '', + 'usersvc', 'int', '', '', '', '', ], 'primary_key' => 'svcnum', 'unique' => [], @@ -818,11 +960,14 @@ sub tables_hashref { 'prepay_credit' => { 'columns' => [ - 'prepaynum', 'serial', '', '', - 'identifier', 'varchar', '', $char_d, - 'amount', @money_type, - 'seconds', 'int', 'NULL', '', - 'agentnum', 'int', 'NULL', '', + 'prepaynum', 'serial', '', '', '', '', + 'identifier', 'varchar', '', $char_d, '', '', + 'amount', @money_type, '', '', + 'seconds', 'int', 'NULL', '', '', '', + 'upbytes', 'bigint', 'NULL', '', '', '', + 'downbytes', 'bigint', 'NULL', '', '', '', + 'totalbytes', 'bigint', 'NULL', '', '', '', + 'agentnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'prepaynum', 'unique' => [ ['identifier'] ], @@ -831,10 +976,10 @@ sub tables_hashref { 'port' => { 'columns' => [ - 'portnum', 'serial', '', '', - 'ip', 'varchar', 'NULL', 15, - 'nasport', 'int', 'NULL', '', - 'nasnum', 'int', '', '', + 'portnum', 'serial', '', '', '', '', + 'ip', 'varchar', 'NULL', 15, '', '', + 'nasport', 'int', 'NULL', '', '', '', + 'nasnum', 'int', '', '', '', '', ], 'primary_key' => 'portnum', 'unique' => [], @@ -843,11 +988,11 @@ sub tables_hashref { 'nas' => { 'columns' => [ - 'nasnum', 'serial', '', '', - 'nas', 'varchar', '', $char_d, - 'nasip', 'varchar', '', 15, - 'nasfqdn', 'varchar', '', $char_d, - 'last', 'int', '', '', + 'nasnum', 'serial', '', '', '', '', + 'nas', 'varchar', '', $char_d, '', '', + 'nasip', 'varchar', '', 15, '', '', + 'nasfqdn', 'varchar', '', $char_d, '', '', + 'last', 'int', '', '', '', '', ], 'primary_key' => 'nasnum', 'unique' => [ [ 'nas' ], [ 'nasip' ] ], @@ -856,11 +1001,11 @@ sub tables_hashref { 'session' => { 'columns' => [ - 'sessionnum', 'serial', '', '', - 'portnum', 'int', '', '', - 'svcnum', 'int', '', '', - 'login', @date_type, - 'logout', @date_type, + 'sessionnum', 'serial', '', '', '', '', + 'portnum', 'int', '', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'login', @date_type, '', '', + 'logout', @date_type, '', '', ], 'primary_key' => 'sessionnum', 'unique' => [], @@ -869,12 +1014,12 @@ sub tables_hashref { 'queue' => { 'columns' => [ - 'jobnum', 'serial', '', '', - 'job', 'text', '', '', - '_date', 'int', '', '', - 'status', 'varchar', '', $char_d, - 'statustext', 'text', 'NULL', '', - 'svcnum', 'int', 'NULL', '', + 'jobnum', 'serial', '', '', '', '', + 'job', 'text', '', '', '', '', + '_date', 'int', '', '', '', '', + 'status', 'varchar', '', $char_d, '', '', + 'statustext', 'text', 'NULL', '', '', '', + 'svcnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'jobnum', 'unique' => [], @@ -883,9 +1028,9 @@ sub tables_hashref { 'queue_arg' => { 'columns' => [ - 'argnum', 'serial', '', '', - 'jobnum', 'int', '', '', - 'arg', 'text', 'NULL', '', + 'argnum', 'serial', '', '', '', '', + 'jobnum', 'int', '', '', '', '', + 'arg', 'text', 'NULL', '', '', '', ], 'primary_key' => 'argnum', 'unique' => [], @@ -894,9 +1039,9 @@ sub tables_hashref { 'queue_depend' => { 'columns' => [ - 'dependnum', 'serial', '', '', - 'jobnum', 'int', '', '', - 'depend_jobnum', 'int', '', '', + 'dependnum', 'serial', '', '', '', '', + 'jobnum', 'int', '', '', '', '', + 'depend_jobnum', 'int', '', '', '', '', ], 'primary_key' => 'dependnum', 'unique' => [], @@ -905,9 +1050,9 @@ sub tables_hashref { 'export_svc' => { 'columns' => [ - 'exportsvcnum' => 'serial', '', '', - 'exportnum' => 'int', '', '', - 'svcpart' => 'int', '', '', + 'exportsvcnum' => 'serial', '', '', '', '', + 'exportnum' => 'int', '', '', '', '', + 'svcpart' => 'int', '', '', '', '', ], 'primary_key' => 'exportsvcnum', 'unique' => [ [ 'exportnum', 'svcpart' ] ], @@ -916,11 +1061,10 @@ sub tables_hashref { 'part_export' => { 'columns' => [ - 'exportnum', 'serial', '', '', - #'svcpart', 'int', '', '', - 'machine', 'varchar', '', $char_d, - 'exporttype', 'varchar', '', $char_d, - 'nodomain', 'char', 'NULL', 1, + 'exportnum', 'serial', '', '', '', '', + 'machine', 'varchar', '', $char_d, '', '', + 'exporttype', 'varchar', '', $char_d, '', '', + 'nodomain', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'exportnum', 'unique' => [], @@ -929,10 +1073,10 @@ sub tables_hashref { 'part_export_option' => { 'columns' => [ - 'optionnum', 'serial', '', '', - 'exportnum', 'int', '', '', - 'optionname', 'varchar', '', $char_d, - 'optionvalue', 'text', 'NULL', '', + 'optionnum', 'serial', '', '', '', '', + 'exportnum', 'int', '', '', '', '', + 'optionname', 'varchar', '', $char_d, '', '', + 'optionvalue', 'text', 'NULL', '', '', '', ], 'primary_key' => 'optionnum', 'unique' => [], @@ -941,9 +1085,9 @@ sub tables_hashref { 'radius_usergroup' => { 'columns' => [ - 'usergroupnum', 'serial', '', '', - 'svcnum', 'int', '', '', - 'groupname', 'varchar', '', $char_d, + 'usergroupnum', 'serial', '', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'groupname', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'usergroupnum', 'unique' => [], @@ -952,10 +1096,10 @@ sub tables_hashref { 'msgcat' => { 'columns' => [ - 'msgnum', 'serial', '', '', - 'msgcode', 'varchar', '', $char_d, - 'locale', 'varchar', '', 16, - 'msg', 'text', '', '', + 'msgnum', 'serial', '', '', '', '', + 'msgcode', 'varchar', '', $char_d, '', '', + 'locale', 'varchar', '', 16, '', '', + 'msg', 'text', '', '', '', '', ], 'primary_key' => 'msgnum', 'unique' => [ [ 'msgcode', 'locale' ] ], @@ -964,23 +1108,41 @@ sub tables_hashref { 'cust_tax_exempt' => { 'columns' => [ - 'exemptnum', 'serial', '', '', - 'custnum', 'int', '', '', - 'taxnum', 'int', '', '', - 'year', 'int', '', '', - 'month', 'int', '', '', - 'amount', @money_type, + 'exemptnum', 'serial', '', '', '', '', + 'custnum', 'int', '', '', '', '', + 'taxnum', 'int', '', '', '', '', + 'year', 'int', '', '', '', '', + 'month', 'int', '', '', '', '', + 'amount', @money_type, '', '', ], 'primary_key' => 'exemptnum', 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ], 'index' => [], }, + 'cust_tax_exempt_pkg' => { + 'columns' => [ + 'exemptpkgnum', 'serial', '', '', '', '', + #'custnum', 'int', '', '', '', '' + 'billpkgnum', 'int', '', '', '', '', + 'taxnum', 'int', '', '', '', '', + 'year', 'int', '', '', '', '', + 'month', 'int', '', '', '', '', + 'amount', @money_type, '', '', + ], + 'primary_key' => 'exemptpkgnum', + 'unique' => [], + 'index' => [ [ 'taxnum', 'year', 'month' ], + [ 'billpkgnum' ], + [ 'taxnum' ] + ], + }, + 'router' => { 'columns' => [ - 'routernum', 'serial', '', '', - 'routername', 'varchar', '', $char_d, - 'svcnum', 'int', 'NULL', '', + 'routernum', 'serial', '', '', '', '', + 'routername', 'varchar', '', $char_d, '', '', + 'svcnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'routernum', 'unique' => [], @@ -989,9 +1151,9 @@ sub tables_hashref { 'part_svc_router' => { 'columns' => [ - 'svcrouternum', 'serial', '', '', - 'svcpart', 'int', '', '', - 'routernum', 'int', '', '', + 'svcrouternum', 'serial', '', '', '', '', + 'svcpart', 'int', '', '', '', '', + 'routernum', 'int', '', '', '', '', ], 'primary_key' => 'svcrouternum', 'unique' => [], @@ -1000,10 +1162,10 @@ sub tables_hashref { 'addr_block' => { 'columns' => [ - 'blocknum', 'serial', '', '', - 'routernum', 'int', '', '', - 'ip_gateway', 'varchar', '', 15, - 'ip_netmask', 'int', '', '', + 'blocknum', 'serial', '', '', '', '', + 'routernum', 'int', '', '', '', '', + 'ip_gateway', 'varchar', '', 15, '', '', + 'ip_netmask', 'int', '', '', '', '', ], 'primary_key' => 'blocknum', 'unique' => [ [ 'blocknum', 'routernum' ] ], @@ -1012,11 +1174,18 @@ sub tables_hashref { 'svc_broadband' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'blocknum', 'int', '', '', - 'speed_up', 'int', '', '', - 'speed_down', 'int', '', '', - 'ip_addr', 'varchar', '', 15, + 'svcnum', 'int', '', '', '', '', + 'description', 'varchar', 'NULL', $char_d, '', '', + 'blocknum', 'int', '', '', '', '', + 'speed_up', 'int', '', '', '', '', + 'speed_down', 'int', '', '', '', '', + 'ip_addr', 'varchar', '', 15, '', '', + 'mac_addr', 'varchar', 'NULL', 12, '', '', + 'authkey', 'varchar', 'NULL', 32, '', '', + 'latitude', 'decimal', 'NULL', '', '', '', + 'longitude', 'decimal', 'NULL', '', '', '', + 'altitude', 'decimal', 'NULL', '', '', '', + 'vlan_profile', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'svcnum', 'unique' => [], @@ -1025,13 +1194,13 @@ sub tables_hashref { 'part_virtual_field' => { 'columns' => [ - 'vfieldpart', 'int', '', '', - 'dbtable', 'varchar', '', 32, - 'name', 'varchar', '', 32, - 'check_block', 'text', 'NULL', '', - 'length', 'int', 'NULL', '', - 'list_source', 'text', 'NULL', '', - 'label', 'varchar', 'NULL', 80, + 'vfieldpart', 'int', '', '', '', '', + 'dbtable', 'varchar', '', 32, '', '', + 'name', 'varchar', '', 32, '', '', + 'check_block', 'text', 'NULL', '', '', '', + 'length', 'int', 'NULL', '', '', '', + 'list_source', 'text', 'NULL', '', '', '', + 'label', 'varchar', 'NULL', 80, '', '', ], 'primary_key' => 'vfieldpart', 'unique' => [], @@ -1040,10 +1209,10 @@ sub tables_hashref { 'virtual_field' => { 'columns' => [ - 'vfieldnum', 'serial', '', '', - 'recnum', 'int', '', '', - 'vfieldpart', 'int', '', '', - 'value', 'varchar', '', 128, + 'vfieldnum', 'serial', '', '', '', '', + 'recnum', 'int', '', '', '', '', + 'vfieldpart', 'int', '', '', '', '', + 'value', 'varchar', '', 128, '', '', ], 'primary_key' => 'vfieldnum', 'unique' => [ [ 'vfieldpart', 'recnum' ] ], @@ -1052,12 +1221,12 @@ sub tables_hashref { 'acct_snarf' => { 'columns' => [ - 'snarfnum', 'int', '', '', - 'svcnum', 'int', '', '', - 'machine', 'varchar', '', 255, - 'protocol', 'varchar', '', $char_d, - 'username', 'varchar', '', $char_d, - '_password', 'varchar', '', $char_d, + 'snarfnum', 'int', '', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'machine', 'varchar', '', 255, '', '', + 'protocol', 'varchar', '', $char_d, '', '', + 'username', 'varchar', '', $char_d, '', '', + '_password', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'snarfnum', 'unique' => [], @@ -1066,9 +1235,9 @@ sub tables_hashref { 'svc_external' => { 'columns' => [ - 'svcnum', 'int', '', '', - 'id', 'int', 'NULL', '', - 'title', 'varchar', 'NULL', $char_d, + 'svcnum', 'int', '', '', '', '', + 'id', 'int', 'NULL', '', '', '', + 'title', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'svcnum', 'unique' => [], @@ -1077,11 +1246,11 @@ sub tables_hashref { 'cust_pay_refund' => { 'columns' => [ - 'payrefundnum', 'serial', '', '', - 'paynum', 'int', '', '', - 'refundnum', 'int', '', '', - '_date', @date_type, - 'amount', @money_type, + 'payrefundnum', 'serial', '', '', '', '', + 'paynum', 'int', '', '', '', '', + 'refundnum', 'int', '', '', '', '', + '_date', @date_type, '', '', + 'amount', @money_type, '', '', ], 'primary_key' => 'payrefundnum', 'unique' => [], @@ -1090,10 +1259,10 @@ sub tables_hashref { 'part_pkg_option' => { 'columns' => [ - 'optionnum', 'serial', '', '', - 'pkgpart', 'int', '', '', - 'optionname', 'varchar', '', $char_d, - 'optionvalue', 'text', 'NULL', '', + 'optionnum', 'serial', '', '', '', '', + 'pkgpart', 'int', '', '', '', '', + 'optionname', 'varchar', '', $char_d, '', '', + 'optionvalue', 'text', 'NULL', '', '', '', ], 'primary_key' => 'optionnum', 'unique' => [], @@ -1102,8 +1271,8 @@ sub tables_hashref { 'rate' => { 'columns' => [ - 'ratenum', 'serial', '', '', - 'ratename', 'varchar', '', $char_d, + 'ratenum', 'serial', '', '', '', '', + 'ratename', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'ratenum', 'unique' => [], @@ -1112,13 +1281,14 @@ sub tables_hashref { 'rate_detail' => { 'columns' => [ - 'ratedetailnum', 'serial', '', '', - 'ratenum', 'int', '', '', - 'orig_regionnum', 'int', 'NULL', '', - 'dest_regionnum', 'int', '', '', - 'min_included', 'int', '', '', - 'min_charge', @money_type, - 'sec_granularity', 'int', '', '', + 'ratedetailnum', 'serial', '', '', '', '', + 'ratenum', 'int', '', '', '', '', + 'orig_regionnum', 'int', 'NULL', '', '', '', + 'dest_regionnum', 'int', '', '', '', '', + 'min_included', 'int', '', '', '', '', + #'min_charge', @money_type, '', '', + 'min_charge', 'decimal', '', '10,5', '', '', + 'sec_granularity', 'int', '', '', '', '', #time period (link to table of periods)? ], 'primary_key' => 'ratedetailnum', @@ -1128,8 +1298,8 @@ sub tables_hashref { 'rate_region' => { 'columns' => [ - 'regionnum', 'serial', '', '', - 'regionname', 'varchar', '', $char_d, + 'regionnum', 'serial', '', '', '', '', + 'regionname', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'regionnum', 'unique' => [], @@ -1138,11 +1308,11 @@ sub tables_hashref { 'rate_prefix' => { 'columns' => [ - 'prefixnum', 'serial', '', '', - 'regionnum', 'int', '', '',, - 'countrycode', 'varchar', '', 3, - 'npa', 'varchar', 'NULL', 6, - 'nxx', 'varchar', 'NULL', 3, + 'prefixnum', 'serial', '', '', '', '', + 'regionnum', 'int', '', '',, '', '', + 'countrycode', 'varchar', '', 3, '', '', + 'npa', 'varchar', 'NULL', 6, '', '', + 'nxx', 'varchar', 'NULL', 3, '', '', ], 'primary_key' => 'prefixnum', 'unique' => [], @@ -1151,9 +1321,9 @@ sub tables_hashref { 'reg_code' => { 'columns' => [ - 'codenum', 'serial', '', '', - 'code', 'varchar', '', $char_d, - 'agentnum', 'int', '', '', + 'codenum', 'serial', '', '', '', '', + 'code', 'varchar', '', $char_d, '', '', + 'agentnum', 'int', '', '', '', '', ], 'primary_key' => 'codenum', 'unique' => [ [ 'agentnum', 'code' ] ], @@ -1162,9 +1332,9 @@ sub tables_hashref { 'reg_code_pkg' => { 'columns' => [ - 'codepkgnum', 'serial', '', '', - 'codenum', 'int', '', '', - 'pkgpart', 'int', '', '', + 'codepkgnum', 'serial', '', '', '', '', + 'codenum', 'int', '', '', '', '', + 'pkgpart', 'int', '', '', '', '', ], 'primary_key' => 'codepkgnum', 'unique' => [ [ 'codenum', 'pkgpart' ] ], @@ -1173,9 +1343,9 @@ sub tables_hashref { 'clientapi_session' => { 'columns' => [ - 'sessionnum', 'serial', '', '', - 'sessionid', 'varchar', '', $char_d, - 'namespace', 'varchar', '', $char_d, + 'sessionnum', 'serial', '', '', '', '', + 'sessionid', 'varchar', '', $char_d, '', '', + 'namespace', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'sessionnum', 'unique' => [ [ 'sessionid', 'namespace' ] ], @@ -1184,10 +1354,10 @@ sub tables_hashref { 'clientapi_session_field' => { 'columns' => [ - 'fieldnum', 'serial', '', '', - 'sessionnum', 'int', '', '', - 'fieldname', 'varchar', '', $char_d, - 'fieldvalue', 'text', 'NULL', '', + 'fieldnum', 'serial', '', '', '', '', + 'sessionnum', 'int', '', '', '', '', + 'fieldname', 'varchar', '', $char_d, '', '', + 'fieldvalue', 'text', 'NULL', '', '', '', ], 'primary_key' => 'fieldnum', 'unique' => [ [ 'sessionnum', 'fieldname' ] ], @@ -1196,12 +1366,12 @@ sub tables_hashref { 'payment_gateway' => { 'columns' => [ - 'gatewaynum', 'serial', '', '', - 'gateway_module', 'varchar', '', $char_d, - 'gateway_username', 'varchar', 'NULL', $char_d, - 'gateway_password', 'varchar', 'NULL', $char_d, - 'gateway_action', 'varchar', 'NULL', $char_d, - 'disabled', 'char', 'NULL', 1, + 'gatewaynum', 'serial', '', '', '', '', + 'gateway_module', 'varchar', '', $char_d, '', '', + 'gateway_username', 'varchar', 'NULL', $char_d, '', '', + 'gateway_password', 'varchar', 'NULL', $char_d, '', '', + 'gateway_action', 'varchar', 'NULL', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'gatewaynum', 'unique' => [], @@ -1210,10 +1380,10 @@ sub tables_hashref { 'payment_gateway_option' => { 'columns' => [ - 'optionnum', 'serial', '', '', - 'gatewaynum', 'int', '', '', - 'optionname', 'varchar', '', $char_d, - 'optionvalue', 'text', 'NULL', '', + 'optionnum', 'serial', '', '', '', '', + 'gatewaynum', 'int', '', '', '', '', + 'optionname', 'varchar', '', $char_d, '', '', + 'optionvalue', 'text', 'NULL', '', '', '', ], 'primary_key' => 'optionnum', 'unique' => [], @@ -1222,11 +1392,11 @@ sub tables_hashref { 'agent_payment_gateway' => { 'columns' => [ - 'agentgatewaynum', 'serial', '', '', - 'agentnum', 'int', '', '', - 'gatewaynum', 'int', '', '', - 'cardtype', 'varchar', 'NULL', $char_d, - 'taxclass', 'varchar', 'NULL', $char_d, + 'agentgatewaynum', 'serial', '', '', '', '', + 'agentnum', 'int', '', '', '', '', + 'gatewaynum', 'int', '', '', '', '', + 'cardtype', 'varchar', 'NULL', $char_d, '', '', + 'taxclass', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'agentgatewaynum', 'unique' => [], @@ -1235,30 +1405,298 @@ sub tables_hashref { 'banned_pay' => { 'columns' => [ - 'bannum', 'serial', '', '', - 'payby', 'char', '', 4, - 'payinfo', 'varchar', '', 128, #say, a 512-big digest _hex encoded - #'paymask', 'varchar', 'NULL', $char_d, - '_date', @date_type, - 'otaker', 'varchar', '', 32, - 'reason', 'varchar', 'NULL', $char_d, + 'bannum', 'serial', '', '', '', '', + 'payby', 'char', '', 4, '', '', + 'payinfo', 'varchar', '', 128, '', '', #say, a 512-big digest _hex encoded + #'paymask', 'varchar', 'NULL', $char_d, '', '' + '_date', @date_type, '', '', + 'otaker', 'varchar', '', 32, '', '', + 'reason', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'bannum', 'unique' => [ [ 'payby', 'payinfo' ] ], 'index' => [], }, - 'cancel_reason' => { + 'pkg_class' => { + 'columns' => [ + 'classnum', 'serial', '', '', '', '', + 'classname', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + ], + 'primary_key' => 'classnum', + 'unique' => [], + 'index' => [ ['disabled'] ], + }, + + 'cdr' => { + 'columns' => [ + # qw( name type null length default local ); + + ### + #asterisk fields + ### + + 'acctid', 'bigserial', '', '', '', '', + 'calldate', 'TIMESTAMP with time zone', '', '', \'now()', '', + 'clid', 'varchar', '', $char_d, \"''", '', + 'src', 'varchar', '', $char_d, \"''", '', + 'dst', 'varchar', '', $char_d, \"''", '', + 'dcontext', 'varchar', '', $char_d, \"''", '', + 'channel', 'varchar', '', $char_d, \"''", '', + 'dstchannel', 'varchar', '', $char_d, \"''", '', + 'lastapp', 'varchar', '', $char_d, \"''", '', + 'lastdata', 'varchar', '', $char_d, \"''", '', + + #these don't seem to be logged by most of the SQL cdr_* modules + #except tds under sql-illegal names, so; + # ... don't rely on them for rating? + # and, what they hey, i went ahead and changed the names and data types + # to freeside-style dates... + #'start', 'timestamp', 'NULL', '', '', '', + #'answer', 'timestamp', 'NULL', '', '', '', + #'end', 'timestamp', 'NULL', '', '', '', + 'startdate', @date_type, '', '', + 'answerdate', @date_type, '', '', + 'enddate', @date_type, '', '', + # + + 'duration', 'int', '', '', 0, '', + 'billsec', 'int', '', '', 0, '', + 'disposition', 'varchar', '', 45, \"''", '', + 'amaflags', 'int', '', '', 0, '', + 'accountcode', 'varchar', '', 20, \"''", '', + 'uniqueid', 'varchar', '', 32, \"''", '', + 'userfield', 'varchar', '', 255, \"''", '', + + ### + # fields for unitel/RSLCOM/convergent that don't map well to asterisk + # defaults + ### + + #cdr_type: Usage = 1, S&E = 7, OC&C = 8 + 'cdrtypenum', 'int', 'NULL', '', '', '', + + 'charged_party', 'varchar', 'NULL', $char_d, '', '', + + 'upstream_currency', 'char', 'NULL', 3, '', '', + 'upstream_price', 'decimal', 'NULL', '10,2', '', '', + 'upstream_rateplanid', 'int', 'NULL', '', '', '', #? + + # how it was rated internally... + 'ratedetailnum', 'int', 'NULL', '', '', '', + 'rated_price', 'decimal', 'NULL', '10,2', '', '', + + 'distance', 'decimal', 'NULL', '', '', '', + 'islocal', 'int', 'NULL', '', '', '', # '', '', 0, '' instead? + + #cdr_calltype: the big list in appendix 2 + 'calltypenum', 'int', 'NULL', '', '', '', + + 'description', 'varchar', 'NULL', $char_d, '', '', + 'quantity', 'int', 'NULL', '', '', '', + + #cdr_carrier: Telstra =1, Optus = 2, RSL COM = 3 + 'carrierid', 'int', 'NULL', '', '', '', + + 'upstream_rateid', 'int', 'NULL', '', '', '', + + ### + #and now for our own fields + ### + + # a svcnum... right..? + 'svcnum', 'int', 'NULL', '', '', '', + + #NULL, done (or something) + 'freesidestatus', 'varchar', 'NULL', 32, '', '', + + ], + 'primary_key' => 'acctid', + 'unique' => [], + 'index' => [ [ 'calldate' ], [ 'dst' ], [ 'accountcode' ], [ 'freesidestatus' ] ], + }, + + 'cdr_calltype' => { + 'columns' => [ + 'calltypenum', 'serial', '', '', '', '', + 'calltypename', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'calltypenum', + 'unique' => [], + 'index' => [], + }, + + 'cdr_type' => { + 'columns' => [ + 'cdrtypenum' => 'serial', '', '', '', '', + 'cdrtypename' => 'varchar', '', '', '', '', + ], + 'primary_key' => 'cdrtypenum', + 'unique' => [], + 'index' => [], + }, + + 'cdr_carrier' => { + 'columns' => [ + 'carrierid' => 'serial', '', '', '', '', + 'carriername' => 'varchar', '', '', '', '', + ], + 'primary_key' => 'carrierid', + 'unique' => [], + 'index' => [], + }, + + #map upstream rateid to ours... + 'cdr_upstream_rate' => { + 'columns' => [ + 'upstreamratenum', 'serial', '', '', '', '', + 'upstream_rateid', 'varchar', '', $char_d, '', '', + 'ratedetailnum', 'int', 'NULL', '', '', '', + ], + 'primary_key' => 'upstreamratenum', #XXX need a primary key + 'unique' => [ [ 'upstream_rateid' ] ], #unless we add another field, yeah + 'index' => [], + }, + + 'inventory_item' => { + 'columns' => [ + 'itemnum', 'serial', '', '', '', '', + 'classnum', 'int', '', '', '', '', + 'item', 'varchar', '', $char_d, '', '', + 'svcnum', 'int', 'NULL', '', '', '', + ], + 'primary_key' => 'itemnum', + 'unique' => [ [ 'classnum', 'item' ] ], + 'index' => [ [ 'classnum' ], [ 'svcnum' ] ], + }, + + 'inventory_class' => { + 'columns' => [ + '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, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + ], + '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' => [], + }, + + 'svc_phone' => { 'columns' => [ - 'reasonnum', 'serial', '', '', - 'reason', 'varchar', '', $char_d, - 'disabled', 'char', 'NULL', 1, + 'svcnum', 'int', '', '', '', '', + 'countrycode', 'varchar', '', 3, '', '', + 'phonenum', 'varchar', '', 15, '', '', #12 ? + 'pin', 'varchar', 'NULL', $char_d, '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [], + 'index' => [ [ 'countrycode', 'phonenum' ] ], + }, + + 'reason_type' => { + 'columns' => [ + 'typenum', 'serial', '', '', '', '', + 'class', 'char', '', 1, '', '', + 'type', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'typenum', + 'unique' => [], + 'index' => [], + }, + + 'reason' => { + 'columns' => [ + 'reasonnum', 'serial', '', '', '', '', + 'reason_type', 'int', '', '', '', '', + 'reason', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'reasonnum', 'unique' => [], - 'index' => [ [ 'disabled' ] ], + 'index' => [], }, + # name type nullability length default local + + #'new_table' => { + # 'columns' => [ + # 'num', 'serial', '', '', '', '', + # ], + # 'primary_key' => 'num', + # 'unique' => [], + # 'index' => [], + #}, + }; } diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm new file mode 100644 index 000000000..4864cfea8 --- /dev/null +++ b/FS/FS/Setup.pm @@ -0,0 +1,492 @@ +package FS::Setup; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +#use Tie::DxHash; +use Tie::IxHash; +use FS::UID qw( dbh ); +use FS::Record; + +use FS::svc_domain; +$FS::svc_domain::whois_hack = 1; +$FS::svc_domain::whois_hack = 1; + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( create_initial_data ); + +=head1 NAME + +FS::Setup - Database setup + +=head1 SYNOPSIS + + use FS::Setup; + +=head1 DESCRIPTION + +Currently this module simply provides a place to store common subroutines for +database setup. + +=head1 SUBROUTINES + +=over 4 + +=item + +=cut + +sub create_initial_data { + my %opt = @_; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + $FS::UID::AutoCommit = 0; + + populate_locales(); + + #initial_data data + populate_initial_data(%opt); + + populate_access(); + + populate_msgcat(); + + if ( $oldAutoCommit ) { + dbh->commit or die dbh->errstr; + } + +} + +sub populate_locales { + + use Locale::Country; + use FS::cust_main_county; + + #cust_main_county + foreach my $country ( sort map uc($_), all_country_codes ) { + _add_country($country); + } + +} + +sub populate_addl_locales { + + my %addl = ( + 'US' => { + 'FM' => 'Federated States of Micronesia', + 'MH' => 'Marshall Islands', + 'PW' => 'Palau', + 'AA' => "Armed Forces Americas (except Canada)", + 'AE' => "Armed Forces Europe / Canada / Middle East / Africa", + 'AP' => "Armed Forces Pacific", + }, + ); + + foreach my $country ( keys %addl ) { + foreach my $state ( keys %{ $addl{$country} } ) { + # $longname = $addl{$country}{$state}; + _add_locale( 'country'=>$country, 'state'=>$state); + } + } + +} + +sub _add_country { + + use Locale::SubCountry; + + my( $country ) = shift; + + my $subcountry = eval { new Locale::SubCountry($country) }; + my @states = $subcountry ? $subcountry->all_codes : undef; + + if ( !scalar(@states) || ( scalar(@states)==1 && !defined($states[0]) ) ) { + + _add_locale( 'country'=>$country ); + + } else { + + if ( $states[0] =~ /^(\d+|\w)$/ ) { + @states = map $subcountry->full_name($_), @states + } + + foreach my $state ( @states ) { + _add_locale( 'country'=>$country, 'state'=>$state); + } + + } + +} + +sub _add_locale { + my $cust_main_county = new FS::cust_main_county( { 'tax'=>0, @_ }); + my $error = $cust_main_county->insert; + die $error if $error; +} + +sub populate_initial_data { + my %opt = @_; + + my $data = initial_data(%opt); + + foreach my $table ( keys %$data ) { + + my $class = "FS::$table"; + eval "use $class;"; + die $@ if $@; + + my @records = @{ $data->{$table} }; + + foreach my $record ( @records ) { + my $args = delete($record->{'_insert_args'}) || []; + my $object = $class->new( $record ); + my $error = $object->insert( @$args ); + die "error inserting record into $table: $error\n" + if $error; + } + + } + +} + +sub initial_data { + my %opt = @_; + + #tie my %hash, 'Tie::DxHash', + tie my %hash, 'Tie::IxHash', + + #superuser group + 'access_group' => [ + { 'groupname' => 'Superuser' }, + ], + + #billing events + 'part_bill_event' => [ + { 'payby' => 'CARD', + 'event' => 'Batch card', + 'seconds' => 0, + 'eventcode' => '$cust_bill->batch_card(%options);', + 'weight' => 40, + 'plan' => 'batch-card', + }, + { 'payby' => 'BILL', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + { 'payby' => 'DCRD', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + { 'payby' => 'DCHK', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + { 'payby' => 'DCLN', + 'event' => 'Suspend', + 'seconds' => 0, + 'eventcode' => '$cust_bill->suspend();', + 'weight' => 40, + 'plan' => 'suspend', + }, + #{ 'payby' => 'DCLN', + # 'event' => 'Retriable', + # 'seconds' => 0, + # 'eventcode' => '$cust_bill_event->retriable();', + # 'weight' => 60, + # 'plan' => 'retriable', + #}, + ], + + #you must create a service definition. An example of a service definition + #would be a dial-up account or a domain. First, it is necessary to create a + #domain definition. Click on View/Edit service definitions and Add a new + #service definition with Table svc_domain (and no modifiers). + 'part_svc' => [ + { 'svc' => 'Domain', + 'svcdb' => 'svc_domain', + } + ], + + #Now that you have created your first service, you must create a package + #including this service which you can sell to customers. Zero, one, or many + #services are bundled into a package. Click on View/Edit package + #definitions and Add a new package definition which includes quantity 1 of + #the svc_domain service you created above. + 'part_pkg' => [ + { 'pkg' => 'System Domain', + 'comment' => '(NOT FOR CUSTOMERS)', + 'freq' => '0', + 'plan' => 'flat', + '_insert_args' => [ + 'pkg_svc' => { 1 => 1 }, # XXX + 'primary_svc' => 1, #XXX + 'options' => { + 'setup_fee' => '0', + 'recur_fee' => '0', + }, + ], + }, + ], + + #After you create your first package, then you must define who is able to + #sell that package by creating an agent type. An example of an agent type + #would be an internal sales representitive which sells regular and + #promotional packages, as opposed to an external sales representitive + #which would only sell regular packages of services. Click on View/Edit + #agent types and Add a new agent type. + 'agent_type' => [ + { 'atype' => 'internal' }, + ], + + #Allow this agent type to sell the package you created above. + 'type_pkgs' => [ + { 'typenum' => 1, #XXX + 'pkgpart' => 1, #XXX + }, + ], + + #After creating a new agent type, you must create an agent. Click on + #View/Edit agents and Add a new agent. + 'agent' => [ + { 'agent' => 'Internal', + 'typenum' => 1, # XXX + }, + ], + + #Set up at least one Advertising source. Advertising sources will help you + #keep track of how effective your advertising is, tracking where customers + #heard of your service offerings. You must create at least one advertising + #source. If you do not wish to use the referral functionality, simply + #create a single advertising source only. Click on View/Edit advertising + #sources and Add a new advertising source. + 'part_referral' => [ + { 'referral' => 'Internal', }, + ], + + #Click on New Customer and create a new customer for your system accounts + #with billing type Complimentary. Leave the First package dropdown set to + #(none). + 'cust_main' => [ + { 'agentnum' => 1, #XXX + 'refnum' => 1, #XXX + 'first' => 'System', + 'last' => 'Accounts', + 'address1' => '1234 System Lane', + 'city' => 'Systemtown', + 'state' => 'CA', + 'zip' => '54321', + 'country' => 'US', + 'payby' => 'COMP', + 'payinfo' => 'system', #or something + 'paydate' => '1/2037', + }, + ], + + #From the Customer View screen of the newly created customer, order the + #package you defined above. + 'cust_pkg' => [ + { 'custnum' => 1, #XXX + 'pkgpart' => 1, #XXX + }, + ], + + #From the Package View screen of the newly created package, choose + #(Provision) to add the customer's service for this new package. + #Add your own domain. + 'svc_domain' => [ + { 'domain' => $opt{'domain'}, + 'pkgnum' => 1, #XXX + 'svcpart' => 1, #XXX + 'action' => 'N', #pseudo-field + }, + ], + + #Go back to View/Edit service definitions on the main menu, and Add a new + #service definition with Table svc_acct. Select your domain in the domsvc + #Modifier. Set Fixed to define a service locked-in to this domain, or + #Default to define a service which may select from among this domain and + #the customer's domains. + + #not yet.... + + #) + ; + + \%hash; + +} + +sub populate_access { + + use FS::AccessRight; + use FS::access_right; + + foreach my $rightname ( FS::AccessRight->rights ) { + my $access_right = new FS::access_right { + 'righttype' => 'FS::access_group', + 'rightobjnum' => 1, #$supergroup->groupnum, + 'rightname' => $rightname, + }; + my $ar_error = $access_right->insert; + die $ar_error if $ar_error; + } + + #foreach my $agent ( qsearch('agent', {} ) ) { + my $access_groupagent = new FS::access_groupagent { + 'groupnum' => 1, #$supergroup->groupnum, + 'agentnum' => 1, #$agent->agentnum, + }; + my $aga_error = $access_groupagent->insert; + die $aga_error if $aga_error; + #} + +} + +sub populate_msgcat { + + use FS::Record qw(qsearch); + use FS::msgcat; + + foreach my $del_msgcat ( qsearch('msgcat', {}) ) { + my $error = $del_msgcat->delete; + die $error if $error; + } + + my %messages = msgcat_messages(); + + foreach my $msgcode ( keys %messages ) { + foreach my $locale ( keys %{$messages{$msgcode}} ) { + my $msgcat = new FS::msgcat( { + 'msgcode' => $msgcode, + 'locale' => $locale, + 'msg' => $messages{$msgcode}{$locale}, + }); + my $error = $msgcat->insert; + die $error if $error; + } + } + +} + +sub msgcat_messages { + + # 'msgcode' => { + # 'en_US' => 'Message', + # }, + + ( + + 'passwords_dont_match' => { + 'en_US' => "Passwords don't match", + }, + + 'invalid_card' => { + 'en_US' => 'Invalid credit card number', + }, + + 'unknown_card_type' => { + 'en_US' => 'Unknown card type', + }, + + 'not_a' => { + 'en_US' => 'Not a ', + }, + + 'empty_password' => { + 'en_US' => 'Empty password', + }, + + 'no_access_number_selected' => { + 'en_US' => 'No access number selected', + }, + + 'illegal_text' => { + 'en_US' => 'Illegal (text)', + #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field', + }, + + 'illegal_or_empty_text' => { + 'en_US' => 'Illegal or empty (text)', + #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field', + }, + + 'illegal_username' => { + 'en_US' => 'Illegal username', + }, + + 'illegal_password' => { + 'en_US' => 'Illegal password (', + }, + + 'illegal_password_characters' => { + 'en_US' => ' characters)', + }, + + 'username_in_use' => { + 'en_US' => 'Username in use', + }, + + 'illegal_email_invoice_address' => { + 'en_US' => 'Illegal email invoice address', + }, + + 'illegal_name' => { + 'en_US' => 'Illegal (name)', + #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field', + }, + + 'illegal_phone' => { + 'en_US' => 'Illegal (phone)', + #'en_US' => '', + }, + + 'illegal_zip' => { + 'en_US' => 'Illegal (zip)', + #'en_US' => '', + }, + + 'expired_card' => { + 'en_US' => 'Expired card', + }, + + 'daytime' => { + 'en_US' => 'Day Phone', + }, + + 'night' => { + 'en_US' => 'Night Phone', + }, + + 'svc_external-id' => { + 'en_US' => 'External ID', + }, + + 'svc_external-title' => { + 'en_US' => 'Title', + }, + + ); +} + +=back + +=head1 BUGS + +Sure. + +=head1 SEE ALSO + +=cut + +1; + diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm index d951cc0e7..3a0d6f0a5 100644 --- a/FS/FS/TicketSystem/RT_External.pm +++ b/FS/FS/TicketSystem/RT_External.pm @@ -10,7 +10,7 @@ use FS::Record qw(qsearchs); use FS::cust_main; FS::UID->install_callback( sub { - my $conf = new FS::Conf; + $conf = new FS::Conf; $default_queueid = $conf->config('ticket_system-default_queueid'); $priority_field = $conf->config('ticket_system-custom_priority_field'); @@ -55,9 +55,11 @@ sub customer_tickets { $limit ||= 0; my( $from_sql, @param) = $self->_from_customer( $custnum, $priority ); - my $sql = "SELECT tickets.*, queues.name". - ( length($priority) ? ", objectcustomfieldvalues.content" : '' ). - " $from_sql ORDER BY priority DESC LIMIT $limit"; + my $sql="SELECT tickets.*, queues.name, ". + "position(tickets.status in 'newopenstalledresolvedrejecteddeleted')". + " AS svalue " . + ( length($priority) ? ", objectcustomfieldvalues.content" : '' ). + " $from_sql ORDER BY svalue, priority DESC, id DESC LIMIT $limit"; my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql"; $sth->execute(@param) or die $sth->errstr. "executing $sql"; @@ -109,6 +111,7 @@ sub _from_customer { ON ( tickets.id = ObjectCustomFieldValues.ObjectId )"; $where = " AND content = ? + AND ObjectCustomFieldValues.disabled != 1 AND ObjectType = 'RT::Ticket' AND $customfield_sql"; @@ -130,7 +133,7 @@ sub _from_customer { JOIN queues ON ( tickets.queue = queues.id ) JOIN links ON ( tickets.id = links.localbase ) $join - WHERE ( status = 'new' OR status = 'open' OR status = 'stalled' ) + WHERE ( ". join(' OR ', map "status = '$_'", $self->statuses ). " ) AND target = 'freeside://freeside/cust_main/$custnum' $where "; @@ -139,31 +142,44 @@ sub _from_customer { } +sub statuses { + #my $self = shift; + my @statuses = grep { ! /^\s*$/ } $conf->config('cust_main-ticket_statuses'); + @statuses = (qw( new open stalled )) unless scalar(@statuses); + @statuses; +} + sub href_customer_tickets { my( $self, $custnum, $priority ) = @_; - my $href = $self->baseurl; + #my $href = $self->baseurl; - #i snarfed this from an RT bookmarked search, it could be unescaped in the - #source for readability and run through uri_escape - $href .= - 'Search/Results.html?Order=ASC&Query=%20MemberOf%20%3D%20%27freeside%3A%2F%2Ffreeside%2Fcust_main%2F'. - $custnum. - '%27%20%20AND%20%28%20Status%20%3D%20%27open%27%20%20OR%20Status%20%3D%20%27new%27%20%20OR%20Status%20%3D%20%27stalled%27%20%29%20' + #i snarfed this from an RT bookmarked search, then unescaped (some of) it with + #perl -npe 's/%([0-9A-F]{2})/pack('C', hex($1))/eg;' + + my $href .= + "Search/Results.html?Order=ASC&". + "Query= MemberOf = 'freeside://freeside/cust_main/$custnum' ". + #" AND ( Status = 'open' OR Status = 'new' OR Status = 'stalled' )" + " AND ( ". join(' OR ', map "Status = '$_'", $self->statuses ). " ) " ; if ( defined($priority) && $field && $priority_field_queue ) { - $href .= 'AND%20Queue%20%3D%20%27'. $priority_field_queue. '%27%20'; + $href .= " AND Queue = '$priority_field_queue' "; } if ( defined($priority) && $field ) { - $href .= '%20AND%20%27CF.'. $field. '%27%20'; + $href .= " AND 'CF.$field' "; if ( $priority ) { - $href .= '%3D%20%27'. $priority. '%27%20'; + $href .= "= '$priority' "; } else { - $href .= 'IS%20%27NULL%27%20'; + $href .= "IS 'NULL' "; #this is "RTQL", not SQL } } + #$href = + uri_escape($href); + #eventually should unescape all of it... + $href .= '&Rows=100'. '&OrderBy=id&Page=1'. '&Format=%27%20%20%20%3Cb%3E%3Ca%20href%3D%22'. @@ -184,7 +200,10 @@ sub href_customer_tickets { $href .= '%20%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27'; - $href; + #$href = + #uri_escape($href); + + $self->baseurl. $href; } diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 49e3fbf7e..9ddcf142d 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -1,6 +1,7 @@ package FS::UI::Web; -use vars qw($DEBUG); +use strict; +use vars qw($DEBUG $me); use FS::Conf; use FS::Record qw(dbdef); @@ -8,21 +9,29 @@ use FS::Record qw(dbdef); #use FS::UI #@ISA = qw( FS::UI ); +$DEBUG = 0; +$me = '[FS::UID::Web]'; + +### +# date parsing +### + use Date::Parse; sub parse_beginning_ending { - my($cgi) = @_; + my($cgi, $prefix) = @_; + $prefix .= '_' if $prefix; my $beginning = 0; - if ( $cgi->param('begin') =~ /^(\d+)$/ ) { + if ( $cgi->param($prefix.'begin') =~ /^(\d+)$/ ) { $beginning = $1; - } elsif ( $cgi->param('beginning') =~ /^([ 0-9\-\/]{1,64})$/ ) { + } elsif ( $cgi->param($prefix.'beginning') =~ /^([ 0-9\-\/]{1,64})$/ ) { $beginning = str2time($1) || 0; } my $ending = 4294967295; #2^32-1 - if ( $cgi->param('end') =~ /^(\d+)$/ ) { + if ( $cgi->param($prefix.'end') =~ /^(\d+)$/ ) { $ending = $1 - 1; - } elsif ( $cgi->param('ending') =~ /^([ 0-9\-\/]{1,64})$/ ) { + } elsif ( $cgi->param($prefix.'ending') =~ /^([ 0-9\-\/]{1,64})$/ ) { #probably need an option to turn off the + 86399 $ending = str2time($1) + 86399; } @@ -30,84 +39,295 @@ sub parse_beginning_ending { ( $beginning, $ending ); } -### -# cust_main report methods -### +=item svc_url + +Returns a service URL, first checking to see if there is a service-specific +page to link to, otherwise to a generic service handling page. Options are +passed as a list of name-value pairs, and include: + +=over 4 + +=item * m - Mason request object ($m) + +=item * action - The action for which to construct "edit", "view", or "search" + +=item ** part_svc - Service definition (see L<FS::part_svc>) + +=item ** svcdb - Service table + +=item *** query - Query string + +=item *** svc - FS::cust_svc or FS::svc_* object -=item cust_header +=item ahref - Optional flag, if set true returns <A HREF="$url"> instead of just the URL. -Returns an array of customer information headers according to the -B<cust-fields> configuration setting. +=back + +* Required fields + +** part_svc OR svcdb is required + +*** query OR svc is required =cut -use vars qw( @cust_fields ); + # ## + # #required + # ## + # 'm' => $m, #mason request object + # 'action' => 'edit', #or 'view' + # + # 'part_svc' => $part_svc, #usual + # #OR + # 'svcdb' => 'svc_table', + # + # 'query' => #optional query string + # # (pass a blank string if you want a "raw" URL to add your + # # own svcnum to) + # #OR + # 'svc' => $svc_x, #or $cust_svc, it just needs a svcnum + # + # ## + # #optional + # ## + # 'ahref' => 1, # if set true, returns <A HREF="$url"> + +use FS::CGI qw(rooturl); +sub svc_url { + my %opt = @_; + + #? return '' unless ref($opt{part_svc}); + + my $svcdb = $opt{svcdb} || $opt{part_svc}->svcdb; + my $query = exists($opt{query}) ? $opt{query} : $opt{svc}->svcnum; + my $url; + warn "$me [svc_url] checking for /$opt{action}/$svcdb.cgi component" + if $DEBUG; + if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) { + $url = "$svcdb.cgi?"; + } else { -sub cust_sql_fields { - my @fields = qw( last first company ); - push @fields, map "ship_$_", @fields - if dbdef->table('cust_main')->column('ship_last'); - map "cust_main.$_", @fields; + my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common'; + + $url = "$generic.html?svcdb=$svcdb;"; + $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq ''; + } + + my $return = rooturl(). "$opt{action}/$url$query"; + + $return = qq!<A HREF="$return">! if $opt{ahref}; + + $return; +} + +sub svc_link { + my($m, $part_svc, $cust_svc) = @_ or return ''; + svc_X_link( $part_svc->svc, @_ ); } +sub svc_label_link { + my($m, $part_svc, $cust_svc) = @_ or return ''; + svc_X_link( ($cust_svc->label)[1], @_ ); +} + +sub svc_X_link { + my ($x, $m, $part_svc, $cust_svc) = @_ or return ''; + my $ahref = svc_url( + 'ahref' => 1, + 'm' => $m, + 'action' => 'view', + 'part_svc' => $part_svc, + 'svc' => $cust_svc, + ); + + "$ahref$x</A>"; +} + +sub parse_lt_gt { + my($cgi, $field) = @_; + + my @search = (); + + my %op = ( + 'lt' => '<', + 'gt' => '>', + ); + + foreach my $op (keys %op) { + + warn "checking for ${field}_$op field\n" + if $DEBUG; + + if ( $cgi->param($field."_$op") =~ /^\s*\$?\s*([\d\,\s]+(\.\d\d)?)\s*$/ ) { + + my $num = $1; + $num =~ s/[\,\s]+//g; + my $search = "$field $op{$op} $num"; + push @search, $search; + + warn "found ${field}_$op field; adding search element $search\n" + if $DEBUG; + } + + } + + @search; + +} + +sub bytecount_unexact { + my $bc = shift; + return("$bc bytes") + if ($bc < 1000); + return(sprintf("%.2f Kbytes", $bc/1000)) + if ($bc < 1000000); + return(sprintf("%.2f Mbytes", $bc/1000000)) + if ($bc < 1000000000); + return(sprintf("%.2f Gbytes", $bc/1000000000)); +} + +### +# cust_main report subroutines +### + + +=item cust_header [ CUST_FIELDS_VALUE ] + +Returns an array of customer information headers according to the supplied +customer fields value, or if no value is supplied, the B<cust-fields> +configuration value. + +=cut + +use vars qw( @cust_fields @cust_colors @cust_styles @cust_aligns ); + sub cust_header { - warn "FS::svc_Common::cust_header called" + warn "FS::UI:Web::cust_header called" if $DEBUG; - my $conf = new FS::Conf; - my %header2method = ( - 'Customer' => 'name', - 'Cust#' => 'custnum', - 'Name' => 'contact', - 'Company' => 'company', - '(bill) Customer' => 'name', - '(service) Customer' => 'ship_name', - '(bill) Name' => 'contact', - '(service) Name' => 'ship_contact', - '(bill) Company' => 'company', - '(service) Company' => 'ship_company', + 'Customer' => 'name', + 'Cust. Status' => 'ucfirst_cust_status', + 'Cust#' => 'custnum', + 'Name' => 'contact', + 'Company' => 'company', + '(bill) Customer' => 'name', + '(service) Customer' => 'ship_name', + '(bill) Name' => 'contact', + '(service) Name' => 'ship_contact', + '(bill) Company' => 'company', + '(service) Company' => 'ship_company', + 'Address 1' => 'address1', + 'Address 2' => 'address2', + 'City' => 'city', + 'State' => 'state', + 'Zip' => 'zip', + 'Country' => 'country_full', + 'Day phone' => 'daytime', # XXX should use msgcat, but how? + 'Night phone' => 'night', # XXX should use msgcat, but how? + 'Invoicing email(s)' => 'invoicing_list_emailonly_scalar', + ); + + my %header2colormethod = ( + 'Cust. Status' => 'cust_statuscolor', + ); + my %header2style = ( + 'Cust. Status' => 'b', + ); + my %header2align = ( + 'Cust. Status' => 'c', ); + my $cust_fields; my @cust_header; - if ( $conf->exists('cust-fields') - && $conf->config('cust-fields') =~ /^([\w \|\#\(\)]+):/ - ) - { - warn " found cust-fields configuration value" - if $DEBUG; + if ( @_ && $_[0] ) { - my $cust_fields = $1; - @cust_header = split(/ \| /, $cust_fields); - @cust_fields = map { $header2method{$_} } @cust_header; - } else { - warn " no cust-fields configuration value found; using default 'Customer'" + warn " using supplied cust-fields override". + " (ignoring cust-fields config file)" if $DEBUG; - @cust_header = ( 'Customer' ); - @cust_fields = ( 'name' ); + $cust_fields = shift; + + } else { + + my $conf = new FS::Conf; + if ( $conf->exists('cust-fields') + && $conf->config('cust-fields') =~ /^([\w\. \|\#\(\)]+):?/ + ) + { + warn " found cust-fields configuration value" + if $DEBUG; + $cust_fields = $1; + } else { + warn " no cust-fields configuration value found; using default 'Cust. Status | Customer'" + if $DEBUG; + $cust_fields = 'Cust. Status | Customer'; + } + } + @cust_header = split(/ \| /, $cust_fields); + @cust_fields = map { $header2method{$_} } @cust_header; + @cust_colors = map { exists $header2colormethod{$_} + ? $header2colormethod{$_} + : '' + } + @cust_header; + @cust_styles = map { exists $header2style{$_} ? $header2style{$_} : '' } + @cust_header; + @cust_aligns = map { exists $header2align{$_} ? $header2align{$_} : 'l' } + @cust_header; + #my $svc_x = shift; @cust_header; } -=item cust_fields +=item cust_sql_fields [ CUST_FIELDS_VALUE ] + +Returns a list of fields for the SELECT portion of an SQL query. + +As with L<the cust_header subroutine|/cust_header>, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the <B>cust-fields</B> configuration value. + +=cut + +sub cust_sql_fields { + + my @fields = qw( last first company ); + push @fields, map "ship_$_", @fields; + push @fields, 'country'; + + cust_header(@_); + #inefficientish, but tiny lists and only run once per page + push @fields, + grep { my $field = $_; grep { $_ eq $field } @cust_fields } + qw( address1 address2 city state zip daytime night ); + + map "cust_main.$_", @fields; +} + +=item cust_fields OBJECT [ CUST_FIELDS_VALUE ] -Given a svc_ object that contains fields from cust_main (say, from a +Given an object that contains fields from cust_main (say, from a JOINed search. See httemplate/search/svc_* for examples), returns an array -of customer information according to the <B>cust-fields</B> configuration -setting, or "(unlinked)" if this service is not linked to a customer. +of customer information, or "(unlinked)" if this service is not linked to a +customer. + +As with L<the cust_header subroutine|/cust_header>, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the <B>cust-fields</B> configuration value. =cut sub cust_fields { my $svc_x = shift; - warn "FS::svc_Common::cust_fields called for $svc_x ". + warn "FS::UI::Web::cust_fields called for $svc_x ". "(cust_fields: @cust_fields)" if $DEBUG > 1; - cust_header() unless @cust_fields; + #cust_header(@_) unless @cust_fields; #now need to cache to keep cust_fields + # #override incase we were passed as a sub my $seen_unlinked = 0; map { @@ -123,6 +343,67 @@ sub cust_fields { } @cust_fields; } +=item cust_colors + +Returns an array of subroutine references (or empty strings) for returning +customer information colors. + +As with L<the cust_header subroutine|/cust_header>, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the <B>cust-fields</B> configuration value. + +=cut + +sub cust_colors { + map { + my $method = $_; + if ( $method ) { + sub { shift->$method(@_) }; + } else { + ''; + } + } @cust_colors; +} + +=item cust_styles + +Returns an array of customer information styles. + +As with L<the cust_header subroutine|/cust_header>, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the <B>cust-fields</B> configuration value. + +=cut + +sub cust_styles { + map { + if ( $_ ) { + $_; + } else { + ''; + } + } @cust_styles; +} + +=item cust_aligns + +Returns an array or scalar (depending on context) of customer information +alignments. + +As with L<the cust_header subroutine|/cust_header>, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the <B>cust-fields</B> configuration value. + +=cut + +sub cust_aligns { + if ( wantarray ) { + @cust_aligns; + } else { + join('', @cust_aligns); + } +} + ### # begin JSRPC code... ### @@ -131,6 +412,7 @@ package FS::UI::Web::JSRPC; use strict; use vars qw($DEBUG); +use Carp; use Storable qw(nfreeze); use MIME::Base64; use JSON; @@ -150,7 +432,7 @@ sub new { bless $self, $class; - die "CGI object required as second argument" unless $self->{'cgi'}; + croak "CGI object required as second argument" unless $self->{'cgi'}; return $self; } @@ -183,6 +465,10 @@ sub process { $self->job_status(@args); + } else { + + die "unknown sub $sub"; + } } @@ -227,11 +513,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; } @@ -252,7 +546,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/UID.pm b/FS/FS/UID.pm index c0c9f7af4..8dd928ec7 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -10,9 +10,10 @@ use subs qw( getsecrets cgisetotaker ); use Exporter; -use Carp qw(carp croak cluck); +use Carp qw(carp croak cluck confess); use DBI; use FS::Conf; +use FS::CurrentUser; @ISA = qw(Exporter); @EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup @@ -20,7 +21,7 @@ use FS::Conf; $freeside_uid = scalar(getpwnam('freeside')); -$conf_dir = "/usr/local/etc/freeside/"; +$conf_dir = "%%%FREESIDE_CONF%%%/"; $AutoCommit = 1; #ours, not DBI @@ -71,10 +72,16 @@ sub adminsuidsetup { sub forksuidsetup { $user = shift; - croak "fatal: adminsuidsetup called without arguements" unless $user; + my $olduser = $user; - $user =~ /^([\w\-\.]+)$/ or croak "fatal: illegal user $user"; - $user = $1; + if ( $FS::CurrentUser::upgrade_hack ) { + $user = 'fs_bootstrap'; + } else { + croak "fatal: adminsuidsetup called without arguements" unless $user; + + $user =~ /^([\w\-\.]+)$/ or croak "fatal: illegal user $user"; + $user = $1; + } $ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin'; $ENV{'SHELL'} = '/bin/sh'; @@ -85,7 +92,17 @@ sub forksuidsetup { croak "Not running uid freeside!" unless checkeuid(); - $dbh = &myconnect; + if ( $FS::CurrentUser::upgrade_hack && $olduser ) { + $dbh = &myconnect($olduser); + } else { + $dbh = &myconnect(); + } + + use FS::Schema qw(reload_dbdef); + reload_dbdef("$conf_dir/dbdef.$datasrc") + unless $FS::Schema::setup_hack; + + FS::CurrentUser->load_user($user); foreach ( keys %callback ) { &{$callback{$_}}; @@ -98,7 +115,11 @@ sub forksuidsetup { } sub myconnect { - DBI->connect( getsecrets, {'AutoCommit' => 0, 'ChopBlanks' => 1, } ) + DBI->connect( getsecrets(@_), { 'AutoCommit' => 0, + 'ChopBlanks' => 1, + 'ShowErrorStatement' => 1, + } + ) or die "DBI->connect error: $DBI::errstr\n"; } @@ -254,15 +275,22 @@ the `/usr/local/etc/freeside/mapsecrets' file. sub getsecrets { my($setuser) = shift; $user = $setuser if $setuser; - die "No user!" unless $user; my($conf) = new FS::Conf $conf_dir; - my($line) = grep /^\s*$user\s/, $conf->config('mapsecrets'); - die "User $user not found in mapsecrets!" unless $line; - $line =~ /^\s*$user\s+(.*)$/; - $secrets = $1; - die "Illegal mapsecrets line for user?!" unless $secrets; + + if ( $conf->exists('mapsecrets') ) { + die "No user!" unless $user; + my($line) = grep /^\s*($user|\*)\s/, $conf->config('mapsecrets'); + confess "User $user not found in mapsecrets!" unless $line; + $line =~ /^\s*($user|\*)\s+(.*)$/; + $secrets = $2; + die "Illegal mapsecrets line for user?!" unless $secrets; + } else { + # no mapsecrets file at all, so do the default thing + $secrets = 'secrets'; + } + ($datasrc, $db_user, $db_pass) = $conf->config($secrets) - or die "Can't get secrets: $!"; + or die "Can't get secrets: $secrets: $!\n"; $FS::Conf::default_dir = $conf_dir. "/conf.$datasrc"; undef $driver_name; ($datasrc, $db_user, $db_pass); diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm new file mode 100644 index 000000000..25190406f --- /dev/null +++ b/FS/FS/access_group.pm @@ -0,0 +1,162 @@ +package FS::access_group; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::m2name_Common; +use FS::access_groupagent; +use FS::access_right; + +@ISA = qw(FS::m2m_Common FS::m2name_Common 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 access group. FS::access_group inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item groupnum - primary key + +=item groupname - Access group name + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new access group. To add the access group 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<hash> 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 access group. 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; +} + +=item access_groupagent + +Returns all associated FS::access_groupagent records. + +=cut + +sub access_groupagent { + my $self = shift; + qsearch('access_groupagent', { 'groupnum' => $self->groupnum } ); +} + +=item access_rights + +Returns all associated FS::access_right records. + +=cut + +sub access_rights { + my $self = shift; + qsearch('access_right', { 'righttype' => 'FS::access_group', + 'rightobjnum' => $self->groupnum + } + ); +} + +=item access_right RIGHTNAME + +Returns the specified FS::access_right record. Can be used as a boolean, to +test if this group has the given RIGHTNAME. + +=cut + +sub access_right { + my( $self, $name ) = shift; + qsearchs('access_right', { 'righttype' => 'FS::access_group', + 'rightobjnum' => $self->groupnum, + 'rightname' => $name, + } + ); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, 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..3de8feeed --- /dev/null +++ b/FS/FS/access_groupagent.pm @@ -0,0 +1,134 @@ +package FS::access_groupagent; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::agent; + +@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 group reseller virtualization. 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 group reseller virtualization. To add the record 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<hash> 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 group reseller virtualization. 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_foreign_key('groupnum', 'access_group', 'groupnum') + || $self->ut_foreign_key('agentnum', 'agent', 'agentnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item agent + +Returns the associated FS::agent object. + +=cut + +sub agent { + my $self = shift; + qsearchs('agent', { 'agentnum' => $self->agentnum } ); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, 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<hash> 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<FS::Record>, 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..cb43b37e9 --- /dev/null +++ b/FS/FS/access_user.pm @@ -0,0 +1,399 @@ +package FS::access_user; + +use strict; +use vars qw( @ISA $htpasswd_file ); +use FS::UID; +use FS::Conf; +use FS::Record qw( qsearch qsearchs dbh ); +use FS::m2m_Common; +use FS::option_Common; +use FS::access_usergroup; +use FS::agent; + +@ISA = qw( FS::m2m_Common FS::option_Common FS::Record ); +#@ISA = qw( FS::m2m_Common FS::option_Common ); + +#kludge htpasswd for now (i hope this bootstraps okay) +FS::UID->install_callback( sub { + my $conf = new FS::Conf; + $htpasswd_file = $conf->base_dir. '/htpasswd'; +} ); + +=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 internal access user. 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 - + +=item disabled - empty or 'Y' + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new internal access user. To add the user 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_user'; } + +sub _option_table { 'access_user_pref'; } +sub _option_namecol { 'prefname'; } +sub _option_valuecol { 'prefvalue'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->htpasswd_kludge(); + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } + + $error = $self->SUPER::insert(@_); + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} + +sub htpasswd_kludge { + my $self = shift; + + #awful kludge to skip setting htpasswd for fs_* users + return '' if $self->username =~ /^fs_/; + + unshift @_, '-c' unless -e $htpasswd_file; + if ( + system('htpasswd', '-b', @_, + $htpasswd_file, + $self->username, + $self->_password, + ) == 0 + ) + { + return ''; + } else { + return 'htpasswd exited unsucessfully'; + } +} + +=item delete + +Delete this record from the database. + +=cut + +sub delete { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = + $self->SUPER::delete(@_) + || $self->htpasswd_kludge('-D') + ; + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} + +=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 + +sub replace { + my $new = shift; + + my $old = ( ref($_[0]) eq ref($new) ) + ? shift + : $new->replace_old; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + if ( $new->_password ne $old->_password ) { + my $error = $new->htpasswd_kludge(); + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } + } + + my $error = $new->SUPER::replace($old, @_); + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} + +=item check + +Checks all fields to make sure this is a valid internal access user. 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_alpha('username') + || $self->ut_text('_password') + || $self->ut_text('last') + || $self->ut_text('first') + || $self->ut_enum('disabled', [ '', 'Y' ] ) + ; + 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 { +# +#} + +=item agentnums + +Returns a list of agentnums this user can view (via group membership). + +=cut + +sub agentnums { + my $self = shift; + my $sth = dbh->prepare( + "SELECT DISTINCT agentnum FROM access_usergroup + JOIN access_groupagent USING ( groupnum ) + WHERE usernum = ?" + ) or die dbh->errstr; + $sth->execute($self->usernum) or die $sth->errstr; + map { $_->[0] } @{ $sth->fetchall_arrayref }; +} + +=item agentnums_href + +Returns a hashref of agentnums this user can view. + +=cut + +sub agentnums_href { + my $self = shift; + { map { $_ => 1 } $self->agentnums }; +} + +=item agentnums_sql + +Returns an sql fragement to select only agentnums this user can view. + +=cut + +sub agentnums_sql { + my $self = shift; + + my @agentnums = map { "agentnum = $_" } $self->agentnums; + + push @agentnums, 'agentnum IS NULL' + if $self->access_right('View/link unlinked services'); + + return ' 1 = 0 ' unless scalar(@agentnums); + '( '. join( ' OR ', @agentnums ). ' )'; +} + +=item agentnum + +Returns true if the user can view the specified agent. + +=cut + +sub agentnum { + my( $self, $agentnum ) = @_; + my $sth = dbh->prepare( + "SELECT COUNT(*) FROM access_usergroup + JOIN access_groupagent USING ( groupnum ) + WHERE usernum = ? AND agentnum = ?" + ) or die dbh->errstr; + $sth->execute($self->usernum, $agentnum) or die $sth->errstr; + $sth->fetchrow_arrayref->[0]; +} + +=item agents + +Returns the list of agents this user can view (via group membership), as +FS::agent objects. + +=cut + +sub agents { + my $self = shift; + qsearch({ + 'table' => 'agent', + 'hashref' => { disabled=>'' }, + 'extra_sql' => ' AND '. $self->agentnums_sql, + }); +} + +=item access_right + +Given a right name, returns true if this user has this right (currently via +group membership, eventually also via user overrides). + +=cut + +sub access_right { + my( $self, $rightname ) = @_; + my $sth = dbh->prepare(" + SELECT groupnum FROM access_usergroup + LEFT JOIN access_group USING ( groupnum ) + LEFT JOIN access_right + ON ( access_group.groupnum = access_right.rightobjnum ) + WHERE usernum = ? + AND righttype = 'FS::access_group' + AND rightname = ? + ") or die dbh->errstr; + $sth->execute($self->usernum, $rightname) or die $sth->errstr; + my $row = $sth->fetchrow_arrayref; + $row ? $row->[0] : ''; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, 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<hash> 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<FS::Record>, 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<hash> 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<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index 83f0ce5b5..e40ef09db 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -195,7 +195,7 @@ sub num_sql { my( $self, $sql ) = @_; my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql"; my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; - $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement"; + $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement"; $sth->fetchrow_arrayref->[0]; } @@ -221,7 +221,8 @@ sub cust_main_sql { =item num_active_cust_main -Returns the number of active customers for this agent. +Returns the number of active customers for this agent (customers with active +recurring packages). =cut @@ -239,6 +240,28 @@ sub active_cust_main { shift->cust_main_sql(FS::cust_main->active_sql); } +=item num_inactive_cust_main + +Returns the number of inactive customers for this agent (customers with no +active recurring packages, but otherwise unsuspended/uncancelled). + +=cut + +sub num_inactive_cust_main { + shift->num_sql(FS::cust_main->inactive_sql); +} + +=item inactive_cust_main + +Returns the inactive customers for this agent, as cust_main objects. + +=cut + +sub inactive_cust_main { + shift->cust_main_sql(FS::cust_main->inactive_sql); +} + + =item num_susp_cust_main Returns the number of suspended customers for this agent. @@ -299,6 +322,17 @@ sub num_pkg_sql { $sth->fetchrow_arrayref->[0]; } +=item num_inactive_cust_pkg + +Returns the number of inactive customer packages (one-time packages otherwise +unsuspended/uncancelled) for this agent. + +=cut + +sub num_inactive_cust_pkg { + shift->num_pkg_sql(FS::cust_pkg->inactive_sql); +} + =item num_susp_cust_pkg Returns the number of suspended customer packages for this agent. diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm index 968b3b72e..2660bb4a3 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 @@ -135,6 +136,27 @@ sub type_pkgs { qsearch('type_pkgs', { 'typenum' => $self->typenum } ); } +=item type_pkgs_enabled + +Returns all FS::type_pkg objects (see L<FS::type_pkgs>) that link to enabled +package definitions (see L<FS::part_pkg>). + +An additional strange feature is that the returned type_pkg objects also have +all fields of the associated part_pkg object. + +=cut + +sub type_pkgs_enabled { + my $self = shift; + qsearch({ + 'table' => 'type_pkgs', + 'addl_from' => 'JOIN part_pkg USING ( pkgpart )', + 'hashref' => { 'typenum' => $self->typenum }, + 'extra_sql' => " AND ( disabled = '' OR disabled IS NULL )". + " ORDER BY pkg", + }); +} + =item pkgpart Returns the pkgpart of all package definitions (see L<FS::part_pkg>) for this @@ -151,6 +173,13 @@ sub pkgpart { =head1 BUGS +type_pkgs_enabled should order itself by something (pkg?) + +type_pkgs_enabled should populate something that caches for the part_pkg method +rather than add fields to this object, right? In fact we need a "poop" object +framework that does that automatically for any joined search at some point.... +right? + =head1 SEE ALSO L<FS::Record>, L<FS::agent>, L<FS::type_pkgs>, L<FS::cust_main>, diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm new file mode 100644 index 000000000..bddd1bf51 --- /dev/null +++ b/FS/FS/cdr.pm @@ -0,0 +1,640 @@ +package FS::cdr; + +use strict; +use vars qw( @ISA ); +use Date::Parse; +use Date::Format; +use Time::Local; +use FS::UID qw( dbh ); +use FS::Record qw( qsearch qsearchs ); +use FS::cdr_type; +use FS::cdr_calltype; +use FS::cdr_carrier; +use FS::cdr_upstream_rate; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cdr - Object methods for cdr records + +=head1 SYNOPSIS + + use FS::cdr; + + $record = new FS::cdr \%hash; + $record = new FS::cdr { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr object represents an Call Data Record, typically from a telephony +system or provider of some sort. FS::cdr inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item acctid - primary key + +=item calldate - Call timestamp (SQL timestamp) + +=item clid - Caller*ID with text + +=item src - Caller*ID number / Source number + +=item dst - Destination extension + +=item dcontext - Destination context + +=item channel - Channel used + +=item dstchannel - Destination channel if appropriate + +=item lastapp - Last application if appropriate + +=item lastdata - Last application data + +=item startdate - Start of call (UNIX-style integer timestamp) + +=item answerdate - Answer time of call (UNIX-style integer timestamp) + +=item enddate - End time of call (UNIX-style integer timestamp) + +=item duration - Total time in system, in seconds + +=item billsec - Total time call is up, in seconds + +=item disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY + +=item amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode. + +=cut + + #ignore the "omit" and "documentation" AMAs?? + #AMA = Automated Message Accounting. + #default: Sets the system default. + #omit: Do not record calls. + #billing: Mark the entry for billing + #documentation: Mark the entry for documentation. + +=item accountcode - CDR account number to use: account + +=item uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID) + +=item userfield - CDR user-defined field + +=item cdr_type - CDR type - see L<FS::cdr_type> (Usage = 1, S&E = 7, OC&C = 8) + +=item charged_party - Service number to be billed + +=item upstream_currency - Wholesale currency from upstream + +=item upstream_price - Wholesale price from upstream + +=item upstream_rateplanid - Upstream rate plan ID + +=item rated_price - Rated (or re-rated) price + +=item distance - km (need units field?) + +=item islocal - Local - 1, Non Local = 0 + +=item calltypenum - Type of call - see L<FS::cdr_calltype> + +=item description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc) + +=item quantity - Number of items (cdr_type 7&8 only) + +=item carrierid - Upstream Carrier ID (see L<FS::cdr_carrier>) + +=cut + +#Telstra =1, Optus = 2, RSL COM = 3 + +=item upstream_rateid - Upstream Rate ID + +=item svcnum - Link to customer service (see L<FS::cust_svc>) + +=item freesidestatus - NULL, done (or something) + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new CDR. To add the CDR 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr'; } + +=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 CDR. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +Note: Unlike most types of records, we don't want to "reject" a CDR and we want +to process them as quickly as possible, so we allow the database to check most +of the data. + +=cut + +sub check { + my $self = shift; + +# we don't want to "reject" a CDR like other sorts of input... +# my $error = +# $self->ut_numbern('acctid') +## || $self->ut_('calldate') +# || $self->ut_text('clid') +# || $self->ut_text('src') +# || $self->ut_text('dst') +# || $self->ut_text('dcontext') +# || $self->ut_text('channel') +# || $self->ut_text('dstchannel') +# || $self->ut_text('lastapp') +# || $self->ut_text('lastdata') +# || $self->ut_numbern('startdate') +# || $self->ut_numbern('answerdate') +# || $self->ut_numbern('enddate') +# || $self->ut_number('duration') +# || $self->ut_number('billsec') +# || $self->ut_text('disposition') +# || $self->ut_number('amaflags') +# || $self->ut_text('accountcode') +# || $self->ut_text('uniqueid') +# || $self->ut_text('userfield') +# || $self->ut_numbern('cdrtypenum') +# || $self->ut_textn('charged_party') +## || $self->ut_n('upstream_currency') +## || $self->ut_n('upstream_price') +# || $self->ut_numbern('upstream_rateplanid') +## || $self->ut_n('distance') +# || $self->ut_numbern('islocal') +# || $self->ut_numbern('calltypenum') +# || $self->ut_textn('description') +# || $self->ut_numbern('quantity') +# || $self->ut_numbern('carrierid') +# || $self->ut_numbern('upstream_rateid') +# || $self->ut_numbern('svcnum') +# || $self->ut_textn('freesidestatus') +# ; +# return $error if $error; + + $self->calldate( $self->startdate_sql ) + if !$self->calldate && $self->startdate; + + unless ( $self->charged_party ) { + if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) { + $self->charged_party($self->dst); + } else { + $self->charged_party($self->src); + } + } + + #check the foreign keys even? + #do we want to outright *reject* the CDR? + my $error = + $self->ut_numbern('acctid') + + #Usage = 1, S&E = 7, OC&C = 8 + || $self->ut_foreign_keyn('cdrtypenum', 'cdr_type', 'cdrtypenum' ) + + #the big list in appendix 2 + || $self->ut_foreign_keyn('calltypenum', 'cdr_calltype', 'calltypenum' ) + + # Telstra =1, Optus = 2, RSL COM = 3 + || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item set_status_and_rated_price STATUS [ RATED_PRICE ] + +Sets the status to the provided string. If there is an error, returns the +error, otherwise returns false. + +=cut + +sub set_status_and_rated_price { + my($self, $status, $rated_price) = @_; + $self->freesidestatus($status); + $self->rated_price($rated_price); + $self->replace(); +} + +=item calldate_unix + +Parses the calldate in SQL string format and returns a UNIX timestamp. + +=cut + +sub calldate_unix { + str2time(shift->calldate); +} + +=item startdate_sql + +Parses the startdate in UNIX timestamp format and returns a string in SQL +format. + +=cut + +sub startdate_sql { + my($sec,$min,$hour,$mday,$mon,$year) = localtime(shift->startdate); + $mon++; + $year += 1900; + "$year-$mon-$mday $hour:$min:$sec"; +} + +=item cdr_carrier + +Returns the FS::cdr_carrier object associated with this CDR, or false if no +carrierid is defined. + +=cut + +my %carrier_cache = (); + +sub cdr_carrier { + my $self = shift; + return '' unless $self->carrierid; + $carrier_cache{$self->carrierid} ||= + qsearchs('cdr_carrier', { 'carrierid' => $self->carrierid } ); +} + +=item carriername + +Returns the carrier name (see L<FS::cdr_carrier>), or the empty string if +no FS::cdr_carrier object is assocated with this CDR. + +=cut + +sub carriername { + my $self = shift; + my $cdr_carrier = $self->cdr_carrier; + $cdr_carrier ? $cdr_carrier->carriername : ''; +} + +=item cdr_calltype + +Returns the FS::cdr_calltype object associated with this CDR, or false if no +calltypenum is defined. + +=cut + +my %calltype_cache = (); + +sub cdr_calltype { + my $self = shift; + return '' unless $self->calltypenum; + $calltype_cache{$self->calltypenum} ||= + qsearchs('cdr_calltype', { 'calltypenum' => $self->calltypenum } ); +} + +=item calltypename + +Returns the call type name (see L<FS::cdr_calltype>), or the empty string if +no FS::cdr_calltype object is assocated with this CDR. + +=cut + +sub calltypename { + my $self = shift; + my $cdr_calltype = $self->cdr_calltype; + $cdr_calltype ? $cdr_calltype->calltypename : ''; +} + +=item cdr_upstream_rate + +Returns the upstream rate mapping (see L<FS::cdr_upstream_rate>), or the empty +string if no FS::cdr_upstream_rate object is associated with this CDR. + +=cut + +sub cdr_upstream_rate { + my $self = shift; + return '' unless $self->upstream_rateid; + qsearchs('cdr_upstream_rate', { 'upstream_rateid' => $self->upstream_rateid }) + or ''; +} + +=item _convergent_format COLUMN [ COUNTRYCODE ] + +Returns the number in COLUMN formatted as follows: + +If the country code does not match COUNTRYCODE (default "61"), it is returned +unchanged. + +If the country code does match COUNTRYCODE (default "61"), it is removed. In +addiiton, "0" is prepended unless the number starts with 13, 18 or 19. (???) + +=cut + +sub _convergent_format { + my( $self, $field ) = ( shift, shift ); + my $countrycode = scalar(@_) ? shift : '61'; #+61 = australia + #my $number = $self->$field(); + my $number = $self->get($field); + #if ( $number =~ s/^(\+|011)$countrycode// ) { + if ( $number =~ s/^\+$countrycode// ) { + $number = "0$number" + unless $number =~ /^1[389]/; #??? + } + $number; +} + +=item downstream_csv [ OPTION => VALUE, ... ] + +=cut + +my %export_formats = ( + 'convergent' => [ + 'carriername', #CARRIER + sub { shift->_convergent_format('src') }, #SERVICE_NUMBER + sub { shift->_convergent_format('charged_party') }, #CHARGED_NUMBER + sub { time2str('%Y-%m-%d', shift->calldate_unix ) }, #DATE + sub { time2str('%T', shift->calldate_unix ) }, #TIME + 'billsec', #'duration', #DURATION + sub { shift->_convergent_format('dst') }, #NUMBER_DIALED + '', #XXX add (from prefixes in most recent email) #FROM_DESC + '', #XXX add (from prefixes in most recent email) #TO_DESC + 'calltypename', #CLASS_CODE + 'rated_price', #PRICE + sub { shift->rated_price ? 'Y' : 'N' }, #RATED + '', #OTHER_INFO + ], +); + +sub downstream_csv { + my( $self, %opt ) = @_; + + my $format = $opt{'format'}; # 'convergent'; + return "Unknown format $format" unless exists $export_formats{$format}; + + eval "use Text::CSV_XS;"; + die $@ if $@; + my $csv = new Text::CSV_XS; + + my @columns = + map { + ref($_) ? &{$_}($self) : $self->$_(); + } + @{ $export_formats{$format} }; + + my $status = $csv->combine(@columns); + die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV" + unless $status; + + $csv->string; + +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item batch_import + +=cut + +my($tmp_mday, $tmp_mon, $tmp_year); + +my %import_formats = ( + 'asterisk' => [ + 'accountcode', + 'src', + 'dst', + 'dcontext', + 'clid', + 'channel', + 'dstchannel', + 'lastapp', + 'lastdata', + 'startdate', # XXX will need massaging + 'answer', # XXX same + 'end', # XXX same + 'duration', + 'billsec', + 'disposition', + 'amaflags', + 'uniqueid', + 'userfield', + ], + 'unitel' => [ + 'uniqueid', + #'cdr_type', + 'cdrtypenum', + 'calldate', # may need massaging? huh maybe not... + #'billsec', #XXX duration and billsec? + sub { $_[0]->billsec( $_[1] ); + $_[0]->duration( $_[1] ); + }, + 'src', + 'dst', # XXX needs to have "+61" prepended unless /^\+/ ??? + 'charged_party', + 'upstream_currency', + 'upstream_price', + 'upstream_rateplanid', + 'distance', + 'islocal', + 'calltypenum', + 'startdate', #XXX needs massaging + 'enddate', #XXX same + 'description', + 'quantity', + 'carrierid', + 'upstream_rateid', + ], + 'ams' => [ + + # Date + sub { my($cdr, $date) = @_; + $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/ + or die "unparsable date: $date"; #maybe we shouldn't die... + #$cdr->startdate( timelocal(0, 0, 0 ,$2, $1-1, $3) ); + ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 ); + }, + + # Time + sub { my($cdr, $time) = @_; + #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); + $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ + or die "unparsable time: $time"; #maybe we shouldn't die... + #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) ); + $cdr->startdate( + timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) + ); + }, + + # Source_Number + 'src', + + # Terminating_Number + 'dst', + + # Duration + sub { my($cdr, $min) = @_; + my $sec = sprintf('%.0f', $min * 60 ); + $cdr->billsec( $sec ); + $cdr->duration( $sec ); + }, + + ], +); + +sub batch_import { + my $param = shift; + + my $fh = $param->{filehandle}; + my $format = $param->{format}; + + return "Unknown format $format" unless exists $import_formats{$format}; + + eval "use Text::CSV_XS;"; + die $@ if $@; + + my $csv = new Text::CSV_XS; + + my $imported = 0; + #my $columns; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + if ( $format eq 'ams' ) { # and other formats with a header too? + + } + + my $body = 0; + my $line; + while ( defined($line=<$fh>) ) { + + #skip header... + if ( ! $body++ && $format eq 'ams' && $line =~ /^[\w\, ]+$/ ) { + next; + } + + $csv->parse($line) or do { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $csv->error_input(); + }; + + my @columns = $csv->fields(); + #warn join('-',@columns); + + if ( $format eq 'ams' ) { + @columns = map { s/^ +//; $_; } @columns; + } + + my @later = (); + my %cdr = + map { + + my $field_or_sub = $_; + if ( ref($field_or_sub) ) { + push @later, $field_or_sub, shift(@columns); + (); + } else { + ( $field_or_sub => shift @columns ); + } + + } + @{ $import_formats{$format} } + ; + + my $cdr = new FS::cdr ( \%cdr ); + + while ( scalar(@later) ) { + my $sub = shift @later; + my $data = shift @later; + &{$sub}($cdr, $data); # $cdr->&{$sub}($data); + } + + my $error = $cdr->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + + #or just skip? + #next; + } + + $imported++; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + #might want to disable this if we skip records for any reason... + return "Empty file!" unless $imported; + + ''; + +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cdr_calltype.pm b/FS/FS/cdr_calltype.pm new file mode 100644 index 000000000..fe456086f --- /dev/null +++ b/FS/FS/cdr_calltype.pm @@ -0,0 +1,115 @@ +package FS::cdr_calltype; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cdr_calltype - Object methods for cdr_calltype records + +=head1 SYNOPSIS + + use FS::cdr_calltype; + + $record = new FS::cdr_calltype \%hash; + $record = new FS::cdr_calltype { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr_calltype object represents an CDR call type. FS::cdr_calltype +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item calltypenum - primary key + +=item calltypename - CDR call type name + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new call type. To add the call type 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr_calltype'; } + +=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 call type. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('calltypenum') + || $self->ut_text('calltypename') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cdr_carrier.pm b/FS/FS/cdr_carrier.pm new file mode 100644 index 000000000..609c93923 --- /dev/null +++ b/FS/FS/cdr_carrier.pm @@ -0,0 +1,116 @@ +package FS::cdr_carrier; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cdr_carrier - Object methods for cdr_carrier records + +=head1 SYNOPSIS + + use FS::cdr_carrier; + + $record = new FS::cdr_carrier \%hash; + $record = new FS::cdr_carrier { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr_carrier object represents an CDR carrier or upstream. +FS::cdr_carrier inherits from FS::Record. The following fields are currently +supported: + +=over 4 + +=item carrierid - primary key + +=item carriername - Carrier name + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new carrier. To add the carrier 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr_carrier'; } + +=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 carrier. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('carrierid') + || $self->ut_text('carriername') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cancel_reason.pm b/FS/FS/cdr_type.pm index 19cc7214e..e258bf878 100644 --- a/FS/FS/cancel_reason.pm +++ b/FS/FS/cdr_type.pm @@ -1,4 +1,4 @@ -package FS::cancel_reason; +package FS::cdr_type; use strict; use vars qw( @ISA ); @@ -8,14 +8,14 @@ use FS::Record qw( qsearch qsearchs ); =head1 NAME -FS::cancel_reason - Object methods for cancel_reason records +FS::cdr_type - Object methods for cdr_type records =head1 SYNOPSIS - use FS::cancel_reason; + use FS::cdr_type; - $record = new FS::cancel_reason \%hash; - $record = new FS::cancel_reason { 'column' => 'value' }; + $record = new FS::cdr_type \%hash; + $record = new FS::cdr_type { 'column' => 'value' }; $error = $record->insert; @@ -27,17 +27,15 @@ FS::cancel_reason - Object methods for cancel_reason records =head1 DESCRIPTION -An FS::cancel_reason object represents an cancellation reason. -FS::cancel_reason inherits from FS::Record. The following fields are -currently supported: +An FS::cdr_type object represents an CDR type. FS::cdr_type inherits from +FS::Record. The following fields are currently supported: =over 4 -=item reasonnum - primary key +=item cdrtypenum - primary key -=item reason - +=item typename - CDR type name -=item disabled - empty or "Y" =back @@ -47,8 +45,7 @@ currently supported: =item new HASHREF -Creates a new cancellation reason. To add the reason to the database, see -L<"insert">. +Creates a new CDR type. To add the CDR type 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<hash> method. @@ -57,7 +54,7 @@ points to. You can ask the object for a copy with the I<hash> method. # the new method can be inherited from FS::Record, if a table method is defined -sub table { 'cancel_reason'; } +sub table { 'cdr_type'; } =item insert @@ -87,7 +84,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid reason. If there is +Checks all fields to make sure this is a valid CDR type. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. @@ -100,9 +97,8 @@ sub check { my $self = shift; my $error = - $self->ut_numbern('reasonnum') - || $self->ut_text('reason') - || $self->ut_enum('disabled', [ '', 'Y' ] ) + $self->ut_numbern('cdrtypenum') + || $self->ut_text('typename') ; return $error if $error; diff --git a/FS/FS/cdr_upstream_rate.pm b/FS/FS/cdr_upstream_rate.pm new file mode 100644 index 000000000..2fd978203 --- /dev/null +++ b/FS/FS/cdr_upstream_rate.pm @@ -0,0 +1,138 @@ +package FS::cdr_upstream_rate; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::rate_detail; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cdr_upstream_rate - Object methods for cdr_upstream_rate records + +=head1 SYNOPSIS + + use FS::cdr_upstream_rate; + + $record = new FS::cdr_upstream_rate \%hash; + $record = new FS::cdr_upstream_rate { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr_upstream_rate object represents an upstream rate mapping to +internal rate detail (see L<FS::rate_detail>). FS::cdr_upstream_rate inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item upstreamratenum - primary key + +=item upstream_rateid - CDR upstream Rate ID (cdr.upstream_rateid - see L<FS::cdr>) + +=item ratedetailnum - Rate detail - see L<FS::rate_detail> + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new upstream rate mapping. To add the upstream rate 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr_upstream_rate'; } + +=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 upstream rate. 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('upstreamratenum') + #|| $self->ut_number('upstream_rateid') + || $self->ut_alpha('upstream_rateid') + #|| $self->ut_text('upstream_rateid') + || $self->ut_foreign_key('ratedetailnum', 'rate_detail', 'ratedetailnum' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item rate_detail + +Returns the internal rate detail object for this upstream rate (see +L<FS::rate_detail>). + +=cut + +sub rate_detail { + my $self = shift; + qsearchs('rate_detail', { 'ratedetailnum' => $self->ratedetailnum } ); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 6e3b2b2f8..2c0b35388 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1,9 +1,10 @@ package FS::cust_bill; use strict; -use vars qw( @ISA $DEBUG $conf $money_char ); +use vars qw( @ISA $DEBUG $me $conf $money_char ); use vars qw( $invoice_lines @buf ); #yuck use Fcntl qw(:flock); #for spool_csv +use List::Util qw(min max); use IPC::Run3; use Date::Format; use Text::Template 1.20; @@ -13,7 +14,7 @@ use HTML::Entities; use Locale::Country; use FS::UID qw( datasrc ); use FS::Misc qw( send_email send_fax ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_main_Mixin; use FS::cust_main; use FS::cust_bill_pkg; @@ -21,15 +22,19 @@ use FS::cust_credit; use FS::cust_pay; use FS::cust_pkg; use FS::cust_credit_bill; +use FS::pay_batch; use FS::cust_pay_batch; use FS::cust_bill_event; use FS::part_pkg; use FS::cust_bill_pay; +use FS::cust_bill_pay_batch; use FS::part_bill_event; +use FS::payby; @ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; +$me = '[FS::cust_bill]'; #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { @@ -121,8 +126,14 @@ returns the error, otherwise returns false. =item delete -Currently unimplemented. I don't remove invoices because there would then be -no record you ever posted this invoice (which is bad, no?) +This method now works but you probably shouldn't use it. Instead, apply a +credit against the invoice. + +Using this method to delete invoices outright is really, really bad. There +would be no record you ever posted this invoice, and there are no check to +make sure charged = 0 or that there are no associated cust_bill_pkg records. + +Really, don't use it. =cut @@ -142,14 +153,20 @@ collect method of a customer object (see L<FS::cust_main>). =cut -sub replace { +#replace can be inherited from Record.pm + +# replace_check is now the preferred way to #implement replace data checks +# (so $object->replace() works without an argument) + +sub replace_check { my( $new, $old ) = ( shift, shift ); return "Can't change custnum!" unless $old->custnum == $new->custnum; #return "Can't change _date!" unless $old->_date eq $new->_date; return "Can't change _date!" unless $old->_date == $new->_date; - return "Can't change charged!" unless $old->charged == $new->charged; + return "Can't change charged!" unless $old->charged == $new->charged + || $old->charged == 0; - $new->SUPER::replace($old); + ''; } =item check @@ -212,6 +229,47 @@ sub cust_bill_pkg { qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } ); } +=item cust_pkg + +Returns the packages (see L<FS::cust_pkg>) corresponding to the line items for +this invoice. + +=cut + +sub cust_pkg { + my $self = shift; + my @cust_pkg = map { $_->cust_pkg } $self->cust_bill_pkg; + my %saw = (); + grep { ! $saw{$_->pkgnum}++ } @cust_pkg; +} + +=item open_cust_bill_pkg + +Returns the open line items for this invoice. + +Note that cust_bill_pkg with both setup and recur fees are returned as two +separate line items, each with only one fee. + +=cut + +# modeled after cust_main::open_cust_bill +sub open_cust_bill_pkg { + my $self = shift; + + # grep { $_->owed > 0 } $self->cust_bill_pkg + + my %other = ( 'recur' => 'setup', + 'setup' => 'recur', ); + my @open = (); + foreach my $field ( qw( recur setup )) { + push @open, map { $_->set( $other{$field}, 0 ); $_; } + grep { $_->owed($field) > 0 } + $self->cust_bill_pkg; + } + + @open; +} + =item cust_bill_event Returns the completed invoice events (see L<FS::cust_bill_event>) for this @@ -251,7 +309,7 @@ sub cust_suspend_if_balance_over { if ( $cust_main->total_owed_date($self->_date) < $amount ) { return (); } else { - $cust_main->suspend; + $cust_main->suspend(@_); } } @@ -354,6 +412,79 @@ sub owed { $balance; } +=item apply_payments_and_credits + +=cut + +sub apply_payments_and_credits { + my $self = shift; + + my @payments = grep { $_->unapplied > 0 } $self->cust_main->cust_pay; + my @credits = grep { $_->credited > 0 } $self->cust_main->cust_credit; + + while ( $self->owed > 0 and ( @payments || @credits ) ) { + + my $app = ''; + if ( @payments && @credits ) { + + #decide which goes first by weight of top (unapplied) line item + + my @open_lineitems = $self->open_cust_bill_pkg; + + my $max_pay_weight = + max( map { $_->cust_pkg->part_pkg->pay_weight || 0 } + @open_lineitems + ); + my $max_credit_weight = + max( map { $_->cust_pkg->part_pkg->credit_weight || 0 } + @open_lineitems + ); + + #if both are the same... payments first? it has to be something + if ( $max_pay_weight >= $max_credit_weight ) { + $app = 'pay'; + } else { + $app = 'credit'; + } + + } elsif ( @payments ) { + $app = 'pay'; + } elsif ( @credits ) { + $app = 'credit'; + } else { + die "guru meditation #12 and 35"; + } + + if ( $app eq 'pay' ) { + + my $payment = shift @payments; + + $app = new FS::cust_bill_pay { + 'paynum' => $payment->paynum, + 'amount' => sprintf('%.2f', min( $payment->unapplied, $self->owed ) ), + }; + + } elsif ( $app eq 'credit' ) { + + my $credit = shift @credits; + + $app = new FS::cust_credit_bill { + 'crednum' => $credit->crednum, + 'amount' => sprintf('%.2f', min( $credit->credited, $self->owed ) ), + }; + + } else { + die "guru meditation #12 and 35"; + } + + $app->invnum( $self->invnum ); + + my $error = $app->insert; + die $error if $error; + + } + +} =item generate_email PARAMHASH @@ -440,16 +571,17 @@ sub generate_email { 'Disposition' => 'inline', ); - $args{'from'} =~ /\@([\w\.\-]+)/ or $1 = 'example.com'; - my $content_id = join('.', rand()*(2**32), $$, time). "\@$1"; + $args{'from'} =~ /\@([\w\.\-]+)/; + my $from = $1 || 'example.com'; + my $content_id = join('.', rand()*(2**32), $$, time). "\@$from"; my $path = "$FS::UID::conf_dir/conf.$FS::UID::datasrc"; my $file; - if ( defined($args{'_template'}) && length($args{'_template'}) - && -e "$path/logo_". $args{'_template'}. ".png" + if ( defined($args{'template'}) && length($args{'template'}) + && -e "$path/logo_". $args{'template'}. ".png" ) { - $file = "$path/logo_". $args{'_template'}. ".png"; + $file = "$path/logo_". $args{'template'}. ".png"; } else { $file = "$path/logo.png"; } @@ -597,6 +729,21 @@ INVOICE_FROM, if specified, overrides the default email invoice From: address. =cut +sub queueable_send { + my %opt = @_; + + my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } ) + or die "invalid invoice number: " . $opt{invnum}; + + my @args = ( $opt{template}, $opt{agentnum} ); + push @args, $opt{invoice_from} + if exists($opt{invoice_from}) && $opt{invoice_from}; + + my $error = $self->send( @args ); + die $error if $error; + +} + sub send { my $self = shift; my $template = scalar(@_) ? shift : ''; @@ -635,6 +782,21 @@ INVOICE_FROM, if specified, overrides the default email invoice From: address. =cut +sub queueable_email { + my %opt = @_; + + my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } ) + or die "invalid invoice number: " . $opt{invnum}; + + my @args = ( $opt{template} ); + push @args, $opt{invoice_from} + if exists($opt{invoice_from}) && $opt{invoice_from}; + + my $error = $self->email( @args ); + die $error if $error; + +} + sub email { my $self = shift; my $template = scalar(@_) ? shift : ''; @@ -824,6 +986,8 @@ Options are: =item agent_spools - if set to a true value, will spool to per-agent files rather than a single global file +=item balanceover - if set, only spools the invoice if the total amount owed on this invoice and all older invoices is greater than the specified amount. + =back =cut @@ -840,6 +1004,11 @@ sub spool_csv { || ! keys %invoicing_list; } + if ( $opt{'balanceover'} ) { + return 'N/A' + if $cust_main->total_owed_date($self->_date) < $opt{'balanceover'}; + } + my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill"; mkdir $spooldir, 0700 unless -d $spooldir; @@ -1253,36 +1422,118 @@ sub realtime_bop { } -=item batch_card +=item batch_card OPTION => VALUE... Adds a payment for this invoice to the pending credit card batch (see -L<FS::cust_pay_batch>). +L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value, +runs the payment using a realtime gateway. =cut sub batch_card { - my $self = shift; + my ($self, %options) = @_; my $cust_main = $self->cust_main; + my $amount = sprintf("%.2f", $cust_main->balance - $cust_main->in_transit_payments); + return '' unless $amount > 0; + + if ($options{'realtime'}) { + return $cust_main->realtime_bop( FS::payby->payby2bop($cust_main->payby), + $amount, + %options, + ); + } + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + $dbh->do("LOCK TABLE pay_batch IN SHARE ROW EXCLUSIVE MODE") + or return "Cannot lock pay_batch: " . $dbh->errstr; + + my %pay_batch = ( + 'status' => 'O', + 'payby' => FS::payby->payby2payment($cust_main->payby), + ); + + my $pay_batch = qsearchs( 'pay_batch', \%pay_batch ); + + unless ( $pay_batch ) { + $pay_batch = new FS::pay_batch \%pay_batch; + my $error = $pay_batch->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + die "error creating new batch: $error\n"; + } + } + + my $old_cust_pay_batch = qsearchs('cust_pay_batch', { + 'batchnum' => $pay_batch->batchnum, + 'custnum' => $cust_main->custnum, + } ); + my $cust_pay_batch = new FS::cust_pay_batch ( { - 'invnum' => $self->getfield('invnum'), - 'custnum' => $cust_main->getfield('custnum'), + 'batchnum' => $pay_batch->batchnum, + 'invnum' => $self->getfield('invnum'), # is there a better value? + # this field should be + # removed... + # cust_bill_pay_batch now + 'custnum' => $cust_main->custnum, 'last' => $cust_main->getfield('last'), 'first' => $cust_main->getfield('first'), - 'address1' => $cust_main->getfield('address1'), - 'address2' => $cust_main->getfield('address2'), - 'city' => $cust_main->getfield('city'), - 'state' => $cust_main->getfield('state'), - 'zip' => $cust_main->getfield('zip'), - 'country' => $cust_main->getfield('country'), - 'cardnum' => $cust_main->payinfo, - 'exp' => $cust_main->getfield('paydate'), - 'payname' => $cust_main->getfield('payname'), - 'amount' => $self->owed, + 'address1' => $cust_main->address1, + 'address2' => $cust_main->address2, + 'city' => $cust_main->city, + 'state' => $cust_main->state, + 'zip' => $cust_main->zip, + 'country' => $cust_main->country, + 'payby' => $cust_main->payby, + 'payinfo' => $cust_main->payinfo, + 'exp' => $cust_main->paydate, + 'payname' => $cust_main->payname, + 'amount' => $amount, # consolidating } ); - my $error = $cust_pay_batch->insert; - die $error if $error; + + $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum) + if $old_cust_pay_batch; + my $error; + if ($old_cust_pay_batch) { + $error = $cust_pay_batch->replace($old_cust_pay_batch) + } else { + $error = $cust_pay_batch->insert; + } + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + + my $unapplied = $cust_main->total_credited + $cust_main->total_unapplied_payments + $cust_main->in_transit_payments; + foreach my $cust_bill ($cust_main->open_cust_bill) { + #$dbh->commit or die $dbh->errstr if $oldAutoCommit; + my $cust_bill_pay_batch = new FS::cust_bill_pay_batch { + 'invnum' => $cust_bill->invnum, + 'paybatchnum' => $cust_pay_batch->paybatchnum, + 'amount' => $cust_bill->owed, + '_date' => time, + }; + if ($unapplied >= $cust_bill_pay_batch->amount){ + $unapplied -= $cust_bill_pay_batch->amount; + next; + }else{ + $cust_bill_pay_batch->amount(sprintf ( "%.2f", + $cust_bill_pay_batch->amount - $unapplied )); + $unapplied = 0; + } + $error = $cust_bill_pay_batch->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -1496,12 +1747,14 @@ sub print_text { #setup template variables package FS::cust_bill::_template; #! - use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent ); + use vars qw( $custnum $invnum $date $agent @address $overdue + $page $total_pages @buf ); + $custnum = $self->custnum; $invnum = $self->invnum; $date = $self->_date; - $page = 1; $agent = $self->cust_main->agent->agent; + $page = 1; if ( $FS::cust_bill::invoice_lines ) { $total_pages = @@ -1634,6 +1887,7 @@ sub print_latex { } my %invoice_data = ( + 'custnum' => $self->custnum, 'invnum' => $self->invnum, 'date' => time2str('%b %o, %Y', $self->_date), 'today' => time2str('%b %o, %Y', $today), @@ -2034,6 +2288,7 @@ sub print_html { or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR; my %invoice_data = ( + 'custnum' => $self->custnum, 'invnum' => $self->invnum, 'date' => time2str('%b %o, %Y', $self->_date), 'today' => time2str('%b %o, %Y', $today), @@ -2095,6 +2350,7 @@ sub print_html { s/\\item / <li>/; s/\\end\{enumerate\}/<\/ol>/; s/\\textbf\{(.*)\}/<b>$1<\/b>/; + s/\\\\\*/ /; $_; } $conf->config_orbase('invoice_latexnotes', $template) @@ -2444,6 +2700,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; @@ -2459,6 +2716,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/cust_bill_ApplicationCommon.pm b/FS/FS/cust_bill_ApplicationCommon.pm new file mode 100644 index 000000000..9f61f5be5 --- /dev/null +++ b/FS/FS/cust_bill_ApplicationCommon.pm @@ -0,0 +1,390 @@ +package FS::cust_bill_ApplicationCommon; + +use strict; +use vars qw( @ISA $DEBUG $me ); +use List::Util qw(min); +use FS::Schema qw( dbdef ); +use FS::Record qw( qsearch qsearchs dbh ); + +@ISA = qw( FS::Record ); + +$DEBUG = 1; +$me = '[FS::cust_bill_ApplicationCommon]'; + +=head1 NAME + +FS::cust_bill_ApplicationCommon - Base class for bill application classes + +=head1 SYNOPSIS + +use FS::cust_bill_ApplicationCommon; + +@ISA = qw( FS::cust_bill_ApplicationCommon ); + +sub _app_source_name { 'payment'; } +sub _app_source_table { 'cust_pay'; } +sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; } + +=head1 DESCRIPTION + +FS::cust_bill_ApplicationCommon is intended as a base class for classes which +represent application of things to invoices, currently payments +(see L<FS::cust_bill_pay>) or credits (see L<FS::cust_credit_bill>). + +=head1 METHODS + +=item insert + +=cut + +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::insert(@_) + || $self->apply_to_lineitems; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; + +} + +=item delete + +=cut + +sub delete { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + foreach my $app ( $self->lineitem_applications ) { + my $error = $app->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + my $error = $self->SUPER::delete(@_); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; + +} + +=item apply_to_lineitems + +Auto-applies this invoice application to specific line items, if possible. + +=cut + +sub apply_to_lineitems { + my $self = shift; + + my @apply = (); + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...? + warn "$me ". scalar(@open). " open line items for invoice ". + $self->cust_bill->invnum. ": ". join(', ', @open). "\n" + if $DEBUG; + my $total = 0; + $total += $_->setup + $_->recur foreach @open; + $total = sprintf('%.2f', $total); + + if ( $self->amount > $total ) { + $dbh->rollback if $oldAutoCommit; + return "Can't apply a ". $self->_app_source_name. ' of $'. $self->amount. + " greater than the remaining owed on line items (\$$total)"; + } + + #easy cases: + # - one lineitem (a simple special case of:) + # - amount is for whole invoice (well, all of remaining lineitem links) + if ( $self->amount == $total ) { + + warn "$me application amount covers remaining balance of invoice in full;". + "applying to those lineitems\n" + if $DEBUG; + + #@apply = map { [ $_, $_->amount ]; } @open; + @apply = map { [ $_, $_->setup || $_->recur ]; } @open; + + } else { + + #slightly magic case: + # - amount exactly and uniquely matches a single open lineitem + # (you must be trying to pay or credit that item, then) + + my @same = grep { $_->setup == $self->amount + || $_->recur == $self->amount + } + @open; + if ( scalar(@same) == 1 ) { + warn "$me application amount exactly and uniquely matches one lineitem;". + " applying to that lineitem\n" + if $DEBUG; + @apply = map { [ $_, $self->amount ]; } @same + } + + } + + unless ( @apply ) { + + warn "$me applying amount based on package weights\n" + if $DEBUG; + + #and the rest: + # - apply based on weights... + + my $weight_col = $self->_app_part_pkg_weight_column; + my @openweight = map { + my $open = $_; + my $cust_pkg = $open->cust_pkg; + my $weight = + $cust_pkg + ? ( $cust_pkg->part_pkg->$weight_col() || 0 ) + : 0; #default or per-tax weight? + [ $open, $weight ] + } + @open; + + my %saw = (); + my @weights = sort { $b <=> $a } # highest weight first + grep { ! $saw{$_}++ } # want a list of unique weights + map { $_->[1] } + @openweight; + + my $remaining_amount = $self->amount; + foreach my $weight ( @weights ) { + + #i hate it when my schwartz gets tangled + my @items = map { $_->[0] } grep { $weight == $_->[1] } @openweight; + + my $itemtotal = 0; + foreach my $item (@items) { $itemtotal += $item->setup || $item->recur; } + my $applytotal = min( $itemtotal, $remaining_amount ); + $remaining_amount -= $applytotal; + + warn "$me applying $applytotal ($remaining_amount remaining)". + " to ". scalar(@items). " lineitems with weight $weight\n" + if $DEBUG; + + #if some items are less than applytotal/num_items, then apply then in full + my $lessflag; + do { + $lessflag = 0; + + #no, not sprintf("%.2f", + # we want this rounded DOWN for purposes of checking for line items + # less than it, we don't want .66666 becoming .67 and causing this + # to trigger when it shouldn't + my $applyeach = int( 100 * $applytotal / scalar(@items) ) / 100; + + my @newitems = (); + foreach my $item ( @items ) { + my $itemamount = $item->setup || $item->recur; + if ( $itemamount < $applyeach ) { + warn "$me applying full $itemamount". + " to small line item (cust_bill_pkg ". $item->billpkgnum. ")\n" + if $DEBUG; + push @apply, [ $item, $itemamount ]; + $applytotal -= $itemamount; + $lessflag=1; + } else { + push @newitems, $item; + } + } + @items = @newitems; + + } while ( $lessflag ); + + #and now that we've fallen out of the loop, distribute the rest equally... + + # should cust_bill_pay_pkg and cust_credit_bill_pkg amount columns + # become real instead of numeric(10,2) ??? no.. + my $applyeach = sprintf("%.2f", $applytotal / scalar(@items) ); + + my @equi_apply = map { [ $_, $applyeach ] } @items; + + # or should we futz with pennies instead? yes, bah! + my $diff = + sprintf('%.0f', 100 * ( $applytotal - $applyeach * scalar(@items) ) ); + $diff = 0 if $diff eq '-0'; #yay ieee fp + if ( abs($diff) > scalar(@items) ) { + #we must have done something really wrong, the difference is more than + #a penny an item + $dbh->rollback if $oldAutoCommit; + return 'Error distributing pennies applying '. $self->_app_source_name. + " - can't distribute difference of $diff pennies". + ' among '. scalar(@items). ' line items'; + } + + warn "$me futzing with $diff pennies difference\n" + if $DEBUG && $diff; + + my $futz = 0; + while ( $diff != 0 && $futz < scalar(@equi_apply) ) { + if ( $diff > 0 ) { + $equi_apply[$futz++]->[1] += .01; + $diff -= 1; + } elsif ( $diff < 0 ) { + $equi_apply[$futz++]->[1] -= .01; + $diff += 1; + } else { + die "guru exception #5 (in fortran tongue the answer)"; + } + } + + if ( sprintf('%.0f', $diff ) ) { + $dbh->rollback if $oldAutoCommit; + return "couldn't futz with pennies enough: still $diff left"; + } + + if ( $DEBUG ) { + warn "$me applying ". $_->[1]. + " to line item (cust_bill_pkg ". $_->[0]->billpkgnum. ")\n" + foreach @equi_apply; + } + + + push @apply, @equi_apply; + + #$remaining_amount -= $applytotal; + last unless $remaining_amount; + + } + + } + + # do the applicaiton(s) + my $table = $self->lineitem_breakdown_table; + my $source_key = dbdef->table($self->table)->primary_key; + my $applied = 0; + foreach my $apply ( @apply ) { + my ( $cust_bill_pkg, $amount ) = @$apply; + $applied += $amount; + my $application = "FS::$table"->new( { + $source_key => $self->$source_key(), + 'billpkgnum' => $cust_bill_pkg->billpkgnum, + 'amount' => sprintf('%.2f', $amount), + 'setuprecur' => ( $cust_bill_pkg->setup > 0 ? 'setup' : 'recur' ), + 'sdate' => $cust_bill_pkg->sdate, + 'edate' => $cust_bill_pkg->edate, + }); + my $error = $application->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + #everything should always be applied to line items in full now... sanity check + $applied = sprintf('%.2f', $applied); + unless ( $applied == $self->amount ) { + $dbh->rollback if $oldAutoCommit; + return 'Error applying '. $self->_app_source_name. ' of $'. $self->amount. + ' to line items - only $'. $applied. ' was applied.'; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item lineitem_applications + +Returns all the specific line item applications for this invoice application. + +=cut + +sub lineitem_applications { + my $self = shift; + my $primary_key = dbdef->table($self->table)->primary_key; + qsearch({ + 'table' => $self->lineitem_breakdown_table, + 'hashref' => { $primary_key => $self->$primary_key() }, + }); + +} + +=item cust_bill + +Returns the invoice (see L<FS::cust_bill>) + +=cut + +sub cust_bill { + my $self = shift; + qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); +} + +=item lineitem_breakdown_table + +=cut + +sub lineitem_breakdown_table { + my $self = shift; + $self->_load_table($self->_app_lineitem_breakdown_table); +} + +sub _load_table { + my( $self, $table ) = @_; + eval "use FS::$table"; + die $@ if $@; + $table; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_bill_pay> and L<FS::cust_bill_pay_pkg>, +L<FS::cust_credit_bill> and L<FS::cust_credit_bill_pkg> + +=cut + +1; + diff --git a/FS/FS/cust_bill_event.pm b/FS/FS/cust_bill_event.pm index 128e5a53d..4496bed65 100644 --- a/FS/FS/cust_bill_event.pm +++ b/FS/FS/cust_bill_event.pm @@ -126,12 +126,13 @@ sub check { || $self->ut_textn('statustext') ; + return "Unknown eventpart ". $self->eventpart + unless my $part_bill_event = + qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } ); + return "Unknown invnum ". $self->invnum unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } ); - return "Unknown eventpart ". $self->eventpart - unless qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } ); - $self->SUPER::check; } @@ -173,6 +174,21 @@ sub retry { $self->replace($old); } +=item retryable + +Changes the statustext of this event to B<retriable>, rendering it +retriable (should retry be called). + +=cut + +sub retriable { + my $self = shift; + return '' unless $self->status eq 'done'; + my $old = ref($self)->new( { $self->hash } ); + $self->statustext('retriable'); + $self->replace($old); +} + =back =head1 SUBROUTINES diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm index 7abbe9a3b..74a8bcdd4 100644 --- a/FS/FS/cust_bill_pay.pm +++ b/FS/FS/cust_bill_pay.pm @@ -2,11 +2,12 @@ package FS::cust_bill_pay; use strict; use vars qw( @ISA $conf ); -use FS::Record qw( qsearch qsearchs dbh ); +use FS::Record qw( qsearchs ); +use FS::cust_bill_ApplicationCommon; use FS::cust_bill; use FS::cust_pay; -@ISA = qw( FS::Record ); +@ISA = qw( FS::cust_bill_ApplicationCommon ); #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { @@ -35,8 +36,9 @@ FS::cust_bill_pay - Object methods for cust_bill_pay records =head1 DESCRIPTION An FS::cust_bill_pay object represents the application of a payment to a -specific invoice. FS::cust_bill_pay inherits from FS::Record. The following -fields are currently supported: +specific invoice. FS::cust_bill_pay inherits from +FS::cust_bill_ApplicationCommon and FS::Record. The following fields are +currently supported: =over 4 @@ -65,6 +67,11 @@ Creates a new record. To add the record to the database, see L<"insert">. sub table { 'cust_bill_pay'; } +sub _app_source_name { 'payment'; } +sub _app_source_table { 'cust_pay'; } +sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; } +sub _app_part_pkg_weight_column { 'pay_weight'; } + =item insert Adds this record to the database. If there is an error, returns the error, @@ -81,6 +88,8 @@ sub delete { my $self = shift; return "Can't delete application for closed payment" if $self->cust_pay->closed =~ /^Y/i; + return "Can't delete application for closed invoice" + if $self->cust_bill->closed =~ /^Y/i; $self->SUPER::delete(@_); } @@ -91,13 +100,14 @@ Currently unimplemented (accounting reasons). =cut sub replace { - return "Can't (yet?) modify cust_bill_pay records!"; + return "Can't modify application of payment!"; } =item check -Checks all fields to make sure this is a valid payment. If there is an error, -returns the error, otherwise returns false. Called by the insert method. +Checks all fields to make sure this is a valid payment application. If there +is an error, returns the error, otherwise returns false. Called by the insert +method. =cut @@ -106,30 +116,22 @@ sub check { my $error = $self->ut_numbern('billpaynum') - || $self->ut_number('invnum') - || $self->ut_number('paynum') - || $self->ut_money('amount') + || $self->ut_foreign_key('paynum', 'cust_pay', 'paynum' ) + || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' ) || $self->ut_numbern('_date') + || $self->ut_money('amount') ; return $error if $error; return "amount must be > 0" if $self->amount <= 0; - return "Unknown invoice" - unless my $cust_bill = - qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); - - return "Unknown payment" - unless my $cust_pay = - qsearchs( 'cust_pay', { 'paynum' => $self->paynum } ); - $self->_date(time) unless $self->_date; return "Cannot apply more than remaining value of invoice" - unless $self->amount <= $cust_bill->owed; + unless $self->amount <= $self->cust_bill->owed; return "Cannot apply more than remaining value of payment" - unless $self->amount <= $cust_pay->unapplied; + unless $self->amount <= $self->cust_pay->unapplied; $self->SUPER::check; } @@ -145,26 +147,12 @@ sub cust_pay { qsearchs( 'cust_pay', { 'paynum' => $self->paynum } ); } -=item cust_bill - -Returns the invoice (see L<FS::cust_bill>) - -=cut - -sub cust_bill { - my $self = shift; - qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); -} - =back =head1 BUGS Delete and replace methods. -the checks for over-applied payments could be better done like the ones in -cust_bill_credit - =head1 SEE ALSO L<FS::cust_pay>, L<FS::cust_bill>, L<FS::Record>, schema.html from the diff --git a/FS/FS/cust_bill_pay_batch.pm b/FS/FS/cust_bill_pay_batch.pm new file mode 100644 index 000000000..30fb74432 --- /dev/null +++ b/FS/FS/cust_bill_pay_batch.pm @@ -0,0 +1,120 @@ +package FS::cust_bill_pay_batch; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_bill_pay_batch - Object methods for cust_bill_pay_batch records + +=head1 SYNOPSIS + + use FS::cust_bill_pay_batch; + + $record = new FS::cust_bill_pay_batch \%hash; + $record = new FS::cust_bill_pay_batch { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_bill_pay_batch object represents a relationship between a +customer's bill and a batch. FS::cust_bill_pay_batch inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item billpaynum - primary key + +=item invnum - customer's bill (invoice) + +=item paybatchnum - entry in cust_pay_batch table + +=item amount - + +=item _date - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record 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<hash> method. + +=cut + +sub table { 'cust_bill_pay_batch'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=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 + +=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 + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('billpaynum') + || $self->ut_number('invnum') + || $self->ut_number('paybatchnum') + || $self->ut_money('amount') + || $self->ut_numbern('_date') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +Just hangs there. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm new file mode 100644 index 000000000..cdbace960 --- /dev/null +++ b/FS/FS/cust_bill_pay_pkg.pm @@ -0,0 +1,141 @@ +package FS::cust_bill_pay_pkg; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records + +=head1 SYNOPSIS + + use FS::cust_bill_pay_pkg; + + $record = new FS::cust_bill_pay_pkg \%hash; + $record = new FS::cust_bill_pay_pkg { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_bill_pay_pkg object represents application of a payment (see +L<FS::cust_bill_pay>) to a specific line item within an invoice (see +L<FS::cust_bill_pkg>). FS::cust_bill_pay_pkg inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item billpaypkgnum - primary key + +=item billpaynum - Payment application to the overall invoice (see L<FS::cust_bill_pay>) + +=item billpkgnum - Line item to which payment is applied (see L<FS::cust_bill_pkg>) + +=item amount - Amount of the payment applied to this line item. + +=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item. + +=item sdate - starting date of recurring fee + +=item edate - ending date of recurring fee + +=back + +sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also +see L<Time::Local> and L<Date::Parse> for conversion functions. + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_bill_pay_pkg'; } + +=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 payment application. 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('billpaypkgnum') + || $self->ut_foreign_key('billpaynum', 'cust_bill_pay', 'billpaynum' ) + || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' ) + || $self->ut_money('amount') + || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] ) + || $self->ut_numbern('sdate') + || $self->ut_numbern('edate') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate +setup and recur fields. It should be removed once that's fixed. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index d718b05fd..9fddf6bf5 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -7,6 +7,8 @@ use FS::cust_main_Mixin; use FS::cust_pkg; use FS::cust_bill; use FS::cust_bill_pkg_detail; +use FS::cust_bill_pay_pkg; +use FS::cust_credit_bill_pkg; @ISA = qw( FS::cust_main_Mixin FS::Record ); @@ -190,6 +192,17 @@ sub cust_pkg { qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); } +=item cust_bill + +Returns the invoice (see L<FS::cust_bill>) for this invoice line item. + +=cut + +sub cust_bill { + my $self = shift; + qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); +} + =item details Returns an array of detail information for the invoice line item. @@ -224,18 +237,78 @@ sub desc { } } -=back +=item owed_setup -=head1 CLASS METHODS +Returns the amount owed (still outstanding) on this line item's setup fee, +which is the amount of the line item minus all payment applications (see +L<FS::cust_bill_pay_pkg> and credit applications (see +L<FS::cust_credit_bill_pkg>). -=over 4 +=cut + +sub owed_setup { + my $self = shift; + $self->owed('setup', @_); +} + +=item owed_recur -=item +Returns the amount owed (still outstanding) on this line item's recurring fee, +which is the amount of the line item minus all payment applications (see +L<FS::cust_bill_pay_pkg> and credit applications (see +L<FS::cust_credit_bill_pkg>). + +=cut + +sub owed_recur { + my $self = shift; + $self->owed('recur', @_); +} + +# modeled after cust_bill::owed... +sub owed { + my( $self, $field ) = @_; + my $balance = $self->$field(); + $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($field) ); + $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) ); + $balance = sprintf( '%.2f', $balance ); + $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp + $balance; +} + +sub cust_bill_pay_pkg { + my( $self, $field ) = @_; + qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum, + 'setuprecur' => $field, + } + ); +} + +sub cust_credit_bill_pkg { + my( $self, $field ) = @_; + qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum, + 'setuprecur' => $field, + } + ); +} =back =head1 BUGS +setup and recur shouldn't be separate fields. There should be one "amount" +field and a flag to tell you if it is a setup/one-time fee or a recurring fee. + +A line item with both should really be two separate records (preserving +sdate and edate for setup fees for recurring packages - that information may +be valuable later). Invoice generation (cust_main::bill), invoice printing +(cust_bill), tax reports (report_tax.cgi) and line item reports +(cust_bill_pkg.cgi) would need to be updated. + +owed_setup and owed_recur could then be repaced by just owed, and +cust_bill::open_cust_bill_pkg and +cust_bill_ApplicationCommon::apply_to_lineitems could be simplified. + =head1 SEE ALSO L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm index 9cc92d2e8..7ffb1d178 100644 --- a/FS/FS/cust_credit.pm +++ b/FS/FS/cust_credit.pm @@ -134,10 +134,13 @@ sub insert { =item delete -Currently unimplemented. +Unless the closed flag is set, deletes this credit and all associated +applications (see L<FS::cust_credit_bill>). In most cases, you want to use +the void method instead to leave a record of the deleted credit. =cut +# very similar to FS::cust_pay::delete sub delete { my $self = shift; return "Can't delete closed credit" if $self->closed =~ /^Y/i; @@ -169,7 +172,7 @@ sub delete { if ( $conf->config('deletecredits') ne '' ) { - my $cust_main = qsearchs('cust_main',{ 'custnum' => $self->custnum }); + my $cust_main = $self->cust_main; my $error = send_email( 'from' => $conf->config('invoice_from'), #??? well as good as any @@ -203,8 +206,7 @@ sub delete { =item replace OLD_RECORD -Credits may not be modified; there would then be no record the credit was ever -posted. +You can, but probably shouldn't modify credits... =cut diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm index 695df6e8d..411bae21a 100644 --- a/FS/FS/cust_credit_bill.pm +++ b/FS/FS/cust_credit_bill.pm @@ -4,12 +4,11 @@ use strict; use vars qw( @ISA $conf ); use FS::UID qw( getotaker ); use FS::Record qw( qsearch qsearchs ); -use FS::cust_main; -#use FS::cust_refund; -use FS::cust_credit; +use FS::cust_bill_ApplicationCommon; use FS::cust_bill; +use FS::cust_credit; -@ISA = qw( FS::Record ); +@ISA = qw( FS::cust_bill_ApplicationCommon ); #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { @@ -39,7 +38,8 @@ FS::cust_credit_bill - Object methods for cust_credit_bill records An FS::cust_credit_bill object represents application of a credit (see L<FS::cust_credit>) to an invoice (see L<FS::cust_bill>). FS::cust_credit_bill -inherits from FS::Record. The following fields are currently supported: +inherits from FS::cust_bill_ApplicationCommon and FS::Record. The following +fields are currently supported: =over 4 @@ -69,6 +69,11 @@ see L<"insert">. sub table { 'cust_credit_bill'; } +sub _app_source_name { 'credit'; } +sub _app_source_table { 'cust_credit'; } +sub _app_lineitem_breakdown_table { 'cust_credit_bill_pkg'; } +sub _app_part_pkg_weight_column { 'credit_weight'; } + =item insert Adds this cust_credit_bill to the database ("Posts" all or part of a credit). @@ -84,6 +89,8 @@ sub delete { my $self = shift; return "Can't delete application for closed credit" if $self->cust_credit->closed =~ /^Y/i; + return "Can't delete application for closed invoice" + if $self->cust_bill->closed =~ /^Y/i; $self->SUPER::delete(@_); } @@ -110,8 +117,8 @@ sub check { my $error = $self->ut_numbern('creditbillnum') - || $self->ut_number('crednum') - || $self->ut_number('invnum') + || $self->ut_foreign_key('crednum', 'cust_credit', 'crednum') + || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' ) || $self->ut_numbern('_date') || $self->ut_money('amount') ; @@ -119,21 +126,13 @@ sub check { return "amount must be > 0" if $self->amount <= 0; - return "Unknown credit" - unless my $cust_credit = - qsearchs( 'cust_credit', { 'crednum' => $self->crednum } ); - - return "Unknown invoice" - unless my $cust_bill = - qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); - $self->_date(time) unless $self->_date; return "Cannot apply more than remaining value of credit" - unless $self->amount <= $cust_credit->credited; + unless $self->amount <= $self->cust_credit->credited; return "Cannot apply more than remaining value of invoice" - unless $self->amount <= $cust_bill->owed; + unless $self->amount <= $self->cust_bill->owed; $self->SUPER::check; } @@ -149,26 +148,17 @@ sub cust_credit { qsearchs( 'cust_credit', { 'crednum' => $self->crednum } ); } -=item cust_bill - -Returns the invoice (see L<FS::cust_bill>) - -=cut - -sub cust_bill { - my $self = shift; - qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); -} - =back =head1 BUGS The delete method. +This probably should have been called cust_bill_credit. + =head1 SEE ALSO -L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, L<FS::cust_credit>, +L<FS::Record>, L<FS::cust_bill>, L<FS::cust_credit>, schema.html from the base documentation. =cut diff --git a/FS/FS/cust_credit_bill_pkg.pm b/FS/FS/cust_credit_bill_pkg.pm new file mode 100644 index 000000000..7252be537 --- /dev/null +++ b/FS/FS/cust_credit_bill_pkg.pm @@ -0,0 +1,141 @@ +package FS::cust_credit_bill_pkg; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records + +=head1 SYNOPSIS + + use FS::cust_credit_bill_pkg; + + $record = new FS::cust_credit_bill_pkg \%hash; + $record = new FS::cust_credit_bill_pkg { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_credit_bill_pkg object represents application of a credit (see +L<FS::cust_credit_bill>) to a specific line item within an invoice +(see L<FS::cust_bill_pkg>). FS::cust_credit_bill_pkg inherits from FS::Record. +The following fields are currently supported: + +=over 4 + +=item creditbillpkg - primary key + +=item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>) + +=item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>) + +=item amount - Amount of the credit applied to this line item. + +=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item. + +=item sdate - starting date of recurring fee + +=item edate - ending date of recurring fee + +=back + +sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also +see L<Time::Local> and L<Date::Parse> for conversion functions. + +=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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_credit_bill_pkg'; } + +=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 credit applicaiton. 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('creditbillpkgnum') + || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum') + || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' ) + || $self->ut_money('amount') + || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] ) + || $self->ut_numbern('sdate') + || $self->ut_numbern('edate') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate +setup and recur fields. It should be removed once that's fixed. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm index 36c77aa59..f237efed2 100644 --- a/FS/FS/cust_credit_refund.pm +++ b/FS/FS/cust_credit_refund.pm @@ -70,20 +70,27 @@ otherwise returns false. sub insert { my $self = shift; - my $error = $self->SUPER::insert; - return $error if $error; - - ''; + return "Can't apply refund to closed credit" + if $self->cust_credit->closed =~ /^Y/i; + return "Can't apply credit to closed refund" + if $self->cust_refund->closed =~ /^Y/i; + $self->SUPER::insert(@_); } =item delete -Currently unimplemented (accounting reasons). +Remove this cust_credit_refund from the database. If there is an error, +returns the error, otherwise returns false. =cut sub delete { - return "Can't (yet?) delete cust_credit_refund records!"; + my $self = shift; + return "Can't remove refund from closed credit" + if $self->cust_credit->closed =~ /^Y/i; + return "Can't remove credit from closed refund" + if $self->cust_refund->closed =~ /^Y/i; + $self->SUPER::delete(@_); } =item replace OLD_RECORD diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index a265e4177..08b8c3749 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -16,9 +16,11 @@ BEGIN { } use Digest::MD5 qw(md5_base64); use Date::Format; +use Date::Parse; #use Date::Manip; use String::Approx qw(amatch); use Business::CreditCard 0.28; +use Locale::Country; use FS::UID qw( getotaker dbh ); use FS::Record qw( qsearchs qsearch dbdef ); use FS::Misc qw( send_email ); @@ -40,15 +42,17 @@ use FS::cust_bill_pay; use FS::prepay_credit; use FS::queue; use FS::part_pkg; -use FS::part_bill_event; +use FS::part_bill_event qw(due_events); use FS::cust_bill_event; use FS::cust_tax_exempt; +use FS::cust_tax_exempt_pkg; use FS::type_pkgs; use FS::payment_gateway; use FS::agent_payment_gateway; use FS::banned_pay; +use FS::payinfo_Mixin; -@ISA = qw( FS::Record ); +@ISA = qw( FS::Record FS::payinfo_Mixin ); @EXPORT_OK = qw( smart_search ); @@ -77,7 +81,7 @@ sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; if ( exists $hashref->{'pkgnum'} ) { -# #@{ $self->{'_pkgnum'} } = (); + #@{ $self->{'_pkgnum'} } = (); my $subcache = $cache->subcache( 'pkgnum', 'cust_pkg', $hashref->{custnum}); $self->{'_pkgnum'} = $subcache; #push @{ $self->{'_pkgnum'} }, @@ -117,8 +121,6 @@ FS::cust_main - Object methods for cust_main records $error = $record->collect; $error = $record->collect %options; $error = $record->collect 'invoice_time' => $time, - 'batch_card' => 'yes', - 'report_badcard' => 'yes', ; =head1 DESCRIPTION @@ -188,81 +190,15 @@ FS::Record. The following fields are currently supported: =item ship_fax - phone (optional) -=item payby +=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values) -I<CARD> (credit card - automatic), I<DCRD> (credit card - on-demand), I<CHEK> (electronic check - automatic), I<DCHK> (electronic check - on-demand), I<LECB> (Phone bill billing), I<BILL> (billing), I<COMP> (free), or I<PREPAY> (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>) - -=item payinfo - -Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>) - -=cut - -sub payinfo { - my($self,$payinfo) = @_; - if ( defined($payinfo) ) { - $self->paymask($payinfo); - $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter' - } else { - $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter' - return $payinfo; - } -} +=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format) +=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works) =item paycvv - -Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card - -=cut - -=item paymask - Masked payment type - -=over 4 - -=item Credit Cards - -Mask all but the last four characters. - -=item Checks -Mask all but last 2 of account number and bank routing number. - -=item Others - -Do nothing, return the unmasked string. - -=back - -=cut - -sub paymask { - my($self,$value)=@_; - - # If it doesn't exist then generate it - my $paymask=$self->getfield('paymask'); - if (!defined($value) && (!defined($paymask) || $paymask eq '')) { - $value = $self->payinfo; - } - - if ( defined($value) && !$self->is_encrypted($value)) { - my $payinfo = $value; - my $payby = $self->payby; - if ($payby eq 'CARD' || $payby eq 'DCRD') { # Credit Cards (Show last four) - $paymask = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4)); - } elsif ($payby eq 'CHEK' || - $payby eq 'DCHK' ) { # Checks (Show last 2 @ bank) - my( $account, $aba ) = split('@', $payinfo ); - $paymask = 'x'x(length($account)-2). substr($account,(length($account)-2))."@".$aba; - } else { # Tie up loose ends - $paymask = $payinfo; - } - $self->setfield('paymask', $paymask); # This is okay since we are the 'setter' - } elsif (defined($value) && $self->is_encrypted($value)) { - $paymask = 'N/A'; - } - return $paymask; -} +Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy @@ -284,6 +220,8 @@ sub paymask { =item referral_custnum - referring customer number +=item spool_cdr - Enable individual CDR spooling, empty or `Y' + =back =head1 METHODS @@ -334,7 +272,7 @@ Currently available options are: I<depend_jobnum> and I<noexport>. If I<depend_jobnum> is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). This can be used to defer provisioning until some action completes (such -as running the customer's credit card sucessfully). +as running the customer's credit card successfully). The I<noexport> option is deprecated. If I<noexport> is set true, no provisioning jobs (exports) are scheduled. (You can schedule them later with @@ -394,6 +332,8 @@ sub insert { warn " inserting $self\n" if $DEBUG > 1; + $self->signupdate(time) unless $self->signupdate; + my $error = $self->SUPER::insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -413,6 +353,20 @@ sub insert { $self->invoicing_list( $invoicing_list ); } + if ( $conf->config('cust_main-skeleton_tables') + && $conf->config('cust_main-skeleton_custnum') ) { + + warn " inserting skeleton records\n" + if $DEBUG > 1; + + my $error = $self->start_copy_skel; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + } + warn " ordering packages\n" if $DEBUG > 1; @@ -455,6 +409,133 @@ sub insert { } +sub start_copy_skel { + my $self = shift; + + #'mg_user_preference' => {}, + #'mg_user_indicator_profile.user_indicator_profile_id' => { 'mg_profile_indicator.profile_indicator_id' => { 'mg_profile_details.profile_detail_id' }, }, + #'mg_watchlist_header.watchlist_header_id' => { 'mg_watchlist_details.watchlist_details_id' }, + #'mg_user_grid_header.grid_header_id' => { 'mg_user_grid_details.user_grid_details_id' }, + #'mg_portfolio_header.portfolio_header_id' => { 'mg_portfolio_trades.portfolio_trades_id' => { 'mg_portfolio_trades_positions.portfolio_trades_positions_id' } }, + my @tables = eval($conf->config_binary('cust_main-skeleton_tables')); + die $@ if $@; + + _copy_skel( 'cust_main', #tablename + $conf->config('cust_main-skeleton_custnum'), #sourceid + $self->custnum, #destid + @tables, #child tables + ); +} + +#recursive subroutine, not a method +sub _copy_skel { + my( $table, $sourceid, $destid, %child_tables ) = @_; + + my $primary_key; + if ( $table =~ /^(\w+)\.(\w+)$/ ) { + ( $table, $primary_key ) = ( $1, $2 ); + } else { + my $dbdef_table = dbdef->table($table); + $primary_key = $dbdef_table->primary_key + or return "$table has no primary key". + " (or do you need to run dbdef-create?)"; + } + + warn " _copy_skel: $table.$primary_key $sourceid to $destid for ". + join (', ', keys %child_tables). "\n" + if $DEBUG > 2; + + foreach my $child_table_def ( keys %child_tables ) { + + my $child_table; + my $child_pkey = ''; + if ( $child_table_def =~ /^(\w+)\.(\w+)$/ ) { + ( $child_table, $child_pkey ) = ( $1, $2 ); + } else { + $child_table = $child_table_def; + + $child_pkey = dbdef->table($child_table)->primary_key; + # or return "$table has no primary key". + # " (or do you need to run dbdef-create?)\n"; + } + + my $sequence = ''; + if ( keys %{ $child_tables{$child_table_def} } ) { + + return "$child_table has no primary key". + " (run dbdef-create or try specifying it?)\n" + unless $child_pkey; + + #false laziness w/Record::insert and only works on Pg + #refactor the proper last-inserted-id stuff out of Record::insert if this + # ever gets use for anything besides a quick kludge for one customer + my $default = dbdef->table($child_table)->column($child_pkey)->default; + $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i + or return "can't parse $child_table.$child_pkey default value ". + " for sequence name: $default"; + $sequence = $1; + + } + + my @sel_columns = grep { $_ ne $primary_key } + dbdef->table($child_table)->columns; + my $sel_columns = join(', ', @sel_columns ); + + my @ins_columns = grep { $_ ne $child_pkey } @sel_columns; + my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) '; + my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) '; + + my $sel_st = "SELECT $sel_columns FROM $child_table". + " WHERE $primary_key = $sourceid"; + warn " $sel_st\n" + if $DEBUG > 2; + my $sel_sth = dbh->prepare( $sel_st ) + or return dbh->errstr; + + $sel_sth->execute or return $sel_sth->errstr; + + while ( my $row = $sel_sth->fetchrow_hashref ) { + + warn " selected row: ". + join(', ', map { "$_=".$row->{$_} } keys %$row ). "\n" + if $DEBUG > 2; + + my $statement = + "INSERT INTO $child_table $ins_columns VALUES $placeholders"; + my $ins_sth =dbh->prepare($statement) + or return dbh->errstr; + my @param = ( $destid, map $row->{$_}, @ins_columns ); + warn " $statement: [ ". join(', ', @param). " ]\n" + if $DEBUG > 2; + $ins_sth->execute( @param ) + or return $ins_sth->errstr; + + #next unless keys %{ $child_tables{$child_table} }; + next unless $sequence; + + #another section of that laziness + my $seq_sql = "SELECT currval('$sequence')"; + my $seq_sth = dbh->prepare($seq_sql) or return dbh->errstr; + $seq_sth->execute or return $seq_sth->errstr; + my $insertid = $seq_sth->fetchrow_arrayref->[0]; + + # don't drink soap! recurse! recurse! okay! + my $error = + _copy_skel( $child_table_def, + $row->{$child_pkey}, #sourceid + $insertid, #destid + %{ $child_tables{$child_table_def} }, + ); + return $error if $error; + + } + + } + + return ''; + +} + =item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ] Like the insert method on an existing record, this method orders a package @@ -478,7 +559,7 @@ Currently available options are: I<depend_jobnum> and I<noexport>. If I<depend_jobnum> is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). This can be used to defer provisioning until some action completes (such -as running the customer's credit card sucessfully). +as running the customer's credit card successfully). The I<noexport> option is deprecated. If I<noexport> is set true, no provisioning jobs (exports) are scheduled. (You can schedule them later with @@ -546,21 +627,23 @@ sub order_pkgs { ''; #no error } -=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF ] +=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF, UPBYTEREF, DOWNBYTEREF ] Recharges this (existing) customer with the specified prepaid card (see L<FS::prepay_credit>), specified either by I<identifier> or as an FS::prepay_credit object. If there is an error, returns the error, otherwise returns false. -Optionally, two scalar references can be passed as well. They will have their -values filled in with the amount and number of seconds applied by this prepaid +Optionally, four scalar references can be passed as well. They will have their +values filled in with the amount, number of seconds, and number of upload and +download bytes applied by this prepaid card. =cut sub recharge_prepay { - my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + my( $self, $prepay_credit, $amountref, $secondsref, + $upbytesref, $downbytesref, $totalbytesref ) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -573,10 +656,14 @@ sub recharge_prepay { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my( $amount, $seconds ) = ( 0, 0 ); + my( $amount, $seconds, $upbytes, $downbytes, $totalbytes) = ( 0, 0, 0, 0, 0 ); - my $error = $self->get_prepay($prepay_credit, \$amount, \$seconds) + my $error = $self->get_prepay($prepay_credit, \$amount, + \$seconds, \$upbytes, \$downbytes, \$totalbytes) || $self->increment_seconds($seconds) + || $self->increment_upbytes($upbytes) + || $self->increment_downbytes($downbytes) + || $self->increment_totalbytes($totalbytes) || $self->insert_cust_pay_prepay( $amount, ref($prepay_credit) ? $prepay_credit->identifier @@ -590,6 +677,9 @@ sub recharge_prepay { if ( defined($amountref) ) { $$amountref = $amount; } if ( defined($secondsref) ) { $$secondsref = $seconds; } + if ( defined($upbytesref) ) { $$upbytesref = $upbytes; } + if ( defined($downbytesref) ) { $$downbytesref = $downbytes; } + if ( defined($totalbytesref) ) { $$totalbytesref = $totalbytes; } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -613,7 +703,8 @@ If there is an error, returns the error, otherwise returns false. sub get_prepay { - my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + my( $self, $prepay_credit, $amountref, $secondsref, + $upref, $downref, $totalref) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -660,12 +751,51 @@ sub get_prepay { $$amountref += $prepay_credit->amount; $$secondsref += $prepay_credit->seconds; + $$upref += $prepay_credit->upbytes; + $$downref += $prepay_credit->downbytes; + $$totalref += $prepay_credit->totalbytes; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } +=item increment_upbytes SECONDS + +Updates this customer's single or primary account (see L<FS::svc_acct>) by +the specified number of upbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_upbytes { + _increment_column( shift, 'upbytes', @_); +} + +=item increment_downbytes SECONDS + +Updates this customer's single or primary account (see L<FS::svc_acct>) by +the specified number of downbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_downbytes { + _increment_column( shift, 'downbytes', @_); +} + +=item increment_totalbytes SECONDS + +Updates this customer's single or primary account (see L<FS::svc_acct>) by +the specified number of totalbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_totalbytes { + _increment_column( shift, 'totalbytes', @_); +} + =item increment_seconds SECONDS Updates this customer's single or primary account (see L<FS::svc_acct>) by @@ -675,10 +805,24 @@ otherwise returns false. =cut sub increment_seconds { - my( $self, $seconds ) = @_; - warn "$me increment_seconds called: $seconds seconds\n" + _increment_column( shift, 'seconds', @_); +} + +=item _increment_column AMOUNT + +Updates this customer's single or primary account (see L<FS::svc_acct>) by +the specified number of seconds or bytes. If there is an error, returns +the error, otherwise returns false. + +=cut + +sub _increment_column { + my( $self, $column, $amount ) = @_; + warn "$me increment_column called: $column, $amount\n" if $DEBUG; + return '' unless $amount; + my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') } $self->ncancelled_pkgs; @@ -708,7 +852,8 @@ sub increment_seconds { ' ('. $svc_acct->email. ")\n" if $DEBUG > 1; - $svc_acct->increment_seconds($seconds); + $column = "increment_$column"; + $svc_acct->$column($amount); } @@ -866,7 +1011,9 @@ sub delete { my %hash = $cust_pkg->hash; $hash{'custnum'} = $new_custnum; my $new_cust_pkg = new FS::cust_pkg ( \%hash ); - my $error = $new_cust_pkg->replace($cust_pkg); + my $error = $new_cust_pkg->replace($cust_pkg, + options => { $cust_pkg->options }, + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -921,6 +1068,8 @@ sub replace { my $self = shift; my $old = shift; my @param = @_; + warn "$me replace called\n" + if $DEBUG; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -929,26 +1078,24 @@ sub replace { local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; - # If the mask is blank then try to set it - if we can... - if (!defined($self->getfield('paymask')) || $self->getfield('paymask') eq '') { - $self->paymask($self->payinfo); - } - # We absolutely have to have an old vs. new record to make this work. if (!defined($old)) { $old = qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } - if ( $self->payby eq 'COMP' && $self->payby ne $old->payby - && $conf->config('users-allow_comp') ) { - return "You are not permitted to create complimentary accounts." - unless grep { $_ eq getotaker } $conf->config('users-allow_comp'); + my $curuser = $FS::CurrentUser::CurrentUser; + if ( $self->payby eq 'COMP' + && $self->payby ne $old->payby + && ! $curuser->access_right('Complimentary customer') + ) + { + return "You are not permitted to create complimentary accounts."; } local($ignore_expired_card) = 1 if $old->payby =~ /^(CARD|DCRD)$/ && $self->payby =~ /^(CARD|DCRD)$/ - && $old->payinfo eq $self->payinfo; + && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask ); my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; @@ -1015,15 +1162,19 @@ sub queue_fuzzyfiles_update { my $dbh = dbh; my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - my $error = $queue->insert($self->getfield('last'), $self->company); + my $error = $queue->insert( map $self->getfield($_), + qw(first last company) + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; } - if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) { + if ( $self->ship_last ) { $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('ship_last'), $self->ship_company); + $error = $queue->insert( map $self->getfield("ship_$_"), + qw(first last company) + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -1052,9 +1203,12 @@ sub check { my $error = $self->ut_numbern('custnum') || $self->ut_number('agentnum') + || $self->ut_textn('agent_custid') || $self->ut_number('refnum') || $self->ut_name('last') || $self->ut_name('first') + || $self->ut_snumbern('birthdate') + || $self->ut_snumbern('signupdate') || $self->ut_textn('company') || $self->ut_text('address1') || $self->ut_textn('address2') @@ -1169,7 +1323,10 @@ sub check { } } - $self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/ + #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/ + # or return "Illegal payby: ". $self->payby; + #$self->payby($1); + FS::payby->can_payby($self->table, $self->payby) or return "Illegal payby: ". $self->payby; $error = $self->ut_numbern('paystart_month') @@ -1194,8 +1351,6 @@ sub check { $check_payinfo = 0; } - $self->payby($1); - if ( $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) { my $payinfo = $self->payinfo; @@ -1211,22 +1366,25 @@ sub check { if cardtype($self->payinfo) eq "Unknown"; my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref); - return "Banned credit card" if $ban; - - if ( defined $self->dbdef_table->column('paycvv') ) { - if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) { - if ( cardtype($self->payinfo) eq 'American Express card' ) { - $self->paycvv =~ /^(\d{4})$/ - or return "CVV2 (CID) for American Express cards is four digits."; - $self->paycvv($1); - } else { - $self->paycvv =~ /^(\d{3})$/ - or return "CVV2 (CVC2/CID) is three digits."; - $self->paycvv($1); - } + if ( $ban ) { + return 'Banned credit card: banned on '. + time2str('%a %h %o at %r', $ban->_date). + ' by '. $ban->otaker. + ' (ban# '. $ban->bannum. ')'; + } + + if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) { + if ( cardtype($self->payinfo) eq 'American Express card' ) { + $self->paycvv =~ /^(\d{4})$/ + or return "CVV2 (CID) for American Express cards is four digits."; + $self->paycvv($1); } else { - $self->paycvv(''); + $self->paycvv =~ /^(\d{3})$/ + or return "CVV2 (CVC2/CID) is three digits."; + $self->paycvv($1); } + } else { + $self->paycvv(''); } my $cardtype = cardtype($payinfo); @@ -1257,13 +1415,23 @@ sub check { my $payinfo = $self->payinfo; $payinfo =~ s/[^\d\@]//g; - $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba'; - $payinfo = "$1\@$2"; + if ( $conf->exists('echeck-nonus') ) { + $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba'; + $payinfo = "$1\@$2"; + } else { + $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba'; + $payinfo = "$1\@$2"; + } $self->payinfo($payinfo); - $self->paycvv('') if $self->dbdef_table->column('paycvv'); + $self->paycvv(''); my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref); - return "Banned ACH account" if $ban; + if ( $ban ) { + return 'Banned ACH account: banned on '. + time2str('%a %h %o at %r', $ban->_date). + ' by '. $ban->otaker. + ' (ban# '. $ban->bannum. ')'; + } } elsif ( $self->payby eq 'LECB' ) { @@ -1272,24 +1440,27 @@ sub check { $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number'; $payinfo = $1; $self->payinfo($payinfo); - $self->paycvv('') if $self->dbdef_table->column('paycvv'); + $self->paycvv(''); } elsif ( $self->payby eq 'BILL' ) { $error = $self->ut_textn('payinfo'); return "Illegal P.O. number: ". $self->payinfo if $error; - $self->paycvv('') if $self->dbdef_table->column('paycvv'); + $self->paycvv(''); } elsif ( $self->payby eq 'COMP' ) { - if ( !$self->custnum && $conf->config('users-allow_comp') ) { + my $curuser = $FS::CurrentUser::CurrentUser; + if ( ! $self->custnum + && ! $curuser->access_right('Complimentary customer') + ) + { return "You are not permitted to create complimentary accounts." - unless grep { $_ eq getotaker } $conf->config('users-allow_comp'); } $error = $self->ut_textn('payinfo'); return "Illegal comp account issuer: ". $self->payinfo if $error; - $self->paycvv('') if $self->dbdef_table->column('paycvv'); + $self->paycvv(''); } elsif ( $self->payby eq 'PREPAY' ) { @@ -1300,12 +1471,12 @@ sub check { return "Illegal prepayment identifier: ". $self->payinfo if $error; return "Unknown prepayment identifier" unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } ); - $self->paycvv('') if $self->dbdef_table->column('paycvv'); + $self->paycvv(''); } if ( $self->paydate eq '' || $self->paydate eq '-' ) { - return "Expriation date required" + return "Expiration date required" unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD)$/; $self->paydate(''); } else { @@ -1336,8 +1507,10 @@ sub check { $self->payname($1); } - $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax; - $self->tax($1); + foreach my $flag (qw( tax spool_cdr )) { + $self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag(); + $self->$flag($1); + } $self->otaker(getotaker) unless $self->otaker; @@ -1355,11 +1528,17 @@ Returns all packages (see L<FS::cust_pkg>) for this customer. sub all_pkgs { my $self = shift; + + return $self->num_pkgs unless wantarray; + + my @cust_pkg = (); if ( $self->{'_pkgnum'} ) { - values %{ $self->{'_pkgnum'}->cache }; + @cust_pkg = values %{ $self->{'_pkgnum'}->cache }; } else { - qsearch( 'cust_pkg', { 'custnum' => $self->custnum }); + @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum }); } + + sort sort_packages @cust_pkg; } =item ncancelled_pkgs @@ -1370,19 +1549,43 @@ Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer. sub ncancelled_pkgs { my $self = shift; + + return $self->num_ncancelled_pkgs unless wantarray; + + my @cust_pkg = (); if ( $self->{'_pkgnum'} ) { - grep { ! $_->getfield('cancel') } values %{ $self->{'_pkgnum'}->cache }; + + @cust_pkg = grep { ! $_->getfield('cancel') } + values %{ $self->{'_pkgnum'}->cache }; + } else { - @{ [ # force list context + + @cust_pkg = qsearch( 'cust_pkg', { - 'custnum' => $self->custnum, - 'cancel' => '', - }), + 'custnum' => $self->custnum, + 'cancel' => '', + }); + push @cust_pkg, qsearch( 'cust_pkg', { - 'custnum' => $self->custnum, - 'cancel' => 0, - }), - ] }; + 'custnum' => $self->custnum, + 'cancel' => 0, + }); + } + + sort sort_packages @cust_pkg; + +} + +# This should be generalized to use config options to determine order. +sub sort_packages { + if ( $a->get('cancel') and $b->get('cancel') ) { + $a->pkgnum <=> $b->pkgnum; + } elsif ( $a->get('cancel') or $b->get('cancel') ) { + return -1 if $b->get('cancel'); + return 1 if $a->get('cancel'); + return 0; + } else { + $a->pkgnum <=> $b->pkgnum; } } @@ -1431,14 +1634,18 @@ customer. =cut sub num_cancelled_pkgs { - my $self = shift; - $self->num_pkgs("cancel IS NOT NULL AND cust_pkg.cancel != 0"); + shift->num_pkgs("cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0"); +} + +sub num_ncancelled_pkgs { + shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )"); } sub num_pkgs { my( $self, $sql ) = @_; + $sql = "AND $sql" if $sql && $sql !~ /^\s*$/ && $sql !~ /^\s*AND/i; my $sth = dbh->prepare( - "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? AND $sql" + "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? $sql" ) or die dbh->errstr; $sth->execute($self->custnum) or die $sth->errstr; $sth->fetchrow_arrayref->[0]; @@ -1467,7 +1674,7 @@ Returns a list: an empty list on success or a list of errors. sub suspend { my $self = shift; - grep { $_->suspend } $self->unsuspended_pkgs; + grep { $_->suspend(@_) } $self->unsuspended_pkgs; } =item suspend_if_pkgpart PKGPART [ , PKGPART ... ] @@ -1481,8 +1688,14 @@ Returns a list: an empty list on success or a list of errors. sub suspend_if_pkgpart { my $self = shift; - my @pkgparts = @_; - grep { $_->suspend } + my (@pkgparts, %opt); + if (ref($_[0]) eq 'HASH'){ + @pkgparts = @{$_[0]{pkgparts}}; + %opt = %{$_[0]}; + }else{ + @pkgparts = @_; + } + grep { $_->suspend(%opt) } grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts } $self->unsuspended_pkgs; } @@ -1498,8 +1711,14 @@ Returns a list: an empty list on success or a list of errors. sub suspend_unless_pkgpart { my $self = shift; - my @pkgparts = @_; - grep { $_->suspend } + my (@pkgparts, %opt); + if (ref($_[0]) eq 'HASH'){ + @pkgparts = @{$_[0]{pkgparts}}; + %opt = %{$_[0]}; + }else{ + @pkgparts = @_; + } + grep { $_->suspend(%opt) } grep { my $pkgpart = $_->pkgpart; ! grep { $pkgpart eq $_ } @pkgparts } $self->unsuspended_pkgs; } @@ -1554,10 +1773,26 @@ sub _banned_pay_hashref { { 'payby' => $payby2ban{$self->payby}, 'payinfo' => md5_base64($self->payinfo), - #'reason' => + #don't ever *search* on reason! #'reason' => }; } +=item notes + +Returns all notes (see L<FS::cust_main_note>) for this customer. + +=cut + +sub notes { + my $self = shift; + #order by? + qsearch( 'cust_main_note', + { 'custnum' => $self->custnum }, + '', + 'ORDER BY _DATE DESC' + ); +} + =item agent Returns the agent (see L<FS::agent>) for this customer. @@ -1617,17 +1852,30 @@ sub bill { $self->select_for_update; #mutex + #create a new invoice + #(we'll remove it later if it doesn't actually need to be generated [contains + # no line items] and we're inside a transaciton so nothing else will see it) + my $cust_bill = new FS::cust_bill ( { + 'custnum' => $self->custnum, + '_date' => $time, + #'charged' => $charged, + 'charged' => 0, + } ); + $error = $cust_bill->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice for customer #". $self->custnum. ": $error"; + } + my $invnum = $cust_bill->invnum; + + ### # find the packages which are due for billing, find out how much they are # & generate invoice database. - - my( $total_setup, $total_recur ) = ( 0, 0 ); - #my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); - my @cust_bill_pkg = (); - #my $tax = 0;## - #my $taxable_charged = 0;## - #my $charged = 0;## + ### + my( $total_setup, $total_recur ) = ( 0, 0 ); my %tax; + my @precommit_hooks = (); foreach my $cust_pkg ( qsearch('cust_pkg', { 'custnum' => $self->custnum } ) @@ -1649,22 +1897,28 @@ sub bill { my @details = (); + ### # bill setup + ### + my $setup = 0; if ( !$cust_pkg->setup || $options{'resetup'} ) { warn " bill setup\n" if $DEBUG > 1; - $setup = eval { $cust_pkg->calc_setup( $time ) }; + $setup = eval { $cust_pkg->calc_setup( $time, \@details ) }; if ( $@ ) { $dbh->rollback if $oldAutoCommit; - return $@; + return "$@ running calc_setup for $cust_pkg\n"; } $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup; } - #bill recurring fee + ### + # bill recurring fee + ### + my $recur = 0; my $sdate; if ( $part_pkg->getfield('freq') ne '0' && @@ -1677,10 +1931,13 @@ sub bill { # XXX shared with $recur_prog $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; - $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details ) }; + #over two params! lets at least switch to a hashref for the rest... + my %param = ( 'precommit_hooks' => \@precommit_hooks, ); + + $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) }; if ( $@ ) { $dbh->rollback if $oldAutoCommit; - return $@; + return "$@ running calc_recur for $cust_pkg\n"; } #change this bit to use Date::Manip? CAREFUL with timezones (see @@ -1719,12 +1976,18 @@ sub bill { warn "\$recur is undefined" unless defined($recur); warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill); - if ( $cust_pkg->modified ) { + ### + # If $cust_pkg has been modified, update it and create cust_bill_pkg records + ### + + if ( $cust_pkg->modified ) { # hmmm.. and if the options are modified? warn " package ". $cust_pkg->pkgnum. " modified; updating\n" if $DEBUG >1; - $error=$cust_pkg->replace($old_cust_pkg); + $error=$cust_pkg->replace($old_cust_pkg, + options => { $cust_pkg->options }, + ); if ( $error ) { #just in case $dbh->rollback if $oldAutoCommit; return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error"; @@ -1740,10 +2003,13 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum; } + if ( $setup != 0 || $recur != 0 ) { - warn " charges (setup=$setup, recur=$recur); queueing line items\n" + + warn " charges (setup=$setup, recur=$recur); adding line items\n" if $DEBUG > 1; my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'invnum' => $invnum, 'pkgnum' => $cust_pkg->pkgnum, 'setup' => $setup, 'recur' => $recur, @@ -1751,35 +2017,40 @@ sub bill { 'edate' => $cust_pkg->bill, 'details' => \@details, }); - push @cust_bill_pkg, $cust_bill_pkg; + $error = $cust_bill_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice line item for invoice #$invnum: $error"; + } $total_setup += $setup; $total_recur += $recur; + ### + # handle taxes + ### + unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - my @taxes = qsearch( 'cust_main_county', { - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - 'taxclass' => $part_pkg->taxclass, - } ); + my $prefix = + ( $conf->exists('tax-ship_address') && length($self->ship_last) ) + ? 'ship_' + : ''; + my %taxhash = map { $_ => $self->get("$prefix$_") } + qw( state county country ); + + $taxhash{'taxclass'} = $part_pkg->taxclass; + + my @taxes = qsearch( 'cust_main_county', \%taxhash ); + unless ( @taxes ) { - @taxes = qsearch( 'cust_main_county', { - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - 'taxclass' => '', - } ); + $taxhash{'taxclass'} = ''; + @taxes = qsearch( 'cust_main_county', \%taxhash ); } #one more try at a whole-country tax rate unless ( @taxes ) { - @taxes = qsearch( 'cust_main_county', { - 'state' => '', - 'county' => '', - 'country' => $self->country, - 'taxclass' => '', - } ); + $taxhash{$_} = '' foreach qw( state county ); + @taxes = qsearch( 'cust_main_county', \%taxhash ); } # maybe eliminate this entirely, along with all the 0% records @@ -1787,8 +2058,10 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "fatal: can't find tax rate for state/county/country/taxclass ". - join('/', ( map $self->$_(), qw(state county country) ), - $part_pkg->taxclass ). "\n"; + join('/', ( map $self->get("$prefix$_"), + qw(state county country) + ), + $part_pkg->taxclass ). "\n"; } foreach my $tax ( @taxes ) { @@ -1803,7 +2076,8 @@ sub bill { next unless $taxable_charged; if ( $tax->exempt_amount && $tax->exempt_amount > 0 ) { - my ($mon,$year) = (localtime($sdate) )[4,5]; + #my ($mon,$year) = (localtime($sdate) )[4,5]; + my ($mon,$year) = (localtime( $sdate || $cust_bill->_date ) )[4,5]; $mon++; my $freq = $part_pkg->freq || 1; if ( $freq !~ /(\d+)$/ ) { @@ -1811,40 +2085,74 @@ sub bill { return "daily/weekly package definitions not (yet?)". " compatible with monthly tax exemptions"; } - my $taxable_per_month = sprintf("%.2f", $taxable_charged / $freq ); + my $taxable_per_month = + sprintf("%.2f", $taxable_charged / $freq ); + + #call the whole thing off if this customer has any old + #exemption records... + my @cust_tax_exempt = + qsearch( 'cust_tax_exempt' => { custnum=> $self->custnum } ); + if ( @cust_tax_exempt ) { + $dbh->rollback if $oldAutoCommit; + return + 'this customer still has old-style tax exemption records; '. + 'run bin/fs-migrate-cust_tax_exempt?'; + } + foreach my $which_month ( 1 .. $freq ) { - my %hash = ( - 'custnum' => $self->custnum, - 'taxnum' => $tax->taxnum, - 'year' => 1900+$year, - 'month' => $mon++, - ); - #until ( $mon < 12 ) { $mon -= 12; $year++; } - until ( $mon < 13 ) { $mon -= 12; $year++; } - my $cust_tax_exempt = - qsearchs('cust_tax_exempt', \%hash) - || new FS::cust_tax_exempt( { %hash, 'amount' => 0 } ); - my $remaining_exemption = sprintf("%.2f", - $tax->exempt_amount - $cust_tax_exempt->amount ); + + #maintain the new exemption table now + my $sql = " + SELECT SUM(amount) + FROM cust_tax_exempt_pkg + LEFT JOIN cust_bill_pkg USING ( billpkgnum ) + LEFT JOIN cust_bill USING ( invnum ) + WHERE custnum = ? + AND taxnum = ? + AND year = ? + AND month = ? + "; + my $sth = dbh->prepare($sql) or do { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't lookup exising exemption: ". dbh->errstr; + }; + $sth->execute( + $self->custnum, + $tax->taxnum, + 1900+$year, + $mon, + ) or do { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't lookup exising exemption: ". dbh->errstr; + }; + my $existing_exemption = $sth->fetchrow_arrayref->[0] || 0; + + my $remaining_exemption = + $tax->exempt_amount - $existing_exemption; if ( $remaining_exemption > 0 ) { my $addl = $remaining_exemption > $taxable_per_month ? $taxable_per_month : $remaining_exemption; $taxable_charged -= $addl; - my $new_cust_tax_exempt = new FS::cust_tax_exempt ( { - $cust_tax_exempt->hash, - 'amount' => - sprintf("%.2f", $cust_tax_exempt->amount + $addl), + + my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg ( { + 'billpkgnum' => $cust_bill_pkg->billpkgnum, + 'taxnum' => $tax->taxnum, + 'year' => 1900+$year, + 'month' => $mon, + 'amount' => sprintf("%.2f", $addl ), } ); - $error = $new_cust_tax_exempt->exemptnum - ? $new_cust_tax_exempt->replace($cust_tax_exempt) - : $new_cust_tax_exempt->insert; + $error = $cust_tax_exempt_pkg->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "fatal: can't update cust_tax_exempt: $error"; + return "fatal: can't insert cust_tax_exempt_pkg: $error"; } - } # if $remaining_exemption > 0 + + #++ + $mon++; + #until ( $mon < 12 ) { $mon -= 12; $year++; } + until ( $mon < 13 ) { $mon -= 12; $year++; } } #foreach $which_month @@ -1866,85 +2174,50 @@ sub bill { } #foreach my $cust_pkg - my $charged = sprintf( "%.2f", $total_setup + $total_recur ); -# my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); - - unless ( @cust_bill_pkg ) { #don't create invoices with no line items + unless ( $cust_bill->cust_bill_pkg ) { + $cust_bill->delete; #don't create an invoice w/o line items $dbh->commit or die $dbh->errstr if $oldAutoCommit; return ''; - } - -# unless ( $self->tax =~ /Y/i -# || $self->payby eq 'COMP' -# || $taxable_charged == 0 ) { -# my $cust_main_county = qsearchs('cust_main_county',{ -# 'state' => $self->state, -# 'county' => $self->county, -# 'country' => $self->country, -# } ) or die "fatal: can't find tax rate for state/county/country ". -# $self->state. "/". $self->county. "/". $self->country. "\n"; -# my $tax = sprintf( "%.2f", -# $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) -# ); - - if ( dbdef->table('cust_bill_pkg')->column('itemdesc') ) { #1.5 schema - - foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) { - my $tax = sprintf("%.2f", $tax{$taxname} ); - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - 'itemdesc' => $taxname, - }); - push @cust_bill_pkg, $cust_bill_pkg; - } + } + + my $charged = sprintf( "%.2f", $total_setup + $total_recur ); + + foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) { + my $tax = sprintf("%.2f", $tax{$taxname} ); + $charged = sprintf( "%.2f", $charged+$tax ); - } else { #1.4 schema - - my $tax = 0; - foreach ( values %tax ) { $tax += $_ }; - $tax = sprintf("%.2f", $tax); - if ( $tax > 0 ) { - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - }); - push @cust_bill_pkg, $cust_bill_pkg; + my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'invnum' => $invnum, + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + 'itemdesc' => $taxname, + }); + $error = $cust_bill_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice line item for invoice #$invnum: $error"; } + $total_setup += $tax; } - my $cust_bill = new FS::cust_bill ( { - 'custnum' => $self->custnum, - '_date' => $time, - 'charged' => $charged, - } ); - $error = $cust_bill->insert; + $cust_bill->charged( sprintf( "%.2f", $total_setup + $total_recur ) ); + $error = $cust_bill->replace; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't create invoice for customer #". $self->custnum. ": $error"; + return "can't update charged for invoice #$invnum: $error"; } - my $invnum = $cust_bill->invnum; - my $cust_bill_pkg; - foreach $cust_bill_pkg ( @cust_bill_pkg ) { - #warn $invnum; - $cust_bill_pkg->invnum($invnum); - $error = $cust_bill_pkg->insert; - if ( $error ) { + foreach my $hook ( @precommit_hooks ) { + eval { + &{$hook}; #($self) ? + }; + if ( $@ ) { $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item for customer #". $self->custnum. - ": $error"; + return "$@ running precommit hook $hook\n"; } } @@ -1977,16 +2250,12 @@ for conversion functions. retry - Retry card/echeck/LEC transactions even when not scheduled by invoice events. -retry_card - Deprecated alias for 'retry' - -batch_card - This option is deprecated. See the invoice events web interface -to control whether cards are batched or run against a realtime gateway. +quiet - set true to surpress email card/ACH decline notices. -report_badcard - This option is deprecated. +freq - "1d" for the traditional, daily events (the default), or "1m" for the +new monthly events -force_print - This option is deprecated; see the invoice events web interface. - -quiet - set true to surpress email card/ACH decline notices. +payby - allows for one time override of normal customer billing method =cut @@ -2028,6 +2297,13 @@ sub collect { } } + my $extra_sql = ''; + if ( defined $options{'freq'} && $options{'freq'} eq '1m' ) { + $extra_sql = " AND freq = '1m' "; + } else { + $extra_sql = " AND ( freq = '1d' OR freq IS NULL OR freq = '' ) "; + } + foreach my $cust_bill ( $self->open_cust_bill ) { # don't try to charge for the same invoice if it's already in a batch @@ -2038,72 +2314,28 @@ sub collect { warn " invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")\n" if $DEBUG > 1; - foreach my $part_bill_event ( - sort { $a->seconds <=> $b->seconds - || $a->weight <=> $b->weight - || $a->eventpart <=> $b->eventpart } - grep { $_->seconds <= ( $invoice_time - $cust_bill->_date ) - && ! qsearch( 'cust_bill_event', { - 'invnum' => $cust_bill->invnum, - 'eventpart' => $_->eventpart, - 'status' => 'done', - } ) - } - qsearch('part_bill_event', { 'payby' => $self->payby, - 'disabled' => '', } ) - ) { + foreach my $part_bill_event ( due_events ( $cust_bill, + exists($options{'payby'}) + ? $options{'payby'} + : $self->payby, + $invoice_time, + $extra_sql ) ) { last if $cust_bill->owed <= 0 # don't run subsequent events if owed<=0 || $self->balance <= 0; # or if balance<=0 - warn " calling invoice event (". $part_bill_event->eventcode. ")\n" - if $DEBUG > 1; - my $cust_main = $self; #for callback - - my $error; { local $realtime_bop_decline_quiet = 1 if $options{'quiet'}; - local $SIG{__DIE__}; # don't want Mason __DIE__ handler active - $error = eval $part_bill_event->eventcode; - } - - my $status = ''; - my $statustext = ''; - if ( $@ ) { - $status = 'failed'; - $statustext = $@; - } elsif ( $error ) { - $status = 'done'; - $statustext = $error; - } else { - $status = 'done' - } - - #add cust_bill_event - my $cust_bill_event = new FS::cust_bill_event { - 'invnum' => $cust_bill->invnum, - 'eventpart' => $part_bill_event->eventpart, - #'_date' => $invoice_time, - '_date' => time, - 'status' => $status, - 'statustext' => $statustext, - }; - $error = $cust_bill_event->insert; - if ( $error ) { - #$dbh->rollback if $oldAutoCommit; - #return "error: $error"; + warn " do_event " . $cust_bill . " ". (%options) . "\n" + if $DEBUG > 1; - # gah, even with transactions. - $dbh->commit if $oldAutoCommit; #well. - my $e = 'WARNING: Event run but database not updated - '. - 'error inserting cust_bill_event, invnum #'. $cust_bill->invnum. - ', eventpart '. $part_bill_event->eventpart. - ": $error"; - warn $e; - return $e; + if (my $error = $part_bill_event->do_event($cust_bill, %options)) { + # gah, even with transactions. + $dbh->commit if $oldAutoCommit; #well. + return $error; + } } - } } @@ -2115,9 +2347,10 @@ sub collect { =item retry_realtime -Schedules realtime credit card / electronic check / LEC billing events for -for retry. Useful if card information has changed or manual retry is desired. -The 'collect' method must be called to actually retry the transaction. +Schedules realtime / batch credit card / electronic check / LEC billing +events for for retry. Useful if card information has changed or manual +retry is desired. The 'collect' method must be called to actually retry +the transaction. Implementation details: For each of this customer's open invoices, changes the status of the first "done" (with statustext error) realtime processing @@ -2148,7 +2381,7 @@ sub retry_realtime { grep { #$_->part_bill_event->plan eq 'realtime-card' $_->part_bill_event->eventcode =~ - /\$cust_bill\->realtime_(card|ach|lec)/ + /\$cust_bill\->(batch|realtime)_(card|ach|lec)/ && $_->status eq 'done' && $_->statustext } @@ -2184,7 +2417,7 @@ if set, will override the value from the customer record. I<description> is a free-text field passed to the gateway. It defaults to "Internet services". -If an I<invnum> is specified, this payment (if sucessful) is applied to the +If an I<invnum> is specified, this payment (if successful) is applied to the specified invoice. If you don't specify an I<invnum> you might want to call the B<apply_payments> method. @@ -2298,8 +2531,9 @@ sub realtime_bop { $payname = "$payfirst $paylast"; } - my @invoicing_list = grep { $_ ne 'POST' } $self->invoicing_list; - if ( $conf->exists('emailinvoiceauto') + my @invoicing_list = $self->invoicing_list_emailonly; + if ( $conf->exists('emailinvoiceautoalways') + || $conf->exists('emailinvoiceauto') && ! @invoicing_list || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) { push @invoicing_list, $self->all_emails; } @@ -2316,6 +2550,9 @@ sub realtime_bop { $content{customer_ip} = $payip if length($payip); + $content{invoice_number} = $options{'invnum'} + if exists($options{'invnum'}) && length($options{'invnum'}); + if ( $method eq 'CC' ) { $content{card_number} = $payinfo; @@ -2351,8 +2588,13 @@ sub realtime_bop { if qsearch('cust_pay', { 'custnum' => $self->custnum, 'payby' => 'CARD', 'payinfo' => $payinfo, + } ) + || qsearch('cust_pay', { 'custnum' => $self->custnum, + 'payby' => 'CARD', + 'paymask' => $self->mask_payinfo('CARD', $payinfo), } ); + } elsif ( $method eq 'ECHECK' ) { ( $content{account_number}, $content{routing_code} ) = split('@', $payinfo); @@ -2381,7 +2623,7 @@ sub realtime_bop { 'action' => $action1, 'description' => $options{'description'}, 'amount' => $amount, - 'invoice_number' => $options{'invnum'}, + #'invoice_number' => $options{'invnum'}, 'customer_id' => $self->custnum, 'last_name' => $paylast, 'first_name' => $payfirst, @@ -2427,7 +2669,8 @@ sub realtime_bop { description => $options{'description'}, ); - foreach my $field (qw( authorization_source_code returned_ACI transaction_identifier validation_code + foreach my $field (qw( authorization_source_code returned_ACI + transaction_identifier validation_code transaction_sequence_num local_transaction_date local_transaction_time AVS_result_code )) { $capture{$field} = $transaction->$field() if $transaction->can($field); @@ -2438,7 +2681,7 @@ sub realtime_bop { $capture->submit(); unless ( $capture->is_success ) { - my $e = "Authorization sucessful but capture failed, custnum #". + my $e = "Authorization successful but capture failed, custnum #". $self->custnum. ': '. $capture->result_code. ": ". $capture->error_message; warn $e; @@ -2495,10 +2738,12 @@ sub realtime_bop { 'payinfo' => $payinfo, 'paybatch' => $paybatch, } ); - my $error = $cust_pay->insert; + my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () ); if ( $error ) { $cust_pay->invnum(''); #try again with no specific invnum - my $error2 = $cust_pay->insert; + my $error2 = $cust_pay->insert( $options{'manual'} ? + ( 'manual' => 1 ) : () + ); if ( $error2 ) { # gah, even with transactions. my $e = 'WARNING: Card/ACH debited but database not updated - '. @@ -2618,7 +2863,7 @@ gateway is attempted. #I<zip>, I<payinfo> and I<paydate> are also available. Any of these options, #if set, will override the value from the customer record. -#If an I<invnum> is specified, this payment (if sucessful) is applied to the +#If an I<invnum> is specified, this payment (if successful) is applied to the #specified invoice. If you don't specify an I<invnum> you might want to #call the B<apply_payments> method. @@ -2775,6 +3020,23 @@ sub realtime_refund_bop { $payname = "$payfirst $paylast"; } + my @invoicing_list = $self->invoicing_list_emailonly; + if ( $conf->exists('emailinvoiceautoalways') + || $conf->exists('emailinvoiceauto') && ! @invoicing_list + || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) { + push @invoicing_list, $self->all_emails; + } + + my $email = ($conf->exists('business-onlinepayment-email-override')) + ? $conf->config('business-onlinepayment-email-override') + : $invoicing_list[0]; + + my $payip = exists($options{'payip'}) + ? $options{'payip'} + : $self->payip; + $content{customer_ip} = $payip + if length($payip); + my $payinfo = ''; if ( $method eq 'CC' ) { @@ -2813,6 +3075,8 @@ sub realtime_refund_bop { 'state' => $self->state, 'zip' => $self->zip, 'country' => $self->country, + 'email' => $email, + 'phone' => $self->daytime || $self->night, %content, #after ); warn join('', map { " $_ => $sub_content{$_}\n" } keys %sub_content ) @@ -2832,7 +3096,7 @@ sub realtime_refund_bop { $paybatch .= ':'. $refund->order_number if $refund->can('order_number') && $refund->order_number; - while ( $cust_pay && $cust_pay->unappled < $amount ) { + while ( $cust_pay && $cust_pay->unapplied < $amount ) { my @cust_bill_pay = $cust_pay->cust_bill_pay; last unless @cust_bill_pay; my $cust_bill_pay = pop @cust_bill_pay; @@ -2902,6 +3166,24 @@ sub total_owed_date { sprintf( "%.2f", $total_bill ); } +=item apply_payments_and_credits + +Applies unapplied payments and credits. + +In most cases, this new method should be used in place of sequential +apply_payments and apply_credits methods. + +=cut + +sub apply_payments_and_credits { + my $self = shift; + + foreach my $cust_bill ( $self->open_cust_bill ) { + $cust_bill->apply_payments_and_credits; + } + +} + =item apply_credits OPTION => VALUE ... Applies (see L<FS::cust_credit_bill>) unapplied credits (see L<FS::cust_credit>) @@ -3074,6 +3356,29 @@ sub balance_date { ); } +=item in_transit_payments + +Returns the total of requests for payments for this customer pending in +batches in transit to the bank. See L<FS::pay_batch> and L<FS::cust_pay_batch> + +=cut + +sub in_transit_payments { + my $self = shift; + my $in_transit_payments = 0; + foreach my $pay_batch ( qsearch('pay_batch', { + 'status' => 'I', + } ) ) { + foreach my $cust_pay_batch ( qsearch('cust_pay_batch', { + 'batchnum' => $pay_batch->batchnum, + 'custnum' => $self->custnum, + } ) ) { + $in_transit_payments += $cust_pay_batch->amount; + } + } + sprintf( "%.2f", $in_transit_payments ); +} + =item paydate_monthyear Returns a two-element list consisting of the month and year of this customer's @@ -3092,21 +3397,6 @@ sub paydate_monthyear { } } -=item payinfo_masked - -Returns a "masked" payinfo field appropriate to the payment type. Masked characters are replaced by 'x'es. Use this to display publicly accessable account Information. - -Credit Cards - Mask all but the last four characters. -Checks - Mask all but last 2 of account number and bank routing number. -Others - Do nothing, return the unmasked string. - -=cut - -sub payinfo_masked { - my $self = shift; - return $self->paymask; -} - =item invoicing_list [ ARRAYREF ] If an arguement is given, sets these email addresses as invoice recipients @@ -3124,6 +3414,7 @@ This interface may change in the future. sub invoicing_list { my( $self, $arrayref ) = @_; + if ( $arrayref ) { my @cust_main_invoice; if ( $self->custnum ) { @@ -3158,12 +3449,14 @@ sub invoicing_list { warn $error if $error; } } + if ( $self->custnum ) { map { $_->address } qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } ); } else { (); } + } =item check_invoicing_list ARRAYREF @@ -3241,6 +3534,34 @@ sub invoicing_list_addpost { $self->invoicing_list(\@invoicing_list); } +=item invoicing_list_emailonly + +Returns the list of email invoice recipients (invoicing_list without non-email +destinations such as POST and FAX). + +=cut + +sub invoicing_list_emailonly { + my $self = shift; + warn "$me invoicing_list_emailonly called" + if $DEBUG; + grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list; +} + +=item invoicing_list_emailonly_scalar + +Returns the list of email invoice recipients (invoicing_list without non-email +destinations such as POST and FAX) as a comma-separated scalar. + +=cut + +sub invoicing_list_emailonly_scalar { + my $self = shift; + warn "$me invoicing_list_emailonly_scalar called" + if $DEBUG; + join(', ', $self->invoicing_list_emailonly); +} + =item referral_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ] Returns an array of customers referred by this customer (referral_custnum set @@ -3336,10 +3657,22 @@ the error, otherwise returns false. =cut sub charge { - my ( $self, $amount ) = ( shift, shift ); - my $pkg = @_ ? shift : 'One-time charge'; - my $comment = @_ ? shift : '$'. sprintf("%.2f",$amount); - my $taxclass = @_ ? shift : ''; + my $self = shift; + my ( $amount, $pkg, $comment, $taxclass, $additional ); + if ( ref( $_[0] ) ) { + $amount = $_[0]->{amount}; + $pkg = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge'; + $comment = exists($_[0]->{comment}) ? $_[0]->{comment} + : '$'. sprintf("%.2f",$amount); + $taxclass = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : ''; + $additional = $_[0]->{additional}; + }else{ + $amount = shift; + $pkg = @_ ? shift : 'One-time charge'; + $comment = @_ ? shift : '$'. sprintf("%.2f",$amount); + $taxclass = @_ ? shift : ''; + $additional = []; + } local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -3355,16 +3688,20 @@ sub charge { my $part_pkg = new FS::part_pkg ( { 'pkg' => $pkg, 'comment' => $comment, - #'setup' => $amount, - #'recur' => '0', 'plan' => 'flat', - 'plandata' => "setup_fee=$amount", 'freq' => 0, 'disabled' => 'Y', 'taxclass' => $taxclass, } ); - my $error = $part_pkg->insert; + my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) } + ( 0 .. @$additional - 1 ) + ), + 'additional_count' => scalar(@$additional), + 'setup_fee' => $amount, + ); + + my $error = $part_pkg->insert( options => \%options ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -3470,18 +3807,6 @@ sub cust_refund { qsearch( 'cust_refund', { 'custnum' => $self->custnum } ) } -=item select_for_update - -Selects this record with the SQL "FOR UPDATE" command. This can be useful as -a mutex. - -=cut - -sub select_for_update { - my $self = shift; - qsearch('cust_main', { 'custnum' => $self->custnum }, '*', 'FOR UPDATE' ); -} - =item name Returns a name string for this customer, either "Company (Last, First)" or @@ -3538,6 +3863,19 @@ sub ship_contact { : $self->contact; } +=item country_full + +Returns this customer's full country name + +=cut + +sub country_full { + my $self = shift; + code2country($self->country); +} + +=item cust_status + =item status Returns a status string for this customer, currently: @@ -3548,6 +3886,8 @@ Returns a status string for this customer, currently: =item active - One or more recurring packages is active +=item inactive - No active recurring packages, but otherwise unsuspended/uncancelled (the inactive status is new - previously inactive customers were mis-identified as cancelled) + =item suspended - All non-cancelled recurring packages are suspended =item cancelled - All recurring packages are cancelled @@ -3556,32 +3896,55 @@ Returns a status string for this customer, currently: =cut -sub status { +sub status { shift->cust_status(@_); } + +sub cust_status { my $self = shift; - for my $status (qw( prospect active suspended cancelled )) { + for my $status (qw( prospect active inactive suspended cancelled )) { my $method = $status.'_sql'; my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g; my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr; - $sth->execute( ($self->custnum) x $numnum ) or die $sth->errstr; + $sth->execute( ($self->custnum) x $numnum ) + or die "Error executing 'SELECT $sql': ". $sth->errstr; return $status if $sth->fetchrow_arrayref->[0]; } } +=item ucfirst_cust_status + +=item ucfirst_status + +Returns the status with the first character capitalized. + +=cut + +sub ucfirst_status { shift->ucfirst_cust_status(@_); } + +sub ucfirst_cust_status { + my $self = shift; + ucfirst($self->cust_status); +} + =item statuscolor Returns a hex triplet color string for this customer's status. =cut -my %statuscolor = ( - 'prospect' => '000000', - 'active' => '00CC00', - 'suspended' => 'FF9900', - 'cancelled' => 'FF0000', +use vars qw(%statuscolor); +%statuscolor = ( + 'prospect' => '7e0079', #'000000', #black? naw, purple + 'active' => '00CC00', #green + 'inactive' => '0000CC', #blue + 'suspended' => 'FF9900', #yellow + 'cancelled' => 'FF0000', #red ); -sub statuscolor { + +sub statuscolor { shift->cust_statuscolor(@_); } + +sub cust_statuscolor { my $self = shift; - $statuscolor{$self->status}; + $statuscolor{$self->cust_status}; } =back @@ -3597,25 +3960,44 @@ with no packages ever ordered) =cut +use vars qw($select_count_pkgs); +$select_count_pkgs = + "SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum"; + +sub select_count_pkgs_sql { + $select_count_pkgs; +} + sub prospect_sql { " - 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) + 0 = ( $select_count_pkgs ) "; } =item active_sql -Returns an SQL expression identifying active cust_main records. +Returns an SQL expression identifying active cust_main records (customers with +no active recurring packages, but otherwise unsuspended/uncancelled). =cut sub active_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ". FS::cust_pkg->active_sql. " + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) "; } +=item inactive_sql + +Returns an SQL expression identifying inactive cust_main records (customers with +active recurring packages). + +=cut + +sub inactive_sql { " + 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) + AND + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " ) +"; } + =item susp_sql =item suspended_sql @@ -3623,23 +4005,12 @@ Returns an SQL expression identifying suspended cust_main records. =cut -#my $recurring_sql = FS::cust_pkg->recurring_sql; -my $recurring_sql = " - '0' != ( select freq from part_pkg - where cust_pkg.pkgpart = part_pkg.pkgpart ) -"; sub suspended_sql { susp_sql(@_); } sub susp_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND $recurring_sql - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ". FS::cust_pkg->active_sql. " - ) + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " ) + AND + 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) "; } =item cancel_sql @@ -3650,22 +4021,45 @@ Returns an SQL expression identifying cancelled cust_main records. =cut sub cancelled_sql { cancel_sql(@_); } -sub cancel_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND $recurring_sql - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) +sub cancel_sql { + + my $recurring_sql = FS::cust_pkg->recurring_sql; + #my $recurring_sql = " + # '0' != ( select freq from part_pkg + # where cust_pkg.pkgpart = part_pkg.pkgpart ) + #"; + + " + 0 < ( $select_count_pkgs ) + AND 0 = ( $select_count_pkgs AND $recurring_sql + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + ) + "; +} + +=item uncancel_sql +=item uncancelled_sql + +Returns an SQL expression identifying un-cancelled cust_main records. + +=cut + +sub uncancelled_sql { uncancel_sql(@_); } +sub uncancel_sql { " + ( 0 < ( $select_count_pkgs + AND ( cust_pkg.cancel IS NULL + OR cust_pkg.cancel = 0 + ) + ) + OR 0 = ( $select_count_pkgs ) + ) "; } =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main -records. Currently, only I<last> or I<company> may be specified (the -appropriate ship_ field is also searched if applicable). +records. Currently, I<first>, I<last> and/or I<company> may be specified (the +appropriate ship_ field is also searched). Additional options are the same as FS::Record::qsearch @@ -3679,19 +4073,25 @@ sub fuzzy_search { check_and_rebuild_fuzzyfiles(); foreach my $field ( keys %$fuzzy ) { - my $sub = \&{"all_$field"}; + + my $all = $self->all_X($field); + next unless scalar(@$all); + my %match = (); - $match{$_}=1 foreach ( amatch($fuzzy->{$field}, ['i'], @{ &$sub() } ) ); + $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, ['i'], @$all ) ); + my @fcust = (); foreach ( keys %match ) { - push @cust_main, qsearch('cust_main', { %$hash, $field=>$_}, @opt); - push @cust_main, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt) - if defined dbdef->table('cust_main')->column('ship_last'); + push @fcust, qsearch('cust_main', { %$hash, $field=>$_}, @opt); + push @fcust, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt); } + my %fsaw = (); + push @cust_main, grep { ! $fsaw{$_->custnum}++ } @fcust; } + # we want the components of $fuzzy ANDed, not ORed, but still don't want dupes my %saw = (); - @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; + @cust_main = grep { ++$saw{$_->custnum} == scalar(keys %$fuzzy) } @cust_main; @cust_main; @@ -3706,10 +4106,11 @@ sub fuzzy_search { =item smart_search OPTION => VALUE ... Accepts the following options: I<search>, the string to search for. The string -will be searched for as a customer number, last name or company name, first -searching for an exact match then fuzzy and substring matches. +will be searched for as a customer number, phone number, name or company name, +as an exact, or, in some cases, a substring or fuzzy match (see the source code +for the exact heuristics used). -Any additional options treated as an additional qualifier on the search +Any additional options are treated as an additional qualifier on the search (i.e. I<agentnum>). Returns a (possibly empty) array of FS::cust_main objects. @@ -3718,72 +4119,215 @@ Returns a (possibly empty) array of FS::cust_main objects. sub smart_search { my %options = @_; - my $search = delete $options{'search'}; + + #here is the agent virtualization + my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; + my @cust_main = (); - if ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search + my $search = delete $options{'search'}; + ( my $alphanum_search = $search ) =~ s/\W//g; + + if ( $alphanum_search =~ /^1?(\d{3})(\d{3})(\d{4})(\d*)$/ ) { #phone# search + + #false laziness w/Record::ut_phone + my $phonen = "$1-$2-$3"; + $phonen .= " x$4" if $4; + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options }, + 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ). + ' ( '. + join(' OR ', map "$_ = '$phonen'", + qw( daytime night fax + ship_daytime ship_night ship_fax ) + ). + ' ) '. + " AND $agentnums_sql", #agent virtualization + } ); + + unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match + #try looking for matches with extensions unless one was specified + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options }, + 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ). + ' ( '. + join(' OR ', map "$_ LIKE '$phonen\%'", + qw( daytime night + ship_daytime ship_night ) + ). + ' ) '. + " AND $agentnums_sql", #agent virtualization + } ); + + } + + } elsif ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $1, %options }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ); + + } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) { + + my($company, $last, $first) = ( $1, $2, $3 ); + + # "Company (Last, First)" + #this is probably something a browser remembered, + #so just do an exact search - push @cust_main, qsearch('cust_main', { 'custnum' => $1, %options } ); + foreach my $prefix ( '', 'ship_' ) { + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { $prefix.'first' => $first, + $prefix.'last' => $last, + $prefix.'company' => $company, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", + } ); + } - } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { #value search + } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { # value search + # try (ship_){last,company} my $value = lc($1); + + # # remove "(Last, First)" in "Company (Last, First)", otherwise the + # # full strings the browser remembers won't work + # $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name + + use Lingua::EN::NameParse; + my $NameParse = new Lingua::EN::NameParse( + auto_clean => 1, + allow_reversed => 1, + ); + + my($last, $first) = ( '', '' ); + #maybe disable this too and just rely on NameParse? + if ( $value =~ /^(.+),\s*([^,]+)$/ ) { # Last, First + + ($last, $first) = ( $1, $2 ); + + #} elsif ( $value =~ /^(.+)\s+(.+)$/ ) { + } elsif ( ! $NameParse->parse($value) ) { + + my %name = $NameParse->components; + $first = $name{'given_name_1'}; + $last = $name{'surname_1'}; + + } + + if ( $first && $last ) { + + my($q_last, $q_first) = ( dbh->quote($last), dbh->quote($first) ); + + #exact + my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; + $sql .= " + ( ( LOWER(last) = $q_last AND LOWER(first) = $q_first ) + OR ( LOWER(ship_last) = $q_last AND LOWER(ship_first) = $q_first ) + )"; + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => \%options, + 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + } ); + + # or it just be something that was typed in... (try that in a sec) + + } + my $q_value = dbh->quote($value); #exact my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; - $sql .= " ( LOWER(last) = $q_value OR LOWER(company) = $q_value"; - $sql .= " OR LOWER(ship_last) = $q_value OR LOWER(ship_company) = $q_value" - if defined dbdef->table('cust_main')->column('ship_last'); - $sql .= ' )'; - - push @cust_main, qsearch( 'cust_main', \%options, '', $sql ); + $sql .= " ( LOWER(last) = $q_value + OR LOWER(company) = $q_value + OR LOWER(ship_last) = $q_value + OR LOWER(ship_company) = $q_value + )"; + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => \%options, + 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + } ); - unless ( @cust_main ) { #no exact match, trying substring/fuzzy + #always do substring & fuzzy, + #getting complains searches are not returning enough + #unless ( @cust_main ) { #no exact match, trying substring/fuzzy #still some false laziness w/ search/cust_main.cgi #substring - push @cust_main, qsearch( 'cust_main', - { 'last' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ); - push @cust_main, qsearch( 'cust_main', - { 'ship_last' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - - } - ) - if defined dbdef->table('cust_main')->column('ship_last'); - - push @cust_main, qsearch( 'cust_main', - { 'company' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ); - push @cust_main, qsearch( 'cust_main', - { 'ship_company' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ) - if defined dbdef->table('cust_main')->column('ship_last'); - #fuzzy - push @cust_main, FS::cust_main->fuzzy_search( - { 'last' => $value }, - \%options, + my @hashrefs = ( + { 'company' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, }, ); - push @cust_main, FS::cust_main->fuzzy_search( - { 'company' => $value }, - \%options, + + if ( $first && $last ) { + + push @hashrefs, + { 'first' => { op=>'ILIKE', value=>"%$first%" }, + 'last' => { op=>'ILIKE', value=>"%$last%" }, + }, + { 'ship_first' => { op=>'ILIKE', value=>"%$first%" }, + 'ship_last' => { op=>'ILIKE', value=>"%$last%" }, + }, + ; + + } else { + + push @hashrefs, + { 'last' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_last' => { op=>'ILIKE', value=>"%$value%" }, }, + ; + } + + foreach my $hashref ( @hashrefs ) { + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %$hashref, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton + } ); + + } + + #fuzzy + my @fuzopts = ( + \%options, #hashref + '', #select + " AND $agentnums_sql", #extra_sql #agent virtualization ); - } + if ( $first && $last ) { + push @cust_main, FS::cust_main->fuzzy_search( + { 'last' => $last, #fuzzy hashref + 'first' => $first }, # + @fuzopts + ); + } + foreach my $field ( 'last', 'company' ) { + push @cust_main, + FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts ); + } + + #} + + #eliminate duplicates + my %saw = (); + @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; } @@ -3795,10 +4339,12 @@ sub smart_search { =cut +use vars qw(@fuzzyfields); +@fuzzyfields = ( 'last', 'first', 'company' ); + sub check_and_rebuild_fuzzyfiles { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - -e "$dir/cust_main.last" && -e "$dir/cust_main.company" - or &rebuild_fuzzyfiles; + rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields } =item rebuild_fuzzyfiles @@ -3810,72 +4356,48 @@ sub rebuild_fuzzyfiles { use Fcntl qw(:flock); my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; + mkdir $dir, 0700 unless -d $dir; - #last - - open(LASTLOCK,">>$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - flock(LASTLOCK,LOCK_EX) - or die "can't lock $dir/cust_main.last: $!"; - - my @all_last = map $_->getfield('last'), qsearch('cust_main', {}); - push @all_last, - grep $_, map $_->getfield('ship_last'), qsearch('cust_main',{}) - if defined dbdef->table('cust_main')->column('ship_last'); - - open (LASTCACHE,">$dir/cust_main.last.tmp") - or die "can't open $dir/cust_main.last.tmp: $!"; - print LASTCACHE join("\n", @all_last), "\n"; - close LASTCACHE or die "can't close $dir/cust_main.last.tmp: $!"; - - rename "$dir/cust_main.last.tmp", "$dir/cust_main.last"; - close LASTLOCK; - - #company - - open(COMPANYLOCK,">>$dir/cust_main.company") - or die "can't open $dir/cust_main.company: $!"; - flock(COMPANYLOCK,LOCK_EX) - or die "can't lock $dir/cust_main.company: $!"; + foreach my $fuzzy ( @fuzzyfields ) { - my @all_company = grep $_ ne '', map $_->company, qsearch('cust_main',{}); - push @all_company, - grep $_ ne '', map $_->ship_company, qsearch('cust_main', {}) - if defined dbdef->table('cust_main')->column('ship_last'); + open(LOCK,">>$dir/cust_main.$fuzzy") + or die "can't open $dir/cust_main.$fuzzy: $!"; + flock(LOCK,LOCK_EX) + or die "can't lock $dir/cust_main.$fuzzy: $!"; - open (COMPANYCACHE,">$dir/cust_main.company.tmp") - or die "can't open $dir/cust_main.company.tmp: $!"; - print COMPANYCACHE join("\n", @all_company), "\n"; - close COMPANYCACHE or die "can't close $dir/cust_main.company.tmp: $!"; + open (CACHE,">$dir/cust_main.$fuzzy.tmp") + or die "can't open $dir/cust_main.$fuzzy.tmp: $!"; - rename "$dir/cust_main.company.tmp", "$dir/cust_main.company"; - close COMPANYLOCK; + foreach my $field ( $fuzzy, "ship_$fuzzy" ) { + my $sth = dbh->prepare("SELECT $field FROM cust_main". + " WHERE $field != '' AND $field IS NOT NULL"); + $sth->execute or die $sth->errstr; -} + while ( my $row = $sth->fetchrow_arrayref ) { + print CACHE $row->[0]. "\n"; + } -=item all_last + } -=cut + close CACHE or die "can't close $dir/cust_main.$fuzzy.tmp: $!"; + + rename "$dir/cust_main.$fuzzy.tmp", "$dir/cust_main.$fuzzy"; + close LOCK; + } -sub all_last { - my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - open(LASTCACHE,"<$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - my @array = map { chomp; $_; } <LASTCACHE>; - close LASTCACHE; - \@array; } -=item all_company +=item all_X =cut -sub all_company { +sub all_X { + my( $self, $field ) = @_; my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - open(COMPANYCACHE,"<$dir/cust_main.company") - or die "can't open $dir/cust_main.last: $!"; - my @array = map { chomp; $_; } <COMPANYCACHE>; - close COMPANYCACHE; + open(CACHE,"<$dir/cust_main.$field") + or die "can't open $dir/cust_main.$field: $!"; + my @array = map { chomp; $_; } <CACHE>; + close CACHE; \@array; } @@ -3884,7 +4406,7 @@ sub all_company { =cut sub append_fuzzyfiles { - my( $last, $company ) = @_; + #my( $first, $last, $company ) = @_; &check_and_rebuild_fuzzyfiles; @@ -3892,33 +4414,23 @@ sub append_fuzzyfiles { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - if ( $last ) { + foreach my $field (qw( first last company )) { + my $value = shift; - open(LAST,">>$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - flock(LAST,LOCK_EX) - or die "can't lock $dir/cust_main.last: $!"; + if ( $value ) { - print LAST "$last\n"; + open(CACHE,">>$dir/cust_main.$field") + or die "can't open $dir/cust_main.$field: $!"; + flock(CACHE,LOCK_EX) + or die "can't lock $dir/cust_main.$field: $!"; - flock(LAST,LOCK_UN) - or die "can't unlock $dir/cust_main.last: $!"; - close LAST; - } + print CACHE "$value\n"; - if ( $company ) { - - open(COMPANY,">>$dir/cust_main.company") - or die "can't open $dir/cust_main.company: $!"; - flock(COMPANY,LOCK_EX) - or die "can't lock $dir/cust_main.company: $!"; - - print COMPANY "$company\n"; - - flock(COMPANY,LOCK_UN) - or die "can't unlock $dir/cust_main.company: $!"; + flock(CACHE,LOCK_UN) + or die "can't unlock $dir/cust_main.$field: $!"; + close CACHE; + } - close COMPANY; } 1; @@ -3933,12 +4445,34 @@ sub batch_import { #warn join('-',keys %$param); my $fh = $param->{filehandle}; my $agentnum = $param->{agentnum}; + my $refnum = $param->{refnum}; my $pkgpart = $param->{pkgpart}; - my @fields = @{$param->{fields}}; - eval "use Date::Parse;"; - die $@ if $@; + #my @fields = @{$param->{fields}}; + my $format = $param->{'format'}; + my @fields; + my $payby; + if ( $format eq 'simple' ) { + @fields = qw( cust_pkg.setup dayphone first last + address1 address2 city state zip comments ); + $payby = 'BILL'; + } elsif ( $format eq 'extended' ) { + @fields = qw( agent_custid refnum + last first address1 address2 city state zip country + daytime night + ship_last ship_first ship_address1 ship_address2 + ship_city ship_state ship_zip ship_country + payinfo paycvv paydate + invoicing_list + cust_pkg.pkgpart + svc_acct.username svc_acct._password + ); + $payby = 'BILL'; + } else { + die "unknown format $format"; + } + eval "use Text::CSV_XS;"; die $@ if $@; @@ -3976,51 +4510,111 @@ sub batch_import { agentnum => $agentnum, refnum => $refnum, country => $conf->config('countrydefault') || 'US', - payby => 'BILL', #default + payby => $payby, #default paydate => '12/2037', #default ); my $billtime = time; my %cust_pkg = ( pkgpart => $pkgpart ); + my %svc_acct = (); foreach my $field ( @fields ) { - if ( $field =~ /^cust_pkg\.(setup|bill|susp|expire|cancel)$/ ) { + + if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|expire|cancel)$/ ) { + #$cust_pkg{$1} = str2time( shift @$columns ); - if ( $1 eq 'setup' ) { + if ( $1 eq 'pkgpart' ) { + $cust_pkg{$1} = shift @columns; + } elsif ( $1 eq 'setup' ) { $billtime = str2time(shift @columns); } else { $cust_pkg{$1} = str2time( shift @columns ); - } + } + + } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) { + + $svc_acct{$1} = shift @columns; + } else { + + #refnum interception + if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) { + + my $referral = $columns[0]; + my %hash = ( 'referral' => $referral, + 'agentnum' => $agentnum, + 'disabled' => '', + ); + + my $part_referral = qsearchs('part_referral', \%hash ) + || new FS::part_referral \%hash; + + unless ( $part_referral->refnum ) { + my $error = $part_referral->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't auto-insert advertising source: $referral: $error"; + } + } + + $columns[0] = $part_referral->refnum; + } + #$cust_main{$field} = shift @$columns; $cust_main{$field} = shift @columns; } } - my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ) if $pkgpart; + $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'}); + + my $invoicing_list = $cust_main{'invoicing_list'} + ? [ delete $cust_main{'invoicing_list'} ] + : []; + my $cust_main = new FS::cust_main ( \%cust_main ); + use Tie::RefHash; tie my %hash, 'Tie::RefHash'; #this part is important - $hash{$cust_pkg} = [] if $pkgpart; - my $error = $cust_main->insert( \%hash ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't insert customer for $line: $error"; + if ( $cust_pkg{'pkgpart'} ) { + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ); + + my @svc_acct = (); + if ( $svc_acct{'username'} ) { + my $part_pkg = $cust_pkg->part_pkg; + unless ( $part_pkg ) { + $dbh->rollback if $oldAutoCommit; + return "unknown pkgnum ". $cust_pkg{'pkgpart'}; + } + $svc_acct{svcpart} = $part_pkg->svcpart( 'svc_acct' ); + push @svc_acct, new FS::svc_acct ( \%svc_acct ) + } + + $hash{$cust_pkg} = \@svc_acct; } - #false laziness w/bill.cgi - $error = $cust_main->bill( 'time' => $billtime ); + my $error = $cust_main->insert( \%hash, $invoicing_list ); + if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't bill customer for $line: $error"; + return "can't insert customer for $line: $error"; } - $cust_main->apply_payments; - $cust_main->apply_credits; + if ( $format eq 'simple' ) { + + #false laziness w/bill.cgi + $error = $cust_main->bill( 'time' => $billtime ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $cust_main->apply_payments_and_credits; + + $error = $cust_main->collect(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't collect customer for $line: $error"; + } - $error = $cust_main->collect(); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't collect customer for $line: $error"; } $imported++; @@ -4044,8 +4638,6 @@ sub batch_charge { my $fh = $param->{filehandle}; my @fields = @{$param->{fields}}; - eval "use Date::Parse;"; - die $@ if $@; eval "use Text::CSV_XS;"; die $@ if $@; @@ -4119,6 +4711,94 @@ sub batch_charge { } +=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS + +Sends a templated email notification to the customer (see L<Text::Template). + +OPTIONS is a hash and may include + +I<from> - the email sender (default is invoice_from) + +I<to> - comma-separated scalar or arrayref of recipients + (default is invoicing_list) + +I<subject> - The subject line of the sent email notification + (default is "Notice from company_name") + +I<extra_fields> - a hashref of name/value pairs which will be substituted + into the template + +The following variables are vavailable in the template. + +I<$first> - the customer first name +I<$last> - the customer last name +I<$company> - the customer company +I<$payby> - a description of the method of payment for the customer + # would be nice to use FS::payby::shortname +I<$payinfo> - the account information used to collect for this customer +I<$expdate> - the expiration of the customer payment in seconds from epoch + +=cut + +sub notify { + my ($customer, $template, %options) = @_; + + return unless $conf->exists($template); + + my $from = $conf->config('invoice_from') if $conf->exists('invoice_from'); + $from = $options{from} if exists($options{from}); + + my $to = join(',', $customer->invoicing_list_emailonly); + $to = $options{to} if exists($options{to}); + + my $subject = "Notice from " . $conf->config('company_name') + if $conf->exists('company_name'); + $subject = $options{subject} if exists($options{subject}); + + my $notify_template = new Text::Template (TYPE => 'ARRAY', + SOURCE => [ map "$_\n", + $conf->config($template)] + ) + or die "can't create new Text::Template object: Text::Template::ERROR"; + $notify_template->compile() + or die "can't compile template: Text::Template::ERROR"; + + my $paydate = $customer->paydate; + $FS::notify_template::_template::first = $customer->first; + $FS::notify_template::_template::last = $customer->last; + $FS::notify_template::_template::company = $customer->company; + $FS::notify_template::_template::payinfo = $customer->mask_payinfo; + my $payby = $customer->payby; + my ($payyear,$paymonth,$payday) = split (/-/,$paydate); + my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear); + + #credit cards expire at the end of the month/year of their exp date + if ($payby eq 'CARD' || $payby eq 'DCRD') { + $FS::notify_template::_template::payby = 'credit card'; + ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++); + $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear); + $expire_time--; + }elsif ($payby eq 'COMP') { + $FS::notify_template::_template::payby = 'complimentary account'; + }else{ + $FS::notify_template::_template::payby = 'current method'; + } + $FS::notify_template::_template::expdate = $expire_time; + + for (keys %{$options{extra_fields}}){ + no strict "refs"; + ${"FS::notify_template::_template::$_"} = $options{extra_fields}->{$_}; + } + + send_email(from => $from, + to => $to, + subject => $subject, + body => $notify_template->fill_in( PACKAGE => + 'FS::notify_template::_template' ), + ); + +} + =back =head1 BUGS @@ -4138,6 +4818,8 @@ No multiple currency support (probably a larger project than just this module). payinfo_masked false laziness with cust_pay.pm and cust_refund.pm +Birthdates rely on negative epoch values. + =head1 SEE ALSO L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit> diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index a114c5a8a..ced0a1f55 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -1,8 +1,12 @@ package FS::cust_main_Mixin; use strict; +use vars qw( $DEBUG ); +use FS::UID qw(dbh); use FS::cust_main; +$DEBUG = 0; + =head1 NAME FS::cust_main_Mixin - Mixin class for records that contain fields from cust_main @@ -89,6 +93,168 @@ sub ship_contact { : $self->cust_unlinked_msg; } +=item country_full + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<country_full> method, or "(unlinked)" if this object is not +linked to a customer. + +=cut + +sub country_full { + my $self = shift; + $self->cust_linked + ? FS::cust_main::country_full($self) + : $self->cust_unlinked_msg; +} + +=item invoicing_list_emailonly + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<invoicing_list_emailonly> method, or "(unlinked)" if this +object is not linked to a customer. + +=cut + +sub invoicing_list_emailonly { + my $self = shift; + warn "invoicing_list_email only called on $self, ". + "custnum ". $self->custnum. "\n" + if $DEBUG; + $self->cust_linked + ? FS::cust_main::invoicing_list_emailonly($self) + : $self->cust_unlinked_msg; +} + +=item invoicing_list_emailonly_scalar + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<invoicing_list_emailonly_scalar> method, or "(unlinked)" if +this object is not linked to a customer. + +=cut + +sub invoicing_list_emailonly_scalar { + my $self = shift; + warn "invoicing_list_emailonly called on $self, ". + "custnum ". $self->custnum. "\n" + if $DEBUG; + $self->cust_linked + ? FS::cust_main::invoicing_list_emailonly_scalar($self) + : $self->cust_unlinked_msg; +} + +=item invoicing_list + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<invoicing_list> method, or "(unlinked)" if this object is not +linked to a customer. + +Note: this method is read-only. + +=cut + +#read-only +sub invoicing_list { + my $self = shift; + $self->cust_linked + ? FS::cust_main::invoicing_list($self) + : (); +} + +=item status + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<status> method, or "(unlinked)" if this object is not linked to +a customer. + +=cut + +sub cust_status { + my $self = shift; + return $self->cust_unlinked_msg unless $self->cust_linked; + + #FS::cust_main::status($self) + #false laziness w/actual cust_main::status + # (make sure FS::cust_main methods are called) + for my $status (qw( prospect active inactive suspended cancelled )) { + my $method = $status.'_sql'; + my $sql = FS::cust_main->$method();; + my $numnum = ( $sql =~ s/cust_main\.custnum/?/g ); + my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr; + $sth->execute( ($self->custnum) x $numnum ) + or die "Error executing 'SELECT $sql': ". $sth->errstr; + return $status if $sth->fetchrow_arrayref->[0]; + } +} + +=item ucfirst_cust_status + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<ucfirst_status> method, or "(unlinked)" if this object is not +linked to a customer. + +=cut + +sub ucfirst_cust_status { + my $self = shift; + $self->cust_linked + ? ucfirst( $self->cust_status(@_) ) + : $self->cust_unlinked_msg; +} + +=item cust_statuscolor + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I<statuscol> method, or "000000" if this object is not linked to +a customer. + +=cut + +sub cust_statuscolor { + my $self = shift; + + $self->cust_linked + ? FS::cust_main::cust_statuscolor($self) + : '000000'; +} + +=item prospect_sql + +=item active_sql + +=item inactive_sql + +=item suspended_sql + +=item cancelled_sql + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +corresponding FS::cust_main method, or "0" if this object is not linked to +a customer. + +=cut + +foreach my $sub (qw( prospect active inactive suspended cancelled )) { + eval " + sub ${sub}_sql { + my \$self = shift; + \$self->cust_linked + ? FS::cust_main::${sub}_sql(\$self) + : '0'; + } + "; + die $@ if $@; +} + =back =head1 BUGS diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm index 48f47e0cd..71029d096 100644 --- a/FS/FS/cust_main_invoice.pm +++ b/FS/FS/cust_main_invoice.pm @@ -91,7 +91,7 @@ sub replace { Checks all fields to make sure this is a valid invoice destination. If there is an error, returns the error, otherwise returns false. Called by the insert -and repalce methods. +and replace methods. =cut diff --git a/FS/FS/cust_main_note.pm b/FS/FS/cust_main_note.pm new file mode 100644 index 000000000..4732d12ce --- /dev/null +++ b/FS/FS/cust_main_note.pm @@ -0,0 +1,131 @@ +package FS::cust_main_note; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_main_note - Object methods for cust_main_note records + +=head1 SYNOPSIS + + use FS::cust_main_note; + + $record = new FS::cust_main_note \%hash; + $record = new FS::cust_main_note { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_main_note object represents a note attachted to a customer. +FS::cust_main_note inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item notenum - primary key + +=item custnum - + +=item _date - + +=item otaker - + +=item comments - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new customer note. To add the note 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_main_note'; } + +=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('notenum') + || $self->ut_number('custnum') + || $self->ut_numbern('_date') + || $self->ut_text('otaker') + || $self->ut_anything('comments') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +Lurking in the cracks. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index f057d2faf..30333e0c4 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -1,20 +1,21 @@ package FS::cust_pay; use strict; -use vars qw( @ISA $conf $unsuspendauto $ignore_noapply ); +use vars qw( @ISA $conf $unsuspendauto $ignore_noapply @encrypted_fields ); use Date::Format; use Business::CreditCard; use Text::Template; use FS::Misc qw(send_email); use FS::Record qw( dbh qsearch qsearchs ); use FS::cust_main_Mixin; +use FS::payinfo_Mixin; use FS::cust_bill; use FS::cust_bill_pay; use FS::cust_pay_refund; use FS::cust_main; use FS::cust_pay_void; -@ISA = qw( FS::cust_main_Mixin FS::Record ); +@ISA = qw(FS::Record FS::cust_main_Mixin FS::payinfo_Mixin ); $ignore_noapply = 0; @@ -24,6 +25,8 @@ FS::UID->install_callback( sub { $unsuspendauto = $conf->exists('unsuspendauto'); } ); +@encrypted_fields = ('payinfo'); + =head1 NAME FS::cust_pay - Object methods for cust_pay objects @@ -60,12 +63,11 @@ currently supported: =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion functions. -=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH), -`LECB' (phone bill billing), `BILL' (billing), `PREP` (prepaid card), -`CASH' (cash), `WEST' (Western Union), `MCRD' (Manual credit card), or -`COMP' (free) +=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values) + +=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format) -=item payinfo - card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively +=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works) =item paybatch - text field for tracking card processing @@ -97,12 +99,15 @@ Adds this payment to the database. For backwards-compatibility and convenience, if the additional field invnum is defined, an FS::cust_bill_pay record for the full amount of the payment -will be created. In this case, custnum is optional. +will be created. In this case, custnum is optional. An hash of optional +arguments may be passed. Currently "manual" is supported. If true, a +payment receipt is sent instead of a statement when 'payment_receipt_email' +configuration option is set. =cut sub insert { - my $self = shift; + my ($self, %options) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -115,8 +120,9 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + my $cust_bill; if ( $self->invnum ) { - my $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } ) + $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } ) or do { $dbh->rollback if $oldAutoCommit; return "Unknown cust_bill.invnum: ". $self->invnum; @@ -187,27 +193,36 @@ sub insert { && grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) { - my $receipt_template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ], - ) or do { - warn "can't create payment receipt template: $Text::Template::ERROR"; - return ''; - }; + $cust_bill ||= ($cust_main->cust_bill)[-1]; #rather inefficient though? + + my $error; + if ( ( exists($options{'manual'}) && $options{'manual'} ) + || ! $conf->exists('invoice_html_statement') + || ! $cust_bill + ) { - my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list; + my $receipt_template = new Text::Template ( + TYPE => 'ARRAY', + SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ], + ) or do { + warn "can't create payment receipt template: $Text::Template::ERROR"; + return ''; + }; - my $payby = $self->payby; - my $payinfo = $self->payinfo; - $payby =~ s/^BILL$/Check/ if $payinfo; - $payinfo = $self->payinfo_masked if $payby eq 'CARD' || $payby eq 'CHEK'; - $payby =~ s/^CHEK$/Electronic check/; + my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } + $cust_main->invoicing_list; - my $error = send_email( - 'from' => $conf->config('invoice_from'), #??? well as good as any - 'to' => \@invoicing_list, - 'subject' => 'Payment receipt', - 'body' => [ $receipt_template->fill_in( HASH => { + my $payby = $self->payby; + my $payinfo = $self->payinfo; + $payby =~ s/^BILL$/Check/ if $payinfo; + $payinfo = $self->paymask if $payby eq 'CARD' || $payby eq 'CHEK'; + $payby =~ s/^CHEK$/Electronic check/; + + $error = send_email( + 'from' => $conf->config('invoice_from'), #??? well as good as any + 'to' => \@invoicing_list, + 'subject' => 'Payment receipt', + 'body' => [ $receipt_template->fill_in( HASH => { 'date' => time2str("%a %B %o, %Y", $self->_date), 'name' => $cust_main->name, 'paynum' => $self->paynum, @@ -215,10 +230,24 @@ sub insert { 'payby' => ucfirst(lc($payby)), 'payinfo' => $payinfo, 'balance' => $cust_main->balance, - } ) ], - ); + } ) ], + ); + + } else { + + my $queue = new FS::queue { + 'paynum' => $self->paynum, + 'job' => 'FS::cust_bill::queueable_email', + }; + $error = $queue->insert( + 'invnum' => $cust_bill->invnum, + 'template' => 'statement', + ); + + } + if ( $error ) { - warn "can't send payment receipt: $error"; + warn "can't send payment receipt/statement: $error"; } } @@ -272,12 +301,14 @@ sub void { =item delete -Deletes this payment and all associated applications (see L<FS::cust_bill_pay>), -unless the closed flag is set. In most cases, you want to use the void -method instead to leave a record of the deleted payment. +Unless the closed flag is set, deletes this payment and all associated +applications (see L<FS::cust_bill_pay> and L<FS::cust_pay_refund>). In most +cases, you want to use the void method instead to leave a record of the +deleted payment. =cut +# very similar to FS::cust_credit::delete sub delete { my $self = shift; return "Can't delete closed payment" if $self->closed =~ /^Y/i; @@ -325,7 +356,7 @@ sub delete { 'paid: $'. sprintf("%.2f", $self->paid). "\n", 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n", 'payby: '. $self->payby. "\n", - 'payinfo: '. $self->payinfo. "\n", + 'payinfo: '. $self->paymask. "\n", 'paybatch: '. $self->paybatch. "\n", ], ); @@ -345,7 +376,16 @@ sub delete { =item replace OLD_RECORD -You probably shouldn't modify payments... +You can, but probably shouldn't modify payments... + +=cut + +sub replace { + #return "Can't modify payment!" + my $self = shift; + return "Can't modify closed payment" if $self->closed =~ /^Y/i; + $self->SUPER::replace(@_); +} =item check @@ -364,6 +404,7 @@ sub check { || $self->ut_numbern('_date') || $self->ut_textn('paybatch') || $self->ut_enum('closed', [ '', 'Y' ]) + || $self->payinfo_check() ; return $error if $error; @@ -375,30 +416,6 @@ sub check { $self->_date(time) unless $self->_date; - $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|PREP|CASH|WEST|MCRD)$/ - or return "Illegal payby"; - $self->payby($1); - - #false laziness with cust_refund::check - if ( $self->payby eq 'CARD' ) { - my $payinfo = $self->payinfo; - $payinfo =~ s/\D//g; - $self->payinfo($payinfo); - if ( $self->payinfo ) { - $self->payinfo =~ /^(\d{13,16})$/ - or return "Illegal (mistyped?) credit card number (payinfo)"; - $self->payinfo($1); - validate($self->payinfo) or return "Illegal credit card number"; - return "Unknown card type" if cardtype($self->payinfo) eq "Unknown"; - } else { - $self->payinfo('N/A'); - } - - } else { - $error = $self->ut_textn('payinfo'); - return $error if $error; - } - $self->SUPER::check; } @@ -438,7 +455,7 @@ sub batch_insert { my $errors = 0; my @errors = map { - my $error = $_->insert; + my $error = $_->insert( 'manual' => 1 ); if ( $error ) { $errors++; } else { @@ -529,33 +546,11 @@ sub cust_main { qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } -=item payinfo_masked - -Returns a "masked" payinfo field with all but the last four characters replaced -by 'x'es. Useful for displaying credit cards. - -=cut - -sub payinfo_masked { - my $self = shift; - #some false laziness w/cust_main::paymask - if ( $self->payby eq 'CARD' ) { - my $payinfo = $self->payinfo; - 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4)); - } elsif ( $self->payby eq 'CHEK' ) { - my( $account, $aba ) = split('@', $self->payinfo ); - 'x'x(length($account)-2). substr($account,(length($account)-2)). "@". $aba; - } else { - $self->payinfo; - } -} - =back =head1 BUGS -Delete and replace methods. payinfo_masked false laziness with cust_main.pm -and cust_refund.pm +Delete and replace methods. =head1 SEE ALSO diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 8059f1ca2..573f06f3a 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -1,11 +1,17 @@ package FS::cust_pay_batch; use strict; -use vars qw( @ISA ); -use FS::Record qw(dbh qsearchs); -use Business::CreditCard; +use vars qw( @ISA $DEBUG ); +use FS::Record qw(dbh qsearch qsearchs); +use FS::payinfo_Mixin; +use Business::CreditCard 0.28; -@ISA = qw( FS::Record ); +@ISA = qw( FS::Record FS::payinfo_Mixin ); + +# 1 is mostly method/subroutine entry and options +# 2 traces progress of some operations +# 3 is even more information including possibly sensitive data +$DEBUG = 0; =head1 NAME @@ -26,6 +32,8 @@ FS::cust_pay_batch - Object methods for batch cards $error = $record->check; + $error = $record->retriable; + =head1 DESCRIPTION An FS::cust_pay_batch object represents a credit card transaction ready to be @@ -37,7 +45,11 @@ following fields are currently supported: =item paybatchnum - primary key (automatically assigned) -=item cardnum +=item batchnum - indentifies group in batch + +=item payby - CARD/CHEK/LECB/BILL/COMP + +=item payinfo =item exp - card expiration @@ -65,6 +77,8 @@ following fields are currently supported: =item country +=item status + =back =head1 METHODS @@ -94,22 +108,14 @@ otherwise returns false. =item replace OLD_RECORD -#inactive -# -#Replaces the OLD_RECORD with this one in the database. If there is an error, -#returns the error, otherwise returns false. - -=cut - -sub replace { - return "Can't (yet?) replace batched transactions!"; -} +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. =item check Checks all fields to make sure this is a valid transaction. If there is an error, returns the error, otherwise returns false. Called by the insert -and repalce methods. +and replace methods. =cut @@ -118,8 +124,7 @@ sub check { my $error = $self->ut_numbern('paybatchnum') - || $self->ut_numbern('trancode') #depriciated - || $self->ut_number('cardnum') + || $self->ut_numbern('trancode') #deprecated || $self->ut_money('amount') || $self->ut_number('invnum') || $self->ut_number('custnum') @@ -137,17 +142,12 @@ sub check { $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name"; $self->first($1); - my $cardnum = $self->cardnum; - $cardnum =~ s/\D//g; - $cardnum =~ /^(\d{13,16})$/ - or return "Illegal credit card number"; - $cardnum = $1; - $self->cardnum($cardnum); - validate($cardnum) or return "Illegal credit card number"; - return "Unknown card type" if cardtype($cardnum) eq "Unknown"; + $error = $self->payinfo_check(); + return $error if $error; if ( $self->exp eq '' ) { - return "Expriation date required"; #unless + return "Expiration date required" + unless $self->payby =~ /^(CHEK|DCHK|LECB|WEST)$/; $self->exp(''); } else { if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) { @@ -173,15 +173,16 @@ sub check { $self->payname($1); } - #$self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/ - # or return "Illegal zip: ". $self->zip; - #$self->zip($1); + #we have lots of old zips in there... don't hork up batch results cause of em + $self->zip =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/ + or return "Illegal zip: ". $self->zip; + $self->zip($1); $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country; $self->country($1); - $error = $self->ut_zip('zip', $self->country); - return $error if $error; + #$error = $self->ut_zip('zip', $self->country); + #return $error if $error; #check invnum, custnum, ? @@ -200,101 +201,22 @@ sub cust_main { qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } -=back - -=head1 SUBROUTINES +=item retriable -=over 4 +Marks the corresponding event (see L<FS::cust_bill_event>) for this batched +credit card payment as retriable. Useful if the corresponding financial +institution account was declined for temporary reasons and/or a manual +retry is desired. -=item import_results +Implementation details: For the named customer's invoice, changes the +statustext of the 'done' (without statustext) event to 'retriable.' =cut -sub import_results { - use Time::Local; - use FS::cust_pay; - eval "use Text::CSV_XS;"; - die $@ if $@; -# - my $param = shift; - my $fh = $param->{'filehandle'}; - my $format = $param->{'format'}; - my $paybatch = $param->{'paybatch'}; - - my @fields; - my $end_condition; - my $end_hook; - my $hook; - my $approved_condition; - my $declined_condition; - - if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) { - - @fields = ( - 'paybatchnum', # Reference#: Invoice number of the transaction - 'paid', # Amount: Amount of the transaction. Dollars and cents - # with no decimal entered. - '', # Card Type: 0 - MCrd, 1 - Visa, 2 - AMEX, 3 - Discover, - # 4 - Insignia, 5 - Diners/EnRoute, 6 - JCB - '_date', # Transaction Date: Date the Transaction was processed - 'time', # Transaction Time: Time the transaction was processed - 'payinfo', # Card Number: Card number for the transaction - '', # Expiry Date: Expiry date of the card - '', # Auth#: Authorization number entered for force post - # transaction - 'type', # Transaction Type: 0 - purchase, 40 - refund, - # 20 - force post - 'result', # Processing Result: 3 - Approval, - # 4 - Declined/Amount over limit, - # 5 - Invalid/Expired/stolen card, - # 6 - Comm Error - '', # Terminal ID: Terminal ID used to process the transaction - ); - - $end_condition = sub { - my $hash = shift; - $hash->{'type'} eq '0BC'; - }; - - $end_hook = sub { - my( $hash, $total) = @_; - $total = sprintf("%.2f", $total); - my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100 ); - return "Our total $total does not match bank total $batch_total!" - if $total != $batch_total; - ''; - }; - - $hook = sub { - my $hash = shift; - $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 ); - $hash->{'_date'} = timelocal( substr($hash->{'time'}, 4, 2), - substr($hash->{'time'}, 2, 2), - substr($hash->{'time'}, 0, 2), - substr($hash->{'_date'}, 6, 2), - substr($hash->{'_date'}, 4, 2)-1, - substr($hash->{'_date'}, 0, 4)-1900, ); - }; - - $approved_condition = sub { - my $hash = shift; - $hash->{'type'} eq '0' && $hash->{'result'} == 3; - }; - - $declined_condition = sub { - my $hash = shift; - $hash->{'type'} eq '0' && ( $hash->{'result'} == 4 - || $hash->{'result'} == 5 ); - }; - - - } else { - return "Unknown format $format"; - } - - my $csv = new Text::CSV_XS; +sub retriable { + my $self = shift; - local $SIG{HUP} = 'IGNORE'; + local $SIG{HUP} = 'IGNORE'; #Hmm local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; local $SIG{TERM} = 'IGNORE'; @@ -305,79 +227,26 @@ sub import_results { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $total = 0; - my $line; - while ( defined($line=<$fh>) ) { - - next if $line =~ /^\s*$/; #skip blank lines - - $csv->parse($line) or do { - $dbh->rollback if $oldAutoCommit; - return "can't parse: ". $csv->error_input(); - }; - - my @values = $csv->fields(); - my %hash; - foreach my $field ( @fields ) { - my $value = shift @values; - next unless $field; - $hash{$field} = $value; - } - - if ( &{$end_condition}(\%hash) ) { - my $error = &{$end_hook}(\%hash, $total); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - last; - } - - my $cust_pay_batch = - qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'} } ); - unless ( $cust_pay_batch ) { - $dbh->rollback if $oldAutoCommit; - return "unknown paybatchnum $hash{'paybatchnum'}\n"; - } - my $custnum = $cust_pay_batch->custnum, - - my $error = $cust_pay_batch->delete; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error removing paybatchnum $hash{'paybatchnum'}: $error\n"; - } - - &{$hook}(\%hash); - - if ( &{$approved_condition}(\%hash) ) { - - my $cust_pay = new FS::cust_pay ( { - 'custnum' => $custnum, - 'payby' => 'CARD', - 'paybatch' => $paybatch, - map { $_ => $hash{$_} } (qw( paid _date payinfo )), - } ); - $error = $cust_pay->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error adding payment paybatchnum $hash{'paybatchnum'}: $error\n"; - } - $total += $hash{'paid'}; - - $cust_pay->cust_main->apply_payments; - - } elsif ( &{$declined_condition}(\%hash) ) { - - #this should be configurable... if anybody else ever uses batches - $cust_pay_batch->cust_main->suspend; - - } - + my $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } ) + or return "event $self->eventnum references nonexistant invoice $self->invnum"; + + warn "cust_pay_batch->retriable working with self of " . $self->paybatchnum . " and invnum of " . $self->invnum; + my @cust_bill_event = + sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds } + grep { + $_->part_bill_event->eventcode =~ /\$cust_bill->batch_card/ + && $_->status eq 'done' + && ! $_->statustext + } + $cust_bill->cust_bill_event; + # complain loudly if scalar(@cust_bill_event) > 1 ? + my $error = $cust_bill_event[0]->retriable; + if ($error ) { + # gah, even with transactions. + $dbh->commit if $oldAutoCommit; #well. + return "error marking invoice event retriable: $error"; } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; - } =back diff --git a/FS/FS/cust_pay_refund.pm b/FS/FS/cust_pay_refund.pm index 15e0e533a..cb9dbcef2 100644 --- a/FS/FS/cust_pay_refund.pm +++ b/FS/FS/cust_pay_refund.pm @@ -73,15 +73,26 @@ sub table { 'cust_pay_refund'; } Adds this cust_pay_refund to the database. If there is an error, returns the error, otherwise returns false. +=cut + +sub insert { + my $self = shift; + return "Can't apply refund to closed payment" + if $self->cust_pay->closed =~ /^Y/i; + return "Can't apply payment to closed refund" + if $self->cust_refund->closed =~ /^Y/i; + $self->SUPER::insert(@_); +} + =item delete =cut sub delete { my $self = shift; - return "Can't apply refund to closed payment" + return "Can't remove refund from closed payment" if $self->cust_pay->closed =~ /^Y/i; - return "Can't apply closed refund" + return "Can't remove payment from closed refund" if $self->cust_refund->closed =~ /^Y/i; $self->SUPER::delete(@_); } diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm index 946d69fe1..de05f710b 100644 --- a/FS/FS/cust_pay_void.pm +++ b/FS/FS/cust_pay_void.pm @@ -1,6 +1,6 @@ package FS::cust_pay_void; use strict; -use vars qw( @ISA ); +use vars qw( @ISA @encrypted_fields ); use Business::CreditCard; use FS::UID qw(getotaker); use FS::Record qw(qsearchs dbh fields); # qsearch ); @@ -10,7 +10,9 @@ use FS::cust_pay; #use FS::cust_pay_refund; #use FS::cust_main; -@ISA = qw( FS::Record ); +@ISA = qw( FS::Record FS::payinfo_Mixin ); + +@encrypted_fields = ('payinfo'); =head1 NAME @@ -207,19 +209,6 @@ sub cust_main { qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } -=item payinfo_masked - -Returns a "masked" payinfo field with all but the last four characters replaced -by 'x'es. Useful for displaying credit cards. - -=cut - -sub payinfo_masked { - my $self = shift; - my $payinfo = $self->payinfo; - 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4)); -} - =back =head1 BUGS diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 783cc73a3..b2ef2a259 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1,7 +1,9 @@ package FS::cust_pkg; use strict; -use vars qw(@ISA $disable_agentcheck @SVCDB_CANCEL_SEQ $DEBUG); +use vars qw(@ISA $disable_agentcheck $DEBUG); +use List::Util qw(max); +use Tie::IxHash; use FS::UID qw( getotaker dbh ); use FS::Misc qw( send_email ); use FS::Record qw( qsearch qsearchs ); @@ -14,6 +16,9 @@ use FS::pkg_svc; use FS::cust_bill_pkg; use FS::h_cust_svc; use FS::reg_code; +use FS::part_svc; +use FS::cust_pkg_reason; +use FS::reason; # need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend, # setup } @@ -26,20 +31,12 @@ use FS::svc_forward; # for sending cancel emails in sub cancel use FS::Conf; -@ISA = qw( FS::cust_main_Mixin FS::Record ); +@ISA = qw( FS::cust_main_Mixin FS::option_Common FS::Record ); $DEBUG = 0; $disable_agentcheck = 0; -# The order in which to unprovision services. -@SVCDB_CANCEL_SEQ = qw( svc_external - svc_www - svc_forward - svc_acct - svc_domain - svc_broadband ); - sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; @@ -178,7 +175,7 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $error = $self->SUPER::insert; + my $error = $self->SUPER::insert($options{options} ? %{$options{options}} : ()); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -269,8 +266,12 @@ Calls =cut sub replace { - my( $new, $old ) = ( shift, shift ); + my( $new, $old, %options ) = @_; + # We absolutely have to have an old vs. new record to make this work. + if (!defined($old)) { + $old = qsearchs( 'cust_pkg', { 'pkgnum' => $new->pkgnum } ); + } #return "Can't (yet?) change pkgpart!" if $old->pkgpart != $new->pkgpart; return "Can't change otaker!" if $old->otaker ne $new->otaker; @@ -294,6 +295,16 @@ sub replace { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + if ($options{'reason'} && $new->expire && $old->expire ne $new->expire) { + my $error = $new->insert_reason( 'reason' => $options{'reason'}, + 'date' => $new->expire, + ); + if ( $error ) { + dbh->rollback if $oldAutoCommit; + return "Error inserting cust_pkg_reason: $error"; + } + } + #save off and freeze RADIUS attributes for any associated svc_acct records my @svc_acct = (); if ( $old->part_pkg->is_prepaid || $new->part_pkg->is_prepaid ) { @@ -308,7 +319,9 @@ sub replace { } - my $error = $new->SUPER::replace($old); + my $error = $new->SUPER::replace($old, + $options{options} ? ${options{options}} : () + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -430,21 +443,28 @@ sub cancel { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + if ($options{'reason'}) { + $error = $self->insert_reason( 'reason' => $options{'reason'} ); + if ( $error ) { + dbh->rollback if $oldAutoCommit; + return "Error inserting cust_pkg_reason: $error"; + } + } + my %svc; foreach my $cust_svc ( - qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) + #schwartz + map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } + qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { - push @{ $svc{$cust_svc->part_svc->svcdb} }, $cust_svc; - } - foreach my $svcdb (@SVCDB_CANCEL_SEQ) { - foreach my $cust_svc (@{ $svc{$svcdb} }) { - my $error = $cust_svc->cancel; + my $error = $cust_svc->cancel; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "Error cancelling cust_svc: $error"; - } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error cancelling cust_svc: $error"; } } @@ -466,7 +486,7 @@ sub cancel { my %hash = $self->hash; $hash{'cancel'} = time; my $new = new FS::cust_pkg ( \%hash ); - $error = $new->replace($self); + $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -502,7 +522,7 @@ If there is an error, returns the error, otherwise returns false. =cut sub suspend { - my $self = shift; + my( $self, %options ) = @_; my $error ; local $SIG{HUP} = 'IGNORE'; @@ -516,6 +536,14 @@ sub suspend { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + if ($options{'reason'}) { + $error = $self->insert_reason( 'reason' => $options{'reason'} ); + if ( $error ) { + dbh->rollback if $oldAutoCommit; + return "Error inserting cust_pkg_reason: $error"; + } + } + foreach my $cust_svc ( qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { @@ -543,7 +571,7 @@ sub suspend { my %hash = $self->hash; $hash{'susp'} = time; my $new = new FS::cust_pkg ( \%hash ); - $error = $new->replace($self); + $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -555,18 +583,27 @@ sub suspend { ''; #no errors } -=item unsuspend +=item unsuspend [ OPTION => VALUE ... ] Unsuspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this package, then unsuspends the package itself (clears the susp field). +Available options are: I<adjust_next_bill>. + +I<adjust_next_bill> can be set true to adjust the next bill date forward by +the amount of time the account was inactive. This was set true by default +since 1.4.2 and 1.5.0pre6; however, starting with 1.7.0 this needs to be +explicitly requested. Price plans for which this makes sense (anniversary-date +based than prorate or subscription) could have an option to enable this +behaviour? + If there is an error, returns the error, otherwise returns false. =cut sub unsuspend { - my $self = shift; - my($error); + my( $self, %opt ) = @_; + my $error; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -605,11 +642,17 @@ sub unsuspend { unless ( ! $self->getfield('susp') ) { my %hash = $self->hash; my $inactive = time - $hash{'susp'}; - $hash{'susp'} = ''; + + my $conf = new FS::Conf; + $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive - if $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} ); + if ( $opt{'adjust_next_bill'} + || $conf->config('unsuspend-always_adjust_next_bill_date') ) + && $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} ); + + $hash{'susp'} = ''; my $new = new FS::cust_pkg ( \%hash ); - $error = $new->replace($self); + $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -639,6 +682,23 @@ sub last_bill { $cust_bill_pkg ? $cust_bill_pkg->sdate : $self->setup || 0; } +=item last_reason + +Returns the most recent FS::reason associated with the package. + +=cut + +sub last_reason { + my $self = shift; + my $cust_pkg_reason = qsearchs( { + 'table' => 'cust_pkg_reason', + 'hashref' => { 'pkgnum' => $self->pkgnum, }, + 'extra_sql'=> 'ORDER BY date DESC LIMIT 1', + } ); + qsearchs ( 'reason', { 'reasonnum' => $cust_pkg_reason->reasonnum } ) + if $cust_pkg_reason; +} + =item part_pkg Returns the definition for this billing item, as an FS::part_pkg object (see @@ -702,6 +762,17 @@ sub calc_cancel { $self->part_pkg->calc_cancel($self, @_); } +=item cust_bill_pkg + +Returns any invoice line items for this package (see L<FS::cust_bill_pkg>). + +=cut + +sub cust_bill_pkg { + my $self = shift; + qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } ); +} + =item cust_svc [ SVCPART ] Returns the services for this package, as FS::cust_svc objects (see @@ -783,7 +854,7 @@ sub num_cust_svc { =item available_part_svc -Returns a list FS::part_svc objects representing services included in this +Returns a list of FS::part_svc objects representing services included in this package but not yet provisioned. Each FS::part_svc object also has an extra field, I<num_avail>, which specifies the number of available services. @@ -801,6 +872,87 @@ sub available_part_svc { $self->part_pkg->pkg_svc; } +=item + +Returns a list of FS::part_svc objects representing provisioned and available +services included in this package. Each FS::part_svc object also has the +following extra fields: + +=over 4 + +=item num_cust_svc (count) + +=item num_avail (quantity - count) + +=item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects + +svcnum +label -> ($cust_svc->label)[1] + +=back + +=cut + +sub part_svc { + my $self = shift; + + #XXX some sort of sort order besides numeric by svcpart... + my @part_svc = sort { $a->svcpart <=> $b->svcpart } map { + my $pkg_svc = $_; + my $part_svc = $pkg_svc->part_svc; + my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); + $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #more evil + $part_svc->{'Hash'}{'num_avail'} = + max( 0, $pkg_svc->quantity - $num_cust_svc ); + $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ]; + $part_svc; + } $self->part_pkg->pkg_svc; + + #extras + push @part_svc, map { + my $part_svc = $_; + my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); + $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail + $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ? + $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ]; + $part_svc; + } $self->extra_part_svc; + + @part_svc; + +} + +=item extra_part_svc + +Returns a list of FS::part_svc objects corresponding to services in this +package which are still provisioned but not (any longer) available in the +package definition. + +=cut + +sub extra_part_svc { + my $self = shift; + + my $pkgnum = $self->pkgnum; + my $pkgpart = $self->pkgpart; + + qsearch( { + 'table' => 'part_svc', + 'hashref' => {}, + 'extra_sql' => "WHERE 0 = ( SELECT COUNT(*) FROM pkg_svc + WHERE pkg_svc.svcpart = part_svc.svcpart + AND pkg_svc.pkgpart = $pkgpart + AND quantity > 0 + ) + AND 0 < ( SELECT count(*) + FROM cust_svc + LEFT JOIN cust_pkg using ( pkgnum ) + WHERE cust_svc.svcpart = part_svc.svcpart + AND pkgnum = $pkgnum + )", + } ); +} + =item status Returns a short status string for this package, currently: @@ -824,26 +976,45 @@ Returns a short status string for this package, currently: sub status { my $self = shift; + my $freq = length($self->freq) ? $self->freq : $self->part_pkg->freq; + return 'cancelled' if $self->get('cancel'); return 'suspended' if $self->susp; return 'not yet billed' unless $self->setup; - return 'one-time charge' if $self->part_pkg->freq =~ /^(0|$)/; + return 'one-time charge' if $freq =~ /^(0|$)/; return 'active'; } -=item statuscolor +=item statuses -Returns a hex triplet color string for this package's status. +Class method that returns the list of possible status strings for pacakges +(see L<the status method|/status>). For example: + + @statuses = FS::cust_pkg->statuses(); =cut -my %statuscolor = ( +tie my %statuscolor, 'Tie::IxHash', 'not yet billed' => '000000', 'one-time charge' => '000000', 'active' => '00CC00', 'suspended' => 'FF9900', 'cancelled' => 'FF0000', -); +; + +sub statuses { + my $self = shift; #could be class... + grep { $_ !~ /^(not yet billed)$/ } #this is a dumb status anyway + # mayble split btw one-time vs. recur + keys %statuscolor; +} + +=item statuscolor + +Returns a hex triplet color string for this package's status. + +=cut + sub statuscolor { my $self = shift; $statuscolor{$self->status}; @@ -1163,7 +1334,7 @@ sub reexport { =back -=head1 CLASS METHOD +=head1 CLASS METHODS =over 4 @@ -1178,6 +1349,17 @@ sub recurring_sql { " where cust_pkg.pkgpart = part_pkg.pkgpart ) "; } +=item onetime_sql + +Returns an SQL expression identifying one-time packages. + +=cut + +sub onetime_sql { " + '0' = ( select freq from part_pkg + where cust_pkg.pkgpart = part_pkg.pkgpart ) +"; } + =item active_sql Returns an SQL expression identifying active packages. @@ -1190,6 +1372,19 @@ sub active_sql { " AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) "; } +=item inactive_sql + +Returns an SQL expression identifying inactive packages (one-time packages +that are otherwise unsuspended/uncancelled). + +=cut + +sub inactive_sql { " + ". $_[0]->onetime_sql(). " + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) +"; } + =item susp_sql =item suspended_sql @@ -1198,11 +1393,13 @@ Returns an SQL expression identifying suspended packages. =cut sub suspended_sql { susp_sql(@_); } -sub susp_sql { " - ". $_[0]->recurring_sql(). " - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 -"; } +sub susp_sql { + #$_[0]->recurring_sql(). ' AND '. + " + ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 + "; +} =item cancel_sql =item cancelled_sql @@ -1212,10 +1409,10 @@ Returns an SQL exprression identifying cancelled packages. =cut sub cancelled_sql { cancel_sql(@_); } -sub cancel_sql { " - ". $_[0]->recurring_sql(). " - AND cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0 -"; } +sub cancel_sql { + #$_[0]->recurring_sql(). ' AND '. + "cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0"; +} =head1 SUBROUTINES @@ -1319,7 +1516,7 @@ sub order { $dbh->rollback if $oldAutoCommit; return "Unable to transfer all services from package ".$old_pkg->pkgnum; } - $error = $old_pkg->cancel; + $error = $old_pkg->cancel( quiet=>1 ); if ($error) { $dbh->rollback; return $error; @@ -1329,6 +1526,44 @@ sub order { ''; } +sub insert_reason { + my ($self, %options) = @_; + + my $otaker = $FS::CurrentUser::CurrentUser->name; + $otaker = $FS::CurrentUser::CurrentUser->username + if (($otaker) eq "User, Legacy"); + + my $cust_pkg_reason = + new FS::cust_pkg_reason({ 'pkgnum' => $self->pkgnum, + 'reasonnum' => $options{'reason'}, + 'otaker' => $otaker, + 'date' => $options{'date'} + ? $options{'date'} + : time, + }); + return $cust_pkg_reason->insert; +} + +=item set_usage USAGE_VALUE_HASHREF + +USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts +to which they should be set (see L<FS::svc_acct>). Currently seconds, +upbytes, downbytes, and totalbytes are appropriate keys. + +All svc_accts which are part of this package have their values reset. + +=cut + +sub set_usage { + my ($self, $valueref) = @_; + + foreach my $cust_svc ($self->cust_svc){ + my $svc_x = $cust_svc->svc_x; + $svc_x->set_usage($valueref) + if $svc_x->can("set_usage"); + } +} + =back =head1 BUGS diff --git a/FS/FS/cust_pkg_option.pm b/FS/FS/cust_pkg_option.pm new file mode 100644 index 000000000..43a153095 --- /dev/null +++ b/FS/FS/cust_pkg_option.pm @@ -0,0 +1,115 @@ +package FS::cust_pkg_option; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_pkg_option - Object methods for cust_pkg_option records + +=head1 SYNOPSIS + + use FS::cust_pkg_option; + + $record = new FS::cust_pkg_option \%hash; + $record = new FS::cust_pkg_option { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_pkg_option object represents an option key an value for a +customer package. FS::cust_pkg_option inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item optionnum - primary key + +=item pkgnum - + +=item optionname - + +=item optionvalue - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new option. To add the option 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<hash> method. + +=cut + +sub table { 'cust_pkg_option'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=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 + +=item check + +Checks all fields to make sure this is a valid option. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('optionnum') + || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum') + || $self->ut_text('optionname') + || $self->ut_textn('optionvalue') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_pkg_reason.pm b/FS/FS/cust_pkg_reason.pm new file mode 100644 index 000000000..2f927401f --- /dev/null +++ b/FS/FS/cust_pkg_reason.pm @@ -0,0 +1,122 @@ +package FS::cust_pkg_reason; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_pkg_reason - Object methods for cust_pkg_reason records + +=head1 SYNOPSIS + + use FS::cust_pkg_reason; + + $record = new FS::cust_pkg_reason \%hash; + $record = new FS::cust_pkg_reason { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_pkg_reason object represents a relationship between a cust_pkg +and a reason, for example cancellation or suspension reasons. +FS::cust_pkg_reason inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item num - primary key + +=item pkgnum - + +=item reasonnum - + +=item otaker - + +=item date - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new cust_pkg_reason. 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<hash> method. + +=cut + +sub table { 'cust_pkg_reason'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=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 + +=item check + +Checks all fields to make sure this is a valid cust_pkg_reason. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('num') + || $self->ut_number('pkgnum') + || $self->ut_number('reasonnum') + || $self->ut_text('otaker') + || $self->ut_numbern('date') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +Here be termites. Don't use on wooden computers. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm index 8c672b8d7..9cd9bf845 100644 --- a/FS/FS/cust_refund.pm +++ b/FS/FS/cust_refund.pm @@ -1,7 +1,7 @@ package FS::cust_refund; use strict; -use vars qw( @ISA ); +use vars qw( @ISA @encrypted_fields ); use Business::CreditCard; use FS::Record qw( qsearch qsearchs dbh ); use FS::UID qw(getotaker); @@ -9,8 +9,11 @@ use FS::cust_credit; use FS::cust_credit_refund; use FS::cust_pay_refund; use FS::cust_main; +use FS::payinfo_Mixin; -@ISA = qw( FS::Record ); +@ISA = qw( FS::Record FS::payinfo_Mixin ); + +@encrypted_fields = ('payinfo'); =head1 NAME @@ -50,11 +53,11 @@ inherits from FS::Record. The following fields are currently supported: =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion functions. -=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH), -`LECB' (Phone bill billing), `BILL' (billing), `CASH' (cash), -`WEST' (Western Union), `MCRD' (Manual credit card), or `COMP' (free) +=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values) + +=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format) -=item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username) +=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works) =item paybatch - text field for tracking card processing @@ -163,14 +166,52 @@ sub insert { =item delete -Currently unimplemented (accounting reasons). +Unless the closed flag is set, deletes this refund and all associated +applications (see L<FS::cust_credit_refund> and L<FS::cust_pay_refund>). =cut sub delete { my $self = shift; return "Can't delete closed refund" if $self->closed =~ /^Y/i; - $self->SUPER::delete(@_); + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + foreach my $cust_credit_refund ( $self->cust_credit_refund ) { + my $error = $cust_credit_refund->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + foreach my $cust_pay_refund ( $self->cust_pay_refund ) { + my $error = $cust_pay_refund->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + my $error = $self->SUPER::delete(@_); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; + } =item replace OLD_RECORD @@ -212,29 +253,8 @@ sub check { unless $self->crednum || qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); - $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|CASH|WEST|MCRD)$/ - or return "Illegal payby"; - $self->payby($1); - - #false laziness with cust_pay::check - if ( $self->payby eq 'CARD' ) { - my $payinfo = $self->payinfo; - $payinfo =~ s/\D//g; - $self->payinfo($payinfo); - if ( $self->payinfo ) { - $self->payinfo =~ /^(\d{13,16})$/ - or return "Illegal (mistyped?) credit card number (payinfo)"; - $self->payinfo($1); - validate($self->payinfo) or return "Illegal credit card number"; - return "Unknown card type" if cardtype($self->payinfo) eq "Unknown"; - } else { - $self->payinfo('N/A'); - } - - } else { - $error = $self->ut_textn('payinfo'); - return $error if $error; - } + $error = $self->payinfo_check; + return $error if $error; $self->otaker(getotaker); @@ -285,29 +305,11 @@ sub unapplied { sprintf("%.2f", $amount ); } - - -=item payinfo_masked - -Returns a "masked" payinfo field with all but the last four characters replaced -by 'x'es. Useful for displaying credit cards. - -=cut - - -sub payinfo_masked { - my $self = shift; - my $payinfo = $self->payinfo; - 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4)); -} - - =back =head1 BUGS -Delete and replace methods. payinfo_masked false laziness with cust_main.pm -and cust_pay.pm +Delete and replace methods. =head1 SEE ALSO diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index ad87cab7e..cdb34cd71 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -1,25 +1,25 @@ package FS::cust_svc; use strict; -use vars qw( @ISA $DEBUG $ignore_quantity ); -use Carp qw( carp cluck ); +use vars qw( @ISA $DEBUG $me $ignore_quantity ); +use Carp; use FS::Conf; use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_pkg; use FS::part_pkg; use FS::part_svc; use FS::pkg_svc; -use FS::svc_acct; -use FS::svc_domain; -use FS::svc_forward; -use FS::svc_broadband; -use FS::svc_external; use FS::domain_record; use FS::part_export; +use FS::cdr; -@ISA = qw( FS::Record ); +#most FS::svc_ classes are autoloaded in svc_x emthod +use FS::svc_acct; #this one is used in the cache stuff + +@ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; +$me = '[cust_svc]'; $ignore_quantity = 0; @@ -276,58 +276,30 @@ Returns a list consisting of: - The table name (i.e. svc_domain) for this service - svcnum +Usage example: + + my($label, $value, $svcdb) = $cust_svc->label; + =cut sub label { my $self = shift; carp "FS::cust_svc::label called on $self" if $DEBUG; my $svc_x = $self->svc_x - or die "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum; + or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum; + $self->_svc_label($svc_x); } sub _svc_label { my( $self, $svc_x ) = ( shift, shift ); - my $svcdb = $self->part_svc->svcdb; - my $tag; - if ( $svcdb eq 'svc_acct' ) { - $tag = $svc_x->email(@_); - } elsif ( $svcdb eq 'svc_forward' ) { - if ( $svc_x->srcsvc ) { - my $svc_acct = $svc_x->srcsvc_acct(@_); - $tag = $svc_acct->email(@_); - } else { - $tag = $svc_x->src; - } - $tag .= '->'; - if ( $svc_x->dstsvc ) { - my $svc_acct = $svc_x->dstsvc_acct(@_); - $tag .= $svc_acct->email(@_); - } else { - $tag .= $svc_x->dst; - } - } elsif ( $svcdb eq 'svc_domain' ) { - $tag = $svc_x->getfield('domain'); - } elsif ( $svcdb eq 'svc_www' ) { - my $domain_record = $svc_x->domain_record(@_); - $tag = $domain_record->zone; - } elsif ( $svcdb eq 'svc_broadband' ) { - $tag = $svc_x->ip_addr; - } elsif ( $svcdb eq 'svc_external' ) { - my $conf = new FS::Conf; - if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) { - $tag = sprintf('%010d', $svc_x->id). '-'. - substr('0000000000'.uc($svc_x->title), -10); - } else { - $tag = $svc_x->id. ': '. $svc_x->title; - } - } else { - cluck "warning: asked for label of unsupported svcdb; using svcnum"; - $tag = $svc_x->getfield('svcnum'); - } - - $self->part_svc->svc, $tag, $svcdb, $self->svcnum; + ( + $self->part_svc->svc, + $svc_x->label(@_), + $self->part_svc->svcdb, + $self->svcnum + ); } @@ -344,7 +316,10 @@ sub svc_x { if ( $svcdb eq 'svc_acct' && $self->{'_svc_acct'} ) { $self->{'_svc_acct'}; } else { - #require "FS/$svcdb.pm"; + require "FS/$svcdb.pm"; + warn "$me svc_x: part_svc.svcpart ". $self->part_svc->svcpart. + ", so searching for $svcdb.svcnum ". $self->svcnum. "\n" + if $DEBUG; qsearchs( $svcdb, { 'svcnum' => $self->svcnum } ); } } @@ -570,6 +545,49 @@ sub get_session_history { } +=item get_cdrs_for_update + +Returns (and SELECTs "FOR UPDATE") all unprocessed (freesidestatus NULL) CDR +objects (see L<FS::cdr>) associated with this service. + +Currently CDRs are associated with svc_acct services via a DID in the +username. This part is rather tenative and still subject to change... + +=cut + +sub get_cdrs_for_update { + my($self, %options) = @_; + + my $default_prefix = $options{'default_prefix'}; + + #CDRs are now associated with svc_phone services via svc_phone.phonenum + #return () unless $self->svc_x->isa('FS::svc_phone'); + return () unless $self->part_svc->svcdb eq 'svc_phone'; + my $number = $self->svc_x->phonenum; + + my @cdrs = + qsearch( { + 'table' => 'cdr', + 'hashref' => { 'freesidestatus' => '', + 'charged_party' => $number + }, + 'extra_sql' => 'FOR UPDATE', + } ); + + if ( length($default_prefix) ) { + push @cdrs, + qsearch( { + 'table' => 'cdr', + 'hashref' => { 'freesidestatus' => '', + 'charged_party' => "$default_prefix$number", + }, + 'extra_sql' => 'FOR UPDATE', + } ); + } + + @cdrs; +} + =item pkg_svc Returns the pkg_svc record for for this service, if applicable. diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm index da0de000a..3e398877a 100644 --- a/FS/FS/cust_tax_exempt.pm +++ b/FS/FS/cust_tax_exempt.pm @@ -3,6 +3,8 @@ package FS::cust_tax_exempt; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs ); +use FS::cust_main; +use FS::cust_main_county; @ISA = qw(FS::Record); @@ -27,7 +29,7 @@ FS::cust_tax_exempt - Object methods for cust_tax_exempt records =head1 DESCRIPTION -An FS::cust_tax_exempt object represents a historical record of a customer tax +An FS::cust_tax_exempt object represents a record of an old-style customer tax exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt inherits from FS::Record. The following fields are currently supported: @@ -47,6 +49,12 @@ inherits from FS::Record. The following fields are currently supported: =back +=head1 NOTE + +Old-style customer tax exemptions are only useful for legacy migrations - if +you are looking for current customer tax exemption data see +L<FS::cust_tax_exempt_pkg>. + =head1 METHODS =over 4 @@ -115,6 +123,17 @@ sub check { ; } +=item cust_main_county + +Returns the FS::cust_main_county object associated with this tax exemption. + +=cut + +sub cust_main_county { + my $self = shift; + qsearchs( 'cust_main_county', { 'taxnum' => $self->taxnum } ); +} + =back =head1 BUGS diff --git a/FS/FS/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm new file mode 100644 index 000000000..128921b9c --- /dev/null +++ b/FS/FS/cust_tax_exempt_pkg.pm @@ -0,0 +1,136 @@ +package FS::cust_tax_exempt_pkg; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::cust_main_Mixin; +use FS::cust_bill_pkg; +use FS::cust_main_county; + +@ISA = qw( FS::cust_main_Mixin FS::Record ); + +=head1 NAME + +FS::cust_tax_exempt_pkg - Object methods for cust_tax_exempt_pkg records + +=head1 SYNOPSIS + + use FS::cust_tax_exempt_pkg; + + $record = new FS::cust_tax_exempt_pkg \%hash; + $record = new FS::cust_tax_exempt_pkg { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_tax_exempt_pkg object represents a record of a customer tax +exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item exemptpkgnum - primary key + +=item billpkgnum - invoice line item (see L<FS::cust_bill_pkg>) + +=item taxnum - tax rate (see L<FS::cust_main_county>) + +=item year + +=item month + +=item amount + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new exemption record. To add the examption record 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_tax_exempt_pkg'; } + +=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 exemption record. 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; + + $self->ut_numbern('exemptnum') +# || $self->ut_foreign_key('custnum', 'cust_main', 'custnum') + || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum') + || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum') + || $self->ut_number('year') #check better + || $self->ut_number('month') #check better + || $self->ut_money('amount') + || $self->SUPER::check + ; +} + +=back + +=head1 BUGS + +Texas tax is still a royal pain in the ass. + +=head1 SEE ALSO + +L<FS::cust_main_county>, L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from +the base documentation. + +=cut + +1; + diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm index 3c65a1a05..6513abf25 100644 --- a/FS/FS/domain_record.pm +++ b/FS/FS/domain_record.pm @@ -59,7 +59,7 @@ supported: =item new HASHREF -Creates a new entry. To add the example to the database, see L<"insert">. +Creates a new entry. To add the entry 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<hash> method. @@ -229,7 +229,7 @@ sub replace { =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid entry. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/h_cust_bill.pm b/FS/FS/h_cust_bill.pm new file mode 100644 index 000000000..7a3d81146 --- /dev/null +++ b/FS/FS/h_cust_bill.pm @@ -0,0 +1,33 @@ +package FS::h_cust_bill; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::cust_bill; + +@ISA = qw( FS::h_Common FS::cust_bill ); + +sub table { 'h_cust_bill' }; + +=head1 NAME + +FS::h_cust_bill - Historical record of customer tax changes (old-style) + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_cust_bill object represents historical changes to invoices. +FS::h_cust_bill inherits from FS::h_Common and FS::cust_bill. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_bill>, L<FS::h_Common>, L<FS::Record>, schema.html from the base +documentation. + +=cut + +1; + diff --git a/FS/FS/h_cust_tax_exempt.pm b/FS/FS/h_cust_tax_exempt.pm new file mode 100644 index 000000000..9d2318bd5 --- /dev/null +++ b/FS/FS/h_cust_tax_exempt.pm @@ -0,0 +1,40 @@ +package FS::h_cust_tax_exempt; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::cust_tax_exempt; + +@ISA = qw( FS::h_Common FS::cust_tax_exempt ); + +sub table { 'h_cust_tax_exempt' }; + +=head1 NAME + +FS::h_cust_tax_exempt - Historical record of customer tax changes (old-style) + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_cust_tax_exempt object represents historical changes to old-style +customer tax exemptions. FS::h_cust_tax_exempt inherits from FS::h_Common and +FS::cust_tax_exempt. + +=head1 NOTE + +Old-style customer tax exemptions are only useful for legacy migrations - if +you are looking for current customer tax exemption data see +L<FS::cust_tax_exempt_pkg>. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_tax_exempt>, L<FS::cust_tax_exempt_pkg>, L<FS::h_Common>, +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/h_svc_phone.pm b/FS/FS/h_svc_phone.pm new file mode 100644 index 000000000..95898c7b0 --- /dev/null +++ b/FS/FS/h_svc_phone.pm @@ -0,0 +1,33 @@ +package FS::h_svc_phone; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::svc_phone; + +@ISA = qw( FS::h_Common FS::svc_phone ); + +sub table { 'h_svc_phone' }; + +=head1 NAME + +FS::h_svc_phone - Historical phone number objects + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_svc_phone object represents a historical phone number. +FS::h_svc_phone inherits from FS::h_Common and FS::svc_phone. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::h_Common>, L<FS::svc_phone>, L<FS::Record>, schema.html from the base +documentation. + +=cut + +1; + diff --git a/FS/FS/inventory_class.pm b/FS/FS/inventory_class.pm new file mode 100644 index 000000000..508889bca --- /dev/null +++ b/FS/FS/inventory_class.pm @@ -0,0 +1,164 @@ +package FS::inventory_class; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( dbh qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::inventory_class - Object methods for inventory_class records + +=head1 SYNOPSIS + + use FS::inventory_class; + + $record = new FS::inventory_class \%hash; + $record = new FS::inventory_class { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::inventory_class object represents a class of inventory, such as "DID +numbers" or "physical equipment serials". FS::inventory_class inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item classnum - primary key + +=item classname - Name of this class + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new inventory class. To add the class 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'inventory_class'; } + +=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 inventory class. 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('classnum') + || $self->ut_textn('classname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item num_avail + +Returns the number of available (unused/unallocated) inventory items of this +class (see L<FS::inventory_item>). + +=cut + +sub num_avail { + shift->num_sql('( svcnum IS NULL OR svcnum = 0 )'); +} + +sub num_sql { + my( $self, $sql ) = @_; + $sql = "AND $sql" if length($sql); + my $statement = + "SELECT COUNT(*) FROM inventory_item WHERE classnum = ? $sql"; + my $sth = dbh->prepare($statement) or die dbh->errstr. " preparing $statement"; + $sth->execute($self->classnum) or die $sth->errstr. " executing $statement"; + $sth->fetchrow_arrayref->[0]; +} + +=item num_used + +Returns the number of used (allocated) inventory items of this class (see +L<FS::inventory_class>). + +=cut + +sub num_used { + shift->num_sql("svcnum IS NOT NULL AND svcnum > 0 "); +} + +=item num_total + +Returns the total number of inventory items of this class (see +L<FS::inventory_class>). + +=cut + +sub num_total { + shift->num_sql(''); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::inventory_item>, L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/inventory_item.pm b/FS/FS/inventory_item.pm new file mode 100644 index 000000000..7fa350f2a --- /dev/null +++ b/FS/FS/inventory_item.pm @@ -0,0 +1,204 @@ +package FS::inventory_item; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( dbh qsearch qsearchs ); +use FS::cust_main_Mixin; +use FS::inventory_class; +use FS::cust_svc; + +@ISA = qw( FS::cust_main_Mixin FS::Record ); + +=head1 NAME + +FS::inventory_item - Object methods for inventory_item records + +=head1 SYNOPSIS + + use FS::inventory_item; + + $record = new FS::inventory_item \%hash; + $record = new FS::inventory_item { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::inventory_item object represents a specific piece of (real or virtual) +inventory, such as a specific DID or serial number. FS::inventory_item +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item itemnum - primary key + +=item classnum - Inventory class (see L<FS::inventory_class>) + +=item item - Item identifier (unique within its inventory class) + +=item svcnum - Customer servcie (see L<FS::cust_svc>) + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new item. To add the item 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'inventory_item'; } + +=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 item. 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('itemnum') + || $self->ut_foreign_key('classnum', 'inventory_class', 'classnum' ) + || $self->ut_text('item') + || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item cust_svc + +Returns the customer service associated with this inventory item, if the +item has been used (see L<FS::cust_svc>). + +=cut + +sub cust_svc { + my $self = shift; + return '' unless $self->svcnum; + qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } ); +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item batch_import + +=cut + +sub batch_import { + my $param = shift; + + my $fh = $param->{filehandle}; + + my $imported = 0; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $line; + while ( defined($line=<$fh>) ) { + + chomp $line; + + my $inventory_item = new FS::inventory_item { + 'classnum' => $param->{'classnum'}, + 'item' => $line, + }; + + my $error = $inventory_item->insert; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + + #or just skip? + #next; + } + + $imported++; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + #might want to disable this if we skip records for any reason... + return "Empty file!" unless $imported; + + ''; + +} + +=back + +=head1 BUGS + +maybe batch_import should be a regular method in FS::inventory_class + +=head1 SEE ALSO + +L<inventory_class>, L<cust_svc>, L<FS::Record>, schema.html from the base +documentation. + +=cut + +1; + diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm new file mode 100644 index 000000000..5dc2a8ec8 --- /dev/null +++ b/FS/FS/m2m_Common.pm @@ -0,0 +1,144 @@ +package FS::m2m_Common; + +use strict; +use vars qw( @ISA $DEBUG ); +use FS::Schema qw( dbdef ); +use FS::Record qw( qsearch qsearchs dbh ); + +#hmm. well. we seem to be used as a mixin. +#@ISA = qw( FS::Record ); + +$DEBUG = 0; + +=head1 NAME + +FS::m2m_Common - Mixin class for classes in a many-to-many relationship + +=head1 SYNOPSIS + +use FS::m2m_Common; + +@ISA = qw( FS::m2m_Common FS::Record ); + +=head1 DESCRIPTION + +FS::m2m_Common is intended as a mixin 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 OPTION => VALUE, ... + +Available options: + +link_table (required) - + +target_table (required) - + +params (required) - hashref; keys are primary key values in target_table (values are boolean). For convenience, keys may optionally be prefixed with the name +of the primary key, as in agentnum54 instead of 54, or passed as an arrayref +of values. + +=cut + +sub process_m2m { + my( $self, %opt ) = @_; + + my $self_pkey = $self->dbdef_table->primary_key; + my %hash = ( $self_pkey => $self->$self_pkey() ); + + 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; + + if ( ref($opt{'params'}) eq 'ARRAY' ) { + $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} }; + } + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + foreach my $del_obj ( + grep { + my $targetnum = $_->$target_pkey(); + ( ! $opt{'params'}->{$targetnum} + && ! $opt{'params'}->{"$target_pkey$targetnum"} + ); + } + qsearch( $link_table, \%hash ) + ) { + my $error = $del_obj->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + foreach my $add_targetnum ( + grep { ! qsearchs( $link_table, { %hash, $target_pkey => $_ } ) } + map { /^($target_pkey)?(\d+)$/; $2; } + grep { /^($target_pkey)?(\d+)$/ } + grep { $opt{'params'}->{$_} } + keys %{ $opt{'params'} } + ) { + + my $add_obj = "FS::$link_table"->new( { + %hash, + $target_pkey => $add_targetnum, + }); + my $error = $add_obj->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; +} + +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<FS::Record> + +=cut + +1; + diff --git a/FS/FS/m2name_Common.pm b/FS/FS/m2name_Common.pm new file mode 100644 index 000000000..7c9637e27 --- /dev/null +++ b/FS/FS/m2name_Common.pm @@ -0,0 +1,95 @@ +package FS::m2name_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::m2name_Common - Base class for tables with a related table listing names + +=head1 SYNOPSIS + +use FS::m2name_Common; + +@ISA = qw( FS::m2name_Common ); + +=head1 DESCRIPTION + +FS::m2name_Common is intended as a base class for classes which have a +related table that lists names. + +=head1 METHODS + +=over 4 + +=item process_m2name + +=cut + +sub process_m2name { + my( $self, %opt ) = @_; + + my $self_pkey = $self->dbdef_table->primary_key; + my $link_sourcekey = $opt{'num_col'} || $self_pkey; + + my $link_table = $self->_load_table($opt{'link_table'}); + + my $link_static = $opt{'link_static'} || {}; + + foreach my $name ( @{ $opt{'names_list'} } ) { + + my $obj = qsearchs( $link_table, { + $link_sourcekey => $self->$self_pkey(), + $opt{'name_col'} => $name, + %$link_static, + }); + + if ( $obj && ! $opt{'params'}->{"$link_table.$name"} ) { + + my $d_obj = $obj; #need to save $obj for below. + my $error = $d_obj->delete; + die "error deleting $d_obj for $link_table.$name: $error" if $error; + + } elsif ( $opt{'params'}->{"$link_table.$name"} && ! $obj ) { + + #ok to clobber it now (but bad form nonetheless?) + #$obj = new "FS::$link_table" ( { + $obj = "FS::$link_table"->new( { + $link_sourcekey => $self->$self_pkey(), + $opt{'name_col'} => $name, + %$link_static, + }); + my $error = $obj->insert; + die "error inserting $obj for $link_table.$name: $error" if $error; + } + + } + + ''; +} + +sub _load_table { + my( $self, $table ) = @_; + eval "use FS::$table"; + die $@ if $@; + $table; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm index 855b8b291..cbdc1d633 100644 --- a/FS/FS/msgcat.pm +++ b/FS/FS/msgcat.pm @@ -52,7 +52,8 @@ If you just want to B<use> message catalogs, see L<FS::Msgcat>. =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new message catalog entry. To add the message catalog entry 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<hash> method. @@ -91,8 +92,8 @@ returns the error, otherwise returns false. =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 +Checks all fields to make sure this is a valid message catalog entry. If there +is an error, returns the error, otherwise returns false. Called by the insert and replace methods. =cut diff --git a/FS/FS/nas.pm b/FS/FS/nas.pm index 3495339e0..97b0ea17d 100644 --- a/FS/FS/nas.pm +++ b/FS/FS/nas.pm @@ -98,7 +98,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid NAS. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm index f258fa1d6..2950b28d4 100644 --- a/FS/FS/option_Common.pm +++ b/FS/FS/option_Common.pm @@ -2,6 +2,7 @@ package FS::option_Common; use strict; use vars qw( @ISA $DEBUG ); +use Scalar::Util qw( blessed ); use FS::Record qw( qsearch qsearchs dbh ); @ISA = qw( FS::Record ); @@ -18,6 +19,11 @@ use FS::option_Common; @ISA = qw( FS::option_Common ); +#optional for non-standard names +sub _option_table { 'table_name'; } #defaults to ${table}_option +sub _option_namecol { 'column_name'; } #defaults to optionname +sub _option_valuecol { 'column_name'; } #defaults to optionvalue + =head1 DESCRIPTION FS::option_Common is intended as a base class for classes which have a @@ -66,14 +72,17 @@ sub insert { return $error; } - my $pkey = $self->pkey; + my $pkey = $self->primary_key; my $option_table = $self->option_table; + my $namecol = $self->_option_namecol; + my $valuecol = $self->_option_valuecol; + foreach my $optionname ( keys %{$options} ) { my $href = { - $pkey => $self->get($pkey), - 'optionname' => $optionname, - 'optionvalue' => $options->{$optionname}, + $pkey => $self->get($pkey), + $namecol => $optionname, + $valuecol => $options->{$optionname}, }; #my $option_record = eval "new FS::$option_table \$href"; @@ -123,7 +132,7 @@ sub delete { return $error; } - my $pkey = $self->pkey; + my $pkey = $self->primary_key; #my $option_table = $self->option_table; foreach my $obj ( $self->option_objects ) { @@ -140,7 +149,7 @@ sub delete { } -=item replace [ HASHREF | OPTION => VALUE ... ] +=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ] Replaces the OLD_RECORD with this one in the database. If there is an error, returns the error, otherwise returns false. @@ -152,12 +161,17 @@ created or modified (see L<FS::part_export_option>). sub replace { my $self = shift; - my $old = shift; + + my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') ) + ? shift + : $self->replace_old; + my $options = ( ref($_[0]) eq 'HASH' ) ? shift : { @_ }; - warn "FS::option_Common::insert called on $self with options ". + + warn "FS::option_Common::replace called on $self with options ". join(', ', map "$_ => ". $options->{$_}, keys %$options) if $DEBUG; @@ -178,30 +192,42 @@ sub replace { return $error; } - my $pkey = $self->pkey; + my $pkey = $self->primary_key; my $option_table = $self->option_table; + my $namecol = $self->_option_namecol; + my $valuecol = $self->_option_valuecol; + foreach my $optionname ( keys %{$options} ) { - my $old = qsearchs( $option_table, { - $pkey => $self->get($pkey), - 'optionname' => $optionname, + + warn "FS::option_Common::replace: inserting or replacing option: $optionname" + if $DEBUG > 1; + + my $oldopt = qsearchs( $option_table, { + $pkey => $self->get($pkey), + $namecol => $optionname, } ); my $href = { - $pkey => $self->get($pkey), - 'optionname' => $optionname, - 'optionvalue' => $options->{$optionname}, + $pkey => $self->get($pkey), + $namecol => $optionname, + $valuecol => $options->{$optionname}, }; - #my $new = eval "new FS::$option_table \$href"; + #my $newopt = eval "new FS::$option_table \$href"; #if ( $@ ) { # $dbh->rollback if $oldAutoCommit; # return $@; #} - my $new = "FS::$option_table"->new($href); + my $newopt = "FS::$option_table"->new($href); + + my $opt_pkey = $newopt->primary_key; - $new->optionnum($old->optionnum) if $old; - my $error = $old ? $new->replace($old) : $new->insert; + $newopt->$opt_pkey($oldopt->$opt_pkey) if $oldopt; + warn "FS::option_Common::replace: ". + ( $oldopt ? "$newopt -> replace($oldopt)" : "$newopt -> insert" ) + if $DEBUG > 2; + my $error = $oldopt ? $newopt->replace($oldopt) : $newopt->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -210,7 +236,7 @@ sub replace { #remove extraneous old options foreach my $opt ( - grep { !exists $options->{$_->optionname} } $old->option_objects + grep { !exists $options->{$_->$namecol()} } $old->option_objects ) { my $error = $opt->delete; if ( $error ) { @@ -233,7 +259,7 @@ Returns all options as FS::I<tablename>_option objects. sub option_objects { my $self = shift; - my $pkey = $self->pkey; + my $pkey = $self->primary_key; my $option_table = $self->option_table; qsearch($option_table, { $pkey => $self->get($pkey) } ); } @@ -246,7 +272,9 @@ Returns a list of option names and values suitable for assigning to a hash. sub options { my $self = shift; - map { $_->optionname => $_->optionvalue } $self->option_objects; + my $namecol = $self->_option_namecol; + my $valuecol = $self->_option_valuecol; + map { $_->$namecol() => $_->$valuecol() } $self->option_objects; } =item option OPTIONNAME @@ -257,30 +285,35 @@ Returns the option value for the given name, or the empty string. sub option { my $self = shift; - my $pkey = $self->pkey; + my $pkey = $self->primary_key; my $option_table = $self->option_table; - my $obj = - qsearchs($option_table, { - $pkey => $self->get($pkey), - optionname => shift, - } ); - $obj ? $obj->optionvalue : ''; + my $namecol = $self->_option_namecol; + my $valuecol = $self->_option_valuecol; + my $hashref = { + $pkey => $self->get($pkey), + $namecol => shift, + }; + warn "$self -> option: searching for ". + join(' / ', map { "$_ => ". $hashref->{$_} } keys %$hashref ) + if $DEBUG; + my $obj = qsearchs($option_table, $hashref); + $obj ? $obj->$valuecol() : ''; } -sub pkey { - my $self = shift; - my $pkey = $self->dbdef_table->primary_key; -} - sub option_table { my $self = shift; - my $option_table = $self->table . '_option'; + my $option_table = $self->_option_table; eval "use FS::$option_table"; die $@ if $@; $option_table; } +#defaults +sub _option_table { shift->table .'_option'; } +sub _option_namecol { 'optionname'; } +sub _option_valuecol { 'optionvalue'; } + =back =head1 BUGS diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm index 8143e3473..683f48423 100644 --- a/FS/FS/part_bill_event.pm +++ b/FS/FS/part_bill_event.pm @@ -1,11 +1,13 @@ package FS::part_bill_event; use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearch qsearchs ); +use vars qw( @ISA $DEBUG @EXPORT_OK ); +use FS::Record qw( dbh qsearch qsearchs ); use FS::Conf; -@ISA = qw(FS::Record); +@ISA = qw( FS::Record ); +@EXPORT_OK = qw( due_events ); +$DEBUG = 0; =head1 NAME @@ -26,6 +28,13 @@ FS::part_bill_event - Object methods for part_bill_event records $error = $record->check; + $error = $record->do_event( $direct_object ); + + @events = due_events ( { 'record' => $event_triggering_record, + 'payby' => $payby, + 'event_time => $_date, + 'extra_sql => $extra } ); + =head1 DESCRIPTION An FS::part_bill_event object represents an invoice event definition - @@ -51,6 +60,8 @@ FS::Record. The following fields are currently supported: =item plandata - additional plan data +=item reason - an associated reason for this event to fire + =item disabled - Disabled flag, empty or `Y' =back @@ -61,8 +72,8 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new invoice event definition. To add the example to the database, -see L<"insert">. +Creates a new invoice event definition. To add the invoice event definition 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<hash> method. @@ -122,13 +133,16 @@ sub check { my $c = $self->eventcode; + #yay, these regexen will go away with the event refactor + $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/ - or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\(\);\s*$/ + or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/ or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/ - or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/ +# or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/ + or $c =~ /^\s*\$cust_main\->apply_payments_and_credits; "";\s*$/ or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/ @@ -144,7 +158,7 @@ sub check { } my $error = $self->ut_numbern('eventpart') - || $self->ut_enum('payby', [qw( CARD DCRD CHEK DCHK LECB BILL COMP )] ) + || $self->ut_enum('payby', [qw( CARD DCLN DCRD CHEK DCHK LECB BILL COMP )] ) || $self->ut_text('event') || $self->ut_anything('eventcode') || $self->ut_number('seconds') @@ -152,6 +166,7 @@ sub check { || $self->ut_number('weight') || $self->ut_textn('plan') || $self->ut_anything('plandata') + || $self->ut_numbern('reason') ; #|| $self->ut_snumber('seconds') return $error if $error; @@ -175,6 +190,11 @@ sub check { } } + if ($self->reason){ + my $reasonr = qsearchs('reason', {'reasonnum' => $self->reason}); + return "Unknown reason" unless $reasonr; + } + $self->SUPER::check; } @@ -197,6 +217,119 @@ sub templatename { } } +=item due_events + +Returns the list of events due, if any, or false if there is none. +Requires record and payby, but event_time and extra_sql are optional. + +=cut + +sub due_events { + my ($record, $payby, $event_time, $extra_sql) = @_; + my $interval = 0; + if ($record->_date){ + $event_time = time unless $event_time; + $interval = $event_time - $record->_date; + } + sort { $a->seconds <=> $b->seconds + || $a->weight <=> $b->weight + || $a->eventpart <=> $b->eventpart } + grep { $_->seconds <= ( $interval ) + && ! qsearch( 'cust_bill_event', { + 'invnum' => $record->get($record->dbdef_table->primary_key), + 'eventpart' => $_->eventpart, + 'status' => 'done', + } ) + } + qsearch( { + 'table' => 'part_bill_event', + 'hashref' => { 'payby' => $payby, + 'disabled' => '', }, + 'extra_sql' => $extra_sql, + } ); + + +} + +=item do_event + +Performs the event and returns any errors that occur. +Requires a record on which to perform the event. +Should only be performed inside a transaction. + +=cut + +sub do_event { + my ($self, $object, %options) = @_; + warn " calling event (". $self->eventcode. ") for " . $object->table . " " , + $object->get($object->dbdef_table->primary_key) . "\n" if $DEBUG > 1; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + + # for "callback" -- heh + my $cust_main = $object->cust_main; + my $cust_bill; + if ($object->table eq 'cust_bill'){ + $cust_bill = $object; + } + my $cust_pay_batch; + if ($object->table eq 'cust_pay_batch'){ + $cust_pay_batch = $object; + } + + my $error; + { + local $SIG{__DIE__}; # don't want Mason __DIE__ handler active + $error = eval $self->eventcode; + } + + my $status = ''; + my $statustext = ''; + if ( $@ ) { + $status = 'failed'; + $statustext = $@; + } elsif ( $error ) { + $status = 'done'; + $statustext = $error; + } else { + $status = 'done'; + } + + #add cust_bill_event + my $cust_bill_event = new FS::cust_bill_event { +# 'invnum' => $object->get($object->dbdef_table->primary_key), + 'invnum' => $object->invnum, + 'eventpart' => $self->eventpart, + '_date' => time, + 'status' => $status, + 'statustext' => $statustext, + }; + $error = $cust_bill_event->insert; + if ( $error ) { + my $e = 'WARNING: Event run but database not updated - '. + 'error inserting cust_bill_event, invnum #'. $object->invnum . + ', eventpart '. $self->eventpart.": $error"; + warn $e; + return $e; + } + ''; +} + +=item reasontext + +Returns the text of any reason associated with this event. + +=cut + +sub reasontext { + my $self = shift; + my $r = qsearchs('reason', { 'reasonnum' => $self->reason }); + if ($r){ + $r->reason; + }else{ + ''; + } +} =back diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index dce2d2a44..6adcab94d 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -10,6 +10,9 @@ use FS::part_svc; use FS::part_export_option; use FS::export_svc; +#for export modules, though they should probably just use it themselves +use FS::queue; + @ISA = qw( FS::option_Common ); @EXPORT_OK = qw(export_info); diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm new file mode 100644 index 000000000..1be820a75 --- /dev/null +++ b/FS/FS/part_export/acct_plesk.pm @@ -0,0 +1,121 @@ +package FS::part_export::acct_plesk; + +use vars qw(@ISA %info); +use Tie::IxHash; +use FS::part_export; + +@ISA = qw(FS::part_export); + +tie my %options, 'Tie::IxHash', + 'URL' => { label=>'URL' }, + 'login' => { label=>'Login' }, + 'password' => { label=>'Password' }, + 'debug' => { label=>'Enable debugging', + type=>'checkbox' }, +; + +%info = ( + 'svc' => 'svc_acct', + 'desc' => 'Real-time export to Plesk managed mail service', + 'options'=> \%options, + 'notes' => <<'END' +Real-time export to +<a href="http://www.swsoft.com/">Plesk</a> managed server. +Requires installation of +<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a> +from CPAN. +END +); + +sub rebless { shift; } + +# experiment: want the status of these right away (don't want account to +# create or whatever and then get error in the queue from dup username or +# something), so no queueing + +sub _export_insert { + my( $self, $svc_acct ) = (shift, shift); + + $self->_plesk_command( 'mail_add', + $svc_acct->domain, + $svc_acct->username, + $svc_acct->_password, + ) || + $self->_export_unsuspend($svc_acct); +} + +sub _plesk_command { + my( $self, $method, $domain, @args ) = @_; + + eval "use Net::Plesk;"; + return $@ if $@; + + local($Net::Plesk::DEBUG) = 1 + if $self->option('debug'); + + my $plesk = new Net::Plesk ( + 'POST' => $self->option('URL'), + ':HTTP_AUTH_LOGIN' => $self->option('login'), + ':HTTP_AUTH_PASSWD' => $self->option('password'), + ); + + my $dresponse = $plesk->domain_get( $domain ); + return $dresponse->errortext unless $dresponse->is_success; + my $domainID = $dresponse->id; + + my $response = $plesk->$method($dresponse->id, @args); + return $response->errortext unless $response->is_success; + ''; + +} + +sub _export_replace { + my( $self, $new, $old ) = (shift, shift, shift); + + return "can't change domain with Plesk" + if $old->domain ne $new->domain; + return "can't change username with Plesk" + if $old->username ne $new->username; + return '' unless $old->_password ne $new->_password; + + $self->_plesk_command( 'mail_set', + $new->domain, + $new->username, + $new->_password, + $old->cust_svc->cust_pkg->susp ? 0 : 1, + ); +} + +sub _export_delete { + my( $self, $svc_acct ) = (shift, shift); + + $self->_plesk_command( 'mail_remove', + $svc_acct->domain, + $svc_acct->username, + ); +} + +sub _export_suspend { + my( $self, $svc_acct ) = (shift, shift); + + $self->_plesk_command( 'mail_set', + $svc_acct->domain, + $svc_acct->username, + $svc_acct->_password, + 0, + ); +} + +sub _export_unsuspend { + my( $self, $svc_acct ) = (shift, shift); + + $self->_plesk_command( 'mail_set', + $svc_acct->domain, + $svc_acct->username, + $svc_acct->_password, + 1, + ); +} + +1; + diff --git a/FS/FS/part_export/acct_sql.pm b/FS/FS/part_export/acct_sql.pm index 4b92e80f1..9f1ae7b5c 100644 --- a/FS/FS/part_export/acct_sql.pm +++ b/FS/FS/part_export/acct_sql.pm @@ -17,6 +17,10 @@ tie my %options, 'Tie::IxHash', 'Database schema mapping to Freeside methods.', type => 'textarea', }, + 'static' => { label => + 'Database schema mapping to static values.', + type => 'textarea', + }, 'primary_key' => { label => 'Database primary key' }, 'crypt' => { label => 'Password encryption', type=>'select', options=>[qw(crypt md5)], @@ -60,6 +64,17 @@ my $postfix_courierimap_alias_map = join('\n', map "$_ $postfix_courierimap_alias_map{$_}", keys %postfix_courierimap_alias_map ); +tie my %postfix_native_mailbox_map, 'Tie::IxHash', + 'userid' => 'email', + 'uid' => 'uid', + 'gid' => 'gid', + 'password' => 'ldap_password', + 'mail' => 'domain_slash_username', +; +my $postfix_native_mailbox_map = + join('\n', map "$_ $postfix_native_mailbox_map{$_}", + keys %postfix_native_mailbox_map ); + %info = ( 'svc' => 'svc_acct', 'desc' => 'Real-time export of accounts to SQL databases '. @@ -94,13 +109,21 @@ to be configured for different mail server setups. this.form.schema.value = "$postfix_courierimap_alias_map"; this.form.primary_key.value = "address"; '> + <LI><INPUT TYPE="button" VALUE="postfix_native_mailbox" onClick=' + this.form.table.value = "users"; + this.form.schema.value = "$postfix_native_mailbox_map"; + this.form.primary_key.value = "userid"; + '> </UL> END ); +sub _schema_map { shift->_map('schema'); } +sub _static_map { shift->_map('static'); } + sub _map { my $self = shift; - map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option('schema') ); + map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) ); } sub rebless { shift; } @@ -108,14 +131,22 @@ sub rebless { shift; } sub _export_insert { my($self, $svc_acct) = (shift, shift); - my %map = $self->_map; + my %schema = $self->_schema_map; + my %static = $self->_static_map; - my %record = map { my $value = $map{$_}; - my @arg = (); - push @arg, $self->option('crypt') - if $value eq 'crypt_password' && $self->option('crypt'); - $_ => $svc_acct->$value(@arg); - } keys %map; + my %record = ( + + ( map { $_ => $static{$_} } keys %static ), + + ( map { my $value = $schema{$_}; + my @arg = (); + push @arg, $self->option('crypt') + if $value eq 'crypt_password' && $self->option('crypt'); + $_ => $svc_acct->$value(@arg); + } keys %schema + ), + + ); my $err_or_queue = $self->acct_sql_queue( @@ -133,25 +164,33 @@ sub _export_insert { sub _export_replace { my($self, $new, $old) = (shift, shift, shift); - my %map = $self->_map; + my %schema = $self->_schema_map; + my %static = $self->_static_map; my @primary_key = (); if ( $self->option('primary_key') =~ /,/ ) { foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) { - my $keymap = $map{$key}; + my $keymap = $schema{$key}; push @primary_key, $old->$keymap(); } } else { - my $keymap = $map{$self->option('primary_key')}; + my $keymap = $schema{$self->option('primary_key')}; push @primary_key, $old->$keymap(); } - my %record = map { my $value = $map{$_}; - my @arg = (); - push @arg, $self->option('crypt') - if $value eq 'crypt_password' && $self->option('crypt'); - $_ => $new->$value(@arg); - } keys %map; + my %record = ( + + ( map { $_ => $static{$_} } keys %static ), + + ( map { my $value = $schema{$_}; + my @arg = (); + push @arg, $self->option('crypt') + if $value eq 'crypt_password' && $self->option('crypt'); + $_ => $new->$value(@arg); + } keys %schema + ), + + ); my $err_or_queue = $self->acct_sql_queue( $new->svcnum, @@ -167,16 +206,16 @@ sub _export_replace { sub _export_delete { my ( $self, $svc_acct ) = (shift, shift); - my %map = $self->_map; + my %schema = $self->_schema_map; my %primary_key = (); if ( $self->option('primary_key') =~ /,/ ) { foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) { - my $keymap = $map{$key}; + my $keymap = $schema{$key}; $primary_key{ $key } = $svc_acct->$keymap(); } } else { - my $keymap = $map{$self->option('primary_key')}; + my $keymap = $schema{$self->option('primary_key')}; $primary_key{ $self->option('primary_key') } = $svc_acct->$keymap(), } diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm index 6a1bf60eb..e25043fbb 100644 --- a/FS/FS/part_export/communigate_pro_singledomain.pm +++ b/FS/FS/part_export/communigate_pro_singledomain.pm @@ -20,7 +20,7 @@ tie my %options, 'Tie::IxHash', %FS::part_export::communigate_pro::options, Real time export to a <a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a> mail server. This is an unusual export to CommuniGate Pro that forces all -accounts into a single domain. As CommuniGate Pro supports multipledomains, +accounts into a single domain. As CommuniGate Pro supports multiple domains, unless you have a specific reason for using this export, you probably want to use the communigate_pro export instead. The <a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a> diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm index d15f41a84..994c113bf 100644 --- a/FS/FS/part_export/domain_shellcommands.pm +++ b/FS/FS/part_export/domain_shellcommands.pm @@ -112,19 +112,22 @@ sub _export_replace { ( $old_qdomain = $old_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES ( $new_qdomain = $new_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES - if ( $old->catchall ) { + { no strict 'refs'; - my $svc_acct = $old->catchall_svc_acct; - ${"old_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir); - } else { - ${"old_$_"} = '' foreach qw(uid gid dir); - } - if ( $new->catchall ) { - no strict 'refs'; - my $svc_acct = $new->catchall_svc_acct; - ${"new_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir); - } else { - ${"new_$_"} = '' foreach qw(uid gid dir); + + if ( $old->catchall ) { + my $svc_acct = $old->catchall_svc_acct; + ${"old_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir); + } else { + ${"old_$_"} = '' foreach qw(uid gid dir); + } + if ( $new->catchall ) { + my $svc_acct = $new->catchall_svc_acct; + ${"new_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir); + } else { + ${"new_$_"} = '' foreach qw(uid gid dir); + } + } #done setting variables for the command diff --git a/FS/FS/part_export/domain_sql.pm b/FS/FS/part_export/domain_sql.pm new file mode 100644 index 000000000..0ce1b16e3 --- /dev/null +++ b/FS/FS/part_export/domain_sql.pm @@ -0,0 +1,238 @@ +package FS::part_export::domain_sql; + +use vars qw(@ISA %info); +use Tie::IxHash; +use FS::part_export; + +@ISA = qw(FS::part_export); + +#quite a bit of false laziness w/acct_sql - some stuff should be generalized +#out to a "dababase base class" + +tie my %options, 'Tie::IxHash', + 'datasrc' => { label => 'DBI data source' }, + 'username' => { label => 'Database username' }, + 'password' => { label => 'Database password' }, + 'table' => { label => 'Database table' }, + 'schema' => { label => + 'Database schema mapping to Freeside methods.', + type => 'textarea', + }, + 'static' => { label => + 'Database schema mapping to static values.', + type => 'textarea', + }, + 'primary_key' => { label => 'Database primary key' }, +; + +tie my %postfix_transport_map, 'Tie::IxHash', + 'domain' => 'domain' +; +my $postfix_transport_map = + join('\n', map "$_ $postfix_transport_map{$_}", + keys %postfix_transport_map ); +tie my %postfix_transport_static, 'Tie::IxHash', + 'transport' => 'virtual:', +; +my $postfix_transport_static = + join('\n', map "$_ $postfix_transport_static{$_}", + keys %postfix_transport_static ); + +%info = ( + 'svc' => 'svc_domain', + 'desc' => 'Real time export of domains to SQL databases '. + '(postfix, others?)', + 'options' => \%options, + 'notes' => <<END +Export domains (svc_domain records) to SQL databases. Currently this is a +simple export with a default for Postfix, but it can be extended for other +uses. + +<BR><BR>Use these buttons for useful presets: +<UL> + <LI><INPUT TYPE="button" VALUE="postfix_transport" onClick=' + this.form.table.value = "transport"; + this.form.schema.value = "$postfix_transport_map"; + this.form.static.value = "$postfix_transport_static"; + this.form.primary_key.value = "domain"; + '> +</UL> +END +); + +sub _schema_map { shift->_map('schema'); } +sub _static_map { shift->_map('static'); } + +sub _map { + my $self = shift; + map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) ); +} + +sub _export_insert { + my($self, $svc_domain) = (shift, shift); + + my %schema = $self->_schema_map; + my %static = $self->_static_map; + + my %record = ( ( map { $_ => $static{$_} } keys %static ), + ( map { my $method = $schema{$_}; + $_ => $svc_domain->$method(); + } + keys %schema + ) + ); + + my $err_or_queue = + $self->domain_sql_queue( + $svc_domain->svcnum, + 'insert', + $self->option('table'), + %record + ); + return $err_or_queue unless ref($err_or_queue); + + ''; +} + +sub _export_replace { + my($self, $new, $old) = (shift, shift, shift); + + my %schema = $self->_schema_map; + my %static = $self->_static_map; + + my @primary_key = (); + if ( $self->option('primary_key') =~ /,/ ) { + foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) { + my $keymap = $schema{$key}; + push @primary_key, $old->$keymap(); + } + } else { + my $keymap = $map{$self->option('primary_key')}; + push @primary_key, $old->$keymap(); + } + + my %record = ( ( map { $_ => $static{$_} } keys %static ), + ( map { my $method = $schema{$_}; + $_ => $new->$method(); + } + keys %schema + ) + ); + + my $err_or_queue = $self->domain_sql_queue( + $new->svcnum, + 'replace', + $self->option('table'), + $self->option('primary_key'), @primary_key, + %record, + ); + return $err_or_queue unless ref($err_or_queue); + ''; +} + +sub _export_delete { + my ( $self, $svc_domain ) = (shift, shift); + + my %schema = $self->_schema_map; + my %static = $self->_static_map; + + my %primary_key = (); + if ( $self->option('primary_key') =~ /,/ ) { + foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) { + my $keymap = $map{$key}; + $primary_key{ $key } = $svc_domain->$keymap(); + } + } else { + my $keymap = $map{$self->option('primary_key')}; + $primary_key{ $self->option('primary_key') } = $svc_domain->$keymap(), + } + + my $err_or_queue = $self->domain_sql_queue( + $svc_domain->svcnum, + 'delete', + $self->option('table'), + %primary_key, + #$self->option('primary_key') => $svc_domain->$keymap(), + ); + return $err_or_queue unless ref($err_or_queue); + ''; +} + +sub domain_sql_queue { + my( $self, $svcnum, $method ) = (shift, shift, shift); + my $queue = new FS::queue { + 'svcnum' => $svcnum, + 'job' => "FS::part_export::domain_sql::domain_sql_$method", + }; + $queue->insert( + $self->option('datasrc'), + $self->option('username'), + $self->option('password'), + @_, + ) or $queue; +} + +sub domain_sql_insert { #subroutine, not method + my $dbh = domain_sql_connect(shift, shift, shift); + my( $table, %record ) = @_; + + my $sth = $dbh->prepare( + "INSERT INTO $table ( ". join(", ", keys %record). + " ) VALUES ( ". join(", ", map '?', keys %record ). " )" + ) or die $dbh->errstr; + + $sth->execute( values(%record) ) + or die "can't insert into $table table: ". $sth->errstr; + + $dbh->disconnect; +} + +sub domain_sql_delete { #subroutine, not method + my $dbh = domain_sql_connect(shift, shift, shift); + my( $table, %record ) = @_; + + my $sth = $dbh->prepare( + "DELETE FROM $table WHERE ". join(' AND ', map "$_ = ? ", keys %record ) + ) or die $dbh->errstr; + + $sth->execute( map $record{$_}, keys %record ) + or die "can't delete from $table table: ". $sth->errstr; + + $dbh->disconnect; +} + +sub domain_sql_replace { #subroutine, not method + my $dbh = domain_sql_connect(shift, shift, shift); + + my( $table, $pkey ) = ( shift, shift ); + + my %primary_key = (); + if ( $pkey =~ /,/ ) { + foreach my $key ( split(/\s*,\s*/, $pkey ) ) { + $primary_key{$key} = shift; + } + } else { + $primary_key{$pkey} = shift; + } + + my %record = @_; + + my $sth = $dbh->prepare( + "UPDATE $table". + ' SET '. join(', ', map "$_ = ?", keys %record ). + ' WHERE '. join(' AND ', map "$_ = ?", keys %primary_key ) + ) or die $dbh->errstr; + + $sth->execute( values(%record), values(%primary_key) ); + + $dbh->disconnect; +} + +sub domain_sql_connect { + #my($datasrc, $username, $password) = @_; + #DBI->connect($datasrc, $username, $password) or die $DBI::errstr; + DBI->connect(@_) or die $DBI::errstr; +} + +1; + diff --git a/FS/FS/part_export/nas_wrapper.pm b/FS/FS/part_export/nas_wrapper.pm new file mode 100644 index 000000000..fee9f48fe --- /dev/null +++ b/FS/FS/part_export/nas_wrapper.pm @@ -0,0 +1,310 @@ +package FS::part_export::nas_wrapper; + +=head1 FS::part_export::nas_wrapper + +This is a meta-export that triggers other exports for FS::svc_broadband objects +based on a set of configurable conditions. These conditions are defined by the +following FS::router virtual fields: + +=over 4 + +=item nas_conf - Per-router meta-export configuration. See L</"nas_conf Syntax">. + +=back + +=head2 nas_conf Syntax + +export_name|routernum[,routernum]|[field,condition[,field,condition]][||...] + +=over 4 + +=item export_name - Name or exportnum of the export to be executed. In order to specify export options you must use the exportnum form. (ex. 'router' for FS::part_export::router). + +=item routernum - FS::router routernum corresponding to the desired FS::router for which this export will be run. + +=item field - FS::svc_broadband field (real or virtual). The following condition (regex) will be matched against the value of this field. + +=item condition - A regular expression to be match against the value of the previously listed FS::svc_broadband field. + +=back + +If multiple routernum's are specified, then the export will be triggered for each router listed. If multiple field/condition pairs are present, then the results of the matches will be and'd. Note that if a false match is found, the rest of the matches may not be checked. + +You can specify multiple export/router/condition sets by concatenating them with '||'. + +=cut + +use strict; +use vars qw(@ISA %info $me $DEBUG); + +use FS::Record qw(qsearchs); +use FS::part_export; + +use Tie::IxHash; +use Data::Dumper qw(Dumper); + +@ISA = qw(FS::part_export); +$me = '[' . __PACKAGE__ . ']'; +$DEBUG = 1; + +%info = ( + 'svc' => 'svc_broadband', + 'desc' => 'A meta-export that triggers other svc_broadband exports.', + 'options' => {}, + 'notes' => '', +); + + +sub rebless { shift; } + +sub _export_insert { + my($self) = shift; + $self->_export_command('insert', @_); +} + +sub _export_delete { + my($self) = shift; + $self->_export_command('delete', @_); +} + +sub _export_suspend { + my($self) = shift; + $self->_export_command('suspend', @_); +} + +sub _export_unsuspend { + my($self) = shift; + $self->_export_command('unsuspend', @_); +} + +sub _export_replace { + my($self) = shift; + $self->_export_command('replace', @_); +} + +sub _export_command { + my ( $self, $action, $svc_broadband) = (shift, shift, shift); + + my ($new, $old); + if ($action eq 'replace') { + $new = $svc_broadband; + $old = shift; + } + + my $router = $svc_broadband->addr_block->router; + + return '' unless grep(/^nas_conf$/, $router->fields); + my $nas_conf = $router->nas_conf; + + my $child_exports = &_parse_nas_conf($nas_conf); + + my $error = ''; + + my $queue_child_exports = {}; + + # Similar to FS::svc_Common::replace, calling insert, delete, and replace + # exports where necessary depending on which conditions match. + if ($action eq 'replace') { + + my @new_child_exports = (); + my @old_child_exports = (); + + # Find all the matching "new" child exports. + foreach my $child_export (@$child_exports) { + my $match = &_test_child_export_conditions( + $child_export->{'conditions'}, + $new, + ); + + if ($match) { + push @new_child_exports, $child_export; + } + } + + # Find all the matching "old" child exports. + foreach my $child_export (@$child_exports) { + my $match = &_test_child_export_conditions( + $child_export->{'conditions'}, + $old, + ); + + if ($match) { + push @old_child_exports, $child_export; + } + } + + # Insert exports for new. + push @{$queue_child_exports->{'insert'}}, ( + map { + my $new_child_export = $_; + if (! grep { $new_child_export eq $_ } @old_child_exports) { + $new_child_export->{'args'} = [ $new ]; + $new_child_export; + } else { + (); + } + } @new_child_exports + ); + + # Replace exports for new and old. + push @{$queue_child_exports->{'replace'}}, ( + map { + my $new_child_export = $_; + if (grep { $new_child_export eq $_ } @old_child_exports) { + $new_child_export->{'args'} = [ $new, $old ]; + $new_child_export; + } else { + (); + } + } @new_child_exports + ); + + # Delete exports for old. + push @{$queue_child_exports->{'delete'}}, ( + grep { + my $old_child_export = $_; + if (! grep { $old_child_export eq $_ } @new_child_exports) { + $old_child_export->{'args'} = [ $old ]; + $old_child_export; + } else { + (); + } + } @old_child_exports + ); + + } else { + + foreach my $child_export (@$child_exports) { + my $match = &_test_child_export_conditions( + $child_export->{'conditions'}, + $svc_broadband, + ); + + if ($match) { + $child_export->{'args'} = [ $svc_broadband ]; + push @{$queue_child_exports->{$action}}, $child_export; + } + } + + } + + warn "[debug]$me Dispatching child exports... " + . &Dumper($queue_child_exports); + + # Actually call the child exports now, with their preset action and arguments. + foreach my $_action (keys(%$queue_child_exports)) { + + foreach my $_child_export (@{$queue_child_exports->{$_action}}) { + $error = &_dispatch_child_export( + $_child_export, + $_action, + @{$_child_export->{'args'}}, + ); + + # Bail if there's an error queueing one of the exports. + # This will all get rolled-back. + return $error if $error; + } + + } + + return ''; + +} + + +sub _parse_nas_conf { + + my $nas_conf = shift; + my @child_exports = (); + + foreach my $cond_set ($nas_conf =~ m/(.*?[^\\])(?:\|\||$)/g) { + + warn "[debug]$me cond_set is '$cond_set'" if $DEBUG; + + my @args = $cond_set =~ m/(.*?[^\\])(?:\||$)/g; + + my %child_export = ( + 'export' => $args[0], + 'routernum' => [ split(/,\s*/, $args[1]) ], + 'conditions' => { @args[2..$#args] }, + ); + + warn "[debug]$me " . Dumper(\%child_export) if $DEBUG; + + push @child_exports, { %child_export }; + + } + + return \@child_exports; + +} + +sub _dispatch_child_export { + + my ($child_export, $action, @args) = (shift, shift, @_); + + my $child_export_name = $child_export->{'export'}; + my @routernums = @{$child_export->{'routernum'}}; + + my $error = ''; + + # And the real hack begins... + + my $child_part_export; + if ($child_export_name =~ /^(\d+)$/) { + my $exportnum = $1; + $child_part_export = qsearchs('part_export', { exportnum => $exportnum }); + unless ($child_part_export) { + return "No such FS::part_export with exportnum '$exportnum'"; + } + + $child_export_name = $child_part_export->exporttype; + } else { + $child_part_export = new FS::part_export { + 'exporttype' => $child_export_name, + 'machine' => 'bogus', + }; + } + + warn "[debug]$me running export '$child_export_name' for routernum(s) '" + . join(',', @routernums) . "'" if $DEBUG; + + my $cmd_method = "_export_$action"; + + foreach my $routernum (@routernums) { + $error ||= $child_part_export->$cmd_method( + @args, + 'routernum' => $routernum, + ); + last if $error; + } + + warn "[debug]$me export '$child_export_name' returned '$error'" + if $DEBUG; + + return $error; + +} + +sub _test_child_export_conditions { + + my ($conditions, $svc_broadband) = (shift, shift); + + my $match = 1; + foreach my $cond_field (keys %$conditions) { + my $cond_regex = $conditions->{$cond_field}; + warn "[debug]$me Condition: $cond_field =~ /$cond_regex/" if $DEBUG; + unless ($svc_broadband->get($cond_field) =~ /$cond_regex/) { + $match = 0; + last; + } + } + + return $match; + +} + + +1; + diff --git a/FS/FS/part_export/prizm.pm b/FS/FS/part_export/prizm.pm new file mode 100644 index 000000000..711888d1f --- /dev/null +++ b/FS/FS/part_export/prizm.pm @@ -0,0 +1,361 @@ +package FS::part_export::prizm; + +use vars qw(@ISA %info %options $DEBUG); +use Tie::IxHash; +use FS::Record qw(fields); +use FS::part_export; + +@ISA = qw(FS::part_export); +$DEBUG = 1; + +tie %options, 'Tie::IxHash', + 'url' => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' }, + 'user' => { label => 'Northbound username', default=>'nbi' }, + 'password' => { label => 'Password', default => '' }, +; + +%info = ( + 'svc' => 'svc_broadband', + 'desc' => 'Real-time export to Northbound Interface', + 'options' => \%options, + 'nodomain' => 'Y', + 'notes' => 'These are notes.' +); + +sub prizm_command { + my ($self,$namespace,$method) = (shift,shift,shift); + + eval "use Net::Prizm qw(CustomerInfo PrizmElement);"; + die $@ if $@; + + my $prizm = new Net::Prizm ( + namespace => $namespace, + url => $self->option('url'), + user => $self->option('user'), + password => $self->option('password'), + ); + + $prizm->$method(@_); +} + +sub _export_insert { + my( $self, $svc ) = ( shift, shift ); + + my $cust_main = $svc->cust_svc->cust_pkg->cust_main; + + my $err_or_som = $self->prizm_command(CustomerIfService, 'getCustomers', + ['import_id'], + [$cust_main->custnum], + ['='], + ); + return $err_or_som + unless ref($err_or_som); + + my $pre = ''; + if ( defined $cust_main->dbdef_table->column('ship_last') ) { + $pre = $cust_main->ship_last ? 'ship_' : ''; + } + my $name = $pre ? $cust_main->ship_name : $cust_main->name; + my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method } + qw (address1 address2 city state zip) + ); + my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method } + qw (daytime night) + ); + + my $pcustomer; + if ($err_or_som->result->[0]) { + $pcustomer = $err_or_som->result->[0]->customerId; + }else{ + my $chashref = $cust_main->hashref; + my $customerinfo = { + importId => $cust_main->custnum, + customerName => $name, + customerType => 'freeside', + address1 => $chashref->{"${pre}address1"}, + address2 => $chashref->{"${pre}address2"}, + city => $chashref->{"${pre}city"}, + state => $chashref->{"${pre}state"}, + zipCode => $chashref->{"${pre}zip"}, + workPhone => $chashref->{"${pre}daytime"}, + homePhone => $chashref->{"${pre}night"}, + email => @{[$cust_main->invoicing_list_emailonly]}[0], + extraFieldNames => [ 'country', 'freesideId', + ], + extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum, + ], + }; + + $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer', + $customerinfo); + return $err_or_som + unless ref($err_or_som); + + $pcustomer = $err_or_som->result; + } + warn "multiple prizm customers found for $cust_main->custnum" + if scalar(@$pcustomer) > 1; + + #kinda big question/expensive + $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements', + ['Network Default Gateway Address'], + [$svc->addr_block->ip_gateway], + ['='], + ); + return $err_or_som + unless ref($err_or_som); + + return "No elements in network" unless exists $err_or_som->result->[0]; + + my $networkid = 0; + for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) { + if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){ + $networkid = $err_or_som->result->[0]->attributeValues->[$i]; + last; + } + } + + $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement', + $networkid, + $svc->mac_addr, + $name . " " . $svc->description, + $location, + $contact, + sprintf("%032X", $svc->authkey), + $svc->cust_svc->cust_pkg->part_pkg->pkg, + $svc->vlan_profile, + 1, + ); + return $err_or_som + unless ref($err_or_som); + + my (@names) = ('Management IP', + 'GPS Latitude', + 'GPS Longitude', + 'GPS Altitude', + 'Site Name', + 'Site Location', + 'Site Contact', + ); + my (@values) = ($svc->ip_addr, + $svc->latitude, + $svc->longitude, + $svc->altitude, + $name, + $location, + $contact, + ); + $element = $err_or_som->result->elementId; + $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig', + [ $element ], + \@names, + \@values, + 0, + 1, + ); + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet', + [ $element ], + $svc->vlan_profile, + 0, + 1, + ); + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet', + [ $element ], + $svc->cust_svc->cust_pkg->part_pkg->pkg, + 0, + 1, + ); + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('NetworkIfService', + 'activateNetworkElements', + [ $element ], + 1, + 1, + ); + + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('CustomerIfService', + 'addElementToCustomer', + 0, + $cust_main->custnum, + 0, + $svc->mac_addr, + ); + + return $err_or_som + unless ref($err_or_som); + + ''; +} + +sub _export_delete { + my( $self, $svc ) = ( shift, shift ); + + my $custnum = $svc->cust_svc->cust_pkg->cust_main->custnum; + + my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements', + ['MAC Address'], + [$svc->mac_addr], + ['='], + ); + return $err_or_som + unless ref($err_or_som); + + return "Can't find prizm element for " . $svc->mac_addr + unless $err_or_som->result->[0]; + + $err_or_som = $self->prizm_command('NetworkIfService', + 'suspendNetworkElements', + [$err_or_som->result->[0]->elementId], + 1, + 1, + ); + + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('CustomerIfService', + 'removeElementFromCustomer', + 0, + $custnum, + 0, + $svc->mac_addr, + ); + + return $err_or_som + unless ref($err_or_som); + + ''; +} + +sub _export_replace { + my( $self, $new, $old ) = ( shift, shift, shift ); + + my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements', + [ 'MAC Address' ], + [ $old->mac_addr ], + [ '=' ], + ); + return $err_or_som + unless ref($err_or_som); + + return "Can't find prizm element for " . $old->mac_addr + unless $err_or_som->result->[0]; + + my %freeside2prizm = ( mac_addr => 'MAC Address', + ip_addr => 'Management IP', + latitude => 'GPS Latitude', + longitude => 'GPS Longitude', + altitude => 'GPS Altitude', + authkey => 'Authentication Key', + ); + + my (@values); + my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} } + grep { $old->$_ ne $new->$_ } + grep { exists($freeside2prizm{$_}) } + fields( 'svc_broadband' ); + + if ($old->description ne $new->description) { + my $cust_main = $old->cust_svc->cust_pkg->cust_main; + my $name = defined($cust_main->dbdef_table->column('ship_last')) + ? $cust_main->ship_name + : $cust_main->name; + push @values, $name . " " . $new->description; + push @names, "Site Name"; + } + + my $element = $err_or_som->result->[0]->elementId; + + $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig', + [ $element ], + \@names, + \@values, + 0, + 1, + ); + return $err_or_som + unless ref($err_or_som); + + $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet', + [ $element ], + $new->vlan_profile, + 0, + 1, + ) + if $old->vlan_profile ne $new->vlan_profile; + + return $err_or_som + unless ref($err_or_som); + + ''; + +} + +sub _export_suspend { + my( $self, $svc ) = ( shift, shift ); + + my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements', + [ 'MAC Address' ], + [ $svc->mac_addr ], + [ '=' ], + ); + return $err_or_som + unless ref($err_or_som); + + return "Can't find prizm element for " . $svc->mac_addr + unless $err_or_som->result->[0]; + + $err_or_som = $self->prizm_command('NetworkIfService', + 'suspendNetworkElements', + [ $err_or_som->result->[0]->elementId ], + 1, + 1, + ); + + return $err_or_som + unless ref($err_or_som); + + ''; + +} + +sub _export_unsuspend { + my( $self, $svc ) = ( shift, shift ); + + my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements', + [ 'MAC Address' ], + [ $svc->mac_addr ], + [ '=' ], + ); + return $err_or_som + unless ref($err_or_som); + + return "Can't find prizm element for " . $svc->mac_addr + unless $err_or_som->result->[0]; + + $err_or_som = $self->prizm_command('NetworkIfService', + 'activateNetworkElements', + [ $err_or_som->result->[0]->elementId ], + 1, + 1, + ); + + return $err_or_som + unless ref($err_or_som); + + ''; + +} + +1; diff --git a/FS/FS/part_export/router.pm b/FS/FS/part_export/router.pm index 648a4372b..e14b57932 100644 --- a/FS/FS/part_export/router.pm +++ b/FS/FS/part_export/router.pm @@ -5,35 +5,47 @@ package FS::part_export::router; This export connects to a router and transmits commands via telnet or SSH. It requires the following custom router fields: +=head1 Required custom fields + =over 4 -=item admin_address - IP address (or hostname) to connect +=item admin_address - IP address (or hostname) to connect. -=item admin_user - username for admin access +=item admin_user - Username for the router. -=item admin_password - password for admin access +=item admin_password - Password for the router. -=back +=item admin_protocol - Protocol to use for the router. 'telnet' or 'ssh'. The ssh protocol only support password-less (ie. RSA key) authentication. As such, the admin_password field isn't used if ssh is specified. -The export itself needs the following options: +=item admin_timeout - Time in seconds to wait for a connection. -=over 4 +=item admin_prompt - A regular expression matching the router's prompt. See Net::Telnet for details. Only applies to the 'telnet' protocol. + +=item admin_cmd_insert - Insert export command. See below. -=item insert, replace, delete - command strings (to be interpolated) +=item admin_cmd_delete - Delete export command. See below. -=item Prompt - prompt string to expect from router after successful login +=item admin_cmd_replace - Replace export command. See below. -=item Timeout - time to wait for prompt string +=item admin_cmd_suspend - Suspend export command. See below. + +=item admin_cmd_unsuspend - Unsuspend export command. See below. + +The admin_cmd_* virtual fields, if set, will be double quoted, eval'd, and executed on the router specified. + +If any of the required router virtual fields are not defined, then the export silently declines. =back -(Prompt and Timeout are required only for telnet connections.) +The export itself takes no options. =cut -use vars qw(@ISA %info @saltset); +use strict; +use vars qw(@ISA %info $me $DEBUG); use Tie::IxHash; use String::ShellQuote; +use FS::Record qw(qsearchs); use FS::part_export; @ISA = qw(FS::part_export); @@ -44,26 +56,32 @@ tie my %options, 'Tie::IxHash', type =>'select', options => [qw(telnet ssh)], default => 'telnet'}, - 'insert' => {label=>'Insert command', default=>'' }, - 'delete' => {label=>'Delete command', default=>'' }, - 'replace' => {label=>'Replace command', default=>'' }, - 'Timeout' => {label=>'Time to wait for prompt', default=>'20' }, - 'Prompt' => {label=>'Prompt string', default=>'#' } ; %info = ( 'svc' => 'svc_broadband', 'desc' => 'Send a command to a router.', 'options' => \%options, - 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. ( more detailed description from Kristian / fire2wire? )', + 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. This export will execute if the following virtual fields are set on the router: admin_user, admin_password, admin_address, admin_timeout, admin_prompt. Option virtual fields are: admin_cmd_insert, admin_cmd_replace, admin_cmd_delete, admin_cmd_suspend, admin_cmd_unsuspend.', ); -@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); +$me = '[' . __PACKAGE__ . ']'; +$DEBUG = 1; + sub rebless { shift; } +sub _field_prefix { 'admin'; } + +sub _req_router_fields { + map { + $_[0]->_field_prefix . '_' . $_ + } (qw(address prompt user)); +} + sub _export_insert { my($self) = shift; + warn "Running insert for " . ref($self); $self->_export_command('insert', @_); } @@ -82,83 +100,159 @@ sub _export_unsuspend { $self->_export_command('unsuspend', @_); } -sub _export_command { - my ( $self, $action, $svc_broadband) = (shift, shift, shift); - my $command = $self->option($action); - return '' if $command =~ /^\s*$/; +sub _export_replace { + my($self) = shift; + $self->_export_command('replace', @_); +} - no strict 'vars'; - { - no strict 'refs'; - ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields; +sub _export_command { + my ($self, $action, $svc_broadband) = (shift, shift, shift); + my ($error, $old); + + if ($action eq 'replace') { + $old = shift; } + + warn "[debug]$me Processing action '$action'" if $DEBUG; + # fetch router info - my $router = $svc_broadband->addr_block->router; - my %r; - $r{$_} = $router->getfield($_) foreach $router->virtual_fields; - #warn qq("$command"); - #warn eval(qq("$command")); - - warn "admin_address: '$r{admin_address}'"; - - if ($r{admin_address} ne '') { - $self->router_queue( $svc_broadband->svcnum, $self->option('protocol'), - user => $r{admin_user}, - password => $r{admin_password}, - host => $r{admin_address}, - Timeout => $self->option('Timeout'), - Prompt => $self->option('Prompt'), - command => eval(qq("$command")), - ); - } else { + my $router = $self->_get_router($svc_broadband, @_); + unless ($router) { + return "Unable to lookup router for $action export"; + } + + unless ($self->_check_router_fields($router)) { + # Virtual fields aren't defined. Exit silently. + warn "[debug]$me Required router virtual fields not defined. Returning..."; return ''; } + + my $args; + ($error, $args) = $self->_prepare_args( + $action, + $router, + $svc_broadband, + ($old ? $old : ()), + @_ + ); + + if ($error) { + # Error occured while preparing args. + return $error; + } elsif (not defined $args) { + # Silently decline. + warn "[debug]$me Declining '$action' export"; + return ''; + } # else ... queue the export. + + warn "[debug]$me Queueing with args: " . join(', ', @$args) if $DEBUG; + + return( + $self->_queue( + $svc_broadband->svcnum, + $self->_get_cmd_sub($svc_broadband, $router), + @$args + ) + ); + } -sub _export_replace { +sub _prepare_args { - # We don't handle the case of a svc_broadband moving between routers. - # If you want to do that, reprovision the service. + my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift); + my $old = shift if ($action eq 'replace'); + + my $field_prefix = $self->_field_prefix; + my $command = $router->getfield("${field_prefix}_cmd_${action}"); + unless ($command) { + warn "[debug]$me router custom field '${field_prefix}_cmd_$action' " + . "is not defined." if $DEBUG; + return ''; + } - my($self, $new, $old ) = (shift, shift, shift); - my $command = $self->option('replace'); - no strict 'vars'; { + no strict 'vars'; no strict 'refs'; - ${"old_$_"} = $old->getfield($_) foreach $old->fields; - ${"new_$_"} = $new->getfield($_) foreach $new->fields; + + if ($action eq 'replace') { + ${"old_$_"} = $old->getfield($_) foreach $old->fields; + ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields; + $command = eval(qq("$command")); + } else { + ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields; + $command = eval(qq("$command")); + } + return $@ if $@; } - my $router = $new->addr_block->router; - my %r; - $r{$_} = $router->getfield($_) foreach $router->virtual_fields; - - if ($r{admin_address} ne '') { - $self->router_queue( $new->svcnum, $self->option('protocol'), - user => $r{admin_user}, - password => $r{admin_password}, - host => $r{admin_address}, - Timeout => $self->option('Timeout'), - Prompt => $self->option('Prompt'), - command => eval(qq("$command")), - ); - } else { - return ''; + my $args = [ + 'user' => $router->getfield($field_prefix . '_user'), + 'password' => $router->getfield($field_prefix . '_password'), + 'host' => $router->getfield($field_prefix . '_address'), + 'Timeout' => $router->getfield($field_prefix . '_timeout'), + 'Prompt' => $router->getfield($field_prefix . '_prompt'), + 'command' => $command, + ]; + + return('', $args); + +} + +sub _get_cmd_sub { + + my ($self, $svc_broadband, $router) = (shift, shift, shift); + + my $protocol = ( + $router->getfield($self->_field_prefix . '_protocol') =~ /^(telnet|ssh)$/ + ) ? $1 : 'telnet'; + + return(ref($self)."::".$protocol."_cmd"); + +} + +sub _check_router_fields { + + my ($self, $router, $action) = (shift, shift, shift); + my @check_fields = $self->_req_router_fields; + + foreach (@check_fields) { + if ($router->getfield($_) eq '') { + warn "[debug]$me Required field '$_' is unset"; + return 0; + } else { + return 1; + } } + } -#a good idea to queue anything that could fail or take any time -sub router_queue { +sub _queue { #warn join ':', @_; - my( $self, $svcnum, $protocol ) = (shift, shift, shift); + my( $self, $svcnum, $cmd_sub ) = (shift, shift, shift); my $queue = new FS::queue { 'svcnum' => $svcnum, }; - $queue->job ("FS::part_export::router::".$protocol."_cmd"); - $queue->insert( @_ ); + $queue->job($cmd_sub); + $queue->insert(@_); +} + +sub _get_router { + my ($self, $svc_broadband, %args) = (shift, shift, shift, @_); + + my $router; + if ($args{'routernum'}) { + $router = qsearchs('router', { routernum => $args{'routernum'}}); + } else { + $router = $svc_broadband->addr_block->router; + } + + return($router); + } -sub ssh_cmd { #subroutine, not method + +# Subroutines +sub ssh_cmd { use Net::SSH '0.08'; &Net::SSH::ssh_cmd( { @_ } ); } @@ -179,12 +273,4 @@ sub telnet_cmd { die @error if (grep /^ERROR/, @error); } -#sub router_insert { #subroutine, not method -#} -#sub router_replace { #subroutine, not method -#} -#sub router_delete { #subroutine, not method -#} - 1; - diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 646c5ff71..b43033405 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -4,6 +4,7 @@ use vars qw(@ISA %info); use Tie::IxHash; use String::ShellQuote; use FS::part_export; +use FS::Record qw( qsearch qsearchs ); @ISA = qw(FS::part_export); @@ -60,6 +61,10 @@ tie my %options, 'Tie::IxHash', type=>'select', options=>[qw(crypt md5)], default => 'crypt', }, + 'groups_susp_reason' => { label => + 'Radius group mapping to reason (via template user)', + type => 'textarea', + }, ; %info = ( @@ -151,22 +156,33 @@ old_ for replace operations): <UL> <LI><code>$username</code> <LI><code>$_password</code> - <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes) - <LI><code>$crypt_password</code> - encrypted password, already quoted for the shell (do not add additional quotes) + <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes). + <LI><code>$crypt_password</code> - encrypted password. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). <LI><code>$uid</code> <LI><code>$gid</code> - <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes) - <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes) - <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes) + <LI><code>$finger</code> - GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). + <LI><code>$first</code> - First name of GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). + <LI><code>$last</code> - Last name of GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). <LI><code>$dir</code> - home directory <LI><code>$shell</code> <LI><code>$quota</code> <LI><code>@radius_groups</code> + <LI><code>$reasonnum (when suspending)</code> + <LI><code>$reasontext (when suspending)</code> + <LI><code>$reasontypenum (when suspending)</code> + <LI><code>$reasontypetext (when suspending)</code> <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available. </UL> END ); +sub _groups_susp_reason_map { shift->_map('groups_susp_reason'); } + +sub _map { + my $self = shift; + map { reverse(/^\s*(\S+)\s*(.*)\s*$/) } split("\n", $self->option(shift) ); +} + sub rebless { shift; } sub _export_insert { @@ -199,7 +215,6 @@ sub _export_command_or_super { } }; - sub _export_command { my ( $self, $action, $svc_acct) = (shift, shift, shift); my $command = $self->option($action); @@ -211,6 +226,7 @@ sub _export_command { no strict 'refs'; ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; + # snarfs are unused at this point? my $count = 1; foreach my $acct_snarf ( $svc_acct->acct_snarf ) { ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) ) @@ -228,22 +244,61 @@ sub _export_command { $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/; ($first, $last ) = ( $1, $2 ); - $first = shell_quote $first; - $last = shell_quote $last; - $finger = shell_quote $finger; - $quoted_password = shell_quote $_password; $domain = $svc_acct->domain; - $crypt_password = - shell_quote( $svc_acct->crypt_password( $self->option('crypt') ) ); + $quoted_password = shell_quote $_password; + + $crypt_password = $svc_acct->crypt_password( $self->option('crypt') ); @radius_groups = $svc_acct->radius_groups; + my ($reasonnum, $reasontext, $reasontypenum, $reasontypetext); + if ( $cust_pkg && $action eq 'suspend' && (my $r = $cust_pkg->last_reason) ) { + $reasonnum = $r->reasonnum; + $reasontext = $r->reason; + $reasontypenum = $r->reason_type; + $reasontypetext = $r->reasontype->type; + + my %reasonmap = $self->_groups_susp_reason_map; + my $userspec = ''; + $userspec = $reasonmap{$reasonnum} + if exists($reasonmap{$reasonnum}); + $userspec = $reasonmap{$reasontext} + if (!$userspec && exists($reasonmap{$reasontext})); + + my $suspend_user; + if ( $userspec =~ /^\d+$/ ) { + $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } ); + } elsif ( $userspec =~ /^\S+\@\S+$/ ) { + my ($username,$domain) = split(/\@/, $userspec); + for my $user (qsearch( 'svc_acct', { 'username' => $username } )){ + $suspend_user = $user if $userspec eq $user->email; + } + } elsif ($userspec) { + $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } ); + } + + @radius_groups = $suspend_user->radius_groups + if $suspend_user; + + } else { + $reasonnum = $reasontext = $reasontypenum = $reasontypetext = ''; + } + + my $stdin_string = eval(qq("$stdin")); + + $first = shell_quote $first; + $last = shell_quote $last; + $finger = shell_quote $finger; + $crypt_password = shell_quote $crypt_password; + + my $command_string = eval(qq("$command")); + $self->shellcommands_queue( $svc_acct->svcnum, user => $self->option('user')||'root', host => $self->machine, - command => eval(qq("$command")), - stdin_string => eval(qq("$stdin")), + command => $command_string, + stdin_string => $stdin_string, ); } @@ -257,18 +312,14 @@ sub _export_replace { ${"old_$_"} = $old->getfield($_) foreach $old->fields; ${"new_$_"} = $new->getfield($_) foreach $new->fields; } - $new_finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/; + $new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/; ($new_first, $new_last ) = ( $1, $2 ); - $new_first = shell_quote $new_first; - $new_last = shell_quote $new_last; - $new_finger = shell_quote $new_finger; $quoted_new__password = shell_quote $new__password; #old, wrong? $new_quoted_password = shell_quote $new__password; #new, better? $old_domain = $old->domain; $new_domain = $new->domain; - $new_crypt_password = - shell_quote( $new->crypt_password( $self->option('crypt') ) ); + $new_crypt_password = $new->crypt_password( $self->option('crypt') ); @old_radius_groups = $old->radius_groups; @new_radius_groups = $new->radius_groups; @@ -300,11 +351,20 @@ sub _export_replace { return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')' if $error; + my $stdin_string = eval(qq("$stdin")); + + $new_first = shell_quote $new_first; + $new_last = shell_quote $new_last; + $new_finger = shell_quote $new_finger; + $new_crypt_password = shell_quote $new_crypt_password; + + my $command_string = eval(qq("$command")); + $self->shellcommands_queue( $new->svcnum, user => $self->option('user')||'root', host => $self->machine, - command => eval(qq("$command")), - stdin_string => eval(qq("$stdin")), + command => $command_string, + stdin_string => $stdin_string, ); } diff --git a/FS/FS/part_export/snmp.pm b/FS/FS/part_export/snmp.pm new file mode 100644 index 000000000..81b3c7eb2 --- /dev/null +++ b/FS/FS/part_export/snmp.pm @@ -0,0 +1,256 @@ +package FS::part_export::snmp; + +=head1 FS::part_export::snmp + +This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly. + +=head1 Required custom fields + +=over 4 + +=item snmp_address - IP address (or hostname) of the router/agent + +=item snmp_comm - R/W SNMP community of the router/agent + +=item snmp_version - SNMP version of the router/agent + +=back + +=head1 Optional custom fields + +=over 4 + +=item snmp_cmd_insert - SNMP SETs to perform on insert. See L</Formatting> + +=item snmp_cmd_replace - SNMP SETs to perform on replace. See L</Formatting> + +=item snmp_cmd_delete - SNMP SETs to perform on delete. See L</Formatting> + +=item snmp_cmd_suspend - SNMP SETs to perform on suspend. See L</Formatting> + +=item snmp_cmd_unsuspend - SNMP SETs to perform on unsuspend. See L</Formatting> + +=back + +=head1 Formatting + +The values for the snmp_cmd_* fields should be formatted as follows: + +<OID>|<Data Type>|<expr>[||<OID>|<Data Type>|<expr>[...]] + +=over 4 + +=item OID - SNMP object ID (ex. 1.3.6.1.4.1.1.20). If the OID string starts with a '.', then the Private Enterprise OID (1.3.6.1.4.1) is prepended. + +=item Data Type - SNMP data types understood by L<Net::SNMP>, as well as HEX_STRING for convenience. ex. INTEGER, OCTET_STRING, IPADDRESS, ... + +=item expr - Expression to be eval'd by freeside. By default, the expression is double quoted and eval'd with all FS::svc_broadband fields available as scalars (ex. $svcnum, $ip_addr, $speed_up). However, if the expression contains a non-escaped double quote, the expression is eval'd without being double quoted. In this case, the expression must be a block of valid perl code that returns the desired value. + +You must escape non-delimiter pipes ("|") with a backslash. + +=back + +=head1 Examples + +This is an example for exporting to a Trango Access5830 AP. Newlines inserted for clarity. + +=over 4 + +=item snmp_cmd_delete - + +1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50|| +1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1| + +=item snmp_cmd_insert - + +1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50|| +1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$radio_addr =~ /[0-9a-fA-F]{2}/g)|| +1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1| + +=item snmp_cmd_replace - + +1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50|| +1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1||1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50|| +1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$new_radio_addr =~ /[0-9a-fA-F]{2}/g)|| +1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1| + +=back + +=cut + + +use strict; +use vars qw(@ISA %info $me $DEBUG); +use Tie::IxHash; +use FS::Record qw(qsearch qsearchs); +use FS::part_export; +use FS::part_export::router; + +@ISA = qw(FS::part_export::router); + +tie my %options, 'Tie::IxHash', (); + +%info = ( + 'svc' => 'svc_broadband', + 'desc' => 'Sends SNMP SETs to an SNMP agent.', + 'options' => \%options, + 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::snmp for required virtual fields and usage information.', +); + +$me= '[' . __PACKAGE__ . ']'; +$DEBUG = 1; + + +sub _field_prefix { 'snmp'; } + +sub _req_router_fields { + map { + $_[0]->_field_prefix . '_' . $_ + } (qw(address comm version)); +} + +sub _get_cmd_sub { + + my ($self, $svc_broadband, $router) = (shift, shift, shift); + + return(ref($self) . '::snmp_cmd'); + +} + +sub _prepare_args { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old; + my $field_prefix = $self->_field_prefix; + + if ($action eq 'replace') { $old = shift; } + + my $raw_cmd = $router->getfield("${field_prefix}_cmd_${action}"); + unless ($raw_cmd) { + warn "[debug]$me router custom field '${field_prefix}_cmd_$action' " + . "is not defined." if $DEBUG; + return ''; + } + + my $args = [ + '-hostname' => $router->getfield($field_prefix.'_address'), + '-version' => $router->getfield($field_prefix.'_version'), + '-community' => $router->getfield($field_prefix.'_comm'), + ]; + + my @varbindlist = (); + + foreach my $snmp_cmd ($raw_cmd =~ m/(.*?[^\\])(?:\|\||$)/g) { + + warn "[debug]$me snmp_cmd is '$snmp_cmd'" if $DEBUG; + + my ($oid, $type, $expr) = $snmp_cmd =~ m/(.*?[^\\])(?:\||$)/g; + + if ($oid =~ /^([\d\.]+)$/) { + $oid = $1; + $oid = ($oid =~ /^\./) ? '1.3.6.1.4.1' . $oid : $oid; + } else { + return "Invalid SNMP OID '$oid'"; + } + + if ($type =~ /^([A-Z_\d]+)$/) { + $type = $1; + } else { + return "Invalid SNMP ASN.1 type '$type'"; + } + + if ($expr =~ /^(.*)$/) { + $expr = $1; + } else { + return "Invalid expression '$expr'"; + } + + { + no strict 'vars'; + no strict 'refs'; + + if ($action eq 'replace') { + ${"old_$_"} = $old->getfield($_) foreach $old->fields; + ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields; + $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr")); + } else { + ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields; + $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr")); + } + return $@ if $@; + } + + push @varbindlist, ($oid, $type, $expr); + + } + + push @$args, ('-varbindlist', @varbindlist); + + return('', $args); + +} + +sub snmp_cmd { + eval "use Net::SNMP;"; + die $@ if $@; + + my %args = (); + my @varbindlist = (); + while (scalar(@_)) { + my $key = shift; + if ($key eq '-varbindlist') { + push @varbindlist, @_; + last; + } else { + $args{$key} = shift; + } + } + + my $i = 0; + while ($i*3 < scalar(@varbindlist)) { + my $type_index = ($i*3)+1; + my $type_name = $varbindlist[$type_index]; + + # Implementing HEX_STRING outselves since Net::SNMP doesn't. Ewwww! + if ($type_name eq 'HEX_STRING') { + my $value_index = $type_index + 1; + $type_name = 'OCTET_STRING'; + $varbindlist[$value_index] = pack('H*', $varbindlist[$value_index]); + } + + my $type = eval "Net::SNMP::$type_name"; + if ($@ or not defined $type) { + warn $@ if $DEBUG; + die "snmp_cmd error: Unable to lookup type '$type_name'"; + } + + $varbindlist[$type_index] = $type; + } continue { + $i++; + } + + my ($snmp, $error) = Net::SNMP->session(%args); + die "snmp_cmd error: $error" unless($snmp); + + my $res = $snmp->set_request('-varbindlist' => \@varbindlist); + unless($res) { + $error = $snmp->error; + $snmp->close; + die "snmp_cmd error: " . $error; + } + + $snmp->close; + + return ''; + +} + + +=head1 BUGS + +Plenty, I'm sure. + +=cut + +1; diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm index 6d61e0e29..cbdaf7f52 100644 --- a/FS/FS/part_export/sqlmail.pm +++ b/FS/FS/part_export/sqlmail.pm @@ -25,9 +25,9 @@ tie my %options, 'Tie::IxHash', 'svc_acct_fields' => { label => 'svc_acct Export Fields', default => 'username _password domsvc svcnum' }, 'svc_forward_fields' => { label => 'svc_forward Export Fields', - default => 'domain svcnum catchall' }, - 'svc_domain_fields' => { label => 'svc_domain Export Fields', default => 'srcsvc dstsvc dst' }, + 'svc_domain_fields' => { label => 'svc_domain Export Fields', + default => 'domain svcnum catchall' }, 'resolve_dstsvc' => { label => q{Resolve svc_forward.dstsvc to an email address and store it in dst. (Doesn't require that you also export dstsvc.)}, type => 'checkbox' }, ; diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 10bccb034..139f4001f 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -2,7 +2,7 @@ package FS::part_export::sqlradius; use vars qw(@ISA $DEBUG %info %options $notes1 $notes2); use Tie::IxHash; -use FS::Record qw( dbh qsearch ); +use FS::Record qw( dbh qsearch qsearchs ); use FS::part_export; use FS::svc_acct; use FS::export_svc; @@ -31,6 +31,12 @@ tie %options, 'Tie::IxHash', type => 'checkbox', label => 'Show the Called-Station-ID on session reports', }, + 'overlimit_groups' => { label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit', } , + 'groups_susp_reason' => { label => + 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)', + type => 'textarea', + }, + ; $notes1 = <<'END'; @@ -75,6 +81,10 @@ END $notes2 ); +sub _groups_susp_reason_map { map { reverse( /^\s*(\S+)\s*(.*)$/ ) } + split( "\n", shift->option('groups_susp_reason')); +} + sub rebless { shift; } sub export_username { @@ -170,50 +180,99 @@ sub _export_replace { } } - # (sorta) false laziness with FS::svc_acct::replace - my @oldgroups = @{$old->usergroup}; #uuuh - my @newgroups = $new->radius_groups; - my @delgroups = (); - foreach my $oldgroup ( @oldgroups ) { - if ( grep { $oldgroup eq $_ } @newgroups ) { - @newgroups = grep { $oldgroup ne $_ } @newgroups; - next; - } - push @delgroups, $oldgroup; + my $error; + my (@oldgroups) = $old->radius_groups; + my (@newgroups) = $new->radius_groups; + $error = $self->sqlreplace_usergroups( $new->svcnum, + $self->export_username($new), + $jobnum ? $jobnum : '', + \@oldgroups, + \@newgroups, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - if ( @delgroups ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_delete', - $self->export_username($new), @delgroups ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; - } - if ( $jobnum ) { - my $error = $err_or_queue->depend_insert( $jobnum ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; +} + +sub _export_suspend { + my( $self, $svc_acct ) = (shift, shift); + + my $new = $svc_acct->clone_suspended; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert', + 'check', $self->export_username($new), $new->radius_check ); + unless ( ref($err_or_queue) ) { + $dbh->rollback if $oldAutoCommit; + return $err_or_queue; } - if ( @newgroups ) { - my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_insert', - $self->export_username($new), @newgroups ); - unless ( ref($err_or_queue) ) { - $dbh->rollback if $oldAutoCommit; - return $err_or_queue; - } - if ( $jobnum ) { - my $error = $err_or_queue->depend_insert( $jobnum ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } + my $error; + my (@newgroups) = $self->suspended_usergroups($svc_acct); + $error = + $self->sqlreplace_usergroups( $new->svcnum, + $self->export_username($new), + '', + $svc_acct->usergroup, + \@newgroups, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; +} + +sub _export_unsuspend { + my( $self, $svc_acct ) = (shift, shift); + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert', + 'check', $self->export_username($svc_acct), $svc_acct->radius_check ); + unless ( ref($err_or_queue) ) { + $dbh->rollback if $oldAutoCommit; + return $err_or_queue; } + my $error; + my (@oldgroups) = $self->suspended_usergroups($svc_acct); + $error = $self->sqlreplace_usergroups( $svc_acct->svcnum, + $self->export_username($svc_acct), + '', + \@oldgroups, + $svc_acct->usergroup, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -240,6 +299,39 @@ sub sqlradius_queue { ) or $queue; } +sub suspended_usergroups { + my ($self, $svc_acct) = (shift, shift); + + return () unless $svc_acct; + + #false laziness with FS::part_export::shellcommands + #subclass part_export? + + my $r = $svc_acct->cust_svc->cust_pkg->last_reason; + my %reasonmap = $self->_groups_susp_reason_map; + my $userspec = ''; + if ($r) { + $userspec = $reasonmap{$r->reasonnum} + if exists($reasonmap{$r->reasonnum}); + $userspec = $reasonmap{$r->reason} + if (!$userspec && exists($reasonmap{$r->reason})); + } + my $suspend_user; + if ($userspec =~ /^d+$/ ){ + $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } ); + }elsif ($userspec =~ /^\S+\@\S+$/){ + my ($username,$domain) = split(/\@/, $userspec); + for my $user (qsearch( 'svc_acct', { 'username' => $username } )){ + $suspend_user = $user if $userspec eq $user->email; + } + }elsif ($userspec){ + $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } ); + } + #esalf + return $suspend_user->radius_groups if $suspend_user; + (); +} + sub sqlradius_insert { #subroutine, not method my $dbh = sqlradius_connect(shift, shift, shift); my( $table, $username, %attributes ) = @_; @@ -350,6 +442,46 @@ sub sqlradius_connect { DBI->connect(@_) or die $DBI::errstr; } +sub sqlreplace_usergroups { + my ($self, $svcnum, $username, $jobnum, $old, $new) = @_; + + # (sorta) false laziness with FS::svc_acct::replace + my @oldgroups = @$old; + my @newgroups = @$new; + my @delgroups = (); + foreach my $oldgroup ( @oldgroups ) { + if ( grep { $oldgroup eq $_ } @newgroups ) { + @newgroups = grep { $oldgroup ne $_ } @newgroups; + next; + } + push @delgroups, $oldgroup; + } + + if ( @delgroups ) { + my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_delete', + $username, @delgroups ); + return $err_or_queue + unless ref($err_or_queue); + if ( $jobnum ) { + my $error = $err_or_queue->depend_insert( $jobnum ); + return $error if $error; + } + } + + if ( @newgroups ) { + my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_insert', + $username, @newgroups ); + return $err_or_queue + unless ref($err_or_queue); + if ( $jobnum ) { + my $error = $err_or_queue->depend_insert( $jobnum ); + return $error if $error; + } + } + ''; +} + + #-- =item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ] @@ -484,7 +616,8 @@ sub update_svc_acct { my $where = ''; my $sth = $dbh->prepare(" - SELECT RadAcctId, UserName, Realm, AcctSessionTime + SELECT RadAcctId, UserName, Realm, AcctSessionTime, + AcctInputOctets, AcctOutputOctets FROM radacct WHERE FreesideStatus IS NULL AND AcctStopTime != 0 @@ -492,7 +625,8 @@ sub update_svc_acct { $sth->execute() or die $sth->errstr; while ( my $row = $sth->fetchrow_arrayref ) { - my($RadAcctId, $UserName, $Realm, $AcctSessionTime) = @$row; + my($RadAcctId, $UserName, $Realm, $AcctSessionTime, + $AcctInputOctets, $AcctOutputOctets) = @$row; warn "processing record: ". "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s" if $DEBUG; @@ -502,7 +636,6 @@ sub update_svc_acct { if ( ref($self) =~ /withdomain/ ) { #well... $extra_sql = " AND '$Realm' = ( SELECT domain FROM svc_domain WHERE svc_domain.svcnum = svc_acct.domsvc ) "; - my $svc_domain = qsearch } my @svc_acct = @@ -523,18 +656,16 @@ sub update_svc_acct { } elsif ( scalar(@svc_acct) > 1 ) { warn "WARNING: multiple svc_acct records found $errinfo - skipping\n"; } else { - my $svc_acct = $svc_acct[0]; - warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG; - if ( $svc_acct->seconds !~ /^$/ ) { - warn " svc_acct.seconds found (". $svc_acct->seconds. - ") - decrementing\n" - if $DEBUG; - my $error = $svc_acct->decrement_seconds($AcctSessionTime); - die $error if $error; - $status = 'done'; - } else { - warn " no existing seconds value for svc_acct - skiping\n" if $DEBUG; - } + warn "found svc_acct ". $svc_acct[0]->svcnum. " $errinfo\n" if $DEBUG; + _try_decrement($svc_acct[0], 'seconds', $AcctSessionTime) + and $status='done'; + _try_decrement($svc_acct[0], 'upbytes', $AcctInputOctets) + and $status='done'; + _try_decrement($svc_acct[0], 'downbytes', $AcctOutputOctets) + and $status='done'; + _try_decrement($svc_acct[0], 'totalbytes', $AcctInputOctets + + $AcctOutputOctets) + and $status='done'; } warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG; @@ -548,5 +679,21 @@ sub update_svc_acct { } +sub _try_decrement { + my ($svc_acct, $column, $amount) = @_; + if ( $svc_acct->$column !~ /^$/ ) { + warn " svc_acct.$column found (". $svc_acct->$column. + ") - decrementing\n" + if $DEBUG; + my $method = 'decrement_' . $column; + my $error = $svc_acct->$method($amount); + die $error if $error; + return 'done'; + } else { + warn " no existing $column value for svc_acct - skipping\n" if $DEBUG; + } + return ''; +} + 1; diff --git a/FS/FS/part_export/trango.pm b/FS/FS/part_export/trango.pm new file mode 100644 index 000000000..e7f1126dd --- /dev/null +++ b/FS/FS/part_export/trango.pm @@ -0,0 +1,434 @@ +package FS::part_export::trango; + +=head1 FS::part_export::trango + +This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly. + +=head1 Required custom fields + +=over 4 + +=item trango_address - IP address (or hostname) of the Trango AP. + +=item trango_comm - R/W SNMP community of the Trango AP. + +=item trango_ap_type - Trango AP Model. Currently 'access5830' is the only supported option. + +=back + +=head1 Optional custom fields + +=over 4 + +=item trango_baseid - Base ID of the Trango AP. See L</"Generating SU IDs">. + +=item trango_apid - AP ID of the Trango AP. See L</"Generating SU IDs">. + +=back + +=head1 Generating SU IDs + +This export will/must generate a unique SU ID for each service exported to a Trango AP. It can be done such that SU IDs are globally unique, unique per Base ID, or unique per Base ID/AP ID pair. This is accomplished by setting neither trango_baseid and trango_apid, only trango_baseid, or both trango_baseid and trango_apid, respectively. An SU ID will be generated if the FS::svc_broadband virtual field specified by suid_field export option is unset, otherwise the existing value will be used. + +=head1 Device Support + +This export has been tested with the Trango Access5830 AP. + + +=cut + + +use strict; +use vars qw(@ISA %info $me $DEBUG $trango_mib $counter_dir); + +use FS::UID qw(dbh datasrc); +use FS::Record qw(qsearch qsearchs); +use FS::part_export::snmp; + +use Tie::IxHash; +use File::CounterFile; +use Data::Dumper qw(Dumper); + +@ISA = qw(FS::part_export::snmp); + +tie my %options, 'Tie::IxHash', ( + 'suid_field' => { + 'label' => 'Trango SU ID field', + 'default' => 'trango_suid', + 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU ID.', + }, + 'mac_field' => { + 'label' => 'Trango MAC address field', + 'default' => '', + 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU\'s MAC address.', + }, +); + +%info = ( + 'svc' => 'svc_broadband', + 'desc' => 'Sends SNMP SETs to a Trango AP.', + 'options' => \%options, + 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.', +); + +$me= '[' . __PACKAGE__ . ']'; +$DEBUG = 1; + +$trango_mib = { + 'access5830' => { + 'snmpversion' => 'snmpv1', + 'varbinds' => { + 'insert' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'delete' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'replace' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'suspend' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'unsuspend' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + }, + }, +}; + + +sub _field_prefix { 'trango'; } + +sub _req_router_fields { + map { + $_[0]->_field_prefix . '_' . $_ + } (qw(address comm ap_type suid_field)); +} + +sub _get_cmd_sub { + + return('FS::part_export::snmp::snmp_cmd'); + +} + +sub _prepare_args { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + my $field_prefix = $self->_field_prefix; + my $error; + + my $ap_type = $router->getfield($field_prefix . '_ap_type'); + + unless (exists $trango_mib->{$ap_type}) { + return "Unsupported Trango AP type '$ap_type'"; + } + + $error = $self->_check_suid( + $action, $router, $svc_broadband, ($old) ? $old : () + ); + return $error if $error; + + $error = $self->_check_mac( + $action, $router, $svc_broadband, ($old) ? $old : () + ); + return $error if $error; + + my $ap_mib = $trango_mib->{$ap_type}; + + my $args = [ + '-hostname' => $router->getfield($field_prefix.'_address'), + '-version' => $ap_mib->{'snmpversion'}, + '-community' => $router->getfield($field_prefix.'_comm'), + ]; + + my @varbindlist = (); + + foreach my $oid (@{$ap_mib->{'varbinds'}->{$action}}) { + warn "[debug]$me Processing OID '" . $oid->{'oid'} . "'" if $DEBUG; + my $value; + if (ref($oid->{'value'}) eq 'CODE') { + eval { + $value = &{$oid->{'value'}}( + $self, $action, $router, $svc_broadband, + (($old) ? $old : ()), + ); + }; + return "While processing OID '" . $oid->{'oid'} . "':" . $@ + if $@; + } else { + $value = $oid->{'value'}; + } + + warn "[debug]$me Value for OID '" . $oid->{'oid'} . "': " if $DEBUG; + + if (defined $value) { # Skip OIDs with undefined values. + push @varbindlist, ($oid->{'oid'}, $oid->{'type'}, $value); + } + } + + + push @$args, ('-varbindlist', @varbindlist); + + return('', $args); + +} + +sub _check_suid { + + my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift); + my $old = shift if $action eq 'replace'; + my $error; + + my $suid_field = $self->option('suid_field'); + unless (grep {$_ eq $suid_field} $svc_broadband->fields) { + return "Missing Trango SU ID field. " + . "See the trango export options for more info."; + } + + my $suid = $svc_broadband->getfield($suid_field); + if ($action eq 'replace') { + my $old_suid = $old->getfield($suid_field); + + if ($old_suid ne '' and $old_suid ne $suid) { + return 'Cannot change Trango SU ID'; + } + } + + if (not $suid =~ /^\d+$/ and $action ne 'delete') { + my $new_suid = eval { $self->_get_next_suid($router); }; + return "Error while getting next Trango SU ID: $@" if ($@); + + warn "[debug]$me Got new SU ID: $new_suid" if $DEBUG; + $svc_broadband->set($suid_field, $new_suid); + + #FIXME: Probably a bad hack. + # We need to update the SU ID field in the database. + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::svc_Common::noexport_hack = 1; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $svcnum = $svc_broadband->svcnum; + + my $old_svc = qsearchs('svc_broadband', { svcnum => $svcnum }); + unless ($old_svc) { + return "Unable to retrieve svc_broadband with svcnum '$svcnum"; + } + + my $svcpart = $svc_broadband->svcpart + ? $svc_broadband->svcpart + : $svc_broadband->cust_svc->svcpart; + + my $new_svc = new FS::svc_broadband { + $old_svc->hash, + $suid_field => $new_suid, + svcpart => $svcpart, + }; + + $error = $new_svc->check; + if ($error) { + $dbh->rollback if $oldAutoCommit; + return "Error while updating the Trango SU ID: $error" if $error; + } + + warn "[debug]$me Updating svc_broadband with SU ID '$new_suid'...\n" . + &Dumper($new_svc) if $DEBUG; + + $error = eval { $new_svc->replace($old_svc); }; + + if ($@ or $error) { + $error ||= $@; + $dbh->rollback if $oldAutoCommit; + return "Error while updating the Trango SU ID: $error" if $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + } + + return ''; + +} + +sub _check_mac { + + my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift); + my $old = shift if $action eq 'replace'; + + my $mac_field = $self->option('mac_field'); + unless (grep {$_ eq $mac_field} $svc_broadband->fields) { + return "Missing Trango MAC address field. " + . "See the trango export options for more info."; + } + + my $mac_addr = $svc_broadband->getfield($mac_field); + unless (length(join('', $mac_addr =~ /[0-9a-fA-F]/g)) == 12) { + return "Invalid Trango MAC address: $mac_addr"; + } + + return(''); + +} + +sub _get_next_suid { + + my ($self, $router) = (shift, shift); + + my $counter_dir = '/usr/local/etc/freeside/export.'. datasrc . '/trango'; + my $baseid = $router->getfield('trango_baseid'); + my $apid = $router->getfield('trango_apid'); + + my $counter_file_suffix = ''; + if ($baseid ne '') { + $counter_file_suffix .= "_B$baseid"; + if ($apid ne '') { + $counter_file_suffix .= "_A$apid"; + } + } + + my $counter_file = $counter_dir . '/SUID' . $counter_file_suffix; + + warn "[debug]$me Using SUID counter file '$counter_file'"; + + my $suid = eval { + mkdir $counter_dir, 0700 unless -d $counter_dir; + + my $cf = new File::CounterFile($counter_file, 0); + $cf->inc; + }; + + die "Error generating next Trango SU ID: $@" if (not $suid or $@); + + return($suid); + +} + + + +# Trango-specific subroutines for generating varbind values. +# +# All subs should die on error, and return undef to decline. OIDs that +# decline will not be added to varbinds. + +sub _trango_access5830_sudbDeleteOrAddId { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + + my $suid = $svc_broadband->getfield($self->option('suid_field')); + + # Sanity check. + unless ($suid =~ /^\d+$/) { + if ($action eq 'delete') { + # Silently ignore. If we don't have a valid SU ID now, we probably + # never did. + return undef; + } else { + die "Invalid Trango SU ID '$suid'"; + } + } + + return ($suid); + +} + +sub _trango_access5830_sudbAddMac { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + + my $mac_addr = $svc_broadband->getfield($self->option('mac_field')); + $mac_addr = join('', $mac_addr =~ /[0-9a-fA-F]/g); + + # Sanity check. + die "Invalid Trango MAC address '$mac_addr'" unless (length($mac_addr)==12); + + return($mac_addr); + +} + + +=head1 BUGS + +Plenty, I'm sure. + +=cut + + +1; diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm index 0fc8266ea..4cda65755 100644 --- a/FS/FS/part_export/vpopmail.pm +++ b/FS/FS/part_export/vpopmail.pm @@ -87,7 +87,7 @@ sub _export_delete { sub vpopmail_queue { my( $self, $svcnum, $method ) = (shift, shift, shift); - my $exportdir = "/usr/local/etc/freeside/export." . datasrc; + my $exportdir = "%%%FREESIDE_EXPORT%%%/export." . datasrc; mkdir $exportdir, 0700 or die $! unless -d $exportdir; $exportdir .= "/vpopmail"; mkdir $exportdir, 0700 or die $! unless -d $exportdir; diff --git a/FS/FS/part_export/www_plesk.pm b/FS/FS/part_export/www_plesk.pm new file mode 100644 index 000000000..82d555761 --- /dev/null +++ b/FS/FS/part_export/www_plesk.pm @@ -0,0 +1,138 @@ +package FS::part_export::www_plesk; + +use vars qw(@ISA %info); +use Tie::IxHash; +use FS::part_export; + +@ISA = qw(FS::part_export); + +tie my %options, 'Tie::IxHash', + 'URL' => { label=>'URL' }, + 'login' => { label=>'Login' }, + 'password' => { label=>'Password' }, + 'template' => { label=>'Domain Template' }, + 'web' => { label=>'Host Website', + type=>'checkbox' }, + 'debug' => { label=>'Enable debugging', + type=>'checkbox' }, +; + +%info = ( + 'svc' => 'svc_www', + 'desc' => 'Real-time export to Plesk managed hosting service', + 'options'=> \%options, + 'notes' => <<'END' +Real-time export to +<a href="http://www.swsoft.com/">Plesk</a> managed server. +Requires installation of +<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a> +from CPAN. +END +); + +sub rebless { shift; } + +# experiment: want the status of these right away (don't want account to +# create or whatever and then get error in the queue from dup username or +# something), so no queueing + +sub _export_insert { + my( $self, $www ) = ( shift, shift ); + + eval "use Net::Plesk;"; + return $@ if $@; + + my $plesk = new Net::Plesk ( + 'POST' => $self->option('URL'), + ':HTTP_AUTH_LOGIN' => $self->option('login'), + ':HTTP_AUTH_PASSWD' => $self->option('password'), + ); + + my $gcresp = $plesk->client_get( $www->svc_acct->username ); + return $gcresp->errortext + unless $gcresp->is_success; + + unless ($gcresp->id) { + my $cust_main = $www->cust_svc->cust_pkg->cust_main; + $gcresp = $plesk->client_add( $cust_main->name, + $www->svc_acct->username, + $www->svc_acct->_password, + $cust_main->daytime, + $cust_main->fax, + $cust_main->invoicing_list->[0], + $cust_main->address1 . $cust_main->address2, + $cust_main->city, + $cust_main->state, + $cust_main->zip, + $cust_main->country, + ); + return $gcresp->errortext + unless $gcresp->is_success; + } + + $plesk->client_ippool_add_ip ( $gcresp->id, + $www->domain_record->recdata, + ); + + if ($self->option('web')) { + $self->_plesk_command( 'domain_add', + $www->domain_record->svc_domain->domain, + $gcresp->id, + $www->domain_record->recdata, + $self->option('template')?$self->option('template'):'', + $www->svc_acct->username, + $www->svc_acct->_password, + ); + }else{ + $self->_plesk_command( 'domain_add', + $www->domain_record->svc_domain->domain, + $gcresp->id, + $www->domain_record->recdata, + $self->option('template')?$self->option('template'):'', + ); + } +} + +sub _plesk_command { + my( $self, $method, @args ) = @_; + + eval "use Net::Plesk;"; + return $@ if $@; + + local($Net::Plesk::DEBUG) = 1 + if $self->option('debug'); + + my $plesk = new Net::Plesk ( + 'POST' => $self->option('URL'), + ':HTTP_AUTH_LOGIN' => $self->option('login'), + ':HTTP_AUTH_PASSWD' => $self->option('password'), + ); + + my $response = $plesk->$method(@args); + return $response->errortext unless $response->is_success; + ''; + +} + +sub _export_replace { + my( $self, $new, $old ) = (shift, shift, shift); + + return "can't change domain with Plesk" + if $old->domain_record->svc_domain->domain ne + $new->domain_record->svc_domain->domain; + + return "can't change client with Plesk" + if $old->svc_acct->username ne + $new->svc_acct->username; + + return ''; + +} + +sub _export_delete { + my( $self, $www ) = ( shift, shift ); + $self->_plesk_command( 'domain_del', $www->domain_record->svc_domain->domain); +} + +1; + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 73f3bae04..e4c13aade 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -1,7 +1,7 @@ package FS::part_pkg; use strict; -use vars qw( @ISA %freq %plans $DEBUG ); +use vars qw( @ISA %plans $DEBUG ); use Carp qw(carp cluck confess); use Tie::IxHash; use FS::Conf; @@ -12,8 +12,9 @@ use FS::cust_pkg; use FS::agent_type; use FS::type_pkgs; use FS::part_pkg_option; +use FS::pkg_class; -@ISA = qw( FS::Record ); # FS::option_Common ); # this can use option_Common +@ISA = qw( FS::m2m_Common FS::Record ); # FS::option_Common ); # this can use option_Common # when all the plandata bs is # gone @@ -58,6 +59,8 @@ inherits from FS::Record. The following fields are currently supported: =item comment - Text name of this package definition (non-customer-viewable) +=item classnum - Optional package class (see L<FS::pkg_class>) + =item promo_code - Promotional code =item setup - Setup fee expression (deprecated) @@ -78,6 +81,10 @@ inherits from FS::Record. The following fields are currently supported: =item disabled - Disabled flag, empty or `Y' +=item pay_weight - Weight (relative to credit_weight and other package definitions) that controls payment application to specific line items. + +=item credit_weight - Weight (relative to other package definitions) that controls credit application to specific line items. + =back =head1 METHODS @@ -304,6 +311,12 @@ FS::pkg_svc record will be updated. sub replace { my( $new, $old ) = ( shift, shift ); my %options = @_; + + # We absolutely have to have an old vs. new record to make this work. + if (!defined($old)) { + $old = qsearchs( 'part_pkg', { 'pkgpart' => $new->pkgpart } ); + } + warn "FS::part_pkg::replace called on $new to replace $old ". "with options %options" if $DEBUG; @@ -434,10 +447,19 @@ sub check { || $self->ut_enum('recurtax', [ '', 'Y' ] ) || $self->ut_textn('taxclass') || $self->ut_enum('disabled', [ '', 'Y' ] ) + || $self->ut_floatn('pay_weight') + || $self->ut_floatn('credit_weight') || $self->SUPER::check ; return $error if $error; + if ( $self->classnum !~ /^$/ ) { + my $error = $self->ut_foreign_key('classnum', 'pkg_class', 'classnum'); + return $error if $error; + } else { + $self->classnum(''); + } + return 'Unknown plan '. $self->plan unless exists($plans{$self->plan}); @@ -448,6 +470,37 @@ sub check { ''; } +=item pkg_class + +Returns the package class, as an FS::pkg_class object, or the empty string +if there is no package class. + +=cut + +sub pkg_class { + my $self = shift; + if ( $self->classnum ) { + qsearchs('pkg_class', { 'classnum' => $self->classnum } ); + } else { + return ''; + } +} + +=item classname + +Returns the package class name, or the empty string if there is no package +class. + +=cut + +sub classname { + my $self = shift; + my $pkg_class = $self->pkg_class; + $pkg_class + ? $pkg_class->classname + : ''; +} + =item pkg_svc Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package @@ -530,6 +583,34 @@ sub is_free { } } + +sub freqs_href { + #method, class method or sub? #my $self = shift; + + tie my %freq, 'Tie::IxHash', + '0' => '(no recurring fee)', + '1h' => 'hourly', + '1d' => 'daily', + '2d' => 'every two days', + '1w' => 'weekly', + '2w' => 'biweekly (every 2 weeks)', + '1' => 'monthly', + '45d' => 'every 45 days', + '2' => 'bimonthly (every 2 months)', + '3' => 'quarterly (every 3 months)', + '6' => 'semiannually (every 6 months)', + '12' => 'annually', + '24' => 'biannually (every 2 years)', + '36' => 'triannually (every 3 years)', + '48' => '(every 4 years)', + '60' => '(every 5 years)', + '120' => '(every 10 years)', + ; + + \%freq; + +} + =item freq_pretty Returns an english representation of the I<freq> field, such as "monthly", @@ -537,29 +618,15 @@ Returns an english representation of the I<freq> field, such as "monthly", =cut -tie %freq, 'Tie::IxHash', - '0' => '(no recurring fee)', - '1h' => 'hourly', - '1d' => 'daily', - '1w' => 'weekly', - '2w' => 'biweekly (every 2 weeks)', - '1' => 'monthly', - '2' => 'bimonthly (every 2 months)', - '3' => 'quarterly (every 3 months)', - '6' => 'semiannually (every 6 months)', - '12' => 'annually', - '24' => 'biannually (every 2 years)', - '36' => 'triannually (every 3 years)', - '48' => '(every 4 years)', - '60' => '(every 5 years)', - '120' => '(every 10 years)', -; - sub freq_pretty { my $self = shift; my $freq = $self->freq; - if ( exists($freq{$freq}) ) { - $freq{$freq}; + + #my $freqs_href = $self->freqs_href; + my $freqs_href = freqs_href(); + + if ( exists($freqs_href->{$freq}) ) { + $freqs_href->{$freq}; } else { my $interval = 'month'; if ( $freq =~ /^(\d+)([hdw])$/ ) { @@ -634,7 +701,8 @@ sub option { my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } split("\n", $self->get('plandata') ); return $plandata{$opt} if exists $plandata{$opt}; - cluck "Package definition option $opt not found in options or plandata!\n" + cluck "WARNING: (pkgpart ". $self->pkgpart. ") Package def option $opt ". + "not found in options or plandata!\n" unless $ornull; ''; } @@ -752,15 +820,16 @@ sub plan_info { =head1 NEW PLAN CLASSES -A module should be added in FS/FS/part_pkg/ (an example may be found in -eg/plan_template.pm) +A module should be added in FS/FS/part_pkg/ Eventually, an example may be +found in eg/plan_template.pm. Until then, it is suggested that you use the +other modules in FS/FS/part_pkg/ as a guide. =head1 BUGS The delete method is unimplemented. setup and recur semantics are not yet defined (and are implemented in -FS::cust_bill. hmm.). +FS::cust_bill. hmm.). now they're deprecated and need to go. plandata should go diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 59b625746..94b7d9947 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -20,21 +20,61 @@ use FS::part_pkg; ' of service at cancellation', 'type' => 'checkbox', }, + 'externalid' => { 'name' => 'Optional External ID', + 'default' => '', + }, + 'seconds' => { 'name' => 'Time limit for this package', + 'default' => '', + }, + 'upbytes' => { 'name' => 'Upload limit for this package', + 'default' => '', + }, + 'downbytes' => { 'name' => 'Download limit for this package', + 'default' => '', + }, + 'totalbytes' => { 'name' => 'Transfer limit for this package', + 'default' => '', + }, + 'recharge_amount' => { 'name' => 'Cost of recharge for this package', + 'default' => '', + }, + 'recharge_seconds' => { 'name' => 'Recharge time for this package', + 'default' => '', + }, + 'recharge_upbytes' => { 'name' => 'Recharge upload for this package', + 'default' => '', + }, + 'recharge_downbytes' => { 'name' => 'Recharge download for this package', + 'default' => '', + }, + 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', + 'default' => '', + }, }, - 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit' ], - #'setup' => 'what.setup_fee.value', - #'recur' => 'what.recur_fee.value', + 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', + 'seconds', 'upbytes', 'downbytes', 'totalbytes', + 'recharge_amount', 'recharge_seconds', 'recharge_upbytes', + 'recharge_downbytes', 'recharge_totalbytes', + 'externalid' ], 'weight' => 10, ); sub calc_setup { - my($self, $cust_pkg ) = @_; + my($self, $cust_pkg, $sdate, $details ) = @_; + + my $i = 0; + my $count = $self->option( 'additional_count', 'quiet' ) || 0; + while ($i < $count) { + push @$details, $self->option( 'additional_info' . $i++ ); + } + $self->option('setup_fee'); } sub calc_recur { - my $self = shift; - $self->base_recur(@_); + my($self, $cust_pkg) = @_; + $self->reset_usage($cust_pkg); + $self->base_recur($cust_pkg); } sub base_recur { @@ -77,4 +117,12 @@ sub is_prepaid { 0; #no, we're postpaid } +sub reset_usage { + my($self, $cust_pkg) = @_; + my %values = map { $_, $self->option($_) } + grep { $self->option($_, 'hush') } + qw(seconds upbytes downbytes totalbytes); + $cust_pkg->set_usage(\%values); +} + 1; diff --git a/FS/FS/part_pkg/flat_comission.pm b/FS/FS/part_pkg/flat_comission.pm index 442415e08..bc02f9658 100644 --- a/FS/FS/part_pkg/flat_comission.pm +++ b/FS/FS/part_pkg/flat_comission.pm @@ -41,8 +41,14 @@ sub calc_recur { $cust_pkg->cust_main->referral_cust_pkg( $self->option('comission_depth') ) ); - my $error = $cust_pkg->cust_main->credit( $amount*$num_active, "commission" ); - die $error if $error; + my $commission = sprintf('%.2f', $amount*$num_active); + + if ( $commission > 0 ) { + + my $error = $cust_pkg->cust_main->credit( $commission, "commission" ); + die $error if $error; + + } $self->option('recur_fee'); } diff --git a/FS/FS/part_pkg/flat_delayed.pm b/FS/FS/part_pkg/flat_delayed.pm index ec11699d9..caade409e 100644 --- a/FS/FS/part_pkg/flat_delayed.pm +++ b/FS/FS/part_pkg/flat_delayed.pm @@ -20,12 +20,19 @@ use FS::part_pkg::flat; 'recur_fee' => { 'name' => 'Recurring fee for this package', 'default' => 0, }, + 'recur_notify' => { 'name' => 'Number of days before recurring billing'. + 'commences to notify customer. (0 means '. + 'no warning)', + 'default' => 0, + }, 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. ' of service at cancellation', 'type' => 'checkbox', }, }, - 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'unused_credit' ], + 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify', + 'unused_credit' + ], #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value', #'recur' => 'what.recur_fee.value', 'weight' => 50, diff --git a/FS/FS/part_pkg/flat_introrate.pm b/FS/FS/part_pkg/flat_introrate.pm new file mode 100644 index 000000000..c92ba978a --- /dev/null +++ b/FS/FS/part_pkg/flat_introrate.pm @@ -0,0 +1,67 @@ +package FS::part_pkg::flat_introrate; + +use strict; +use vars qw(@ISA %info $DEBUG $DEBUG_PRE); +#use FS::Record qw(qsearch qsearchs); +use FS::part_pkg::flat; + +use Date::Manip qw(DateCalc UnixDate ParseDate); + +@ISA = qw(FS::part_pkg::flat); +$DEBUG = 0; +$DEBUG_PRE = '[' . __PACKAGE__ . ']: '; + +%info = ( + 'name' => 'Introductory price for X months, then flat rate,'. + 'relative to setup date (anniversary billing)', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'intro_fee' => { 'name' => 'Introductory recurring free for this package', + 'default' => 0, + }, + 'intro_duration' => { 'name' => 'Duration of the introductory period, ' . + 'in number of months', + 'default' => 0, + }, + 'recur_fee' => { 'name' => 'Recurring fee for this package', + 'default' => 0, + }, + 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. + ' of service at cancellation', + 'type' => 'checkbox', + }, + }, + 'fieldorder' => [ 'setup_fee', 'intro_duration', 'intro_fee', 'recur_fee', 'unused_credit' ], + 'weight' => 150, +); + +sub calc_recur { + my($self, $cust_pkg, $time ) = @_; + + my ($duration) = ($self->option('intro_duration') =~ /^(\d+)$/); + unless ($duration) { + die "Invalid intro_duration: " . $self->option('intro_duration'); + } + + my $setup = &ParseDate('epoch ' . $cust_pkg->getfield('setup')); + my $intro_end = &DateCalc($setup, "+${duration} month"); + my $recur; + + warn $DEBUG_PRE . "\$duration = ${duration}" if $DEBUG; + warn $DEBUG_PRE . "\$intro_end = ${intro_end}" if $DEBUG; + warn $DEBUG_PRE . "$$time < " . &UnixDate($intro_end, '%s') if $DEBUG; + + if ($$time < &UnixDate($intro_end, '%s')) { + $recur = $self->option('intro_fee'); + } else { + $recur = $self->option('recur_fee'); + } + + $recur; + +} + + +1; diff --git a/FS/FS/part_pkg/incomplete/billoneday.pm b/FS/FS/part_pkg/incomplete/billoneday.pm new file mode 100644 index 000000000..8740547a3 --- /dev/null +++ b/FS/FS/part_pkg/incomplete/billoneday.pm @@ -0,0 +1,48 @@ +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/part_pkg/prepaid.pm b/FS/FS/part_pkg/prepaid.pm index 5e7d2baae..d309d453f 100644 --- a/FS/FS/part_pkg/prepaid.pm +++ b/FS/FS/part_pkg/prepaid.pm @@ -1,22 +1,32 @@ package FS::part_pkg::prepaid; use strict; -use vars qw(@ISA %info); +use vars qw(@ISA %info %recur_action); +use Tie::IxHash; use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); +tie %recur_action, 'Tie::IxHash', + 'suspend' => 'suspend', + 'cancel' => 'cancel', +; + %info = ( 'name' => 'Prepaid, flat rate', 'fields' => { - 'setup_fee' => { 'name' => 'One-time setup fee for this package', - 'default' => 0, - }, - 'recur_fee' => { 'name' => 'Initial and recharge fee for this package', - 'default' => 0, - } + 'setup_fee' => { 'name' => 'One-time setup fee for this package', + 'default' => 0, + }, + 'recur_fee' => { 'name' => 'Initial and recharge fee for this package', + 'default' => 0, + }, + 'recur_action' => { 'name' => 'Action to take upon reaching end of prepaid preiod', + 'type' => 'select', + 'select_options' => \%recur_action, + }, }, - 'fieldorder' => [ 'setup_fee', 'recur_fee', ], + 'fieldorder' => [ 'setup_fee', 'recur_fee', 'recur_action', ], 'weight' => 25, ); diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index 86c64d53a..7ce73647b 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -9,7 +9,7 @@ use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); %info = ( - 'name' => 'First partial month pro-rated, then flat-rate (1st of month billing)', + 'name' => 'First partial month pro-rated, then flat-rate (selectable billing day)', 'fields' => { 'setup_fee' => { 'name' => 'Setup fee for this package', 'default' => 0, @@ -21,22 +21,71 @@ use FS::part_pkg::flat; ' of service at cancellation', 'type' => 'checkbox', }, - }, - 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit' ], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; my $mstart = timelocal(0,0,0,1,$mon,$year); my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $sdate = $mstart; ( $part_pkg->freq - 1 ) * \' + what.recur_fee.value + \' / $part_pkg->freq + \' + what.recur_fee.value + \' / $part_pkg->freq * ($mend-$mnow) / ($mend-$mstart) ; \'', + 'cutoff_day' => { 'name' => 'billing day', + 'default' => 1, + }, + 'seconds' => { 'name' => 'Time limit for this package', + 'default' => '', + }, + 'upbytes' => { 'name' => 'Upload limit for this package', + 'default' => '', + }, + 'downbytes' => { 'name' => 'Download limit for this package', + 'default' => '', + }, + 'totalbytes' => { 'name' => 'Transfer limit for this package', + 'default' => '', + }, + 'recharge_amount' => { 'name' => 'Cost of recharge for this package', + 'default' => '', + }, + 'recharge_seconds' => { 'name' => 'Recharge time for this package', + 'default' => '', + }, + 'recharge_upbytes' => { 'name' => 'Recharge upload for this package', + 'default' => '', + }, + 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '', + }, + 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '', + }, + #it would be better if this had to be turned on, its confusing + 'externalid' => { 'name' => 'Optional External ID', + 'default' => '', + }, + }, + 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'cutoff_day', + 'seconds', 'upbyte', 'downbytes', 'totalbytes', + 'recharge_amount', 'recharge_seconds', 'recharge_upbytes', + 'recharge_downbytes', 'recharge_totalbytes', + 'externalid', ], 'freq' => 'm', 'weight' => 20, ); sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; + my $cutoff_day = $self->option('cutoff_day', 1) || 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,1,$mon,$year); - my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - $$sdate = $mstart; + my $mend; + my $mstart; + + $self->reset_usage($cust_pkg); + if ( $mday >= $cutoff_day ) { + $mend = + timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); + $mstart = + timelocal(0,0,0,$cutoff_day,$mon,$year); + + } else { + $mend = timelocal(0,0,0,$cutoff_day, $mon, $year); + if ($mon==0) {$mon=11;$year--;} else {$mon--;} + $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year); + } + + $$sdate = $mstart; my $permonth = $self->option('recur_fee') / $self->freq; $permonth * ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) ); diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm index 36b5a96fb..db04842f5 100644 --- a/FS/FS/part_pkg/subscription.pm +++ b/FS/FS/part_pkg/subscription.pm @@ -9,28 +9,75 @@ use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); %info = ( - 'name' => 'First partial month full charge, then flat-rate (1st of month billing)', + 'name' => 'First partial month full charge, then flat-rate (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, + }, + 'seconds' => { 'name' => 'Time limit for this package', + 'default' => '', + }, + 'upbytes' => { 'name' => 'Upload limit for this package', + 'default' => '', + }, + 'downbytes' => { 'name' => 'Download limit for this package', + 'default' => '', + }, + 'totalbytes' => { 'name' => 'Transfer limit for this package', + 'default' => '', + }, + 'recharge_amount' => { 'name' => 'Cost of recharge for this package', + 'default' => '', + }, + 'recharge_seconds' => { 'name' => 'Recharge time for this package', + 'default' => '', + }, + 'recharge_upbytes' => { 'name' => 'Recharge upload for this package', + 'default' => '', + }, + 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '', + }, + 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '', + }, + #it would be better if this had to be turned on, its confusing + 'externalid' => { 'name' => 'Optional External ID', + 'default' => '', + }, }, - 'fieldorder' => [ 'setup_fee', 'recur_fee' ], - #'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,1,$mon,$year); \' + what.recur_fee.value', + 'fieldorder' => [ 'setup_fee', 'recur_fee', 'cutoff_day', 'seconds', + 'upbytes', 'downbytes', 'totalbytes', + 'recharge_amount', 'recharge_seconds', 'recharge_upbytes', + 'recharge_downbytes', 'recharge_totalbytes', + 'externalid' ], + 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day', 'seconds', + 'upbytes', 'downbytes', 'totalbytes', + 'recharge_amount', 'recharge_seconds', 'recharge_upbytes', + 'recharge_downbytes', 'recharge_totalbytes', + ], 'freq' => 'm', 'weight' => 30, ); sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; - + my $cutoff_day = $self->option('cutoff_day', 1) || 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - $$sdate = timelocal(0,0,0,1,$mon,$year); + + if ( $mday < $cutoff_day ) { + if ($mon==0) {$mon=11;$year--;} + else {$mon--;} + } + + $$sdate = timelocal(0,0,0,$cutoff_day,$mon,$year); + + $self->reset_usage($cust_pkg); $self->option('recur_fee'); } diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm new file mode 100644 index 000000000..500a1b0a4 --- /dev/null +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -0,0 +1,353 @@ +package FS::part_pkg::voip_cdr; + +use strict; +use vars qw(@ISA $DEBUG %info); +use Date::Format; +use Tie::IxHash; +use FS::Conf; +use FS::Record qw(qsearchs qsearch); +use FS::part_pkg::flat; +#use FS::rate; +#use FS::rate_prefix; + +@ISA = qw(FS::part_pkg::flat); + +$DEBUG = 1; + +tie my %rating_method, 'Tie::IxHash', + 'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables', + 'upstream' => 'Rate calls based on upstream data: If the call type is "1", map the upstream rate ID directly to an internal rate (rate_detail), otherwise, pass the upstream price through directly.', +; + +#tie my %cdr_location, 'Tie::IxHash', +# 'internal' => 'Internal: CDR records imported into the internal CDR table', +# 'external' => 'External: CDR records queried directly from an external '. +# 'Asterisk (or other?) CDR table', +#; + +%info = ( + 'name' => 'VoIP rating by plan of CDR records in an internal (or external?) SQL table', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_flat' => { 'name' => 'Base recurring fee for this package', + 'default' => 0, + }, + 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. + ' of service at cancellation', + 'type' => 'checkbox', + }, + 'ratenum' => { 'name' => 'Rate plan', + 'type' => 'select', + 'select_table' => 'rate', + 'select_key' => 'ratenum', + 'select_label' => 'ratename', + }, + 'rating_method' => { 'name' => 'Region rating method', + 'type' => 'select', + 'select_options' => \%rating_method, + }, + + 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records', + 'default' => '+1', + }, + + #XXX also have option for an external db?? +# 'cdr_location' => { 'name' => 'CDR database location' +# 'type' => 'select', +# 'select_options' => \%cdr_location, +# 'select_callback' => { +# 'external' => { +# 'enable' => [ 'datasrc', 'username', 'password' ], +# }, +# 'internal' => { +# 'disable' => [ 'datasrc', 'username', 'password' ], +# } +# }, +# }, +# 'datasrc' => { 'name' => 'DBI data source for external CDR table', +# 'disabled' => 'Y', +# }, +# 'username' => { 'name' => 'External database username', +# 'disabled' => 'Y', +# }, +# 'password' => { 'name' => 'External database password', +# 'disabled' => 'Y', +# }, + + }, + 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum rating_method default_prefix )], + 'weight' => 40, +); + +sub calc_setup { + my($self, $cust_pkg ) = @_; + $self->option('setup_fee'); +} + +#false laziness w/voip_sqlradacct... resolve it if that one ever gets used again +sub calc_recur { + my($self, $cust_pkg, $sdate, $details, $param ) = @_; + + my $last_bill = $cust_pkg->last_bill; + + my $ratenum = $cust_pkg->part_pkg->option('ratenum'); + + my $spool_cdr = $cust_pkg->cust_main->spool_cdr; + + my %included_min = (); + + my $charges = 0; + + my $downstream_cdr = ''; + + foreach my $cust_svc ( + grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc + ) { + + foreach my $cdr ( + $cust_svc->get_cdrs_for_update() # $last_bill, $$sdate ) + ) { + if ( $DEBUG > 1 ) { + warn "rating CDR $cdr\n". + join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr ); + } + + my $rate_detail; + my( $rate_region, $regionnum ); + my $pretty_destnum; + my $charge = 0; + my @call_details = (); + if ( $self->option('rating_method') eq 'prefix' + || ! $self->option('rating_method') + ) + { + + ### + # look up rate details based on called station id + # (or calling station id for toll free calls) + ### + + my( $to_or_from, $number ); + if ( $cdr->dst =~ /^(\+?1)?8[02-8]{2}/ ) { #tollfree call + $to_or_from = 'from'; + $number = $cdr->src; + } else { #regular call + $to_or_from = 'to'; + $number = $cdr->dst; + } + + #remove non-phone# stuff and whitespace + $number =~ s/\s//g; +# my $proto = ''; +# $dest =~ s/^(\w+):// and $proto = $1; #sip: +# my $siphost = ''; +# $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com + + #determine the country code + my $countrycode; + if ( $number =~ /^011(((\d)(\d))(\d))(\d+)$/ + || $number =~ /^\+(((\d)(\d))(\d))(\d+)$/ + ) + { + + my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); + #first look for 1 digit country code + if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { + $countrycode = $one; + $number = $u1.$u2.$rest; + } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 + $countrycode = $two; + $number = $u2.$rest; + } else { #3 digit country code + $countrycode = $three; + $number = $rest; + } + + } else { + $countrycode = '1'; + $number =~ s/^1//;# if length($number) > 10; + } + + warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG; + $pretty_destnum = "+$countrycode $number"; + + #find a rate prefix, first look at most specific (4 digits) then 3, etc., + # finally trying the country code only + my $rate_prefix = ''; + for my $len ( reverse(1..6) ) { + $rate_prefix = qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) } + 'npa' => substr($number, 0, $len), + } ) and last; + } + $rate_prefix ||= qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + 'npa' => '', + }); + + # + die "Can't find rate for call $to_or_from +$countrycode $\numbern" + unless $rate_prefix; + + $regionnum = $rate_prefix->regionnum; + $rate_detail = qsearchs('rate_detail', { + 'ratenum' => $ratenum, + 'dest_regionnum' => $regionnum, + } ); + + $rate_region = $rate_prefix->rate_region; + + warn " found rate for regionnum $regionnum ". + "and rate detail $rate_detail\n" + if $DEBUG; + + } elsif ( $self->option('rating_method') eq 'upstream' ) { + + if ( $cdr->cdrtypenum == 1 ) { #rate based on upstream rateid + + $rate_detail = $cdr->cdr_upstream_rate->rate_detail; + + $regionnum = $rate_detail->dest_regionnum; + $rate_region = $rate_detail->dest_region; + + $pretty_destnum = $cdr->dst; + + warn " found rate for regionnum $regionnum and ". + "rate detail $rate_detail\n" + if $DEBUG; + + } else { #pass upstream price through + + $charge = sprintf('%.2f', $cdr->upstream_price); + + @call_details = ( + #time2str("%Y %b %d - %r", $cdr->calldate_unix ), + time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot + 'N/A', #minutes... + '$'.$charge, + #$pretty_destnum, + $cdr->description, #$rate_region->regionname, + ); + + } + + } else { + die "don't know how to rate CDRs using method: ". + $self->option('rating_method'). "\n"; + } + + ### + # find the price and add detail to the invoice + ### + + # if $rate_detail is not found, skip this CDR... i.e. + # don't add it to invoice, don't set its status to NULL, + # don't call downstream_csv or something on it... + # but DO emit a warning... + if ( ! $rate_detail && ! scalar(@call_details) ) { + + warn "no rate_detail found for CDR.acctid: ". $cdr->acctid. + "; skipping\n" + + } else { # there *is* a rate_detail (or call_details), proceed... + + unless ( @call_details ) { + + $included_min{$regionnum} = $rate_detail->min_included + unless exists $included_min{$regionnum}; + + my $granularity = $rate_detail->sec_granularity; + my $seconds = $cdr->billsec; # |ength($cdr->billsec) ? $cdr->billsec : $cdr->duration; + $seconds += $granularity - ( $seconds % $granularity ); + my $minutes = sprintf("%.1f", $seconds / 60); + $minutes =~ s/\.0$// if $granularity == 60; + + $included_min{$regionnum} -= $minutes; + + if ( $included_min{$regionnum} < 0 ) { + my $charge_min = 0 - $included_min{$regionnum}; + $included_min{$regionnum} = 0; + $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min ); + $charges += $charge; + } + + # this is why we need regionnum/rate_region.... + warn " (rate region $rate_region)\n" if $DEBUG; + + @call_details = ( + #time2str("%Y %b %d - %r", $cdr->calldate_unix ), + time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot + $minutes.'m', + '$'.$charge, + $pretty_destnum, + $rate_region->regionname, + ); + + } + + warn " adding details on charge to invoice: ". + join(' - ', @call_details ) + if $DEBUG; + + push @$details, join(' - ', @call_details); #\@call_details, + + # if the customer flag is on, call "downstream_csv" or something + # like it to export the call downstream! + # XXX price plan option to pick format, or something... + $downstream_cdr .= $cdr->downstream_csv( 'format' => 'convergent' ) + if $spool_cdr; + + my $error = $cdr->set_status_and_rated_price('done', $charge); + die $error if $error; + + } + + } # $cdr + + } # $cust_svc + + if ( $spool_cdr && length($downstream_cdr) ) { + + use FS::UID qw(datasrc); + my $dir = '/usr/local/etc/freeside/export.'. datasrc. '/cdr'; + mkdir $dir, 0700 unless -d $dir; + $dir .= '/'. $cust_pkg->custnum. + mkdir $dir, 0700 unless -d $dir; + my $filename = time2str("$dir/CDR%Y%m%d-spool.CSV", time); #XXX invoice date instead? would require changing the order things are generated in cust_main::bill insert cust_bill first - with transactions it could be done though + + push @{ $param->{'precommit_hooks'} }, + sub { + #lock the downstream spool file and append the records + use Fcntl qw(:flock); + use IO::File; + my $spool = new IO::File ">>$filename" + or die "can't open $filename: $!\n"; + flock( $spool, LOCK_EX) + or die "can't lock $filename: $!\n"; + seek($spool, 0, 2) + or die "can't seek to end of $filename: $!\n"; + print $spool $downstream_cdr; + flock( $spool, LOCK_UN ); + close $spool; + }; + + } #if ( $spool_cdr && length($downstream_cdr) ) + + $self->option('recur_flat') + $charges; + +} + +sub is_free { + 0; +} + +sub base_recur { + my($self, $cust_pkg) = @_; + $self->option('recur_flat'); +} + +1; + diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm index fd9c1ddb5..bf18003ab 100644 --- a/FS/FS/part_pkg/voip_sqlradacct.pm +++ b/FS/FS/part_pkg/voip_sqlradacct.pm @@ -41,6 +41,7 @@ sub calc_setup { $self->option('setup_fee'); } +#false laziness w/voip_cdr... resolve it if this one ever gets used again sub calc_recur { my($self, $cust_pkg, $sdate, $details ) = @_; diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm index c0858c0ed..87bc87cba 100644 --- a/FS/FS/part_referral.pm +++ b/FS/FS/part_referral.pm @@ -2,7 +2,8 @@ package FS::part_referral; use strict; use vars qw( @ISA ); -use FS::Record; +use FS::Record qw( qsearch qsearchs dbh ); +use FS::agent; @ISA = qw( FS::Record ); @@ -40,6 +41,8 @@ The following fields are currently supported: =item disabled - Disabled flag, empty or 'Y' +=item agentnum - Optional agentnum (see L<FS::agent>) + =back =head1 NOTE @@ -95,17 +98,92 @@ sub check { my $error = $self->ut_numbern('refnum') || $self->ut_text('referral') + || $self->ut_enum('disabled', [ '', 'Y' ] ) + #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') + || $self->ut_agentnum_acl('agentnum', 'Edit global advertising sources') ; return $error if $error; - if ( $self->dbdef_table->column('disabled') ) { - $error = $self->ut_enum('disabled', [ '', 'Y' ] ); - return $error if $error; - } - $self->SUPER::check; } +=item agent + +Returns the associated agent for this referral, if any, as an FS::agent object. + +=cut + +sub agent { + my $self = shift; + qsearchs('agent', { 'agentnum' => $self->agentnum } ); +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item acl_agentnum_sql [ INCLUDE_GLOBAL_BOOL ] + +Returns an SQL fragment for searching for part_referral records allowed by the +current users's agent ACLs (and "Edit global advertising sources" right). + +Pass a true value to include global advertising sources (for example, when +simply using rather than editing advertising sources). + +=cut + +sub acl_agentnum_sql { + my $self = shift; + + my $curuser = $FS::CurrentUser::CurrentUser; + my $sql = $curuser->agentnums_sql; + $sql = " ( $sql OR agentnum IS NULL ) " + if $curuser->access_right('Edit global advertising sources') + or defined($_[0]) && $_[0]; + + $sql; + +} + +=item all_part_referral [ INCLUDE_GLOBAL_BOOL ] + +Returns all part_referral records allowed by the current users's agent ACLs +(and "Edit global advertising sources" right). + +Pass a true value to include global advertising sources (for example, when +simply using rather than editing advertising sources). + +=cut + +sub all_part_referral { + my $self = shift; + + qsearch({ + 'table' => 'part_referral', + 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql(@_). ' ORDER BY refnum ', + }); + +} + +=item num_part_referral [ INCLUDE_GLOBAL_BOOL ] + +Returns the number of part_referral records allowed by the current users's +agent ACLs (and "Edit global advertising sources" right). + +=cut + +sub num_part_referral { + my $self = shift; + + my $sth = dbh->prepare( + 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql(@_) + ) or die dbh->errstr; + $sth->execute() or die $sth->errstr; + $sth->fetchrow_arrayref->[0]; +} + =back =head1 BUGS diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 1a478a9cd..5b4e54cc2 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -2,6 +2,7 @@ package FS::part_svc; use strict; use vars qw( @ISA $DEBUG ); +use Tie::IxHash; use FS::Record qw( qsearch qsearchs fields dbh ); use FS::Schema qw( dbdef ); use FS::part_svc_column; @@ -79,7 +80,7 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>). =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>. -=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed. For virtual fields, can also be 'X' for excluded. +=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded. =back @@ -142,7 +143,8 @@ sub insert { } ); my $flag = $self->getfield($svcdb.'__'.$field.'_flag'); - if ( uc($flag) =~ /^([DFX])$/ ) { + #if ( uc($flag) =~ /^([DFMAX])$/ ) { + if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it $part_svc_column->setfield('columnflag', $1); $part_svc_column->setfield('columnvalue', $self->getfield($svcdb.'__'.$field) @@ -260,7 +262,8 @@ sub replace { } ); my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); - if ( uc($flag) =~ /^([DFX])$/ ) { + #if ( uc($flag) =~ /^([DFMAX])$/ ) { + if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it $part_svc_column->setfield('columnflag', $1); $part_svc_column->setfield('columnvalue', $new->getfield($svcdb.'__'.$field) @@ -345,7 +348,6 @@ and replace methods. sub check { my $self = shift; - my $recref = $self->hashref; my $error; $error= @@ -356,8 +358,9 @@ sub check { ; return $error if $error; - my @fields = eval { fields( $recref->{svcdb} ) }; #might die - return "Unknown svcdb!" unless @fields; + my @fields = eval { fields( $self->svcdb ) }; #might die + return "Unknown svcdb: ". $self->svcdb. " (Error: $@)" + unless @fields; $self->SUPER::check; } @@ -498,6 +501,161 @@ sub svc_x { map { $_->svc_x } $self->cust_svc; } +=back + +=head1 CLASS METHODS + +=over 4 + +=cut + +my $svc_defs; +sub _svc_defs { + + return $svc_defs if $svc_defs; #cache + + my $conf = new FS::Conf; + + #false laziness w/part_pkg.pm::plan_info + + my %info; + foreach my $INC ( @INC ) { + warn "globbing $INC/FS/svc_*.pm\n" if $DEBUG; + foreach my $file ( glob("$INC/FS/svc_*.pm") ) { + + warn "attempting to load service table info from $file\n" if $DEBUG; + $file =~ /\/(\w+)\.pm$/ or do { + warn "unrecognized file in $INC/FS/: $file\n"; + next; + }; + my $mod = $1; + + if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^svc_acct_pop$/ ) { + warn "skipping FS::$mod" if $DEBUG; + next; + } + + eval "use FS::$mod;"; + if ( $@ ) { + die "error using FS::$mod (skipping): $@\n" if $@; + next; + } + unless ( UNIVERSAL::can("FS::$mod", 'table_info') ) { + warn "FS::$mod has no table_info method; skipping"; + next; + } + + my $info = "FS::$mod"->table_info; + unless ( keys %$info ) { + warn "FS::$mod->table_info doesn't return info, skipping\n"; + next; + } + warn "got info from FS::$mod: $info\n" if $DEBUG; + if ( exists($info->{'disabled'}) && $info->{'disabled'} ) { + warn "skipping disabled service FS::$mod" if $DEBUG; + next; + } + $info{$mod} = $info; + } + } + + tie my %svc_defs, 'Tie::IxHash', + map { $_ => $info{$_}->{'fields'} } + sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } + keys %info, + ; + + # yuck. maybe this won't be so bad when virtual fields become real fields + my %vfields; + foreach my $svcdb (grep dbdef->table($_), keys %svc_defs ) { + eval "use FS::$svcdb;"; + my $self = "FS::$svcdb"->new; + $vfields{$svcdb} = {}; + foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them + my $pvf = $self->pvf($field); + my @list = $pvf->list; + if (scalar @list) { + $svc_defs{$svcdb}->{$field} = { desc => $pvf->label, + type => 'select', + select_list => \@list }; + } else { + $svc_defs{$svcdb}->{$field} = $pvf->label; + } #endif + $vfields{$svcdb}->{$field} = $pvf; + warn "\$vfields{$svcdb}->{$field} = $pvf" + if $DEBUG; + } #next $field + } #next $svcdb + + $svc_defs = \%svc_defs; #cache + +} + +=item svc_tables + +Returns a list of all svc_ tables. + +=cut + +sub svc_tables { + my $class = shift; + my $svc_defs = $class->_svc_defs; + grep { defined( dbdef->table($_) ) } keys %$svc_defs; +} + +=item svc_table_fields TABLE + +Given a table name, returns a hashref of field names. The field names +returned are those with additional (service-definition related) information, +not necessarily all database fields of the table. Pseudo-fields may also +be returned (i.e. svc_acct.usergroup). + +Each value of the hashref is another hashref, which can have one or more of +the following keys: + +=over 4 + +=item label - Description of the field + +=item def_label - Optional description of the field in the context of service definitions + +=item type - Currently "text", "select", "disabled", or "radius_usergroup_selector" + +=item disable_default - This field should not allow a default value in service definitions + +=item disable_fixed - This field should not allow a fixed value in service definitions + +=item disable_inventory - This field should not allow inventory values in service definitions + +=item select_list - If type is "text", this can be a listref of possible values. + +=item select_table - An alternative to select_list, this defines a database table with the possible choices. + +=item select_key - Used with select_table, this is the field name of keys + +=item select_label - Used with select_table, this is the field name of labels + +=back + +=cut + +#maybe this should move and be a class method in svc_Common.pm +sub svc_table_fields { + my($class, $table) = @_; + my $svc_defs = $class->_svc_defs; + my $def = $svc_defs->{$table}; + + foreach ( grep !ref($def->{$_}), keys %$def ) { + + #normalize the shortcut in %info hash + $def->{$_} = { 'label' => $def->{$_} }; + + $def->{$_}{'type'} ||= 'text'; + + } + + $def; +} =back @@ -536,9 +694,23 @@ sub process { map { my $svcdb = $_; my @fields = fields($svcdb); push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge - map { ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' ) } @fields; - } grep defined( dbdef->table($_) ), - qw( svc_acct svc_domain svc_forward svc_www svc_broadband ) + + map { + if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^[MA]$/ ) { + $param->{ $svcdb.'__'.$_ } = + delete( $param->{ $svcdb.'__'.$_.'_classnum' } ); + } + if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^S$/ ) { + $param->{ $svcdb.'__'.$_} = + ref($param->{ $svcdb.'__'.$_}) + ? join(',', @{$param->{ $svcdb.'__'.$_ }} ) + : $param->{ $svcdb.'__'.$_ }; + } + ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' ); + } + @fields; + + } FS::part_svc->svc_tables() ) } ); @@ -632,8 +804,8 @@ sub process_bulk_cust_svc { Delete is unimplemented. -The list of svc_* tables is hardcoded. When svc_acct_pop is renamed, this -should be fixed. +The list of svc_* tables is no longer hardcoded, but svc_acct_pop is skipped +as a special case until it is renamed. all_part_svc_column methods should be documented diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index 0450b35ef..d2b8fd91b 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -41,7 +41,7 @@ fields are currently supported: =item columnvalue - default or fixed value for the column -=item columnflag - null, D, F, X (virtual fields) +=item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded. =back @@ -91,10 +91,16 @@ sub check { ; return $error if $error; - $self->columnflag =~ /^([DFX])$/ + $self->columnflag =~ /^([DFSMAX])$/ or return "illegal columnflag ". $self->columnflag; $self->columnflag(uc($1)); + if ( $self->columnflag =~ /^[MA]$/ ) { + $error = + $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' ); + return $error if $error; + } + $self->SUPER::check; } diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm new file mode 100644 index 000000000..add4da9e0 --- /dev/null +++ b/FS/FS/pay_batch.pm @@ -0,0 +1,486 @@ +package FS::pay_batch; + +use strict; +use vars qw( @ISA ); +use Time::Local; +use Text::CSV_XS; +use FS::Record qw( dbh qsearch qsearchs ); +use FS::cust_pay; +use FS::part_bill_event qw(due_events); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::pay_batch - Object methods for pay_batch records + +=head1 SYNOPSIS + + use FS::pay_batch; + + $record = new FS::pay_batch \%hash; + $record = new FS::pay_batch { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::pay_batch object represents an example. FS::pay_batch inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item batchnum - primary key + +=item payby - CARD or CHEK + +=item status - O (Open), I (In-transit), or R (Resolved) + +=item download - + +=item upload - + + +=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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'pay_batch'; } + +=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('batchnum') + || $self->ut_enum('payby', [ 'CARD', 'CHEK' ]) + || $self->ut_enum('status', [ 'O', 'I', 'R' ]) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item rebalance + +=cut + +sub rebalance { + my $self = shift; +} + +=item set_status + +=cut + +sub set_status { + my $self = shift; + $self->status(shift); + $self->download(time) + if $self->status eq 'I' && ! $self->download; + $self->upload(time) + if $self->status eq 'R' && ! $self->upload; + $self->replace(); +} + +=item import results OPTION => VALUE, ... + +Import batch results. + +Options are: + +I<filehandle> - open filehandle of results file. + +I<format> - "csv-td_canada_trust-merchant_pc_batch", "csv-chase_canada-E-xactBatch" or "PAP" + +=cut + +sub import_results { + my $self = shift; + + my $param = ref($_[0]) ? shift : { @_ }; + my $fh = $param->{'filehandle'}; + my $format = $param->{'format'}; + + my $filetype; # CSV, Fixed80, Fixed264 + my @fields; + my $formatre; # for Fixed.+ + my @values; + my $begin_condition; + my $end_condition; + my $end_hook; + my $hook; + my $approved_condition; + my $declined_condition; + + if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) { + + $filetype = "CSV"; + + @fields = ( + 'paybatchnum', # Reference#: Invoice number of the transaction + 'paid', # Amount: Amount of the transaction. Dollars and cents + # with no decimal entered. + '', # Card Type: 0 - MCrd, 1 - Visa, 2 - AMEX, 3 - Discover, + # 4 - Insignia, 5 - Diners/EnRoute, 6 - JCB + '_date', # Transaction Date: Date the Transaction was processed + 'time', # Transaction Time: Time the transaction was processed + 'payinfo', # Card Number: Card number for the transaction + '', # Expiry Date: Expiry date of the card + '', # Auth#: Authorization number entered for force post + # transaction + 'type', # Transaction Type: 0 - purchase, 40 - refund, + # 20 - force post + 'result', # Processing Result: 3 - Approval, + # 4 - Declined/Amount over limit, + # 5 - Invalid/Expired/stolen card, + # 6 - Comm Error + '', # Terminal ID: Terminal ID used to process the transaction + ); + + $end_condition = sub { + my $hash = shift; + $hash->{'type'} eq '0BC'; + }; + + $end_hook = sub { + my( $hash, $total) = @_; + $total = sprintf("%.2f", $total); + my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100 ); + return "Our total $total does not match bank total $batch_total!" + if $total != $batch_total; + ''; + }; + + $hook = sub { + my $hash = shift; + $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 ); + $hash->{'_date'} = timelocal( substr($hash->{'time'}, 4, 2), + substr($hash->{'time'}, 2, 2), + substr($hash->{'time'}, 0, 2), + substr($hash->{'_date'}, 6, 2), + substr($hash->{'_date'}, 4, 2)-1, + substr($hash->{'_date'}, 0, 4)-1900, ); + }; + + $approved_condition = sub { + my $hash = shift; + $hash->{'type'} eq '0' && $hash->{'result'} == 3; + }; + + $declined_condition = sub { + my $hash = shift; + $hash->{'type'} eq '0' && ( $hash->{'result'} == 4 + || $hash->{'result'} == 5 ); + }; + + + }elsif ( $format eq 'csv-chase_canada-E-xactBatch' ) { + + $filetype = "CSV"; + + @fields = ( + '', # Internal(bank) id of the transaction + '', # Transaction Type: 00 - purchase, 01 - preauth, + # 02 - completion, 03 - forcepost, + # 04 - refund, 05 - auth, + # 06 - purchase corr, 07 - refund corr, + # 08 - void 09 - void return + '', # gateway used to process this transaction + 'paid', # Amount: Amount of the transaction. Dollars and cents + # with decimal entered. + 'auth', # Auth#: Authorization number (if approved) + 'payinfo', # Card Number: Card number for the transaction + '', # Expiry Date: Expiry date of the card + '', # Cardholder Name + 'bankcode', # Bank response code (3 alphanumeric) + 'bankmess', # Bank response message + 'etgcode', # ETG response code (2 alphanumeric) + 'etgmess', # ETG response message + '', # Returned customer number for the transaction + 'paybatchnum', # Reference#: paybatch number of the transaction + '', # Reference#: Invoice number of the transaction + 'result', # Processing Result: Approved of Declined + ); + + $end_condition = sub { + ''; + }; + + $hook = sub { + my $hash = shift; + my $cpb = shift; + $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'}); #hmmmm + $hash->{'_date'} = time; # got a better one? + $hash->{'payinfo'} = $cpb->{'payinfo'} + if( substr($hash->{'payinfo'}, -4) eq substr($cpb->{'payinfo'}, -4) ); + }; + + $approved_condition = sub { + my $hash = shift; + $hash->{'etgcode'} eq '00' && $hash->{'result'} eq "Approved"; + }; + + $declined_condition = sub { + my $hash = shift; + $hash->{'etgcode'} ne '00' # internal processing error + || ( $hash->{'result'} eq "Declined" ); + }; + + + }elsif ( $format eq 'PAP' ) { + + $filetype = "Fixed264"; + + @fields = ( + 'recordtype', # We are interested in the 'D' or debit records + 'batchnum', # Record#: batch number we used when sending the file + 'datacenter', # Where in the bowels of the bank the data was processed + 'paid', # Amount: Amount of the transaction. Dollars and cents + # with no decimal entered. + '_date', # Transaction Date: Date the Transaction was processed + 'bank', # Routing information + 'payinfo', # Account number for the transaction + 'paybatchnum', # Reference#: Invoice number of the transaction + ); + + $formatre = '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$'; + + $end_condition = sub { + my $hash = shift; + $hash->{'recordtype'} eq 'W'; + }; + + $end_hook = sub { + my( $hash, $total) = @_; + $total = sprintf("%.2f", $total); + my $batch_total = $hash->{'datacenter'}.$hash->{'paid'}. + substr($hash->{'_date'},0,1); # YUCK! + $batch_total = sprintf("%.2f", $batch_total / 100 ); + return "Our total $total does not match bank total $batch_total!" + if $total != $batch_total; + ''; + }; + + $hook = sub { + my $hash = shift; + $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 ); + my $tmpdate = timelocal( 0,0,1,1,0,substr($hash->{'_date'}, 0, 3)+2000); + $tmpdate += 86400*(substr($hash->{'_date'}, 3, 3)-1) ; + $hash->{'_date'} = $tmpdate; + $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'}; + }; + + $approved_condition = sub { + 1; + }; + + $declined_condition = sub { + 0; + }; + + + } else { + return "Unknown format $format"; + } + + my $csv = new Text::CSV_XS; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $reself = $self->select_for_update; + + unless ( $reself->status eq 'I' ) { + $dbh->rollback if $oldAutoCommit; + return "batchnum ". $self->batchnum. "no longer in transit"; + }; + + my $error = $self->set_status('R'); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error + } + + my $total = 0; + my $line; + while ( defined($line=<$fh>) ) { + + next if $line =~ /^\s*$/; #skip blank lines + + if ($filetype eq "CSV") { + $csv->parse($line) or do { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $csv->error_input(); + }; + @values = $csv->fields(); + }elsif ($filetype eq "Fixed80" || $filetype eq "Fixed264"){ + @values = $line =~ /$formatre/; + unless (@values) { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $line; + }; + }else{ + $dbh->rollback if $oldAutoCommit; + return "Unknown file type $filetype"; + } + + my %hash; + foreach my $field ( @fields ) { + my $value = shift @values; + next unless $field; + $hash{$field} = $value; + } + + if ( &{$end_condition}(\%hash) ) { + my $error = &{$end_hook}(\%hash, $total); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + last; + } + + my $cust_pay_batch = + qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'}+0 } ); + unless ( $cust_pay_batch ) { + return "unknown paybatchnum $hash{'paybatchnum'}\n"; + } + my $custnum = $cust_pay_batch->custnum, + my $payby = $cust_pay_batch->payby, + + my $new_cust_pay_batch = new FS::cust_pay_batch { $cust_pay_batch->hash }; + + &{$hook}(\%hash, $cust_pay_batch->hashref); + + if ( &{$approved_condition}(\%hash) ) { + + $new_cust_pay_batch->status('Approved'); + + my $cust_pay = new FS::cust_pay ( { + 'custnum' => $custnum, + 'payby' => $payby, + 'paybatch' => $self->batchnum, + map { $_ => $hash{$_} } (qw( paid _date payinfo )), + } ); + $error = $cust_pay->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error adding payment paybatchnum $hash{'paybatchnum'}: $error\n"; + } + $total += $hash{'paid'}; + + $cust_pay->cust_main->apply_payments; + + } elsif ( &{$declined_condition}(\%hash) ) { + + $new_cust_pay_batch->status('Declined'); + + foreach my $part_bill_event ( due_events ( $new_cust_pay_batch, + 'DCLN', + '', + '') ) { + + # don't run subsequent events if balance<=0 + last if $cust_pay_batch->cust_main->balance <= 0; + + if (my $error = $part_bill_event->do_event($new_cust_pay_batch)) { + # gah, even with transactions. + $dbh->commit if $oldAutoCommit; #well. + return $error; + } + + } + + } + + my $error = $new_cust_pay_batch->replace($cust_pay_batch); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=back + +=head1 BUGS + +status is somewhat redundant now that download and upload exist + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm new file mode 100644 index 000000000..28afd037f --- /dev/null +++ b/FS/FS/payby.pm @@ -0,0 +1,195 @@ +package FS::payby; + +use strict; +use vars qw(%hash %payby2bop); +use Tie::IxHash; +use Business::CreditCard; + + +=head1 NAME + +FS::payby - Object methods for payment type records + +=head1 SYNOPSIS + + use FS::payby; + + #for now... + + my @payby = FS::payby->payby; + + my $bool = FS::payby->can_payby('cust_main', 'CARD'); + + tie my %payby, 'Tie::IxHash', FS::payby->payby2longname + + my @cust_payby = FS::payby->cust_payby; + + tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname + +=head1 DESCRIPTION + +Payment types. + +=head1 METHODS + +=over 4 + +=item + +=cut + +# paybys can be any/all of: +# - a customer payment type (cust_main.payby) +# - a payment or refund type (cust_pay.payby, cust_pay_batch.payby, cust_refund.payby) +# - an event type (part_bill_event.payby) + +tie %hash, 'Tie::IxHash', + 'CARD' => { + tinyname => 'card', + shortname => 'Credit card', + longname => 'Credit card (automatic)', + }, + 'DCRD' => { + tinyname => 'card', + shortname => 'Credit card', + longname => 'Credit card (on-demand)', + cust_pay => 'CARD', #this is a customer type only, payments are CARD... + }, + 'CHEK' => { + tinyname => 'check', + shortname => 'Electronic check', + longname => 'Electronic check (automatic)', + }, + 'DCHK' => { + tinyname => 'check', + shortname => 'Electronic check', + longname => 'Electronic check (on-demand)', + cust_pay => 'CHEK', #this is a customer type only, payments are CHEK... + }, + 'LECB' => { + tinyname => 'phone bill', + shortname => 'Phone bill billing', + longname => 'Phone bill billing', + }, + 'BILL' => { + tinyname => 'billing', + shortname => 'Billing', + longname => 'Billing', + }, + 'PREP' => { + tinyname => 'prepaid card', + shortname => 'Prepaid card', + longname => 'Prepaid card', + cust_main => 'BILL', #this is a payment type only, customers go to BILL... + }, + 'CASH' => { + tinyname => 'cash', + shortname => 'Cash', # initial payment, then billing + longname => 'Cash', + cust_main => 'BILL', #this is a payment type only, customers go to BILL... + }, + 'WEST' => { + tinyname => 'western union', + shortname => 'Western Union', # initial payment, then billing + longname => 'Western Union', + cust_main => 'BILL', #this is a payment type only, customers go to BILL... + }, + 'MCRD' => { #not the same as DCRD + tinyname => 'card', + shortname => 'Manual credit card', # initial payment, then billing + longname => 'Manual credit card', + cust_main => 'BILL', #this is a payment type only, customers go to BILL... + }, + 'COMP' => { + tinyname => 'comp', + shortname => 'Complimentary', + longname => 'Complimentary', + cust_pay => '', # (free) is depricated as a payment type in cust_pay + }, + 'CBAK' => { + tinyname => 'chargeback', + shortname => 'Chargeback', + longname => 'Chargeback', + cust_main => '', # not a customer type + }, + 'DCLN' => { # This is only an event. + tinyname => 'declined', + shortname => 'Batch declined payment', + longname => 'Batch declined payment', + + #its neither of these.. + cust_main => '', + cust_pay => '', + + }, +; + +sub payby { + keys %hash; +} + +sub can_payby { + my( $self, $table, $payby ) = @_; + + #return "Illegal payby" unless $hash{$payby}; + return 0 unless $hash{$payby}; + + $table = 'cust_pay' if $table eq 'cust_pay_batch' || $table eq 'cust_refund'; + return 0 if exists( $hash{$payby}->{$table} ); + + return 1; +} + +sub payby2longname { + my $self = shift; + map { $_ => $hash{$_}->{longname} } $self->payby; +} + +sub shortname { + my( $self, $payby ) = @_; + $hash{$payby}->{shortname}; +} + +sub longname { + my( $self, $payby ) = @_; + $hash{$payby}->{longname}; +} + +%payby2bop = ( + 'CARD' => 'CC', + 'CHEK' => 'ECHECK', +); + +sub payby2bop { + my( $self, $payby ) = @_; + $payby2bop{ $self->payby2payment($payby) }; +} + +sub payby2payment { + my( $self, $payby ) = @_; + $hash{$payby}{'cust_pay'} || $payby; +} + +sub cust_payby { + my $self = shift; + grep { ! exists $hash{$_}->{cust_main} } $self->payby; +} + +sub cust_payby2longname { + my $self = shift; + map { $_ => $hash{$_}->{longname} } $self->cust_payby; +} + +=back + +=head1 BUGS + +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 + +=cut + +1; + diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm new file mode 100644 index 000000000..2d7b4ffe0 --- /dev/null +++ b/FS/FS/payinfo_Mixin.pm @@ -0,0 +1,243 @@ +package FS::payinfo_Mixin; + +use strict; +use Business::CreditCard; +use FS::payby; + +=head1 NAME + +FS::payinfo_Mixin - Mixin class for records in tables that contain payinfo. + +=head1 SYNOPSIS + +package FS::some_table; +use vars qw(@ISA); +@ISA = qw( FS::payinfo_Mixin FS::Record ); + +=head1 DESCRIPTION + +This is a mixin class for records that contain payinfo. + +This class handles the following functions for payinfo... + +Payment Mask (Generation and Storage) +Data Validation (parent checks need to be sure to call this) +Encryption - In the Future (Pull from Record.pm) +Bad Card Stuff - In the Future (Integrate Banned Pay) +Currency - In the Future + +=head1 FIELDS + +=over 4 + +=item payby + +The following payment types (payby) are supported: + +For Customers (cust_main): +'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand), +'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand), +'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or +'PREPAY' (special billing type: applies a credit and sets billing type to I<BILL> - see L<FS::prepay_credit>) + +For Refunds (cust_refund): +'CARD' (credit cards), 'CHEK' (electronic check/ACH), +'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash), +'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free) + + +For Payments (cust_pay): +'CARD' (credit cards), 'CHEK' (electronic check/ACH), +'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card), +'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card) +'COMP' (free) is depricated as a payment type in cust_pay + +=cut + +# was this supposed to do something? + +#sub payby { +# my($self,$payby) = @_; +# if ( defined($payby) ) { +# $self->setfield('payby', $payby); +# } +# return $self->getfield('payby') +#} + +=item payinfo + +Payment information (payinfo) can be one of the following types: + +Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>) + +=cut + +sub payinfo { + my($self,$payinfo) = @_; + if ( defined($payinfo) ) { + $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter' + $self->paymask($self->mask_payinfo()); + } else { + $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter' + return $payinfo; + } +} + +=item paycvv + +Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card + +=cut + +sub paycvv { + my($self,$paycvv) = @_; + # This is only allowed in cust_main... Even then it really shouldn't be stored... + if ($self->table eq 'cust_main') { + if ( defined($paycvv) ) { + $self->setfield('paycvv', $paycvv); # This is okay since we are the 'setter' + } else { + $paycvv = $self->getfield('paycvv'); # This is okay since we are the 'getter' + return $paycvv; + } + } else { +# warn "This doesn't work for other tables besides cust_main + ''; + } +} + +=item paymask + +=cut + +sub paymask { + my($self, $paymask) = @_; + + if ( defined($paymask) && $paymask ne '' ) { + # I hate this little bit of magic... I don't expect it to cause a problem, + # but who knows... If the payinfo is passed in masked then ignore it and + # set it based on the payinfo. The only guy that should call this in this + # way is... $self->payinfo + $self->setfield('paymask', $self->mask_payinfo()); + + } else { + + $paymask=$self->getfield('paymask'); + if (!defined($paymask) || $paymask eq '') { + # Generate it if it's blank - Note that we're not going to set it - just + # generate + $paymask = $self->mask_payinfo(); + } + + } + + return $paymask; +} + +=back + +=head1 METHODS + +=over 4 + +=item mask_payinfo [ PAYBY, PAYINFO ] + +This method converts the payment info (credit card, bank account, etc.) into a +masked string. + +Optionally, an arbitrary payby and payinfo can be passed. + +=cut + +sub mask_payinfo { + my $self = shift; + my $payby = scalar(@_) ? shift : $self->payby; + my $payinfo = scalar(@_) ? shift : $self->payinfo; + + # Check to see if it's encrypted... + my $paymask; + if ( $self->is_encrypted($payinfo) ) { + $paymask = 'N/A'; + } else { + # if not, mask it... + if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') { + # Credit Cards (Show first and last four) + $paymask = substr($payinfo,0,6). + 'x'x(length($payinfo)-10). + substr($payinfo,(length($payinfo)-4)); + } elsif ($payby eq 'CHEK' || $payby eq 'DCHK' ) { + # Checks (Show last 2 @ bank) + my( $account, $aba ) = split('@', $payinfo ); + $paymask = 'x'x(length($account)-2). + substr($account,(length($account)-2))."@".$aba; + } else { # Tie up loose ends + $paymask = $payinfo; + } + } + return $paymask; +} + +=cut + +sub _mask_payinfo { + my $self = shift; + +=item payinfo_check + +Checks payby and payinfo. + +For Customers (cust_main): +'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand), +'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand), +'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or +'PREPAY' (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>) + +For Refunds (cust_refund): +'CARD' (credit cards), 'CHEK' (electronic check/ACH), +'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash), +'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' (Chargeback), or 'COMP' (free) + +For Payments (cust_pay): +'CARD' (credit cards), 'CHEK' (electronic check/ACH), +'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card), +'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card) +'COMP' (free) is depricated as a payment type in cust_pay + +=cut + +sub payinfo_check { + my $self = shift; + + FS::payby->can_payby($self->table, $self->payby) + or return "Illegal payby: ". $self->payby; + + if ( $self->payby eq 'CARD' ) { + my $payinfo = $self->payinfo; + $payinfo =~ s/\D//g; + $self->payinfo($payinfo); + if ( $self->payinfo ) { + $self->payinfo =~ /^(\d{13,16})$/ + or return "Illegal (mistyped?) credit card number (payinfo)"; + $self->payinfo($1); + validate($self->payinfo) or return "Illegal credit card number"; + return "Unknown card type" if cardtype($self->payinfo) eq "Unknown"; + } else { + $self->payinfo('N/A'); + } + } else { + my $error = $self->ut_textn('payinfo'); + return $error if $error; + } +} + +=head1 BUGS + +Have to add the future items... + +=head1 SEE ALSO + +L<FS::payby>, L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/pkg_class.pm b/FS/FS/pkg_class.pm new file mode 100644 index 000000000..bab6e5e56 --- /dev/null +++ b/FS/FS/pkg_class.pm @@ -0,0 +1,113 @@ +package FS::pkg_class; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch ); +use FS::part_pkg; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::pkg_class - Object methods for pkg_class records + +=head1 SYNOPSIS + + use FS::pkg_class; + + $record = new FS::pkg_class \%hash; + $record = new FS::pkg_class { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::pkg_class object represents an package class. Every package definition +(see L<FS::part_pkg>) has, optionally, a package class. FS::pkg_class inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item classnum - primary key (assigned automatically for new package classes) + +=item classname - Text name of this package class + +=item disabled - Disabled flag, empty or 'Y' + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new package class. To add the package class to the database, see +L<"insert">. + +=cut + +sub table { 'pkg_class'; } + +=item insert + +Adds this package class to the database. If there is an error, returns the +error, otherwise returns false. + +=item delete + +Deletes this package class from the database. Only package classes with no +associated package definitions can be deleted. If there is an error, returns +the error, otherwise returns false. + +=cut + +sub delete { + my $self = shift; + + return "Can't delete an pkg_class with part_pkg records!" + if qsearch( 'part_pkg', { 'classnum' => $self->classnum } ); + + $self->SUPER::delete; +} + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid package class. If there is an +error, returns the error, otherwise returns false. Called by the insert and +replace methods. + +=cut + +sub check { + my $self = shift; + + $self->ut_numbern('classnum') + or $self->ut_text('classname') + or $self->SUPER::check; + +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, L<FS::part_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm index 065ddbe51..9f3a4a1b7 100644 --- a/FS/FS/pkg_svc.pm +++ b/FS/FS/pkg_svc.pm @@ -82,7 +82,9 @@ returns the error, otherwise returns false. =cut sub replace { - my ( $new, $old ) = ( shift, shift ); + my( $new, $old ) = ( shift, shift ); + + $old = $new->replace_old unless defined($old); return "Can't change pkgpart!" if $old->pkgpart != $new->pkgpart; return "Can't change svcpart!" if $old->svcpart != $new->svcpart; diff --git a/FS/FS/port.pm b/FS/FS/port.pm index 253727ba7..c26ca85d4 100644 --- a/FS/FS/port.pm +++ b/FS/FS/port.pm @@ -52,7 +52,7 @@ from FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new port. To add the example to the database, see L<"insert">. +Creates a new port. To add the port 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<hash> method. @@ -91,7 +91,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid port. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/prepay_credit.pm b/FS/FS/prepay_credit.pm index 92f2a3032..bf85dfaa6 100644 --- a/FS/FS/prepay_credit.pm +++ b/FS/FS/prepay_credit.pm @@ -61,7 +61,7 @@ fields are currently supported: =item new HASHREF -Creates a new pre-paid credit. To add the example to the database, see +Creates a new pre-paid credit. To add the pre-paid credit to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it @@ -110,6 +110,9 @@ sub check { || $self->ut_alpha('identifier') || $self->ut_money('amount') || $self->ut_numbern('seconds') + || $self->ut_numbern('upbytes') + || $self->ut_numbern('downbytes') + || $self->ut_numbern('totalbytes') || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') || $self->SUPER::check ; diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm index f42d99837..5f8bf11f0 100644 --- a/FS/FS/queue.pm +++ b/FS/FS/queue.pm @@ -68,7 +68,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new job. To add the example to the database, see L<"insert">. +Creates a new job. To add the job 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<hash> method. diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm index 39c71192f..c96ff1236 100644 --- a/FS/FS/queue_arg.pm +++ b/FS/FS/queue_arg.pm @@ -46,7 +46,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new argument. To add the example to the database, see L<"insert">. +Creates a new argument. To add the argument 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<hash> method. diff --git a/FS/FS/queue_depend.pm b/FS/FS/queue_depend.pm index bc910d8e9..99a22c5c6 100644 --- a/FS/FS/queue_depend.pm +++ b/FS/FS/queue_depend.pm @@ -43,7 +43,7 @@ inherits from FS::Record. The following fields are currently supported: The job specified by B<jobnum> depends on the job specified B<depend_jobnum> - the B<jobnum> job will not be run until the B<depend_jobnum> job has completed -sucessfully (or manually removed). +successfully (or manually removed). =head1 METHODS diff --git a/FS/FS/rate.pm b/FS/FS/rate.pm index a471e2ea4..c50ca044a 100644 --- a/FS/FS/rate.pm +++ b/FS/FS/rate.pm @@ -7,7 +7,7 @@ use FS::rate_detail; @ISA = qw(FS::Record); -$DEBUG = 1; +$DEBUG = 0; =head1 NAME diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm index 7d54355fb..6f023f575 100644 --- a/FS/FS/rate_detail.pm +++ b/FS/FS/rate_detail.pm @@ -56,7 +56,8 @@ inherits from FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new call plan rate. To add the call plan rate 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<hash> method. @@ -95,7 +96,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid call plan rate. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. @@ -113,7 +114,11 @@ sub check { || $self->ut_foreign_keyn('orig_regionnum', 'rate_region', 'regionnum' ) || $self->ut_foreign_key('dest_regionnum', 'rate_region', 'regionnum' ) || $self->ut_number('min_included') - || $self->ut_money('min_charge') + + #|| $self->ut_money('min_charge') + #good enough for now... + || $self->ut_float('min_charge') + || $self->ut_number('sec_granularity') ; return $error if $error; @@ -121,6 +126,30 @@ sub check { $self->SUPER::check; } +=item orig_region + +Returns the origination region (see L<FS::rate_region>) associated with this +call plan rate. + +=cut + +sub orig_region { + my $self = shift; + qsearchs('rate_region', { 'regionnum' => $self->orig_regionnum } ); +} + +=item dest_region + +Returns the destination region (see L<FS::rate_region>) associated with this +call plan rate. + +=cut + +sub dest_region { + my $self = shift; + qsearchs('rate_region', { 'regionnum' => $self->dest_regionnum } ); +} + =back =head1 BUGS diff --git a/FS/FS/reason.pm b/FS/FS/reason.pm new file mode 100644 index 000000000..0ce2f80b0 --- /dev/null +++ b/FS/FS/reason.pm @@ -0,0 +1,125 @@ +package FS::reason; + +use strict; +use vars qw( @ISA ); +use FS::reason_type; +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::reason - Object methods for reason records + +=head1 SYNOPSIS + + use FS::reason; + + $record = new FS::reason \%hash; + $record = new FS::reason { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::reason object represents a reason message. FS::reason inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item reasonnum - primary key + +=item reason_type - index into FS::reason_type + +=item reason - text of the reason + +=item disabled - 'Y' or '' + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new reason. 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<hash> method. + +=cut + +sub table { 'reason'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=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 + +=item check + +Checks all fields to make sure this is a valid reason. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('reasonnum') + || $self->ut_text('reason') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item reasontype + +Returns the reason_type (see <I>FS::reason_type</I>) associated with this reason. + +=cut + +sub reasontype { + qsearchs( 'reason_type', { 'typenum' => shift->reason_type } ); +} + +=back + +=head1 BUGS + +Here be termintes. Don't use on wooden computers. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm new file mode 100644 index 000000000..89278d08a --- /dev/null +++ b/FS/FS/reason_type.pm @@ -0,0 +1,135 @@ +package FS::reason_type; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::reason_type - Object methods for reason_type records + +=head1 SYNOPSIS + + use FS::reason_type; + + $record = new FS::reason_type \%hash; + $record = new FS::reason_type { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::reason_type object represents a grouping of reasons. FS::reason_type +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item typenum - primary key + +=item class - currently 'C' or 'S' for cancel or suspend + +=item type - name of the type of reason + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new reason_type. 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<hash> method. + +=cut + +sub table { 'reason_type'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=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 + +=item check + +Checks all fields to make sure this is a valid reason_type. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('typenum') + || $self->ut_enum('class', [ 'C', 'S' ] ) + || $self->ut_text('type') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item reasons + +Returns a list of all reasons associated with this type. + +=cut + +sub reasons { + qsearch( 'reason', { 'reason_type' => shift->typenum } ); +} + +=item enabled_reasons + +Returns a list of enabled reasons associated with this type. + +=cut + +sub enabled_reasons { + qsearch( 'reason', { 'reason_type' => shift->typenum, + 'enabled' => '', + } ); +} + +=back + +=head1 BUGS + +Here be termintes. Don't use on wooden computers. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/reg_code_pkg.pm b/FS/FS/reg_code_pkg.pm index 9b9a87712..837b755e6 100644 --- a/FS/FS/reg_code_pkg.pm +++ b/FS/FS/reg_code_pkg.pm @@ -49,7 +49,8 @@ supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new registration code. To add the registration code 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<hash> method. diff --git a/FS/FS/registrar.pm b/FS/FS/registrar.pm new file mode 100644 index 000000000..cf5dc4907 --- /dev/null +++ b/FS/FS/registrar.pm @@ -0,0 +1,119 @@ +package FS::registrar; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::registrar - Object methods for registrar records + +=head1 SYNOPSIS + + use FS::registrar; + + $record = new FS::registrar \%hash; + $record = new FS::registrar { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::registrar object represents a registrar. FS::registrar inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item registrarnum - primary key + +=item registrarname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new registrar. To add the registrar 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'registrar'; } + +=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 registrar. 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('registrarnum') + || $self->ut_text('registrarname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 962e36a07..f60a7d945 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -1,17 +1,20 @@ package FS::svc_Common; use strict; -use vars qw( @ISA $noexport_hack $DEBUG ); -use Carp; +use vars qw( @ISA $noexport_hack $DEBUG $me ); +use Carp qw( cluck carp croak ); #specify cluck have to specify them all.. use FS::Record qw( qsearch qsearchs fields dbh ); use FS::cust_main_Mixin; use FS::cust_svc; use FS::part_svc; use FS::queue; use FS::cust_main; +use FS::inventory_item; +use FS::inventory_class; @ISA = qw( FS::cust_main_Mixin FS::Record ); +$me = '[FS::svc_Common]'; $DEBUG = 0; =head1 NAME @@ -33,6 +36,27 @@ inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record. =over 4 +=item search_sql_field FIELD STRING + +Class method which returns an SQL fragment to search for STRING in FIELD. + +=cut + +sub search_sql_field { + my( $class, $field, $string ) = @_; + my $table = $class->table; + my $q_string = dbh->quote($string); + "$table.$field = $q_string"; +} + +#fallback for services that don't provide a search... +sub search_sql { + #my( $class, $string ) = @_; + '1 = 0'; #false +} + +=item new + =cut sub new { @@ -49,7 +73,10 @@ sub new { #$self->{'Hash'} = shift; my $newhash = shift; $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) }; - $self->setdefault; + + $self->setdefault( $self->_fieldhandlers ) + unless $self->svcnum; + $self->{'Hash'}{$_} = $newhash->{$_} foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) } keys %$newhash; @@ -67,6 +94,9 @@ sub new { $self; } +#empty default +sub _fieldhandlers { {}; } + sub virtual_fields { # This restricts the fields based on part_svc_column and the svcpart of @@ -105,6 +135,19 @@ sub virtual_fields { return (); } +=item label + +svc_Common provides a fallback label subroutine that just returns the svcnum. + +=cut + +sub label { + my $self = shift; + cluck "warning: ". ref($self). " not loaded or missing label method; ". + "using svcnum"; + $self->svcnum; +} + =item check Checks the validity of fields in this record. @@ -149,13 +192,13 @@ jobnum(s) (they will not run until the specific job(s) complete(s)). sub insert { my $self = shift; my %options = @_; - warn "FS::svc_Common::insert called with options ". - join(', ', map { "$_: $options{$_}" } keys %options ). "\n" - if $DEBUG; + warn "[$me] insert called with options ". + join(', ', map { "$_: $options{$_}" } keys %options ). "\n" + if $DEBUG; my @jobnums = (); local $FS::queue::jobnums = \@jobnums; - warn "FS::svc_Common::insert: set \$FS::queue::jobnums to $FS::queue::jobnums" + warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n" if $DEBUG; my $objects = $options{'child_objects'} || []; my $depend_jobnums = $options{'depend_jobnum'} || []; @@ -202,6 +245,12 @@ sub insert { $self->svcpart($cust_svc->svcpart); } + $error = $self->set_auto_inventory; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + $error = $self->SUPER::insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -227,7 +276,7 @@ sub insert { #new-style exports! unless ( $noexport_hack ) { - warn "FS::svc_Common::insert: \$FS::queue::jobnums is $FS::queue::jobnums" + warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n" if $DEBUG; foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { @@ -240,11 +289,11 @@ sub insert { } foreach my $depend_jobnum ( @$depend_jobnums ) { - warn "inserting dependancies on supplied job $depend_jobnum\n" + warn "[$me] inserting dependancies on supplied job $depend_jobnum\n" if $DEBUG; foreach my $jobnum ( @jobnums ) { my $queue = qsearchs('queue', { 'jobnum' => $jobnum } ); - warn "inserting dependancy for job $jobnum on $depend_jobnum\n" + warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n" if $DEBUG; my $error = $queue->depend_insert($depend_jobnum); if ( $error ) { @@ -285,33 +334,20 @@ sub delete { local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; - my $svcnum = $self->svcnum; - my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->SUPER::delete; - return $error if $error; - - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_delete($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } - } + $error = $self->SUPER::delete + || $self->export('delete') + || $self->return_inventory + || $self->cust_svc->delete + ; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - return $error if $error; - - my $cust_svc = $self->cust_svc; - $error = $cust_svc->delete; - return $error if $error; - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -338,7 +374,16 @@ sub replace { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $error = $new->SUPER::replace($old); + # We absolutely have to have an old vs. new record to make this work. + $old = $new->replace_old unless defined($old); + + my $error = $new->set_auto_inventory; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $error = $new->SUPER::replace($old); if ($error) { $dbh->rollback if $oldAutoCommit; return $error; @@ -409,7 +454,7 @@ to test the return). Usually called by the check method. sub setfixed { my $self = shift; - $self->setx('F'); + $self->setx('F', @_); } =item setdefault @@ -422,20 +467,66 @@ the FS::part_svc object (use ref() to test the return). sub setdefault { my $self = shift; - $self->setx('D'); + $self->setx('D', @_ ); +} + +=item set_default_and_fixed + +=cut + +sub set_default_and_fixed { + my $self = shift; + $self->setx( [ 'D', 'F' ], @_ ); } +=item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ] + +Sets fields according to the passed in flag or arrayref of flags. + +Optionally, a hashref of field names and callback coderefs can be passed. +If a coderef exists for a given field name, instead of setting the field, +the coderef is called with the column value (part_svc_column.columnvalue) +as the single parameter. + +=cut + sub setx { my $self = shift; my $x = shift; + my @x = ref($x) ? @$x : ($x); + my $coderef = scalar(@_) ? shift : $self->_fieldhandlers; - my $error; - - $error = + my $error = $self->ut_numbern('svcnum') ; return $error if $error; + my $part_svc = $self->part_svc; + return "Unkonwn svcpart" unless $part_svc; + + #set default/fixed/whatever fields from part_svc + + foreach my $part_svc_column ( + grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x + $part_svc->all_part_svc_column + ) { + + my $columnname = $part_svc_column->columnname; + my $columnvalue = $part_svc_column->columnvalue; + + $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue ) + if exists( $coderef->{$columnname} ); + $self->setfield( $columnname, $columnvalue ); + + } + + $part_svc; + +} + +sub part_svc { + my $self = shift; + #get part_svc my $svcpart; if ( $self->get('svcpart') ) { @@ -445,41 +536,89 @@ sub setx { return "Unknown svcnum" unless $cust_svc; $svcpart = $cust_svc->svcpart; } - my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); + + qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); + +} + +=item set_auto_inventory + +Sets any fields which auto-populate from inventory (see L<FS::part_svc>). +If there is an error, returns the error, otherwise returns false. + +=cut + +sub set_auto_inventory { + my $self = shift; + + my $error = + $self->ut_numbern('svcnum') + ; + return $error if $error; + + my $part_svc = $self->part_svc; return "Unkonwn svcpart" unless $part_svc; + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + #set default/fixed/whatever fields from part_svc my $table = $self->table; foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) { my $part_svc_column = $part_svc->part_svc_column($field); - if ( $part_svc_column->columnflag eq $x ) { - $self->setfield( $field, $part_svc_column->columnvalue ); - } - } + if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) { + + my $classnum = $part_svc_column->columnvalue; + my $inventory_item = qsearchs({ + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum, + 'svcnum' => '', + }, + 'extra_sql' => 'LIMIT 1 FOR UPDATE', + }); + + unless ( $inventory_item ) { + $dbh->rollback if $oldAutoCommit; + my $inventory_class = + qsearchs('inventory_class', { 'classnum' => $classnum } ); + return "Can't find inventory_class.classnum $classnum" + unless $inventory_class; + return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS + #for pluralizing + } - $part_svc; + $inventory_item->svcnum( $self->svcnum ); + my $ierror = $inventory_item->replace(); + if ( $ierror ) { + $dbh->rollback if $oldAutoCommit; + return "Error provisioning inventory: $ierror"; + + } -} + $self->setfield( $field, $inventory_item->item ); -=item cust_svc + } + } -Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc -object (see L<FS::cust_svc>). + $dbh->commit or die $dbh->errstr if $oldAutoCommit; -=cut + ''; -sub cust_svc { - my $self = shift; - qsearchs('cust_svc', { 'svcnum' => $self->svcnum } ); } -=item suspend - -Runs export_suspend callbacks. +=item return_inventory =cut -sub suspend { +sub return_inventory { my $self = shift; local $SIG{HUP} = 'IGNORE'; @@ -493,21 +632,56 @@ sub suspend { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_suspend($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } + foreach my $inventory_item ( $self->inventory_item ) { + $inventory_item->svcnum(''); + my $error = $inventory_item->replace(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error returning inventory: $error"; } } $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; +} + +=item inventory_item + +Returns the inventory items associated with this svc_ record, as +FS::inventory_item objects (see L<FS::inventory_item>. + +=cut +sub inventory_item { + my $self = shift; + qsearch({ + 'table' => 'inventory_item', + 'hashref' => { 'svcnum' => $self->svcnum, }, + }); +} + +=item cust_svc + +Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc +object (see L<FS::cust_svc>). + +=cut + +sub cust_svc { + my $self = shift; + qsearchs('cust_svc', { 'svcnum' => $self->svcnum } ); +} + +=item suspend + +Runs export_suspend callbacks. + +=cut + +sub suspend { + my $self = shift; + $self->export('suspend'); } =item unsuspend @@ -518,6 +692,19 @@ Runs export_unsuspend callbacks. sub unsuspend { my $self = shift; + $self->export('unsuspend'); +} + +=item export HOOK [ EXPORT_ARGS ] + +Runs the provided export hook (i.e. "suspend", "unsuspend") for this service. + +=cut + +sub export { + my( $self, $method ) = ( shift, shift ); + + $method = "export_$method" unless $method =~ /^export_/; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -533,10 +720,11 @@ sub unsuspend { #new-style exports! unless ( $noexport_hack ) { foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_unsuspend($self); + next unless $part_export->can($method); + my $error = $part_export->$method($self, @_); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. + return "error exporting $method event to ". $part_export->exporttype. " (transaction rolled back): $error"; } } @@ -549,9 +737,13 @@ sub unsuspend { =item cancel -Stub - returns false (no error) so derived classes don't need to define these +Stub - returns false (no error) so derived classes don't need to define this methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). +This method is called *before* the deletion step which actually deletes the +services. This method should therefore only be used for "pre-deletion" +cancellation steps, if necessary. + =cut sub cancel { ''; } @@ -586,6 +778,8 @@ sub clone_kludge_unsuspend { The setfixed method return value. +B<export> method isn't used by insert and replace methods yet. + =head1 SEE ALSO L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html diff --git a/FS/FS/svc_External_Common.pm b/FS/FS/svc_External_Common.pm new file mode 100644 index 000000000..a5805aafd --- /dev/null +++ b/FS/FS/svc_External_Common.pm @@ -0,0 +1,199 @@ +package FS::svc_External_Common; + +use strict; +use vars qw(@ISA); +use FS::svc_Common; + +@ISA = qw( FS::svc_Common ); + +=head1 NAME + +FS::svc_external - Object methods for svc_external records + +=head1 SYNOPSIS + + use FS::svc_external; + + $record = new FS::svc_external \%hash; + $record = new FS::svc_external { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + + $error = $record->suspend; + + $error = $record->unsuspend; + + $error = $record->cancel; + +=head1 DESCRIPTION + +FS::svc_External_Common is intended as a base class for table-specific classes +to inherit from. FS::svc_External_Common is used for services which connect +to externally tracked services via "id" and "table" fields. + +FS::svc_External_Common inherits from FS::svc_Common. + +The following fields are currently supported: + +=over 4 + +=item svcnum - primary key + +=item id - unique number of external record + +=item title - for invoice line items + +=back + +=head1 METHODS + +=over 4 + +=item search_sql + +Provides a default search_sql method which returns an SQL fragment to search +the B<title> field. + +=cut + +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('title', $string); +} + +=item new HASHREF + +Creates a new external service. To add the external service 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<hash> method. + +=cut + +=item label + +Returns a string identifying this external service in the form "id:title" + +=cut + +sub label { + my $self = shift; + $self->id. ':'. $self->title; +} + +=item insert [ , OPTION => VALUE ... ] + +Adds this external service to the database. If there is an error, returns the +error, otherwise returns false. + +The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be +defined. An FS::cust_svc record will be created and inserted. + +Currently available options are: I<depend_jobnum> + +If I<depend_jobnum> is set (to a scalar jobnum or an array reference of +jobnums), all provisioning jobs will have a dependancy on the supplied +jobnum(s) (they will not run until the specific job(s) complete(s)). + +=cut + +#sub insert { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::insert(@_); +# return $error if $error; +# +# ''; +#} + +=item delete + +Delete this record from the database. + +=cut + +#sub delete { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::delete; +# return $error if $error; +# +# ''; +#} + + +=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 + +#sub replace { +# my ( $new, $old ) = ( shift, shift ); +# my $error; +# +# $error = $new->SUPER::replace($old); +# return $error if $error; +# +# ''; +#} + +=item suspend + +Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item unsuspend + +Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item cancel + +Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item check + +Checks all fields to make sure this is a valid external service. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $x = $self->setfixed; + return $x unless ref($x); + my $part_svc = $x; + + my $error = + $self->ut_numbern('svcnum') + || $self->ut_numbern('id') + || $self->ut_textn('title') + ; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, +L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_Parent_Mixin.pm b/FS/FS/svc_Parent_Mixin.pm new file mode 100644 index 000000000..4501bafc8 --- /dev/null +++ b/FS/FS/svc_Parent_Mixin.pm @@ -0,0 +1,103 @@ +package FS::svc_Parent_Mixin; + +use strict; +use NEXT; +use FS::Record qw(qsearch qsearchs); +use FS::cust_svc; + +=head1 NAME + +FS::svc_Parent_Mixin - Mixin class for svc_ classes with a parent_svcnum field + +=head1 SYNOPSIS + +package FS::svc_table; +use vars qw(@ISA); +@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common ); + +=head1 DESCRIPTION + +This is a mixin class for svc_ classes that contain a parent_svcnum field. + +=cut + +=head1 METHODS + +=over 4 + +=item parent_cust_svc + +Returns the parent FS::cust_svc object. + +=cut + +sub parent_cust_svc { + my $self = shift; + qsearchs('cust_svc', { 'svcnum' => $self->parent_svcnum } ); +} + +=item parent_svc_x + +Returns the corresponding parent FS::svc_ object. + +=cut + +sub parent_svc_x { + my $self = shift; + $self->parent_cust_svc->svc_x; +} + +=item children_cust_svc + +Returns a list of any child FS::cust_svc objects. + +Note: This is not recursive; it only returns direct children. + +=cut + +sub children_cust_svc { + my $self = shift; + qsearch('cust_svc', { 'parent_svcnum' => $self->svcnum } ); +} + +=item children_svc_x + +Returns the corresponding list of child FS::svc_ objects. + +=cut + +sub children_svc_x { + my $self = shift; + map { $_->svc_x } $self->children_cust_svc; +} + +=item check + +This class provides a check subroutine which takes care of checking the +parent_svcnum field. The svc_ class which uses it will call SUPER::check at +the end of its own checks, and this class will call NEXT::check to pass +the check "up the chain" (see L<NEXT>). + +=cut + +sub check { + my $self = shift; + + $self->ut_foreign_keyn('parent_svcnum', 'cust_svc', 'svcnum') + || $self->NEXT::check; + +} + +=back + +=head1 BUGS + +Do we need a recursive child finder for multi-layered children? + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record> + +=cut + +1; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index c1851d3ce..0a7d6be6d 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -8,7 +8,10 @@ use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles $username_noperiod $username_nounderscore $username_nodash $username_uppercase $username_percent $password_noampersand $password_noexclamation - $welcome_template $welcome_from $welcome_subject $welcome_mimetype + $welcome_template $welcome_from + $welcome_subject $welcome_subject_template $welcome_mimetype + $warning_template $warning_from $warning_subject $warning_mimetype + $warning_cc $smtpmachine $radius_password $radius_ip $dirhash @@ -17,9 +20,11 @@ use Carp; use Fcntl qw(:flock); use Date::Format; use Crypt::PasswdMD5 1.2; +use Data::Dumper; use FS::UID qw( datasrc ); use FS::Conf; use FS::Record qw( qsearch qsearchs fields dbh dbdef ); +use FS::Msgcat qw(gettext); use FS::svc_Common; use FS::cust_svc; use FS::part_svc; @@ -31,9 +36,9 @@ use FS::queue; use FS::radius_usergroup; use FS::export_svc; use FS::part_export; -use FS::Msgcat qw(gettext); use FS::svc_forward; use FS::svc_www; +use FS::cdr; @ISA = qw( FS::svc_Common ); @@ -67,6 +72,10 @@ $FS::UID::callback{'FS::svc_acct'} = sub { ) or warn "can't create welcome email template: $Text::Template::ERROR"; $welcome_from = $conf->config('welcome_email-from'); # || 'your-isp-is-dum' $welcome_subject = $conf->config('welcome_email-subject') || 'Welcome'; + $welcome_subject_template = new Text::Template ( + TYPE => 'STRING', + SOURCE => $welcome_subject, + ) or warn "can't create welcome email subject template: $Text::Template::ERROR"; $welcome_mimetype = $conf->config('welcome_email-mimetype') || 'text/plain'; } else { $welcome_template = ''; @@ -74,6 +83,22 @@ $FS::UID::callback{'FS::svc_acct'} = sub { $welcome_subject = ''; $welcome_mimetype = ''; } + if ( $conf->exists('warning_email') ) { + $warning_template = new Text::Template ( + TYPE => 'ARRAY', + SOURCE => [ map "$_\n", $conf->config('warning_email') ] + ) or warn "can't create warning email template: $Text::Template::ERROR"; + $warning_from = $conf->config('warning_email-from'); # || 'your-isp-is-dum' + $warning_subject = $conf->config('warning_email-subject') || 'Warning'; + $warning_mimetype = $conf->config('warning_email-mimetype') || 'text/plain'; + $warning_cc = $conf->config('warning_email-cc'); + } else { + $warning_template = ''; + $warning_from = ''; + $warning_subject = ''; + $warning_mimetype = ''; + $warning_cc = ''; + } $smtpmachine = $conf->config('smtpmachine'); $radius_password = $conf->config('radius-password') || 'Password'; $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address'; @@ -166,6 +191,12 @@ FS::svc_Common. The following fields are currently supported: =item seconds - +=item upbytes - + +=item downbytes - + +=item totalbytes - + =item domsvc - svcnum from svc_domain =item radius_I<Radius_Attribute> - I<Radius-Attribute> (reply) @@ -184,8 +215,148 @@ Creates a new account. To add the account to the database, see L<"insert">. =cut +sub table_info { + { + 'name' => 'Account', + 'longname_plural' => 'Access accounts and mailboxes', + 'sorts' => [ 'username', 'uid', ], + 'display_weight' => 10, + 'cancel_weight' => 50, + 'fields' => { + 'dir' => 'Home directory', + 'uid' => { + label => 'UID', + def_label => 'UID (set to fixed and blank for no UIDs)', + type => 'text', + }, + 'slipip' => 'IP address', + # 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, + 'popnum' => { + label => 'Access number', + type => 'select', + select_table => 'svc_acct_pop', + select_key => 'popnum', + select_label => 'city', + disable_select => 1, + }, + 'username' => { + label => 'Username', + type => 'text', + disable_default => 1, + disable_fixed => 1, + disable_select => 1, + }, + 'quota' => { + label => 'Quota', + type => 'text', + disable_inventory => 1, + disable_select => 1, + }, + '_password' => 'Password', + 'gid' => { + label => 'GID', + def_label => 'GID (when blank, defaults to UID)', + type => 'text', + }, + 'shell' => { + #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)', + label => 'Shell', + def_label=> 'Shell (set to blank for no shell tracking)', + type =>'select', + select_list => [ $conf->config('shells') ], + disable_inventory => 1, + disable_select => 1, + }, + 'finger' => 'Real name (GECOS)', + 'domsvc' => { + label => 'Domain', + #def_label => 'svcnum from svc_domain', + type => 'select', + select_table => 'svc_domain', + select_key => 'svcnum', + select_label => 'domain', + disable_inventory => 1, + + }, + 'usergroup' => { + label => 'RADIUS groups', + type => 'radius_usergroup_selector', + disable_inventory => 1, + disable_select => 1, + }, + 'seconds' => { label => 'Seconds', + type => 'text', + disable_inventory => 1, + disable_select => 1, + }, + }, + }; +} + sub table { 'svc_acct'; } +sub _fieldhandlers { + { + #false laziness with edit/svc_acct.cgi + 'usergroup' => sub { + my( $self, $groups ) = @_; + if ( ref($groups) eq 'ARRAY' ) { + $groups; + } elsif ( length($groups) ) { + [ split(/\s*,\s*/, $groups) ]; + } else { + []; + } + }, + }; +} + +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + if ( $string =~ /^([^@]+)@([^@]+)$/ ) { + my( $username, $domain ) = ( $1, $2 ); + my $q_username = dbh->quote($username); + my @svc_domain = qsearch('svc_domain', { 'domain' => $domain } ); + if ( @svc_domain ) { + "svc_acct.username = $q_username AND ( ". + join( ' OR ', map { "svc_acct.domsvc = ". $_->svcnum; } @svc_domain ). + " )"; + } else { + '1 = 0'; #false + } + } elsif ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) { + ' ( '. + $class->search_sql_field('slipip', $string ). + ' OR '. + $class->search_sql_field('username', $string ). + ' ) '; + } else { + $class->search_sql_field('username', $string); + } +} + +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns the "username@domain" string for this account. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + $self->email(@_); +} + +=cut + =item insert [ , OPTION => VALUE ... ] Adds this account to the database. If there is an error, returns the error, @@ -220,7 +391,11 @@ jobnum(s) (they will not run until the specific job(s) complete(s)). sub insert { my $self = shift; my %options = @_; - my $error; + + if ( $DEBUG ) { + warn "[$me] insert called on $self: ". Dumper($self). + "\nwith options: ". Dumper(%options); + } local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -233,7 +408,7 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->check; + my $error = $self->check; return $error if $error; if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) { @@ -290,7 +465,10 @@ sub insert { if ( $cust_pkg ) { my $cust_main = $cust_pkg->cust_main; - if ( $conf->exists('emailinvoiceauto') ) { + if ( $conf->exists('emailinvoiceautoalways') + || $conf->exists('emailinvoiceauto') + && ! $cust_main->invoicing_list_emailonly + ) { my @invoicing_list = $cust_main->invoicing_list; push @invoicing_list, $self->email; $cust_main->invoicing_list(\@invoicing_list); @@ -301,6 +479,15 @@ sub insert { if ( $welcome_template && $cust_pkg ) { my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ); if ( $to ) { + + my %hash = ( + 'custnum' => $self->custnum, + 'username' => $self->username, + 'password' => $self->_password, + 'first' => $cust_main->first, + 'last' => $cust_main->getfield('last'), + 'pkg' => $cust_pkg->part_pkg->pkg, + ); my $wqueue = new FS::queue { 'svcnum' => $self->svcnum, 'job' => 'FS::svc_acct::send_email' @@ -308,16 +495,9 @@ sub insert { my $error = $wqueue->insert( 'to' => $to, 'from' => $welcome_from, - 'subject' => $welcome_subject, + 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ), 'mimetype' => $welcome_mimetype, - 'body' => $welcome_template->fill_in( HASH => { - 'custnum' => $self->custnum, - 'username' => $self->username, - 'password' => $self->_password, - 'first' => $cust_main->first, - 'last' => $cust_main->getfield('last'), - 'pkg' => $cust_pkg->part_pkg->pkg, - } ), + 'body' => $welcome_template->fill_in( HASH => \%hash, ), ); if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -462,6 +642,11 @@ sub replace { my $error; warn "$me replacing $old with $new\n" if $DEBUG; + # We absolutely have to have an old vs. new record to make this work. + if (!defined($old)) { + $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } ); + } + return "can't modify system account" if $old->_check_system; { @@ -681,7 +866,7 @@ sub check { my($recref) = $self->hashref; - my $x = $self->setfixed; + my $x = $self->setfixed( $self->_fieldhandlers ); return $x unless ref($x); my $part_svc = $x; @@ -694,6 +879,10 @@ sub check { #|| $self->ut_number('domsvc') || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum' ) || $self->ut_textn('sec_phrase') + || $self->ut_snumbern('seconds') + || $self->ut_snumbern('upbytes') + || $self->ut_snumbern('downbytes') + || $self->ut_snumbern('totalbytes') ; return $error if $error; @@ -843,7 +1032,7 @@ sub check { unless ( $recref->{_password} ); #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) { - if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { + if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { $recref->{_password} = $1.$3; #uncomment this to encrypt password immediately upon entry, or run #bin/crypt_pw in cron to give new users a window during which their @@ -852,7 +1041,7 @@ sub check { #$recref->{password} = $1. # crypt($3,$saltset[int(rand(64))].$saltset[int(rand(64))] #; - } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([\w\.\/\$\;\+]{13,60})$/ ) { + } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) { $recref->{_password} = $1.$3; } elsif ( $recref->{_password} eq '*' ) { $recref->{_password} = '*'; @@ -901,6 +1090,9 @@ per export and with identical I<svcpart> values. sub _check_duplicate { my $self = shift; + my $global_unique = $conf->config('global_unique-username') || 'none'; + return '' if $global_unique eq 'disabled'; + #this is Pg-specific. what to do for mysql etc? # ( mysql LOCK TABLES certainly isn't equivalent or useful here :/ ) warn "$me locking svc_acct table for duplicate search" if $DEBUG; @@ -913,8 +1105,6 @@ sub _check_duplicate { return 'unknown svcpart '. $self->svcpart; } - my $global_unique = $conf->config('global_unique-username') || 'none'; - my @dup_user = grep { !$self->svcnum || $_->svcnum != $self->svcnum } qsearch( 'svc_acct', { 'username' => $self->username } ); return gettext('username_in_use') @@ -1077,7 +1267,10 @@ sub radius_check { my $password = $self->_password; my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password'; $check{$pw_attrib} = $password; - my $cust_pkg = $self->cust_svc->cust_pkg; + my $cust_svc = $self->cust_svc; + die "FATAL: no cust_svc record for svc_acct.svcnum ". $self->svcnum. "\n" + unless $cust_svc; + my $cust_pkg = $cust_svc->cust_pkg; if ( $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill ) { $check{'Expiration'} = time2str('%B %e %Y %T', $cust_pkg->bill ); #http://lists.cistron.nl/pipermail/freeradius-users/2005-January/040184.html } @@ -1121,10 +1314,13 @@ sub forget_snapshot { } -=item domain +=item domain [ END_TIMESTAMP [ START_TIMESTAMP ] ] Returns the domain associated with this account. +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + =cut sub domain { @@ -1142,6 +1338,8 @@ L<FS::svc_domain>). =cut +# FS::h_svc_acct has a history-aware svc_domain override + sub svc_domain { my $self = shift; $self->{'_domsvc'} @@ -1157,10 +1355,13 @@ Returns the FS::cust_svc record for this account (see L<FS::cust_svc>). #inherited from svc_Common -=item email +=item email [ END_TIMESTAMP [ START_TIMESTAMP ] ] Returns an email address associated with the account. +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + =cut sub email { @@ -1184,6 +1385,72 @@ sub acct_snarf { qsearch('acct_snarf', { 'svcnum' => $self->svcnum } ); } +=item decrement_upbytes OCTETS + +Decrements the I<upbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub decrement_upbytes { + shift->_op_usage('-', 'upbytes', @_); +} + +=item increment_upbytes OCTETS + +Increments the I<upbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub increment_upbytes { + shift->_op_usage('+', 'upbytes', @_); +} + +=item decrement_downbytes OCTETS + +Decrements the I<downbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub decrement_downbytes { + shift->_op_usage('-', 'downbytes', @_); +} + +=item increment_downbytes OCTETS + +Increments the I<downbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub increment_downbytes { + shift->_op_usage('+', 'downbytes', @_); +} + +=item decrement_totalbytes OCTETS + +Decrements the I<totalbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub decrement_totalbytes { + shift->_op_usage('-', 'totalbytes', @_); +} + +=item increment_totalbytes OCTETS + +Increments the I<totalbytes> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub increment_totalbytes { + shift->_op_usage('+', 'totalbytes', @_); +} + =item decrement_seconds SECONDS Decrements the I<seconds> field of this record by the given amount. If there @@ -1192,7 +1459,7 @@ is an error, returns the error, otherwise returns false. =cut sub decrement_seconds { - shift->_op_seconds('-', @_); + shift->_op_usage('-', 'seconds', @_); } =item increment_seconds SECONDS @@ -1203,7 +1470,7 @@ is an error, returns the error, otherwise returns false. =cut sub increment_seconds { - shift->_op_seconds('+', @_); + shift->_op_usage('+', 'seconds', @_); } @@ -1212,20 +1479,32 @@ my %op2action = ( '+' => 'unsuspend', ); my %op2condition = ( - '-' => sub { my($self, $seconds) = @_; - $self->seconds - $seconds <= 0; + '-' => sub { my($self, $column, $amount) = @_; + $self->$column - $amount <= 0; + }, + '+' => sub { my($self, $column, $amount) = @_; + $self->$column + $amount > 0; + }, +); +my %op2warncondition = ( + '-' => sub { my($self, $column, $amount) = @_; + my $threshold = $column . '_threshold'; + $self->$column - $amount <= $self->$threshold + 0; }, - '+' => sub { my($self, $seconds) = @_; - $self->seconds + $seconds > 0; + '+' => sub { my($self, $column, $amount) = @_; + $self->$column + $amount > 0; }, ); -sub _op_seconds { - my( $self, $op, $seconds ) = @_; - warn "$me _op_seconds called for svcnum ". $self->svcnum. - ' ('. $self->email. "): $op $seconds\n" +sub _op_usage { + my( $self, $op, $column, $amount ) = @_; + + warn "$me _op_usage called for $column on svcnum ". $self->svcnum. + ' ('. $self->email. "): $op $amount\n" if $DEBUG; + return '' unless $amount; + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -1237,24 +1516,46 @@ sub _op_seconds { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $sql = "UPDATE svc_acct SET seconds = ". - " CASE WHEN seconds IS NULL THEN 0 ELSE seconds END ". #$seconds||0 + my $sql = "UPDATE svc_acct SET $column = ". + " CASE WHEN $column IS NULL THEN 0 ELSE $column END ". #$column||0 " $op ? WHERE svcnum = ?"; warn "$me $sql\n" if $DEBUG; my $sth = $dbh->prepare( $sql ) or die "Error preparing $sql: ". $dbh->errstr; - my $rv = $sth->execute($seconds, $self->svcnum); + my $rv = $sth->execute($amount, $self->svcnum); die "Error executing $sql: ". $sth->errstr unless defined($rv); - die "Can't update seconds for svcnum". $self->svcnum + die "Can't update $column for svcnum". $self->svcnum if $rv == 0; my $action = $op2action{$op}; + if ( &{$op2condition{$op}}($self, $column, $amount) ) { + foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { + if ($part_export->option('overlimit_groups')) { + my ($new,$old); + my $other = new FS::svc_acct $self->hashref; + my $groups = &{ $self->_fieldhandlers->{'usergroup'} } + ($self, $part_export->option('overlimit_groups')); + $other->usergroup( $groups ); + if ($action eq 'suspend'){ + $new = $other; $old = $self; + }else{ + $new = $self; $old = $other; + } + my $error = $part_export->export_replace($new, $old); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error replacing radius groups in export, ${op}: $error"; + } + } + } + } + if ( $conf->exists("svc_acct-usage_$action") - && &{$op2condition{$op}}($self, $seconds) ) { + && &{$op2condition{$op}}($self, $column, $amount) ) { #my $error = $self->$action(); my $error = $self->cust_svc->cust_pkg->$action(); if ( $error ) { @@ -1263,13 +1564,134 @@ sub _op_seconds { } } - warn "$me update sucessful; committing\n" + if ($warning_template && &{$op2warncondition{$op}}($self, $column, $amount)) { + my $wqueue = new FS::queue { + 'svcnum' => $self->svcnum, + 'job' => 'FS::svc_acct::reached_threshold', + }; + + my $to = ''; + if ($op eq '-'){ + $to = $warning_cc if &{$op2condition{$op}}($self, $column, $amount); + } + + # x_threshold race + my $error = $wqueue->insert( + 'svcnum' => $self->svcnum, + 'op' => $op, + 'column' => $column, + 'to' => $to, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error queuing threshold activity: $error"; + } + } + + warn "$me update successful; committing\n" if $DEBUG; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } +sub set_usage { + my( $self, $valueref ) = @_; + + warn "$me set_usage called for svcnum ". $self->svcnum. + ' ('. $self->email. "): ". + join(', ', map { "$_ => " . $valueref->{$_}} keys %$valueref) . "\n" + if $DEBUG; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + local $FS::svc_Common::noexport_hack = 1; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $reset = 0; + foreach my $field (keys %$valueref){ + $reset = 1 if $valueref->{$field}; + $self->setfield($field, $valueref->{$field}); + $self->setfield( $field.'_threshold', + int($self->getfield($field) + * ( $conf->exists('svc_acct-usage_threshold') + ? 1 - $conf->config('svc_acct-usage_threshold')/100 + : 0.20 + ) + ) + ); + } + my $error = $self->replace; + die $error if $error; + + if ( $conf->exists("svc_acct-usage_unsuspend") && $reset ) { + my $error = $self->cust_svc->cust_pkg->unsuspend; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error unsuspending: $error"; + } + } + + warn "$me update successful; committing\n" + if $DEBUG; + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + + +=item recharge HASHREF + + Increments usage columns by the amount specified in HASHREF as + column=>amount pairs. + +=cut + +sub recharge { + my ($self, $vhash) = @_; + + if ( $DEBUG ) { + warn "[$me] recharge called on $self: ". Dumper($self). + "\nwith vhash: ". Dumper($vhash); + } + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + my $error = ''; + + foreach my $column (keys %$vhash){ + $error ||= $self->_op_usage('+', $column, $vhash->{$column}); + } + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + }else{ + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + } + return $error; +} + +=item is_rechargeable + +Returns true if this svc_account can be "recharged" and false otherwise. + +=cut + +sub is_rechargable { + my $self = shift; + $self->seconds ne '' + || $self->upbytes ne '' + || $self->downbytes ne '' + || $self->totalbytes ne ''; +} =item seconds_since TIMESTAMP @@ -1341,6 +1763,67 @@ sub get_session_history { $self->cust_svc->get_session_history(@_); } +=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ] + +=cut + +sub get_cdrs { + my($self, $start, $end, %opt ) = @_; + + my $did = $self->username; #yup + + my $prefix = $opt{'default_prefix'}; #convergent.au '+61' + + my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : ''; + + #SELECT $for_update * FROM cdr + # WHERE calldate >= $start #need a conversion + # AND calldate < $end #ditto + # AND ( charged_party = "$did" + # OR charged_party = "$prefix$did" #if length($prefix); + # OR ( ( charged_party IS NULL OR charged_party = '' ) + # AND + # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix) + # ) + # ) + # AND ( freesidestatus IS NULL OR freesidestatus = '' ) + + my $charged_or_src; + if ( length($prefix) ) { + $charged_or_src = + " AND ( charged_party = '$did' + OR charged_party = '$prefix$did' + OR ( ( charged_party IS NULL OR charged_party = '' ) + AND + ( src = '$did' OR src = '$prefix$did' ) + ) + ) + "; + } else { + $charged_or_src = + " AND ( charged_party = '$did' + OR ( ( charged_party IS NULL OR charged_party = '' ) + AND + src = '$did' + ) + ) + "; + + } + + qsearch( + 'select' => "$for_update *", + 'table' => 'cdr', + 'hashref' => { + #( freesidestatus IS NULL OR freesidestatus = '' ) + 'freesidestatus' => '', + }, + 'extra_sql' => $charged_or_src, + + ); + +} + =item radius_groups Returns all RADIUS groups for this account (see L<FS::radius_usergroup>). @@ -1350,6 +1833,8 @@ Returns all RADIUS groups for this account (see L<FS::radius_usergroup>). sub radius_groups { my $self = shift; if ( $self->usergroup ) { + confess "explicitly specified usergroup not an arrayref: ". $self->usergroup + unless ref($self->usergroup) eq 'ARRAY'; #when provisioning records, export callback runs in svc_Common.pm before #radius_usergroup records can be inserted... @{$self->usergroup}; @@ -1390,7 +1875,7 @@ sub clone_kludge_unsuspend { =item check_password Checks the supplied password against the (possibly encrypted) password in the -database. Returns true for a sucessful authentication, false for no match. +database. Returns true for a successful authentication, false for no match. Currently supported encryptions are: classic DES crypt() and MD5 @@ -1457,13 +1942,67 @@ sub crypt_password { } elsif ( $encryption eq 'md5' ) { unix_md5_crypt( $self->_password ); } elsif ( $encryption eq 'blowfish' ) { - die "unknown encryption method $encryption"; + croak "unknown encryption method $encryption"; } else { - die "unknown encryption method $encryption"; + croak "unknown encryption method $encryption"; } } } +=item ldap_password [ DEFAULT_ENCRYPTION_TYPE ] + +Returns an encrypted password in "LDAP" format, with a curly-bracked prefix +describing the format, for example, "{CRYPT}94pAVyK/4oIBk" or +"{PLAIN-MD5}5426824942db4253f87a1009fd5d2d4f". + +The optional DEFAULT_ENCRYPTION_TYPE is not yet used, but the idea is for it +to work the same as the B</crypt_password> method. + +=cut + +sub ldap_password { + my $self = shift; + #eventually should check a "password-encoding" field + if ( length($self->_password) == 13 ) { #crypt + return '{CRYPT}'. $self->_password; + } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5 + return '{MD5}'. $1; + } elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish + die "Blowfish encryption not supported in this context, svcnum ". + $self->svcnum. "\n"; + } elsif ( $self->_password =~ /^(\w{48})$/ ) { #LDAP SSHA + return '{SSHA}'. $1; + } elsif ( $self->_password =~ /^(\w{64})$/ ) { #LDAP NS-MTA-MD5 + return '{NS-MTA-MD5}'. $1; + } else { #plaintext + return '{PLAIN}'. $self->_password; + #my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; + #if ( $encryption eq 'crypt' ) { + # return '{CRYPT}'. crypt( + # $self->_password, + # $saltset[int(rand(64))].$saltset[int(rand(64))] + # ); + #} elsif ( $encryption eq 'md5' ) { + # unix_md5_crypt( $self->_password ); + #} elsif ( $encryption eq 'blowfish' ) { + # croak "unknown encryption method $encryption"; + #} else { + # croak "unknown encryption method $encryption"; + #} + } +} + +=item domain_slash_username + +Returns $domain/$username/ + +=cut + +sub domain_slash_username { + my $self = shift; + $self->domain. '/'. $self->username. '/'; +} + =item virtual_maildir Returns $domain/maildirs/$username/ @@ -1637,6 +2176,82 @@ END $html; } +=item reached_threshold + +Performs some activities when svc_acct thresholds (such as number of seconds +remaining) are reached. + +=cut + +sub reached_threshold { + my %opt = @_; + + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $opt{'svcnum'} } ); + die "Cannot find svc_acct with svcnum " . $opt{'svcnum'} unless $svc_acct; + + if ( $opt{'op'} eq '+' ){ + $svc_acct->setfield( $opt{'column'}.'_threshold', + int($svc_acct->getfield($opt{'column'}) + * ( $conf->exists('svc_acct-usage_threshold') + ? $conf->config('svc_acct-usage_threshold')/100 + : 0.80 + ) + ) + ); + my $error = $svc_acct->replace; + die $error if $error; + }elsif ( $opt{'op'} eq '-' ){ + + my $threshold = $svc_acct->getfield( $opt{'column'}.'_threshold' ); + return '' if ($threshold eq '' ); + + $svc_acct->setfield( $opt{'column'}.'_threshold', 0 ); + my $error = $svc_acct->replace; + die $error if $error; # email next time, i guess + + if ( $warning_template ) { + eval "use FS::Misc qw(send_email)"; + die $@ if $@; + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + my $cust_main = $cust_pkg->cust_main; + + my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } + $cust_main->invoicing_list, + $svc_acct->email, + ($opt{'to'} ? $opt{'to'} : ()) + ); + + my $mimetype = $warning_mimetype; + $mimetype .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/; + + my $body = $warning_template->fill_in( HASH => { + 'custnum' => $cust_main->custnum, + 'username' => $svc_acct->username, + 'password' => $svc_acct->_password, + 'first' => $cust_main->first, + 'last' => $cust_main->getfield('last'), + 'pkg' => $cust_pkg->part_pkg->pkg, + 'column' => $opt{'column'}, + 'amount' => $svc_acct->getfield($opt{'column'}), + 'threshold' => $threshold, + } ); + + + my $error = send_email( + 'from' => $warning_from, + 'to' => $to, + 'subject' => $warning_subject, + 'content-type' => $mimetype, + 'body' => [ map "$_\n", split("\n", $body) ], + ); + die $error if $error; + } + }else{ + die "unknown op: " . $opt{'op'}; + } +} + =back =head1 BUGS diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index aaac891e6..ab97ac82c 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -85,8 +85,51 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'Broadband', + 'name_plural' => 'Broadband services', + 'longname_plural' => 'Fixed (username-less) broadband services', + 'display_weight' => 50, + 'cancel_weight' => 70, + 'fields' => { + 'description' => 'Descriptive label for this particular device.', + 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', + 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', + 'ip_addr' => 'IP address. Leave blank for automatic assignment.', + 'blocknum' => 'Address block.', + }, + }; +} + sub table { 'svc_broadband'; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) { + $class->search_sql_field('ip_addr', $string ); + } else { + '1 = 0'; #false + } +} + +=item label + +Returns the IP address. + +=cut + +sub label { + my $self = shift; + $self->ip_addr; +} + =item insert [ , OPTION => VALUE ... ] Adds this record to the database. If there is an error, returns the error, @@ -151,15 +194,29 @@ sub check { my $error = $self->ut_numbern('svcnum') || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum') + || $self->ut_textn('description') || $self->ut_number('speed_up') || $self->ut_number('speed_down') || $self->ut_ipn('ip_addr') + || $self->ut_hexn('mac_addr') + || $self->ut_hexn('auth_key') + || $self->ut_floatn('latitude') + || $self->ut_floatn('longitude') + || $self->ut_floatn('altitude') + || $self->ut_textn('vlan_profile') ; return $error if $error; if($self->speed_up < 0) { return 'speed_up must be positive'; } if($self->speed_down < 0) { return 'speed_down must be positive'; } + if($self->latitude < -90 || $self->latitude > 90) { + return 'latitude must be between -90 and 90'; + } + if($self->longitude < -180 || $self->longitude > 180) { + return 'longitude must be between -180 and 180'; + } + if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') { my $next_addr = $self->addr_block->next_free_addr; if ($next_addr) { diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 6d5435718..529127158 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -11,6 +11,7 @@ use Date::Format; use FS::Record qw(fields qsearch qsearchs dbh); use FS::Conf; use FS::svc_Common; +use FS::svc_Parent_Mixin; use FS::cust_svc; use FS::svc_acct; use FS::cust_pkg; @@ -18,7 +19,7 @@ use FS::cust_main; use FS::domain_record; use FS::queue; -@ISA = qw( FS::svc_Common ); +@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common ); #ask FS::UID to run this stuff for us later $FS::UID::callback{'FS::domain'} = sub { @@ -72,6 +73,20 @@ FS::svc_Common. The following fields are currently supported: =item catchall - optional svcnum of an svc_acct record, designating an email catchall account. +=item suffix - + +=item parent_svcnum - + +=item registrarnum - Registrar (see L<FS::registrar>) + +=item registrarkey - Registrar key or password for this domain + +=item setup_date - UNIX timestamp + +=item renewal_interval - Number of days before expiration date to start renewal + +=item expiration_date - UNIX timestamp + =back =head1 METHODS @@ -84,8 +99,37 @@ Creates a new domain. To add the domain to the database, see L<"insert">. =cut +sub table_info { + { + 'name' => 'Domain', + 'sorts' => 'domain', + 'display_weight' => 20, + 'cancel_weight' => 60, + 'fields' => { + 'domain' => 'Domain', + }, + }; +} + sub table { 'svc_domain'; } +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('domain', $string); +} + + +=item label + +Returns the domain. + +=cut + +sub label { + my $self = shift; + $self->domain; +} + =item insert [ , OPTION => VALUE ... ] Adds this domain to the database. If there is an error, returns the error, @@ -141,15 +185,6 @@ sub insert { return "Domain in use (here)" if qsearchs( 'svc_domain', { 'domain' => $self->domain } ); - my $whois = $self->whois; - if ( $self->action eq "N" && ! $whois_hack && $whois ) { - $dbh->rollback if $oldAutoCommit; - return "Domain in use (see whois)"; - } - if ( $self->action eq "M" && ! $whois ) { - $dbh->rollback if $oldAutoCommit; - return "Domain not found (see whois)"; - } $error = $self->SUPER::insert(@_); if ( $error ) { @@ -157,8 +192,6 @@ sub insert { return $error; } - $self->submit_internic unless $whois_hack; - if ( $soamachine ) { my $soa = new FS::domain_record { 'svcnum' => $self->svcnum, @@ -230,7 +263,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"; } } @@ -253,6 +290,9 @@ returns the error, otherwise returns false. sub replace { my ( $new, $old ) = ( shift, shift ); + # We absolutely have to have an old vs. new record to make this work. + $old = $new->replace_old unless defined($old); + return "Can't change domain - reorder." if $old->getfield('domain') ne $new->getfield('domain'); @@ -313,45 +353,32 @@ sub check { my($recref) = $self->hashref; - unless ( $whois_hack ) { - unless ( $self->email ) { #find out an email address - my @svc_acct; - foreach ( qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ) ) { - my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $_->svcnum } ); - push @svc_acct, $svc_acct if $svc_acct; - } - - if ( scalar(@svc_acct) == 0 ) { - return "Must order an account in package ". $pkgnum. " first"; - } elsif ( scalar(@svc_acct) > 1 ) { - return "More than one account in package ". $pkgnum. ": specify admin contact email"; - } else { - $self->email($svc_acct[0]->email ); - } - } - } - #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) { - if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu)$/ ) { + if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu|tv|info|biz)$/ ) { $recref->{domain} = "$1.$2"; + $recref->{suffix} ||= $2; # hmmmmmmmm. - } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)$/ ) { - $recref->{domain} = $1; + } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)\.(\w+)$/ ) { + $recref->{domain} = "$1.$2"; + # need to match a list of suffixes - no guarantee they're top-level.. } else { return "Illegal domain ". $recref->{domain}. " (or unknown registry - try \$whois_hack)"; } - $recref->{action} =~ /^(M|N)$/ - or return "Illegal action: ". $recref->{action}; - $recref->{action} = $1; if ( $recref->{catchall} ne '' ) { my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } ); return "Unknown catchall" unless $svc_acct; } - $self->ut_textn('purpose') + $self->ut_alphan('suffix') + or $self->ut_foreign_keyn('registrarnum', 'registrar', 'registrarnum') + or $self->ut_textn('registrarkey') + or $self->ut_numbern('setup_date') + or $self->ut_numbern('renewal_interval') + or $self->ut_numbern('expiration_date') + or $self->ut_textn('purpose') or $self->SUPER::check; } @@ -364,16 +391,34 @@ sub domain_record { my $self = shift; my %order = ( - SOA => 1, - NS => 2, - MX => 3, - CNAME => 4, - A => 5, - TXT => 6, + 'SOA' => 1, + 'NS' => 2, + 'MX' => 3, + 'CNAME' => 4, + 'A' => 5, + 'TXT' => 6, + 'PTR' => 7, + ); + + my %sort = ( + #'SOA' => sub { $_[0]->recdata cmp $_[1]->recdata }, #sure hope not though +# 'SOA' => sub { 0; }, +# 'NS' => sub { 0; }, + 'MX' => sub { my( $a_weight, $a_name ) = split(/\s+/, $_[0]->recdata); + my( $b_weight, $b_name ) = split(/\s+/, $_[1]->recdata); + $a_weight <=> $b_weight or $a_name cmp $b_name; + }, + 'CNAME' => sub { $_[0]->reczone cmp $_[1]->reczone }, + 'A' => sub { $_[0]->reczone cmp $_[1]->reczone }, + +# 'TXT' => sub { 0; }, + 'PTR' => sub { $_[0]->reczone <=> $_[1]->reczone }, ); - sort { $order{$a->rectype} <=> $order{$b->rectype} } - qsearch('domain_record', { svcnum => $self->svcnum } ); + sort { $order{$a->rectype} <=> $order{$b->rectype} + or &{ $sort{$a->rectype} || sub { 0; } }($a, $b) + } + qsearch('domain_record', { svcnum => $self->svcnum } ); } @@ -397,7 +442,7 @@ sub catchall_svc_acct { sub whois { #$whois_hack or new Net::Whois::Domain $_[0]->domain; - $whois_hack or die "whois_hack not set...\n"; + #$whois_hack or die "whois_hack not set...\n"; } =item _whois diff --git a/FS/FS/svc_external.pm b/FS/FS/svc_external.pm index 79eec97c4..5aaee4872 100644 --- a/FS/FS/svc_external.pm +++ b/FS/FS/svc_external.pm @@ -1,16 +1,11 @@ package FS::svc_external; use strict; -use vars qw(@ISA); # $conf -use FS::UID; -#use FS::Record qw( qsearch qsearchs dbh); -use FS::svc_Common; +use vars qw(@ISA); +use FS::Conf; +use FS::svc_External_Common; -@ISA = qw( FS::svc_Common ); - -#FS::UID::install_callback( sub { -# $conf = new FS::Conf; -#}; +@ISA = qw( FS::svc_External_Common ); =head1 NAME @@ -39,9 +34,9 @@ FS::svc_external - Object methods for svc_external records =head1 DESCRIPTION -An FS::svc_external object represents a externally tracked service. -FS::svc_external inherits from FS::svc_Common. The following fields are -currently supported: +An FS::svc_external object represents a generic externally tracked service. +FS::svc_external inherits from FS::svc_External_Common (and FS::svc_Common). +The following fields are currently supported: =over 4 @@ -67,8 +62,31 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'External service', + 'sorts' => 'id', + 'display_weight' => 90, + 'cancel_weight' => 10, + 'fields' => { + }, + }; +} + sub table { 'svc_external'; } +# oh! this should be moved to svc_artera_turbo or something now +sub label { + my $self = shift; + my $conf = new FS::Conf; + if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) { + sprintf('%010d', $self->id). '-'. + substr('0000000000'.uc($self->title), -10); + } else { + $self->SUPER::label; + } +} + =item insert [ , OPTION => VALUE ... ] Adds this external service to the database. If there is an error, returns the @@ -145,25 +163,19 @@ Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). Checks all fields to make sure this is a valid external service. If there is an error, returns the error, otherwise returns false. Called by the insert -and repalce methods. +and replace methods. =cut -sub check { - my $self = shift; - - my $x = $self->setfixed; - return $x unless ref($x); - my $part_svc = $x; - - my $error = - $self->ut_numbern('svcnum') - || $self->ut_numbern('id') - || $self->ut_textn('title') - ; - - $self->SUPER::check; -} +#sub check { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::delete; +# return $error if $error; +# +# ''; +#} =back @@ -171,8 +183,8 @@ sub check { =head1 SEE ALSO -L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, -L<FS::cust_pkg>, schema.html from the base documentation. +L<FS::svc_External_Common>, L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, +L<FS::part_svc>, L<FS::cust_pkg>, schema.html from the base documentation. =cut diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm index 12b556f33..91e251fa0 100644 --- a/FS/FS/svc_forward.pm +++ b/FS/FS/svc_forward.pm @@ -66,8 +66,67 @@ database, see L<"insert">. =cut + +sub table_info { + { + 'name' => 'Forward', + 'name_plural' => 'Mail forwards', + 'display_weight' => 30, + 'cancel_weight' => 30, + 'fields' => { + 'srcsvc' => 'service from which mail is to be forwarded', + 'dstsvc' => 'service to which mail is to be forwarded', + 'dst' => 'someone@another.domain.com to use when dstsvc is 0', + }, + }; +} + sub table { 'svc_forward'; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + $class->search_sql_field('src', $string); +} + +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns a text string representing this forward. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + my $tag = ''; + + if ( $self->srcsvc ) { + my $svc_acct = $self->srcsvc_acct(@_); + $tag = $svc_acct->email(@_); + } else { + $tag = $self->src; + } + + $tag .= ' -> '; + + if ( $self->dstsvc ) { + my $svc_acct = $self->dstsvc_acct(@_); + $tag .= $svc_acct->email(@_); + } else { + $tag .= $self->dst; + } + + $tag; +} + + =item insert [ , OPTION => VALUE ... ] Adds this mail forwarding alias to the database. If there is an error, returns @@ -257,9 +316,15 @@ sub check { } if ( $self->dst ) { - $self->dst =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)$/ - or return "Illegal dst: ". $self->dst; - $self->dst("$1$2"); + my $conf = new FS::Conf; + if ( $conf->exists('svc_forward-arbitrary_dst') ) { + my $error = $self->ut_textn('dst'); + return $error if $error; + } else { + $self->dst =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)$/ + or return "Illegal dst: ". $self->dst; + $self->dst("$1$2"); + } } else { $self->dst(''); } diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm new file mode 100644 index 000000000..00ccc1958 --- /dev/null +++ b/FS/FS/svc_phone.pm @@ -0,0 +1,190 @@ +package FS::svc_phone; + +use strict; +use vars qw( @ISA ); +#use FS::Record qw( qsearch qsearchs ); +use FS::svc_Common; + +@ISA = qw( FS::svc_Common ); + +=head1 NAME + +FS::svc_phone - Object methods for svc_phone records + +=head1 SYNOPSIS + + use FS::svc_phone; + + $record = new FS::svc_phone \%hash; + $record = new FS::svc_phone { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + + $error = $record->suspend; + + $error = $record->unsuspend; + + $error = $record->cancel; + +=head1 DESCRIPTION + +An FS::svc_phone object represents a phone number. FS::svc_phone inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item svcnum - primary key + +=item countrycode - + +=item phonenum - + +=item pin - + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new phone number. To add the number 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined +# +sub table_info { + { + 'name' => 'Phone number', + 'sorts' => 'phonenum', + 'display_weight' => 60, + 'cancel_weight' => 80, + 'fields' => { + 'countrycode' => { label => 'Country code', + type => 'text', + disable_inventory => 1, + disable_select => 1, + }, + 'phonenum' => 'Phone number', + 'pin' => { label => 'Personal Identification Number', + type => 'text', + disable_inventory => 1, + disable_select => 1, + }, + }, + }; +} + +sub table { 'svc_phone'; } + +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + $class->search_sql_field('phonenum', $string ); +} + +=item label + +Returns the phone number. + +=cut + +sub label { + my $self = shift; + $self->phonenum; #XXX format it better +} + +=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 suspend + +Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item unsuspend + +Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item cancel + +Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item check + +Checks all fields to make sure this is a valid phone number. 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('svcnum') + || $self->ut_numbern('countrycode') + || $self->ut_number('phonenum') + || $self->ut_numbern('pin') + ; + return $error if $error; + + $self->countrycode(1) unless $self->countrycode; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, +L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm index 12d7e92f3..066719bbe 100644 --- a/FS/FS/svc_www.pm +++ b/FS/FS/svc_www.pm @@ -72,8 +72,33 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'Hosting', + 'name_plural' => 'Virtual hosting services', + 'display_weight' => 40, + 'cancel_weight' => 20, + 'fields' => { + }, + }; +}; + sub table { 'svc_www'; } +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns the zone name for this virtual host. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + $self->domain_record(@_)->zone; +} + =item insert [ , OPTION => VALUE ... ] Adds this record to the database. If there is an error, returns the error, @@ -190,7 +215,7 @@ Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). Checks all fields to make sure this is a valid web virtual host. If there is an error, returns the error, otherwise returns false. Called by the insert -and repalce methods. +and replace methods. =cut diff --git a/FS/MANIFEST b/FS/MANIFEST index e7d9dea34..82f106412 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -5,9 +5,9 @@ Makefile.PL README bin/freeside-addoutsource bin/freeside-addoutsourceuser +bin/freeside-addgroup bin/freeside-adduser bin/freeside-apply-credits -bin/freeside-bill bin/freeside-count-active-customers bin/freeside-daily bin/freeside-deloutsource @@ -25,6 +25,7 @@ bin/freeside-sqlradius-radacctd bin/freeside-sqlradius-reset bin/freeside-sqlradius-seconds FS.pm +FS/AccessRight.pm FS/CGI.pm FS/InitHandler.pm FS/ClientAPI.pm @@ -33,6 +34,9 @@ FS/ClientAPI/passwd.pm FS/ClientAPI/MyAccount.pm FS/Conf.pm FS/ConfItem.pm +FS/Cron/backup.pm +FS/Cron/bill.pm +FS/Cron/vacuum.pm FS/Daemon.pm FS/Misc.pm FS/Record.pm @@ -43,6 +47,7 @@ FS/SearchCache.pm FS/UI/Web.pm FS/UID.pm FS/Msgcat.pm +FS/Pony.pm FS/acct_snarf.pm FS/agent.pm FS/agent_type.pm @@ -65,7 +70,9 @@ FS/cust_refund.pm FS/cust_credit_refund.pm FS/cust_svc.pm FS/h_Common.pm +FS/h_cust_bill.pm FS/h_cust_svc.pm +FS/h_cust_tax_exempt.pm FS/h_domain_record.pm FS/h_svc_acct.pm FS/h_svc_broadband.pm @@ -74,6 +81,7 @@ FS/h_svc_external.pm FS/h_svc_forward.pm FS/h_svc_www.pm FS/part_bill_event.pm +FS/payinfo_Mixin.pm FS/export_svc.pm FS/part_export.pm FS/part_export_option.pm @@ -117,12 +125,15 @@ FS/part_pkg/sql_generic.pm FS/part_pkg/sqlradacct_hour.pm FS/part_pkg/subscription.pm FS/part_pkg/voip_sqlradacct.pm +FS/part_pkg/voip_cdr.pm FS/part_pop_local.pm FS/part_referral.pm FS/part_svc.pm FS/part_svc_column.pm FS/part_svc_router.pm FS/part_virtual_field.pm +FS/payby.pm +FS/pkg_class.pm FS/pkg_svc.pm FS/rate.pm FS/rate_detail.pm @@ -152,16 +163,21 @@ FS/queue_arg.pm FS/queue_depend.pm FS/msgcat.pm FS/cust_tax_exempt.pm +FS/cust_tax_exempt_pkg.pm FS/clientapi_session.pm FS/clientapi_session_field.pm t/agent.t t/agent_type.t +t/AccessRight.t t/CGI.t t/InitHandler.t t/ClientAPI.t t/ClientAPI_SessionCache.t t/Conf.t t/ConfItem.t +t/Cron-backup.t +t/Cron-bill.t +t/Cron-vacuum.t t/Daemon.t t/Misc.t t/Record.t @@ -189,7 +205,9 @@ t/cust_pay_refund.t t/cust_pkg.t t/cust_refund.t t/cust_svc.t +t/h_cust_bill.t t/h_cust_svc.t +t/h_cust_tax_exempt.t t/h_Common.t t/h_cust_svc.t t/h_domain_record.t @@ -200,6 +218,7 @@ t/h_svc_external.t t/h_svc_forward.t t/h_svc_www.t t/cust_tax_exempt.t +t/cust_tax_exempt_pkg.t t/domain_record.t t/nas.t t/part_bill_event.t @@ -248,10 +267,14 @@ t/part_pkg-sql_generic.t t/part_pkg-sqlradacct_hour.t t/part_pkg-subscription.t t/part_pkg-voip_sqlradacct.t +t/part_pkg-voip_cdr.t t/part_pop_local.t t/part_referral.t t/part_svc.t t/part_svc_column.t +t/payby.t +t/payinfo_Mixin.t +t/pkg_class.t t/pkg_svc.t t/port.t t/prepay_credit.t @@ -289,6 +312,62 @@ FS/agent_payment_gateway.pm t/agent_payment_gateway.t FS/banned_pay.pm t/banned_pay.t -FS/cancel_reason.pm -t/cancel_reason.t bin/freeside-prepaidd +FS/cdr.pm +t/cdr.t +FS/cdr_calltype.pm +t/cdr_calltype.t +FS/cdr_type.pm +t/cdr_type.t +FS/cdr_carrier.pm +t/cdr_carrier.t +FS/inventory_class.pm +t/inventory_class.t +FS/inventory_item.pm +t/inventory_item.t +FS/cdr_upstream_rate.pm +t/cdr_upstream_rate.t +FS/access_user.pm +t/access_user.t +FS/access_user_pref.pm +t/access_user_pref.t +FS/access_group.pm +t/access_group.t +FS/access_usergroup.pm +t/access_usergroup.t +FS/access_groupagent.pm +t/access_groupagent.t +FS/access_right.pm +t/access_right.t +FS/m2m_Common.pm +FS/pay_batch.pm +t/pay_batch.t +FS/ConfDefaults.pm +t/ConfDefaults.t +FS/m2name_Common.pm +FS/CurrentUser.pm +FS/svc_phone.pm +t/svc_phone.t +FS/h_svc_phone.pm +FS/cust_bill_pay_batch.pm +t/cust_bill_pay_batch.t +FS/cust_bill_pay_pkg.pm +t/cust_bill_pay_pkg.t +FS/cust_credit_bill_pkg.pm +t/cust_credit_bill_pkg.t +FS/registrar.pm +t/registrar.t +FS/svc_External_Common.pm +t/svc_External_Common.t +FS/svc_Parent_Mixin.pm +t/svc_Parent_Mixin.t +FS/cust_main_note.pm +t/cust_main_note.t +FS/cust_pkg_reason.pm +t/cust_pkg_reason.t +FS/reason.pm +t/reason.t +FS/reason_type.pm +t/reason_type.t +FS/cust_pkg_option.pm +t/cust_pkg_option.t diff --git a/FS/bin/freeside-addgroup b/FS/bin/freeside-addgroup new file mode 100755 index 000000000..7b30f7d95 --- /dev/null +++ b/FS/bin/freeside-addgroup @@ -0,0 +1,50 @@ +#!/usr/bin/perl + +use strict; +use vars qw($opt_s); +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch); +use FS::CurrentUser; +use FS::AccessRight; +use FS::access_group; +use FS::access_right; +use FS::access_groupagent; + +getopts("s"); +my $user = shift or die &usage; #just for adminsuidsetup +my $group = shift or die &usage; + +$FS::CurrentUser::upgrade_hack = 1; +#adminsuidsetup $rootuser; +adminsuidsetup $user; + +my $access_group = new FS::access_group { 'groupname' => $group }; +my $error = $access_group->insert; +die $error if $error; + +if ( $opt_s ) { + foreach my $rightname ( FS::AccessRight->rights ) { + my $access_right = new FS::access_right { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $access_group->groupnum, + 'rightname' => $rightname, + }; + my $ar_error = $access_right->insert; + die $ar_error if $ar_error; + } + + foreach my $agent ( qsearch('agent', {} ) ) { + my $access_groupagent = new FS::access_groupagent { + 'groupnum' => $access_group->groupnum, + 'agentnum' => $agent->agentnum, + }; + my $aga_error = $access_groupagent->insert; + die $aga_error if $aga_error; + } +} + +sub usage { + die "Usage:\n\n freeside-addgroup [ -s ] username groupname" +} + diff --git a/FS/bin/freeside-addoutsource b/FS/bin/freeside-addoutsource index db4e7a307..9cb12195a 100644 --- a/FS/bin/freeside-addoutsource +++ b/FS/bin/freeside-addoutsource @@ -2,23 +2,31 @@ domain=$1 +FREESIDE_CONF=%%%FREESIDE_CONF%%% +FREESIDE_CACHE=%%%FREESIDE_CACHE%%% +FREESIDE_EXPORT=%%%FREESIDE_EXPORT%%% + +#without this, [a-z]* matches CVS/, the copy doesn't return a sucessful error +# status, and the rest of the commands aren't run +export LANG=C + createdb $domain && \ \ -mkdir /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain && \ +mkdir $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \ \ -chown freeside /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain && \ +chown freeside $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \ \ -cp /home/ivan/freeside/conf/[a-z]* /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain && \ +cp /home/ivan/freeside/conf/[a-z]* $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \ \ -touch /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain/secrets && \ +touch $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \ \ -chown freeside /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain/secrets && \ +chown freeside $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \ \ -chmod 600 /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain/secrets && \ +chmod 600 $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \ \ -echo -e "DBI:Pg:dbname=$domain\nfreeside\n" >/usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain/secrets && \ +echo -e "DBI:Pg:dbname=$domain\nfreeside\n" >$FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \ \ -mkdir /usr/local/etc/freeside/counters.DBI:Pg:dbname=$domain && \ -mkdir /usr/local/etc/freeside/cache.DBI:Pg:dbname=$domain && \ -mkdir /usr/local/etc/freeside/export.DBI:Pg:dbname=$domain +mkdir $FREESIDE_CACHE/counters.DBI:Pg:dbname=$domain && \ +mkdir $FREESIDE_CACHE/cache.DBI:Pg:dbname=$domain && \ +mkdir $FREESIDE_EXPORT/export.DBI:Pg:dbname=$domain diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser index cad07f1fd..cbe792acc 100644 --- a/FS/bin/freeside-addoutsourceuser +++ b/FS/bin/freeside-addoutsourceuser @@ -3,13 +3,16 @@ username=$1 domain=$2 password=$3 +realdomain=$4 +FREESIDE_CONF=%%%FREESIDE_CONF%%% -freeside-adduser -h /usr/local/etc/freeside/htpasswd \ - -s conf.DBI:Pg:dbname=$domain/secrets \ - -b \ - $username $password 2>/dev/null +freeside-adduser -s conf.DBI:Pg:dbname=$domain/secrets \ + -n \ + $username #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 ) +[ -e $FREESIDE_CONF/dbdef.DBI:Pg:dbname=$domain ] \ + || ( freeside-setup -d $realdomain -u $username ) +freeside-adduser -g 1 $username + +htpasswd -b $FREESIDE_CONF/htpasswd $username $password diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser index c3ee05b9b..237e29ef8 100644 --- a/FS/bin/freeside-adduser +++ b/FS/bin/freeside-adduser @@ -1,37 +1,85 @@ #!/usr/bin/perl -w -# -# $Id: freeside-adduser,v 1.8 2002-09-27 05:36:29 ivan Exp $ use strict; -use vars qw($opt_h $opt_b $opt_c $opt_s); +use vars qw($opt_s $opt_g $opt_n); use Fcntl qw(:flock); use Getopt::Std; -my $FREESIDE_CONF = "/usr/local/etc/freeside"; +my $FREESIDE_CONF = "%%%FREESIDE_CONF%%%"; -getopts("bch:s:"); -die &usage if $opt_c && ! $opt_h; +getopts("s:g:n"); my $user = shift or die &usage; -if ( $opt_h ) { - my @args = ( 'htpasswd' ); - push @args, '-b' if $opt_b; - push @args, '-c' if $opt_c; - push @args, $opt_h, $user; - push @args, shift if $opt_b; - system(@args) == 0 or die "htpasswd failed: $?"; +if ( $opt_s ) { + + #if ( -e "$FREESIDE_CONF/mapsecrets" ) { + # open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets") + # or die "can't open $FREESIDE_CONF/mapsecrets: $!"; + # while (<MAPSECRETS>) { + # /^(\S+) / or die "unparsable line in mapsecrets: $_"; + # die "user $user already exists\n" if $user eq $1; + # } + # close MAPSECRETS; + #} + + #insert new entry before a wildcard... + open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets") + and flock(MAPSECRETS,LOCK_EX) + or die "can't open $FREESIDE_CONF/mapsecrets: $!"; + open(NEW,">$FREESIDE_CONF/mapsecrets.new") + or die "can't open $FREESIDE_CONF/mapsecrets.new: $!"; + while(<MAPSECRETS>) { + if ( /^\*\s/ ) { + print NEW "$user $opt_s\n"; + } + print NEW $_; + } + close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; + close NEW or die "can't close $FREESIDE_CONF/mapsecrets.new: $!"; + rename("$FREESIDE_CONF/mapsecrets.new", "$FREESIDE_CONF/mapsecrets") + or die "can't move mapsecrets.new into place: $!"; + } -my $secretfile = $opt_s || 'secrets'; +### + +exit if $opt_n; + +### + +use FS::UID qw(adminsuidsetup); +use FS::CurrentUser; +use FS::access_user; +use FS::access_usergroup; + +$FS::CurrentUser::upgrade_hack = 1; +#adminsuidsetup $rootuser; +adminsuidsetup $user; + +my $access_user = new FS::access_user { + 'username' => $user, + '_password' => 'notyet', + 'first' => 'Firstname', # $opt_f || + 'last' => 'Lastname', # $opt_l || +}; +my $au_error = $access_user->insert; +die $au_error if $au_error; + +if ( $opt_g ) { -open(MAPSECRETS,">>$FREESIDE_CONF/mapsecrets") - and flock(MAPSECRETS,LOCK_EX) - or die "can't open $FREESIDE_CONF/mapsecrets: $!"; -print MAPSECRETS "$user $secretfile\n"; -close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; + my $access_usergroup = new FS::access_usergroup { + 'usernum' => $access_user->usernum, + 'groupnum' => $opt_g, + }; + my $aug_error = $access_usergroup->insert; + die $aug_error if $aug_error; + +} + +### sub usage { - die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] [ -b ] ] [ -s secretfile ] username" + die "Usage:\n\n freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ]" } =head1 NAME @@ -40,24 +88,32 @@ freeside-adduser - Command line interface to add (freeside) users. =head1 SYNOPSIS - freeside-adduser [ -h htpasswd_file [ -c ] ] [ -s secretfile ] username + freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ] =head1 DESCRIPTION Adds a user to the Freeside billing system. This is for adding users (internal sales/tech folks) to the web interface, not for adding customer accounts. - -h: Also call htpasswd for this user with the given filename +This functionality is now available in the web interface as well, under +B<Configuration | Employees | View/Edit employees>. + + -g: initial groupnum + + Development/multi-DB options: + + -s: alternate secrets file - -c: Passed to htpasswd(1) + -n: no ACL added, for bootstrapping - -s: Specify an alternate secret file +=head1 NOTE - -b: same as htpasswd(1), probably insecure, not recommended +No explicit htpasswd options are available in 1.7 - passwordsa are now +maintained automatically. =head1 SEE ALSO -L<htpasswd>(1), base Freeside documentation +Base Freeside documentation =cut diff --git a/FS/bin/freeside-bill b/FS/bin/freeside-bill deleted file mode 100755 index 49ad4a768..000000000 --- a/FS/bin/freeside-bill +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/perl -w -# don't take any world-facing input -#!/usr/bin/perl -Tw - -use strict; -use Fcntl qw(:flock); -use Date::Parse; -use Getopt::Std; -use FS::UID qw(adminsuidsetup); -use FS::Record qw(qsearch qsearchs); -use FS::cust_main; - -&untaint_argv; #what it sounds like (eww) -use vars qw($opt_a $opt_c $opt_d $opt_p); -getopts("acd:p"); -my $user = shift or die &usage; - -adminsuidsetup $user; - -my %bill_only = map { $_ => 1 } ( - @ARGV ? @ARGV : ( map $_->custnum, qsearch('cust_main', {} ) ) -); - -#we're at now now (and later). -my($time)= $opt_d ? str2time($opt_d) : $^T; - -# find packages w/ bill < time && cancel != '', and create corresponding -# customer objects - -my($cust_main,%saw); -foreach $cust_main ( - map { - unless ( exists $saw{ $_->custnum } && defined $saw{ $_->custnum} ) { - $saw{ $_->custnum } = 0; # to avoid 'use of uninitialized value' errors - } - if ( - ( $opt_a || ( ( $_->getfield('bill') || 0 ) <= $time ) ) - && $bill_only{ $_->custnum } - && !$saw{ $_->custnum }++ - ) { - qsearchs('cust_main',{'custnum'=> $_->custnum } ); - } else { - (); - } - } ( qsearch('cust_pkg', { 'cancel' => '' }), - qsearch('cust_pkg', { 'cancel' => 0 }), - ) -) { - - # and bill them - - print "Billing customer #" . $cust_main->getfield('custnum') . "\n"; - - my($error); - - $error=$cust_main->bill('time'=>$time); - warn "Error billing, customer #" . $cust_main->getfield('custnum') . - ":" . $error if $error; - - if ($opt_p) { - $cust_main->apply_payments; - $cust_main->apply_credits; - } - - if ($opt_c) { - $error=$cust_main->collect( 'invoice_time' => $time); - warn "Error collecting from customer #" . $cust_main->custnum. ":$error" - if $error; - - #sleep 1; - } - -} - -# subroutines - -sub untaint_argv { - foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV - #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; - # Date::Parse - $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; - $ARGV[$_]=$1; - } -} - -sub usage { - die "Usage:\n\n freeside-bill [ -c [ -p ] ] [ -d 'date' ] user [ custnum custnum ... ]\n"; -} - -=head1 NAME - -freeside-bill - Command line (crontab, script) interface to customer billing. - -=head1 SYNOPSIS - - freeside-bill [ -c [ -p ] [ -a ] ] [ -d 'date' ] user [ custnum custnum ... ] - -=head1 DESCRIPTION - -This script is deprecated in 1.4.0. You should use freeside-daily instead. - -Bills customers. Searches for customers who are due for billing and calls -the bill and collect methods of a cust_main object. See L<FS::cust_main>. - - -c: Turn on collecting (you probably want this). - - -p: Apply unapplied payments and credits before collecting (you probably want - this too) - - -a: Call collect even if there isn't a new invoice (probably a bad idea for - daily use) - - -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, - but be careful. - -user: From the mapsecrets file - see config.html from the base documentation - -custnum: if one or more customer numbers are specified, only bills those -customers. Otherwise, bills all customers. - -=head1 BUGS - -=head1 SEE ALSO - -L<freeside-daily>, L<FS::cust_main>, config.html from the base documentation - -=cut - diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index 603da12b8..a06a2b185 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -1,157 +1,32 @@ #!/usr/bin/perl -w use strict; -use Fcntl qw(:flock); -use Date::Parse; use Getopt::Std; -use FS::UID qw(adminsuidsetup driver_name dbh datasrc); -use FS::Record qw(qsearch qsearchs dbdef); -use FS::Conf; -use FS::cust_main; +use FS::UID qw(adminsuidsetup); &untaint_argv; #what it sounds like (eww) -use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); -getopts("p:a:d:vsy:"); -my $user = shift or die &usage; +#use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); +use vars qw(%opt); +getopts("p:a:d:vsy:", \%opt); +my $user = shift or die &usage; adminsuidsetup $user; -$FS::cust_main::DEBUG = 1 if $opt_v; - -my %search = (); -$search{'payby'} = $opt_p if $opt_p; -$search{'agentnum'} = $opt_a if $opt_a; - -#we're at now now (and later). -my($time)= $opt_d ? str2time($opt_d) : $^T; -$time += $opt_y * 86400 if $opt_y; - -# select * from cust_main where -my $where_pkg = <<"END"; - 0 < ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and ( cancel is null or cancel = 0 ) - and ( setup is null or setup = 0 - or bill is null or bill <= $time - or ( expire is not null and expire <= $^T ) - ) - ) -END - -# or -my $where_bill_event = <<"END"; - 0 < ( select count(*) from cust_bill - where cust_main.custnum = cust_bill.custnum - and 0 < charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - and 0 < ( select count(*) from part_bill_event - where payby = cust_main.payby - and ( disabled is null or disabled = '' ) - and seconds <= $time - cust_bill._date - and 0 = ( select count(*) from cust_bill_event - where cust_bill.invnum = cust_bill_event.invnum - and part_bill_event.eventpart = cust_bill_event.eventpart - and status = 'done' - ) - - ) - ) -END - -my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "( $where_pkg OR $where_bill_event )"; - -my @cust_main; -if ( @ARGV ) { - @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV -} else { - @cust_main = qsearch('cust_main', \%search, '', $extra_sql ); -} -; - -my($cust_main,%saw); -foreach $cust_main ( @cust_main ) { - - # $^T not $time because -d is for pre-printing invoices - foreach my $cust_pkg ( - grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs - ) { - my $error = $cust_pkg->cancel; - warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ". - $cust_main->custnum. ": $error" - if $error; - } - # $^T not $time because -d is for pre-printing invoices - foreach my $cust_pkg ( - grep { $_->part_pkg->is_prepaid - && $_->bill && $_->bill < $^T && ! $_->susp - } - $cust_main->ncancelled_pkgs - ) { - my $error = $cust_pkg->suspend; - warn "Error suspending package ". $cust_pkg->pkgnum. - " for custnum ". $cust_main->custnum. - ": $error" - if $error; - } - - my $error = $cust_main->bill( 'time' => $time, - 'resetup' => $opt_s, ); - warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error; - - $cust_main->apply_payments; - $cust_main->apply_credits; - - $error = $cust_main->collect( 'invoice_time' => $time ); - warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error; +use FS::Cron::bill qw(bill); +bill(%opt); -} +use FS::Cron::notify qw(notify_flat_delay); +notify_flat_delay(%opt); -if ( driver_name eq 'Pg' ) { - dbh->{AutoCommit} = 1; #so we can vacuum - foreach my $table ( dbdef->tables ) { - my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr; - $sth->execute or die $sth->errstr; - } -} +use FS::Cron::vacuum qw(vacuum); +vacuum(); -my $conf = new FS::Conf; -my $dest = $conf->config('dump-scpdest'); -if ( $dest ) { - datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc; - my $database = $1; - eval "use Net::SCP qw(scp);"; - if ( driver_name eq 'Pg' ) { - system("pg_dump $database >/var/tmp/$database.sql") - } else { - die "database dumps not yet supported for ". driver_name; - } - if ( $conf->config('dump-pgpid') ) { - eval 'use GnuPG'; - my $gpg = new GnuPG; - $gpg->encrypt( plaintext => "/var/tmp/$database.sql", - output => "/var/tmp/$database.gpg", - recipient => $conf->config('dump-pgpid'), - ); - chmod 0600, '/var/tmp/$database.gpg'; - scp("/var/tmp/$database.gpg", $dest); - unlink "/var/tmp/$database.gpg" or die $!; - } else { - chmod 0600, '/var/tmp/$database.sql'; - scp("/var/tmp/$database.sql", $dest); - } - unlink "/var/tmp/$database.sql" or die $!; -} +use FS::Cron::backup qw(backup_scp); +backup_scp(); +### # subroutines +### sub untaint_argv { foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV @@ -166,6 +41,10 @@ sub usage { die "Usage:\n\n freeside-daily [ -d 'date' ] user [ custnum custnum ... ]\n"; } +### +# documentation +### + =head1 NAME freeside-daily - Run daily billing and invoice collection events. @@ -179,8 +58,6 @@ freeside-daily - Run daily billing and invoice collection events. Bills customers and runs invoice collection events. Should be run from crontab daily. -This script replaces freeside-bill from 1.3.1. - Bills customers. Searches for customers who are due for billing and calls the bill and collect methods of a cust_main object. See L<FS::cust_main>. diff --git a/FS/bin/freeside-deloutsource b/FS/bin/freeside-deloutsource index 561853539..afc3a0118 100644 --- a/FS/bin/freeside-deloutsource +++ b/FS/bin/freeside-deloutsource @@ -1,11 +1,14 @@ #!/bin/sh domain=$1 +FREESIDE_CONF=%%%FREESIDE_CONF%%% +FREESIDE_CACHE=%%%FREESIDE_CACHE%%% +FREESIDE_EXPORT=%%%FREESIDE_EXPORT%%% dropdb $domain && \ -rm -rf /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \ -rm -rf /usr/local/etc/freeside/counters.DBI:Pg:host=localhost\;dbname=$domain && \ -rm -rf /usr/local/etc/freeside/cache.DBI:Pg:host=localhost\;dbname=$domain && \ -rm -rf /usr/local/etc/freeside/export.DBI:Pg:host=localhost\;dbname=$domain && \ -rm /usr/local/etc/freeside/dbdef.DBI:Pg:host=localhost\;dbname=$domain +rm -rf $FREESIDE_CONF/conf.DBI:Pg:host=localhost\;dbname=$domain && \ +rm -rf $FREESIDE_CACHE/counters.DBI:Pg:host=localhost\;dbname=$domain && \ +rm -rf $FREESIDE_CACHE/cache.DBI:Pg:host=localhost\;dbname=$domain && \ +rm -rf $FREESIDE_EXPORT/export.DBI:Pg:host=localhost\;dbname=$domain && \ +rm $FREESIDE_CONF/dbdef.DBI:Pg:host=localhost\;dbname=$domain diff --git a/FS/bin/freeside-deloutsourceuser b/FS/bin/freeside-deloutsourceuser index 96871e50c..dc4ff9cdc 100644 --- a/FS/bin/freeside-deloutsourceuser +++ b/FS/bin/freeside-deloutsourceuser @@ -2,5 +2,5 @@ username=$1 -freeside-deluser -h /usr/local/etc/freeside/htpasswd $username 2>/dev/null +freeside-deluser -h %%%FREESIDE_CONF%%%/htpasswd $username 2>/dev/null diff --git a/FS/bin/freeside-deluser b/FS/bin/freeside-deluser index 57d6ce165..a2a361a83 100644 --- a/FS/bin/freeside-deluser +++ b/FS/bin/freeside-deluser @@ -5,7 +5,7 @@ use vars qw($opt_h); use Fcntl qw(:flock); use Getopt::Std; -my $FREESIDE_CONF = "/usr/local/etc/freeside"; +my $FREESIDE_CONF = "%%%FREESIDE_CONF%%%"; getopts("h:"); my $user = shift or die &usage; diff --git a/FS/bin/freeside-email b/FS/bin/freeside-email index 400dc2ac7..7a93f78ee 100755 --- a/FS/bin/freeside-email +++ b/FS/bin/freeside-email @@ -47,10 +47,6 @@ Prints the email addresses of all customers on STDOUT, separated by newlines. user: From the mapsecrets file - see config.html from the base documentation -=head1 VERSION - -$Id: freeside-email,v 1.2 2002-09-18 22:50:44 ivan Exp $ - =head1 BUGS =head1 SEE ALSO diff --git a/FS/bin/freeside-expiration-alerter b/FS/bin/freeside-expiration-alerter index 691fd3aa5..e49bd62aa 100755 --- a/FS/bin/freeside-expiration-alerter +++ b/FS/bin/freeside-expiration-alerter @@ -200,10 +200,6 @@ is about to expire. Usually run as a cron job. user: From the mapsecrets file - see config.html from the base documentation -=head1 VERSION - -$Id: freeside-expiration-alerter,v 1.5 2003-04-21 20:53:57 ivan Exp $ - =head1 BUGS Yes..... Use at your own risk. No guarantees or warrantees of any diff --git a/FS/bin/freeside-monthly b/FS/bin/freeside-monthly new file mode 100755 index 000000000..a6c75e715 --- /dev/null +++ b/FS/bin/freeside-monthly @@ -0,0 +1,91 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Std; +use FS::UID qw(adminsuidsetup); + +&untaint_argv; #what it sounds like (eww) +#use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); +use vars qw(%opt); +getopts("p:a:d:vsy:", \%opt); + +my $user = shift or die &usage; +adminsuidsetup $user; + +use FS::Cron::bill qw(bill); +bill(%opt, 'freq'=>'1m' ); + +### +# subroutines +### + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + # Date::Parse + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-monthly [ -d 'date' ] user [ custnum custnum ... ]\n"; +} + +### +# documentation +### + +=head1 NAME + +freeside-monthly - Run monthly billing and invoice collection events. + +=head1 SYNOPSIS + + freeside-monthly [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] [ -v ] user [ custnum custnum ... ] + +=head1 DESCRIPTION + +Bills customers and runs invoice collection events, for the alternate monthly +event chain. If you have defined monthly event checks, should be run from +crontab monthly. + +Bills customers. Searches for customers who are due for billing and calls +the bill and collect methods of a cust_main object. See L<FS::cust_main>. + + -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, + but be careful. + + -y: In addition to -d, which specifies an absolute date, the -y switch + specifies an offset, in days. For example, "-y 15" would increment the + "pretend date" 15 days from whatever was specified by the -d switch + (or now, if no -d switch was given). + + -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>) + + -a: Only process customers with the specified agentnum + + -s: re-charge setup fees + + -v: enable debugging + +user: From the mapsecrets file - see config.html from the base documentation + +custnum: if one or more customer numbers are specified, only bills those +customers. Otherwise, bills all customers. + +=head1 NOTE + +In most cases, you would use freeside-daily only and not freeside-monthly. +freeside-monthly would only be used in cases where you have events that can +only be run once each month, for example, batching invoices to a third-party +print/mail provider. + +=head1 BUGS + +=head1 SEE ALSO + +L<freeside-daily>, L<FS::cust_main>, config.html from the base documentation + +=cut + diff --git a/FS/bin/freeside-prepaidd b/FS/bin/freeside-prepaidd index e51a56350..73f7523c4 100644 --- a/FS/bin/freeside-prepaidd +++ b/FS/bin/freeside-prepaidd @@ -3,7 +3,7 @@ use strict; use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm); use FS::UID qw(adminsuidsetup); -use FS::Record qw(qsearch); # qsearchs); +use FS::Record qw(qsearch qsearchs); use FS::cust_pkg; my $user = shift or die &usage; @@ -37,9 +37,38 @@ while (1) { " AND ( cancel IS NULL OR cancel = 0)" } ) ) { - my $error = $cust_pkg->suspend; - warn "Error suspended package ". $cust_pkg->pkgnum. - " for custnum ". $cust_pkg->custnum. + + my $work_cust_pkg = $cust_pkg; + + my $cust_main = $cust_pkg->cust_main; + if ( $cust_main->total_unapplied_payments > 0 + or $cust_main->total_credited > 0 + ) + { + #this needs a flag to say only do the prepaid packages... + # and only try em if the renewal price matches.. but this will do for now + my $b_error = $cust_main->bill; + if ( $b_error ) { + warn "Error billing customer #". $cust_main->custnum; + next; + } + #$b_error = $cust_main->apply_payments_and_credits; + $b_error = $cust_main->apply_payments; + $b_error = $cust_main->apply_credits; + + $work_cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $work_cust_pkg->pkgnum } ); + + next + if $cust_main->balance <= 0 + and $work_cust_pkg->bill >= time; + } + + my $action = $work_cust_pkg->part_pkg->option('recur_action') || 'suspend'; + + my $error = $work_cust_pkg->$action(); + + warn "Error ${action}ing package ". $work_cust_pkg->pkgnum. + " for custnum ". $work_cust_pkg->custnum. ": $error\n" if $error; } @@ -65,8 +94,8 @@ freeside-prepaidd - Real-time daemon for prepaid packages =head1 DESCRIPTION -Runs continuously and suspendes any prepaid customer packages which have -passed their renewal date (next bill date). +Runs continuously and suspends or cancels any prepaid customer packages which +have passed their renewal date (next bill date). =head1 SEE ALSO diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index 3a0a9b4e5..93d735d1a 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -10,11 +10,8 @@ use FS::Record qw(qsearch qsearchs); use FS::queue; use FS::queue_depend; -# no autoloading just yet -use FS::cust_main; -use FS::svc_acct; +# no autoloading for non-FS classes... use Net::SSH 0.07; -use FS::part_export; $DEBUG = 0; @@ -44,7 +41,7 @@ while ( $@ ) { } } -logfile( "/usr/local/etc/freeside/queuelog.". $FS::UID::datasrc ); +logfile( "%%%FREESIDE_LOG%%%/queuelog.". $FS::UID::datasrc ); warn "completing daemonization (detaching))\n" if $DEBUG; daemonize2(); diff --git a/FS/bin/freeside-reset-fixed b/FS/bin/freeside-reset-fixed new file mode 100755 index 000000000..5829d441b --- /dev/null +++ b/FS/bin/freeside-reset-fixed @@ -0,0 +1,69 @@ +#!/usr/bin/perl -w + +use strict; +use vars qw($opt_p $opt_s $opt_r); +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::cust_svc; +use FS::svc_Common; + +getopts('p:s:r'); + +my $user = shift or die &usage; +adminsuidsetup $user; + +die &usage + if ($opt_p && $opt_s); + +$FS::Record::nowarn_identical = 1; +$FS::svc_Common::noexport_hack = 1 + unless $opt_r; + +my @svc_x = (); +if ( $opt_s ) { + $opt_s =~ /^(\d+)$/ or die "invalid svcnum"; + my $cust_svc = qsearchs('cust_svc', { svcnum => $1 } ) + or die "svcnum $opt_s not found\n"; + push @svc_x, $cust_svc->svc_x; +} elsif ( $opt_p ) { + $opt_p =~ /^(\d+)$/ or die "invalid svcpart"; + push @svc_x, map { $_->svc_x } qsearch('cust_svc', { svcpart => $1 } ); + die "no services with svcpart $opt_p found\n" unless @svc_x; +} else { + push @svc_x, map { $_->svc_x } qsearch('cust_svc', {} ); + die "no services found\n" unless @svc_x; +} + +foreach my $svc_x ( @svc_x ) { + my $result = $svc_x->setfixed; + die $result unless ref($result); + my $error = $svc_x->replace + if $svc_x->modified; + die $error if $error; +} + + +sub usage { + die "Usage:\n\n freeside-reset-fixed user [ -s svcnum | -p svcpart ] [ -r ]\n"; +} + +=head1 NAME + +freeside-reset-fixed - Command line tool to set the fixed columns for existing services + +=head1 SYNOPSIS + + freeside-reset-fixed user [ -s svcnum | -p svcpart ] [ -r ] + +=head1 DESCRIPTION + + Resets the fixed columns for the specified service part or service number. + Re-exports the service if -r is specified. + +=head1 SEE ALSO + +L<freeside-reexport>, L<FS::part_svc> + +=cut + diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index c73349a60..187bc1469 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -1,7 +1,8 @@ #!/usr/bin/perl -w use strict; -use vars qw( $Debug %kids $kids $max_kids $ssh_pid $keepalives ); +use vars qw( $FREESIDE_LOG $FREESIDE_LOCK ); +use vars qw( $Debug %kids $kids $max_kids $ssh_pid %old_ssh_pid $keepalives ); use subs qw( lock_write unlock_write myshutdown usage ); use Fcntl qw(:flock); use POSIX qw(:sys_wait_h); @@ -18,6 +19,9 @@ use FS::Conf; use FS::cust_bill; use FS::cust_pkg; +$FREESIDE_LOG = "%%%FREESIDE_LOG%%%"; +$FREESIDE_LOCK = "%%%FREESIDE_LOCK%%%"; + $Debug = 1; # 2 will turn on more logging # 3 will log packet contents, including passwords @@ -29,8 +33,7 @@ my $user = shift or die &usage; my $machine = shift or die &usage; my $tag = scalar(@ARGV) ? shift : ''; -my $lock_file = "/usr/local/etc/freeside/selfservice.$machine.writelock"; - +my $lock_file = "$FREESIDE_LOCK/selfservice.$machine.writelock"; # to keep pid files unique w/multi machines (and installs!) # $FS::UID::datasrc not posible @@ -50,11 +53,10 @@ $ENV{HOME} = (getpwuid($>))[7]; #for ssh adminsuidsetup $user; #logfile("/usr/local/etc/freeside/selfservice.". $FS::UID::datasrc); #MACHINE -logfile("/usr/local/etc/freeside/selfservice.$machine.log"); +logfile("$FREESIDE_LOG/selfservice.$machine.log"); daemonize2(); - my $conf = new FS::Conf; my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name? @@ -102,6 +104,7 @@ while (1) { if ( $ssh_pid ) { warn "sending TERM signal to ssh process $ssh_pid\n" if $Debug; kill 'TERM', $ssh_pid; + $old_ssh_pid{$ssh_pid} = 1; $ssh_pid = 0; } last; @@ -133,11 +136,11 @@ while (1) { } else { #kid time ##get new db handle - #$FS::UID::dbh->{InactiveDestroy} = 1; - #forksuidsetup($user); + $FS::UID::dbh->{InactiveDestroy} = 1; + forksuidsetup($user); #get db handle - adminsuidsetup($user); + #adminsuidsetup($user); my $type = $packet->{_packet}; warn "calling $type handler\n" if $Debug; @@ -180,6 +183,10 @@ sub reap_kids { delete $kids{$kid}; } } + + foreach my $pid ( keys %old_ssh_pid ) { + waitpid($pid, WNOHANG) and delete $old_ssh_pid{$pid}; + } #warn "done reaping\n"; } diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index a16e51749..ddc210f50 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -4,28 +4,27 @@ BEGIN { $FS::Schema::setup_hack = 1; } use strict; -use vars qw($opt_s); +use vars qw($opt_u $opt_d $opt_v); use Getopt::Std; -use Locale::Country; -use Locale::SubCountry; use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets); +use FS::CurrentUser; use FS::Schema qw( dbdef_dist reload_dbdef ); use FS::Record; -use FS::cust_main_county; #use FS::raddb; -use FS::part_bill_event; +use FS::Setup qw(create_initial_data); die "Not running uid freeside!" unless checkeuid(); #my %attrib2db = # map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib; -getopts("s"); -my $user = shift or die &usage; -getsecrets($user); +getopts("u:vd:"); +#my $user = shift or die &usage; + +getsecrets($opt_u); #$user); #needs to match FS::Record -my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc; +my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; ### @@ -88,12 +87,14 @@ $dbdef->save($dbdef_file); # create 'em ### -my $dbh = adminsuidsetup $user; +$FS::CurrentUser::upgrade_hack = 1; +my $dbh = adminsuidsetup $opt_u; #$user; #create tables $|=1; foreach my $statement ( $dbdef->sql($dbh) ) { + warn $statement if $statement =~ /TABLE cdr/; $dbh->do( $statement ) or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement"; } @@ -104,69 +105,14 @@ dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); -#cust_main_county -foreach my $country ( sort map uc($_), all_country_codes ) { - - my $subcountry = eval { new Locale::SubCountry($country) }; - my @states = $subcountry ? $subcountry->all_codes : undef; - - if ( !scalar(@states) || ( scalar(@states) == 1 && !defined($states[0]) ) ) { - - my $cust_main_county = new FS::cust_main_county({ - 'tax' => 0, - 'country' => $country, - }); - my $error = $cust_main_county->insert; - die $error if $error; - - } else { - - if ( $states[0] =~ /^(\d+|\w)$/ ) { - @states = map $subcountry->full_name($_), @states - } - - foreach my $state ( @states ) { +create_initial_data('domain' => $opt_d); - my $cust_main_county = new FS::cust_main_county({ - 'state' => $state, - 'tax' => 0, - 'country' => $country, - }); - my $error = $cust_main_county->insert; - die $error if $error; - - } - - } -} - -#billing events -foreach my $aref ( - #[ 'COMP', 'Comp invoice', '$cust_bill->comp();', 30, 'comp' ], - [ 'CARD', 'Batch card', '$cust_bill->batch_card();', 40, 'batch-card' ], - [ 'BILL', 'Send invoice', '$cust_bill->send();', 50, 'send' ], - [ 'DCRD', 'Send invoice', '$cust_bill->send();', 50, 'send' ], - [ 'DCHK', 'Send invoice', '$cust_bill->send();', 50, 'send' ], -) { - - my $part_bill_event = new FS::part_bill_event({ - 'payby' => $aref->[0], - 'event' => $aref->[1], - 'eventcode' => $aref->[2], - 'seconds' => 0, - 'weight' => $aref->[3], - 'plan' => $aref->[4], - }); - my($error); - $error=$part_bill_event->insert; - die $error if $error; - -} +warn "Freeside database initialized - commiting transaction\n" if $opt_v; $dbh->commit or die $dbh->errstr; $dbh->disconnect or die $dbh->errstr; -#print "Freeside database initialized sucessfully\n"; +warn "Database initialization committed successfully\n" if $opt_v; sub dbdef_create { # reverse engineer the schema from the DB and save to file my( $dbh, $file ) = @_; @@ -175,8 +121,10 @@ sub dbdef_create { # reverse engineer the schema from the DB and save to file } sub usage { - die "Usage:\n freeside-setup user\n"; + die "Usage:\n freeside-setup -d domain.name [ -v ]\n" + # [ -u user ] for devel/multi-db installs } 1; + diff --git a/FS/bin/freeside-sqlradius-radacctd b/FS/bin/freeside-sqlradius-radacctd index e98eaa015..83fd4bfd1 100644 --- a/FS/bin/freeside-sqlradius-radacctd +++ b/FS/bin/freeside-sqlradius-radacctd @@ -23,7 +23,7 @@ drop_root(); adminsuidsetup $user; -logfile( "/usr/local/etc/freeside/sqlradius-radacctd-log.". $FS::UID::datasrc ); +logfile( "%%%FREESIDE_LOG%%%/sqlradius-radacctd-log.". $FS::UID::datasrc ); daemonize2(); diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index 419384c2a..3a4e4f8e3 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -1,88 +1,45 @@ #!/usr/bin/perl -w use strict; +use vars qw($opt_d $opt_q $opt_v); use vars qw($DEBUG $DRY_RUN); -use Term::ReadKey; -use DBIx::DBSchema 0.27; +use Getopt::Std; +use DBIx::DBSchema 0.31; use FS::UID qw(adminsuidsetup checkeuid datasrc ); #getsecrets); +use FS::CurrentUser; use FS::Schema qw( dbdef dbdef_dist reload_dbdef ); +die "Not running uid freeside!" unless checkeuid(); -$DEBUG = 1; -$DRY_RUN = 0; +getopts("dq"); +$DEBUG = !$opt_q; +#$DEBUG = $opt_v; -die "Not running uid freeside!" unless checkeuid(); +$DRY_RUN = $opt_d; my $user = shift or die &usage; +$FS::CurrentUser::upgrade_hack = 1; my $dbh = adminsuidsetup($user); #needs to match FS::Schema... -my $dbdef_file = "/usr/local/etc/freeside/dbdef.". datasrc; +my $dbdef_file = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); +$DBIx::DBSchema::DEBUG = $DEBUG; +$DBIx::DBSchema::Table::DEBUG = $DEBUG; -foreach my $table ( dbdef_dist->tables ) { - - if ( dbdef->table($table) ) { - - warn "$table exists\n" if $DEBUG > 1; - - foreach my $column ( dbdef_dist->table($table)->columns ) { - if ( dbdef->table($table)->column($column) ) { - warn " $table.$column exists\n" if $DEBUG > 2; - } else { - - if ( $DEBUG ) { - print STDERR "column $table.$column does not exist. create?"; - next unless yesno(); - } - - foreach my $statement ( - dbdef_dist->table($table)->column($column)->sql_add_column( $dbh ) - ) { - warn "$statement\n" if $DEBUG || $DRY_RUN; - unless ( $DRY_RUN ) { - $dbh->do( $statement) - or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement"; - } - } - - } - - } - - #should eventually check & create missing indices - - #should eventually drop columns not in dbdef_dist... - - } else { - - if ( $DEBUG ) { - print STDERR "table $table does not exist. create?"; - next unless yesno(); - } - - foreach my $statement ( - dbdef_dist->table($table)->sql_create_table( $dbh ) - ) { - warn "$statement\n" if $DEBUG || $DRY_RUN; - unless ( $DRY_RUN ) { - $dbh->do( $statement) - or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement"; - } - } - - } - +if ( $DRY_RUN ) { + print join(";\n", dbdef->sql_update_schema( dbdef_dist, $dbh ) ). ";\n"; + exit; +} else { + dbdef->update_schema( dbdef_dist, $dbh ); } -# should eventually drop tables not in dbdef_dist too i guess... - $dbh->commit or die $dbh->errstr; dbdef_create($dbh, $dbdef_file); @@ -91,32 +48,6 @@ $dbh->disconnect or die $dbh->errstr; ### -my $all = 0; -sub yesno { - print STDERR ' [yes/no/all] '; - if ( $all ) { - warn "yes\n"; - return 1; - } else { - while ( 1 ) { - ReadMode 4; - my $x = lc(ReadKey); - ReadMode 0; - if ( $x eq 'n' ) { - warn "no\n"; - return 0; - } elsif ( $x eq 'y' ) { - warn "yes\n"; - return 1; - } elsif ( $x eq 'a' ) { - warn "yes\n"; - $all = 1; - return 1; - } - } - } -} - sub dbdef_create { # reverse engineer the schema from the DB and save to file my( $dbh, $file ) = @_; my $dbdef = new_native DBIx::DBSchema $dbh; @@ -124,8 +55,31 @@ sub dbdef_create { # reverse engineer the schema from the DB and save to file } sub usage { - die "Usage:\n freeside-upgrade user\n"; + die "Usage:\n freeside-upgrade [ -d ] [ -q | -v ] user\n"; } -1; +=head1 NAME + +freeside-upgrade - Upgrades database schema for new freeside verisons. + +=head1 SYNOPSIS + + freeside-adduser [ -d ] [ -q | -v ] + +=head1 DESCRIPTION + +Reads your existing database schema and updates it to match the current schema, +adding any columns or tables necessary. + + [ -d ]: Dry run; output SQL statements (to STDOUT) only, but do not execute + them. + + [ -q ]: Run quietly. This may become the default at some point. + + [ -v ]: Run verbosely, sending debugging information to STDERR. This is the + current default. + +=head1 SEE ALSO + +=cut diff --git a/FS/t/cancel_reason.t b/FS/t/AccessRight.t index a5948f657..a96684224 100644 --- a/FS/t/cancel_reason.t +++ b/FS/t/AccessRight.t @@ -1,5 +1,5 @@ BEGIN { $| = 1; print "1..1\n" } END {print "not ok 1\n" unless $loaded;} -use FS::cancel_reason; +use FS::AccessRight; $loaded=1; print "ok 1\n"; diff --git a/FS/t/ConfDefaults.t b/FS/t/ConfDefaults.t new file mode 100644 index 000000000..433555adb --- /dev/null +++ b/FS/t/ConfDefaults.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::ConfDefaults; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/Cron-backup.t b/FS/t/Cron-backup.t new file mode 100644 index 000000000..847d41aed --- /dev/null +++ b/FS/t/Cron-backup.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::backup; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/Cron-bill.t b/FS/t/Cron-bill.t new file mode 100644 index 000000000..42c7b4f9e --- /dev/null +++ b/FS/t/Cron-bill.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::bill; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/Cron-vacuum.t b/FS/t/Cron-vacuum.t new file mode 100644 index 000000000..eaa6b762a --- /dev/null +++ b/FS/t/Cron-vacuum.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::vacuum; +$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/FS/t/cdr.t b/FS/t/cdr.t new file mode 100644 index 000000000..1d1f3eb4e --- /dev/null +++ b/FS/t/cdr.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cdr_calltype.t b/FS/t/cdr_calltype.t new file mode 100644 index 000000000..d4e13943e --- /dev/null +++ b/FS/t/cdr_calltype.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_calltype; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cdr_carrier.t b/FS/t/cdr_carrier.t new file mode 100644 index 000000000..1e2161558 --- /dev/null +++ b/FS/t/cdr_carrier.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_carrier; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cdr_type.t b/FS/t/cdr_type.t new file mode 100644 index 000000000..9dff15a32 --- /dev/null +++ b/FS/t/cdr_type.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_type; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cdr_upstream_rate.t b/FS/t/cdr_upstream_rate.t new file mode 100644 index 000000000..f9458c527 --- /dev/null +++ b/FS/t/cdr_upstream_rate.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_upstream_rate; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_bill_ApplicationCommon.t b/FS/t/cust_bill_ApplicationCommon.t new file mode 100644 index 000000000..fa03d3420 --- /dev/null +++ b/FS/t/cust_bill_ApplicationCommon.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_bill_ApplicationCommon; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_bill_pay_batch.t b/FS/t/cust_bill_pay_batch.t new file mode 100644 index 000000000..bc3a8277c --- /dev/null +++ b/FS/t/cust_bill_pay_batch.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_bill_pay_batch; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_bill_pay_pkg.t b/FS/t/cust_bill_pay_pkg.t new file mode 100644 index 000000000..b8fcddb41 --- /dev/null +++ b/FS/t/cust_bill_pay_pkg.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_bill_pay_pkg; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_credit_bill_pkg.t b/FS/t/cust_credit_bill_pkg.t new file mode 100644 index 000000000..4eb84c327 --- /dev/null +++ b/FS/t/cust_credit_bill_pkg.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_credit_bill_pkg; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_main_note.t b/FS/t/cust_main_note.t new file mode 100644 index 000000000..41a7bac0b --- /dev/null +++ b/FS/t/cust_main_note.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_main_note; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_pkg_reason.t b/FS/t/cust_pkg_reason.t new file mode 100644 index 000000000..2f0a4fa4f --- /dev/null +++ b/FS/t/cust_pkg_reason.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_pkg_reason; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/cust_tax_exempt_pkg.t b/FS/t/cust_tax_exempt_pkg.t new file mode 100644 index 000000000..099a0ce8a --- /dev/null +++ b/FS/t/cust_tax_exempt_pkg.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_tax_exempt_pkg; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/h_cust_bill.t b/FS/t/h_cust_bill.t new file mode 100644 index 000000000..ceccb2a3d --- /dev/null +++ b/FS/t/h_cust_bill.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::h_cust_bill; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/h_cust_tax_exempt.t b/FS/t/h_cust_tax_exempt.t new file mode 100644 index 000000000..432238aa5 --- /dev/null +++ b/FS/t/h_cust_tax_exempt.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::h_cust_tax_exempt; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/inventory_class.t b/FS/t/inventory_class.t new file mode 100644 index 000000000..80b2fa210 --- /dev/null +++ b/FS/t/inventory_class.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::inventory_class; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/inventory_item.t b/FS/t/inventory_item.t new file mode 100644 index 000000000..8ce9d677c --- /dev/null +++ b/FS/t/inventory_item.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::inventory_item; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/part_pkg-voip_cdr.t b/FS/t/part_pkg-voip_cdr.t new file mode 100644 index 000000000..2d988a34f --- /dev/null +++ b/FS/t/part_pkg-voip_cdr.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_pkg::voip_cdr; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/pay_batch.t b/FS/t/pay_batch.t new file mode 100644 index 000000000..c43133dc2 --- /dev/null +++ b/FS/t/pay_batch.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::pay_batch; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/payby.t b/FS/t/payby.t new file mode 100644 index 000000000..7430bc8e5 --- /dev/null +++ b/FS/t/payby.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::payby; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/payinfo_Mixin.t b/FS/t/payinfo_Mixin.t new file mode 100644 index 000000000..3567c8e08 --- /dev/null +++ b/FS/t/payinfo_Mixin.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::payinfo_Mixin; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/pkg_class.t b/FS/t/pkg_class.t new file mode 100644 index 000000000..fb3774f8c --- /dev/null +++ b/FS/t/pkg_class.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::pkg_class; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/reason.t b/FS/t/reason.t new file mode 100644 index 000000000..d5e4dc9e7 --- /dev/null +++ b/FS/t/reason.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::reason; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/reason_type.t b/FS/t/reason_type.t new file mode 100644 index 000000000..279d5b950 --- /dev/null +++ b/FS/t/reason_type.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::reason_type; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/registrar.t b/FS/t/registrar.t new file mode 100644 index 000000000..a6ba13437 --- /dev/null +++ b/FS/t/registrar.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::registrar; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_External_Common.t b/FS/t/svc_External_Common.t new file mode 100644 index 000000000..a0b2ea2fd --- /dev/null +++ b/FS/t/svc_External_Common.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_External_Common; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_Parent_Mixin.t b/FS/t/svc_Parent_Mixin.t new file mode 100644 index 000000000..ed9923fc0 --- /dev/null +++ b/FS/t/svc_Parent_Mixin.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_Parent_Mixin; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_phone.t b/FS/t/svc_phone.t new file mode 100644 index 000000000..15b9ca275 --- /dev/null +++ b/FS/t/svc_phone.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_phone; +$loaded=1; +print "ok 1\n"; @@ -9,13 +9,26 @@ DATASOURCE = DBI:Pg:dbname=freeside DB_USER = freeside DB_PASSWORD= -#TEMPLATE = asp +#changable now (some things which should go to the others still go to CONF) +FREESIDE_CONF = /usr/local/etc/freeside +FREESIDE_LOG = /usr/local/etc/freeside +FREESIDE_LOCK = /usr/local/etc/freeside +FREESIDE_CACHE = /usr/local/etc/freeside +FREESIDE_EXPORT = /usr/local/etc/freeside + +MASON_HANDLER = ${FREESIDE_CONF}/handler.pl +MASONDATA = ${FREESIDE_CACHE}/masondata + +#mod_perl v1 +APACHE_VERSION = 1 +#mod_perl v2 prereleases up to and including 1.999_21 +#APACHE_VERSON = 1.99 +#mod_perl v2 proper and prereleases 1.999_22 and after +#APACHE_VERSION = 2 + +# only mason now TEMPLATE = mason -ASP_GLOBAL = /usr/local/etc/freeside/asp-global -MASON_HANDLER = /usr/local/etc/freeside/handler.pl -MASONDATA = /usr/local/etc/freeside/masondata - #deb FREESIDE_DOCUMENT_ROOT = /var/www/freeside #redhat, fedora, mandrake @@ -41,10 +54,8 @@ INIT_INSTALL = /usr/sbin/update-rc.d freeside defaults 21 20 #not necessary (freebsd) #INIT_INSTALL = /usr/bin/true -#deb -HTTPD_RESTART = /etc/init.d/apache reload -#suse -#HTTPD_RESTART = /etc/init.d/apache restart +#deb, suse +HTTPD_RESTART = /etc/init.d/apache restart #redhat, fedora, mandrake #HTTPD_RESTART = /etc/init.d/httpd restart #freebsd @@ -93,17 +104,16 @@ RT_DB_DATABASE = freeside #--- -#not changable yet -FREESIDE_CONF = /usr/local/etc/freeside + #rt/config.layout.in RT_PATH = /opt/rt3 #only used for dev kludge now, not a big deal FREESIDE_PATH = `pwd` -PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.8.7/ +PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.8.8/ -VERSION=1.5.8cvs -TAG=freeside_1_5_8 +VERSION=1.7.2 +TAG=freeside_1_7_2 help: @echo "supported targets:" @@ -119,27 +129,20 @@ help: @echo @echo " dev dev-docs dev-perl-modules" @echo - @echo " aspdocs masondocs alldocs docs" + @echo " masondocs alldocs docs" @echo " htmlman forcehtmlman" @echo " perl-modules" #@echo #@echo " upload-docs release update-webdemo" -aspdocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* httemplate/*/*/*/*/* - rm -rf aspdocs - cp -pr httemplate aspdocs - touch aspdocs - -masondocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* httemplate/*/*/*/*/* +#masondocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* httemplate/*/*/*/*/* +masondocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* rm -rf masondocs cp -pr httemplate masondocs - ( cd masondocs; \ - ../bin/masonize; \ - ) touch masondocs -alldocs: aspdocs masondocs +alldocs: masondocs docs: make ${TEMPLATE}docs @@ -164,29 +167,24 @@ forcehtmlman: install-docs: docs [ -e ${FREESIDE_DOCUMENT_ROOT} ] && mv ${FREESIDE_DOCUMENT_ROOT} ${FREESIDE_DOCUMENT_ROOT}.`date +%Y%m%d%H%M%S` || true cp -r ${TEMPLATE}docs ${FREESIDE_DOCUMENT_ROOT} - [ "${TEMPLATE}" = "asp" -a ! -e ${ASP_GLOBAL} ] && mkdir ${ASP_GLOBAL} || true - [ "${TEMPLATE}" = "asp" ] && chown -R freeside ${ASP_GLOBAL} || true - [ "${TEMPLATE}" = "asp" ] && cp htetc/global.asa ${ASP_GLOBAL} || true - [ "${TEMPLATE}" = "asp" ] && \ - perl -p -i -e "\ - s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \ - " ${ASP_GLOBAL}/global.asa || true - [ "${TEMPLATE}" = "mason" ] && cp htetc/handler.pl ${MASON_HANDLER} || true - [ "${TEMPLATE}" = "mason" ] && \ + chown -R freeside:freeside ${FREESIDE_DOCUMENT_ROOT} + cp htetc/handler.pl ${MASON_HANDLER} perl -p -i -e "\ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \ s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \ - " ${MASON_HANDLER} || true - [ "${TEMPLATE}" = "mason" -a ! -e ${MASONDATA} ] && mkdir ${MASONDATA} || true - [ "${TEMPLATE}" = "mason" ] && chown -R freeside ${MASONDATA} || true + s'%%%MASONDATA%%%'${MASONDATA}'g;\ + " ${MASON_HANDLER} + [ ! -e ${MASONDATA} ] && mkdir ${MASONDATA} || true + chown -R freeside ${MASONDATA} -dev-docs: docs +dev-docs: [ -e ${FREESIDE_DOCUMENT_ROOT} ] && mv ${FREESIDE_DOCUMENT_ROOT} ${FREESIDE_DOCUMENT_ROOT}.`date +%Y%m%d%H%M%S` || true - ln -s ${FREESIDE_PATH}/masondocs ${FREESIDE_DOCUMENT_ROOT} + ln -s ${FREESIDE_PATH}/httemplate ${FREESIDE_DOCUMENT_ROOT} cp htetc/handler.pl ${MASON_HANDLER} perl -p -i -e "\ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \ s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \ + s'%%%MASONDATA%%%'${MASONDATA}'g;\ s'###use Module::Refresh;###'use Module::Refresh;'; \ s'###Module::Refresh->refresh;###'Module::Refresh->refresh;'; \ " ${MASON_HANDLER} || true @@ -198,7 +196,20 @@ perl-modules: make; \ perl -p -i -e "\ s/%%%VERSION%%%/${VERSION}/g;\ - " blib/lib/FS.pm + " blib/lib/FS.pm;\ + perl -p -i -e "\ + s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\ + " blib/lib/FS/*.pm;\ + perl -p -i -e "\ + s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\ + " blib/lib/FS/part_export/*.pm;\ + perl -p -i -e "\ + s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\ + s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\ + s|%%%FREESIDE_LOCK%%%|${FREESIDE_LOCK}|g;\ + s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\ + s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\ + " blib/script/* install-perl-modules: perl-modules [ -L ${PERL_INC_DEV_KLUDGE}/FS ] \ @@ -208,13 +219,13 @@ install-perl-modules: perl-modules cd FS; \ make install UNINST=1 -dev-perl-modules: +dev-perl-modules: perl-modules [ -d ${PERL_INC_DEV_KLUDGE}/FS -a ! -L ${PERL_INC_DEV_KLUDGE}/FS ] \ && mv ${PERL_INC_DEV_KLUDGE}/FS ${PERL_INC_DEV_KLUDGE}/FS.old \ || true rm -rf ${PERL_INC_DEV_KLUDGE}/FS - ln -sf ${FREESIDE_PATH}/FS/FS ${PERL_INC_DEV_KLUDGE}/FS + ln -sf ${FREESIDE_PATH}/FS/blib/lib/FS ${PERL_INC_DEV_KLUDGE}/FS install-init: #[ -e ${INIT_FILE} ] || install -o root -g ${INSTALLGROUP} -m 711 init.d/freeside-init ${INIT_FILE} @@ -227,11 +238,13 @@ install-init: ${INIT_INSTALL} install-apache: + [ -e ${APACHE_CONF}/freeside-base.conf ] && rm ${APACHE_CONF}/freeside-base.conf || true [ -d ${APACHE_CONF} ] && \ - ( install -o root -m 755 htetc/freeside-base.conf ${APACHE_CONF} && \ + ( install -o root -m 755 htetc/freeside-base${APACHE_VERSION}.conf ${APACHE_CONF} && \ ( [ ${RT_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-rt.conf ${APACHE_CONF} || true ) && \ perl -p -i -e "\ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \ + s'%%%MASON_HANDLER%%%'${MASON_HANDLER}'g; \ " ${APACHE_CONF}/freeside-*.conf \ ) || true @@ -283,14 +296,14 @@ create-config: install-perl-modules cp `ls -d conf/[a-z]* | grep -v CVS` "${FREESIDE_CONF}/conf.${DATASOURCE}" chown -R freeside "${FREESIDE_CONF}/conf.${DATASOURCE}" - mkdir "${FREESIDE_CONF}/counters.${DATASOURCE}" - chown freeside "${FREESIDE_CONF}/counters.${DATASOURCE}" + mkdir "${FREESIDE_CACHE}/counters.${DATASOURCE}" + chown freeside "${FREESIDE_CACHE}/counters.${DATASOURCE}" - mkdir "${FREESIDE_CONF}/cache.${DATASOURCE}" - chown freeside "${FREESIDE_CONF}/cache.${DATASOURCE}" + mkdir "${FREESIDE_CACHE}/cache.${DATASOURCE}" + chown freeside "${FREESIDE_CACHE}/cache.${DATASOURCE}" - mkdir "${FREESIDE_CONF}/export.${DATASOURCE}" - chown freeside "${FREESIDE_CONF}/export.${DATASOURCE}" + mkdir "${FREESIDE_EXPORT}/export.${DATASOURCE}" + chown freeside "${FREESIDE_EXPORT}/export.${DATASOURCE}" configure-rt: cd rt; \ @@ -334,8 +347,13 @@ install-rt: [ ${RT_ENABLED} -eq 1 ] && ( cd rt; make install ) || true clean: - rm -rf aspdocs masondocs - cd FS; \ + rm -rf masondocs + rm -rf httemplate/docs/man + rm -rf pod2htmi.tmp + rm -rf pod2htmd.tmp + -cd FS; \ + make clean + -cd fs_selfservice/FS-SelfService; \ make clean #these are probably only useful if you're me... @@ -344,7 +362,8 @@ upload-docs: forcehtmlman ssh 420.am rm -rf /var/www/www.sisd.com/freeside/docs scp -pr httemplate/docs 420.am:/var/www/www.sisd.com/freeside/docs -release: upload-docs +#release: upload-docs +release: cd /home/ivan/freeside #cvs tag ${TAG} cvs tag -F ${TAG} diff --git a/README.1.5.0pre6 b/README.1.5.0pre6 deleted file mode 100644 index f2ee7e20f..000000000 --- a/README.1.5.0pre6 +++ /dev/null @@ -1,46 +0,0 @@ -CREATE TABLE cust_pay_refund ( - payrefundnum serial NOT NULL, - paynum int NOT NULL, - refundnum int NOT NULL, - _date int NOT NULL, - amount decimal(10,2) NOT NULL, - PRIMARY KEY (payrefundnum) -); -CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum); -CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum); - -CREATE TABLE cust_pay_void ( - paynum int NOT NULL, - custnum int NOT NULL, - paid decimal(10,2) NOT NULL, - _date int, - payby char(4) NOT NULL, - payinfo varchar(80), - paybatch varchar(80), - closed char(1), - void_date int, - reason varchar(80), - otaker varchar(32) NOT NULL, - PRIMARY KEY (paynum) -); -CREATE INDEX cust_pay_void1 ON cust_pay_void(custnum); - -alter table svc_external alter column id drop not null; -alter table h_svc_external alter column id drop not null; - -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' ); -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' ); - -CREATE TABLE part_pkg_option ( - optionnum int primary key, - pkgpart int not null, - optionname varchar(80) not null, - optionvalue text NULL -); -CREATE INDEX part_pkg_option1 ON part_pkg_option ( pkgpart ); -CREATE INDEX part_pkg_option2 ON part_pkg_option ( optionname ); - -dbdef-create username -create-history-tables username cust_pay_refund cust_pay_void part_pkg_option -dbdef-create username - diff --git a/README.1.5.7 b/README.1.5.7 deleted file mode 100644 index e890f01bc..000000000 --- a/README.1.5.7 +++ /dev/null @@ -1,199 +0,0 @@ -NOTE: Version numbering has been simplified. 1.5.7 is the version after -1.5.0pre6. It is still a development version - releases with odd numbered -middle parts (NN in x.NN.x) are development versions, like Perl or Linux. - -install DBIx::DBSchema 0.26 - -CREATE TABLE rate ( - ratenum serial NOT NULL, - ratename varchar(80) NOT NULL, - PRIMARY KEY (ratenum) -); - -CREATE TABLE rate_detail ( - ratedetailnum serial NOT NULL, - ratenum int NOT NULL, - orig_regionnum int NULL, - dest_regionnum int NOT NULL, - min_included int NOT NULL, - min_charge decimal(10,2) NOT NULL, - sec_granularity int NOT NULL -); -CREATE UNIQUE INDEX rate_detail1 ON rate_detail ( ratenum, orig_regionnum, dest_regionnum ); - -CREATE TABLE rate_region ( - regionnum serial NOT NULL, - regionname varchar(80) NOT NULL, - PRIMARY KEY (regionnum) -); - -CREATE TABLE rate_prefix ( - prefixnum serial NOT NULL, - regionnum int NOT NULL, - countrycode varchar(3) NOT NULL, - npa varchar(6) NULL, - nxx varchar(3) NULL, - PRIMARY KEY (prefixnum) -); -CREATE INDEX rate_prefix1 ON rate_prefix ( countrycode ); -CREATE INDEX rate_prefix2 ON rate_prefix ( regionnum ); - -CREATE TABLE reg_code ( - codenum serial NOT NULL, - code varchar(80) NOT NULL, - agentnum int NOT NULL, - PRIMARY KEY (codenum) -); -CREATE UNIQUE INDEX reg_code1 ON reg_code ( agentnum, code ); -CREATE INDEX reg_code2 ON reg_code ( agentnum ); - -CREATE TABLE reg_code_pkg ( - codepkgnum serial, - codenum int NOT NULL, - pkgpart int NOT NULL, - PRIMARY KEY (codepkgnum) -); -CREATE UNIQUE INDEX reg_code_pkg1 ON reg_code_pkg ( codenum, pkgpart ); -CREATE INDEX reg_code_pkg2 ON reg_code_pkg ( codenum ); - -CREATE TABLE clientapi_session ( - sessionnum serial NOT NULL, - sessionid varchar(80) NOT NULL, - namespace varchar(80) NOT NULL, - PRIMARY KEY (sessionnum) -); -CREATE UNIQUE INDEX clientapi_session1 ON clientapi_session ( sessionid, namespace ); - -CREATE TABLE clientapi_session_field ( - fieldnum serial NOT NULL, - sessionnum int NOT NULL, - fieldname varchar(80) NOT NULL, - fieldvalue text NULL, - PRIMARY KEY (fieldnum) -); -CREATE UNIQUE INDEX clientapi_session_field1 ON clientapi_session_field ( sessionnum, fieldname ); - -ALTER TABLE part_pkg ADD promo_code varchar(80) NULL; -ALTER TABLE h_part_pkg ADD promo_code varchar(80) NULL; -CREATE INDEX part_pkg2 ON part_pkg ( promo_code ); -CREATE INDEX h_part_pkg2 ON h_part_pkg ( promo_code ); - -ALTER TABLE cust_main ALTER COLUMN zip DROP NOT NULL; -ALTER TABLE h_cust_main ALTER COLUMN zip DROP NOT NULL; - -ALTER TABLE prepay_credit ADD agentnum integer NULL; -ALTER TABLE h_prepay_credit ADD agentnum integer NULL; - -On current (7.3? definitely 7.4+) Pg: - - ALTER TABLE type_pkgs ADD typepkgnum int; - ALTER TABLE type_pkgs ALTER COLUMN typepkgnum SET DEFAULT nextval('public.type_pkgs_typepkgnum_seq'::text); - CREATE SEQUENCE type_pkgs_typepkgnum_seq; - UPDATE type_pkgs SET typepkgnum = nextval('public.type_pkgs_typepkgnum_seq'::text) WHERE typepkgnum IS NULL; - ALTER TABLE type_pkgs ALTER typepkgnum SET NOT NULL; - ALTER TABLE type_pkgs ADD PRIMARY KEY (typepkgnum); - ALTER TABLE h_type_pkgs ADD typepkgnum int; - - ALTER TABLE cust_bill_pkg ADD billpkgnum int; - ALTER TABLE cust_bill_pkg ALTER COLUMN billpkgnum SET DEFAULT nextval('public.cust_bill_pkg_billpkgnum_seq'::text); - CREATE SEQUENCE cust_bill_pkg_billpkgnum_seq; - UPDATE cust_bill_pkg SET billpkgnum = nextval('public.cust_bill_pkg_billpkgnum_seq'::text) WHERE billpkgnum IS NULL; - ALTER TABLE cust_bill_pkg ALTER billpkgnum SET NOT NULL; - ALTER TABLE cust_bill_pkg ADD PRIMARY KEY (billpkgnum); - ALTER TABLE h_cust_bill_pkg ADD billpkgnum int; - - ALTER TABLE pkg_svc ADD pkgsvcnum int; - ALTER TABLE pkg_svc ALTER COLUMN pkgsvcnum SET DEFAULT nextval('public.pkg_svc_pkgsvcnum_seq'::text); - CREATE SEQUENCE pkg_svc_pkgsvcnum_seq; - UPDATE pkg_svc SET pkgsvcnum = nextval('public.pkg_svc_pkgsvcnum_seq'::text) WHERE pkgsvcnum IS NULL; - ALTER TABLE pkg_svc ALTER pkgsvcnum SET NOT NULL; - ALTER TABLE pkg_svc ADD PRIMARY KEY (pkgsvcnum); - ALTER TABLE h_pkg_svc ADD pkgsvcnum int; - - ALTER TABLE part_svc_router ADD svcrouternum int; - ALTER TABLE part_svc_router ALTER COLUMN svcrouternum SET DEFAULT nextval('public.part_svc_router_svcrouternum_seq'::text); - CREATE SEQUENCE part_svc_router_svcrouternum_seq; - UPDATE part_svc_router SET svcrouternum = nextval('public.part_svc_router_svcrouternum_seq'::text) WHERE svcrouternum IS NULL; - ALTER TABLE part_svc_router ALTER svcrouternum SET NOT NULL; - ALTER TABLE part_svc_router ADD PRIMARY KEY (svcrouternum); - ALTER TABLE h_part_svc_router ADD svcrouternum int; - -Or on very old Pg (7.2 and earlier (eek), 7.3?): - - ALTER TABLE type_pkgs ADD typepkgnum int; - ALTER TABLE type_pkgs ALTER COLUMN typepkgnum SET DEFAULT nextval('type_pkgs_typepkgnum_seq'::text); - CREATE SEQUENCE type_pkgs_typepkgnum_seq; - UPDATE type_pkgs SET typepkgnum = nextval('type_pkgs_typepkgnum_seq'::text) WHERE typepkgnum IS NULL; - UPDATE pg_attribute SET attnotnull = TRUE WHERE attname = 'typepkgnum' AND attrelid = ( SELECT oid FROM pg_class WHERE relname = 'type_pkgs'); - ALTER TABLE type_pkgs ADD PRIMARY KEY (typepkgnum); - ALTER TABLE h_type_pkgs ADD typepkgnum int; - - ALTER TABLE cust_bill_pkg ADD billpkgnum int; - ALTER TABLE cust_bill_pkg ALTER COLUMN billpkgnum SET DEFAULT nextval('cust_bill_pkg_billpkgnum_seq'::text); - CREATE SEQUENCE cust_bill_pkg_billpkgnum_seq; - UPDATE cust_bill_pkg SET billpkgnum = nextval('cust_bill_pkg_billpkgnum_seq'::text) WHERE billpkgnum IS NULL; - UPDATE pg_attribute SET attnotnull = TRUE WHERE attname = 'billpkgnum' AND attrelid = ( SELECT oid FROM pg_class WHERE relname = 'cust_bill_pkg'); - ALTER TABLE cust_bill_pkg ADD PRIMARY KEY (billpkgnum); - ALTER TABLE h_cust_bill_pkg ADD billpkgnum int; - - ALTER TABLE pkg_svc ADD pkgsvcnum int; - ALTER TABLE pkg_svc ALTER COLUMN pkgsvcnum SET DEFAULT nextval('pkg_svc_pkgsvcnum_seq'::text); - CREATE SEQUENCE pkg_svc_pkgsvcnum_seq; - UPDATE pkg_svc SET pkgsvcnum = nextval('pkg_svc_pkgsvcnum_seq'::text) WHERE pkgsvcnum IS NULL; - UPDATE pg_attribute SET attnotnull = TRUE WHERE attname = 'pkgsvcnum' AND attrelid = ( SELECT oid FROM pg_class WHERE relname = 'pkg_svc'); - ALTER TABLE pkg_svc ADD PRIMARY KEY (pkgsvcnum); - ALTER TABLE h_pkg_svc ADD pkgsvcnum int; - - ALTER TABLE part_svc_router ADD svcrouternum int; - ALTER TABLE part_svc_router ALTER COLUMN svcrouternum SET DEFAULT nextval('part_svc_router_svcrouternum_seq'::text); - CREATE SEQUENCE part_svc_router_svcrouternum_seq; - UPDATE part_svc_router SET svcrouternum = nextval('part_svc_router_svcrouternum_seq'::text) WHERE svcrouternum IS NULL; - UPDATE pg_attribute SET attnotnull = TRUE WHERE attname = 'svcrouternum' AND attrelid = ( SELECT oid FROM pg_class WHERE relname = 'part_svc_router'); - ALTER TABLE part_svc_router ADD PRIMARY KEY (svcrouternum); - ALTER TABLE h_part_svc_router ADD svcrouternum int; - -Installs w/integrated RT: - CREATE SEQUENCE attributes_id_seq; - - CREATE TABLE Attributes ( - id INTEGER DEFAULT nextval('attributes_id_seq'), - Name varchar(255) NOT NULL , - Description varchar(255) NULL , - Content text, - ContentType varchar(16), - ObjectType varchar(64), - ObjectId integer, -- foreign key to anything - Creator integer NOT NULL DEFAULT 0 , - Created TIMESTAMP NULL , - LastUpdatedBy integer NOT NULL DEFAULT 0 , - LastUpdated TIMESTAMP NULL , - PRIMARY KEY (id) - - ); - - CREATE INDEX Attributes1 on Attributes(Name); - CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId); - - Add these lines to /opt/rt3/etc/RT_SiteConfig.pm (before the "1;"): - $RT::URI::freeside::IntegrationType = 'Internal'; - $RT::URI::freeside::URL = 'http://path/to/your/freeside/'; - Set($DatabaseHost , ''); - -(End of Installs w/integrated RT) - - -(make sure you have upgraded DBIx::DBSchema to 0.26) -dbdef-create username -create-history-tables username rate rate_detail rate_region rate_prefix reg_code reg_code_pkg -dbdef-create username - -install Text::CSV_XS, Spreadsheet::WriteExcel, IO-stringy (IO::Scalar), -Frontier::RPC (Frontier::RPC2), MIME::Entity (MIME-tools) and IPC::Run3 - -afterwords (for installs w/integrated RT): -install HTML::Scrubber, Text::Quoted and Tree::Simple -make configure-rt -make deploy -/opt/rt3/sbin/rt-setup-database --action insert --datadir etc/upgrade/3.1.15 -/opt/rt3/sbin/rt-setup-database --action insert --datadir etc/upgrade/3.1.17 - diff --git a/README.1.5.7.lastbit b/README.1.5.7.lastbit deleted file mode 100644 index ad29e4c02..000000000 --- a/README.1.5.7.lastbit +++ /dev/null @@ -1,64 +0,0 @@ -this is ONLY for people upgrading from CVS snapshots after march 12th who -have most of the changes in README.1.5.7 already. - -if you're upgrading from 1.5.0pre6, see README.1.5.7 instead. - -if you're upgrading from 1.4.x, see httemplate/docs/upgrade10.html instead. - - -ALTER TABLE rate_detail ADD COLUMN ratedetailnum int; -ALTER TABLE rate_detail ALTER COLUMN ratedetailnum SET DEFAULT nextval('public.rate_detail_ratedetailnum_seq'::text); -CREATE SEQUENCE rate_detail_ratedetailnum_seq; -UPDATE rate_detail SET ratedetailnum = nextval('public.rate_detail_ratedetailnum_seq'::text) WHERE ratedetailnum IS NULL; -ALTER TABLE rate_detail ALTER ratedetailnum SET NOT NULL; -ALTER TABLE rate_detail ADD PRIMARY KEY (ratedetailnum); -ALTER TABLE h_rate_detail ADD COLUMN ratedetailnum int; - -ALTER TABLE type_pkgs ADD typepkgnum int; -ALTER TABLE type_pkgs ALTER COLUMN typepkgnum SET DEFAULT nextval('public.type_pkgs_typepkgnum_seq'::text); -CREATE SEQUENCE type_pkgs_typepkgnum_seq; -UPDATE type_pkgs SET typepkgnum = nextval('public.type_pkgs_typepkgnum_seq'::text) WHERE typepkgnum IS NULL; -ALTER TABLE type_pkgs ALTER typepkgnum SET NOT NULL; -ALTER TABLE type_pkgs ADD PRIMARY KEY (typepkgnum); -ALTER TABLE h_type_pkgs ADD typepkgnum int; - -ALTER TABLE cust_bill_pkg ADD billpkgnum int; -ALTER TABLE cust_bill_pkg ALTER COLUMN billpkgnum SET DEFAULT nextval('public.cust_bill_pkg_billpkgnum_seq'::text); -CREATE SEQUENCE cust_bill_pkg_billpkgnum_seq; -UPDATE cust_bill_pkg SET billpkgnum = nextval('public.cust_bill_pkg_billpkgnum_seq'::text) WHERE billpkgnum IS NULL; -ALTER TABLE cust_bill_pkg ALTER billpkgnum SET NOT NULL; -ALTER TABLE cust_bill_pkg ADD PRIMARY KEY (billpkgnum); -ALTER TABLE h_cust_bill_pkg ADD billpkgnum int; - -ALTER TABLE pkg_svc ADD pkgsvcnum int; -ALTER TABLE pkg_svc ALTER COLUMN pkgsvcnum SET DEFAULT nextval('public.pkg_svc_pkgsvcnum_seq'::text); -CREATE SEQUENCE pkg_svc_pkgsvcnum_seq; -UPDATE pkg_svc SET pkgsvcnum = nextval('public.pkg_svc_pkgsvcnum_seq'::text) WHERE pkgsvcnum IS NULL; -ALTER TABLE pkg_svc ALTER pkgsvcnum SET NOT NULL; -ALTER TABLE pkg_svc ADD PRIMARY KEY (pkgsvcnum); -ALTER TABLE h_pkg_svc ADD pkgsvcnum int; - -ALTER TABLE part_svc_router ADD svcrouternum int; -ALTER TABLE part_svc_router ALTER COLUMN svcrouternum SET DEFAULT nextval('public.part_svc_router_svcrouternum_seq'::text); -CREATE SEQUENCE part_svc_router_svcrouternum_seq; -UPDATE part_svc_router SET svcrouternum = nextval('public.part_svc_router_svcrouternum_seq'::text) WHERE svcrouternum IS NULL; -ALTER TABLE part_svc_router ALTER svcrouternum SET NOT NULL; -ALTER TABLE part_svc_router ADD PRIMARY KEY (svcrouternum); -ALTER TABLE h_part_svc_router ADD svcrouternum int; - -ALTER TABLE reg_code_pkg ADD codepkgnum int; -ALTER TABLE reg_code_pkg ALTER COLUMN codepkgnum SET DEFAULT nextval('public.reg_code_pkg_codepkgnum_seq'::text); -CREATE SEQUENCE reg_code_pkg_codepkgnum_seq; -UPDATE reg_code_pkg SET codepkgnum = nextval('public.reg_code_pkg_codepkgnum_seq'::text) WHERE codepkgnum IS NULL; -ALTER TABLE reg_code_pkg ALTER codepkgnum SET NOT NULL; -ALTER TABLE reg_code_pkg ADD PRIMARY KEY (codepkgnum); -ALTER TABLE h_reg_code_pkg ADD codepkgnum int; - -ALTER TABLE virtual_field ADD vfieldnum int; -ALTER TABLE virtual_field ALTER COLUMN vfieldnum SET DEFAULT nextval('public.virtual_field_vfieldnum_seq'::text); -CREATE SEQUENCE virtual_field_vfieldnum_seq; -UPDATE virtual_field SET vfieldnum = nextval('public.virtual_field_vfieldnum_seq'::text) WHERE vfieldnum IS NULL; -ALTER TABLE virtual_field ALTER vfieldnum SET NOT NULL; -ALTER TABLE virtual_field ADD PRIMARY KEY (vfieldnum); --- ALTER TABLE h_virtual_field ADD vfieldnum int; - diff --git a/README.1.5.8 b/README.1.5.8 deleted file mode 100644 index cf414545e..000000000 --- a/README.1.5.8 +++ /dev/null @@ -1,56 +0,0 @@ - -install JSON -install Term::ReadKey - -install DBIx::DBSchema 0.27 (or later) - (if you are running Pg version 7.2.x or earlier, install at least - DBIx::DBSchema 0.29) -install HTML::Widgets:SelectLayers 0.05 (or later) -install Business::CreditCard 0.28 (or later) - -make install-perl-modules -run "freeside-upgrade username" to uprade your database schema - -(if freeside-upgrade hangs, try stopping Apache, all Freeside processes, and - anything else connected to your database, especially on older Pg versions) - -Optional: -CREATE INDEX cust_pkg2 ON cust_pkg ( pkgpart ); - -CREATE INDEX cust_bill_pkg2 ON cust_bill_pkg ( pkgnum ); -CREATE INDEX cust_main9 ON cust_main ( county ); -CREATE INDEX cust_main10 ON cust_main ( state ); -CREATE INDEX cust_main11 ON cust_main ( country ); -CREATE INDEX cust_main_county1 ON cust_main_county ( county ); -CREATE INDEX cust_main_county2 ON cust_main_county ( state ); -CREATE INDEX cust_main_county3 ON cust_main_county ( country ); - -Optional for better VoIP performance: -CREATE INDEX rate_detail2 ON rate_detail ( ratenum, dest_regionnum ); - ------ - -Installs w/integrated RT: - Install Module::Versions::Report - - chmod a+r /opt/rt3/etc/RT*Config.pm - cd rt - su freeside - - /opt/rt3/sbin/rt-setup-database --action schema --datadir etc/upgrade/3.3.0 - /opt/rt3/sbin/rt-setup-database --action acl --datadir etc/upgrade/3.3.0 - /opt/rt3/sbin/rt-setup-database --action insert --datadir etc/upgrade/3.3.0 - - /opt/rt3/sbin/rt-setup-database --action schema --datadir etc/upgrade/3.3.11 - /opt/rt3/sbin/rt-setup-database --action acl --datadir etc/upgrade/3.3.11 - /opt/rt3/sbin/rt-setup-database --action insert --datadir etc/upgrade/3.3.11 - - run "dbdef-create username" - - make configure-rt - ------- - -make install-docs - (or "make deploy" if you've got everything setup in the Makefile) - diff --git a/SCHEMA_CHANGE b/SCHEMA_CHANGE index 26ebeea76..b3d77aaf8 100644 --- a/SCHEMA_CHANGE +++ b/SCHEMA_CHANGE @@ -3,9 +3,10 @@ primarily: if the changes are something other than table and/or column additions: - httemplate/docs/upgrade10.html -- README.1.5.X +- README.1.7.X for new tables: +- make sure the new tables are added to FS/FS/Schema.pm and run make install-perl-modules - run bin/generate-table-module tablename - edit the resulting FS/FS/table.pm diff --git a/bin/breakdown-bill-applications b/bin/breakdown-bill-applications new file mode 100644 index 000000000..44c3e36b0 --- /dev/null +++ b/bin/breakdown-bill-applications @@ -0,0 +1,25 @@ +#!/usr/bin/perl -w + +use strict; +use FS::UID qw(adminsuidsetup dbh); +use FS::Record qw( qsearch ); +use FS::cust_bill_pay; +use FS::cust_credit_bill; + +$FS::CurrentUser::upgrade_hack = 1; +adminsuidsetup(shift) or die "Usage: breakdown-bill-applications username\n"; + +#quick and dirty conversion script if you have enough memory to throw at it + +my @tables = qw( cust_bill_pay cust_credit_bill ); + +my @apps = (); +foreach my $table { + push @apps, qsearch($table, + + +) { + +} + +foreach my $cust_bill_ diff --git a/bin/cdr_calltype.import b/bin/cdr_calltype.import new file mode 100755 index 000000000..a998284f6 --- /dev/null +++ b/bin/cdr_calltype.import @@ -0,0 +1,41 @@ +#!/usr/bin/perl -w +# +# bin/cdr_calltype.import ivan ~ivan/convergent/newspecs/fixed_inbound/calltypes.csv + +use strict; +use FS::UID qw(dbh adminsuidsetup); +use FS::cdr_calltype; + +my $user = shift or die &usage; +adminsuidsetup $user; + +while (<>) { + + chomp; + my $line = $_; + + #$line =~ /^(\d+),"([^"]+)"$/ or do { + $line =~ /^(\d+),"([^"]+)"/ or do { + warn "unparsable line: $line\n"; + next; + }; + + my $cdr_calltype = new FS::cdr_calltype { + 'calltypenum' => $1, + 'calltypename' => $2, + }; + + #my $error = $cdr_calltype->check; + my $error = $cdr_calltype->insert; + if ( $error ) { + warn "********** $error FOR LINE: $line\n"; + dbh->commit; + #my $wait = scalar(<STDIN>); + } + +} + +sub usage { + "Usage:\n\ncdr_calltype.import username filename ...\n"; +} + diff --git a/bin/cdr_upstream_rate.import b/bin/cdr_upstream_rate.import new file mode 100755 index 000000000..fda3883b5 --- /dev/null +++ b/bin/cdr_upstream_rate.import @@ -0,0 +1,142 @@ +#!/usr/bin/perl -w +# +# Usage: bin/cdr_upstream_rate.import username ratenum filename +# +# records will be imported into cdr_upstream_rate, rate_detail and rate_region +# +# Example: bin/cdr_upstream_rate.import ivan 1 ~ivan/convergent/sample_rate_table.csv +# +# username: a freeside login (from /usr/local/etc/freeside/mapsecrets) +# ratenum: rate plan (FS::rate) created with the web UI +# filename: CSV file +# +# the following fields are currently used: +# - Class Code => cdr_upstream_rate.rateid +# - Description => rate_region.regionname +# (rate_detail->dest_region) +# - 1_rate => ( * 60 / 1_rate_seconds ) => rate_detail.min_charge +# - 1_rate_seconds => (used above) +# - 1_second_increment => rate_detail.sec_granularity +# +# the following fields are not (yet) used: +# - Flagfall => what's this for? +# +# - 1_cap_time => freeside doesn't have voip time caps yet... +# - 1_cap_cost => freeside doesn't have voip cost caps yet... +# - 1_repeat => not sure what this is for, sample data is all 0 +# +# - 2_rate => \ +# - 2_rate_seconds => | +# - 2_second_increment => | not sure what the second set of rate data +# - 2_cap_time => | is supposed to be for... +# - 2_cap_cost => | +# - 2_repeat => / +# +# - Carrier => probably not needed? +# - Start Date => not necessary? + +use strict; +use vars qw( $DEBUG ); +use Text::CSV_XS; +use FS::UID qw(dbh adminsuidsetup); +use FS::Record qw(qsearchs); +use FS::rate; +use FS::cdr_upstream_rate; +use FS::rate_detail; +use FS::rate_region; + +$DEBUG = 1; + +my $user = shift or die &usage; +adminsuidsetup $user; + +my $ratenum = shift or die &usage; + +my $rate = qsearchs( 'rate', { 'ratenum' => $ratenum } ); +die "rate plan $ratenum not found in rate table\n" + unless $rate; + +my $csv = new Text::CSV_XS; +my $hline = scalar(<>); +chomp($hline); +$csv->parse($hline) or die "can't parse header: $hline\n"; +my @header = $csv->fields(); + +$FS::UID::AutoCommit = 0; + +while (<>) { + + chomp; + my $line = $_; + +# #$line =~ /^(\d+),"([^"]+)"$/ or do { +# #} +# $line =~ /^(\d+),"([^"]+)"/ or do { +# warn "unparsable line: $line\n"; +# next; +# }; + + $csv->parse($line) or die "can't parse line: $line\n"; + my @line = $csv->fields(); + + my %hash = map { $_ => shift(@line) } @header; + + warn join('', map { "$_ => $hash{$_}\n" } keys %hash ) + if $DEBUG > 1; + + my $rate_region = new FS::rate_region { + 'regionname' => $hash{'Description'} + }; + + my $error = $rate_region->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into rate_region: $error\n"; + } + my $dest_regionnum = $rate_region->regionnum; + warn "rate_region $dest_regionnum inserted\n" + if $DEBUG; + + my $rate_detail = new FS::rate_detail { + 'ratenum' => $ratenum, + 'dest_regionnum' => $dest_regionnum, + 'min_included' => 0, + #'min_charge', => sprintf('%.5f', 60 * $hash{'1_rate'} / $hash{'1_rate_seconds'} ), + 'min_charge', => sprintf('%.5f', $hash{'1_rate'} / + ( $hash{'1_rate_seconds'} / 60 ) + ), + 'sec_granularity' => $hash{'1_second_increment'}, + }; + $error = $rate_detail->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into rate_detail: $error\n"; + } + my $ratedetailnum = $rate_detail->ratedetailnum; + warn "rate_detail $ratedetailnum inserted\n" + if $DEBUG; + + my $cdr_upstream_rate = new FS::cdr_upstream_rate { + 'upstream_rateid' => $hash{'Class Code'}, + 'ratedetailnum' => $rate_detail->ratedetailnum, + }; + $error = $cdr_upstream_rate->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into cdr_upstream_rate: $error\n"; + } + warn "cdr_upstream_rate ". $cdr_upstream_rate->upstreamratenum. " inserted\n" + if $DEBUG; + + dbh->commit or die "can't commit: ". dbh->errstr; + + warn "\n" if $DEBUG; + +} + +dbh->commit or die "can't commit: ". dbh->errstr; + +sub usage { + "Usage:\n\ncdr_upstream_rate.import username ratenum filename\n"; +} + diff --git a/bin/customer-faker b/bin/customer-faker new file mode 100755 index 000000000..b4032d1ec --- /dev/null +++ b/bin/customer-faker @@ -0,0 +1,118 @@ +#!/usr/bin/perl + +use strict; +use Getopt::Std; +use Data::Faker; +use Business::CreditCard; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch); +use FS::cust_main; +use FS::cust_pkg; +use FS::svc_acct; + +my $agentnum = 1; +my $refnum = 1; + +#my @pkgs = ( 2, 3, 4 ); +my @pkgs = ( 4, 5, 6 ); +my $svcpart = 2; + +use vars qw( $opt_p ); +getopts('p:'); + +my $user = shift or die &usage; +my $num = shift or die &usage; +adminsuidsetup($user); + +my $onum = $num; +my $start = time; + +until ( $num-- <= 0 ) { + + my $faker = new Data::Faker; + + my $cust_main = new FS::cust_main { + 'agentnum' => $agentnum, + 'refnum' => $refnum, + 'first' => $faker->first_name, + 'last' => $faker->last_name, + 'company' => ( $num % 2 ? $faker->company. ', '. $faker->company_suffix : '' ), #half with companies.. + 'address1' => $faker->street_address, + 'city' => 'Tofutown', #missing, so everyone is from tofutown# $faker->city, + 'state' => $faker->us_state_abbr, + 'zip' => $faker->us_zip_code, + 'country' => 'US', + 'daytime' => $faker->phone_number, + 'night' => $faker->phone_number, + #forget it, these can have extensions# 'fax' => ( $num % 2 ? $faker->phone_number : '' ), #ditto + #bah, forget shipping addresses + 'payby' => 'BILL', + 'payip' => $faker->ip_address, + }; + + if ( $opt_p eq 'CARD' || ( !$opt_p && rand() > .33 ) ) { + $cust_main->payby('CARD'); + my $cardnum = '4123'. sprintf('%011u', int(rand(100000000000)) ); + $cust_main->payinfo( $cardnum. generate_last_digit($cardnum) ); + $cust_main->paydate( '2009-05-01' ); + } elsif ( $opt_p eq 'CHEK' || ( !$opt_p && rand() > .66 ) ) { + $cust_main->payby('CHEK'); + my $payinfo = sprintf('%7u@%09u', int(rand(10000000)), int(rand(1000000000)) ); + $cust_main->payinfo($payinfo); + $cust_main->payname( 'First International Bank of Testing' ); + } + + # could insert invoicing_list and other stuff too.. hell, could insert + # packages, services, more + # but i just wanted 10k customers to test the pager and this was good enough + # not anymore, here's some services and packages + + my $now = time; + my $year = 31556736; #60*60*24*365.24 + my $setup = $now - int(rand($year)); + + my $cust_pkg = new FS::cust_pkg { + 'pkgpart' => $pkgs[ int(rand(scalar(@pkgs))) ], + + #some dates in here would be nice + 'setup' => $setup, + #'last_bill' + #'bill' + #'susp' + #'expire' + #'cancel' + }; + + my $svc_acct = new FS::svc_acct { + 'svcpart' => $svcpart, + 'username' => $faker->username, + }; + + while ( qsearch( 'svc_acct', { 'username' => $svc_acct->username } ) ) { + my $username = $svc_acct->username; + $username++; + $svc_acct->username($username); + } + + use Tie::RefHash; + tie my %hash, 'Tie::RefHash', + $cust_pkg => [ $svc_acct ], + ; + + my $error = $cust_main->insert( \%hash ); + die $error if $error; + +} + +my $end = time; + +my $sec = $end-$start; +$sec=1 if $sec==0; +my $persec = $onum / $sec; +print "$onum customers inserted in $sec seconds ($persec customers/sec)\n"; + +#--- + +sub usage { + die "Usage:\n\n customer-faker [ -p payby ] user num_fakes\n"; +} diff --git a/bin/dbdef-create b/bin/dbdef-create index fea02c8c5..5063a3ce9 100755 --- a/bin/dbdef-create +++ b/bin/dbdef-create @@ -4,13 +4,16 @@ use strict; use DBI; use DBIx::DBSchema 0.26; use FS::UID qw(adminsuidsetup datasrc driver_name); +use FS::Schema; my $user = shift or die &usage; +$FS::Schema::setup_hack = 1; +$FS::CurrentUser::upgrade_hack = 1; my($dbh)=adminsuidsetup $user; #needs to match FS::Record -my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc; +my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; my $dbdef = new_native DBIx::DBSchema $dbh; diff --git a/bin/expand-country b/bin/expand-country new file mode 100755 index 000000000..c6f2a1f09 --- /dev/null +++ b/bin/expand-country @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w + +use strict; +use Locale::SubCountry; +use FS::UID qw(adminsuidsetup); +use FS::Setup; +use FS::Record qw(qsearch); +use FS::cust_main_county; + +my $user = shift or die &usage; +my $country = shift or die &usage; + +adminsuidsetup($user); + +my @country = qsearch('cust_main_county', { 'country' => $country } ); +die "unknown country $country" unless (@country); +#die "$country already expanded" if scalar(@country) > 1; + +foreach my $cust_main_county ( @country ) { + my $error = $cust_main_county->delete; + die $error if $error; +} + +FS::Setup::_add_country($country); + +sub usage { + die "Usage:\n\n expand-country user countrycode\n"; +} + diff --git a/bin/fs-migrate-svc_acct_sm b/bin/fs-migrate-svc_acct_sm index e34b23596..07f7b611c 100755 --- a/bin/fs-migrate-svc_acct_sm +++ b/bin/fs-migrate-svc_acct_sm @@ -1,7 +1,5 @@ #!/usr/bin/perl -Tw # -# $Id: fs-migrate-svc_acct_sm,v 1.4 2002-06-21 09:13:16 ivan Exp $ -# # jeff@cmh.net 01-Jul-20 #to delay loading dbdef until we're ready diff --git a/bin/generate-table-module b/bin/generate-table-module index fcc3f1d1f..b3204fa06 100755 --- a/bin/generate-table-module +++ b/bin/generate-table-module @@ -13,6 +13,7 @@ my %ut = ( #just guesses 'number' => 'float', 'varchar' => 'text', 'text' => 'text', + 'serial' => 'number', ); my $dbdef_table = dbdef_dist->table($table) diff --git a/bin/import-county-tax-rates b/bin/import-county-tax-rates new file mode 100755 index 000000000..05798c9a2 --- /dev/null +++ b/bin/import-county-tax-rates @@ -0,0 +1,30 @@ +#!/usr/bin/perl +# +# import-county-tax-rates username state country <filename.csv +# example: import-county-tax-rates ivan CA US <taxes.csv +# +# rates.csv: taxrate,county + +use FS::UID qw(adminsuidsetup); +use FS::cust_main_county; + +my $user = shift; +adminsuidsetup $user; + +my($state, $country) = (shift, shift); + +while (<>) { + my($tax, $county) = split(','); #half-ass CSV parser + + my $cust_main_county = new FS::cust_main_county { + 'county' => $county, + 'state' => $state, + 'country' => $country, + 'tax' => $tax, + }; + + my $error = $cust_main_county->insert; + #my $error = $cust_main_county->check; + die $error if $error; + +} diff --git a/bin/mapsecrets2access_user b/bin/mapsecrets2access_user new file mode 100755 index 000000000..945f130ef --- /dev/null +++ b/bin/mapsecrets2access_user @@ -0,0 +1,87 @@ +#!/usr/bin/perl -w + +use strict; +use File::Copy "cp"; +use FS::UID qw(adminsuidsetup); +use FS::CurrentUser; +use FS::AccessRight; +use FS::Record qw(qsearchs qsearch); +use FS::access_group; +use FS::access_user; +use FS::access_usergroup; +use FS::access_right; +use FS::access_groupagent; +use FS::agent; + +$FS::CurrentUser::upgrade_hack = 1; +my $user = shift or die &usage; +adminsuidsetup $user; + +my $supergroup = qsearchs('access_group', { 'groupname' => 'Superuser' } ); +unless ( $supergroup ) { + + $supergroup = new FS::access_group { 'groupname' => 'Superuser' }; + my $error = $supergroup->insert; + die $error if $error; + + foreach my $rightname ( FS::AccessRight->rights ) { + my $access_right = new FS::access_right { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $supergroup->groupnum, + 'rightname' => $rightname, + }; + my $ar_error = $access_right->insert; + die $ar_error if $ar_error; + } + + foreach my $agent ( qsearch('agent', {} ) ) { + my $access_groupagent = new FS::access_groupagent { + 'groupnum' => $supergroup->groupnum, + 'agentnum' => $agent->agentnum, + }; + my $aga_error = $access_groupagent->insert; + die $aga_error if $aga_error; + } + +} +my $supergroupnum = $supergroup->groupnum; + +my $conf = new FS::Conf; +my $dir = $conf->base_dir; +my $mapsecrets = "$dir/mapsecrets"; +open(MAPSECRETS, "<$mapsecrets") or die "Can't open $mapsecrets: $!"; +while (<MAPSECRETS>) { + /([\w]+)\s+secrets\s*$/ or die "unparsable line in mapsecrets: $_"; + my $username = $1; + + next if qsearchs('access_user', { 'username' => $username } ); + + my $access_user = new FS::access_user { + 'username' => $username, + '_password' => 'notyet', + 'first' => 'Legacy', + 'last' => 'User', + }; + my $au_error = $access_user->insert; + die $au_error if $au_error; + + my $access_usergroup = new FS::access_usergroup { + 'usernum' => $access_user->usernum, + 'groupnum' => $supergroupnum, + }; + my $aug_error = $access_usergroup->insert; + die $aug_error if $aug_error; + +} +close MAPSECRETS; + +# okay to clobber mapsecrets now i guess +cp $mapsecrets, "$mapsecrets.bak$$"; +open(MAPSECRETS, ">$mapsecrets") or die $!; +print MAPSECRETS '* secrets'. "\n"; +close MAPSECRETS or die $!; + +sub usage { + die "Usage:\n mapsecrets2access_user user\n"; +} + diff --git a/bin/payment-faker b/bin/payment-faker new file mode 100755 index 000000000..03316e1c0 --- /dev/null +++ b/bin/payment-faker @@ -0,0 +1,54 @@ +#!/usr/bin/perl + +use Date::Parse; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch); +use FS::cust_pay; +use FS::cust_credit; + +my $user; +$user = shift or die "usage: payment-faker $user"; +adminsuidsetup($user); + +for $month ( 1 .. 11 ) { + + print "month $month\n"; + + system(qq!freeside-daily -d "$month/1/2006" $user!); + + foreach my $cust_main ( qsearch('cust_main', {} ) ) { + next unless $cust_main->balance > 0; + my $item = ''; + if ( rand() > .95 ) { + $item = new FS::cust_credit { + 'amount' => $cust_main->balance, + '_date' => str2time("$month/1/2006"), + 'reason' => 'testing', + }; + } else { + + if ( rand() > .5 ) { + $payby = 'BILL'; + $payinfo = int(rand(10000)); + } else { + $payby = 'CARD'; + $payinfo = '4111111111111111'; + } + + $item = new FS::cust_pay { + 'paid' => $cust_main->balance, + '_date' => str2time("$month/1/2006"), + 'payby' => $payby, + 'payinfo' => $payinfo, + }; + } + + $item->custnum($cust_main->custnum); + my $error = $item->insert; + die $error if $error; + $cust_main->apply_payments; + $cust_main->apply_credits; + + } + +} diff --git a/bin/pg-version b/bin/pg-version new file mode 100755 index 000000000..b6cddb612 --- /dev/null +++ b/bin/pg-version @@ -0,0 +1,13 @@ +#!/usr/bin/perl -w + +use strict; +use FS::UID qw(adminsuidsetup dbh); + +my $user = shift or die &usage; +adminsuidsetup($user); + +print "pg_server_version: ". dbh->{'pg_server_version'}. "\n"; + +sub usage { + "\n\nUsage: pg-version username\n"; +}; @@ -30,7 +30,8 @@ foreach my $file ( glob("$site_perl/*/*/*.pm"), glob("$site_perl/bin/*.pod"), glob("./fs_sesmon/FS-SessionClient/*.pm"), - glob("./fs_signup/FS-SignupClient/*.pm"), + #glob("./fs_signup/FS-SignupClient/*.pm"), + glob("./fs_selfservice/FS-SelfService/*.pm"), glob("./fs_selfadmin/FS-MailAdminServer/*.pm"), ) { next if $file =~ /(^|\/)blib\//; diff --git a/bin/populate-msgcat b/bin/populate-msgcat deleted file mode 100755 index adac92dd0..000000000 --- a/bin/populate-msgcat +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/perl -Tw - -use strict; -use FS::UID qw(adminsuidsetup); -use FS::Record qw(qsearch); -use FS::msgcat; - -my $user = shift or die &usage; -adminsuidsetup $user; - -foreach my $del_msgcat ( qsearch('msgcat', {}) ) { - my $error = $del_msgcat->delete; - die $error if $error; -} - -my %messages = messages(); - -foreach my $msgcode ( keys %messages ) { - foreach my $locale ( keys %{$messages{$msgcode}} ) { - my $msgcat = new FS::msgcat( { - 'msgcode' => $msgcode, - 'locale' => $locale, - 'msg' => $messages{$msgcode}{$locale}, - }); - my $error = $msgcat->insert; - die $error if $error; - } -} - -#print "Message catalog initialized sucessfully\n"; - -sub messages { - - # 'msgcode' => { - # 'en_US' => 'Message', - # }, - - ( - - 'passwords_dont_match' => { - 'en_US' => "Passwords don't match", - }, - - 'invalid_card' => { - 'en_US' => 'Invalid credit card number', - }, - - 'unknown_card_type' => { - 'en_US' => 'Unknown card type', - }, - - 'not_a' => { - 'en_US' => 'Not a ', - }, - - 'empty_password' => { - 'en_US' => 'Empty password', - }, - - 'no_access_number_selected' => { - 'en_US' => 'No access number selected', - }, - - 'illegal_text' => { - 'en_US' => 'Illegal (text)', - #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field', - }, - - 'illegal_or_empty_text' => { - 'en_US' => 'Illegal or empty (text)', - #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field', - }, - - 'illegal_username' => { - 'en_US' => 'Illegal username', - }, - - 'illegal_password' => { - 'en_US' => 'Illegal password (', - }, - - 'illegal_password_characters' => { - 'en_US' => ' characters)', - }, - - 'username_in_use' => { - 'en_US' => 'Username in use', - }, - - 'illegal_email_invoice_address' => { - 'en_US' => 'Illegal email invoice address', - }, - - 'illegal_name' => { - 'en_US' => 'Illegal (name)', - #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field', - }, - - 'illegal_phone' => { - 'en_US' => 'Illegal (phone)', - #'en_US' => '', - }, - - 'illegal_zip' => { - 'en_US' => 'Illegal (zip)', - #'en_US' => '', - }, - - 'expired_card' => { - 'en_US' => 'Expired card', - }, - - 'daytime' => { - 'en_US' => 'Day Phone', - }, - - 'night' => { - 'en_US' => 'Night Phone', - }, - - 'svc_external-id' => { - 'en_US' => 'External ID', - }, - - 'svc_external-title' => { - 'en_US' => 'Title', - }, - - ); -} - -sub usage { - die "Usage:\n\n populate-msgcat user\n"; -} - diff --git a/bin/rotate-cdrs b/bin/rotate-cdrs new file mode 100755 index 000000000..7bef0bbb0 --- /dev/null +++ b/bin/rotate-cdrs @@ -0,0 +1,38 @@ +#!/usr/bin/perl -w + +use strict; +use Fcntl qw(:flock); +use IO::File; + +my $dir = '/usr/local/etc/freeside/export/cdr'; +#chdir $dir; + +#XXX glob might not handle lots of args at some point... +foreach my $file ( glob("$dir/*/CDR*-spool.CSV") ) { + + $file =~ m{(\d+)/CDR(\d+)-spool.CSV$} + or die "guru meditation #54: can't parse filename: $file\n"; + my($custnum, $date) = ($1, $2); + + + my $alpha = 'A'; + while ( -e "$dir/$custnum/CDR$date$alpha.CSV" ) { + $alpha++; # A -> Z -> AA etc. + } + my $newfile = "$dir/$custnum/CDR$date$alpha.CSV"; + + rename $file, $newfile + or die "$! moving $file to $newfile\n"; + + use IO::File; + my $lock = new IO::File ">>$newfile" + or die "can't open $newfile: $!\n"; + sleep 1; #just in case. i guess there's still a *remotely* possible + #race condition, but i'm not losing any sleep over it... (rimshot) + flock($lock, LOCK_EX) + or die "can't lock $newfile: $!\n"; + #okay we've got the lock, any pending write should be done... + + print "$custnum: $newfile\n"; + +} diff --git a/bin/rt-update-links b/bin/rt-update-links new file mode 100644 index 000000000..75d554f48 --- /dev/null +++ b/bin/rt-update-links @@ -0,0 +1,36 @@ +#!/usr/bin/perl + +use FS::UID qw(adminsuidsetup); + +my( $olddb, $newdb ) = ( shift, shift ); + +$FS::CurrentUser::upgrade_hack = 1; +my $dbh = adminsuidsetup; + +my $statement = "select * from links where base like 'fsck.com-rt://$olddb/%' OR target like 'fsck.com-rt://$olddb/%'"; + +my $sth = $dbh->prepare($statement) or die $dbh->errstr; +$sth->execute or die $sth->errstr; + +while ( my $row = $sth->fetchrow_hashref ) { + + ( my $base = $row->{'base'} ) + =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/); + + ( my $target = $row->{'target'} ) + =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/); + + if ( $row->{'base'} ne $base || $row->{'target'} ne $target ) { + + my $update = 'UPDATE links SET base = ?, target = ? where id = ?'; + my @param = ( $base, $target, $row->{'id'} ); + + warn "$update : ". join(', ', @param). "\n"; + $dbh->do($update, {}, @param ); + + } + +} + +$dbh->commit; + diff --git a/bin/svc_acct.export b/bin/svc_acct.export deleted file mode 100755 index 0bc370fc0..000000000 --- a/bin/svc_acct.export +++ /dev/null @@ -1,641 +0,0 @@ -#!/usr/bin/perl -w -# -# $Id: svc_acct.export,v 1.36 2002-05-16 14:28:35 ivan Exp $ -# -# Create and export password, radius and vpopmail password files: -# passwd, passwd.adjunct, shadow, acp_passwd, acp_userinfo, acp_dialup -# users/assign, domains/vdomain/vpasswd -# Also export sendmail and qmail config files. - -use strict; -use vars qw($conf); -use Fcntl qw(:flock); -use File::Path; -use IO::Handle; -use FS::Conf; -use Net::SSH qw(ssh); -use Net::SCP qw(scp); -use FS::UID qw(adminsuidsetup datasrc dbh); -use FS::Record qw(qsearch qsearchs fields); -use FS::svc_acct; -use FS::svc_domain; -use FS::svc_forward; - -my $ssh='ssh'; -my $rsync='rsync'; - -my $user = shift or die &usage; -adminsuidsetup $user; - -$conf = new FS::Conf; - -my $userpolicy = $conf->config('username_policy') - if $conf->exists('username_policy'); - -my @shellmachines = $conf->config('shellmachines') - if $conf->exists('shellmachines'); - -my @bsdshellmachines = $conf->config('bsdshellmachines') - if $conf->exists('bsdshellmachines'); - -my @nismachines = $conf->config('nismachines') - if $conf->exists('nismachines'); - -my @erpcdmachines = $conf->config('erpcdmachines') - if $conf->exists('erpcdmachines'); - -my @radiusmachines = $conf->config('radiusmachines') - if $conf->exists('radiusmachines'); - -my $textradiusprepend = - $conf->exists('textradiusprepend') - ? $conf->config('textradiusprepend') - : ''; - -warn "using depriciated textradiusprepend file" if $textradiusprepend; - - -my $radiusprepend = - $conf->exists('radiusprepend') - ? join("\n", $conf->config('radiusprepend')) - : ''; - -my @vpopmailmachines = $conf->config('vpopmailmachines') - if $conf->exists('vpopmailmachines'); -my $vpopmailrestart = ''; -$vpopmailrestart = $conf->config('vpopmailrestart') - if $conf->exists('vpopmailrestart'); - -my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachines[0]) if $vpopmailmachines[0]; - -my($shellmachine, @qmailmachines); -if ( $conf->exists('qmailmachines') ) { - $shellmachine = $conf->config('shellmachine'); - @qmailmachines = $conf->config('qmailmachines'); -} - -my(@sendmailmachines, $sendmailconfigpath, $sendmailrestart); -if ( $conf->exists('sendmailmachines') ) { - @sendmailmachines = $conf->config('sendmailmachines'); - $sendmailconfigpath = $conf->config('sendmailconfigpath') || '/etc'; - $sendmailrestart = $conf->config('sendmailrestart'); -} - -my $mydomain = $conf->config('domain') if $conf->exists('domain'); - - - - -my(@saltset)= ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); -require 5.004; #srand(time|$$); - -my $spooldir = "/usr/local/etc/freeside/export.". datasrc; -my $spoollock = "/usr/local/etc/freeside/svc_acct.export.lock.". datasrc; - -open(EXPORT,"+>>$spoollock") or die "Can't open $spoollock: $!"; -select(EXPORT); $|=1; select(STDOUT); -unless ( flock(EXPORT,LOCK_EX|LOCK_NB) ) { - seek(EXPORT,0,0); - my($pid)=<EXPORT>; - chop($pid); - #no reason to start lots of blocking processes - die "Is another export process running under pid $pid?\n"; -} -seek(EXPORT,0,0); -print EXPORT $$,"\n"; - -my(@svc_domain)=qsearch('svc_domain',{}); - -( open(MASTER,">$spooldir/master.passwd") - and flock(MASTER,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/.master.passwd: $!"; -( open(PASSWD,">$spooldir/passwd") - and flock(PASSWD,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/passwd: $!"; -( open(SHADOW,">$spooldir/shadow") - and flock(SHADOW,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/shadow: $!"; -( open(ACP_PASSWD,">$spooldir/acp_passwd") - and flock(ACP_PASSWD,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/acp_passwd: $!"; -( open(ACP_DIALUP,">$spooldir/acp_dialup") - and flock(ACP_DIALUP,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/acp_dialup: $!"; -( open(USERS,">$spooldir/users") - and flock(USERS,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/users: $!"; - -( open(ASSIGN,">$spooldir/assign") - and flock(ASSIGN,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/assign: $!"; -( open(RCPTHOSTS,">$spooldir/rcpthosts") - and flock(RCPTHOSTS,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/rcpthosts: $!"; -( open(VPOPRCPTHOSTS,">$spooldir/vpoprcpthosts") - and flock(VPOPRCPTHOSTS,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/rcpthosts: $!"; -( open(RECIPIENTMAP,">$spooldir/recipientmap") - and flock(RECIPIENTMAP,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/recipientmap: $!"; -( open(VIRTUALDOMAINS,">$spooldir/virtualdomains") - and flock(VIRTUALDOMAINS,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/virtualdomains: $!"; -( open(VPOPVIRTUALDOMAINS,">$spooldir/vpopvirtualdomains") - and flock(VPOPVIRTUALDOMAINS,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/virtualdomains: $!"; -( open(VIRTUSERTABLE,">$spooldir/virtusertable") - and flock(VIRTUSERTABLE,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/virtusertable: $!"; -( open(SENDMAIL_CW,">$spooldir/sendmail.cw") - and flock(SENDMAIL_CW,LOCK_EX|LOCK_NB) -) or die "Can't open $spooldir/sendmail.cw: $!"; - - - -chmod 0644, "$spooldir/passwd", - "$spooldir/acp_dialup", - "$spooldir/assign", - "$spooldir/sendmail.cw", - "$spooldir/virtusertable", - "$spooldir/rcpthosts", - "$spooldir/vpoprcpthosts", - "$spooldir/recipientmap", - "$spooldir/virtualdomains", - "$spooldir/vpopvirtualdomains", - -; -chmod 0600, "$spooldir/master.passwd", - "$spooldir/acp_passwd", - "$spooldir/shadow", - "$spooldir/users", -; - -rmtree"$spooldir/domains", 0, 1; -mkdir "$spooldir/domains", 0700; - -setpriority(0,0,10); - -print USERS "$radiusprepend\n"; - -my %usernames; ## this hack helps keep the passwd files sane -my @sendmail; - -my $svc_domain; -foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) { - - my($domain)=$svc_domain->domain; - print RCPTHOSTS "$domain\n.$domain\n"; - print VPOPRCPTHOSTS "$domain\n"; - print SENDMAIL_CW "$domain\n"; - - ### - # FORMAT OF THE ASSIGN/USERS FILE HERE - print ASSIGN join(":", - "+" . $domain . "-", - $domain, - $vpopuid, - $vpopgid, - $vpopdir . "/domains/" . $domain, - "-", - "", - "", - ), "\n" if $vpopmailmachines[0]; - - (mkdir "$spooldir/domains/" . $domain, 0700) - or die "Can't create $spooldir/domains/" . $domain .": $!"; - - ( open(QMAILDEFAULT,">$spooldir/domains/" . $domain . "/.qmail-default") - and flock(QMAILDEFAULT,LOCK_EX|LOCK_NB) - ) or die "Can't open $spooldir/domains/" . $domain . "/.qmail-default: $!"; - - ( open(VPASSWD,">$spooldir/domains/" . $domain . "/vpasswd") - and flock(VPASSWD,LOCK_EX|LOCK_NB) - ) or die "Can't open $spooldir/domains/" . $domain . "/vpasswd: $!"; - - my ($svc_acct); - - if ($svc_domain->getfield('catchall')) { - $svc_acct = qsearchs('svc_acct', {'svcnum' => $svc_domain->catchall}); - die "Cannot find catchall account for domain $domain\n" unless $svc_acct; - - my $username = $svc_acct->username; - push @sendmail, "\@$domain\t$username\n"; - print VIRTUALDOMAINS "$domain:$username-$domain\n", - ".$domain:$username-$domain\n", - ; - - ### - # FORMAT OF THE .QMAIL-DEFAULT FILE HERE - print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" " . $svc_acct->email . "\n" - if $vpopmailmachines[0]; - - }else{ - ### - # FORMAT OF THE .QMAIL-DEFAULT FILE HERE - print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" bounce-no-mailbox\n" - if $vpopmailmachines[0]; - } - - print VPOPVIRTUALDOMAINS "$domain:$domain\n"; - - foreach $svc_acct (qsearch('svc_acct', {'domsvc' => $svc_domain->svcnum})) { - my($password)=$svc_acct->getfield('_password'); - my($cpassword,$rpassword); - #if ( ( length($password) <= 8 ) - if ( ( length($password) <= 12 ) - && ( $password ne '*' ) - && ( $password ne '!!' ) - && ( $password ne '' ) - ) { - $cpassword=crypt($password, - $saltset[int(rand(64))].$saltset[int(rand(64))] - ); - $rpassword=$password; - } else { - $cpassword=$password; - $rpassword='UNIX'; - } - - my $username; - - if ($mydomain && ($mydomain eq $svc_domain->domain)) { - $username=$svc_acct->username; - } elsif ($userpolicy =~ /^prepend domsvc$/) { - $username=$svc_acct->domsvc . $svc_acct->username; - } elsif ($userpolicy =~ /^append domsvc$/) { - $username=$svc_acct->username . $svc_acct->domsvc; - } elsif ($userpolicy =~ /^append domain$/) { - $username=$svc_acct->username . $svc_domain->domain; - } elsif ($userpolicy =~ /^append domain$/) { - $username=$svc_acct->username . $svc_domain->domain; - } elsif ($userpolicy =~ /^append \@domain$/) { - $username=$svc_acct->username . '@'. $svc_domain->domain; - } else { - die "Unknown policy in username_policy\n"; - } - - if ($svc_acct->dir ne '/dev/null' || $svc_acct->slipip ne '') { - if ($usernames{$username}++) { - die "Duplicate username detected: $username\n"; - } - } - - if ( $svc_acct->uid =~ /^(\d+)$/ ) { - - die "Non-root user ". $svc_acct->username. " has 0 UID!" - if $svc_acct->uid == 0 && $svc_acct->username ne 'root'; - - if ( $svc_acct->dir ne "/dev/null") { - - ### - # FORMAT OF FreeBSD MASTER PASSWD FILE HERE - print MASTER join(":", - $username, # User name - $cpassword, # Encrypted password - $svc_acct->uid, # User ID - $svc_acct->gid, # Group ID - "", # Login Class - "0", # Password Change Time - "0", # Password Expiration Time - $svc_acct->finger, # Users name - $svc_acct->dir, # Users home directory - $svc_acct->shell, # shell - ), "\n" ; - - - ### - # FORMAT OF THE PASSWD FILE HERE - print PASSWD join(":", - $username, - 'x', # "##". $username, - $svc_acct->uid, - $svc_acct->gid, - $svc_acct->finger, - $svc_acct->dir, - $svc_acct->shell, - ), "\n"; - - ### - # FORMAT OF THE SHADOW FILE HERE - print SHADOW join(":", - $username, - $cpassword, - '', - '', - '', - '', - '', - '', - '', - ), "\n"; - } - } - - ### - # FORMAT OF THE VPASSWD FILE HERE - print VPASSWD join(":", - $svc_acct->username, - $cpassword, - '1', - '0', - $svc_acct->username, - "$vpopdir/domains/" . $svc_domain->domain ."/" . $svc_acct->username, - 'NOQUOTA', - ), "\n"; - - - if ( $svc_acct->slipip ne '' ) { - - ### - # FORMAT OF THE ACP_* FILES HERE - print ACP_PASSWD join(":", - $username, - $cpassword, - "0", - "0", - "", - "", - "", - ), "\n"; - - my($ip)=$svc_acct->slipip; - - unless ( $ip eq '0.0.0.0' || $svc_acct->slipip eq '0e0' ) { - print ACP_DIALUP $username, "\t*\t", $svc_acct->slipip, "\n"; - } - - my %radreply = $svc_acct->radius_reply; - my %radcheck = $svc_acct->radius_check; - - my $radcheck = join ", ", map { qq($_ = "$radcheck{$_}") } keys %radcheck; - $radcheck .= ", " if $radcheck; - - ### - # FORMAT OF THE USERS FILE HERE - print USERS - $username, - qq(\t${textradiusprepend}), - $radcheck, -# qq(Password = "$rpassword"\n\t), - join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply; - - #if ( $ip && $ip ne '0e0' ) { - # #print USERS qq(,\n\tFramed-Address = "$ip"\n\n); - # print USERS qq(,\n\tFramed-IP-Address = "$ip"\n\n); - #} else { - print USERS qq(\n\n); - #} - - } - - ### - # vpopmail directory structure creation - - (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username, 0700) - or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . ": $!"; - (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir", 0700) - or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir: $!"; - (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/cur", 0700) - or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/cur: $!"; - (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/new", 0700) - or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/new: $!"; - (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/tmp", 0700) - or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/tmp: $!"; - - ( open(DOTQMAIL,">$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail") - and flock(DOTQMAIL,LOCK_EX|LOCK_NB) - ) or die "Can't open $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail: $!"; - - my($svc_forward); - foreach $svc_forward (qsearch('svc_forward', {'srcsvc' => $svc_acct->svcnum})) { - my($destination); - if ($svc_forward->dstsvc) { - my $dst_acct = qsearchs('svc_acct', {'svcnum' => $svc_forward->dstsvc}); - my $dst_domain = qsearchs('svc_domain', {'svcnum' => $dst_acct->domsvc}); - $destination = $dst_acct->username . '@' . $dst_domain->domain; - - if ($dst_domain->domain eq $mydomain) { - print VIRTUSERTABLE $svc_acct->username . "@" . $svc_domain->domain . - "\t" . $dst_acct->username . "\n"; - print RECIPIENTMAP $svc_acct->username . "@" . $svc_domain->domain . - ":$destination\n"; - } - } else { - $destination = $svc_forward->dst; - } - - ### - # FORMAT OF .QMAIL FILES HERE - print DOTQMAIL "$destination\n"; - } - - flock(DOTQMAIL,LOCK_UN); - close DOTQMAIL; - - } - - flock(VPASSWD,LOCK_UN); - flock(QMAILDEFAULT,LOCK_UN); - close VPASSWD; - close QMAILDEFAULT; - -} - -### -# FORMAT OF THE ASSIGN/USERS FILE FINAL LINE HERE -print ASSIGN ".\n"; - -print VIRTUSERTABLE @sendmail; - -flock(MASTER,LOCK_UN); -flock(PASSWD,LOCK_UN); -flock(SHADOW,LOCK_UN); -flock(ACP_DIALUP,LOCK_UN); -flock(ACP_PASSWD,LOCK_UN); -flock(USERS,LOCK_UN); -flock(ASSIGN,LOCK_UN); -flock(SENDMAIL_CW,LOCK_UN); -flock(VIRTUSERTABLE,LOCK_UN); -flock(RCPTHOSTS,LOCK_UN); -flock(VPOPRCPTHOSTS,LOCK_UN); -flock(RECIPIENTMAP,LOCK_UN); -flock(VPOPVIRTUALDOMAINS,LOCK_UN); - -close MASTER; -close PASSWD; -close SHADOW; -close ACP_DIALUP; -close ACP_PASSWD; -close USERS; -close ASSIGN; -close SENDMAIL_CW; -close VIRTUSERTABLE; -close RCPTHOSTS; -close VPOPRCPTHOSTS; -close RECIPIENTMAP; -close VPOPVIRTUALDOMAINS; - -### -# export stuff -# - -my($ashellmachine); -foreach $ashellmachine (@shellmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/passwd","root\@$ashellmachine:/etc/passwd.new") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/shadow","root\@$ashellmachine:/etc/shadow.new") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$ashellmachine", - "( ". - "mv /etc/passwd.new /etc/passwd; ". - "mv /etc/shadow.new /etc/shadow; ". - " )" - ) - == 0 or die "ssh error: $!"; -} - -my($bsdshellmachine); -foreach $bsdshellmachine (@bsdshellmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/passwd","root\@$bsdshellmachine:/etc/passwd.new") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/master.passwd","root\@$bsdshellmachine:/etc/master.passwd.new") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$bsdshellmachine", - "( ". - "mv /etc/passwd.new /etc/passwd; ". - #"mv /etc/master.passwd.new /etc/master.passwd; ". - "pwd_mkdb /etc/master.passwd.new; ". - " )" - ) - == 0 or die "ssh error: $!"; -} - -my($nismachine); -foreach $nismachine (@nismachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/passwd","root\@$nismachine:/etc/global/passwd") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/shadow","root\@$nismachine:/etc/global/shadow") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$nismachine", - "( ". - "cd /var/yp; make; ". - " )" - ) - == 0 or die "ssh error: $!"; -} - -my($erpcdmachine); -foreach $erpcdmachine (@erpcdmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/acp_passwd","root\@$erpcdmachine:/usr/annex/acp_passwd") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/acp_dialup","root\@$erpcdmachine:/usr/annex/acp_dialup") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$erpcdmachine", - "( ". - "kill -USR1 \`cat /usr/annex/erpcd.pid\'". - " )" - ) - == 0 or die "ssh error: $!"; -} - -my($radiusmachine); -foreach $radiusmachine (@radiusmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/users","root\@$radiusmachine:/etc/raddb/users") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$radiusmachine", - "( ". - "builddbm". - " )" - ) - == 0 or die "ssh error: $!"; -} - -#my @args = ("/bin/tar", "c", "--force-local", "-C", "$spooldir", "-f", "$spooldir/vpoptarball", "domains"); - -#system {$args[0]} @args; - -my($vpopmailmachine); -foreach $vpopmailmachine (@vpopmailmachines) { - my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachine); - my $scp = new Net::SCP; -# $scp->scp("$spooldir/vpoptarball","root\@$machine:vpoptarball") -# or die "scp error: ". $scp->{errstr}; -# ssh("root\@$machine", -# "( ". -# "rm -rf domains; ". -# "tar xf vpoptarball; ". -# "chown -R $vpopuid:$vpopgid domains; ". -# "tar cf vpoptarball domains; ". -# "cd $vpopdir; ". -# "tar xf ~/vpoptarball; ". -# " )" -# ) -# == 0 or die "ssh error: $!"; - - chdir $spooldir; - my @args = ("$rsync", "-rlpt", "-e", "$ssh", "domains/", "vpopmail\@$machine:$vpopdir/domains/"); - - system {$args[0]} @args; - - $scp->scp("$spooldir/assign","root\@$machine:/var/qmail/users/assign") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/vpopvirtualdomains","root\@$machine:/var/qmail/control/virtualdomains") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/vpoprcpthosts","root\@$machine:/var/qmail/control/rcpthosts") - or die "scp error: ". $scp->{errstr}; - - ssh("root\@$machine", - "( ". - $vpopmailrestart . - " )" - ) - == 0 or die "ssh error: $!"; - - -} - -my($sendmailmachine); -foreach $sendmailmachine (@sendmailmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/sendmail.cw","root\@$sendmailmachine:$sendmailconfigpath/sendmail.cw.new") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/virtusertable","root\@$sendmailmachine:$sendmailconfigpath/virtusertable.new") - or die "scp error: ". $scp->{errstr}; - ssh("root\@$sendmailmachine", - "( ". - "mv $sendmailconfigpath/sendmail.cw.new $sendmailconfigpath/sendmail.cw; ". - "mv $sendmailconfigpath/virtusertable.new $sendmailconfigpath/virtusertable; ". - $sendmailrestart. - " )" - ) - == 0 or die "ssh error: $!"; -} - -my($qmailmachine); -foreach $qmailmachine (@qmailmachines) { - my $scp = new Net::SCP; - $scp->scp("$spooldir/recipientmap","root\@$qmailmachine:/var/qmail/control/recipientmap") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/virtualdomains","root\@$qmailmachine:/var/qmail/control/virtualdomains") - or die "scp error: ". $scp->{errstr}; - $scp->scp("$spooldir/rcpthosts","root\@$qmailmachine:/var/qmail/control/rcpthosts") - or die "scp error: ". $scp->{errstr}; - #ssh("root\@$qmailmachine","/etc/init.d/qmail restart") - # == 0 or die "ssh error: $!"; -} - -unlink $spoollock; -flock(EXPORT,LOCK_UN); -close EXPORT; - -# - -sub usage { - die "Usage:\n\n svc_acct.export user\n"; -} - diff --git a/bin/svc_acct.import b/bin/svc_acct.import index eb94e1c37..aff26b943 100755 --- a/bin/svc_acct.import +++ b/bin/svc_acct.import @@ -1,5 +1,4 @@ #!/usr/bin/perl -Tw -# $Id: svc_acct.import,v 1.17 2001-08-19 10:25:44 ivan Exp $ use strict; use vars qw(%part_svc); diff --git a/bin/svc_domain.erase b/bin/svc_domain.erase index c0236614b..435dd5fdd 100755 --- a/bin/svc_domain.erase +++ b/bin/svc_domain.erase @@ -1,6 +1,4 @@ #!/usr/bin/perl -w -# -# $Id: svc_domain.erase,v 1.1 2002-04-20 11:57:35 ivan Exp $ use strict; use FS::UID qw(adminsuidsetup); diff --git a/conf/blank_logo.eps b/conf/blank_logo.eps new file mode 100644 index 000000000..e7e3bab51 --- /dev/null +++ b/conf/blank_logo.eps @@ -0,0 +1,22 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%BoundingBox: 0 0 1 1 +%%HiResBoundingBox: 0 0 0 0 +%%Creator: Karbon14 EPS Exportfilter 0.5 +%%CreationDate: (01/03/2007 11:23:26 PM) +%%For: (ivan) () +%%Title: () + +/N {newpath} def +/C {closepath} def +/m {moveto} def +/c {curveto} def +/l {lineto} def +/s {stroke} def +/f {fill} def +/w {setlinewidth} def +/d {setdash} def +/r {setrgbcolor} def +/S {gsave} def +/R {grestore} def + +%%EOF diff --git a/conf/impending_recur_template b/conf/impending_recur_template new file mode 100644 index 000000000..9075ac8bf --- /dev/null +++ b/conf/impending_recur_template @@ -0,0 +1,20 @@ + + +Ivan Kohler +12345 Test Lane +Truckee, CA 96161 + + +{ $first; } { $last; }: + + We thank you for your continuing patronage. This notice is to remind you +that your { $packages->[0] } Internet service will expire on { use Date::Format; time2str("%x", $recurdates->[0]); }. +At that time we will begin charging you on a recurring basis so that we may +continue your service uninterrupted. + +Very Truly Yours, + + SISD Service Team + + + diff --git a/conf/invoice_html b/conf/invoice_html index 32e5362f9..fa1f9434e 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -116,13 +116,6 @@ </table> <br><br> -<!-- <p><b><font size="+1">N</font><font size="+0">OTES</font></b> - - <ol> - <li>Please make your check payable to <b>Ivan Kohler</b> - <li>If you have any questions please email or telephone. - </ol> ---> <%= $notes %> <hr NOSHADE SIZE=2 COLOR="#000000"> diff --git a/conf/invoice_html_statement b/conf/invoice_html_statement new file mode 100644 index 000000000..4e4d259af --- /dev/null +++ b/conf/invoice_html_statement @@ -0,0 +1,124 @@ +<STYLE TYPE="text/css"> +.invoice { font-family: sans-serif; font-size: 10pt } +.invoice_header { font-size: 10pt } +.invoice_headerright TH { border-top: 2px solid #000000; border-bottom: 2px solid #000000 } +.invoice_headerright TD { font-size: 10pt; empty-cells: show } +.invoice_longtable table { cellspacing: none } +.invoice_longtable TH { border-top: 2px solid #000000; border-bottom: 1px solid #000000; padding-left: none; padding-right: none; font-size: 10pt } +.invoice_desc TD { border-top: 2px solid #000000; font-weight: bold; font-size: 10pt } +.invoice_extdesc TD { font-size: 8pt } +.invoice_totaldesc TD { font-size: 10pt; empty-cells: show } +</STYLE> + +<table class="invoice" bgcolor="#ffffff" WIDTH=768 CELLSPACING=8><tr><td> + + <table class="invoice_header" width="100%"> + <tr> + <td><img src="<%= $cid ? "cid:$cid" : "cust_bill-logo.cgi?$template" %>"></td> + <td align="left"><%= $returnaddress %></td> + <td align="right"> + <table CLASS="invoice_headerright" cellspacing=0> + <tr> + <td align="right"> + Invoice date<BR> + <B><%= $date %></B> + </td> + <td> + </td> + <td align="left"> + Invoice number<BR> + <B><%= $invnum %></B> + </td> + </tr> + <tr> + <th> </th> + <th colspan=1 align="center"> + <FONT SIZE="+3">S</FONT><FONT SIZE="+2">TATEMENT</FONT> + </th> + <th> </th> + </tr> + </table> + </td> + </tr> + + <tr> + <td> + </td> + <td align="left"> + <b><%= $payname %></b><BR> + <%= join('<BR>', grep length($_), $company, + $address1, + $address2, + "$city, $state $zip", + $country, + ) + %> + </td> + <td align="right"> + Terms: <%= $terms %><BR> + <%= $po_line %> + </td> + </tr> + + </table> + + <p><b><font size="+1">C</font><font size="+0">HARGES</font></b> + <p> + <table class="invoice_longtable" CELLSPACING=0 WIDTH="100%"> + <tr> + <th align="center">Ref</th> + <th align="left">Description</th> + <th align="right">Amount</th> + </tr> + <%= + + foreach my $line ( @detail_items ) { + $OUT .= + '<tr class="invoice_desc">'. + '<td align="center">'. $line->{'ref'}. '</td>'. + '<td align="left">'. $line->{'description'}. '</td>'. + '<td align="right">'. $line->{'amount'}. '</td>'. + '</tr>' + ; + foreach my $ext_desc ( @{$line->{'ext_description'} } ) { + $OUT .= + '<tr class="invoice_extdesc">'. + '<td></td>'. + '<td align="left">- '. $ext_desc. '</td>'. + '<td></td>'. + '</tr>' + } + } + + my $style = 'border-top: 3px solid #000000;'; + my $linenum = 0; + + foreach my $line ( @total_items ) { + + $style .= 'border-bottom: 3px solid #000000;' + if ++$linenum == scalar(@total_items); + + $OUT .= + '<tr class="invoice_totaldesc">'. + qq(<td style="$style"> </td>). + qq(<td align="left" style="$style">). + $line->{'total_item'}. '</td>'. + qq(<td align="right" style="$style">). + $line->{'total_amount'}. '</td>'. + '</tr>' + ; + + $style=''; + + } + + %> + </table> + <br><br> + +<%= $notes %> + + <hr NOSHADE SIZE=2 COLOR="#000000"> + <p align="center"><%= $footer %> + +</td></tr></table> diff --git a/conf/invoice_latex_statement b/conf/invoice_latex_statement new file mode 100644 index 000000000..302306aa7 --- /dev/null +++ b/conf/invoice_latex_statement @@ -0,0 +1,244 @@ +%% file: Standard Multipage.tex
+%% Purpose: Multipage bill template for e-Bills
+%%
+%% Created by Mark Asplen-Taylor
+%% Asplen Management Ltd
+%% www.asplen.co.uk
+%%
+%% Modified for Freeside by Kristian Hoffman
+%%
+%% Changes
+%% 0.1 4/12/00 Created
+%% 0.2 18/10/01 More fields added
+%% 1.0 16/11/01 RELEASED
+%% 1.2 16/10/02 Invoice number added
+%% 1.3 2/12/02 Logo graphic added
+%% 1.4 7/2/03 Multipage headers/footers added
+%% n/a forked for Freeside; checked into CVS
+%%
+
+\documentclass[letterpaper]{article}
+
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0cm} % top margin to top of header
+\addtolength{\hoffset}{-0.6cm} % left margin on page
+\addtolength{\topmargin}{-1.25cm} % top margin to top of header
+\setlength{\headheight}{2.0cm} % height of header
+\setlength{\headsep}{1.0cm} % between header and text
+\setlength{\footskip}{1.0cm} % bottom of footer from bottom of text
+
+%\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\textwidth}{19.5cm}
+\setlength{\textheight}{19.5cm}
+\setlength{\oddsidemargin}{-0.9cm} % odd page left margin
+\setlength{\evensidemargin}{-0.9cm} % even page left margin
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{1pt}
+
+% Adjust the inset of the mailing address
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
+% Adjust the inset of the return address and logo
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
+% New command for address lines i.e. skip them if blank
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}
+
+% Inserts dollar symbol
+\newcommand{\dollar}[1][]{\symbol{36}}
+
+% Remove plain style header/footer
+\fancypagestyle{plain}{
+ \fancyhead{}
+}
+\fancyhf{}
+
+% Define fancy header/footer for first and subsequent pages
+\fancyfoot[C]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \small{
+[@-- $footer --@]
+ }
+ }
+ { % ... pages
+ \small{
+[@-- $smallfooter --@]
+ }
+ }
+}
+
+\fancyfoot[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ }
+ { % ... pages
+ \small{\thepage\ of \pageref{LastPage}}
+ }
+}
+
+\fancyhead[L]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \returninset
+ \makebox{
+ \begin{tabular}{ll}
+ \includegraphics{[@-- $conf_dir --@]/logo.eps} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+ \end{tabular}
+ }
+ }
+ { % ... pages
+ %\includegraphics{[@-- $conf_dir --@]/logo.eps} % Uncomment if you want the logo on all pages.
+ }
+}
+
+\fancyhead[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \begin{tabular}{rcl}
+ Invoice date & & Invoice number \\
+ \vspace{0.2cm}
+ \textbf{[@-- $date --@]} & & \textbf{[@-- $invnum --@]} \\\hline
+ \rule{0pt}{5ex} &~~ \huge{\textsc{Statement}} & \\
+ \vspace{-0.2cm}
+ & & \\\hline
+ \end{tabular}
+ }
+ { % ... pages
+ \small{
+ \begin{tabular}{ll}
+ Invoice date & Invoice number\\
+ \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]}\\
+ \end{tabular}
+ }
+ }
+}
+
+\pagestyle{fancy}
+
+
+%% Font options are:
+%% bch Bitsream Charter
+%% put Utopia
+%% phv Adobe Helvetica
+%% pnc New Century Schoolbook
+%% ptm Times
+%% pcr Courier
+
+\renewcommand{\familydefault}{phv}
+
+
+% Commands for freeside description...
+\newcommand{\FSdesc}[3]{
+ \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &
+ \textbf{#2} &
+ \multicolumn{1}{r}{\textbf{\dollar #3}}\\
+}
+% ...extended description...
+\newcommand{\FSextdesc}[1]{
+ \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
+ \multicolumn{2}{l}{\small{~-~#1}}\\
+}
+% ...and total line items.
+\newcommand{\FStotaldesc}[2]{
+ & \multicolumn{1}{l}{#1} & #2\\
+}
+
+
+\begin{document}
+%
+%% Headers and footers defined for the first page
+%
+%% The LH Heading comprising logo
+%% UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo
+%
+%% The Heading comprising isue date, customer ref & INVOICE name
+%
+%% Header & footer changes for subsequent pages
+%
+%
+%
+\begin{tabular}{ll}
+\addressinset \rule{0cm}{0cm} &
+\makebox{
+\begin{minipage}[t]{5.0cm}
+\vspace{0.25cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}
+\end{tabular}
+\hfill
+\makebox{
+\begin{minipage}[t]{6.4cm}
+\begin{flushright}
+Terms: [@-- $terms --@]\\
+[@-- $po_line --@]\\
+\end{flushright}
+\end{minipage}}
+\vspace{1.5cm}
+%
+\section*{\textsc{Charges}}
+\begin{longtable}{clr}
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[12.8cm][l]{\textbf{Description}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endfirsthead
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[12.8cm][l]{\textbf{Description}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endhead
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\
+\endfoot
+\hline
+[@--
+
+ foreach my $line (@total_items) {
+ $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
+ '{' . $line->{'total_amount'} . '}' . "\n";
+ }
+
+--@]
+\hline
+\endlastfoot
+[@--
+
+ foreach my $line (@detail_items) {
+ my $ext_description = $line->{'ext_description'};
+
+ # Don't break-up small packages.
+ my $rowbreak = @$ext_description < 5 ? '*' : '';
+
+ $OUT .= "\\hline\n";
+ $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' .
+ '{' . $line->{'amount'} . "}${rowbreak}\n";
+
+ foreach my $ext_desc (@$ext_description) {
+ $ext_desc = substr($ext_desc, 0, 80) . '...'
+ if (length($ext_desc) > 80);
+ $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";
+ }
+
+ }
+
+--@]
+\end{longtable}
+\vfill
+[@-- $notes --@]
+\end{document}
diff --git a/conf/invoice_latexnotes_statement b/conf/invoice_latexnotes_statement new file mode 100644 index 000000000..0836d2745 --- /dev/null +++ b/conf/invoice_latexnotes_statement @@ -0,0 +1,8 @@ +%% +%% Add any customer specific notes in here +%% +\section*{\textsc{Notes}} +\begin{enumerate} +\item This statement reflects current charges and payments. +\item If you have any questions please email or telephone. +\end{enumerate} diff --git a/conf/invoice_template_statement b/conf/invoice_template_statement new file mode 100644 index 000000000..674416a87 --- /dev/null +++ b/conf/invoice_template_statement @@ -0,0 +1,27 @@ + + Statement + { substr("Page $page of $total_pages ", 0, 19); } { use Date::Format; time2str("%x", $date); } FS-{ $invnum; } + + +Ivan Kohler +12345 Test Lane +Truckee, CA 96161 + + +{ $address[0]; } +{ $address[1]; } +{ $address[2]; } +{ $address[3]; } +{ $address[4]; } +{ $address[5]; } + +{ + join("\n", + map { + my ( $desc, $price ) = @{$_}; + " ". substr( $desc. " "x65, 0, 65). " ". substr( $price. " "x11, 0, 11); + } invoice_lines(31) + ); +} + + -=> Freeside - open-source billing for ISPs - http://www.sisd.com/freeside <=- diff --git a/conf/ticket_system b/conf/ticket_system new file mode 100644 index 000000000..631f98a94 --- /dev/null +++ b/conf/ticket_system @@ -0,0 +1 @@ +RT_Internal diff --git a/eg/TEMPLATE_cust_main.import b/eg/TEMPLATE_cust_main.import index e91a2f1d2..f6d88c701 100755 --- a/eg/TEMPLATE_cust_main.import +++ b/eg/TEMPLATE_cust_main.import @@ -1,8 +1,6 @@ #!/usr/bin/perl -w # # Template for importing legacy customer data -# -# $Id: TEMPLATE_cust_main.import,v 1.4 2001-08-21 02:44:47 ivan Exp $ use strict; use Date::Parse; diff --git a/eg/table_template-svc.pm b/eg/table_template-svc.pm index 7f7ef4b68..47dcbe6e4 100644 --- a/eg/table_template-svc.pm +++ b/eg/table_template-svc.pm @@ -59,6 +59,60 @@ points to. You can ask the object for a copy with the I<hash> method. sub table { 'table_name'; } +sub table_info { + { + 'name' => 'Example', + 'name_plural' => 'Example services', #optional, + 'longname_plural' => 'Example services', #optional + 'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first) + 'display_weight' => 100, + 'cancel_weight' => 100, + 'fields' => { + 'field' => 'Description', + 'another_field' => { + 'label' => 'Description', + 'def_label' => 'Description for service definitions', + 'type' => 'text', + 'disable_default' => 1, #disable switches + 'disable_fixed' => 1, # + 'disable_inventory' => 1, # + }, + 'foreign_key' => { + 'label' => 'Description', + 'def_label' => 'Description for service defs', + 'type' => 'select', + 'select_table' => 'foreign_table', + 'select_key' => 'key_field_in_table', + 'select_label' => 'label_field_in_table', + }, + + }, + }; +} + +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +#or something more complicated if necessary +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('search_field', $string); +} + +=item label + +Returns a meaningful identifier for this example + +=cut + +sub label { + my $self = shift; + $self->label_field; #or something more complicated if necessary +} + =item insert Adds this record to the database. If there is an error, returns the error, diff --git a/etc/megapop.pl b/etc/megapop.pl index b250bcdde..e2930fb55 100755 --- a/etc/megapop.pl +++ b/etc/megapop.pl @@ -1,7 +1,5 @@ #!/usr/bin/perl -Tw # -# $Id: megapop.pl,v 1.1 1999-04-19 10:32:44 ivan Exp $ -# # this will break when megapop changes the URL or format of their listing page. # that's stupid. perhaps they can provide a machine-readable listing? diff --git a/fs_selfservice/DEPLOY b/fs_selfservice/DEPLOY index 63412784f..f39c50564 100755 --- a/fs_selfservice/DEPLOY +++ b/fs_selfservice/DEPLOY @@ -12,10 +12,10 @@ cd .. ( cd ..; make deploy; cd fs_selfservice ) -cp /home/ivan/freeside/fs_selfservice/FS-SelfService/cgi/* /var/www/MyAccount -chown freeside /var/www/MyAccount/*.cgi -chmod 755 /var/www/MyAccount/*.cgi -ln -s /var/www/MyAccount/selfservice.cgi /var/www/MyAccount/index.cgi || true +#cp /home/ivan/freeside/fs_selfservice/FS-SelfService/cgi/* /var/www/MyAccount +#chown freeside /var/www/MyAccount/*.cgi +#chmod 755 /var/www/MyAccount/*.cgi +#ln -s /var/www/MyAccount/selfservice.cgi /var/www/MyAccount/index.cgi || true #cp /home/ivan/freeside/fs_signup/FS-SignupClient/cgi/* /var/www/signup/ ##mv /var/www/signup/signup-snarf.html /var/www/signup/signup.html #!!!!! diff --git a/fs_selfservice/FS-SelfService/Makefile.PL b/fs_selfservice/FS-SelfService/Makefile.PL index 0b7fc4606..85c92b4fb 100644 --- a/fs_selfservice/FS-SelfService/Makefile.PL +++ b/fs_selfservice/FS-SelfService/Makefile.PL @@ -9,6 +9,7 @@ WriteMakefile( ], 'INSTALLSCRIPT' => '/usr/local/sbin', 'INSTALLSITEBIN' => '/usr/local/sbin', + 'INSTALLSITESCRIPT' => '/usr/local/sbin', #recent deb users this... 'PERM_RWX' => '750', 'PREREQ_PM' => { 'Storable' => 2.09, diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index bfce1287b..fe17fae5d 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -1,7 +1,7 @@ package FS::SelfService; use strict; -use vars qw($VERSION @ISA @EXPORT_OK $dir $socket %autoload $tag); +use vars qw($VERSION @ISA @EXPORT_OK $DEBUG $dir $socket %autoload $tag); use Exporter; use Socket; use FileHandle; @@ -13,6 +13,8 @@ $VERSION = '0.03'; @ISA = qw( Exporter ); +$DEBUG = 0; + $dir = "/usr/local/freeside"; $socket = "$dir/selfservice_socket"; $socket .= '.'.$tag if defined $tag && length($tag); @@ -27,19 +29,25 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'customer_info' => 'MyAccount/customer_info', 'edit_info' => 'MyAccount/edit_info', #add to ss cgi! 'invoice' => 'MyAccount/invoice', + 'invoice_logo' => 'MyAccount/invoice_logo', 'list_invoices' => 'MyAccount/list_invoices', #? 'cancel' => 'MyAccount/cancel', #add to ss cgi! 'payment_info' => 'MyAccount/payment_info', 'process_payment' => 'MyAccount/process_payment', 'process_prepay' => 'MyAccount/process_prepay', - 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss cgi! + 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss cgi (added?) + 'list_svcs' => 'MyAccount/list_svcs', #add to ss cgi (added?) + 'list_svc_usage' => 'MyAccount/list_svc_usage', 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi! + 'change_pkg' => 'MyAccount/change_pkg', + 'order_recharge' => 'MyAccount/order_recharge', 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi! 'charge' => 'MyAccount/charge', #? 'part_svc_info' => 'MyAccount/part_svc_info', 'provision_acct' => 'MyAccount/provision_acct', 'provision_external' => 'MyAccount/provision_external', 'unprovision_svc' => 'MyAccount/unprovision_svc', + 'myaccount_passwd' => 'MyAccount/myaccount_passwd', 'signup_info' => 'Signup/signup_info', 'new_customer' => 'Signup/new_customer', 'agent_login' => 'Agent/agent_login', @@ -72,6 +80,7 @@ foreach my $autoload ( keys %autoload ) { if ( ref($_[0]) ) { $param = shift; } else { + #warn scalar(@_). ": ". join(" / ", @_); $param = { @_ }; } @@ -87,6 +96,8 @@ foreach my $autoload ( keys %autoload ) { sub simple_packet { my $packet = shift; + warn "sending ". $packet->{_packet}. " to server" + if $DEBUG; socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!"; nstore_fd($packet, \*SOCK) or die "can't send packet: $!"; @@ -98,9 +109,16 @@ sub simple_packet { # my $w = new IO::Select; # $w->add(\*SOCK); # my @wait = $w->can_read; + + warn "reading message from server" + if $DEBUG; + my $return = fd_retrieve(\*SOCK) or die "error reading result: $!"; die $return->{'_error'} if defined $return->{_error} && $return->{_error}; + warn "returning message to client" + if $DEBUG; + $return; } @@ -908,15 +926,15 @@ sub expselect { } my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!; for ( 1 .. 12 ) { - $return .= "<OPTION"; + $return .= qq!<OPTION VALUE="$_"!; $return .= " SELECTED" if $_ == $m; $return .= ">$_"; } $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!; my @t = localtime; my $thisYear = $t[5] + 1900; - for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. 2037 ) { - $return .= "<OPTION"; + for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) { + $return .= qq!<OPTION VALUE="$_"!; $return .= " SELECTED" if $_ == $y; $return .= ">$_"; } diff --git a/fs_selfservice/FS-SelfService/cgi/agent.cgi b/fs_selfservice/FS-SelfService/cgi/agent.cgi index 695d20e4c..6e8de619a 100644 --- a/fs_selfservice/FS-SelfService/cgi/agent.cgi +++ b/fs_selfservice/FS-SelfService/cgi/agent.cgi @@ -1,3 +1,4 @@ +#!/usr/bin/perl -T #!/usr/bin/perl -Tw #some false laziness w/selfservice.cgi @@ -193,7 +194,7 @@ sub process_signup { } else { $action = 'agent_main'; my $agent_info = agent_info( 'session_id' => $session_id ); - $agent_info->{'message'} = 'Signup sucessful'; + $agent_info->{'message'} = 'Signup successful'; $agent_info; } @@ -324,7 +325,7 @@ sub process_svc_acct { $action = 'agent_provision'; return { %{agent_provision()}, - 'message' => $result->{'svc'}. ' setup sucessfully.', + 'message' => $result->{'svc'}. ' setup successfully.', }; } @@ -343,7 +344,7 @@ sub process_svc_external { %{agent_provision()}, 'message' => $result->{'error'} ? '<FONT COLOR="#FF0000">'. $result->{'error'}. '</FONT>' - : $result->{'svc'}. ' setup sucessfully'. + : $result->{'svc'}. ' setup successfully'. ': serial number '. sprintf('%010d', $result->{'id'}). '-'. $result->{'title'} }; @@ -403,7 +404,7 @@ sub process_order_pkg { #$cgi->delete( grep { $_ ne 'custnum' } $cgi->param ); return { %{view_customer()}, - 'message' => 'Package order sucessful.', + 'message' => 'Package order successful.', }; } diff --git a/fs_selfservice/FS-SelfService/cgi/change_password.html b/fs_selfservice/FS-SelfService/cgi/change_password.html new file mode 100644 index 000000000..af7b45313 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/change_password.html @@ -0,0 +1,53 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Change password</FONT><BR><BR> + +<%= if ( $error ) { + $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!; +} ''; %> + +<FORM ACTION="<%= $selfurl %>" METHOD="POST"> +<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>"> +<INPUT TYPE="hidden" NAME="action" VALUE="process_change_password"> + +<TABLE BGCOLOR="#cccccc"> + + <TR> + <TH ALIGN="right">Change password for account: </TH> + <TD> + <SELECT NAME="svcnum"> + <%= foreach my $svc ( @svcs ) { + $OUT .= '<OPTION VALUE="'. $svc->{'svcnum'}. '"'. + ( $svc->{'svcnum'} eq $svcnum ? ' SELECTED' : '' ). '>'. + $svc->{'label'}. ': '. $svc->{'value'}. "\n"; + } + %> + </SELECT> + </TD> + </TR> + + <TR> + <TH ALIGN="right">New password: </TH> + <TD><INPUT TYPE="password" NAME="new_password" SIZE="18"></TD> + </TR> + + <TR> + <TH ALIGN="right">Re-enter new password: </TH> + <TD><INPUT TYPE="password" NAME="new_password2" SIZE="18"></TD> + </TR> + +</TABLE> +<BR> + +<INPUT TYPE="submit" VALUE="Change password"> + +</FORM> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/change_pkg.html b/fs_selfservice/FS-SelfService/cgi/change_pkg.html new file mode 100644 index 000000000..a841308a5 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/change_pkg.html @@ -0,0 +1,37 @@ +<SCRIPT TYPE="text/javascript"> +function enable_change_pkg () { + if ( document.ChangePkgForm.pkgpart.selectedIndex > 0 ) { + document.ChangePkgForm.submit.disabled = false; + } else { + document.ChangePkgForm.submit.disabled = true; + } +} +</SCRIPT> +<FONT SIZE=4>Purchase replacement package for "<%= $pkg; %>"</FONT><BR><BR> +<%= if ( $error ) { + $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!; +} ''; %> +<FORM NAME="ChangePkgForm" ACTION="<%= $selfurl %>" METHOD=POST> +<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>"> +<INPUT TYPE="hidden" NAME="action" VALUE="process_change_pkg"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> +<INPUT TYPE="hidden" NAME="pkg" VALUE="<%= $pkg %>"> +<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> +<TR> + <TD COLSPAN=2><SELECT NAME="pkgpart" onChange="enable_change_pkg()"> + <OPTION VALUE=""> + + <%= + foreach my $part_pkg ( @part_pkg ) { + $OUT .= '<OPTION VALUE="'. $part_pkg->{'pkgpart'}. '"'; + $OUT .= ' SELECTED' if $pkgpart && $part_pkg->{'pkgpart'} == $pkgpart; + $OUT .= '>'. $part_pkg->{'pkg'}; + } + %> + + </SELECT></TD> +</TR> +</TABLE> +<INPUT NAME="submit" TYPE="submit" VALUE="Purchase" disabled> +</FORM> + diff --git a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi new file mode 100644 index 000000000..5f344a32e --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi @@ -0,0 +1,19 @@ +#!/usr/bin/perl -T +#!/usr/bin/perl -Tw + +use strict; +use CGI; +use FS::SelfService qw( invoice_logo ); + +my $cgi = new CGI; + +my($query) = $cgi->keywords; +$query =~ /^([^\.\/]*)$/ or '' =~ /^()$/; +my $templatename = $1; +my $hashref = invoice_logo('templatename' => $templatename); + +print $cgi->header( '-type' => $hashref->{'content_type'}, + '-expires' => 'now', + ). + $hashref->{'logo'}; + diff --git a/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html new file mode 100644 index 000000000..d08ab9679 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html @@ -0,0 +1,10 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> +<%= include('change_pkg') %> +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html new file mode 100755 index 000000000..c01b6b384 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html @@ -0,0 +1,10 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> +<%= include('order_pkg') %> +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/make_payment.html b/fs_selfservice/FS-SelfService/cgi/make_payment.html index 1bbbe90b2..64b1e00b5 100644 --- a/fs_selfservice/FS-SelfService/cgi/make_payment.html +++ b/fs_selfservice/FS-SelfService/cgi/make_payment.html @@ -1,5 +1,18 @@ <HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> -<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<BODY BGCOLOR="#eeeeee"> +<script language="JavaScript"><!-- + var mywindow = -1; + function myopen(filename,windowname,properties) { + myclose(); + mywindow = window.open(filename,windowname,properties); + } + function myclose() { + if ( mywindow != -1 ) + mywindow.close(); + mywindow = -1 + } +//--></script> +<FONT SIZE=5>MyAccount</FONT><BR><BR> <%= $url = "$selfurl?session=$session_id;action="; ''; %> <%= include('myaccount_menu') %> <TD VALIGN="top"> @@ -60,6 +73,9 @@ </TABLE> </TD> </TR><TR> + <TD ALIGN="right">CVV2 (<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)</TD> + <TD><INPUT TYPE="text" NAME="paycvv" VALUE="" SIZE=4 MAXLENGTH=4></TD></TR> +</TR><TR> <TD ALIGN="right">Exact name on card</TD> <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD> </TR><TR> diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index f2e5e998e..47fcfc127 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -12,7 +12,8 @@ my @menu = ( { title=>' ' }, { title=>'Purchase', size=>'+1', }, - { title=>'Purchase additional package*', url=>'order', 'indent'=>2 }, + { title=>'Purchase additional package', + url=>'customer_order_pkg', 'indent'=>2 }, ); if ( 1 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy @@ -30,14 +31,15 @@ push @menu, ( { title=>' ' }, +{ title=>'View my usage', url=>'view_usage', size=>'+1', }, { title=>'Setup my services', url=>'provision', size=>'+1', }, { title=>' ' }, { title=>'Change my information', size=>'+1', }, - { title=>'Change payment information*', url=>'change_bill', indent=>2 }, - { title=>'Change service address*', url=>'change_ship', indent=>2 }, - { title=>'Change password(s)*', url=>'hmmmFIXME', indent=>2 }, +# { title=>'Change payment information*', url=>'change_bill', indent=>2 }, +# { title=>'Change service address*', url=>'change_ship', indent=>2 }, + { title=>'Change password(s)', url=>'change_password', indent=>2 }, { title=>' ' }, @@ -82,8 +84,8 @@ foreach my $item ( @menu ) { %> +<TR><TD STYLE="border-right: 1px solid black" HEIGHT="100%"><BR><BR><BR><BR></TD></TR> + </TABLE> -<A HREF="passwd.html">(tempFIXME) Change password(s)</A><BR><BR> -* coming soon </TD> diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.cgi b/fs_selfservice/FS-SelfService/cgi/passwd.cgi index d77876e37..87e5e6843 100755 --- a/fs_selfservice/FS-SelfService/cgi/passwd.cgi +++ b/fs_selfservice/FS-SelfService/cgi/passwd.cgi @@ -1,3 +1,4 @@ +#!/usr/bin/perl -T #!/usr/bin/perl -Tw use strict; diff --git a/fs_selfservice/FS-SelfService/cgi/payment_results.html b/fs_selfservice/FS-SelfService/cgi/payment_results.html index de6c54dae..9fe400faf 100644 --- a/fs_selfservice/FS-SelfService/cgi/payment_results.html +++ b/fs_selfservice/FS-SelfService/cgi/payment_results.html @@ -7,7 +7,7 @@ <%= if ( $error ) { $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!; } else { - $OUT .= 'Your payment was processed sucessfully. Thank you.'; + $OUT .= 'Your payment was processed successfully. Thank you.'; } %> </TD></TR></TABLE> <HR> diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_password.html b/fs_selfservice/FS-SelfService/cgi/process_change_password.html new file mode 100644 index 000000000..4fdee79f3 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_change_password.html @@ -0,0 +1,13 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Password changed for <%= $value %> <%= $label %>.</FONT> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> + diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html new file mode 100644 index 000000000..9347434ba --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html @@ -0,0 +1,13 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Package change successful.</FONT> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> + diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html new file mode 100755 index 000000000..79be5eba5 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html @@ -0,0 +1,13 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Package order successful.</FONT> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> + diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html b/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html new file mode 100644 index 000000000..851bbed44 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html @@ -0,0 +1,13 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4><%= $svc %> recharged successfully.</FONT> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> + diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html index 200a80dc9..3b812919a 100644 --- a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html +++ b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html @@ -4,7 +4,7 @@ <%= include('myaccount_menu') %> <TD VALIGN="top"> -<FONT SIZE=4><%= $svc %> setup sucessfully.</FONT> +<FONT SIZE=4><%= $svc %> setup successfully.</FONT> </TD></TR></TABLE> <HR> diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html index 2328fa10f..19fec737f 100644 --- a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html +++ b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html @@ -4,7 +4,7 @@ <%= include('myaccount_menu') %> <TD VALIGN="top"> -<FONT SIZE=4><%= $svc %> setup sucessfully.</FONT> +<FONT SIZE=4><%= $svc %> setup successfully.</FONT> <BR><BR>Your serial number is <%= sprintf("%010d-$title", $id) %> diff --git a/fs_selfservice/FS-SelfService/cgi/provision_list.html b/fs_selfservice/FS-SelfService/cgi/provision_list.html index 0f68dfe3c..88d1c848b 100644 --- a/fs_selfservice/FS-SelfService/cgi/provision_list.html +++ b/fs_selfservice/FS-SelfService/cgi/provision_list.html @@ -16,9 +16,11 @@ function areyousure(href, message) { ) { $OUT .= #'<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#ffffff">'. - '<TR><TH BGCOLOR="#6666ff" COLSPAN=3>'. - $pkg->{'pkg'}. - '</TH></TR>'; + '<TR><TH BGCOLOR="#6666ff" COLSPAN=2>'. + $pkg->{'pkg'}. '</TH><TH BGCOLOR="#6666ff" >' . + qq!(<A style="font-size: smaller;color: #000000" HREF="! . + qq!${url}customer_change_pkg;pkgnum=$pkg->{'pkgnum'};pkg=$pkg->{'pkg'}">! . + 'change</A>)</TH></TR>'; my $col1 = "ffffff"; my $col2 = "dddddd"; @@ -75,7 +77,10 @@ function areyousure(href, message) { $OUT .= "<TR>$td COLSPAN=3 ALIGN=center>". qq!<A HREF="$link">!. 'Setup '. $part_svc->{'svc'}. '</A> '. '('. $part_svc->{'num_avail'}. ' available)'. - '</TD></TR>'; + '</TD></TR>' + #self-service only supports these services so far + if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external ); + $col = $col eq $col1 ? $col2 : $col1; } diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_results.html b/fs_selfservice/FS-SelfService/cgi/recharge_results.html index ec3ea2c7a..b1eb7cb7a 100644 --- a/fs_selfservice/FS-SelfService/cgi/recharge_results.html +++ b/fs_selfservice/FS-SelfService/cgi/recharge_results.html @@ -7,7 +7,7 @@ <%= if ( $error ) { $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your prepaid card: $error</FONT>!; } else { - $OUT .= 'Prepaid card recharge sucessful!<BR><BR>'; + $OUT .= 'Prepaid card recharge successful!<BR><BR>'; $OUT .= '$'. sprintf('%.2f', $amount). ' added to your account.<BR><BR>' if $amount; diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index 034a684c6..975203dc8 100644 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -1,22 +1,26 @@ #!/usr/bin/perl -Tw use strict; -use vars qw($cgi $session_id $form_max $template_dir); +use vars qw($DEBUG $cgi $session_id $form_max $template_dir); use subs qw(do_template); use CGI; use CGI::Carp qw(fatalsToBrowser); use Text::Template; use HTML::Entities; +use Date::Format; use FS::SelfService qw( login customer_info invoice payment_info process_payment process_prepay - list_pkgs + list_pkgs order_pkg signup_info order_recharge part_svc_info provision_acct provision_external - unprovision_svc + unprovision_svc change_pkg + list_svcs list_svc_usage myaccount_passwd ); $template_dir = '.'; +$DEBUG = 1; + $form_max = 255; $cgi = new CGI; @@ -62,14 +66,19 @@ $session_id = $cgi->param('session'); #order|pw_list XXX ??? $cgi->param('action') =~ - /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc)$/ + /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|change_password|process_change_password)$/ or die "unknown action ". $cgi->param('action'); my $action = $1; +warn "calling $action sub\n" + if $DEBUG; +$FS::SelfService::DEBUG = $DEBUG; my $result = eval "&$action();"; die $@ if $@; -if ( $result->{error} eq "Can't resume session" ) { #ick +if ( $result->{error} eq "Can't resume session" + || $result->{error} eq "Expired session" ) { #ick + do_template('login',{}); exit; } @@ -77,7 +86,8 @@ if ( $result->{error} eq "Can't resume session" ) { #ick #warn $result->{'open_invoices'}; #warn scalar(@{$result->{'open_invoices'}}); -warn "processing template $action\n"; +warn "processing template $action\n" + if $DEBUG; do_template($action, { 'session_id' => $session_id, 'action' => $action, #so the menu knows what tab we're on... @@ -99,6 +109,127 @@ sub view_invoice { } +sub customer_order_pkg { + my $init_data = signup_info( 'customer_session_id' => $session_id ); + return $init_data if ( $init_data->{'error'} ); + + my $customer_info = customer_info( 'session_id' => $session_id ); + return $customer_info if ( $customer_info->{'error'} ); + + return { + ( map { $_ => $init_data->{$_} } + qw( part_pkg security_phrase svc_acct_pop ), + ), + %$customer_info, + }; +} + +sub customer_change_pkg { + my $init_data = signup_info( 'customer_session_id' => $session_id ); + return $init_data if ( $init_data->{'error'} ); + + my $customer_info = customer_info( 'session_id' => $session_id ); + return $customer_info if ( $customer_info->{'error'} ); + + return { + ( map { $_ => $init_data->{$_} } + qw( part_pkg security_phrase svc_acct_pop ), + ), + ( map { $_ => $cgi->param($_) } + qw( pkgnum pkg ) + ), + %$customer_info, + }; +} + +sub process_order_pkg { + + my $results = ''; + + unless ( length($cgi->param('_password')) ) { + my $init_data = signup_info( 'customer_session_id' => $session_id ); + $results = { 'error' => $init_data->{msgcat}{empty_password} }; + $results = { 'error' => $init_data->{error} } if($init_data->{error}); + } + if ( $cgi->param('_password') ne $cgi->param('_password2') ) { + my $init_data = signup_info( 'customer_session_id' => $session_id ); + $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} }; + $results = { 'error' => $init_data->{error} } if($init_data->{error}); + $cgi->param('_password', ''); + $cgi->param('_password2', ''); + } + + $results ||= order_pkg ( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) } + qw( custnum pkgpart username _password _password2 sec_phrase popnum ) + ); + + + if ( $results->{'error'} ) { + $action = 'customer_order_pkg'; + return { + $cgi->Vars, + %{customer_order_pkg()}, + 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>', + }; + } else { + return $results; + } + +} + +sub process_change_pkg { + + my $results = ''; + + $results ||= change_pkg ( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) } + qw( pkgpart pkgnum ) + ); + + + if ( $results->{'error'} ) { + $action = 'customer_change_pkg'; + return { + $cgi->Vars, + %{customer_change_pkg()}, + 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>', + }; + } else { + return $results; + } + +} + +sub process_order_recharge { + + my $results = ''; + + $results ||= order_recharge ( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) } + qw( svcnum ) + ); + + + if ( $results->{'error'} ) { + $action = 'view_usage'; + if ($results->{'error'} eq '_decline') { + $results->{'error'} = "There has been an error processing your account. Please contact customer support." + } + return { + $cgi->Vars, + %{view_usage()}, + 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>', + }; + } else { + return $results; + } + +} + sub make_payment { payment_info( 'session_id' => $session_id ); } @@ -107,6 +238,10 @@ sub payment_results { use Business::CreditCard; + #we should only do basic checking here for DoS attacks and things + #that couldn't be constructed by the web form... let process_payment() do + #the rest, it gives better error messages + $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/ or die "illegal amount"; #!!! my $amount = $1; @@ -120,9 +255,15 @@ sub payment_results { validate($payinfo) #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo; or die "invalid card"; #!!! - cardtype($payinfo) eq $cgi->param('card_type') - #or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type'); - or die "not a ". $cgi->param('card_type'); + + if ( $cgi->param('card_type') ) { + cardtype($payinfo) eq $cgi->param('card_type') + #or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type'); + or die "not a ". $cgi->param('card_type'); + } + + $cgi->param('paycvv') =~ /^\s*(.{0,4})\s*$/ or die "illegal CVV2"; + my $paycvv = $1; $cgi->param('month') =~ /^(\d{2})$/ or die "illegal month"; my $month = $1; @@ -160,6 +301,7 @@ sub payment_results { 'session_id' => $session_id, 'amount' => $amount, 'payinfo' => $payinfo, + 'paycvv' => $paycvv, 'month' => $month, 'year' => $year, 'payname' => $payname, @@ -257,6 +399,58 @@ sub delete_svc { ); } +sub view_usage { + list_svcs( + 'session_id' => $session_id, + 'svcdb' => 'svc_acct', + 'ncancelled' => 1, + ); +} + +sub view_usage_details { + list_svc_usage( + 'session_id' => $session_id, + 'svcnum' => $cgi->param('svcnum'), + 'beginning' => $cgi->param('beginning') || '', + 'ending' => $cgi->param('ending') || '', + ); +} + +sub change_password { + list_svcs( + 'session_id' => $session_id, + 'svcdb' => 'svc_acct', + ); +}; + +sub process_change_password { + + my $result = myaccount_passwd( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) } qw( svcnum new_password new_password2 ) + ); + + if ( exists $result->{'error'} && $result->{'error'} ) { + + $action = 'change_password'; + return { + $cgi->Vars, + %{ list_svcs( 'session_id' => $session_id, + 'svcdb' => 'svc_acct', + ) + }, + #'svcnum' => $cgi->param('svcnum'), + 'error' => $result->{'error'} + }; + + } else { + + return $result; + + } + +} + #-- sub do_template { diff --git a/fs_selfservice/FS-SelfService/cgi/signup.cgi b/fs_selfservice/FS-SelfService/cgi/signup.cgi index d2ad0d64b..1514db52a 100755 --- a/fs_selfservice/FS-SelfService/cgi/signup.cgi +++ b/fs_selfservice/FS-SelfService/cgi/signup.cgi @@ -1,7 +1,5 @@ #!/usr/bin/perl -T #!/usr/bin/perl -Tw -# -# $Id: signup.cgi,v 1.2 2005-03-12 14:35:12 ivan Exp $ use strict; use vars qw( @payby $cgi $init_data @@ -178,6 +176,10 @@ if ( ( defined($cgi->param('magic')) && $cgi->param('magic') eq 'process' ) or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type'); } + if ($init_data->{emailinvoiceonly} && (length $cgi->param('invoicing_list') < 1)) { + $error ||= $init_data->{msgcat}{illegal_or_empty_text}; + } + unless ( $error ) { my $rv = new_customer( { map { $_ => scalar($cgi->param($_)) } @@ -229,6 +231,8 @@ sub print_form { 'error' => $error, }; + $r->{pkgpart} ||= $r->{default_pkgpart}; + $r->{referral_custnum} = $r->{'ref'}; #$cgi->delete('ref'); #$cgi->delete('init_popstate'); @@ -276,9 +280,10 @@ sub print_okay { } #global for template - my $pkg = ( grep { $_->{'pkgpart'} eq $param{'pkgpart'} } - @{ $init_data->{'part_pkg'} } - )[0]->{'pkg'}; + my $part_pkg = ( grep { $_->{'pkgpart'} eq $param{'pkgpart'} } + @{ $init_data->{'part_pkg'} } + )[0]; + my $pkg = $part_pkg->{'pkg'}; if ( $ieak_template && $user_agent->windows && $user_agent->ie ) { #send an IEAK config @@ -295,6 +300,7 @@ sub print_okay { exch => $exch, loc => $loc, pkg => $pkg, + part_pkg => \$part_pkg, }); } } diff --git a/fs_selfservice/FS-SelfService/cgi/signup.html b/fs_selfservice/FS-SelfService/cgi/signup.html index 2ab07b37e..c2c5d700a 100755 --- a/fs_selfservice/FS-SelfService/cgi/signup.html +++ b/fs_selfservice/FS-SelfService/cgi/signup.html @@ -19,6 +19,15 @@ <INPUT TYPE="hidden" NAME="action" VALUE="process_signup"> <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<%= $referral_custnum %>"> <INPUT TYPE="hidden" NAME="ss" VALUE=""> +<input type="hidden" name="payby" /> +<%= + $OUT = join("\n",map { my $method = $_ ; map { qq|<input type="hidden" name="${method}_$_" />| } qw / payinfo payinfo1 payinfo2 payname paycvv month year type / } @payby); +%> + +<%= + $OUT = join("\n", map { qq|<input type="hidden" name="$_" />| } qw / promo_code reg_code pkgpart username _password _password2 sec_phrase popnum / ); +%> + Where did you hear about our service? <SELECT NAME="refnum"> <%= $OUT .= '<OPTION VALUE="">' unless $refnum; @@ -90,34 +99,30 @@ Contact Information <TR><TD> <%= + $OUT =''; + unless ( $emailinvoiceonly ) { $OUT .= '<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"'; my @invoicing_list = split(', ', $invoicing_list ); $OUT .= ' CHECKED' if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list; - $OUT .= '>'; + $OUT .= '> Postal mail invoice'; } %> - Postal mail invoice + </TD></TR> -<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>"> +<TR><TD><%= $OUT = ( $emailinvoiceonly ? q|<font color="#ff0000">*</font>| : q|| ) %> Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>"> </TD></TR> -<%= scalar(@payby) > 1 ? '<TR><TD>Billing type</TD></TR>' : '' %> -</TABLE> +<%= ( scalar(@payby) > 1 or 1 ) ? '<TR><TD>Billing type ' : '' %> +<!--</TABLE> <TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%"> -<TR> +<TR>--> <%= my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>'; - my %types = ( - 'VISA' => 'VISA card', - 'MasterCard' => 'MasterCard', - 'Discover' => 'Discover card', - 'American Express' => 'American Express card', - ); - foreach ( keys %types ) { - $selected = $CARD_type eq $types{$_} ? 'SELECTED' : ''; - $cardselect .= qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!; + foreach ( keys %card_types ) { + $selected = $CARD_type eq $card_types{$_} ? 'SELECTED' : ''; + $cardselect .= qq!<OPTION $selected VALUE="$card_types{$_}">$_</OPTION>!; } $cardselect .= '</SELECT>'; @@ -126,7 +131,7 @@ Contact Information 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!, 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!, 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!, - 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!, + 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" NAME="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!, 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!, 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"), 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!, @@ -134,13 +139,13 @@ Contact Information if ( $cvv_enabled ) { foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 - $payby{$payby} .= qq!<BR>CVV2 (<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>) <INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!; + $payby{$payby} .= qq!<TR><TD ALIGN="right">CVV2 (<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)</TD><TD><INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4></TD></TR>!; } } my( $account, $aba ) = split('@', $payinfo); my %paybychecked = ( - 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!, + 'CARD' => qq!<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%"><TR><TD ALIGN="right"><font color="#ff0000">*</font> Card type</TD><TD>$cardselect</TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Card number</TD><TD><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19></TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Expration</TD><TD>!. expselect("CARD", $paydate). qq!</TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Name on card</TD><TD><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname"></TD></TR>!, 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!, 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!, 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!, @@ -152,29 +157,43 @@ Contact Information if ( $cvv_enabled ) { foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 - $paybychecked{$payby} .= qq!<BR>CVV2 (<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>) <INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!; + $paybychecked{$payby} .= qq!<TR><TD ALIGN="right">CVV2 (<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)</TD><TD><INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4></TD></TR>!; } } +use Tie::IxHash; +use HTML::Widgets::SelectLayers; + + my %payby_index = ( 'CARD' => qq/Credit Card/, + 'DCRD' => qq/Credit Card/, + 'CHEK' => qq/Check/, + 'DCHK' => qq/Check/, + 'LECB' => qq/Phone Bill Billing/, + 'BILL' => qq/Billing/, + 'COMP' => qq/Complimentary/, + 'PREPAY' => qq/Prepaid Card/, + ); + + +tie my %options, 'Tie::IxHash', (); + +foreach my $payby_option ( @payby ) { + $options{$payby_option} = $payby_index{$payby_option}; +} + +HTML::Widgets::SelectLayers->new( + options => \%options, + selected_layer => 'CARD', + form_name => 'dummy', + html_between => '</td></tr></table>', + form_action => 'dummy.cgi', + layer_callback => sub { my $layer = shift; return $paybychecked{$layer}. '</TABLE>'; }, +)->html; - for (@payby) { - if ( scalar(@payby) == 1) { - $OUT .= '<TD VALIGN=TOP>'. - qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$_">!. - "$paybychecked{$_}</TD>"; - } else { - $OUT .= qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!; - if ($payby eq $_) { - $OUT .= qq! CHECKED> $paybychecked{$_}</TD>!; - } else { - $OUT .= qq!> $payby{$_}</TD>!; - } - } - } %> -</TR></TABLE><font color="#ff0000">*</font> required fields for each billing type -<BR><BR>First package +</TR></TABLE><font color="#ff0000">*</font> required fields +<FORM name="signup_form" action="<%= $self_url %>" METHOD="POST" onsubmit="return fixup_form();"><BR><BR>First package <INPUT TYPE="hidden" NAME="promo_code" VALUE="<%= $promo_code %>"> <INPUT TYPE="hidden" NAME="reg_code" VALUE="<%= $reg_code %>"> <TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%"> @@ -182,7 +201,8 @@ Contact Information <TD COLSPAN=2><SELECT NAME="pkgpart"> <%= - $OUT .= '<OPTION VALUE="">(none)' unless scalar(@part_pkg ) ==1; + $OUT .= '<OPTION VALUE="">(none)' + unless scalar(@part_pkg) == 1 or $default_pkgpart; foreach my $part_pkg ( @part_pkg ) { $OUT .= '<OPTION VALUE="'. $part_pkg->{'pkgpart'}. '"'; $OUT .= ' SELECTED' if $pkgpart && $part_pkg->{'pkgpart'} == $pkgpart; @@ -232,5 +252,87 @@ ENDOUT } %> </TABLE> -<BR><BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup"> + +<%= +if ( @optional_packages ) { + my @html; + foreach my $ii ( 0 .. $#optional_packages) { + my $friendly_index = $ii + 1; + if ($optional_packages[$ii]) { + push @html, qq|<BR>Optional Package #$friendly_index <br />|,'<table bgcolor="#c0c0c0"><tr><td>'; + + push @html, qq|<select name="optional_package${ii}">|; + push @html, qq|<option value="none"></option>|; + push @html, map { qq|<option value="$_->{pkgpart}">$_->{pkg}</option>| } @{$optional_packages[$ii]}; + push @html, q|</select>|; + + push @html, '</td></tr></table>'; + } + $OUT = join("\n", @html); + } +} else { +$OUT = '' +} +%> + +<BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup"> +<script language="JavaScript"> + +function fixup_form() { + + // copy payment method data up to OneTrueForm + + var payment_method_elements = new Array( 'payinfo', 'payinfo1', 'payinfo2', 'payname', 'paycvv' , 'month', 'year','type' ); + var payment_method_form_name = document.OneTrueForm.select.options[document.OneTrueForm.select.selectedIndex].value; + document.OneTrueForm.elements['payby'].value = payment_method_form_name; + var payment_method_form = document.forms[payment_method_form_name]; + + for ( ii = 0 ; ii < payment_method_elements.length ; ii++ ) { + var true_element_name = payment_method_form_name + '_' + payment_method_elements[ii]; + copyelement ( payment_method_form.elements[true_element_name], + document.OneTrueForm.elements[true_element_name] ); + } + + // Copy signup details to OneTrueForm + + var signup_elements = new Array ( 'promo_code', 'reg_code', + 'pkgpart', 'username', + '_password', '_password2', + 'sec_phrase', 'popnum' ); + + for ( ii = 0 ; ii < signup_elements.length ; ii ++ ) { + copyelement ( document.signup_form.elements[signup_elements[ii]], + document.OneTrueForm.elements[signup_elements[ii]]); + } + + document.OneTrueForm.submit(); + return false; +} + +function copyelement(from, to) { +// alert ( from + ' ' + to ); + + if ( from == undefined ) { + to.value = ''; + } else { + if ( from.type == 'select-one' ) { + to.value = from.options[from.selectedIndex].value; + } else if ( from.type == 'checkbox' ) { + if ( from.checked ) { + to.value = from.value; + } else { + to.value = ''; + } + } else { + if ( from.value == undefined ) { + to.value = ''; + } else { + to.value = from.value; + } + } +// alert(from.name + " (" + from.type + "): " + to.name + " => " + to.value); + } +} + +</script> </FORM></BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/success-delayed.html b/fs_selfservice/FS-SelfService/cgi/success-delayed.html new file mode 100644 index 000000000..5eeed5957 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/success-delayed.html @@ -0,0 +1,16 @@ +<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD> +<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR> +Thanks for signing up! +<BR><BR> +Signup information for <%= $email_name %>: +<BR><BR> +Username: <%= $username %><BR> +Password: <%= $password %><BR> +Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR> +Package: <%= $pkg %><BR> +Charge: <%= sprintf('$%.2f', $part_pkg->{'options'}->{'setup_fee'}) %><BR> +In <%= $part_pkg->{'options'}->{'free_days'} %> days you will be charged + <%= sprintf('$%.2f', $part_pkg->{'options'}->{'recur_fee'}) %> +and <%= $part_pkg->{'freq_pretty'} %> thereafter.<BR> + +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/view_invoice.html b/fs_selfservice/FS-SelfService/cgi/view_invoice.html index 72d061980..ad2f4f419 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_invoice.html +++ b/fs_selfservice/FS-SelfService/cgi/view_invoice.html @@ -4,9 +4,7 @@ <%= include('myaccount_menu') %> <TD VALIGN="top"> -<FONT SIZE="-1"><PRE> -<%= $invoice_text %> -</FONT></PRE> +<%= $invoice_html %> </TD></TR></TABLE> <HR> diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage.html b/fs_selfservice/FS-SelfService/cgi/view_usage.html new file mode 100644 index 000000000..79d07d4df --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/view_usage.html @@ -0,0 +1,59 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Service usage</FONT><BR><BR> + +<%= if ( $error ) { + $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!; +} ''; %> + +<TABLE BGCOLOR="#cccccc"> + <TR> + <TH ALIGN="left">Account</TH> + <TH ALIGN="right">Time remaining</TH> + <TH ALIGN="right">Upload remaining</TH> + <TH ALIGN="right">Download remaining</TH> + <TH ALIGN="right">Total remaining</TH> + </TR> +<%= foreach my $svc ( @svcs ) { + my $link = "${url}view_usage_details;". + "svcnum=$svc->{'svcnum'};beginning=0;ending=0"; + $OUT .= '<TR><TD>'; + $OUT .= qq!<A HREF="$link">!. $svc->{'label'}. ': '. $svc->{'value'}.'</A>'; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'seconds'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'upbytes'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'downbytes'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'totalbytes'}; + $OUT .= '</TD></TR>'; + if ( $svc->{'recharge_amount'} ) { + my $link = "${url}process_order_recharge;". + "svcnum=$svc->{'svcnum'}"; + $OUT .= '<TR><TD ALIGN="right">'; + $OUT .= qq!<A HREF="$link">!.'Recharge for $'; + $OUT .= $svc->{'recharge_amount'} . '</A> with'; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'recharge_seconds'} if $svc->{'recharge_seconds'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'recharge_upbytes'} if $svc->{'recharge_upbytes'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'recharge_downbytes'} if $svc->{'recharge_downbytes'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= $svc->{'recharge_totalbytes'} if $svc->{'recharge_totalbytes'}; + $OUT .= '</TD></TR>'; + } + } %> + +</TABLE> +<BR> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage_details.html b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html new file mode 100644 index 000000000..9067755b0 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html @@ -0,0 +1,66 @@ +<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD> +<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR> +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +<TD VALIGN="top"> + +<FONT SIZE=4>Service usage details</FONT><BR><BR> + +<%= if ( $error ) { + $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!; +} ''; %> + +<TABLE WIDTH="100%"> + <TR> + <TD WIDTH="50%"> +<%= if ($previous < $beginning) { + $OUT .= qq!<A HREF="${url}view_usage_details;svcnum=$svcnum;beginning=!; + $OUT .= qq!$previous;ending=$beginning">Previous period</A>!; + }else{ + ''; + } %> + </TD> + <TD WIDTH="50%" ALIGN="right"> +<%= if ($next > $ending) { + $OUT .= qq!<A HREF="${url}view_usage_details;svcnum=$svcnum;beginning=!; + $OUT .= qq!$ending;ending=$next">Next period</A>!; + }else{ + ''; + }%> + </TD> + </TR> +</TABLE> +<TABLE BGCOLOR="#cccccc"> + <TR> + <TH ALIGN="left">Account</TH> + <TH ALIGN="right">Start Time</TH> + <TH ALIGN="right">Duration</TH> + </TR> +<%= my $total = 0; + foreach my $usage ( @usage ) { + $OUT .= '<TR><TD>'; + $OUT .= $usage->{'username'}; + $OUT .= '</TD><TD ALIGN="right">'; + $OUT .= Date::Format::time2str('%T%P %a %b %o %Y', $usage->{'acctstarttime'}); + $OUT .= '</TD><TD ALIGN="right">'; + my $duration = $usage->{'acctstoptime'} - $usage->{'acctstarttime'}; + $total += $duration; + my $h = int($duration/3600); + my $m = sprintf("%02d", int(($duration % 3600) / 60)); + my $s = sprintf("%02d", $duration % 60); + $OUT .= "$h:$m:$s"; + $OUT .= '</TD></TR>'; + } + my $h = int($total/3600); + my $m = sprintf("%02d", int(($total % 3600) / 60)); + my $s = sprintf("%02d", $total % 60); + $OUT .= qq!<TR><TD></TD><TD></TD><TD ALIGN="right">========</TD></TR>!; + $OUT .= qq!<TR><TD></TD><TD></TD><TD ALIGN="right">$h:$m:$s</TD></TR>!; %> + +</TABLE> +<BR> + +</TD></TR></TABLE> +<HR> +<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT> +</BODY></HTML> diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd index ededfa6e9..bdc8e1547 100644 --- a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd +++ b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd @@ -57,8 +57,9 @@ if ( -e $pid_file ) { open(PIDFILE,"<$pid_file"); my $old_pid = <PIDFILE>; close PIDFILE; - $old_pid =~ /^(\d+)$/; - kill 'TERM', $1; + if ( $old_pid =~ /^(\d+)$/ ) { + kill 'TERM', $1; + } } open(PIDFILE,">$pid_file"); print PIDFILE "$$\n"; diff --git a/htetc/freeside-base1.99.conf b/htetc/freeside-base1.99.conf new file mode 100644 index 000000000..c1c187c8d --- /dev/null +++ b/htetc/freeside-base1.99.conf @@ -0,0 +1,21 @@ +PerlModule Apache::compat + +#PerlModule Apache::DBI + +PerlModule HTML::Mason +PerlSetVar MasonArgsMethod CGI +PerlModule HTML::Mason::ApacheHandler + +PerlRequire "%%%MASON_HANDLER%%%" + +<Directory %%%FREESIDE_DOCUMENT_ROOT%%%> +AuthName Freeside +AuthType Basic +AuthUserFile /usr/local/etc/freeside/htpasswd +require valid-user +<Files ~ (\.cgi|\.html)> +SetHandler perl-script +PerlHandler HTML::Mason +</Files> +</Directory> + diff --git a/htetc/freeside-base.conf b/htetc/freeside-base1.conf index f8ebece9e..3f6bd0ee3 100644 --- a/htetc/freeside-base.conf +++ b/htetc/freeside-base1.conf @@ -1,4 +1,7 @@ +#PerlModule Apache::DBI + PerlModule HTML::Mason + <Directory %%%FREESIDE_DOCUMENT_ROOT%%%> AuthName Freeside AuthType Basic @@ -9,7 +12,7 @@ AddHandler perl-script .cgi .html PerlHandler HTML::Mason </Files> <Perl> -require "/usr/local/etc/freeside/handler.pl"; +require "%%%MASON_HANDLER%%%"; </Perl> </Directory> diff --git a/htetc/freeside-base2.conf b/htetc/freeside-base2.conf new file mode 100644 index 000000000..38f784068 --- /dev/null +++ b/htetc/freeside-base2.conf @@ -0,0 +1,21 @@ +PerlModule Apache2::compat + +#PerlModule Apache::DBI + +PerlModule HTML::Mason +PerlSetVar MasonArgsMethod CGI +PerlModule HTML::Mason::ApacheHandler + +PerlRequire "%%%MASON_HANDLER%%%" + +<Directory %%%FREESIDE_DOCUMENT_ROOT%%%> +AuthName Freeside +AuthType Basic +AuthUserFile /usr/local/etc/freeside/htpasswd +require valid-user +<Files ~ (\.cgi|\.html)> +SetHandler perl-script +PerlHandler HTML::Mason +</Files> +</Directory> + diff --git a/htetc/global.asa b/htetc/global.asa deleted file mode 100644 index bb30608a4..000000000 --- a/htetc/global.asa +++ /dev/null @@ -1,263 +0,0 @@ -BEGIN { eval "use Devel::AutoProfiler;"; } #only if installed... -#BEGIN { package Devel::AutoProfiler; use vars qw(%caller_info); } -#use Devel::AutoProfiler; - -use strict; -use vars qw( $cgi $p ); -use Apache::ASP 2.55; -use CGI 2.47; -#use CGI::Carp qw(fatalsToBrowser); -use Date::Format; -use Date::Parse; -use Time::Local; -use Time::Duration; -use Tie::IxHash; -use URI::Escape; -use HTML::Entities; -use JSON; -use IO::Handle; -use IO::File; -use IO::Scalar; -use Net::Whois::Raw qw(whois); -if ( $] < 5.006 ) { - eval "use Net::Whois::Raw 0.32 qw(whois)"; - die $@ if $@; -} -use Text::CSV_XS; -use Spreadsheet::WriteExcel; -use Business::CreditCard; -use String::Approx qw(amatch); -use Chart::LinesPoints; -use HTML::Widgets::SelectLayers 0.05; -use FS; -use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); -use FS::Record qw(qsearch qsearchs fields dbdef); -use FS::Conf; -use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot - small_custview myexit http_header); -use FS::UI::Web; -use FS::Msgcat qw(gettext geterror); -use FS::Misc qw( send_email ); -use FS::Report::Table::Monthly; -use FS::TicketSystem; - -use FS::agent; -use FS::agent_type; -use FS::domain_record; -use FS::cust_bill; -use FS::cust_bill_pay; -use FS::cust_credit; -use FS::cust_credit_bill; -use FS::cust_main qw(smart_search); -use FS::cust_main_county; -use FS::cust_pay; -use FS::cust_pkg; -use FS::cust_refund; -use FS::cust_svc; -use FS::nas; -use FS::part_bill_event; -use FS::part_pkg; -use FS::part_referral; -use FS::part_svc; -use FS::part_svc_router; -use FS::part_virtual_field; -use FS::pkg_svc; -use FS::port; -use FS::queue qw(joblisting); -use FS::raddb; -use FS::session; -use FS::svc_acct; -use FS::svc_acct_pop qw(popselector); -use FS::svc_domain; -use FS::svc_forward; -use FS::svc_www; -use FS::router; -use FS::addr_block; -use FS::svc_broadband; -use FS::svc_external; -use FS::type_pkgs; -use FS::part_export; -use FS::part_export_option; -use FS::export_svc; -use FS::msgcat; -use FS::rate; -use FS::rate_region; -use FS::rate_prefix; -use FS::payment_gateway; -use FS::agent_payment_gateway; - -sub Script_OnStart { - $Response->AddHeader('Cache-control' => 'no-cache'); -# $Response->AddHeader('Expires' => 0); - $Response->{Expires} = -36288000; - - $cgi = new CGI; - &cgisuidsetup($cgi); - $p = popurl(2); - #print $cgi->header( '-expires' => 'now' ); - #dbh->{'private_profile'} = {} if dbh->can('sprintProfile'); - dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); - - #really should check for FS::Profiler or something - # Devel::AutoProfiler _our_ VERSION? thanks a fucking lot - if ( Devel::AutoProfiler->can('__recursively_fetch_subs_in_package') ) { - #should check to see it's my special version. well, switch to FS::Profiler - - #nicked from Devel::AutoProfiler::INIT - my %subs = Devel::AutoProfiler::__recursively_fetch_subs_in_package('main'); - - - SUB : while( my ($name, $ref) = each(%subs) ) - { - #next if $name =~ /^(main::)?Apache::/; - next unless $name =~ /FS/; - foreach my $sub (@Devel::AutoProfiler::do_not_instrument_this_sub) - { - if ($name =~ /$sub/) - { - next SUB; - } - } - next if ($Devel::AutoProfiler::do_not_instrument_this_sub{$name}); - #warn "INIT name is $name \n"; - Devel::AutoProfiler::__instrument_sub($name, $ref); - } - - } - -} - -sub Script_OnFlush { - my $ref = $Response->{BinaryRef}; - #$$ref = $cgi->header( @FS::CGI::header ) . $$ref; - #$$ref = $cgi->header() . $$ref; - #warn "Script_OnFlush called with dbh ". dbh. "\n"; - #if ( dbh->can('sprintProfile') ) { - if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { - #warn "dbh can sprintProfile\n"; - if ( lc($Response->{ContentType}) eq 'text/html' ) { #con - #warn "contenttype is sprintProfile\n"; - $$ref =~ s/<\/BODY>[\s\n]*<\/HTML>[\s\n]*$//i - or warn "can't remove"; - - #$$ref .= '<PRE>'. ("\n"x96). encode_entities(dbh->sprintProfile()). '</PRE>'; - # wtf? konqueror... - $$ref .= '<PRE>'. ("\n"x4096). encode_entities(dbh->sprintProfile()). - "\n\n". &sprintAutoProfile(). '</PRE>'; - - $$ref .= '</BODY></HTML>'; - } - dbh->{'private_profile'} = {}; - } -} - -#if ( defined(@DBIx::Profile::ISA) && DBIx::Profile::db->can('sprintProfile') ) { -#if ( defined(@DBIx::Profile::ISA) && UNIVERSAL::can('DBIx::Profile::db', 'sprintProfile') ) { -if ( defined(@DBIx::Profile::ISA) ) { - - #warn "enabling profiling redirects"; - *CGI::redirect = sub { - my( $self, $location) = @_; - my $page = - $cgi->header. - qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!. - '<BR><BR><PRE>'. - ( UNIVERSAL::can(dbh, 'sprintProfile') - ? encode_entities(dbh->sprintProfile()) - : 'DBIx::Profile missing sprintProfile method;'. - 'unpatched or too old?' ). - "\n\n". &sprintAutoProfile(). '</PRE>'. - '</BODY></HTML>'; - dbh->{'private_profile'} = {}; - return $page; - }; - -} - -sub by_total_time -{ - return $a->{total_time_in_sub} <=> $b->{total_time_in_sub}; -} - -sub sprintAutoProfile { - my %caller_info = %Devel::AutoProfiler::caller_info; - return unless keys %caller_info; - - %Devel::AutoProfiler::caller_info = (); - - my @keys = keys(%caller_info); - - foreach my $key (@keys) - { - my $href = $caller_info{$key}; - - $href->{who_am_i} = $key; - } - - my @subs = values(%caller_info); - - #my @sorted = sort by_total_time ( @subs ); - my @sorted = reverse sort by_total_time ( @subs ); - - # print Dumper \@sorted; - - my @readable_info; - - foreach my $sort (@sorted) - { - push(@readable_info, delete($sort->{who_am_i})); - push(@readable_info, $sort); - } - - use Data::Dumper; - return encode_entities(Dumper(\@readable_info)); - -} - -sub include { - my $file = shift; - my $shift = 0; - if ( $file =~ m(^([^/].*)/[^/]+) ) { - unshift @{$Response->{asp}{includes_dir}}, "./$1"; - $shift = 1; - } - $file =~ s(^/)(%%%FREESIDE_DOCUMENT_ROOT%%%/); - #broken in 5.005# ${$Response->TrapInclude($file, @_)}; - my $ref = $Response->TrapInclude($file, @_); - shift @{$Response->{asp}{includes_dir}} if $shift; - $$ref; -} - -if ( defined(@DBIx::Profile::ISA) ) { - - #false laziness w/above - *redirect = sub { - my($location) = @_; - - ${$Response->{BinaryRef}} = - $cgi->header. - qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!. - '<BR><BR><PRE>'. - ( UNIVERSAL::can(dbh, 'sprintProfile') - ? encode_entities(dbh->sprintProfile()) - : 'DBIx::Profile missing sprintProfile method;'. - 'unpatched or too old?' ). - "\n\n". &sprintAutoProfile(). '</PRE>'. - '</BODY></HTML>'; - - dbh->{'private_profile'} = {}; - - $Response->End; - - }; - -} else { - - *redirect = sub { - $Response->Redirect(@_); - } - -} - -1; - diff --git a/htetc/handler.pl b/htetc/handler.pl index 2f2b0af4a..c1ca954e1 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -60,7 +60,7 @@ my $ah = new HTML::Mason::ApacheHandler ( [ 'freeside' => '%%%FREESIDE_DOCUMENT_ROOT%%%' ], [ 'rt' => '%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ], ], - data_dir=>'/usr/local/etc/freeside/masondata', + data_dir=>'%%%MASONDATA%%%', #out_mode=>'stream', #RT @@ -88,14 +88,18 @@ sub handler #rar { package HTML::Mason::Commands; use strict; - use vars qw( $cgi $p ); + use vars qw( $cgi $p $fsurl); use vars qw( %session ); use CGI 2.47 qw(-private_tempfiles); #use CGI::Carp qw(fatalsToBrowser); + use List::Util qw( max min ); use Date::Format; use Date::Parse; use Time::Local; use Time::Duration; + use DateTime; + use DateTime::Format::Strptime; + use Lingua::EN::Inflect qw(PL); use Tie::IxHash; use URI::Escape; use HTML::Entities; @@ -110,19 +114,23 @@ sub handler } use Text::CSV_XS; use Spreadsheet::WriteExcel; - use Business::CreditCard; + use Business::CreditCard 0.30; #for mask-aware cardtype() use String::Approx qw(amatch); use Chart::LinesPoints; - use HTML::Widgets::SelectLayers 0.05; + use Chart::Mountain; + use Color::Scheme; + use HTML::Widgets::SelectLayers 0.06; + #use HTML::Widgets::SelectLayers 0.07; # after 1.7.2 + use Locale::Country; use FS; use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); use FS::Record qw(qsearch qsearchs fields dbdef); use FS::Conf; - use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot - small_custview myexit http_header); + use FS::CGI qw(header menubar popurl rooturl table itable ntable idiot + eidiot small_custview myexit http_header); use FS::UI::Web; use FS::Msgcat qw(gettext geterror); - use FS::Misc qw( send_email send_fax ); + use FS::Misc qw( send_email send_fax states_hash counties state_label ); use FS::Report::Table::Monthly; use FS::TicketSystem; @@ -137,6 +145,7 @@ sub handler use FS::cust_main_county; use FS::cust_pay; use FS::cust_pkg; + use FS::cust_pkg_reason; use FS::cust_refund; use FS::cust_svc; use FS::nas; @@ -146,6 +155,7 @@ sub handler use FS::part_svc; use FS::part_svc_router; use FS::part_virtual_field; + use FS::pay_batch; use FS::pkg_svc; use FS::port; use FS::queue qw(joblisting); @@ -171,6 +181,21 @@ sub handler use FS::payment_gateway; use FS::agent_payment_gateway; use FS::XMLRPC; + use FS::payby; + use FS::cdr; + use FS::inventory_class; + use FS::inventory_item; + use FS::pkg_class; + use FS::access_user; + use FS::access_group; + use FS::access_usergroup; + use FS::access_groupagent; + use FS::access_right; + use FS::AccessRight; + use FS::svc_phone; + use FS::reason_type; + use FS::reason; + use FS::cust_main_note; if ( %%%RT_ENABLED%%% ) { eval ' @@ -204,6 +229,7 @@ sub handler my( $self, $location ) = @_; use vars qw($m); + # false laziness w/below if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect my $page = @@ -233,9 +259,9 @@ sub handler &cgisuidsetup($cgi); #&cgisuidsetup($r); $p = popurl(2); + $fsurl = rooturl(); } - sub include { use vars qw($m); $m->scomp(@_); @@ -261,7 +287,10 @@ sub handler ); dbh->{'private_profile'} = {}; - $m->abort(200); + #whew. removing this is all that's needed to fix the annoying + #blank-page-instead-of-profiling-redirect-when-called-from-an-include + #bug triggered by mason 1.32 + #my $rv = $m->abort(200); } else { #normal redirect @@ -307,6 +336,8 @@ sub handler $ah->interp->set_escape( 'h' => sub { ${$_[0]}; } ); } + $ah->interp->ignore_warnings_expr('.'); + my %session; my $status; eval { $status = $ah->handle_request($r); }; diff --git a/httemplate/autohandler b/httemplate/autohandler index a3f7eb008..bdea50534 100644 --- a/httemplate/autohandler +++ b/httemplate/autohandler @@ -9,7 +9,18 @@ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { if ( lc($r->content_type) eq 'text/html' ) { - $profile = '<PRE>'. 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 ); + my $text = dbh->sprintProfile(); + #my $text = $wrapper->wrap( dbh->sprintProfile() ); + $text =~ s/^/ /mg; + + $profile = '<PRE>'. + encode_entities( $text ). #"\n\n". &sprintAutoProfile(). '</PRE>'; "\n\n". '</PRE>'; } @@ -19,3 +30,6 @@ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { s/(<\/BODY>[\s\n]*<\/HTML>[\s\n]*)$/$profile$1/i; </%filter> +<%cleanup> + dbh->commit(); +</%cleanup> diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html new file mode 100644 index 000000000..ca162a094 --- /dev/null +++ b/httemplate/browse/access_group.html @@ -0,0 +1,82 @@ +<% 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', + 'Agents', + 'Rights', + ], + 'fields' => [ 'groupnum', + 'groupname', + $agents_sub, + $rights_sub, + ], + 'links' => [ $link, + $link, + '', + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + "Internal access groups control access to the back-office interface.<BR><BR>". + qq!<A HREF="${p}edit/access_group.html"><I>Add an internal access group</I></A><BR><BR>!; + +#false laziness w/access_user.html & agent_type.cgi +my $agents_sub = sub { + my $access_group = shift; + + [ map { + my $access_groupagent = $_; + my $agent = $access_groupagent->agent; + [ + { + 'data' => $agent->agent, + 'align' => 'left', + 'link' => $p. 'edit/agent.cgi?'. $agent->agentnum, + }, + ]; + } + grep { $_->agent } #? + $access_group->access_groupagent, + + ]; + +}; + +my $rights_sub = sub { + my $access_group = shift; + + [ map { my $access_right = $_; + [ + { + 'data' => $access_right->rightname, + 'align' => 'left', + }, + ]; + } + $access_group->access_rights, + + ]; + +}; + +my $count_query = 'SELECT COUNT(*) FROM access_group'; + +my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; + +</%init> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html new file mode 100644 index 000000000..8eb3e330a --- /dev/null +++ b/httemplate/browse/access_user.html @@ -0,0 +1,102 @@ +<% include( 'elements/browse.html', + 'title' => 'Internal Users', + 'menubar' => [ #'Main menu' => $p, + 'Internal access groups' => $p.'browse/access_group.html', + ], + 'html_init' => $html_init, + 'html_posttotal' => $posttotal, + 'name' => 'internal users', + 'query' => { 'table' => 'access_user', + 'hashref' => \%search, + 'extra_sql' => 'ORDER BY last, first', + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'style' => \@style, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +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 <B>highly recommended</B> to add a <B>separate account for each person</B> rather than using role accounts.<BR><BR>". + qq!<A HREF="${p}edit/access_user.html"><I>Add an internal user</I></A><BR><BR>!; + +#false laziness w/part_pkg.cgi +my %search = (); +my $search = ''; +unless ( $cgi->param('showdisabled') ) { + %search = ( 'disabled' => '' ); + $search = "( disabled = '' OR disabled IS NULL )"; +} + +#false laziness w/access_group.html & 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 $posttotal; +if ( $cgi->param('showdisabled') ) { + $cgi->param('showdisabled', 0); + $posttotal = '( <a href="'. $cgi->self_url. '">hide disabled users</a> )'; + $cgi->param('showdisabled', 1); +} else { + $cgi->param('showdisabled', 1); + $posttotal = '( <a href="'. $cgi->self_url. '">show disabled users</a> )'; + $cgi->param('showdisabled', 0); +} + +my $count_query = 'SELECT COUNT(*) FROM access_user'; +$count_query .= " WHERE $search" + if $search; + +my $link = [ $p.'edit/access_user.html?', 'usernum' ]; + +my @header = ( '#', 'Username' ); +my @fields = ( 'usernum', 'username' ); +my $align = 'rl'; +my @links = ( $link, $link ); +my @style = ( '', '' ); + +#false laziness w/part_pkg.cgi +#unless ( $cgi->param('showdisabled') ) { #its been reversed already +if ( $cgi->param('showdisabled') ) { #its been reversed already + push @header, 'Status'; + push @fields, sub { shift->disabled + ? '<FONT COLOR="#FF0000">DISABLED</FONT>' + : '<FONT COLOR="#00CC00">Active</FONT>' + }; + push @links, ''; + $align .= 'c'; + push @style, 'b'; +} + +push @header, 'Full name', 'Groups'; +push @fields, 'name', $groups_sub; +push @links, $link, ''; +$align .= 'll'; + +</%init> diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi index 06ac556cf..408d57298 100644 --- a/httemplate/browse/addr_block.cgi +++ b/httemplate/browse/addr_block.cgi @@ -1,66 +1,73 @@ -<%= header('Address Blocks', menubar('Main Menu' => $p)) %> -<% +<% include("/elements/header.html",'Address Blocks', menubar('Main Menu' => $p)) %> +% +% +%use NetAddr::IP; +% +%my @addr_block = qsearch('addr_block', {}); +%my @router = qsearch('router', {}); +%my $block; +%my $p2 = popurl(2); +%my $path = $p2 . "edit/process/addr_block"; +% +% +% if ($cgi->param('error')) { -use NetAddr::IP; - -my @addr_block = qsearch('addr_block', {}); -my @router = qsearch('router', {}); -my $block; -my $p2 = popurl(2); -my $path = $p2 . "edit/process/addr_block"; - -%> - -<% if ($cgi->param('error')) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> <BR><BR> -<% } %> +% } -<%=table()%> -<% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { %> +<%table()%> +% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { + <TR> - <TD><%=$block->NetAddr%></TD> - <% if (my $router = $block->router) { %> - <% if (scalar($block->svc_broadband) == 0) { %> + <TD><%$block->NetAddr%></TD> +% if (my $router = $block->router) { +% if (scalar($block->svc_broadband) == 0) { + <TD> - <%=$router->routername%> + <%$router->routername%> </TD> <TD> - <FORM ACTION="<%=$path%>/deallocate.cgi" METHOD="POST"> - <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>"> + <FORM ACTION="<%$path%>/deallocate.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> <INPUT TYPE="submit" NAME="submit" VALUE="Deallocate"> </FORM> </TD> - <% } else { %> +% } else { + <TD COLSPAN="2"> - <%=$router->routername%> + <%$router->routername%> </TD> - <% } %> - <% } else { %> +% } +% } else { + <TD> - <FORM ACTION="<%=$path%>/allocate.cgi" METHOD="POST"> - <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>"> + <FORM ACTION="<%$path%>/allocate.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> <SELECT NAME="routernum" SIZE="1"> - <% foreach (@router) { %> - <OPTION VALUE="<%=$_->routernum %>"><%=$_->routername%></OPTION> - <% } %> +% foreach (@router) { + + <OPTION VALUE="<%$_->routernum %>"><%$_->routername%></OPTION> +% } + </SELECT> <INPUT TYPE="submit" NAME="submit" VALUE="Allocate"> </FORM> </TD> <TD> - <FORM ACTION="<%=$path%>/split.cgi" METHOD="POST"> - <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>"> + <FORM ACTION="<%$path%>/split.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> <INPUT TYPE="submit" NAME="submit" VALUE="Split"> </FORM> </TD> </TR> -<% } - } %> +% } +% } + <TR><TD COLSPAN="3"><BR></TD></TR> <TR> - <FORM ACTION="<%=$path%>/add.cgi" METHOD="POST"> + <FORM ACTION="<%$path%>/add.cgi" METHOD="POST"> <TD>Gateway/Netmask</TD> <TD> <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2"> @@ -73,4 +80,7 @@ my $path = $p2 . "edit/process/addr_block"; </TABLE> </BODY> </HTML> - +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index 05300d0bd..063f259de 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -1,226 +1,374 @@ -<% - - my %search; - if ( $cgi->param('showdisabled') - || !dbdef->table('agent')->column('disabled') ) { - %search = (); - } else { - %search = ( 'disabled' => '' ); - } - - my $conf = new FS::Conf; - -%> -<%= header('Agent Listing', menubar( +<% include("/elements/header.html",'Agent Listing', menubar( 'Main Menu' => $p, 'Agent Types' => $p. 'browse/agent_type.cgi', # 'Add new agent' => '../edit/agent.cgi' )) %> Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type).<BR><BR> -<A HREF="<%= $p %>edit/agent.cgi"><I>Add a new agent</I></A><BR><BR> +<A HREF="<% $p %>edit/agent.cgi"><I>Add a new agent</I></A><BR><BR> +% if ( dbdef->table('agent')->column('disabled') ) { -<% if ( dbdef->table('agent')->column('disabled') ) { %> - <%= $cgi->param('showdisabled') + <% $cgi->param('showdisabled') ? do { $cgi->param('showdisabled', 0); '( <a href="'. $cgi->self_url. '">hide disabled agents</a> )'; } : do { $cgi->param('showdisabled', 1); '( <a href="'. $cgi->self_url. '">show disabled agents</a> )'; } %> -<% } %> +% } + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + -<%= table() %> <TR> - <TH COLSPAN=<%= ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH> - <TH>Type</TH> - <TH>Customers</TH> - <TH><FONT SIZE=-1>Customer<BR>packages</FONT></TH> - <TH>Reports</TH> - <TH>Registration codes</TH> - <TH>Prepaid cards</TH> - <% if ( $conf->config('ticket_system') ) { %> - <TH>Ticketing</TH> - <% } %> - <TH><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH> - <TH><FONT SIZE=-1>Freq.</FONT></TH> - <TH><FONT SIZE=-1>Prog.</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Customer<BR>packages</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Reports</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Registration codes</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Prepaid cards</TH> +% if ( $conf->config('ticket_system') ) { + + <TH CLASS="grid" BGCOLOR="#cccccc">Ticketing</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Freq.</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Prog.</FONT></TH> </TR> -<% -# <TH><FONT SIZE=-1>Agent #</FONT></TH> -# <TH>Agent</TH> +% +%# <TH><FONT SIZE=-1>Agent #</FONT></TH> +%# <TH>Agent</TH> +% +%foreach my $agent ( sort { +% #$a->getfield('agentnum') <=> $b->getfield('agentnum') +% $a->getfield('agent') cmp $b->getfield('agent') +%} qsearch('agent', \%search ) ) { +% +% my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'. +% 'agentnum='. $agent->agentnum; +% +% my $cust_pkg_link = $p. 'search/cust_pkg.cgi?agentnum='. $agent->agentnum; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% -foreach my $agent ( sort { - #$a->getfield('agentnum') <=> $b->getfield('agentnum') - $a->getfield('agent') cmp $b->getfield('agent') -} qsearch('agent', \%search ) ) { - my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'. - 'agentnum='. $agent->agentnum; + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"> + <% $agent->agentnum %></A></TD> +% if ( dbdef->table('agent')->column('disabled') +% && !$cgi->param('showdisabled') ) { - my $cust_pkg_link = $p. 'search/cust_pkg.cgi?agentnum='. $agent->agentnum; + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->disabled ? 'DISABLED' : '' %></TD> +% } -%> - <TR> - <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>"> - <%= $agent->agentnum %></A></TD> -<% if ( dbdef->table('agent')->column('disabled') - && !$cgi->param('showdisabled') ) { %> - <TD><%= $agent->disabled ? 'DISABLED' : '' %></TD> -<% } %> - - <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>"> - <%= $agent->agent %></A></TD> - <TD><A HREF="<%=$p%>edit/agent_type.cgi?<%= $agent->typenum %>"><%= $agent->agent_type->atype %></A></TD> - - <TD> - <TABLE CELLSPACING=0 CELLPADDING=0> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"> + <% $agent->agent %></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent_type.cgi?<% $agent->typenum %>"><% $agent->agent_type->atype %></A></TD> + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + <TR> <TH ALIGN="right" WIDTH="40%"> - <%= my $num_prospect = $agent->num_prospect_cust_main %> + <FONT COLOR="#7e0079"> + <% my $num_prospect = $agent->num_prospect_cust_main %> + </FONT> </TH> + <TD> - <% if ( $num_prospect ) { %> - <A HREF="<%= $cust_main_link %>&prospect=1"><% } %>prospects<% if ($num_prospect ) { %></A><% } %> +% if ( $num_prospect ) { + + <A HREF="<% $cust_main_link %>&prospect=1"> +% } +prospects +% if ($num_prospect ) { +</A> +% } + + <TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#0000CC"> + <% my $num_inactive = $agent->num_inactive_cust_main %> + </FONT> + </TH> + <TD> +% if ( $num_inactive ) { + + <A HREF="<% $cust_main_link %>&inactive=1"> +% } +inactive +% if ( $num_inactive ) { +</A> +% } + + </TD> </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#00CC00"> - <%= my $num_active = $agent->num_active_cust_main %> + <% my $num_active = $agent->num_active_cust_main %> </FONT> </TH> + <TD> - <% if ( $num_active ) { %> - <A HREF="<%= $cust_main_link %>&active=1"><% } %>active<% if ( $num_active ) { %></A><% } %> +% if ( $num_active ) { + + <A HREF="<% $cust_main_link %>&active=1"> +% } +active +% if ( $num_active ) { +</A> +% } + </TD> </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#FF9900"> - <%= my $num_susp = $agent->num_susp_cust_main %> + <% my $num_susp = $agent->num_susp_cust_main %> </FONT> </TH> + <TD> - <% if ( $num_susp ) { %> - <A HREF="<%= $cust_main_link %>&suspended=1"><% } %>suspended<% if ( $num_susp ) { %></A><% } %> +% if ( $num_susp ) { + + <A HREF="<% $cust_main_link %>&suspended=1"> +% } +suspended +% if ( $num_susp ) { +</A> +% } + </TD> </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#FF0000"> - <%= my $num_cancel = $agent->num_cancel_cust_main %> + <% my $num_cancel = $agent->num_cancel_cust_main %> </FONT> </TH> + <TD> - <% if ( $num_cancel ) { %> - <A HREF="<%= $cust_main_link %>&showcancelledcustomers=1&cancelled=1"><% } %>cancelled<% if ( $num_cancel ) { %></A><% } %> +% if ( $num_cancel ) { + + <A HREF="<% $cust_main_link %>&showcancelledcustomers=1&cancelled=1"> +% } +cancelled +% if ( $num_cancel ) { +</A> +% } + </TD> </TR> + </TABLE> </TD> - <TD> - <TABLE CELLSPACING=0 CELLPADDING=0> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="bottom"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#0000CC"> + <% my $num_inactive_pkg = $agent->num_inactive_cust_pkg %> + </FONT> + </TH> + + <TD> +% if ( $num_inactive_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=inactive"> +% } +inactive +% if ( $num_inactive_pkg ) { +</A> +% } + + </TD> + </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#00CC00"> - <%= my $num_active_pkg = $agent->num_active_cust_pkg %> + <% my $num_active_pkg = $agent->num_active_cust_pkg %> </FONT> </TH> + <TD> - <% if ( $num_active_pkg ) { %> - <A HREF="<%= $cust_pkg_link %>&magic=active"><% } %>active<% if ( $num_active_pkg ) { %></A><% } %> +% if ( $num_active_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=active"> +% } +active +% if ( $num_active_pkg ) { +</A> +% } + </TD> </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#FF9900"> - <%= my $num_susp_pkg = $agent->num_susp_cust_pkg %> + <% my $num_susp_pkg = $agent->num_susp_cust_pkg %> </FONT> + </TH> <TD> - <% if ( $num_susp_pkg ) { %> - <A HREF="<%= $cust_pkg_link %>&magic=suspended"><% } %>suspended<% if ( $num_susp_pkg ) { %></A><% } %> +% if ( $num_susp_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=suspended"> +% } +suspended +% if ( $num_susp_pkg ) { +</A> +% } + </TD> </TR> + <TR> <TH ALIGN="right" WIDTH="40%"> <FONT COLOR="#FF0000"> - <%= my $num_cancel_pkg = $agent->num_cancel_cust_pkg %> + <% my $num_cancel_pkg = $agent->num_cancel_cust_pkg %> </FONT> </TH> + <TD> - <% if ( $num_cancel_pkg ) { %> - <A HREF="<%= $cust_pkg_link %>&magic=cancelled"><% } %>cancelled<% if ( $num_cancel_pkg ) { %></A><% } %> +% if ( $num_cancel_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=cancelled"> +% } +cancelled +% if ( $num_cancel_pkg ) { +</A> +% } + </TD> </TR> + </TABLE> </TD> - <TD> - <A HREF="<%= $p %>search/report_cust_pay.html?agentnum=<%= $agent->agentnum %>">Payments</A> - <BR><A HREF="<%= $p %>search/report_cust_credit.html?agentnum=<%= $agent->agentnum %>">Credits</A> - <BR><A HREF="<%= $p %>search/report_receivables.cgi?agentnum=<%= $agent->agentnum %>">A/R Aging</A> - <!--<BR><A HREF="<%= $p %>search/money_time.cgi?agentnum=<%= $agent->agentnum %>">Sales/Credits/Receipts</A>--> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $p %>search/report_cust_pay.html?agentnum=<% $agent->agentnum %>">Payments</A> + <BR><A HREF="<% $p %>search/report_cust_credit.html?agentnum=<% $agent->agentnum %>">Credits</A> + <BR><A HREF="<% $p %>search/report_receivables.cgi?agentnum=<% $agent->agentnum %>">A/R Aging</A> + <!--<BR><A HREF="<% $p %>search/money_time.cgi?agentnum=<% $agent->agentnum %>">Sales/Credits/Receipts</A>--> </TD> - <TD> - <%= my $num_reg_code = $agent->num_reg_code %> - <% if ( $num_reg_code ) { %> - <A HREF="<%=$p%>search/reg_code.html?agentnum=<%= $agent->agentnum %>"><% } %>Unused<% if ( $num_reg_code ) { %></A><% } %> - <BR><A HREF="<%=$p%>edit/reg_code.cgi?agentnum=<%= $agent->agentnum %>">Generate codes</A> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% my $num_reg_code = $agent->num_reg_code %> +% if ( $num_reg_code ) { + + <A HREF="<%$p%>search/reg_code.html?agentnum=<% $agent->agentnum %>"> +% } +Unused +% if ( $num_reg_code ) { +</A> +% } + + <BR><A HREF="<%$p%>edit/reg_code.cgi?agentnum=<% $agent->agentnum %>">Generate codes</A> </TD> - <TD> - <%= my $num_prepay_credit = $agent->num_prepay_credit %> - <% if ( $num_prepay_credit ) { %> - <A HREF="<%=$p%>search/prepay_credit.html?agentnum=<%= $agent->agentnum %>"><% } %>Unused<% if ( $num_prepay_credit ) { %></A><% } %> - <BR><A HREF="<%=$p%>edit/prepay_credit.cgi?agentnum=<%= $agent->agentnum %>">Generate cards</A> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% my $num_prepay_credit = $agent->num_prepay_credit %> +% if ( $num_prepay_credit ) { + + <A HREF="<%$p%>search/prepay_credit.html?agentnum=<% $agent->agentnum %>"> +% } +Unused +% if ( $num_prepay_credit ) { +</A> +% } + + <BR><A HREF="<%$p%>edit/prepay_credit.cgi?agentnum=<% $agent->agentnum %>">Generate cards</A> </TD> +% if ( $conf->config('ticket_system') ) { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $agent->ticketing_queueid ) { - <% if ( $conf->config('ticket_system') ) { %> + Queue: <% $agent->ticketing_queueid %>: <% $agent->ticketing_queue %><BR> +% } - <TD> - <% if ( $agent->ticketing_queueid ) { %> - Queue: <%= $agent->ticketing_queueid %>: <%= $agent->ticketing_queue %><BR> - <% } %> </TD> +% } - <% } %> - <TD> - <TABLE CELLSPACING=0 CELLPADDING=0> - <% foreach my $override ( - # sort { } want taxclass-full stuff first? and default cards (empty cardtype) - qsearch('agent_payment_gateway', { 'agentnum' => $agent->agentnum } ) - ) { - %> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> +% foreach my $override ( +% # sort { } want taxclass-full stuff first? and default cards (empty cardtype) +% qsearch('agent_payment_gateway', { 'agentnum' => $agent->agentnum } ) +% ) { +% + <TR> <TD> - <%= $override->cardtype || 'Default' %> to <%= $override->payment_gateway->gateway_module %> (<%= $override->payment_gateway->gateway_username %>) - <%= $override->taxclass + <% $override->cardtype || 'Default' %> to <% $override->payment_gateway->gateway_module %> (<% $override->payment_gateway->gateway_username %>) + <% $override->taxclass ? ' for '. $override->taxclass. ' only' : '' %> - <FONT SIZE=-1><A HREF="<%=$p%>misc/delete-agent_payment_gateway.cgi?<%= 'XXXoverridenum' %>">(delete)</A></FONT> + <FONT SIZE=-1><A HREF="<%$p%>misc/delete-agent_payment_gateway.cgi?<% $override->agentgatewaynum %>">(delete)</A></FONT> </TD> </TR> - <% } %> +% } + <TR> - <TD><FONT SIZE=-1><A HREF="<%=$p%>edit/agent_payment_gateway.html?agentnum=<%= $agent->agentnum %>">(add override)</A></FONT></TD> + <TD><FONT SIZE=-1><A HREF="<%$p%>edit/agent_payment_gateway.html?agentnum=<% $agent->agentnum %>">(add override)</A></FONT></TD> </TR> </TABLE> </TD> - <TD><%= $agent->freq %></TD> - <TD><%= $agent->prog %></TD> +<!-- + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->freq %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->prog %></TD> +--> </TR> +% } -<% } %> </TABLE> </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %search; +if ( $cgi->param('showdisabled') + || !dbdef->table('agent')->column('disabled') ) { + %search = (); +} else { + %search = ( 'disabled' => '' ); +} + +my $conf = new FS::Conf; + +</%init> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi index 5473804e8..b4e4fcf99 100755 --- a/httemplate/browse/agent_type.cgi +++ b/httemplate/browse/agent_type.cgi @@ -1,60 +1,63 @@ -<!-- mason kludge --> -<%= header("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.<BR><BR> -<A HREF="<%= $p %>edit/agent_type.cgi"><I>Add a new agent type</I></A><BR><BR> - -<%= table() %> -<TR> - <TH COLSPAN=2>Agent Type</TH> - <TH COLSPAN=2>Packages</TH> -</TR> - -<% -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 <<END; - <TR> - <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}"> - $hashref->{typenum} - </A></TD> - <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}">$hashref->{atype}</A></TD> -END - - 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!<TR>! if ($tdcount == 0) ; - $tdcount = 0 if ($tdcount == -1) ; - print qq!<TD><A HREF="${p}edit/part_pkg.cgi?$pkgpart">!, - $part_pkg->getfield('pkg'),"</A></TD>"; - $tdcount ++ ; - if ($tdcount == 2) - { - print qq!</TR>\n! ; - $tdcount = 0 ; - } - } - - print "</TR>"; -} - -print <<END; - </TABLE> - </BODY> -</HTML> -END - +<% 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, + '', + ], + ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = +'Agent types define groups of packages that you can then assign to'. +' particular agents.<BR><BR>'. +qq!<A HREF="${p}edit/agent_type.cgi"><I>Add a new agent type</I></A><BR><BR>!; + +my $count_query = 'SELECT COUNT(*) FROM agent_type'; + +#false laziness w/access_user.html +my $packages_sub = sub { +my $agent_type = shift; + +[ map { + my $type_pkgs = $_; + #my $part_pkg = $type_pkgs->part_pkg; + [ + { + #'data' => $part_pkg->pkg. ' - '. $part_pkg->comment, + 'data' => $type_pkgs->pkg. ' - '. $type_pkgs->comment, + 'align' => 'left', + 'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart, + }, + ]; + } + + $agent_type->type_pkgs_enabled +]; + +}; + +my $link = [ $p.'edit/agent_type.cgi?', 'typenum' ]; + +</%init> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index 1e0e0880c..3bbbb4b47 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -1,142 +1,170 @@ -<!-- mason kludge --> -<% - -my $conf = new FS::Conf; -my $enable_taxclasses = $conf->exists('enable_taxclasses'); - -print header("Tax Rate Listing", menubar( - 'Main Menu' => $p, +<% include('/elements/header.html', "Tax Rate Listing", menubar( 'Edit tax rates' => $p. "edit/cust_main_county.cgi", -)),<<END; +)) %> + Click on <u>expand country</u> to specify a country's tax rates by state. <BR>Click on <u>expand state</u> to specify a state's tax rates by county. -END - -if ( $enable_taxclasses ) { - print '<BR>Click on <u>expand taxclasses</u> to specify tax classes'; -} +% +%my $conf = new FS::Conf; +%my $enable_taxclasses = $conf->exists('enable_taxclasses'); +% +%if ( $enable_taxclasses ) { + + + <BR>Click on <u>expand taxclasses</u> to specify tax classes +% } + + +<BR><BR> +<% table() %> + + <TR> + <TH><FONT SIZE=-1>Country</FONT></TH> + <TH><FONT SIZE=-1>State</FONT></TH> + <TH>County</TH> + <TH>Taxclass<BR><FONT SIZE=-1>(per-package classification)</FONT></TH> + <TH>Tax name<BR><FONT SIZE=-1>(printed on invoices)</FONT></TH> + <TH><FONT SIZE=-1>Tax</FONT></TH> + <TH><FONT SIZE=-1>Exemption</TH> + </TR> +% +%my @regions = sort { $a->country cmp $b->country +% or $a->state cmp $b->state +% or $a->county cmp $b->county +% or $a->taxclass cmp $b->taxclass +% } qsearch('cust_main_county',{}); +% +%my $sup=0; +%#foreach $cust_main_county ( @regions ) { +%for ( my $i=0; $i<@regions; $i++ ) { +% my $cust_main_county = $regions[$i]; +% my $hashref = $cust_main_county->hashref; +% +% -print '<BR><BR>'. &table(). <<END; <TR> - <TH><FONT SIZE=-1>Country</FONT></TH> - <TH><FONT SIZE=-1>State</FONT></TH> - <TH>County</TH> - <TH>Taxclass<BR><FONT SIZE=-1>(per-package classification)</FONT></TH> - <TH>Tax name<BR><FONT SIZE=-1>(printed on invoices)</FONT></TH> - <TH><FONT SIZE=-1>Tax</FONT></TH> - <TH><FONT SIZE=-1>Exemption</TH> - </TR> -END - -my @regions = sort { $a->country cmp $b->country - or $a->state cmp $b->state - or $a->county cmp $b->county - or $a->taxclass cmp $b->taxclass - } qsearch('cust_main_county',{}); - -my $sup=0; -#foreach $cust_main_county ( @regions ) { -for ( my $i=0; $i<@regions; $i++ ) { - my $cust_main_county = $regions[$i]; - my $hashref = $cust_main_county->hashref; - print <<END; - <TR> - <TD BGCOLOR="#ffffff">$hashref->{country}</TD> -END - - my $j; - if ( $sup ) { - $sup--; - } else { - - #lookahead - for ( $j=1; $i+$j<@regions; $j++ ) { - last if $hashref->{country} ne $regions[$i+$j]->country - || $hashref->{state} ne $regions[$i+$j]->state - || $hashref->{tax} != $regions[$i+$j]->tax - || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount - || $hashref->{setuptax} ne $regions[$i+$j]->setuptax - || $hashref->{recurtax} ne $regions[$i+$j]->recurtax; - } - - my $newsup=0; - if ( $j>1 && $i+$j+1 < @regions - && ( $hashref->{state} ne $regions[$i+$j+1]->state - || $hashref->{country} ne $regions[$i+$j+1]->country - ) - && ( ! $i - || $hashref->{state} ne $regions[$i-1]->state - || $hashref->{country} ne $regions[$i-1]->country - ) - ) { - $sup = $j-1; - } else { - $j = 1; - } - - print "<TD ROWSPAN=$j", $hashref->{state} + <TD BGCOLOR="#ffffff"><% $hashref->{country} %></TD> +% +% +% my $j; +% if ( $sup ) { +% $sup--; +% } else { +% +% #lookahead +% for ( $j=1; $i+$j<@regions; $j++ ) { +% last if $hashref->{country} ne $regions[$i+$j]->country +% || $hashref->{state} ne $regions[$i+$j]->state +% || $hashref->{tax} != $regions[$i+$j]->tax +% || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount +% || $hashref->{setuptax} ne $regions[$i+$j]->setuptax +% || $hashref->{recurtax} ne $regions[$i+$j]->recurtax; +% } +% +% my $newsup=0; +% if ( $j>1 && $i+$j+1 < @regions +% && ( $hashref->{state} ne $regions[$i+$j+1]->state +% || $hashref->{country} ne $regions[$i+$j+1]->country +% ) +% && ( ! $i +% || $hashref->{state} ne $regions[$i-1]->state +% || $hashref->{country} ne $regions[$i-1]->country +% ) +% ) { +% $sup = $j-1; +% } else { +% $j = 1; +% } +% +% + + + <TD ROWSPAN=<% $j %><% + $hashref->{state} ? ' BGCOLOR="#ffffff">'. $hashref->{state} : qq! BGCOLOR="#cccccc">(ALL) <FONT SIZE=-1>!. qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}. - qq!">expand country</A></FONT>!; + qq!">expand country</A></FONT>! + %> +% if ( $j>1 ) { - print qq! <FONT SIZE=-1><A HREF="${p}edit/process/cust_main_county-collapse.cgi?!. $hashref->{taxnum}. qq!">collapse state</A></FONT>! if $j>1; + <FONT SIZE=-1><A HREF="<% $p %>edit/process/cust_main_county-collapse.cgi?<% $hashref->{taxnum} %>">collapse state</A></FONT> +% } - print "</TD>"; - } -# $sup=$newsup; + </TD> +% } +% # $sup=$newsup; - print "<TD"; - if ( $hashref->{county} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{county}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $hashref->{state} ) { - print qq!<FONT SIZE=-1>!. - qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}. - qq!">expand state</A></FONT>!; - } - } - print "</TD>"; - - print "<TD"; - if ( $hashref->{taxclass} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxclass}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $enable_taxclasses ) { - print qq!<FONT SIZE=-1>!. - qq!<A HREF="${p}edit/cust_main_county-expand.cgi?taxclass!. - $hashref->{taxnum}. qq!">expand taxclasses</A></FONT>!; - } - - } - print "</TD>"; - - print "<TD"; - if ( $hashref->{taxname} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxname}; - } else { - print ' BGCOLOR="#cccccc">Tax'; - } - print "</TD>"; - - print "<TD BGCOLOR=\"#ffffff\">$hashref->{tax}%</TD>". - '<TD BGCOLOR="#ffffff">'; - print '$'. sprintf("%.2f", $hashref->{exempt_amount} ). - ' per month<BR>' - if $hashref->{exempt_amount} > 0; - print 'Setup fee<BR>' if $hashref->{setuptax} =~ /^Y$/i; - print 'Recurring fee<BR>' if $hashref->{recurtax} =~ /^Y$/i; - print '</TD></TR>'; - -} - -print <<END; - </TABLE> - </BODY> -</HTML> -END - -%> + + <TD +% if ( $hashref->{county} ) { +% + BGCOLOR="#ffffff"><% $hashref->{county} %> +% } else { +% + BGCOLOR="#cccccc">(ALL) +% if ( $hashref->{state} ) { + + <FONT SIZE=-1><A HREF="<% $p %>edit/cust_main_county-expand.cgi?<% $hashref->{taxnum} %>">expand state</A></FONT> +% } +% } + + </TD> + + <TD +% if ( $hashref->{taxclass} ) { +% + BGCOLOR="#ffffff"><% $hashref->{taxclass} %> +% } else { +% + BGCOLOR="#cccccc">(ALL) +% if ( $enable_taxclasses ) { + + <FONT SIZE=-1><A HREF="<% $p %>edit/cust_main_county-expand.cgi?taxclass<% $hashref->{taxnum} %>">expand taxclasses</A></FONT> +% } +% } + + </TD> + + <TD +% if ( $hashref->{taxname} ) { +% + BGCOLOR="#ffffff"><% $hashref->{taxname} %> +% } else { +% + BGCOLOR="#cccccc">Tax +% } + + </TD> + + <TD BGCOLOR="#ffffff"><% $hashref->{tax} %>%</TD> + + <TD BGCOLOR="#ffffff"> +% if ( $hashref->{exempt_amount} > 0 ) { + + $<% sprintf("%.2f", $hashref->{exempt_amount} ) %> per month<BR> +% } +% if ( $hashref->{setuptax} =~ /^Y$/i ) { + + Setup fee<BR> +% } +% if ( $hashref->{recurtax} =~ /^Y$/i ) { + + Recurring fee<BR> +% } + + + </TD> + + </TR> +% } + + +</TABLE> + +<% include('/elements/footer.html') %> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi deleted file mode 100755 index 3420e97b6..000000000 --- a/httemplate/browse/cust_pay_batch.cgi +++ /dev/null @@ -1,76 +0,0 @@ -<!-- mason kludge --> -<%= header("Pending credit card batch", menubar( 'Main Menu' => $p,)) %> - -<FORM ACTION="<%=$p%>misc/download-batch.cgi" METHOD="POST"> -Download batch in format <SELECT NAME="format"> -<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION> -</SELECT><INPUT TYPE="submit" VALUE="Download"></FORM> -<BR><BR> - -<FORM ACTION="<%=$p%>misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data"> -Upload results<BR> -Filename <INPUT TYPE="file" NAME="batch_results"><BR> -Format <SELECT NAME="format"> -<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION> -</SELECT><BR> -<INPUT TYPE="submit" VALUE="Upload"></FORM> -<BR> - -<% - my $statement = "SELECT SUM(amount) from cust_pay_batch"; - my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement"; - $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; - my $total = $sth->fetchrow_arrayref->[0]; - - my $c_statement = "SELECT COUNT(*) from cust_pay_batch"; - my $c_sth = dbh->prepare($c_statement) - or die dbh->errstr. "doing $c_statement"; - $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr; - my $cards = $c_sth->fetchrow_arrayref->[0]; -%> -<%= $cards %> credit card payments batched<BR> -$<%= sprintf("%.2f", $total) %> total in pending batch<BR> - -<BR> -<%= &table() %> - <TR> - <TH>#</TH> - <TH><font size=-1>inv#</font></TH> - <TH COLSPAN=2>Customer</TH> - <TH>Card name</TH> - <TH>Card</TH> - <TH>Exp</TH> - <TH>Amount</TH> - </TR> - -<% -foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } - qsearch('cust_pay_batch', {} ) -) { - my $cardnum = $cust_pay_batch->cardnum; - #$cardnum =~ s/.{4}$/xxxx/; - $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); - - $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; - my( $mon, $year ) = ( $2, $1 ); - $mon = "0$mon" if $mon < 10; - my $exp = "$mon/$year"; - -%> - - <TR> - <TD><%= $cust_pay_batch->paybatchnum %></TD> - <TD><A HREF="../view/cust_bill.cgi?<%= $cust_pay_batch->invnum %>"><%= $cust_pay_batch->invnum %></TD> - <TD><A HREF="../view/cust_main.cgi?<%= $cust_pay_batch->custnum %>"><%= $cust_pay_batch->custnum %></TD> - <TD><%= $cust_pay_batch->get('last'). ', '. $cust_pay_batch->first %></TD> - <TD><%= $cust_pay_batch->payname %></TD> - <TD><%= $cardnum %></TD> - <TD><%= $exp %></TD> - <TD align="right">$<%= $cust_pay_batch->amount %></TD> - </TR> - -<% } %> - - </TABLE> - </BODY> -</HTML> diff --git a/httemplate/browse/elements/browse.html b/httemplate/browse/elements/browse.html new file mode 100644 index 000000000..2cc5a9660 --- /dev/null +++ b/httemplate/browse/elements/browse.html @@ -0,0 +1,6 @@ +<% include( '/search/elements/search.html', + @_, + 'disable_download' => 1, + 'disable_nonefound' => 1, + ) +%> diff --git a/httemplate/browse/generic.cgi b/httemplate/browse/generic.cgi deleted file mode 100644 index 9ac0f2391..000000000 --- a/httemplate/browse/generic.cgi +++ /dev/null @@ -1,46 +0,0 @@ -<% - -use FS::Record qw(qsearch dbdef); -use DBIx::DBSchema; -use DBIx::DBSchema::Table; - -my $error; -my $p2 = popurl(2); -my ($table) = $cgi->keywords; -my $dbdef = dbdef or die "Cannot fetch dbdef!"; -my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; - -my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; -print header("Browse $table", menubar('Main Menu' => $p)); - -my @rec = qsearch($table, {}); -my @col = $dbdef_table->columns; - -if ($cgi->param('error')) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> - <BR><BR> -<% } -%> -<A HREF="<%=$p2%>edit/<%=$table%>.cgi"><I>Add a new <%=$table%></I></A><BR><BR> - -<%=table()%> -<TH> -<% foreach (grep { $_ ne $pkey } @col) { - %><TD><%=$_%></TD> - <% } %> -</TH> -<% foreach $rec (sort {$a->getfield($pkey) cmp $b->getfield($pkey) } @rec) { - %> - <TR> - <TD> - <A HREF="<%=$p2%>edit/<%=$table%>.cgi?<%=$rec->getfield($pkey)%>"> - <%=$rec->getfield($pkey)%></A> </TD> <% - foreach $col (grep { $_ ne $pkey } @col) { %> - <TD><%=$rec->getfield($col)%></TD> <% } %> - </A> - </TR> -<% } %> -</TABLE> -</BODY> -</HTML> - diff --git a/httemplate/browse/inventory_class.html b/httemplate/browse/inventory_class.html new file mode 100644 index 000000000..8ce131ac2 --- /dev/null +++ b/httemplate/browse/inventory_class.html @@ -0,0 +1,93 @@ +<% include( 'elements/browse.html', + 'title' => 'Inventory Classes', + 'name' => 'inventory classes', + 'menubar' => [ 'Add a new inventory class' => + $p.'edit/inventory_class.html', + ], + 'query' => { 'table' => 'inventory_class', }, + 'count_query' => 'SELECT COUNT(*) FROM inventory_class', + 'header' => [ '#', 'Inventory class', 'Inventory' ], + 'fields' => [ 'classnum', + 'classname', + sub { + #my $inventory_class = shift; + my $i_c = shift; + + my $link = + $p. 'search/inventory_item.html?'. + 'classnum='. $i_c->classnum; + + my %actioncol = (); + foreach ( keys %inv_action_link ) { + my($label, $baseurl, $method) = + @{ $inv_action_link{$_} }; + my $url = $baseurl. $i_c->$method(); + $actioncol{$_} = + '<FONT SIZE="-1">'. + '('. + '<A HREF="'.$url.'">'. + $label. + '</A>'. + ')'. + '</FONT>'; + } + + my %num = map { + $_ => $i_c->$_(); + } keys %labels; + + [ map { + [ + { + 'data' => '<B>'. $num{$_}. '</B>', + 'align' => 'right', + }, + { + 'data' => $labels{$_}, + 'align' => 'left', + 'link' => ( $num{$_} + ? $link.$link{$_} + : '' + ), + }, + { 'data' => $actioncol{$_}, + 'align' => 'left', + }, + ] + } keys %labels + ]; + }, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +tie my %labels, 'Tie::IxHash', + 'num_avail' => 'Available', # <FONT SIZE="-1"><A HREF="eventually">(upload batch)</A></FONT>', + 'num_used' => 'In use', #'Used', #'Allocated', + 'num_total' => 'Total', +; + +my %link = ( + 'num_avail' => ';avail=1', + 'num_used' => ';used=1', + 'num_total' => '', +); + +my %inv_action_link = ( + 'num_avail' => [ 'upload batch', + $p.'misc/inventory_item-import.html?classnum=', + 'classnum' + ], +); + +my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; + +</%init> diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi index d4adf9f1a..2c916dc9f 100755 --- a/httemplate/browse/msgcat.cgi +++ b/httemplate/browse/msgcat.cgi @@ -1,10 +1,12 @@ -<!-- mason kludge --> -<% - -print header("View Message catalog", menubar( - 'Main Menu' => $p, +<% include('/elements/header.html', "View Message catalog", menubar( 'Edit message catalog' => $p. "edit/msgcat.cgi", -)), '<BR>'; +)) %> +<% $widget->html %> +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => 'en_US', @@ -39,12 +41,4 @@ my $widget = new HTML::Widgets::SelectLayers( ); -print $widget->html; - -print <<END; - </TABLE> - </BODY> -</HTML> -END - -%> +</%init> diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi index 9ccbfe632..022c65ea7 100755 --- a/httemplate/browse/nas.cgi +++ b/httemplate/browse/nas.cgi @@ -1,80 +1,81 @@ <!-- mason kludge --> -<% +% +% +%print header('NAS ports', menubar( +% 'Main Menu' => $p, +%)); +% +%my $now = time; +% +%foreach my $nas ( sort { $a->nasnum <=> $b->nasnum } qsearch( 'nas', {} ) ) { +% print $nas->nasnum. ": ". $nas->nas. " ". +% $nas->nasfqdn. " (". $nas->nasip. ") ". +% "as of ". time2str("%c",$nas->last). +% " (". &pretty_interval($now - $nas->last). " ago)<br>". +% &table(). "<TR><TH>Nas<BR>Port #</TH><TH>Global<BR>Port #</BR></TH>". +% "<TH>IP address</TH><TH>User</TH><TH>Since</TH><TH>Duration</TH><TR>", +% ; +% foreach my $port ( sort { +% $a->nasport <=> $b->nasport || $a->portnum <=> $b->portnum +% } qsearch( 'port', { 'nasnum' => $nas->nasnum } ) ) { +% my $session = $port->session; +% my($user, $since, $pretty_since, $duration); +% if ( ! $session ) { +% $user = "(empty)"; +% $since = 0; +% $pretty_since = "(never)"; +% $duration = ''; +% } elsif ( $session->logout ) { +% $user = "(empty)"; +% $since = $session->logout; +% } else { +% my $svc_acct = $session->svc_acct; +% $user = "<A HREF=\"$p/view/svc_acct.cgi?". $svc_acct->svcnum. "\">". +% $svc_acct->username. "</A>"; +% $since = $session->login; +% } +% $pretty_since = time2str("%c", $since) if $since; +% $duration = pretty_interval( $now - $since ). " ago" +% unless defined($duration); +% print "<TR><TD>". $port->nasport. "</TD><TD>". $port->portnum. "</TD><TD>". +% $port->ip. "</TD><TD>$user</TD><TD>$pretty_since". +% "</TD><TD>$duration</TD></TR>" +% ; +% } +% print "</TABLE><BR>"; +%} +% +%#Time::Duration?? +%sub pretty_interval { +% my $interval = shift; +% my %howlong = ( +% '604800' => 'week', +% '86400' => 'day', +% '3600' => 'hour', +% '60' => 'minute', +% '1' => 'second', +% ); +% +% my $pretty = ""; +% foreach my $key ( sort { $b <=> $a } keys %howlong ) { +% my $value = int( $interval / $key ); +% if ( $value ) { +% if ( $value == 1 ) { +% $pretty .= +% ( $howlong{$key} eq 'hour' ? 'an ' : 'a ' ). $howlong{$key}. " " +% } else { +% $pretty .= $value. ' '. $howlong{$key}. 's '; +% } +% } +% $interval -= $value * $key; +% } +% $pretty =~ /^\s*(\S.*\S)\s*$/; +% $1; +%} +% +%#print &table(), <<END; +%#<TR> +%# <TH>#</TH> +%# <TH>NAS</ +% -print header('NAS ports', menubar( - 'Main Menu' => $p, -)); - -my $now = time; - -foreach my $nas ( sort { $a->nasnum <=> $b->nasnum } qsearch( 'nas', {} ) ) { - print $nas->nasnum. ": ". $nas->nas. " ". - $nas->nasfqdn. " (". $nas->nasip. ") ". - "as of ". time2str("%c",$nas->last). - " (". &pretty_interval($now - $nas->last). " ago)<br>". - &table(). "<TR><TH>Nas<BR>Port #</TH><TH>Global<BR>Port #</BR></TH>". - "<TH>IP address</TH><TH>User</TH><TH>Since</TH><TH>Duration</TH><TR>", - ; - foreach my $port ( sort { - $a->nasport <=> $b->nasport || $a->portnum <=> $b->portnum - } qsearch( 'port', { 'nasnum' => $nas->nasnum } ) ) { - my $session = $port->session; - my($user, $since, $pretty_since, $duration); - if ( ! $session ) { - $user = "(empty)"; - $since = 0; - $pretty_since = "(never)"; - $duration = ''; - } elsif ( $session->logout ) { - $user = "(empty)"; - $since = $session->logout; - } else { - my $svc_acct = $session->svc_acct; - $user = "<A HREF=\"$p/view/svc_acct.cgi?". $svc_acct->svcnum. "\">". - $svc_acct->username. "</A>"; - $since = $session->login; - } - $pretty_since = time2str("%c", $since) if $since; - $duration = pretty_interval( $now - $since ). " ago" - unless defined($duration); - print "<TR><TD>". $port->nasport. "</TD><TD>". $port->portnum. "</TD><TD>". - $port->ip. "</TD><TD>$user</TD><TD>$pretty_since". - "</TD><TD>$duration</TD></TR>" - ; - } - print "</TABLE><BR>"; -} - -#Time::Duration?? -sub pretty_interval { - my $interval = shift; - my %howlong = ( - '604800' => 'week', - '86400' => 'day', - '3600' => 'hour', - '60' => 'minute', - '1' => 'second', - ); - - my $pretty = ""; - foreach my $key ( sort { $b <=> $a } keys %howlong ) { - my $value = int( $interval / $key ); - if ( $value ) { - if ( $value == 1 ) { - $pretty .= - ( $howlong{$key} eq 'hour' ? 'an ' : 'a ' ). $howlong{$key}. " " - } else { - $pretty .= $value. ' '. $howlong{$key}. 's '; - } - } - $interval -= $value * $key; - } - $pretty =~ /^\s*(\S.*\S)\s*$/; - $1; -} - -#print &table(), <<END; -#<TR> -# <TH>#</TH> -# <TH>NAS</ -%> diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi index 670474d48..682058b1d 100755 --- a/httemplate/browse/part_bill_event.cgi +++ b/httemplate/browse/part_bill_event.cgi @@ -1,71 +1,123 @@ -<!-- mason kludge --> -<% +<% include("/elements/header.html",'Invoice Event Listing', menubar( 'Main Menu' => $p) ) %> -my %search; -if ( $cgi->param('showdisabled') ) { - %search = (); -} else { - %search = ( 'disabled' => '' ); -} - -my @part_bill_event = qsearch('part_bill_event', \%search ); -my $total = scalar(@part_bill_event); - -%> -<%= header('Invoice Event Listing', menubar( 'Main Menu' => $p) ) %> + Invoice events are actions taken on open invoices.<BR><BR> - Invoice events are actions taken on overdue invoices.<BR><BR> -<A HREF="<%= $p %>edit/part_bill_event.cgi"><I>Add a new invoice event</I></A> +<A HREF="<% $p %>edit/part_bill_event.cgi"><I>Add a new invoice event</I></A> <BR><BR> -<%= $total %> events -<%= $cgi->param('showdisabled') + +<% $total %> events +<% $cgi->param('showdisabled') ? do { $cgi->param('showdisabled', 0); '( <a href="'. $cgi->self_url. '">hide disabled events</a> )'; } : do { $cgi->param('showdisabled', 1); '( <a href="'. $cgi->self_url. '">show disabled events</a> )'; } %> -<%= table() %> - <TR> - <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH> - <TH>Payby</TH> - <TH>After</TH> - <TH>Action</TH> - <TH>Options</TH> - <TH>Code</TH> - </TR> - -<% foreach my $part_bill_event ( sort { $a->payby cmp $b->payby - || $a->seconds <=> $b->seconds - || $a->weight <=> $b->weight - || $a->eventpart <=> $b->eventpart - } @part_bill_event ) { - my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart; - use Time::Duration; - my $delay = duration_exact($part_bill_event->seconds); - my $plandata = $part_bill_event->plandata; - $plandata =~ s/\n/<BR>/go; -%> - <TR> - <TD><A HREF="<%= $url %>"> - <%= $part_bill_event->eventpart %></A></TD> -<% unless ( $cgi->param('showdisabled') ) { %> - <TD> - <%= $part_bill_event->disabled ? 'DISABLED' : '' %></TD> -<% } %> - <TD><A HREF="<%= $url %>"> - <%= $part_bill_event->event %></A></TD> - <TD> - <%= $part_bill_event->payby %></TD> - <TD> - <%= $delay %></TD> - <TD> - <%= $part_bill_event->plan %></TD> - <TD> - <%= $plandata %></TD> - <TD><FONT SIZE="-1"> - <%= $part_bill_event->eventcode %></FONT></TD> - </TR> -<% } %> -</TABLE> +<BR><BR> +% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname; +% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly'; +% foreach my $payby ( keys %payby ) { +% my $oldfreq = ''; +% +% my @payby_part_bill_event = +% grep { $payby eq $_->payby } +% sort { ( $a->freq || '1d') cmp ( $b->freq || '1d' ) # for now +% || $a->seconds <=> $b->seconds +% || $a->weight <=> $b->weight +% || $a->eventpart <=> $b->eventpart +% } +% @part_bill_event; +% +% +% if ( @payby_part_bill_event ) { + + + <% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% +% foreach my $part_bill_event ( @payby_part_bill_event ) { +% my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart; +% my $delay = duration_exact($part_bill_event->seconds); +% ( my $plandata = $part_bill_event->plandata ) =~ s/\n/<BR>/go; +% my $freq = $part_bill_event->freq || '1d'; +% my $reason = $part_bill_event->reasontext ; +% +% if ( $oldfreq ne $freq ) { + + + <TR> + <TH CLASS="grid" BGCOLOR="#999999" COLSPAN=<% $cgi->param('showdisabled') ? 7 : 8 %>><% ucfirst($freq{$freq}) %> event tests for <FONT SIZE="+1"><I><% $payby{$payby} %> customers</I></FONT></TH> + </TR> + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">After</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Reason</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Options</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Code</TH> + </TR> +% +% $oldfreq = $freq; +% $bgcolor = ''; +% +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_bill_event->eventpart %></A></TD> +% unless ( $cgi->param('showdisabled') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_bill_event->disabled ? 'DISABLED' : '' %></TD> +% } + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_bill_event->event %></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $delay %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_bill_event->plan %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $reason %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $plandata %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="-1"> + <% $part_bill_event->eventcode %></FONT></TD> + </TR> +% } + + </TABLE> + <BR><BR> +% } +% } + + </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %search; +if ( $cgi->param('showdisabled') ) { +%search = (); +} else { +%search = ( 'disabled' => '' ); +} + +my @part_bill_event = qsearch('part_bill_event', \%search ); +my $total = scalar(@part_bill_event); + +</%init> diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 79c57aefc..7b8ac8c20 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -1,7 +1,6 @@ -<!-- mason kludge --> -<%= header("Export Listing", menubar( 'Main Menu' => "$p#sysadmin" )) %> +<% include("/elements/header.html","Export Listing", menubar( 'Main Menu' => "$p#sysadmin" )) %> Provisioning services to external machines, databases and APIs.<BR><BR> -<A HREF="<%= $p %>edit/part_export.cgi"><I>Add a new export</I></A><BR><BR> +<A HREF="<% $p %>edit/part_export.cgi"><I>Add a new export</I></A><BR><BR> <SCRIPT> function part_export_areyousure(href) { if (confirm("Are you sure you want to delete this export?") == true) @@ -9,31 +8,37 @@ function part_export_areyousure(href) { } </SCRIPT> -<%= table() %> +<% table() %> <TR> <TH COLSPAN=2>Export</TH> <TH>Options</TH> </TR> +% foreach my $part_export ( sort { +% $a->getfield('exportnum') <=> $b->getfield('exportnum') +% } qsearch('part_export',{}) ) { +% -<% foreach my $part_export ( sort { - $a->getfield('exportnum') <=> $b->getfield('exportnum') - } qsearch('part_export',{}) ) { -%> <TR> - <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exportnum %></A></TD> - <TD><%= $part_export->exporttype %> to <%= $part_export->machine %> (<A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<%= $p %>misc/delete-part_export.cgi?<%= $part_export->exportnum %>')">delete</A>)</TD> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD> + <TD><% $part_export->exporttype %> to <% $part_export->machine %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)</TD> <TD> - <%= itable() %> - <% my %opt = $part_export->options; - foreach my $opt ( keys %opt ) { %> - <TR><TD><%= $opt %></TD><TD><%= encode_entities($opt{$opt}) %></TD></TR> - <% } %> + <% itable() %> +% my %opt = $part_export->options; +% foreach my $opt ( keys %opt ) { + + <TR><TD><% $opt %></TD><TD><% encode_entities($opt{$opt}) %></TD></TR> +% } + </TABLE> </TD> </TR> +% } -<% } %> </TABLE> </BODY> </HTML> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 8d5b55451..6b62ec67b 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -1,169 +1,261 @@ -<!-- mason kludge --> -<% +<% include( 'elements/browse.html', + 'title' => 'Package Definitions', + 'menubar' => [ 'Main Menu' => $p ], + 'html_init' => $html_init, + 'html_posttotal' => $posttotal, + 'name' => 'package definitions', + 'query' => { 'select' => $select, + 'table' => 'part_pkg', + 'hashref' => \%search, + 'extra_sql' => "ORDER BY $orderby", + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'align' => $align, + 'style' => \@style, + ) +%> +<%init> -my %search; -if ( $cgi->param('showdisabled') ) { - %search = (); -} else { +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +#false laziness w/access_user.html +my %search = (); +my $search = ''; +unless ( $cgi->param('showdisabled') ) { %search = ( 'disabled' => '' ); + $search = "( disabled = '' OR disabled IS NULL )"; } -my @part_pkg = qsearch('part_pkg', \%search ); -my $total = scalar(@part_pkg); - -my $sortby; -my %num_active_cust_pkg = (); -my( $suspended_sth, $canceled_sth ) = ( '', '' ); +my $select = '*'; +my $orderby = 'pkgpart'; if ( $cgi->param('active') ) { - my $active_sth = dbh->prepare( - 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. - ' AND ( cancel IS NULL OR cancel = 0 )'. - ' AND ( susp IS NULL OR susp = 0 )' - ) or die dbh->errstr; - foreach my $part_pkg ( @part_pkg ) { - $active_sth->execute($part_pkg->pkgpart) or die $active_sth->errstr; - $num_active_cust_pkg{$part_pkg->pkgpart} = - $active_sth->fetchrow_arrayref->[0]; - } - $sortby = sub { - $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart}; - }; - - $suspended_sth = dbh->prepare( - 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. - ' AND ( cancel IS NULL OR cancel = 0 )'. - ' AND susp IS NOT NULL AND susp != 0' - ) or die dbh->errstr; - - $canceled_sth = dbh->prepare( - 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. - ' AND cancel IS NOT NULL AND cancel != 0' - ) or die dbh->errstr; -} else { - $sortby = sub { $a->pkgpart <=> $b->pkgpart; }; + $orderby = 'num_active DESC'; } + $select = " + + *, + + ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart + AND ( cancel IS NULL OR cancel = 0 ) + AND ( susp IS NULL OR susp = 0 ) + ) AS num_active, + + ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart + AND ( cancel IS NULL OR cancel = 0 ) + AND susp IS NOT NULL AND susp != 0 + ) AS num_suspended, + + ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart + AND cancel IS NOT NULL AND cancel != 0 + ) AS num_cancelled + + "; + +#} my $conf = new FS::Conf; my $taxclasses = $conf->exists('enable_taxclasses'); -%> -<%= header("Package Definition Listing",menubar( 'Main Menu' => $p )) %> -<% unless ( $cgi->param('active') ) { %> - One or more service definitions are grouped together into a package - definition and given pricing information. Customers purchase packages - rather than purchase services directly.<BR><BR> - <A HREF="<%= $p %>edit/part_pkg.cgi"><I>Add a new package definition</I></A> - <BR><BR> -<% } %> - -<%= $total %> package definitions -<% if ( $cgi->param('showdisabled') ) { $cgi->param('showdisabled', 0); %> - ( <a href="<%= $cgi->self_url %>">hide disabled packages</a> ) -<% } else { $cgi->param('showdisabled', 1); %> - ( <a href="<%= $cgi->self_url %>">show disabled packages</a> ) -<% } %> - -<% my $colspan = $cgi->param('showdisabled') ? 2 : 3; %> - -<%= &table() %> - <TR> - <TH COLSPAN=<%= $colspan %>>Package</TH> - <TH>Comment</TH> -<% if ( $cgi->param('active') ) { %> - <TH><FONT SIZE=-1>Customer<BR>packages</FONT></TH> -<% } %> - <TH><FONT SIZE=-1>Freq.</FONT></TH> -<% if ( $taxclasses ) { %> - <TH><FONT SIZE=-1>Taxclass</FONT></TH> -<% } %> - <TH><FONT SIZE=-1>Plan</FONT></TH> - <TH><FONT SIZE=-1>Data</FONT></TH> - <TH>Service</TH> - <TH><FONT SIZE=-1>Quan.</FONT></TH> -<% if ( dbdef->table('pkg_svc')->column('primary_svc') ) { %> - <TH><FONT SIZE=-1>Primary</FONT></TH> -<% } %> - - </TR> - -<% -foreach my $part_pkg ( sort $sortby @part_pkg ) { - my @pkg_svc = $part_pkg->pkg_svc; - my($rowspan)=scalar(@pkg_svc); - my $plandata; - if ( $part_pkg->plan ) { - $plandata = $part_pkg->plandata; - $plandata =~ s/^(\w+)=/$1 /mg; - $plandata =~ s/\n/<BR>/g; - } else { - $part_pkg->plan('(legacy)'); - $plandata = "Setup ". $part_pkg->setup. - "<BR>Recur ". $part_pkg->recur; - } -%> - <TR> - <TD ROWSPAN=<%= $rowspan %>><A HREF="<%=$p%>edit/part_pkg.cgi?<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkgpart %></A></TD> - -<% unless ( $cgi->param('showdisabled') ) { %> - <TD ROWSPAN=<%= $rowspan %>> - <% if ( $part_pkg->disabled ) { %> - DISABLED - <% } %> - </TD> -<% } %> - - <TD ROWSPAN=<%= $rowspan %>><A HREF="<%=$p%>edit/part_pkg.cgi?<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg %></A></TD> - <TD ROWSPAN=<%= $rowspan %>><%= $part_pkg->comment %></TD> - -<% if ( $cgi->param('active') ) { %> - <TD ROWSPAN=<%= $rowspan %>> - <FONT COLOR="#00CC00"><B><%= $num_active_cust_pkg{$part_pkg->pkgpart} %></B></FONT> <A HREF="<%=$p%>search/cust_pkg.cgi?magic=active;pkgpart=<%= $part_pkg->pkgpart %>">active</A><BR> - - <% $suspended_sth->execute( $part_pkg->pkgpart ) - or die $suspended_sth->errstr; - my $num_suspended = $suspended_sth->fetchrow_arrayref->[0]; - %> - <FONT COLOR="#FF9900"><B><%= $num_suspended %></B></FONT> <A HREF="<%=$p%>search/cust_pkg.cgi?magic=suspended;pkgpart=<%= $part_pkg->pkgpart %>">suspended</A><BR> - - <% $canceled_sth->execute( $part_pkg->pkgpart ) - or die $canceled_sth->errstr; - my $num_canceled = $canceled_sth->fetchrow_arrayref->[0]; - %> - <FONT COLOR="#FF0000"><B><%= $num_canceled %></B></FONT> <A HREF="<%=$p%>search/cust_pkg.cgi?magic=canceled;pkgpart=<%= $part_pkg->pkgpart %>">canceled</A> - </TD> -<% } %> - - <TD ROWSPAN=<%= $rowspan %>><%= $part_pkg->freq_pretty %></TD> - -<% if ( $taxclasses ) { %> - <TD ROWSPAN=<%= $rowspan %>><%= $part_pkg->taxclass || ' ' %></TD> -<% } %> - - <TD ROWSPAN=<%= $rowspan %>><%= $part_pkg->plan %></TD> - <TD ROWSPAN=<%= $rowspan %>><%= $plandata %></TD> - -<% - my($n)=""; - foreach my $pkg_svc ( @pkg_svc ) { - my($svcpart)=$pkg_svc->getfield('svcpart'); - my($part_svc) = qsearchs('part_svc',{'svcpart'=> $svcpart }); - print $n,qq!<TD><A HREF="${p}edit/part_svc.cgi?$svcpart">!, - $part_svc->getfield('svc'),"</A></TD><TD>", - $pkg_svc->getfield('quantity'),"</TD>"; - if ( dbdef->table('pkg_svc')->column('primary_svc') ) { - print '<TD>'; - print 'PRIMARY' if $pkg_svc->primary_svc =~ /^Y/i; - print '</TD>'; - } - print "</TR>\n"; - $n="<TR>"; - } -%> +my $html_init; +#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 + rather than purchase services directly.<BR><BR> + <A HREF="${p}edit/part_pkg.cgi"><I>Add a new package definition</I></A> + <BR><BR> + !; +#} + +my $posttotal; +if ( $cgi->param('showdisabled') ) { + $cgi->param('showdisabled', 0); + $posttotal = '( <a href="'. $cgi->self_url. '">hide disabled packages</a> )'; + $cgi->param('showdisabled', 1); +} else { + $cgi->param('showdisabled', 1); + $posttotal = '( <a href="'. $cgi->self_url. '">show disabled packages</a> )'; + $cgi->param('showdisabled', 0); +} + + +# ------ + +my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ]; + +my @header = ( '#', 'Package', 'Comment' ); +my @fields = ( 'pkgpart', 'pkg', 'comment' ); +my $align = 'rll'; +my @links = ( $link, $link, '' ); +my @style = ( '', '', '' ); + +#false laziness w/access_user.html +#unless ( $cgi->param('showdisabled') ) { #its been reversed already +if ( $cgi->param('showdisabled') ) { #its been reversed already + push @header, 'Status'; + push @fields, sub { shift->disabled + ? '<FONT COLOR="#FF0000">DISABLED</FONT>' + : '<FONT COLOR="#00CC00">Active</FONT>' + }; + push @links, ''; + $align .= 'c'; + push @style, 'b'; +} + +unless ( 0 ) { #already showing only one class or something? + push @header, 'Class'; + push @fields, sub { shift->classname || '(none)'; }; + $align .= 'l'; +} + +#if ( $cgi->param('active') ) { + push @header, 'Customer<BR>packages'; + my %col = ( + 'active' => '00CC00', + 'suspended' => 'FF9900', + 'cancelled' => 'FF0000', + #'one-time charge' => '000000', + 'charge' => '000000', + ); + my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart='; + push @fields, sub { my $part_pkg = shift; + [ + map { + my $magic = $_; + my $label = $_; + if ( $magic eq 'active' && $part_pkg->freq == 0 ) { + $magic = 'inactive'; + #$label = 'one-time charge', + $label = 'charge', + } + + [ + { + 'data' => '<B><FONT COLOR="#'. $col{$label}. '">'. + $part_pkg->get("num_$_"). + '</FONT></B>', + 'align' => 'right', + }, + { + 'data' => $label. + ( $part_pkg->get("num_$_") != 1 + && $label =~ /charge$/ + ? 's' + : '' + ), + 'align' => 'left', + 'link' => ( $part_pkg->get("num_$_") + ? $cust_pkg_link. + $part_pkg->pkgpart. + ";magic=$magic" + : '' + ), + }, + ], + } (qw( active suspended cancelled )) + ]; }; + $align .= 'r'; +#} + +push @header, 'Frequency'; +push @fields, sub { shift->freq_pretty; }; +$align .= 'l'; + +if ( $taxclasses ) { + push @header, 'Taxclass'; + push @fields, sub { shift->taxclass() || ' '; }; + $align .= 'l'; +} + +push @header, 'Plan', + 'Data', + 'Services'; + #'Service', 'Quan', 'Primary'; + +push @fields, sub { shift->plan || '(legacy)' }, + + sub { + my $part_pkg = shift; + if ( $part_pkg->plan ) { + + [ map { + /^(\w+)=(.*)$/; #or something; + [ + { 'data' => $1, + 'align' => 'right', + }, + { 'data' => $2, + 'align' => 'left', + }, + ]; + } + split(/\n/, $part_pkg->plandata) + ]; + + } else { + + [ map { [ + { 'data' => uc($_), + 'align' => 'right', + }, + { + 'data' => $part_pkg->$_(), + 'align' => 'left', + }, + ]; + } + (qw(setup recur)) + ]; + + } + + }, + + sub { + my $part_pkg = shift; + + [ map { + my $pkg_svc = $_; + my $part_svc = $pkg_svc->part_svc; + my $svc = $part_svc->svc; + if ( $pkg_svc->primary_svc =~ /^Y/i ) { + $svc = "<B>$svc (PRIMARY)</B>"; + } + $svc =~ s/ +/ /g; + + [ + { + 'data' => '<B>'. $pkg_svc->quantity. '</B>', + 'align' => 'right' + }, + { + 'data' => $svc, + 'align' => 'left', + 'link' => $p. 'edit/part_svc.cgi?'. + $part_svc->svcpart, + }, + ]; + } + sort { $b->primary_svc =~ /^Y/i + <=> $a->primary_svc =~ /^Y/i + } + $part_pkg->pkg_svc + + ]; + + }; + +$align .= 'lrl'; #rr'; + +# -------- - </TR> -<% } %> +my $count_query = 'SELECT COUNT(*) FROM part_pkg'; +$count_query .= " WHERE $search" + if $search; - </TABLE> - </BODY> -</HTML> +</%init> diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi deleted file mode 100755 index 581e01bb6..000000000 --- a/httemplate/browse/part_referral.cgi +++ /dev/null @@ -1,97 +0,0 @@ -<!-- mason kludge --> -<%= header("Advertising source Listing", menubar( - 'Main Menu' => $p, -# 'Add new referral' => "../edit/part_referral.cgi", -)) %> -Where a customer heard about your service. Tracked for informational purposes. -<BR><BR> -<A HREF="<%= $p %>edit/part_referral.cgi"><I>Add a new advertising source</I></A> -<BR><BR> - -<% - my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); - my %after; - tie %after, 'Tie::IxHash', - 'Today' => 0, - 'Yesterday' => 86400, # 60sec * 60min * 24hrs - 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days - 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days - 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days - 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days - 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days - 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days - 'Total' => $today, - ; - my %before = ( - 'Today' => 86400, # 60sec * 60min * 24hrs - 'Yesterday' => 0, - 'Past week' => 86400, # 60sec * 60min * 24hrs - 'Past 30 days' => 86400, # 60sec * 60min * 24hrs - 'Past 60 days' => 86400, # 60sec * 60min * 24hrs - 'Past 90 days' => 86400, # 60sec * 60min * 24hrs - 'Past 6 months' => 86400, # 60sec * 60min * 24hrs - 'Past year' => 86400, # 60sec * 60min * 24hrs - 'Total' => 86400, # 60sec * 60min * 24hrs - ); - - my $statement = "SELECT COUNT(*) FROM h_cust_main - WHERE history_action = 'insert' - AND refnum = ? - AND history_date >= ? - AND history_date < ? - "; - my $sth = dbh->prepare($statement) - or die dbh->errstr; -%> - -<%= table() %> -<TR> - <TH COLSPAN=2 ROWSPAN=2>Advertising source</TH> - <TH COLSPAN=<%= scalar(keys %after) %>>Customers</TH> -</TR> -<% for my $period ( keys %after ) { %> - <TH><FONT SIZE=-1><%= $period %></FONT></TH> -<% } %> -</TR> - -<% -foreach my $part_referral ( sort { - $a->getfield('refnum') <=> $b->getfield('refnum') -} qsearch('part_referral',{}) ) { -%> - <TR> - <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>"> - <%= $part_referral->refnum %></A></TD> - <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>"> - <%= $part_referral->referral %></A></TD> - <% for my $period ( keys %after ) { - $sth->execute( $part_referral->refnum, - $today-$after{$period}, - $today+$before{$period}, - ) or die $sth->errstr; - my $number = $sth->fetchrow_arrayref->[0]; - %> - <TD ALIGN="right"><%= $number %></TD> - <% } %> - </TR> -<% } %> - -<% - $statement =~ s/AND refnum = \?//; - $sth = dbh->prepare($statement) - or die dbh->errstr; -%> - <TR> - <TH COLSPAN=2>Total</TH> - <% for my $period ( keys %after ) { - $sth->execute( $today-$after{$period}, - $today+$before{$period}, - ) or die $sth->errstr; - my $number = $sth->fetchrow_arrayref->[0]; - %> - <TD ALIGN="right"><%= $number %></TD> - <% } %> - </TR> - </TABLE> - </BODY> -</HTML> diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html new file mode 100755 index 000000000..065d8c1c1 --- /dev/null +++ b/httemplate/browse/part_referral.html @@ -0,0 +1,147 @@ +<% include("/elements/header.html","Advertising source Listing" ) %> + +Where a customer heard about your service. Tracked for informational purposes. +<BR><BR> + +<A HREF="<% $p %>edit/part_referral.html"><I>Add a new advertising source</I></A> +<BR><BR> +% +% my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); +% my %after; +% tie %after, 'Tie::IxHash', +% 'Today' => 0, +% 'Yesterday' => 86400, # 60sec * 60min * 24hrs +% 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days +% 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days +% 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days +% 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days +% 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days +% 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days +% 'Total' => $today, +% ; +% my %before = ( +% 'Today' => 86400, # 60sec * 60min * 24hrs +% 'Yesterday' => 0, +% 'Past week' => 86400, # 60sec * 60min * 24hrs +% 'Past 30 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 60 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 90 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 6 months' => 86400, # 60sec * 60min * 24hrs +% 'Past year' => 86400, # 60sec * 60min * 24hrs +% 'Total' => 86400, # 60sec * 60min * 24hrs +% ); +% +% my $curuser = $FS::CurrentUser::CurrentUser; +% +% my $statement = "SELECT COUNT(*) FROM h_cust_main +% WHERE history_action = 'insert' +% AND refnum = ? +% AND history_date >= ? +% AND history_date < ? +% AND ". $curuser->agentnums_sql; +% my $sth = dbh->prepare($statement) +% or die dbh->errstr; +% +% my $show_agentnums = scalar($curuser->agentnums); +% +% + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2 ROWSPAN=2>Advertising source</TH> +% if ( $show_agentnums ) { + + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Agent</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% scalar(keys %after) %>>Customers</TH> +</TR> +% for my $period ( keys %after ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% $period %></FONT></TH> +% } + +</TR> +% +%foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% $a = 0; +% +% + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $part_referral->agentnum || $curuser->access_right('Edit global advertising sources') ) { +% $a++; +% + + <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>"> +% } + + <% $part_referral->refnum %><% $a ? '</A>' : '' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $a ) { + + <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>"> +% } + + <% $part_referral->referral %><% $a ? '</A>' : '' %></TD> +% if ( $show_agentnums ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %></TD> +% } +% for my $period ( keys %after ) { +% $sth->execute( $part_referral->refnum, +% $today-$after{$period}, +% $today+$before{$period}, +% ) or die $sth->errstr; +% my $number = $sth->fetchrow_arrayref->[0]; +% + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $number %></TD> +% } + + </TR> +% } +% +% $statement =~ s/AND refnum = \?//; +% $sth = dbh->prepare($statement) +% or die dbh->errstr; +% + + <TR> + <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD> +% for my $period ( keys %after ) { +% $sth->execute( $today-$after{$period}, +% $today+$before{$period}, +% ) or die $sth->errstr; +% my $number = $sth->fetchrow_arrayref->[0]; +% + + <TD BGCOLOR="#dddddd" ALIGN="right"><B><% $number %><B></TD> +% } + + </TR> + </TABLE> + </BODY> +</HTML> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration') + || $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources') + || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources'); +</%init> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index a725dc051..369495571 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -1,34 +1,4 @@ -<% - -my %flag = ( - 'D' => 'Default', - 'F' => 'Fixed', - '' => '', -); - -my %search; -if ( $cgi->param('showdisabled') ) { - %search = (); -} else { - %search = ( 'disabled' => '' ); -} - -my @part_svc = - sort { $a->getfield('svcpart') <=> $b->getfield('svcpart') } - qsearch('part_svc', \%search ); -my $total = scalar(@part_svc); - -my %num_active_cust_svc = map { $_->svcpart => $_->num_cust_svc } @part_svc; - -if ( $cgi->param('orderby') eq 'active' ) { - @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=> - $num_active_cust_svc{$a->svcpart} } @part_svc; -} elsif ( $cgi->param('orderby') eq 'svc' ) { - @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc; -} - -%> -<%= header('Service Definition Listing', menubar( 'Main Menu' => $p) ) %> +<% include("/elements/header.html",'Service Definition Listing', menubar( 'Main Menu' => $p) ) %> <SCRIPT> function part_export_areyousure(href) { @@ -39,99 +9,204 @@ function part_export_areyousure(href) { Service definitions are the templates for items you offer to your customers.<BR><BR> -<FORM METHOD="POST" ACTION="<%= $p %>edit/part_svc.cgi"> -<A HREF="<%= $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A><% if ( @part_svc ) { %> or <SELECT NAME="clone"><OPTION></OPTION> -<% foreach my $part_svc ( @part_svc ) { %> - <OPTION VALUE="<%= $part_svc->svcpart %>"><%= $part_svc->svc %></OPTION> -<% } %> +<FORM METHOD="POST" ACTION="<% $p %>edit/part_svc.cgi"> +<A HREF="<% $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A> +% if ( @part_svc ) { + or <SELECT NAME="clone"><OPTION></OPTION> +% foreach my $part_svc ( @part_svc ) { + + <OPTION VALUE="<% $part_svc->svcpart %>"><% $part_svc->svc %></OPTION> +% } + </SELECT><INPUT TYPE="submit" VALUE="Clone existing service"> -<% } %> +% } + </FORM><BR> -<%= $total %> service definitions -<%= $cgi->param('showdisabled') +<% $total %> service definitions +<% $cgi->param('showdisabled') ? do { $cgi->param('showdisabled', 0); '( <a href="'. $cgi->self_url. '">hide disabled services</a> )'; } : do { $cgi->param('showdisabled', 1); '( <a href="'. $cgi->self_url. '">show disabled services</a> )'; } %> -<% $cgi->param('showdisabled', ( 1 ^ $cgi->param('showdisabled') ) ); %> -<%= table() %> +% $cgi->param('showdisabled', ( 1 ^ $cgi->param('showdisabled') ) ); + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + <TR> - <TH><A HREF="<%= do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> - <% if ( $cgi->param('showdisabled') ) { %> - <TH>Status</TH> - <% } %> - <TH><A HREF="<%= do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> - <TH>Table</TH> - <TH><A HREF="<%= do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> - <TH>Export</TH> - <TH>Field</TH> - <TH COLSPAN=2>Modifier</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> + +% if ( $cgi->param('showdisabled') ) { + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Table</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Export</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Field</TH> + + <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH> + </TR> -<% foreach my $part_svc ( @part_svc ) { - my $svcdb = $part_svc->svcdb; - my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } ); - my @dfields = $svc_x->fields; - push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge - my @fields = - grep { $svc_x->pvf($_) - or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag } - @dfields ; - my $rowspan = scalar(@fields) || 1; - my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart; -%> +% foreach my $part_svc ( @part_svc ) { +% my $svcdb = $part_svc->svcdb; +% my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } ); +% my @dfields = $svc_x->fields; +% push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge +% my @fields = +% grep { $svc_x->pvf($_) +% or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag } +% @dfields ; +% my $rowspan = scalar(@fields) || 1; +% my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + <TR> - <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>"> - <%= $part_svc->svcpart %></A></TD> -<% if ( $cgi->param('showdisabled') ) { %> - <TD ROWSPAN=<%= $rowspan %>> - <%= $part_svc->disabled + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $url %>"><% $part_svc->svcpart %></A> + </TD> + +% if ( $cgi->param('showdisabled') ) { + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_svc->disabled ? '<FONT COLOR="#FF0000"><B>Disabled</B></FONT>' : '<FONT COLOR="#00CC00"><B>Enabled</B></FONT>' %> </TD> -<% } %> - <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>"> - <%= $part_svc->svc %></A></TD> - <TD ROWSPAN=<%= $rowspan %>> - <%= $svcdb %></TD> - <TD ROWSPAN=<%= $rowspan %>> - <FONT COLOR="#00CC00"><B><%= $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <A HREF="<%=$p%>search/<%= $svcdb %>.cgi?svcpart=<%= $part_svc->svcpart %>">active</A> - <% if ( $num_active_cust_svc{$part_svc->svcpart} ) { %> - <BR><FONT SIZE="-1">[ <A HREF="<%=$p%>edit/bulk-cust_svc.html?svcpart=<%= $part_svc->svcpart %>">change</A> ]</FONT> - <% } %> +% } + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_svc->svc %></A></TD> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $svcdb %></TD> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <% $num_active_cust_svc{$part_svc->svcpart} ? FS::UI::Web::svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart ) : '<A NAME="zero">' %>active</A> + +% if ( $num_active_cust_svc{$part_svc->svcpart} ) { + <BR><FONT SIZE="-1">[ <A HREF="<%$p%>edit/bulk-cust_svc.html?svcpart=<% $part_svc->svcpart %>">change</A> ]</FONT> +% } + </TD> - <TD ROWSPAN=<%= $rowspan %>><%= itable() %> -<% -# my @part_export = -map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ; - foreach my $part_export ( - map { qsearchs('part_export', { exportnum => $_->exportnum } ) } - qsearch('export_svc', { svcpart => $part_svc->svcpart } ) - ) { -%> - <TR> - <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exportnum %>: <%= $part_export->exporttype %> to <%= $part_export->machine %></A></TD></TR> -<% } %> - </TABLE></TD> - -<% my($n1)=''; - foreach my $field ( @fields ) { - my $flag = $part_svc->part_svc_column($field)->columnflag; -%> - <%= $n1 %> - <TD><%= $field %></TD> - <TD><%= $flag{$flag} %></TD> - <TD><%= $part_svc->part_svc_column($field)->columnvalue%></TD> -<% $n1="</TR><TR>"; - } -%> + <TD ROWSPAN=<% $rowspan %> CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv"> +% +%# my @part_export = +%map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ; +% foreach my $part_export ( +% map { qsearchs('part_export', { exportnum => $_->exportnum } ) } +% qsearch('export_svc', { svcpart => $part_svc->svcpart } ) +% ) { +% + + <TR> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %>: <% $part_export->exporttype %> to <% $part_export->machine %></A></TD> + </TR> +% } + + </TABLE> + </TD> + +% unless ( @fields ) { +% for ( 1..3 ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD> +% } +% } +% +% my($n1)=''; +% foreach my $field ( @fields ) { +% my $flag = $part_svc->part_svc_column($field)->columnflag; +% + + <% $n1 %> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% my $value = $part_svc->part_svc_column($field)->columnvalue; +% if ( $flag =~ /^[MA]$/ ) { +% $inventory_class{$value} +% ||= qsearchs('inventory_class', { 'classnum' => $value } ); +% + + <% $inventory_class{$value} + ? $inventory_class{$value}->classname + : "WARNING: inventory_class.classnum $value not found" %> +% } else { + + <% $value %> +% } + + </TD> +% $n1="</TR><TR>"; +% } +% + </TR> -<% } %> +% } + </TABLE> </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +#code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm +my %flag = ( + '' => '', + 'D' => 'Default', + 'F' => 'Fixed (unchangeable)', + 'S' => 'Selectable choice', + #'M' => 'Manual selection from inventory', + 'M' => 'Manual selected from inventory', + #'A' => 'Automatically fill in from inventory', + 'A' => 'Automatically filled in from inventory', + 'X' => 'Excluded', +); + +my %search; +if ( $cgi->param('showdisabled') ) { + %search = (); +} else { + %search = ( 'disabled' => '' ); +} + +my @part_svc = + sort { $a->getfield('svcpart') <=> $b->getfield('svcpart') } + qsearch('part_svc', \%search ); +my $total = scalar(@part_svc); + +my %num_active_cust_svc = map { $_->svcpart => $_->num_cust_svc } @part_svc; + +if ( $cgi->param('orderby') eq 'active' ) { + @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=> + $num_active_cust_svc{$a->svcpart} } @part_svc; +} elsif ( $cgi->param('orderby') eq 'svc' ) { + @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc; +} + +my %inventory_class = (); + +</%init> diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi index a0009dabd..2e12603a0 100644 --- a/httemplate/browse/part_virtual_field.cgi +++ b/httemplate/browse/part_virtual_field.cgi @@ -1,39 +1,46 @@ -<%= header('Virtual field definitions', menubar('Main Menu' => $p)) %> -<% +<% include("/elements/header.html",'Virtual field definitions', menubar('Main Menu' => $p)) %> +% +% +%my %pvfs; +%my $block; +%my $p2 = popurl(2); +%my $dbtable; +% +%foreach (qsearch('part_virtual_field', {})) { +% push @{ $pvfs{$_->dbtable} }, $_; +%} +% +% if ($cgi->param('error')) { -my %pvfs; -my $block; -my $p2 = popurl(2); -my $dbtable; - -foreach (qsearch('part_virtual_field', {})) { - push @{ $pvfs{$_->dbtable} }, $_; -} -%> - -<% if ($cgi->param('error')) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> <BR><BR> -<% } %> +% } -<A HREF="<%=$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR> -<% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { %> -<H3><%=$dbtable%></H3> +<A HREF="<%$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR> +% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { -<%=table()%> +<H3><%$dbtable%></H3> + +<%table()%> <TH><TD>Field name</TD><TD>Description</TD></TH> -<% foreach my $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { %> +% foreach my $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { + <TR> <TD></TD> <TD> - <A HREF="<%=$p2%>edit/part_virtual_field.cgi?<%=$pvf->vfieldpart%>"> - <%=$pvf->name%></A></TD> - <TD><%=$pvf->label%></TD> + <A HREF="<%$p2%>edit/part_virtual_field.cgi?<%$pvf->vfieldpart%>"> + <%$pvf->name%></A></TD> + <TD><%$pvf->label%></TD> </TR> -<% } %> +% } + </TABLE> -<% } %> +% } + </BODY> </HTML> - +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html index bb7f31514..720858e9b 100644 --- a/httemplate/browse/payment_gateway.html +++ b/httemplate/browse/payment_gateway.html @@ -1,70 +1,79 @@ -<% - - my %search; - if ( $cgi->param('showdisabled') ) { - %search = (); - } else { - %search = ( 'disabled' => '' ); - } - -%> -<%= header('Payment gateways', menubar( +<% include("/elements/header.html",'Payment gateways', menubar( 'Main Menu' => $p, 'Agents' => $p. 'browse/agent.cgi', )) %> -<A HREF="<%= $p %>edit/payment_gateway.html"><I>Add a new payment gateway</I></A><BR><BR> +<A HREF="<% $p %>edit/payment_gateway.html"><I>Add a new payment gateway</I></A><BR><BR> -<%= $cgi->param('showdisabled') +<% $cgi->param('showdisabled') ? do { $cgi->param('showdisabled', 0); '( <a href="'. $cgi->self_url. '">hide disabled gateways</a> )'; } : do { $cgi->param('showdisabled', 1); '( <a href="'. $cgi->self_url. '">show disabled gateways</a> )'; } %> -<%= table() %> +<% table() %> <TR> - <TH COLSPAN=<%= $cgi->param('showdisabled') ? 1 : 2 %>>#</TH> + <TH COLSPAN=<% $cgi->param('showdisabled') ? 1 : 2 %>>#</TH> <TH>Gateway</TH> <TH>Username</TH> <TH>Password</TH> <TH>Action</TH> <TH>Options</TH> </TR> +% foreach my $payment_gateway ( qsearch( 'payment_gateway', \%search ) ) { -<% foreach my $payment_gateway ( qsearch( 'payment_gateway', \%search ) ) { %> <TR> - <TD><%= $payment_gateway->gatewaynum %></TD> - <% if ( !$cgi->param('showdisabled') ) { %> - <TD><%= $payment_gateway->disabled ? 'DISABLED' : '' %></TD> - <% } %> - <TD><%= $payment_gateway->gateway_module %> - <%= !$payment_gateway->disabled - ? '<FONT SIZE="-1"> <A HREF="misc/disable-payment_gateway.cgi?'. $payment_gateway->gatewaynum.'">(disable)</A></FONT>' - : '' - %> + <TD><% $payment_gateway->gatewaynum %></TD> +% if ( !$cgi->param('showdisabled') ) { + + <TD><% $payment_gateway->disabled ? 'DISABLED' : '' %></TD> +% } + + <TD><% $payment_gateway->gateway_module %> + <FONT SIZE="-1"> + <A HREF="<%$p%>edit/payment_gateway.html?<% $payment_gateway->gatewaynum %>">(edit)</A> + <% !$payment_gateway->disabled + ? '<A HREF="'. $p. 'misc/disable-payment_gateway.cgi?'. $payment_gateway->gatewaynum.'">(disable)</A>' + : '' + %> + </FONT> </TD> - <TD><%= $payment_gateway->gateway_username %></TD> + <TD><% $payment_gateway->gateway_username %></TD> <TD> - </TD> - <TD><%= $payment_gateway->gateway_action %></TD> + <TD><% $payment_gateway->gateway_action %></TD> <TD> <TABLE CELLSPACING=0 CELLPADDING=0> - <% my %options = $payment_gateway->options; - foreach my $option ( keys %options ) { - %> +% my %options = $payment_gateway->options; +% foreach my $option ( keys %options ) { +% + <TR> - <TH><%= $option %>:</TH> - <TD><%= $options{$option} %></TD> + <TH><% $option %>:</TH> + <TD><% $options{$option} %></TD> </TR> - <% } %> +% } + </TABLE> </TD> </TR> +% } -<% } %> </TABLE> </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %search; +if ( $cgi->param('showdisabled') ) { + %search = (); +} else { + %search = ( 'disabled' => '' ); +} +</%init> diff --git a/httemplate/browse/pkg_class.html b/httemplate/browse/pkg_class.html new file mode 100644 index 000000000..886029df5 --- /dev/null +++ b/httemplate/browse/pkg_class.html @@ -0,0 +1,30 @@ +<% include( 'elements/browse.html', + 'title' => 'Package classes', + 'html_init' => $html_init, + 'name' => 'package classes', + 'disableable' => 1, + 'query' => { 'table' => 'pkg_class', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY classnum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Class', ], + 'fields' => [ 'classnum', 'classname' ], + 'links' => [ $link, $link ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + 'Package classes define groups of packages, for reporting and '. + 'convenience purposes.<BR><BR>'. + qq!<A HREF="${p}edit/pkg_class.html"><I>Add a package class</I></A><BR><BR>!; + +my $count_query = 'SELECT COUNT(*) FROM pkg_class'; + +my $link = [ $p.'edit/pkg_class.html?', 'classnum' ]; + +</%init> diff --git a/httemplate/browse/queue.cgi b/httemplate/browse/queue.cgi deleted file mode 100755 index 0afdd48d7..000000000 --- a/httemplate/browse/queue.cgi +++ /dev/null @@ -1,5 +0,0 @@ -<!-- mason kludge --> -<%= header("Job Queue", menubar( 'Main Menu' => $p, )) %> -<%= joblisting({}) %> -</BODY> -</HTML> diff --git a/httemplate/browse/rate.cgi b/httemplate/browse/rate.cgi index c31260166..584891aea 100644 --- a/httemplate/browse/rate.cgi +++ b/httemplate/browse/rate.cgi @@ -1,34 +1,37 @@ -<!-- mason kludge --> -<%= header("Rate plan listing", menubar( 'Main Menu' => "$p#sysadmin" )) %> -Rate plans, regions and prefixes for VoIP and call billing.<BR><BR> -<A HREF="<%=$p%>edit/rate.cgi"><I>Add a rate plan</I></A> -| <A HREF="<%=$p%>edit/rate_region.cgi"><I>Add a region</I></A> -<BR><BR> -<SCRIPT> -function rate_areyousure(href) { - if (confirm("Are you sure you want to delete this rate plan?") == true) - window.location.href = href; -} -</SCRIPT> +<% include( 'elements/browse.html', + 'title' => 'Rate plans', + 'menubar' => [ 'Main menu' => $p, ], + 'html_init' => $html_init, + 'name' => 'rate plans', + 'query' => { 'table' => 'rate', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY ratenum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Rate plan', ], + 'fields' => [ 'ratenum', 'ratename' ], + 'links' => [ $link, $link ], + ) +%> +<%init> -<%= table() %> - <TR> - <TH COLSPAN=2>Rate plan</TH> - </TR> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); -<% foreach my $rate ( sort { - $a->getfield('ratenum') <=> $b->getfield('ratenum') - } qsearch('rate',{}) ) { -%> - <TR> - <TD><A HREF="<%= $p %>edit/rate.cgi?<%= $rate->ratenum %>"><%= $rate->ratenum %></A></TD> - <TD><A HREF="<%= $p %>edit/rate.cgi?<%= $rate->ratenum %>"><%= $rate->ratename %></A></TD> - </TR> +my $html_init = +'Rate plans, regions and prefixes for VoIP and call billing.<BR><BR>'. +qq!<A HREF="${p}edit/rate.cgi"><I>Add a rate plan</I></A>!. +qq! | <A HREF="${p}edit/rate_region.cgi"><I>Add a region</I></A>!. +'<BR><BR> + <SCRIPT> + function rate_areyousure(href) { + if (confirm("Are you sure you want to delete this rate plan?") == true) + window.location.href = href; + } + </SCRIPT>'; -<% } %> +my $count_query = 'SELECT COUNT(*) FROM rate'; -</TABLE> -</BODY> -</HTML> +my $link = [ $p.'edit/rate.cgi?', 'ratenum' ]; - +</%init> diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html new file mode 100644 index 000000000..b017f8f58 --- /dev/null +++ b/httemplate/browse/reason.html @@ -0,0 +1,68 @@ +<% include( 'elements/browse.html', + 'title' => ucfirst($classname) . ' Reasons', + 'menubar' => [ # 'Main menu' => $p, + ucfirst($classname) . ' Reason Types' => + $p.'browse/reason_type.html?class='. + $class, + ], + 'html_init' => $html_init, + 'name' => $classname . ' reasons', + 'query' => { 'table' => 'reason', + 'hashref' => {}, + 'extra_sql' => $where_clause . + 'ORDER BY reason_type', + 'addl_from' => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type', + }, + 'count_query' => $count_query, + 'header' => [ '#', + ucfirst($classname) . ' Reason Type', + ucfirst($classname) . ' Reason', + ], + 'fields' => [ 'reasonnum', + sub { shift->reasontype->type }, + 'reason', + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +$cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +my $class = $1; + +my %classmap = ( 'C' => 'cancel', + 'S' => 'suspend', + ); + +my $classname = $classmap{$class}; + +my $html_init = ucfirst($classname) . +" reasons explain why we $classname a package.<BR><BR>". +qq!<A HREF="${p}edit/reason.html?class=$class">!. +"<I>Add a $classname reason</I></A><BR><BR>"; + +my $where_clause = "WHERE class='$class'"; +$where_clause .= " AND (disabled = '' OR disabled IS NULL)" +unless $cgi->param('showdisabled'); + +my $disabledurl = $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled reasons</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled reasons</a> )'; } + ; + +$html_init .= $disabledurl; + +my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' . + 'reason_type.typenum = reason.reason_type ' . $where_clause; + +my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ]; + +</%init> diff --git a/httemplate/browse/reason_type.html b/httemplate/browse/reason_type.html new file mode 100644 index 000000000..09f451c9f --- /dev/null +++ b/httemplate/browse/reason_type.html @@ -0,0 +1,72 @@ +<% include( 'elements/browse.html', + 'title' => ucfirst($classname) . " Reason Types", + 'menubar' => [ ucfirst($classname) . " reasons" => + $p.'browse/reason.html?class=' . $class, + ], + 'html_init' => $html_init, + 'name' => $classname . " reason types", + 'query' => { 'table' => 'reason_type', + 'hashref' => {}, + 'extra_sql' => $where_clause . + 'ORDER BY typenum', + }, + 'count_query' => $count_query, + 'header' => [ '#', + ucfirst($classname) . ' Reason Type', + ucfirst($classname) . ' Reasons', + ], + 'fields' => [ 'typenum', + 'type', + $reasons_sub, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +$cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +my $class=$1; + +my %classmap = ( 'C' => 'cancel', + 'S' => 'suspend', + ); + +my $classname = $classmap{$class}; + +my $html_init = ucfirst($classname) . + " reason types allow groups of $classname reasons for reporting purposes." . + qq!<BR><BR><A HREF="${p}edit/reason_type.html?class=$class"><I>Add a ! . + $classname . " reason type</I></A><BR><BR>"; + +my $reasons_sub = sub { + my $reason_type = shift; + + [ map { + [ + { + 'data' => $_->reason, + 'align' => 'left', + 'link' => $p. "edit/reason.html?class=$class&reasonnum=". + $_->reasonnum, + }, + ]; + } + $reason_type->enabled_reasons, + + ]; + +}; + +my $where_clause = "WHERE class='$class'"; +my $count_query = 'SELECT COUNT(*) FROM reason_type '; +$count_query .= $where_clause; + +my $link = [ $p.'edit/reason_type.html?class='.$class.'&typenum=', 'typenum' ]; + +</%init> diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi index 149db4903..6dcd93a71 100644 --- a/httemplate/browse/router.cgi +++ b/httemplate/browse/router.cgi @@ -1,57 +1,64 @@ -<%= header('Routers', menubar('Main Menu' => $p)) %> -<% +<% include("/elements/header.html",'Routers', menubar('Main Menu' => $p)) %> +% +% +%my @router = qsearch('router', {}); +%my $p2 = popurl(2); +% +% +% if ($cgi->param('error')) { -my @router = qsearch('router', {}); -my $p2 = popurl(2); + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> + <BR><BR> +% } +% +%my $hidecustomerrouters = 0; +%my $hideurl = ''; +%if ($cgi->param('hidecustomerrouters') eq '1') { +% $hidecustomerrouters = 1; +% $cgi->param('hidecustomerrouters', 0); +% $hideurl = '<A HREF="' . $cgi->self_url() . '">Show customer routers</A>'; +%} else { +% $hidecustomerrouters = 0; +% $cgi->param('hidecustomerrouters', 1); +% $hideurl = '<A HREF="' . $cgi->self_url() . '">Hide customer routers</A>'; +%} +% -%> -<% if ($cgi->param('error')) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> - <BR><BR> -<% } %> - -<% -my $hidecustomerrouters = 0; -my $hideurl = ''; -if ($cgi->param('hidecustomerrouters') eq '1') { - $hidecustomerrouters = 1; - $cgi->param('hidecustomerrouters', 0); - $hideurl = '<A HREF="' . $cgi->self_url() . '">Show customer routers</A>'; -} else { - $hidecustomerrouters = 0; - $cgi->param('hidecustomerrouters', 1); - $hideurl = '<A HREF="' . $cgi->self_url() . '">Hide customer routers</A>'; -} -%> - -<A HREF="<%=$p2%>edit/router.cgi">Add a new router</A> | <%=$hideurl%> - -<%=table()%> +<A HREF="<%$p2%>edit/router.cgi">Add a new router</A> | <%$hideurl%> + +<%table()%> <TR> <TD><B>Router name</B></TD> <TD><B>Address block(s)</B></TD> </TR> -<% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) { - next if $hidecustomerrouters && $router->svcnum; - my @addr_block = $router->addr_block; - if (scalar(@addr_block) == 0) { - push @addr_block, ' '; - } -%> +% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) { +% next if $hidecustomerrouters && $router->svcnum; +% my @addr_block = $router->addr_block; +% if (scalar(@addr_block) == 0) { +% push @addr_block, ' '; +% } +% + <TR> - <TD ROWSPAN="<%=scalar(@addr_block)+1%>"> - <A HREF="<%=$p2%>edit/router.cgi?<%=$router->routernum%>"><%=$router->routername%></A> + <TD ROWSPAN="<%scalar(@addr_block)+1%>"> + <A HREF="<%$p2%>edit/router.cgi?<%$router->routernum%>"><%$router->routername%></A> </TD> </TR> - <% foreach my $block ( @addr_block ) { %> +% foreach my $block ( @addr_block ) { + <TR> - <TD><%=UNIVERSAL::isa($block, 'FS::addr_block') ? $block->NetAddr : ' '%></TD> + <TD><%UNIVERSAL::isa($block, 'FS::addr_block') ? $block->NetAddr : ' '%></TD> </TR> - <% } %> +% } + </TR> -<% } %> +% } + </TABLE> </BODY> </HTML> - +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi index 44cda81ad..306d02afb 100755 --- a/httemplate/browse/svc_acct_pop.cgi +++ b/httemplate/browse/svc_acct_pop.cgi @@ -1,13 +1,7 @@ -<!-- mason kludge --> -<% - my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct - WHERE popnum = ? ") - or die dbh->errstr; -%> -<%= header('Access Number Listing', menubar( 'Main Menu' => $p )) %> +<% include("/elements/header.html",'Access Number Listing', menubar( 'Main Menu' => $p )) %> Points of Presence<BR><BR> -<A HREF="<%= $p %>edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A><BR><BR> -<%= table() %> +<A HREF="<% $p %>edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A><BR><BR> +<% table() %> <TR> <TH></TH> <TH>City</TH> @@ -17,47 +11,63 @@ Points of Presence<BR><BR> <TH>Local</TH> <TH>Accounts</TH> </TR> +% +%foreach my $svc_acct_pop ( sort { +% #$a->getfield('popnum') <=> $b->getfield('popnum') +% $a->state cmp $b->state || $a->city cmp $b->city +% || $a->ac <=> $b->ac || $a->exch <=> $b->exch || $a->loc <=> $b->loc +%} qsearch('svc_acct_pop',{}) ) { +% +% my $svc_acct_pop_link = $p . 'edit/svc_acct_pop.cgi?'. $svc_acct_pop->popnum; +% +% $accounts_sth->execute($svc_acct_pop->popnum) or die $accounts_sth->errstr; +% my $num_accounts = $accounts_sth->fetchrow_arrayref->[0]; +% +% my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='. $svc_acct_pop->popnum; +% +% -<% -foreach my $svc_acct_pop ( sort { - #$a->getfield('popnum') <=> $b->getfield('popnum') - $a->state cmp $b->state || $a->city cmp $b->city - || $a->ac <=> $b->ac || $a->exch <=> $b->exch || $a->loc <=> $b->loc -} qsearch('svc_acct_pop',{}) ) { - - my $svc_acct_pop_link = $p . 'edit/svc_acct_pop.cgi?'. $svc_acct_pop->popnum; - - $accounts_sth->execute($svc_acct_pop->popnum) or die $accounts_sth->errstr; - my $num_accounts = $accounts_sth->fetchrow_arrayref->[0]; - - my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='. $svc_acct_pop->popnum; - -%> <TR> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->popnum %></A></TD> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->city %></A></TD> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->state %></A></TD> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->ac %></A></TD> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->exch %></A></TD> - <TD><A HREF="<%= $svc_acct_pop_link %>"> - <%= $svc_acct_pop->loc %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->popnum %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->city %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->state %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->ac %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->exch %></A></TD> + <TD><A HREF="<% $svc_acct_pop_link %>"> + <% $svc_acct_pop->loc %></A></TD> <TD> - <FONT COLOR="#00CC00"><B><%= $num_accounts %></B></FONT> - <% if ( $num_accounts ) { %><A HREF="<%= $svc_acct_link %>"><% } %> + <FONT COLOR="#00CC00"><B><% $num_accounts %></B></FONT> +% if ( $num_accounts ) { +<A HREF="<% $svc_acct_link %>"> +% } + active - <% if ( $num_accounts ) { %></A><% } %> +% if ( $num_accounts ) { +</A> +% } + </TD> </TR> -<% } %> +% } + <TR> </TR> </TABLE> </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct + WHERE popnum = ? ") + or die dbh->errstr; +</%init> diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi index 259713260..d8f0d8e93 100644 --- a/httemplate/config/config-process.cgi +++ b/httemplate/config/config-process.cgi @@ -1,51 +1,62 @@ -<% - my $conf = new FS::Conf; - $FS::Conf::DEBUG = 1; - my @config_items = $conf->config_items; +<%init> - foreach my $i ( @config_items ) { - my @touch = (); - my @delete = (); - my $n = 0; - foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { - if ( $type eq '' ) { - } elsif ( $type eq 'textarea' ) { - if ( $cgi->param($i->key. $n) ne '' ) { - my $value = $cgi->param($i->key. $n); - $value =~ s/\r\n/\n/g; #browsers? - $conf->set($i->key, $value); - } else { - $conf->delete($i->key); - } - } elsif ( $type eq 'checkbox' ) { +die "access denied\n" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +# errant GET/POST protection +my $Vars = scalar($cgi->Vars); +my $num_Vars = scalar(keys %$Vars); +die "only received $num_Vars params; errant or truncated GET/POST?". + " aborting - not updating config\n" + unless $num_Vars > 100; + +my $conf = new FS::Conf; +$FS::Conf::DEBUG = 1; +my @config_items = $conf->config_items; + +foreach my $i ( @config_items ) { + my @touch = (); + my @delete = (); + my $n = 0; + foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { + if ( $type eq '' ) { + } elsif ( $type eq 'textarea' ) { + if ( $cgi->param($i->key. $n) ne '' ) { + my $value = $cgi->param($i->key. $n); + $value =~ s/\r\n/\n/g; #browsers? + $conf->set($i->key, $value); + } else { + $conf->delete($i->key); + } + } elsif ( $type eq 'checkbox' ) { # if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) { - if ( defined $cgi->param($i->key. $n) ) { - #$conf->touch($i->key); - push @touch, $i->key; - } else { - #$conf->delete($i->key); - push @delete, $i->key; - } - } elsif ( $type eq 'text' || $type eq 'select' ) { - if ( $cgi->param($i->key. $n) ne '' ) { - $conf->set($i->key, $cgi->param($i->key. $n)); - } else { - $conf->delete($i->key); - } - } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' ) { - if ( scalar(@{[ $cgi->param($i->key. $n) ]}) ) { - $conf->set($i->key, join("\n", @{[ $cgi->param($i->key. $n) ]} )); - } else { - $conf->delete($i->key); - } + if ( defined $cgi->param($i->key. $n) ) { + #$conf->touch($i->key); + push @touch, $i->key; + } else { + #$conf->delete($i->key); + push @delete, $i->key; + } + } elsif ( $type eq 'text' || $type eq 'select' || $type eq 'select-sub' ) { + if ( $cgi->param($i->key. $n) ne '' ) { + $conf->set($i->key, $cgi->param($i->key. $n)); + } else { + $conf->delete($i->key); + } + } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' ) { + if ( scalar(@{[ $cgi->param($i->key. $n) ]}) ) { + $conf->set($i->key, join("\n", @{[ $cgi->param($i->key. $n) ]} )); } else { + $conf->delete($i->key); } - $n++; + } else { } - # warn @touch; - $conf->touch($_) foreach @touch; - $conf->delete($_) foreach @delete; + $n++; } + # warn @touch; + $conf->touch($_) foreach @touch; + $conf->delete($_) foreach @delete; +} -%> -<%= $cgi->redirect("config-view.cgi") %> +</%init> +<% $cgi->redirect("config-view.cgi") %> diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index 8011e7697..91ba33769 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -1,80 +1,95 @@ -<!-- mason kludge --> -<%= header('View Configuration', menubar( 'Main Menu' => $p, +<% include("/elements/header.html",'View Configuration', menubar( 'Main Menu' => $p, 'Edit Configuration' => 'config.cgi' ) ) %> +% my $conf = new FS::Conf; my @config_items = $conf->config_items; +% foreach my $section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { -<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %> - -<% foreach my $section ( qw(required billing username password UI session - shell BIND - ), - '', 'deprecated') { %> - <A NAME="<%= $section || 'unclassified' %>"></A> + <A NAME="<% $section || 'unclassified' %>"></A> <FONT SIZE="-2"> - <% foreach my $nav_section ( qw(required billing username password UI session - shell BIND - ), - '', 'deprecated') { %> - <% if ( $section eq $nav_section ) { %> - [<A NAME="not<%= $nav_section || 'unclassified' %>" style="background-color: #cccccc"><%= ucfirst($nav_section || 'unclassified') %></A>] - <% } else { %> - [<A HREF="#<%= $nav_section || 'unclassified' %>"><%= ucfirst($nav_section || 'unclassified') %></A>] - <% } %> - <% } %> +% foreach my $nav_section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { +% if ( $section eq $nav_section ) { + + [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>] +% } else { + + [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>] +% } +% } + </FONT><BR> - <%= table("#cccccc", 2) %> + <% table("#cccccc", 2) %> <tr> <th colspan="2" bgcolor="#dcdcdc"> - <%= ucfirst($section || 'unclassified') %> configuration options + <% ucfirst($section || 'unclassified') %> configuration options </th> </tr> - <% foreach my $i (grep $_->section eq $section, @config_items) { %> +% foreach my $i (grep $_->section eq $section, @config_items) { + <tr> - <td><a name="<%= $i->key %>"> - <b><%= $i->key %></b> - <%= $i->description %> + <td><a name="<% $i->key %>"> + <b><% $i->key %></b> - <% $i->description %> </a></td> <td><table border=0> - <% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { - my $n = 0; %> - <% if ( $type eq '' ) { %> +% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +% my $n = 0; +% if ( $type eq '' ) { + <tr> <td><font color="#ff0000">no type</font></td> </tr> - <% } elsif ( $type eq 'textarea' - || $type eq 'editlist' - || $type eq 'selectmultiple' ) { %> +% } elsif ( $type eq 'textarea' +% || $type eq 'editlist' +% || $type eq 'selectmultiple' ) { + <tr> <td bgcolor="#ffffff"> <pre> -<%= encode_entities(join("\n", $conf->config($i->key) ) ) %> +<% encode_entities(join("\n", $conf->config($i->key) ) ) %> </pre> </td> </tr> - <% } elsif ( $type eq 'checkbox' ) { %> +% } elsif ( $type eq 'checkbox' ) { + <tr> - <td bgcolor="#<%= $conf->exists($i->key) ? '00ff00">YES' : 'ff0000">NO' %></td> + <td bgcolor="#<% $conf->exists($i->key) ? '00ff00">YES' : 'ff0000">NO' %></td> </tr> - <% } elsif ( $type eq 'text' || $type eq 'select' ) { %> +% } elsif ( $type eq 'text' || $type eq 'select' ) { + <tr> <td bgcolor="#ffffff"> - <%= $conf->exists($i->key) ? $conf->config($i->key) : '' %> + <% $conf->exists($i->key) ? $conf->config($i->key) : '' %> </td></tr> - <% } elsif ( $type eq 'select-sub' ) { %> +% } elsif ( $type eq 'select-sub' ) { + <tr> <td bgcolor="#ffffff"> - <%= $conf->config($i->key) %>: - <%= &{ $i->option_sub }( $conf->config($i->key) ) %> + <% $conf->config($i->key) %>: + <% &{ $i->option_sub }( $conf->config($i->key) ) %> </td> </tr> - <% } else { %> +% } else { + <tr><td> - <font color="#ff0000">unknown type <%= $type %></font> + <font color="#ff0000">unknown type <% $type %></font> </td></tr> - <% } %> - <% $n++; } %> +% } +% $n++; } + </table></td> </tr> - <% } %> +% } + </table><br><br> -<% } %> +% } + </body></html> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index ff29d8578..6c3a51aca 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -1,5 +1,4 @@ -<!-- mason kludge --> -<%= header('Edit Configuration', menubar( 'Main Menu' => $p ) ) %> +<% include("/elements/header.html",'Edit Configuration', menubar( 'Main Menu' => $p ) ) %> <SCRIPT> var gSafeOnload = new Array(); var gSafeOnsubmit = new Array(); @@ -19,173 +18,246 @@ function SafeOnsubmit() { gSafeOnsubmit[i](); } </SCRIPT> +% my $conf = new FS::Conf; my @config_items = $conf->config_items; -<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %> <form name="OneTrueForm" action="config-process.cgi" METHOD="POST" onSubmit="SafeOnsubmit()"> +% foreach my $section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { -<% foreach my $section ( qw(required billing username password UI session - shell BIND - ), - '', 'deprecated') { %> - <A NAME="<%= $section || 'unclassified' %>"></A> + <A NAME="<% $section || 'unclassified' %>"></A> <FONT SIZE="-2"> - <% foreach my $nav_section ( qw(required billing username password UI session - shell BIND - ), - '', 'deprecated') { %> - <% if ( $section eq $nav_section ) { %> - [<A NAME="not<%= $nav_section || 'unclassified' %>" style="background-color: #cccccc"><%= ucfirst($nav_section || 'unclassified') %></A>] - <% } else { %> - [<A HREF="#<%= $nav_section || 'unclassified' %>"><%= ucfirst($nav_section || 'unclassified') %></A>] - <% } %> - <% } %> +% foreach my $nav_section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { +% if ( $section eq $nav_section ) { + + [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>] +% } else { + + [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>] +% } +% } + </FONT><BR> - <%= table("#cccccc", 2) %> + <% table("#cccccc", 2) %> <tr> <th colspan="2" bgcolor="#dcdcdc"> - <%= ucfirst($section || 'unclassified') %> configuration options + <% ucfirst($section || 'unclassified') %> configuration options </th> </tr> - <% foreach my $i (grep $_->section eq $section, @config_items) { %> +% foreach my $i (grep $_->section eq $section, @config_items) { + <tr> <td> - <% my $n = 0; - foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { - #warn $i->key unless defined($type); - %> - <% if ( $type eq '' ) { %> - <font color="#ff0000">no type</font> - <% } elsif ( $type eq 'textarea' ) { %> - <textarea name="<%= $i->key. $n %>" rows=5><%= "\n". join("\n", $conf->config($i->key) ) %></textarea> - <% } elsif ( $type eq 'checkbox' ) { %> - <input name="<%= $i->key. $n %>" type="checkbox" value="1"<%= $conf->exists($i->key) ? ' CHECKED' : '' %>> - <% } elsif ( $type eq 'text' ) { %> - <input name="<%= $i->key. $n %>" type="<%= $type %>" value="<%= $conf->exists($i->key) ? $conf->config($i->key) : '' %>"> - <% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { %> - <select name="<%= $i->key. $n %>" <%= $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>> - <% my %saw; - foreach my $value ( "", @{$i->select_enum} ) { - local($^W)=0; next if $saw{$value}++; %> - <option value="<%= $value %>"<%= $value eq $conf->config($i->key) || ( $type eq 'selectmultiple' && grep { $_ eq $value } $conf->config($i->key) ) ? ' SELECTED' : '' %>><%= $value %> - <% } %> - <% if ( $conf->exists($i->key) && $conf->config($i->key) && ! grep { $conf->config($i->key) eq $_ } @{$i->select_enum}) { %> - <option value=<%= $conf->config($i->key) %> SELECTED><%= $conf->config($i->key) %> - <% } %> +% my $n = 0; +% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +% #warn $i->key unless defined($type); +% +% if ( $type eq '' ) { + + + <font color="#ff0000">no type</font> +% } elsif ( $type eq 'textarea' ) { + + + <textarea name="<% $i->key. $n %>" rows=5><% "\n". join("\n", $conf->config($i->key) ) %></textarea> +% } elsif ( $type eq 'checkbox' ) { + + + <input name="<% $i->key. $n %>" type="checkbox" value="1"<% $conf->exists($i->key) ? ' CHECKED' : '' %>> +% } elsif ( $type eq 'text' ) { + + + <input name="<% $i->key. $n %>" type="<% $type %>" value="<% $conf->exists($i->key) ? $conf->config($i->key) : '' %>"> +% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { + + + <select name="<% $i->key. $n %>" <% $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>> +% +% my %hash = (); +% if ( $i->select_enum ) { +% tie %hash, 'Tie::IxHash', +% '' => '', map { $_ => $_ } @{ $i->select_enum }; +% } elsif ( $i->select_hash ) { +% if ( ref($i->select_hash) eq 'ARRAY' ) { +% tie %hash, 'Tie::IxHash', +% '' => '', @{ $i->select_hash }; +% } else { +% tie %hash, 'Tie::IxHash', +% '' => '', %{ $i->select_hash }; +% } +% } else { +% %hash = ( '' => 'WARNING: neither select_enum nor select_hash specified in Conf.pm for configuration option "'. $i->key. '"' ); +% } +% +% my %saw = (); +% foreach my $value ( keys %hash ) { +% local($^W)=0; next if $saw{$value}++; +% my $label = $hash{$value}; +% + + + <option value="<% $value %>"<% $value eq $conf->config($i->key) || ( $type eq 'selectmultiple' && grep { $_ eq $value } $conf->config($i->key) ) ? ' SELECTED' : '' %>><% $label %> +% } +% my $curvalue = $conf->config($i->key); +% if ( $conf->exists($i->key) && $curvalue +% && ! $hash{$curvalue} +% ) { +% + + + <option value="<% $conf->config($i->key) %>" SELECTED><% exists( $hash{ $conf->config($i->key) } ) ? $hash{ $conf->config($i->key) } : $conf->config($i->key) %> +% } + + </select> - <% } elsif ( $type eq 'select-sub' ) { %> - <select name="<%= $i->key. $n %>"> +% } elsif ( $type eq 'select-sub' ) { + + + <select name="<% $i->key. $n %>"> <option value=""> - <% my %options = &{$i->options_sub}; - my @options = sort { $a <=> $b } keys %options; - my %saw; - foreach my $value ( @options ) { - local($^W)=0; next if $saw{$value}++; - %> - <option value="<%= $value %>"<%= $value eq $conf->config($i->key) ? ' SELECTED' : '' %>><%= $value %>: <%= $options{$value} %> - <% } %> - <% if ( $conf->exists($i->key) && $conf->config($i->key) && ! exists $options{$conf->config($i->key)} ) { %> - <option value=<%= $conf->config($i->key) %> SELECTED><%= $conf->config($i->key) %>: <%= &{ $i->option_sub }( $conf->config($i->key) ) %> - <% } %> +% my %options = &{$i->options_sub}; +% my @options = sort { $a <=> $b } keys %options; +% my %saw; +% foreach my $value ( @options ) { +% local($^W)=0; next if $saw{$value}++; +% + + <option value="<% $value %>"<% $value eq $conf->config($i->key) ? ' SELECTED' : '' %>><% $value %>: <% $options{$value} %> +% } +% if ( $conf->exists($i->key) && $conf->config($i->key) && ! exists $options{$conf->config($i->key)} ) { + + <option value=<% $conf->config($i->key) %> SELECTED><% $conf->config($i->key) %>: <% &{ $i->option_sub }( $conf->config($i->key) ) %> +% } + </select> - <% } elsif ( $type eq 'editlist' ) { %> +% } elsif ( $type eq 'editlist' ) { + + <script> - function doremove<%= $i->key. $n %>() { - fromObject = document.OneTrueForm.<%= $i->key. $n %>; + function doremove<% $i->key. $n %>() { + fromObject = document.OneTrueForm.<% $i->key. $n %>; for (var i=fromObject.options.length-1;i>-1;i--) { if (fromObject.options[i].selected) - deleteOption<%= $i->key. $n %>(fromObject,i); + deleteOption<% $i->key. $n %>(fromObject,i); } } - function deleteOption<%= $i->key. $n %>(object,index) { + function deleteOption<% $i->key. $n %>(object,index) { object.options[index] = null; } - function selectall<%= $i->key. $n %>() { - fromObject = document.OneTrueForm.<%= $i->key. $n %>; + function selectall<% $i->key. $n %>() { + fromObject = document.OneTrueForm.<% $i->key. $n %>; for (var i=fromObject.options.length-1;i>-1;i--) { fromObject.options[i].selected = true; } } - function doadd<%= $i->key. $n %>(object) { + function doadd<% $i->key. $n %>(object) { var myvalue = ""; - <% if ( defined($i->editlist_parts) ) { %> +% if ( defined($i->editlist_parts) ) { +% foreach my $pnum ( 0 .. scalar(@{$i->editlist_parts})-1 ) { - <% foreach my $pnum ( 0 .. scalar(@{$i->editlist_parts})-1 ) { %> if ( myvalue != "" ) { myvalue = myvalue + " "; } - <% if ( $i->editlist_parts->[$pnum]{type} eq 'select' ) { %> - myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.options[object.add<%= $i->key. $n . "_$pnum" %>.selectedIndex].value; +% if ( $i->editlist_parts->[$pnum]{type} eq 'select' ) { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.options[object.add<% $i->key. $n . "_$pnum" %>.selectedIndex].value; <!-- #RESET SELECT?? maybe not... --> - <% } elsif ( $i->editlist_parts->[$pnum]{type} eq 'immutable' ) { %> - myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.value; - <% } else { %> - myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.value; - object.add<%= $i->key. $n. "_$pnum" %>.value = ""; - <% } %> - - - <% } %> - <% } else { %> - myvalue = object.add<%= $i->key. $n. "_1" %>.value; - <% } %> +% } elsif ( $i->editlist_parts->[$pnum]{type} eq 'immutable' ) { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.value; +% } else { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.value; + object.add<% $i->key. $n. "_$pnum" %>.value = ""; +% } +% } +% } else { + + myvalue = object.add<% $i->key. $n. "_1" %>.value; +% } + var optionName = new Option(myvalue, myvalue); - var length = object.<%= $i->key. $n %>.length; - object.<%= $i->key. $n %>.options[length] = optionName; + var length = object.<% $i->key. $n %>.length; + object.<% $i->key. $n %>.options[length] = optionName; } </script> - <select multiple size=5 name="<%= $i->key. $n %>"> + <select multiple size=5 name="<% $i->key. $n %>"> <option selected>----------------------------------------------------------------</option> - <% foreach my $line ( $conf->config($i->key) ) { %> - <option value="<%= $line %>"><%= $line %></option> - <% } %> +% foreach my $line ( $conf->config($i->key) ) { + + <option value="<% $line %>"><% $line %></option> +% } + </select><br> - <input type="button" value="remove selected" onClick="doremove<%= $i->key. $n %>()"> - <script>SafeAddOnLoad(doremove<%= $i->key. $n %>); - SafeAddOnSubmit(selectall<%= $i->key. $n %>);</script> + <input type="button" value="remove selected" onClick="doremove<% $i->key. $n %>()"> + <script>SafeAddOnLoad(doremove<% $i->key. $n %>); + SafeAddOnSubmit(selectall<% $i->key. $n %>);</script> <br> - <%= itable() %><tr> - <% if ( defined $i->editlist_parts ) { %> - <% my $pnum=0; foreach my $part ( @{$i->editlist_parts} ) { %> + <% itable() %><tr> +% if ( defined $i->editlist_parts ) { +% my $pnum=0; foreach my $part ( @{$i->editlist_parts} ) { + <td> - <% if ( $part->{type} eq 'text' ) { %> - <input type="text" name="add<%= $i->key. $n."_$pnum" %>"> - <% } elsif ( $part->{type} eq 'immutable' ) { %> - <%= $part->{value} %><input type="hidden" name="add<%= $i->key. $n. "_$pnum" %>" value="<%= $part->{value} %>"> - <% } elsif ( $part->{type} eq 'select' ) { %> - <select name="add<%= $i->key. $n. "_$pnum" %>"> - <% foreach my $key ( keys %{$part->{select_enum}} ) { %> - <option value="<%= $key %>"><%= $part->{select_enum}{$key} %></option> - <% } %> +% if ( $part->{type} eq 'text' ) { + + <input type="text" name="add<% $i->key. $n."_$pnum" %>"> +% } elsif ( $part->{type} eq 'immutable' ) { + + <% $part->{value} %><input type="hidden" name="add<% $i->key. $n. "_$pnum" %>" value="<% $part->{value} %>"> +% } elsif ( $part->{type} eq 'select' ) { + + <select name="add<% $i->key. $n. "_$pnum" %>"> +% foreach my $key ( keys %{$part->{select_enum}} ) { + + <option value="<% $key %>"><% $part->{select_enum}{$key} %></option> +% } + </select> - <% } else { %> - <font color="#ff0000">unknown type <%= $part->type %></font> - <% } %> +% } else { + + <font color="#ff0000">unknown type <% $part->type %></font> +% } + </td> - <% $pnum++; } %> - <% } else { %> - <td><input type="text" name="add<%= $i->key. $n %>_0"></td> - <% } %> - <td><input type="button" value="add" onClick="doadd<%= $i->key. $n %>(this.form)"></td> +% $pnum++; } +% } else { + + <td><input type="text" name="add<% $i->key. $n %>_0"></td> +% } + + <td><input type="button" value="add" onClick="doadd<% $i->key. $n %>(this.form)"></td> </tr></table> - <% } else { %> - <font color="#ff0000">unknown type <%= $type %></font> - <% } %> - <% $n++; } %> +% } else { + + + <font color="#ff0000">unknown type <% $type %></font> +% } +% $n++; } + </td> - <td><a name="<%= $i->key %>"> - <b><%= $i->key %></b> - <%= $i->description %> + <td><a name="<% $i->key %>"> + <b><% $i->key %></b> - <% $i->description %> </a></td> </tr> - <% } %> +% } + </table><br> You may need to restart Apache and/or freeside-queued for configuration changes to take effect.<br> <input type="submit" value="Apply changes"><br><br> +% } -<% } %> </form> </body></html> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html index 9ce259c2b..2aa934812 100755 --- a/httemplate/docs/admin.html +++ b/httemplate/docs/admin.html @@ -11,49 +11,8 @@ /home/httpd/html, open https://your_host/freeside/. Replace "your_host" with the name or network address of your web server. <li>Select <u>Configuration</u> from the main menu and update your configuration values. - <li>Next you must create a service definition. An example of a service - definition would be a dial-up account or a domain. First, it is - necessary to create a domain definition. Click on <u>View/Edit service - definitions</u> and <u>Add a new service definition</u> with <i>Table</i> - <b>svc_domain</b> (and no modifiers). - <li>Now that you have created your first service, you must create a package - including this service which you can sell to customers. Zero, one, or many - services are bundled into a package. Click on <u>View/Edit package - definitions</u> and <u>Add a new package definition</u> which includes - quantity <b>1</b> of the svc_domain service you created above. - - <li>After you create your first package, then you must define who is - able to sell that package by creating an agent type. An example of - an agent type would be an internal sales representitive which sells - regular and promotional packages, as opposed to an external sales - representitive which would only sell regular packages of services. Click on - <u>View/Edit agent types</u> and <u>Add a new agent type</u>. Allow this - agent type to sell the package you created above. - - <li>After creating a new agent type, you must create an agent. Click on - <u>View/Edit agents</u> and <u>Add a new agent</u>. - - <li>Set up at least one Advertising source. Advertising sources will help - you keep track of how effective your advertising is, tracking where customers - heard of your service offerings. You must create at least one advertising - source. If you do not wish to use the referral functionality, simply create - a single advertising source only. Click on <u>View/Edit advertising - sources</u> and <u>Add a new advertising source</u>. - - <li>Click on <u>New Customer</u> and create a new customer for your system - accounts with billing type <b>Complimentary</b>. Leave the <i>First - package</i> dropdown set to <b>(none)</b>. - - <li>From the Customer View screen of the newly created customer, order the - package you defined above. - - <li>From the Package View screen of the newly created package, choose - <u>(Provision)</u> to add the customer's service for this new package. - - <li>Add your own domain. - - <li>Go back to <u>View/Edit service definitions</u> on the main menu, and + <li>Go to <u>View/Edit service definitions</u> on the main menu, and <u>Add a new service definition</u> with <i>Table</i> <b>svc_acct</b>. Select your domain in the <b>domsvc</b> Modifier. Set <b>Fixed</b> to define a service locked-in to this domain, or <b>Default</b> to define a service @@ -69,7 +28,7 @@ <li>If you are using Freeside to keep track of sales taxes, define tax information for your locales by clicking on the <u>View/Edit locales and tax - rates</b> on the main menu. + rates</u> on the main menu. <li>If you would like Freeside to notify your customers when their credit cards and other billing arrangements are about to expire, arrange for diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html deleted file mode 100644 index adaac17dc..000000000 --- a/httemplate/docs/billing.html +++ /dev/null @@ -1,68 +0,0 @@ -<head> - <title>Billing</title> -</head> -<body> - <h1>Billing</h1> - <ul> - <li>Add one or more <a href="../browse/part_bill_event.cgi">Invoice events</a> implmenting your business rules for re-sending invoices, retrying cards, suspending, etc. - <li>You can bill individual customers by clicking on the <i>Bill now</i> link on the main customer view. - <li>The <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> script should be run daily to bill customers and run invoice collection events. - <li>Real-time credit card processing: Install the <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> module for your processor. Configure the <a href="../config/config-view.cgi#business-onlinepayment">business-onlinepayment</a> configuration option. Disable the default <b>Batch card</b> <a href="../browse/part_bill_event.cgi">invoice event</a> and add one for Business::OnlinePayment. - <li>Optional: Credit card expiration alerts: Customize <a href="../config/config.cgi#alerter_template">alerter_template</a> configuration option and run <a href="man/bin/freeside-expiration-alerter.html">freeside-expiration-alerter</a> daily. - <li>Credit card decline alerts: Customize the <a href="../config/config.cgi#declinetemplate">declinetemplate</a> configuration option and set the <a href="../config/config.cgi#emaildecline">emaildecline</a> configuration option. - <li>Typeset (LaTeX) invoice templates - <ul> - <li>Install teTeX and Ghostscript (included with most distributions). - <li>Place your logo in EPS (Encapsulated PostScript) format with size 90pt X 36pt (<code>epsffit -c 0 0 90 33 yourlogo.eps >logo.eps</code>) at <code>/usr/local/etc/freeside/conf.<i>your_datasrc</i>/logo.eps</code>. - <li>Edit the <b>invoice_latexreturnaddress</b>, <b>invoice_latexfooter</b>, <b>invoice_latexnotes</b>, and <b>invoice_latexsmallfooter</b> configuration options. If you are adventurous, edit <b>invoice_latex</b> as well. - </ul> - <li>Plaintext invoice templates - <ul> - <li>See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the substitution language. - <li>You <b>must</b> call the invoice_lines() function at least once - pass it a number of lines, and it returns a list of array references, each of two elements: a service description column, and a price column. Alternatively, call invoice_lines() with no arguments, and pagination will be disabled - all invoice line items will print on one page, with no padding (recommended for email invoices). - <li>In addition, the following variables are available: - <ul> - <li>$invnum - invoice number - <li>$date - as a UNIX timestamp (see <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Format.pm">Date::Format</a> for conversion functions). - <li>$page - current page - <li>$total_pages - total pages - <li>@address - A six-element array containing the customer name, company, and address. -<!-- <li>$overdue - true if this invoice is overdue --> - </ul> - </ul> - <li>HTML invoice templates - <ul> - <li>Place your logo in PNG format at <code>/usr/local/etc/freeside/conf.<i>your_datasrc</i>/logo.png</code>. - <li>HTML invoices also use <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a>. - <li>Edit the <b>invoice_html</b> configuration option. - <li>The following configuration options can be set to override the default behaviour of using the invoice_latex* data: <b>invoice_htmlreturnaddress</b>, and <b>invoice_htmlfooter</b>, <b>invoice_htmlnotes</b>. - </ul> -<!-- <li>Batch credit card processing - <ul> - <li>After <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table. Export this table to your credit card batching. - <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table. Example code to add payments is: - <pre>use FS::cust_pay; - -# loop over all records in batch - -my $payment=create FS::cust_pay ( - 'invnum' => $invnum, - 'paid' => $paid, - '_date' => $_date, - 'payby' => $payby, - 'payinfo' => $payinfo, - 'paybatch' => $paybatch, -); - -my $error=$payment->insert; -if ( $error ) { - #process error -} - -# end loop -</pre> -All fields except paybatch are contained in the cust_pay_batch table. You can use paybatch field to track particular batches and/or particular transactions within a batch. - </ul> -<!-- <li>The <a href="man/bin/freeside-print-batch.html"><b>freeside-print-batch</b></a> script can print or email pending credit card batches for manual entry. --> - </ul> -</body> diff --git a/httemplate/docs/config.html b/httemplate/docs/config.html deleted file mode 100644 index 9caf3bb3a..000000000 --- a/httemplate/docs/config.html +++ /dev/null @@ -1,36 +0,0 @@ -<head> - <title>Configuration files</title> -</head> -<body> - <h1>Configuration files</h1> -<font size="+1" color="#ff0000">Configuration is now done by the top-level Makefile and web interface. The instructions below are no longer necessary.</font> -<ul> - <li>Create the <b>/usr/local/etc/freeside</b> directory to hold your configuration. - <li>Setting up <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication">Apache user authetication</a> is mandatory. - <li>Create the <b>/usr/local/etc/freeside/mapsecrets</b> file, which maps Apache users to a secrets file which contains a DBI data source, username and password. Every -line in <b>/usr/local/etc/freeside/mapsecrets</b> should contain a username and -filename, separated by whitespace. Note that these are not local usernames - -they are passed from Apache. <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication"> -Apache user authetication</a> is mandatory. For example, if you had the Apache users admin, -john, and sam, -you mapsecrets file might look like: -<pre> -admin secretfile -john secretfile -sam secretfile -</pre> - <li>Next, the filename(s) referenced in <b>/usr/local/etc/freeside/mapsecrets</b> file should be created in the <b>/usr/local/etc/freeside/</b> directory. Each file contains three lines: <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI data source</a> (for example, - <tt>DBI:mysql:freeside</tt> or <tt>DBI:Pg:host=localhost;dbname=freeside</tt>), database username, and database password. - These files should not be world readable. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD">manpage for your DBD</a> for the exact syntax of a DBI data source. In a normal installation such as the example above, a single file <b>/usr/local/etc/freeside/secretfile</b> would be created - for example: -<pre> -DBI:Pg:host=localhost;dbname=freeside -dbusername -dbpassword -</pre> -<li>Create the <b>/usr/local/etc/freeside/conf.<i>datasource</i></b> directory, for example, <b>/usr/local/etc/freeside/conf.DBI:Pg:host=localhost;dbname=freeside</b> (remember to backslash-escape the ; character when creating directories in the shell: -<pre>mkdir /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=freeside -</pre> -<li>The rest of the configuration can be done with the web interface. Select <u>Configuration</u> from the main menu and update your configuration values. -</ul> -</body> -</html> diff --git a/httemplate/docs/export.html b/httemplate/docs/export.html deleted file mode 100755 index c6c6abd0d..000000000 --- a/httemplate/docs/export.html +++ /dev/null @@ -1,19 +0,0 @@ -<head> - <title>Exports</title> -</head> -<body> - <h1>Exports</h1> - <p>Exports allow you to provision services to remote machines, databases and - APIs. Some exports, such as <b>sqlradius</b> and - <b>sqlradius_withdomain</b>, enable a feed for retreiving rating/usage data. - <p>Exports can be added and edited under - <a href="../browse/part_export.cgi"><i>Sysadmin | View/Edit Exports</i></a>. - <p>Selecting an export on the - <a href="../edit/part_export.cgi"><i>Sysadmin | View/Edit Exports | Add a new export</i></a> page will - show more information on that specific export, including available - options, setup and usage. - <p>Exports are activated by associating them with one or more service - definitions: <a href="../browse/part_svc.cgi"><i>Sysadmin | View/Edit Service definitions<i></a>. - -</body> - diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html index 7254d76f3..3b419de00 100644 --- a/httemplate/docs/index.html +++ b/httemplate/docs/index.html @@ -6,13 +6,10 @@ <img src="overview-new.png"> <h3>Installation and upgrades</h3> <ul> - <li><a href="install.html">New Installation</a> - <li><a href="install-rt.html">Installing integrated RT ticketing</a> - <li><a href="upgrade7.html">Upgrading from 1.3.0 to 1.3.1</a> - <li><a href="upgrade8.html">Upgrading from 1.3.1 to 1.4.0</a> - <li><a href="upgrade9.html">Upgrading from 1.4.0 to 1.4.1</a> - <li><a href="upgrade-1.4.2.html">Upgrading from 1.4.1 to 1.4.2</a> - <li><a href="upgrade10.html">Upgrading from 1.4.1 (or 1.4.2) to 1.5.7</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Installation">New Installation</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:RT_Installation">Installing integrated RT ticketing</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Self-Service_Installation">Signup/Self-service installation</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Upgrading">Upgrading from 1.5.8 or 1.6.X</a> </ul> <h3>Configuration and setup</h3> <ul> @@ -23,9 +20,8 @@ <!-- <li><a href="../index.html#admin">Administration</a> !--> - <li><a href="export.html">Exports</a> - <li><a href="selfservice.html">Signup, self-service and reseller interfaces</a> - <li><a href="billing.html">Billing</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Exports_.28provisioning.29">Exports</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Billing">Billing</a> </ul> <h3>Developer</h3> <ul> diff --git a/httemplate/docs/install-rt.html b/httemplate/docs/install-rt.html deleted file mode 100644 index da0941a09..000000000 --- a/httemplate/docs/install-rt.html +++ /dev/null @@ -1,78 +0,0 @@ -<head> - <title>Installing integrated RT ticketing</title> -</head> -<body> -<h1>Installing integrated RT ticketing</h1> - -<p><i>Integrated ticketing is an new feature and these instructions are preliminary. Documentation contributions are welcome.</i> - -<p><i>There is also support for running this integration against an external RT installation, but it is not (yet) documented.</i> - -<p>Perl minimum version 5.8.3 is required. HTML::Mason is required. - -<p>Install the following perl modules: - <ul> - <li><a href="http://search.cpan.org/search?dist=Apache-Session">Apache::Session</a> - <li><a href="http://search.cpan.org/search?dist=HTML-Tree">HTML::TreeBuilder (HTML-Tree)</a> - <li><a href="http://search.cpan.org/search?dist=HTML-Format">HTML::FormatText (HTML-Format)</a> - <li><a href="http://search.cpan.org/search?dist=Test-Inline">Test::Inline</a> - <li><a href="http://search.cpan.org/search?dist=Class-ReturnValue">Class::ReturnValue</a> - <li><a href="http://search.cpan.org/search?dist=DBIx-SearchBuilder">DBIx::SearchBuilder</a> - <li><a href="http://search.cpan.org/search?dist=Log-Dispatch">Log::Dispatch</a> - <li><a href="http://search.cpan.org/search?dist=Locale-Maketext-Lexicon">Locale::Maketext::Lexicon</a> - <li><a href="http://search.cpan.org/search?dist=Locale-Maketext-Fuzzy">Locale::Maketext::Fuzzy</a> - <li><a href="http://search.cpan.org/search?dist=Text-Wrapper">Text::Wrapper</a> - <li><a href="http://search.cpan.org/search?dist=Time-modules">Time::ParseDate (Time-modules)</a> - <li><a href="http://search.cpan.org/search?dist=TermReadKey">Term::ReadKey (TermReadKey)</a> - <li><a href="http://search.cpan.org/search?dist=Text-Autoformat">Text::Autoformat</a> - <li><a href="http://search.cpan.org/search?dist=Text-Quoted">Text::Quoted</a> - <li><a href="http://search.cpan.org/search?dist=Regexp-Common">Regexp::Common</a> - <li><a href="http://search.cpan.org/search?dist=HTML-Scrubber">HTML::Scrubber</a> - <li><a href="http://search.cpan.org/search?dist=Tree-Simple">Tree::Simple</a> - </ul> - -<p>Create a new Unix group called 'rt' - -<p>Edit the top-level Makefile, set RT_ENABLED to 1 and set the RT_DOMAIN, RT_TIMEZONE, and FREESIDE_URL variables. - -<p><pre>make configure-rt -make create-rt -make install-rt -</pre> - -<p>Add the following to your httpd.conf: -<pre> -# replace /var/www/freeside with your freeside document root -<DirectoryMatch "^/var/www/freeside/rt/.*NoAuth"> -<Limit GET POST> -allow from all -Satisfy any -SetHandler perl-script -PerlHandler HTML::Mason -</Limit> -</DirectoryMatch> -# replace /var/www/freeside with your freeside document root -<DirectoryMatch "^/var/www/freeside/rt/.*NoAuth/images"> -SetHandler None -</DirectoryMatch> -# replace /var/www/freeside with your freeside document root -<Directory /var/www/freeside/rt/Ticket/Attachment> -SetHandler perl-script -PerlHandler HTML::Mason -</Directory> -</pre> - -<p>Set the <b>ticket_system</b> configuration value to <b>RT_Internal</b>. You may also wish to set <b>ticket_system-default_queueid</b> once you have RT configured. - -<p>Bootstrap RT's permissions: - <ul> - <li>Click on "Ticketing Main" on the Freeside main menu to auto-create an RT login for your username - <li>Run <code>freeside-adduser -h /usr/local/etc/freeside/htpasswd root</code> and set a (temporary) password - <li>Log into your Freeside installation as the "root" user you just created, by closing your browser or using <code>https://root@yourmachone/freeside/</code> syntax. - <li>Click on "Ticketing Main" on the Freeside main menu. Click on "Configuration", then "Global", and then "User Rights". Grant the "SuperUser" right to your RT login. - <li>Remove the temporary "root" user from /usr/local/etc/freeside/mapsecrets and /usr/local/etc/freeside/htpasswd - </ul> - -<p>Follow the <A HREF="http://wiki.bestpractical.com/">regular RT documentation</A> to configure RT, setup the mailgate, etc. - -</body> diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html deleted file mode 100644 index 1f80db1a7..000000000 --- a/httemplate/docs/install.html +++ /dev/null @@ -1,214 +0,0 @@ -<head> - <title>Installation</title> -</head> -<body> -<h1>Installation</h1> -<i>Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.</i><br><br> -Before installing, you need: -<ul> - <li><a href="http://www.perl.com/">Perl</a>, minimum version 5.005_03. (5.8.3 for the integrated RT ticketing) - <li><a href="http://www.apache.org">Apache</a> (<a href="http://www.modssl.org/">mod_ssl</a> or <a href="http://www.apache-ssl.org">Apache-SSL</a> highly recommended) - <li><a href="http://perl.apache.org/">mod_perl</a> (if compiling your own mod_perl, make sure you set the <a href="http://perl.apache.org/guide/install.html#EVERYTHING">EVERYTHING</a>=1 compile-time option) - <li><a href="http://www.openssh.com/">SSH</a> (<a href="http://www.openssh.com//">OpenSSH</a> is recommended. SSH Communications Security <a href="http://www.ssh.com/products/ssh/download.cfm">commercial SSH version 3</a> has been reported incompatible with Freeside.) - <li><a href="http://rsync.samba.org/">rsync</a> - <li>Optional, enables typeset invoices: teTeX and Ghostscript (included with most distributions). - <li>A <b>transactional</b> database engine <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">supported</a> by Perl's <a href="http://dbi.perl.org">DBI</a>. - <ul> - <li><a href="http://www.postgresql.org/">PostgreSQL</a> is recommended (v7.2 or later, 7.4 or later recommended). - <li> <a href="http://www.mysql.com/">MySQL</a> is <b>not currently supported</b>. <FONT SIZE="-1"><i>Developers intersted in maintaining MySQL support are welcome to ask on the -devel mailing list; many things work, but MySQL support needs a maintainer to update it for recent (and future) changes.</i></FONT> - <!-- <li><a href="http://www.mysql.com/">MySQL</a> <b>MINIMUM VERSION 4.1</b> is untested but may work. Versions before 4.1 do not support standard SQL subqueries and are <b>NOT SUPPORTED</b>. -<!-- <li>MySQL has been reported to work. - MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are not supported. You <b>must</b> use one of the new <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/I/n/InnoDB.html">InnoDB</a>. Set it as the default table type using the <code>--default-table-type=InnoDB</code> <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Command-line_options">mysqld command-line option</a> or by setting <code>default-table-type=InnoDB</code> in the <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Option_files">my.cnf option file</a>. ---> - </ul> - <i>Note: the above only applies to the database used by the Freeside software itself. Freeside can integrate with RADIUS and other servers running MySQL <!--(any version)--> or any other DBI-supported database.</i> - <li>Perl modules (<a href="http://search.cpan.org/~andk/CPAN/lib/CPAN.pm">CPAN</a> will query, download and build perl modules automatically) - <ul> -<!-- <li><a href="http://search.cpan.org/dist/Array-PrintCols">Array-PrintCols</a> - <li><a href="http://search.cpan.org/dist/Term-Query">Term-Query</a> (make test broken; install manually) --> - <li><a href="http://search.cpan.org/dist/MIME-Base64">MIME-Base64</a> - <li><a href="http://search.cpan.org/dist/Digest-MD5">Digest-MD5</a> -<!-- <li><a href="http://search.cpan.org/dist/MD5">MD5</a> --> - <li><a href="http://search.cpan.org/dist/URI">URI</a> - <li><a href="http://search.cpan.org/dist/HTML-Tagset">HTML-Tagset</a> - <li><a href="http://search.cpan.org/dist/HTML-Parser">HTML-Parser</a> - <li><a href="http://search.cpan.org/dist/libnet">libnet</a> - <li><a href="http://search.cpan.org/dist/Locale-Codes">Locale-Codes</a> - <li><a href="http://search.cpan.org/dist/Net-Whois-Raw">Net-Whois-Raw</a> - <li><a href="http://search.cpan.org/dist/libwww-perl">libwww-perl</a> - <li><a href="http://search.cpan.org/dist/Business-CreditCard">Business-CreditCard</a> -<!-- <li><a href="http://search.cpan.org/dist/Data-ShowTable">Data-ShowTable</a> --> - <li><a href="http://search.cpan.org/dist/MailTools">MailTools</a> - <li><a href="http://search.cpan.org/dist/TimeDate">TimeDate</a> - <li><a href="http://search.cpan.org/dist/DateManip">DateManip</a> - <li><a href="http://search.cpan.org/dist/File-CounterFile">File-CounterFile</a> - <li><a href="http://search.cpan.org/dist/FreezeThaw">FreezeThaw</a> - <li><a href="http://search.cpan.org/dist/String-Approx">String-Approx</a> - <li><a href="http://search.cpan.org/dist/Text-Template">Text-Template</a> - <li><a href="http://search.cpan.org/dist/DBI">DBI</a> - <li><a href="http://search.cpan.org/search?mode=module&query=DBD">DBD for your database engine</a> (<a href="http://search.cpan.org/dist/DBD-Pg">DBD::Pg</a> for PostgreSQL<!--, <a href="http://search.cpan.org/search?dist=DBD-mysql">DBD::mysql</a> for MySQL-->) -<!-- <li><a href="http://search.cpan.org/dist/DBIx-DataSource">DBIx-DataSource</a> --> - <li><a href="http://search.cpan.org/dist/DBIx-DBSchema">DBIx-DBSchema</a> - <li><a href="http://search.cpan.org/dist/Net-SSH">Net-SSH</a> - <li><a href="http://search.cpan.org/dist/String-ShellQuote">String-ShellQuote</a> - <li><a href="http://search.cpan.org/dist/Net-SCP">Net-SCP</a> - <li><a href="http://www.masonhq.com/">HTML::Mason</a> (recommended, enables full functionality) or <a href="http://www.apache-asp.org/">Apache::ASP</a> (deprecated, integrated RT ticketing will not be available) - <li><a href="http://search.cpan.org/dist/Tie-IxHash">Tie-IxHash</a> - <li><a href="http://search.cpan.org/dist/Time-Duration">Time-Duration</a> - <li><a href="http://search.cpan.org/dist/HTML-Widgets-SelectLayers">HTML-Widgets-SelectLayers</a> - <li><a href="http://search.cpan.org/dist/Storable">Storable</a> - <li><a href="http://search.cpan.org/dist/Cache-Cache">Cache::Cache</a> - <li><a href="http://search.cpan.org/dist/NetAddr-IP">NetAddr-IP</a> - <li><a href="http://search.cpan.org/dist/Chart">Chart</a> - <li><a href="http://search.cpan.org/dist/Crypt-PasswdMD5">Crypt::PasswdMD5</a> - <li><a href="http://search.cpan.org/dist/Locale-SubCountry">Locale::SubCountry</a> - <li><a href="http://search.cpan.org/dist/Frontier-RPC">Frontier::RPC</a> - <li><a href="http://search.cpan.org/dist/Text-CSV_XS">Text::CSV_XS</a> - <li><a href="http://search.cpan.org/dist/Spreadsheet-WriteExcel">Spreadsheet::WriteExcel</a> - <li><a href="http://search.cpan.org/dist/IO-stringy">IO-stringy (IO::Scalar)</a> - <li><a href="http://search.cpan.org/dist/Frontier-RPC">Frontier::RPC (Frontier::RPC2)</a> - <li><a href="http://search.cpan.org/dist/MIME-tools">MIME::Entity (MIME-tools)</a> - <li><a href="http://search.cpan.org/dist/IPC-Run3">IPC::Run3</a> - <li><a href="http://search.cpan.org/dist/Term-ReadKey">Term::ReadKey</a> -<!-- <li><a href="http://search.cpan.org/dist/Crypt-YAPassGen">Crypt::YAPassGen</a> --> - <li><a href="http://search.cpan.org/search?mode=module&query=MIME::Entity">Fax::Hylafax::Client</a> <i>(Required if using FAX invoice destinations)</i> - <li><a href="http://search.cpan.org/dist/ApacheDBI">Apache::DBI</a> <i>(optional but recommended for better webinterface performance)</i> - </ul> -</ul> -Install the Freeside distribution: -<ul> - <li>Add the user and group `freeside' to your system. - <li>Allow the freeside user full access to the freeside database. - <ul> - <li> with <a href="http://www.postgresql.org/users-lounge/docs/7.1/postgres/user-manag.html#DATABASE-USERS">PostgreSQL</a>: - <pre> -$ su postgres (pgsql on some distributions) -$ createuser -P freeside -Enter password for user "freeside": -Enter it again: -Shall the new user be allowed to create databases? (y/n) y -Shall the new user be allowed to create more new users? (y/n) n -CREATE USER</pre> - <li> with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#User_Account_Management">MySQL</a>: - <pre> -$ mysqladmin -u root password '<i>set_a_root_database_password</i>' -$ mysql -u root -p -mysql> GRANT SELECT,INSERT,UPDATE,DELETE,INDEX,ALTER,CREATE,DROP on freeside.* TO freeside@localhost IDENTIFIED BY '<i>set_a_freeside_database_password</i>';</pre> - </ul> -<!-- <li>Unpack the tarball: <pre>gunzip -c fs-x.y.z.tar.gz | tar xvf -</pre>--> - <li>Edit the top-level Makefile: - <ul> - <li>Set <tt>DATASOURCE</tt> to your <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI data source</a>, for example, <tt>DBI:Pg:dbname=freeside</tt> for PostgresSQL or <tt>DBI:mysql:freeside</tt> for MySQL. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">manpage for your DBD</a> for the exact syntax of your DBI data source. - <li>Set <tt>DB_PASSWORD</tt> to the freeside database user's password. - </ul> - <li>Add the freeside database to your database engine: - <ul> - <li>with Postgres: - <pre> -$ su freeside -$ createdb -E sql_ascii freeside</pre> - <li>with MySQL: - <pre> -$ mysqladmin -u freeside -p create freeside </pre> - </ul> - <li>Build and install the Perl modules: - <pre> -$ make perl-modules -$ su -# make install-perl-modules</pre> - <li>Create the necessary configuration files:<pre> -$ su -# make create-config -</pre> - <li>Run a <b>separate</b> iteration of Apache[-SSL] with mod_perl enabled <b>as the freeside user</b>. - <li>Edit the <tt>Makefile</tt> and set <tt>TEMPLATE</tt> to <tt>asp</tt> or <tt>mason</tt>. Also set <tt>FREESIDE_DOCUMENT_ROOT</tt>. - <li>Run <tt> make install-docs</tt>. -</ul> -<table> - <tr> - <th>Mason (recommended)</th><th>Apache::ASP (deprecated)</th> - </tr> - <tr> - - <td valign="top"><ul> - <li>Configure Apache: -<font size="-1"><pre> -PerlModule HTML::Mason -# your freeside docuemnt root -<Directory /var/www/freeside> -<Files ~ (\.cgi|\.html)> -AddHandler perl-script .cgi .html -PerlHandler HTML::Mason -</Files> -<Perl> -require "/usr/local/etc/freeside/handler.pl"; -</Perl> -</Directory> -</pre></font> - </ul></td> - - <td valign="top"><ul> - <li>Configure Apache: -<font size="-1"><pre> -PerlModule Apache::ASP -# your freeside document root -<Directory /var/www/freeside> -<Files ~ (\.cgi|\.html)> -AddHandler perl-script .cgi .html -PerlHandler Apache::ASP -</Files> -<Perl> -$MLDBM::RemoveTaint = 1; -</Perl> -PerlSetVar Global /usr/local/etc/freeside/asp-global/ -PerlSetVar Debug 2 -PerlSetVar RequestBinaryRead Off -# your freeside document root -PerlSetVar IncludesDir /var/www/freeside -</Directory> -</pre></font> - </ul></td> - - </tr> -</table> -<ul> -<li>Restrict access to this web interface - see the <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache documentation on user authentication</a>. For example, to configure user authentication with <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files), add something like the following to your Apache httpd.conf file, adjusting for your actual paths: -<pre> -#your freeside document root -<Directory /var/www/freeside> -AuthName Freeside -AuthType Basic -AuthUserFile /usr/local/etc/freeside/htpasswd -require valid-user -</Directory> -</pre> - <li>Create one or more Freeside users (your internal sales/tech folks, not customer accounts). These users are setup using using Apache authentication, not UNIX user accounts. For example, using <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files): - <ul> - <li>First user:<font size="-1"> -<pre>$ su -# <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -c -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font> - <li>Additional users:<font size="-1"> -<pre>$ su -# <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font> - </ul> - <i>(using other auth types, add each user to your <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache authentication</a> and then run: <tt>freeside-adduser <b>username</b></tt>)</i> - <li>Create the Freeside system users: -<pre>$ su -# <a href="man/bin/freeside-adduser.html">freeside-adduser</a> fs_queue -# <a href="man/bin/freeside-adduser.html">freeside-adduser</a> fs_selfservice</pre> - <li>As the freeside UNIX user, run <tt>freeside-setup <b>username</b></tt> to create the database tables, passing the username of a Freeside user you created above: -<pre> -$ su freeside -$ freeside-setup <b>username</b> -</pre> - Alternately, use the -s option to enable shipping addresses: <tt>freeside-setup -s <b>username</b></tt> - <li>As the freeside UNIX user, run <tt>bin/populate-msgcat <b>username</b></tt> (in the untar'ed freeside directory) to populate the message catalog, passing the username of a Freeside user you created above: -<pre> -$ su freeside -$ cd <b>/path/to/freeside/</b> -$ bin/populate-msgcat <b>username</b> -</pre> - <li><tt>freeside-queued</tt> was installed with the Perl modules. Start it now and ensure that is run upon system startup (Do this manually, or edit the top-level Makefile, replacing INIT_FILE with the appropriate location on your systemand QUEUED_USER with the username of a Freeside user you created above, and run <tt>make install-init</tt>) - <li>Now proceed to the initial <a href="admin.html">administration</a> of your installation. -</ul> -</body> diff --git a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack deleted file mode 100644 index e69de29bb..000000000 --- a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack +++ /dev/null diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html index cdb59a2e9..cd4914a6c 100644 --- a/httemplate/docs/schema.html +++ b/httemplate/docs/schema.html @@ -199,10 +199,19 @@ <li>amount <li>_date </ul> - <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch + <li><a name="pay_batch" href="man/FS/pay_batch.html">pay_batch</a> - Pending batch + <ul> + <li>batchnum + <li>status + <li>download + <li>upload + </ul> + <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch members <ul> <li>paybatchnum - <li>cardnum + <li>batchnum + <li>payby - CARD, CHEK, LECB, BILL, or COMP + <li>payinfo - account number <li>exp - card expiration <li>amount <li>invnum - <a href="#cust_bill">invoice</a> @@ -216,6 +225,7 @@ <li>state <li>zip <li>country + <li>status </ul> <li><a name="cust_pkg" href="man/FS/cust_pkg.html">cust_pkg</a> - Customer billing items <ul> diff --git a/httemplate/docs/selfservice.html b/httemplate/docs/selfservice.html deleted file mode 100644 index 9dc8f2a5e..000000000 --- a/httemplate/docs/selfservice.html +++ /dev/null @@ -1,78 +0,0 @@ -<head> - <title>Signup, self-service and reseller interfaces</title> -</head> -<body> - <h1>Signup, self-service and reseller interfaces</h1> -For security reasons, the self-service interface should run on a public -machine, not the backend Freeside server. On the public machine, install: -<ul> - <li>A web server, such as <a href="http://www.apache.org">Apache</a> (<a href="http://www.modssl.org/">mod_ssl</a> or <a href="http://www.apache-ssl.org">Apache-SSL</a> highly recommended) - - <li><a href="ftp://ftp.cs.hut.fi/pub/ssh/">SSH</a> - <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a>. - <li><a href="http://search.cpan.org/search?dist=Text-Template">Text::Template</a> - <li><a href="http://search.cpan.org/search?dist=Storable">Storable</a> - <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a> - <li><a href="http://search.cpan.org/search?dist=HTTP-BrowserDetect">HTTP::BrowserDetect</a> - <li><a href="http://search.cpan.org/search?dist=HTML-Parser">HTML::Parser</a> - - <li><a href="man/FS/SelfService.html">FS::SelfService</a> (copy the fs_selfservice/FS-SelfService directory to the external machine, then: perl Makefile.PL; make; make install) -</ul> -Then: -<ul> - <li>Set the <a href="../config/config.cgi#unclassified"><i>signup_server-default_agentnum</i></a> configuration value to a default <a href="../browse/agent.cgi">agent number</a>. - <li>Set the <a href="../config/config.cgi#unclassified"><i>signup_server-default_refnum</i></a> configuration value to a default <a href="../browse/part_referral.cgi">advertising source</a>. - <li>Set the <a href="../config/config.cgi#unclassified"><i>signup_server-payby</i></a> configuration value to the acceptable payment types for signups. - <li>Set the <a href="../config/config.cgi#unclassified"><i>signup_server-realtime</i></a> configuration value to run billing for signups immediately. - <li>Add the user `freeside' to the the external machine. - <li>Copy or symlink the <code>fs_selfservice/FS-SelfService/cgi/</code> directory into the web server's document space. Optionally, customize the .html templates. "Entry points" (useful places to link to) are: - <ul> - <li>signup.cgi - Signup - <li>selfservice.cgi - Customer self-service - <li>agent.cgi - Reseller interface - <li>passwd.cgi - Simple password-changing interface - <li>promocode.html - Promotional code pre-signup - <li>regcode.html - Registration code pre-signup - <li>stateselect.html - State selection pre-signup - </ul> - <li>Enable CGI execution for files with the `.cgi' extension. (with <a href="http://www.apache.org/docs/mod/mod_mime.html#addhandler">Apache</a>), for example: <pre> -#directory where selfservice .cgi scripts and .html templates are located -<Directory /var/www/selfservice> -AddHandler cgi-script .cgi -Options +ExecCGI -</Directory></pre> - <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user). - <li>touch /usr/local/freeside/selfservice_socket; chown freeside /usr/local/freeside/selfservice_socket; chmod 600 /usr/local/freeside/selfservice_socket - <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> to run signup.cgi, selfservice.cgi, agent.cgi and passwd.cgi as the freeside user. <b>Do not run your public web server as the freeside user!</b> - <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s). - <li>Run an instance of <pre>freeside-selfservice-server <i>user</i> <i>machine</i></pre> on the Freeside machine for each external machine. - <ul> - <li><i>user</i> is a user from the mapsecrets file. - <li><i>machine</i> is the name of the external machine. - - - </ul> -</ul> -Optional: -<ul> - <li>You can install the files in the <code>fs_selfservice/FS-SelfService/cgi/</code> directory multiple places in your web server's document space, and customize the .html templates differently for each. You can set the agentnum used for each signup by editing signup.html and including a hidden field with the agentnum: - <pre> - <INPUT TYPE="hidden" NAME="agentnum" VALUE="3"></pre> - <li>When linking to signup.cgi, you can include a referring custnum in the URL as follows: <code>http://public.web.server/path/signup.cgi?ref=1542</code> - <li>If you create a <b>/usr/local/freeside/ieak.template</b> file on the external machine, it will be sent to IE users with MIME type <i>application/x-Internet-signup</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available. - (an example file is included as <b>fs_selfservice/FS-SelfService/ieak.template</b>) See the section on <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/INS.HTM">internet settings files</a> in the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information. -<!-- <li>If you create a <b>/usr/local/freeside/success.html</b> file on the external machine, it will be used as the success HTML page. Although template substiutions are available, a regular HTML file will work fine here, unlike signup.html. An example file is included as <b>fs_signup/FS-SignupClient/cgi/success.html</b>--> - <li>Variable substitutions available in <b>ieak.template</b> and <b>success.html</b>: - <ul> - <li>$ac - area code of selected POP - <li>$exch - exchange of selected POP - <li>$loc - local part of selected POP - <li>$username - <li>$password - <li>$email_name - first and last name - <li>$pkg - package name - </ul> -<!-- <li>If you create a <b>/usr/local/freeside/signup.html</b> file on the external machine, it will be used as a template for the form HTML. This requires the template to be constructed appropriately; probably best to start with the example file included as <b>fs_signup/FS-SignupClient/cgi/signup.html</b>. --> -<!-- <li>If there are any entries in the <i>prepay_credit</i> table, a user can enter a string matching the <b>identifier</i> column to receive the credit specified in the <b>amount</b> column, and/or the time specified in the <b>seconds</b> column (for use with the <a href="session.html">session monitor</a>), after which that <b>identifier</b> is no longer valid. This can be used to implement pre-paid "calling card" type signups. The <i>bin/generate-prepay</i> script can be used to populate the <i>prepay_credit</i> table. --> -</ul> -</body> diff --git a/httemplate/docs/trouble.html b/httemplate/docs/trouble.html deleted file mode 100755 index fce743928..000000000 --- a/httemplate/docs/trouble.html +++ /dev/null @@ -1,26 +0,0 @@ -<head> - <title>Troubleshooting</title> -</head> -<body> - <h1>Troubleshooting</h1> - <ul> - <li>When troubleshooting the web interface, helpful information is often in your web server's error log. - <li>If bin/svc_acct.import fails with an "Out of memory!" error using MySQL, upgrede MySQL and recompile the Perl DBD. There was a memory leak in some older versions of MySQL. - <li>If you get tons of errors in your web server's error log like this: -<pre> -Ambiguous use of value => resolved to "value" => -at /usr/lib/perl5/site_perl/File/CounterFile.pm line 132. -</pre> - This clutters up your log files but is otherwise harmless. Upgrade to the latest File::CounterFile. - <li>If you get errors like this: -<pre> -UID.pm: Can't open /var/spool/freeside/conf/secrets: Permission denied -at <i>/your/path</i>/site_perl/FS/UID.pm line 26. -BEGIN failed--compilation aborted at -<i>/your/path</i>/edit/process/part_svc.cgi line 15. -</pre> - Then the scripts are not running as the freeside freeside user. See -the <a href="install.html">New Installation</a> section of the documentation. - <li>If you receive `can not connect to server' errors using MySQL on a system that doesn't support native threading, you may need to specify the full hostname in your DBI datasource. See the <a href="http://www.mysql.com/Manual_chapter/manual_Problems.html#Can_not_connect_to_server">MySQL documentation</a>, DBI manpage and the DBD::mysql manpage for details. - </ul> -</body> diff --git a/httemplate/docs/upgrade-1.4.2.html b/httemplate/docs/upgrade-1.4.2.html deleted file mode 100644 index a24661142..000000000 --- a/httemplate/docs/upgrade-1.4.2.html +++ /dev/null @@ -1,27 +0,0 @@ -<head> - <title>Upgrading to 1.4.2</title> -</head> -<body> -<h1>Upgrading to 1.4.2 from 1.4.1</h1> -<ul> - <li>If migrating from less than 1.4.1, see these <a href="upgrade9.html">instructions</a> first. - <li>Back up your data and current Freeside installation. - <li>Install <a href="http://search.cpan.org/search?dist=Locale-SubCountry">Locale::SubCountry</a> - <li>Install <a href="http://search.cpan.org/search?dist=IPC-ShareLite">IPC::ShareLite</a> - <li>Install <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML::Widgets::SelectLayers</a> 0.04. - <li>Install <a href="http://search.cpan.org/search?dist=DBIx-DBSchema">DBIx::DBSchema</a> 0.23. - <li>Install <a href="http://search.cpan.org/search?dist=DBD-Pg">DBD::Pg</a> 1.32. - <li>Install <a href="http://search.cpan.org/search?dist=Cache-Cache">Cache::Cache</a>. - <li>Install <a href="http://search.cpan.org/search?dist=Net-SSH">Net::SSH</a> 0.08. - <li>Install <a href="http://search.cpan.org/search?dist=Crypt-PasswdMD5">Crypt::PasswdMD5</a> - <li>Install <a href="http://search.cpan.org/search?dist=Net-Whois-Raw">Net::Whois::Raw</a> - <li>CGI.pm minimum version 2.47 is required. You will probably need to install a current CGI.pm from CPAN if you are using Perl 5.005 or earlier. - <li>File::Temp minimum version 0.14 is required. You will probably need to install a currrent File::Temp from CPAN if you are using Perl 5.6 or earlier. - <li>If using Apache::ASP, add <code>PerlSetVar RequestBinaryRead Off</code> to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55. - <li>Run <code>make aspdocs</code> or <code>make masondocs</code>. - <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space. - <li>Run <code>make install-perl-modules</code>. - <li>The signup server and password server are deprecated in 1.4.2. Their functionality has been incorperated into the self-service server. Edit or reinstall your init script, and set the "signup_server-default_agentnum" and "signup_server-default_refnum" configuration options. The FS::SignupClient interface is still available as a compatibility wrapper, so you should be able to continue to use your current signup.cgi. - <li>Optional: To use typeset invoices, install tetex and ghostscript, and copy conf/invoice_latex, conf/invoice_latexnotes, and conf/invoice_latexfooter to /usr/local/etc/freeside/conf.<datasrc>/ - <li>Restart Apache and freeside-queued. -</body> diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html deleted file mode 100644 index ac2c6238d..000000000 --- a/httemplate/docs/upgrade10.html +++ /dev/null @@ -1,93 +0,0 @@ -<pre> -this is incomplete - -NOTE: Version numbering has been simplified. 1.5.7 is the version after -1.5.0pre6. It is still a development version - releases with odd numbered -middle parts (NN in x.NN.x) are development versions, like Perl or Linux. - -If migrating from 1.5.7, see README.1.5.8 instead - -If migrating from 1.5.0pre6, see README.1.5.7 instead - -install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with <a href="http://420.am/~ivan/DBD-Pg-1.22-fixvercmp.patch">this patch</a> and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm) -install DBIx::DBSchema 0.27 (or later) - (if you are running Pg version 7.2.x or earlier, install at least - DBIx::DBSchema 0.29) -install Net::SSH 0.08 -install HTML::Widgets::SelectLayers 0.05 -install Business::CreditCard 0.28 - -- If using Apache::ASP, add PerlSetVar RequestBinaryRead Off and PerlSetVar IncludesDir /your/freeside/document/root/ to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55. -- In httpd.conf, change <Files ~ \.cgi> to <Files ~ (\.cgi|\.html)> -- In httpd.conf, change <b>AddHandler perl-script .cgi</b> or <b>SetHandler perl-script</b> to <b>AddHandler perl-script .cgi .html</b> - -install NetAddr::IP, Chart::Base, Locale::SubCountry, Text::CSV_XS, -Spreadsheet::WriteExcel, IO-stringy (IO::Scalar), Frontier::RPC -(Frontier::RPC2), MIME::Entity (MIME-tools), IPC::Run3, Net::Whois::Raw, -JSON and Term::ReadKey -<!-- and Crypt::YAPassGen--> - -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' ); -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' ); - -DROP INDEX cust_bill_pkg1; - -On recent Pg versions: - -ALTER TABLE cust_main ALTER COLUMN payinfo varchar(512) NULL; -ALTER TABLE h_cust_main ALTER COLUMN payinfo varchar(512) NULL; - -Or on older Pg versions that don't support altering columns directly: -(dump database, edit & reload) - -On recent Pg versions: - -ALTER TABLE svc_forward ALTER COLUMN srcsvc DROP NOT NULL; -ALTER TABLE h_svc_forward ALTER COLUMN srcsvc DROP NOT NULL; -ALTER TABLE svc_forward ALTER COLUMN dstsvc DROP NOT NULL; -ALTER TABLE h_svc_forward ALTER COLUMN dstsvc DROP NOT NULL; -ALTER TABLE cust_main ALTER COLUMN zip DROP NOT NULL; -ALTER TABLE h_cust_main ALTER COLUMN zip DROP NOT NULL; - -Or on Pg versions that don't support DROP NOT NULL (tested on 7.1 and 7.2 so far): -UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'srcsvc' OR attname = 'dstsvc' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'svc_forward' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_svc_forward' ) ); -UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'zip' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'cust_main' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_cust_main' ) ); - -If you created your database with a version before 1.4.2, dump database, edit: -- cust_main and h_cust_main: increase otaker from 8 to 32 -- cust_main and h_cust_main: change ss from char(11) to varchar(11) ( "character(11)" to "character varying(11)" ) -- cust_credit and h_cust_credit: increase otaker from 8 to 32 -- cust_pkg and h_cust_pkg: increase otaker from 8 to 32 -- cust_refund and h_cust_refund: increase otaker from 8 to 32 -- domain_record and h_domain_record: increase reczone from 80 to 255 -- domain_record and h_domain_record: change rectype from char to varchar ( "character(5)" to "character varying(5)" ) -- domain_record and h_domain_record: increase recdata from 80 to 255 -then reload - -mandatory again: - -make install-perl-modules to install the new libraries and CLI utilities -run "freeside-upgrade username" to create the remaining new tables and columns - -optionally: - -CREATE INDEX cust_main4 ON cust_main ( daytime ); -CREATE INDEX cust_main5 ON cust_main ( night ); -CREATE INDEX cust_main6 ON cust_main ( fax ); -CREATE INDEX cust_main7 ON cust_main ( refnum ); -CREATE INDEX cust_main8 ON cust_main ( county ); -CREATE INDEX cust_main9 ON cust_main ( state ); -CREATE INDEX cust_main10 ON cust_main ( country ); -CREATE INDEX cust_main11 ON cust_main ( ship_last ); -CREATE INDEX cust_main12 ON cust_main ( ship_company ); -CREATE INDEX cust_main13 ON cust_main ( ship_daytime ); -CREATE INDEX cust_main14 ON cust_main ( ship_night ); -CREATE INDEX cust_main15 ON cust_main ( ship_fax ); -CREATE INDEX agent2 ON agent ( disabled ); -CREATE INDEX part_bill_event2 ON part_bill_event ( disabled ); -CREATE INDEX cust_pay4 ON cust_pay (_date); -CREATE INDEX part_referral1 ON part_referral ( disabled ); -CREATE INDEX part_pkg2 ON part_pkg ( promo_code ); -CREATE INDEX h_part_pkg2 ON h_part_pkg ( promo_code ); - -</pre> diff --git a/httemplate/docs/upgrade7.html b/httemplate/docs/upgrade7.html deleted file mode 100644 index d9dcfe2ae..000000000 --- a/httemplate/docs/upgrade7.html +++ /dev/null @@ -1,24 +0,0 @@ -<head> - <title>Upgrading to 1.3.1</title> -</head> -<body> -<h1>Upgrading to 1.3.1 from 1.3.0</h1> -<ul> - <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first. - <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first. - <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first. - <li>If migrating from less than 1.2.2, see these <a href="upgrade4.html">instructions</a> first. - <li>If migrating from less than 1.2.3, see these <a href="upgrade5.html">instructions</a> first. - <li>If migrating from less than 1.3.0, see these <a href="upgrade6.html">instructions</a> first. - <li>Back up your data and current Freeside installation. - <li>Copy or symlink htdocs to the new copy. - <li>Change to the FS directory in the new tarball, and build and install the - Perl modules: - <pre> -$ cd FS/ -$ perl Makefile.PL -$ make -$ su -# make install UNINST=1</pre> - <li>Run bin/dbdef-create. -</body> diff --git a/httemplate/docs/upgrade8.html b/httemplate/docs/upgrade8.html deleted file mode 100644 index 9ca7cb7b9..000000000 --- a/httemplate/docs/upgrade8.html +++ /dev/null @@ -1,394 +0,0 @@ -<head> - <title>Upgrading to 1.4.0</title> -</head> -<body> -<h1>Upgrading to 1.4.0 from 1.3.1</h1> -<ul> - <li>If migrating from less than 1.3.1, see these <a href="upgrade7.html">instructions</a> first. - <li><font size="+2" color="#ff0000">Backup your database and current Freeside installation.</font> (with <a href="http://www.ca.postgresql.org/devel-corner/docs/postgres/backup.html">PostgreSQL</a>) (with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Backup">MySQL</a>) - <li><a href="http://perl.apache.org/">mod_perl</a> is now required. - <li>Install <a href="http://search.cpan.org/search?dist=Time-Duration">Time-Duration</a>, <a href="http://search.cpan.org/search?dist=Tie-IxHash">Tie-IxHash</a> and <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML-Widgets-SelectLayers</a> (minimum version 0.02). - <li>Install <a href="http://www.apache-asp.org/">Apache::ASP</a> or <a href="http://www.masonhq.com/">HTML::Mason</a> (use version 1.0x - Freeside is not yet compatible with version 1.1x). - <li>Install <a href="http://rsync.samba.org/">rsync</a> -</ul> -<table> - <tr> - <th>Apache::ASP</th><th>Mason</th> - </tr> - <tr> - <td><ul> - <li>Run <tt>make aspdocs</tt> - <li>Copy <tt>aspdocs/</tt> to your web server's document space. - <li>Create a <a href="http://www.apache-asp.org/config.html#Global">Global</a> directory, such as <tt>/usr/local/etc/freeside/asp-global/</tt> - <li>Copy <tt>htetc/global.asa</tt> to the Global directory. - <li>Configure Apache for the Global directory and to execute .cgi files using Apache::ASP. For example: -<font size="-1"><pre> -<Directory /usr/local/apache/htdocs/freeside-asp> -<Files ~ (\.cgi)> -AddHandler perl-script .cgi -PerlHandler Apache::ASP -</Files> -<Perl> -$MLDBM::RemoveTaint = 1; -</Perl> -PerlSetVar Global /usr/local/etc/freeside/asp-global/ -</Directory> -</pre></font> - </ul></td> - <td><ul> - <li>(use version 1.0x - Freeside is not yet compatible with version 1.1x) - <li>Run <tt>make masondocs</tt> - <li>Copy <tt>masondocs/</tt> to your web server's document space. - <li>Copy <tt>htetc/handler.pl</tt> to your web server's configuration directory. - <li>Edit <tt>handler.pl</tt> and set an appropriate <tt>data_dir</tt>, such as <tt>/usr/local/etc/freeside/mason-data</tt> - <li>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason. For example: -<font size="-1"><pre> -<Directory /usr/local/apache/htdocs/freeside-mason> -<Files ~ (\.cgi)> -AddHandler perl-script .cgi -PerlHandler HTML::Mason -</Files> -<Perl> -require "/usr/local/apache/conf/handler.pl"; -</Perl> -</Directory> -</pre></font> - </ul></td> - </tr> -</table> -<ul> - <li>Build and install the Perl modules: - <pre> -$ su -# make install-perl-modules</pre> - <li>Apply the following changes to your database: -<pre> -CREATE TABLE svc_forward ( - svcnum int NOT NULL, - srcsvc int NOT NULL, - dstsvc int NOT NULL, - dst varchar(80), - PRIMARY KEY (svcnum) -); -ALTER TABLE part_svc ADD svc_forward__srcsvc varchar(80) NULL; -ALTER TABLE part_svc ADD svc_forward__srcsvc_flag char(1) NULL; -ALTER TABLE part_svc ADD svc_forward__dstsvc varchar(80) NULL; -ALTER TABLE part_svc ADD svc_forward__dstsvc_flag char(1) NULL; -ALTER TABLE part_svc ADD svc_forward__dst varchar(80) NULL; -ALTER TABLE part_svc ADD svc_forward__dst_flag char(1) NULL; - -CREATE TABLE cust_credit_bill ( - creditbillnum int primary key, - crednum int not null, - invnum int not null, - _date int not null, - amount decimal(10,2) not null -); - -CREATE TABLE cust_bill_pay ( - billpaynum int primary key, - invnum int not null, - paynum int not null, - _date int not null, - amount decimal(10,2) not null -); - -CREATE TABLE cust_credit_refund ( - creditrefundnum int primary key, - crednum int not null, - refundnum int not null, - _date int not null, - amount decimal(10,2) not null -); - -CREATE TABLE part_svc_column ( - columnnum int primary key, - svcpart int not null, - columnname varchar(64) not null, - columnvalue varchar(80) null, - columnflag char(1) null -); - -CREATE TABLE queue ( - jobnum int primary key, - job text not null, - _date int not null, - status varchar(80) not null, - statustext text null, - svcnum int null -); -CREATE INDEX queue1 ON queue ( svcnum ); -CREATE INDEX queue2 ON queue ( status ); - -CREATE TABLE queue_arg ( - argnum int primary key, - jobnum int not null, - arg text null -); -CREATE INDEX queue_arg1 ON queue_arg ( jobnum ); - -CREATE TABLE queue_depend ( - dependnum int primary key, - jobnum int not null, - depend_jobnum int not null -); -CREATE INDEX queue_depend1 ON queue_depend ( jobnum ); -CREATE INDEX queue_depend2 ON queue_depend ( depend_jobnum ); - -CREATE TABLE part_pop_local ( - localnum int primary key, - popnum int not null, - city varchar(80) null, - state char(2) null, - npa char(3) not null, - nxx char(3) not null -); -CREATE UNIQUE INDEX part_pop_local1 ON part_pop_local ( npa, nxx ); - -CREATE TABLE cust_bill_event ( - eventnum int primary key, - invnum int not null, - eventpart int not null, - _date int not null, - status varchar(80) not null, - statustext text -); -CREATE UNIQUE INDEX cust_bill_event1 ON cust_bill_event ( eventpart, invnum ); -CREATE INDEX cust_bill_event2 ON cust_bill_event ( invnum ); - -CREATE TABLE part_bill_event ( - eventpart int primary key, - payby char(4) not null, - event varchar(80) not null, - eventcode text null, - seconds int null, - weight int not null, - plan varchar(80) null, - plandata text null, - disabled char(1) null -); -CREATE INDEX part_bill_event1 ON part_bill_event ( payby ); - -CREATE TABLE export_svc ( - exportsvcnum int primary key, - exportnum int not null, - svcpart int not null -); -CREATE UNIQUE INDEX export_svc1 ON export_svc ( exportnum, svcpart ); -CREATE INDEX export_svc2 ON export_svc ( exportnum ); -CREATE INDEX export_svc3 ON export_svc ( svcpart ); - -CREATE TABLE part_export ( - exportnum int primary key, - machine varchar(80) not null, - exporttype varchar(80) not null, - nodomain char(1) NULL -); -CREATE INDEX part_export1 ON part_export ( machine ); -CREATE INDEX part_export2 ON part_export ( exporttype ); - -CREATE TABLE part_export_option ( - optionnum int primary key, - exportnum int not null, - optionname varchar(80) not null, - optionvalue text NULL -); -CREATE INDEX part_export_option1 ON part_export_option ( exportnum ); -CREATE INDEX part_export_option2 ON part_export_option ( optionname ); - -CREATE TABLE radius_usergroup ( - usergroupnum int primary key, - svcnum int not null, - groupname varchar(80) not null -); -CREATE INDEX radius_usergroup1 ON radius_usergroup ( svcnum ); -CREATE INDEX radius_usergroup2 ON radius_usergroup ( groupname ); - -CREATE TABLE msgcat ( - msgnum int primary key, - msgcode varchar(80) not null, - locale varchar(16) not null, - msg text not null -); -CREATE INDEX msgcat1 ON msgcat ( msgcode, locale ); - -CREATE TABLE cust_tax_exempt ( - exemptnum int primary key, - custnum int not null, - taxnum int not null, - year int not null, - month int not null, - amount decimal(10,2) -); -CREATE UNIQUE INDEX cust_tax_exempt1 ON cust_tax_exempt ( taxnum, year, month ); - -ALTER TABLE svc_acct ADD domsvc integer NULL; -ALTER TABLE part_svc ADD svc_acct__domsvc varchar(80) NULL; -ALTER TABLE part_svc ADD svc_acct__domsvc_flag char(1) NULL; -ALTER TABLE svc_domain ADD catchall integer NULL; -ALTER TABLE cust_main ADD referral_custnum integer NULL; -ALTER TABLE cust_main ADD comments text NULL; -ALTER TABLE cust_pay ADD custnum integer; -ALTER TABLE cust_pay_batch ADD paybatchnum integer; -ALTER TABLE cust_refund ADD custnum integer; -ALTER TABLE cust_pkg ADD manual_flag char(1) NULL; -ALTER TABLE part_pkg ADD plan varchar(80) NULL; -ALTER TABLE part_pkg ADD plandata text NULL; -ALTER TABLE part_pkg ADD setuptax char(1) NULL; -ALTER TABLE part_pkg ADD recurtax char(1) NULL; -ALTER TABLE part_pkg ADD disabled char(1) NULL; -ALTER TABLE part_svc ADD disabled char(1) NULL; -ALTER TABLE cust_bill ADD closed char(1) NULL; -ALTER TABLE cust_pay ADD closed char(1) NULL; -ALTER TABLE cust_credit ADD closed char(1) NULL; -ALTER TABLE cust_refund ADD closed char(1) NULL; -ALTER TABLE cust_bill_event ADD status varchar(80); -ALTER TABLE cust_bill_event ADD statustext text NULL; -ALTER TABLE svc_acct ADD sec_phrase varchar(80) NULL; -ALTER TABLE part_svc ADD svc_acct__sec_phrase varchar(80) NULL; -ALTER TABLE part_svc ADD svc_acct__sec_phrase_flag char(1) NULL; -ALTER TABLE part_pkg ADD taxclass varchar(80) NULL; -ALTER TABLE cust_main_county ADD taxclass varchar(80) NULL; -ALTER TABLE cust_main_county ADD exempt_amount decimal(10,2); -CREATE INDEX cust_main3 ON cust_main ( referral_custnum ); -CREATE INDEX cust_credit_bill1 ON cust_credit_bill ( crednum ); -CREATE INDEX cust_credit_bill2 ON cust_credit_bill ( invnum ); -CREATE INDEX cust_bill_pay1 ON cust_bill_pay ( invnum ); -CREATE INDEX cust_bill_pay2 ON cust_bill_pay ( paynum ); -CREATE INDEX cust_credit_refund1 ON cust_credit_refund ( crednum ); -CREATE INDEX cust_credit_refund2 ON cust_credit_refund ( refundnum ); -CREATE UNIQUE INDEX cust_pay_batch_pkey ON cust_pay_batch ( paybatchnum ); -CREATE UNIQUE INDEX part_svc_column1 ON part_svc_column ( svcpart, columnname ); -CREATE INDEX cust_pay2 ON cust_pay ( paynum ); -CREATE INDEX cust_pay3 ON cust_pay ( custnum ); -CREATE INDEX cust_pay4 ON cust_pay ( paybatch ); -</pre> - - <li>If you are using PostgreSQL, apply the following changes to your database: -<pre> -CREATE UNIQUE INDEX agent_pkey ON agent ( agentnum ); -CREATE UNIQUE INDEX agent_type_pkey ON agent_type ( typenum ); -CREATE UNIQUE INDEX cust_bill_pkey ON cust_bill ( invnum ); -CREATE UNIQUE INDEX cust_credit_pkey ON cust_credit ( crednum ); -CREATE UNIQUE INDEX cust_main_pkey ON cust_main ( custnum ); -CREATE UNIQUE INDEX cust_main_county_pkey ON cust_main_county ( taxnum ); -CREATE UNIQUE INDEX cust_main_invoice_pkey ON cust_main_invoice ( destnum ); -CREATE UNIQUE INDEX cust_pay_pkey ON cust_pay ( paynum ); -CREATE UNIQUE INDEX cust_pkg_pkey ON cust_pkg ( pkgnum ); -CREATE UNIQUE INDEX cust_refund_pkey ON cust_refund ( refundnum ); -CREATE UNIQUE INDEX cust_svc_pkey ON cust_svc ( svcnum ); -CREATE UNIQUE INDEX domain_record_pkey ON domain_record ( recnum ); -CREATE UNIQUE INDEX nas_pkey ON nas ( nasnum ); -CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg ( pkgpart ); -CREATE UNIQUE INDEX part_referral_pkey ON part_referral ( refnum ); -CREATE UNIQUE INDEX part_svc_pkey ON part_svc ( svcpart ); -CREATE UNIQUE INDEX port_pkey ON port ( portnum ); -CREATE UNIQUE INDEX prepay_credit_pkey ON prepay_credit ( prepaynum ); -CREATE UNIQUE INDEX session_pkey ON session ( sessionnum ); -CREATE UNIQUE INDEX svc_acct_pkey ON svc_acct ( svcnum ); -CREATE UNIQUE INDEX svc_acct_pop_pkey ON svc_acct_pop ( popnum ); -CREATE UNIQUE INDEX svc_acct_sm_pkey ON svc_acct_sm ( svcnum ); -CREATE UNIQUE INDEX svc_domain_pkey ON svc_domain ( svcnum ); -CREATE UNIQUE INDEX svc_www_pkey ON svc_www ( svcnum ); -</pre> - <li>If you wish to enable service/shipping addresses, apply the following - changes to your database: -<pre> -ALTER TABLE cust_main ADD COLUMN ship_last varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_first varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_company varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_address1 varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_address2 varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_city varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_county varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_state varchar(80) NULL; -ALTER TABLE cust_main ADD COLUMN ship_zip varchar(10) NULL; -ALTER TABLE cust_main ADD COLUMN ship_country char(2) NULL; -ALTER TABLE cust_main ADD COLUMN ship_daytime varchar(20) NULL; -ALTER TABLE cust_main ADD COLUMN ship_night varchar(20) NULL; -ALTER TABLE cust_main ADD COLUMN ship_fax varchar(12) NULL; -CREATE INDEX cust_main4 ON cust_main ( ship_last ); -CREATE INDEX cust_main5 ON cust_main ( ship_company ); -</pre> - <li>If you are using the signup server, reinstall it according to the <a href="signup.html">instructions</a>. The 1.3.x signup server is not compatible with 1.4.x. - <li>Run <tt>bin/dbdef-create <i>username</i></tt> - <li>If you have svc_acct_sm records or service definitions: - <ul> - <li>Create a service definition with table svc_forward - <li>Run <tt>bin/fs-migrate-svc_acct_sm <i>username</i></tt> - </ul> - <li>Or if you just have svc_acct records: - <ul> - <li>Order and provision a package for your default domain and note down the <b>Service #</b> or <i>svcnum</i>. - <li><tt>UPDATE svc_acct SET domsvc = </tt><i>svcnum</i> - <li>Update your service definitions to have default (or fixed) <b>domsvc</b>. - </ul> - <li>Run <tt>bin/fs-migrate-payref<i>username</i></tt> - <li>Run <tt>bin/fs-migrate-part_svc<i>username</i></tt> - <li><b>After running bin/fs-migrate-payref</b>, apply the following changes to your database: - <table border><tr><th>PostgreSQL</th><th>MySQL, others</th></tr> -<tr><td> -<font size=-1><pre> -CREATE TABLE cust_pay_temp ( - paynum int primary key, - custnum int not null, - paid decimal(10,2) not null, - _date int null, - payby char(4) not null, - payinfo varchar(16) null, - paybatch varchar(80) null, - closed char(1) null -); -INSERT INTO cust_pay_temp SELECT paynum, custnum, paid, _date, payby, payinfo, paybatch, closed FROM cust_pay; -DROP TABLE cust_pay; -ALTER TABLE cust_pay_temp RENAME TO cust_pay; -CREATE UNIQUE INDEX cust_pay1 ON cust_pay (paynum); -CREATE TABLE cust_refund_temp ( - refundnum int primary key, - custnum int not null, - _date int null, - refund decimal(10,2) not null, - otaker varchar(8) not null, - reason varchar(80) not null, - payby char(4) not null, - payinfo varchar(16) null, - paybatch varchar(80) null, - closed char(1) null -); -INSERT INTO cust_refund_temp SELECT refundnum, custnum, _date, refund, otaker, reason, payby, payinfo, '', closed from cust_refund; -DROP TABLE cust_refund; -ALTER TABLE cust_refund_temp RENAME TO cust_refund; -CREATE UNIQUE INDEX cust_refund1 ON cust_refund (refundnum); -</pre></font> -</td><td> -<font size=-1><pre> -ALTER TABLE cust_pay DROP COLUMN invnum; -ALTER TABLE cust_refund DROP COLUMN crednum; -</pre></font> -</td></tr></table> - <li><b>IMPORTANT: After applying the second set of database changes</b>, run <tt>bin/dbdef-create <i>username</i></tt> again. - <li><b>IMPORTANT</b>: run <tt>bin/create-history-tables <i>username</i></tt> - <li><b>IMPORTANT: After running bin/create-history-tables</b>, run <tt>bin/dbdef-create <i>username</i></tt> again. - <li>As the freeside UNIX user, run <tt>bin/populate-msgcat <i>username</i></tt -> to populate the message catalog -<!-- <li>set the <a href="../config/config.cgi#username_policy">user_policy configuration value</a> as appropriate for your site. --> - <li>set the <a href="../config/config.cgi#locale">locale configuration value</a> to en_US. - <li>the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated. Set the <a href="../config/config.cgi#defaultrecords">defaultrecords configuration value</a> instead. - <li>Create the `/usr/local/etc/freeside/cache.<i>datasrc</i>' directory - (owned by the freeside user). - <li>freeside-queued was installed with the Perl modules. Start it now and ensure that is run upon system startup. - <li>Set appropriate <a href="../browse/part_bill_event.cgi">invoice events</a> for your site. At the very least, you'll want to set some invoice events "<i>After 0 days</i>": a <i>BILL</i> invoice event to print invoices, a <i>CARD</i> invoice event to batch or run cards real-time, and a <i>COMP</i> invoice event to "pay" complimentary customers. If you were using the <i>-i</i> option to <a href="man/bin/freeside-bill.html">freeside-bill</a> it should be removed. - <li>Use <a href="man/bin/freeside-daily.html">freeside-daily</a> instead of <a href="man/bin/freeside-bill.html">freeside-bill</a>. - <li>If you would like Freeside to notify your customers when their credit - cards and other billing arrangements are about to expire, arrange for - <b>freeside-expiration-alerter</b> to be run daily by cron or similar - facility. The message it sends can be configured from the - <u>Configuration</u> choice of the main menu as <u>alerter_template</u>. - <li>Export has been rewritten. If you were using the icradiusmachines, - icradius_mysqldest, icradius_mysqlsource, or icradius_secrets files, add - an appropriate "sqlradius" export to all relevant Service Definitions - instead. Use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or - point the "sqlradius" export directly at your external ICRADIUS or FreeRADIUS - database (or through an SSL-necrypting proxy...) -</ul> -</body> diff --git a/httemplate/docs/upgrade9.html b/httemplate/docs/upgrade9.html deleted file mode 100644 index 6a8fd965d..000000000 --- a/httemplate/docs/upgrade9.html +++ /dev/null @@ -1,28 +0,0 @@ -<head> - <title>Upgrading to 1.4.1</title> -</head> -<body> -<h1>Upgrading to 1.4.1 from 1.4.0</h1> -<ul> - <li>If migrating from less than 1.4.0, see these <a href="upgrade8.html">instructions</a> first. - <li>Back up your data and current Freeside installation. - <li>Run <code>make aspdocs</code> or <code>make masondocs</code>. - <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space. - <li>Run <code>make install-perl-modules</code>. - <li>Install <a href="http://search.cpan.org/search?dist=Net-SSH">Net::SSH</a> minimum version 0.07 - <li>Apply the following changes to your database: -<pre> -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 18, 'daytime', 'en_US', 'Day Phone' ); -INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 19, 'night', 'en_US', 'Night Phone' ); -</pre> - <li>Optionally, apply the following changes to your database (performance improvements): -<pre> -CREATE INDEX part_pkg1 ON part_pkg ( disabled ); -CREATE INDEX part_svc1 ON part_svc ( disabled ); -CREATE INDEX cust_bill2 ON cust_bill ( _date ); -</pre> - <li>If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump. - <li>If you will be doing bind exports you should make additional changes to your database. Follow the directions above to dump the database and change the reczone and recdata fields in the domain_record and h_domain_record tables from varchar(80) to varchar(255). - <li>If you made changes to your db schema from a dump as listed above run dbdef-create. - <li>Restart Apache and freeside-queued. -</body> diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi index 78dd0fafa..69bbb9b22 100755 --- a/httemplate/edit/REAL_cust_pkg.cgi +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -1,177 +1,183 @@ -<% - -my $error =''; -my $pkgnum = ''; -if ( $cgi->param('error') ) { - $error = $cgi->param('error'); - $pkgnum = $cgi->param('pkgnum'); - if ( $error eq '_bill_areyousure' ) { - my $bill = $cgi->param('bill'); - $error = "You are attempting to set the next bill date to $bill, which is - in the past. This will charge the customer for the interval - from $bill until now. Are you sure you want to do this? ". - '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">'; - } -} else { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/ or die "no pkgnum"; - $pkgnum = $1; -} - -#get package record -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); -die "No package!" unless $cust_pkg; -my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); - -if ( $error ) { - #$cust_pkg->$_(str2time($cgi->param($_)) foreach qw(setup bill); - $cust_pkg->setup(str2time($cgi->param('setup'))); - $cust_pkg->bill(str2time($cgi->param('bill'))); - $cust_pkg->last_bill(str2time($cgi->param('last_bill'))); -} - -#my $custnum = $cust_pkg->getfield('custnum'); -%> - -<%= header('Customer package - Edit dates') %> -<% -#, menubar( -# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", -# 'Main Menu' => popurl(2) -#)); -%> +% +% +%my $error =''; +%my $pkgnum = ''; +%if ( $cgi->param('error') ) { +% $error = $cgi->param('error'); +% $pkgnum = $cgi->param('pkgnum'); +% if ( $error eq '_bill_areyousure' ) { +% my $bill = $cgi->param('bill'); +% $error = "You are attempting to set the next bill date to $bill, which is +% in the past. This will charge the customer for the interval +% from $bill until now. Are you sure you want to do this? ". +% '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">'; +% } +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "no pkgnum"; +% $pkgnum = $1; +%} +% +%#get package record +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%die "No package!" unless $cust_pkg; +%my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); +% +%if ( $error ) { +% #$cust_pkg->$_(str2time($cgi->param($_)) foreach qw(setup bill); +% $cust_pkg->setup(str2time($cgi->param('setup'))); +% $cust_pkg->bill(str2time($cgi->param('bill'))); +% $cust_pkg->last_bill(str2time($cgi->param('last_bill'))); +%} +% +%#my $custnum = $cust_pkg->getfield('custnum'); +% + + +<% include("/elements/header.html",'Customer package - Edit dates') %> +% +%#, menubar( +%# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", +%# 'Main Menu' => popurl(2) +%#)); +% + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> +% +% +%#print info +%my($susp,$cancel,$expire)=( +% $cust_pkg->getfield('susp'), +% $cust_pkg->getfield('cancel'), +% $cust_pkg->getfield('expire'), +%); +%my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); +%my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); +%my $otaker = $cust_pkg->getfield('otaker'); +% +% -<% - -#print info -my($susp,$cancel,$expire)=( - $cust_pkg->getfield('susp'), - $cust_pkg->getfield('cancel'), - $cust_pkg->getfield('expire'), -); -my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); -my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); -my $otaker = $cust_pkg->getfield('otaker'); - -%> <FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> - -<% if ( $error ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $error %></FONT> -<% } %> - -<% - -#my $format = "%c %z (%Z)"; -my $format = "%m/%d/%Y %T %z (%Z)"; - -#false laziness w/view/cust_main/packages.html -#my( $billed_or_prepaid, -my( $last_bill_or_renewed, $next_bill_or_prepaid_until ); -unless ( $part_pkg->is_prepaid ) { - #$billed_or_prepaid = 'billed'; - $last_bill_or_renewed = 'Last bill'; - $next_bill_or_prepaid_until = 'Next bill'; -} else { - #$billed_or_prepaid = 'prepaid'; - $last_bill_or_renewed = 'Renewed'; - $next_bill_or_prepaid_until = 'Prepaid until'; -} - -%> - -<%= ntable("#cccccc",2) %> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +% if ( $error ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT> +% } +% +% +%#my $format = "%c %z (%Z)"; +%my $format = "%m/%d/%Y %T %z (%Z)"; +% +%#false laziness w/view/cust_main/packages.html +%#my( $billed_or_prepaid, +%my( $last_bill_or_renewed, $next_bill_or_prepaid_until ); +%unless ( $part_pkg->is_prepaid ) { +% #$billed_or_prepaid = 'billed'; +% $last_bill_or_renewed = 'Last bill'; +% $next_bill_or_prepaid_until = 'Next bill'; +%} else { +% #$billed_or_prepaid = 'prepaid'; +% $last_bill_or_renewed = 'Renewed'; +% $next_bill_or_prepaid_until = 'Prepaid until'; +%} +% +% + + +<% ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Package number</TD> - <TD BGCOLOR="#ffffff"><%= $pkgnum %></TD> + <TD BGCOLOR="#ffffff"><% $pkgnum %></TD> </TR> <TR> <TD ALIGN="right">Package</TD> - <TD BGCOLOR="#ffffff"><%= $pkg %></TD> + <TD BGCOLOR="#ffffff"><% $pkg %></TD> </TR> <TR> <TD ALIGN="right">Comment</TD> - <TD BGCOLOR="#ffffff"><%= $comment %></TD> + <TD BGCOLOR="#ffffff"><% $comment %></TD> </TR> <TR> <TD ALIGN="right">Order taker</TD> - <TD BGCOLOR="#ffffff"><%= $otaker %></TD> + <TD BGCOLOR="#ffffff"><% $otaker %></TD> </TR> <TR> <TD ALIGN="right">Setup date</TD> <TD> - <INPUT TYPE="text" NAME="setup" SIZE=32 ID="setup_text" VALUE="<%= ( $setup ? time2str($format, $setup) : "" ) %>"> + <INPUT TYPE="text" NAME="setup" SIZE=32 ID="setup_text" VALUE="<% ( $setup ? time2str($format, $setup) : "" ) %>"> <IMG SRC="../images/calendar.png" ID="setup_button" STYLE="cursor: pointer" TITLE="Select date"> </TD> </TR> <TR> - <TD ALIGN="right"><%= $last_bill_or_renewed %> date</TD> + <TD ALIGN="right"><% $last_bill_or_renewed %> date</TD> <TD> - <INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="<%= ( $cust_pkg->last_bill ? time2str($format, $cust_pkg->last_bill) : "" ) %>"> + <INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="<% ( $cust_pkg->last_bill ? time2str($format, $cust_pkg->last_bill) : "" ) %>"> <IMG SRC="../images/calendar.png" ID="last_bill_button" STYLE="cursor: pointer" TITLE="Select date"> </TD> </TR> <TR> - <TD ALIGN="right"><%= $next_bill_or_prepaid_until %> date</TD> + <TD ALIGN="right"><% $next_bill_or_prepaid_until %> date</TD> <TD> - <INPUT TYPE="text" NAME="bill" SIZE=32 ID="bill_text" VALUE="<%= ( $bill ? time2str($format, $bill) : "" ) %>"> + <INPUT TYPE="text" NAME="bill" SIZE=32 ID="bill_text" VALUE="<% ( $bill ? time2str($format, $bill) : "" ) %>"> <IMG SRC="../images/calendar.png" ID="bill_button" STYLE="cursor: pointer" TITLE="Select date"> </TD> </TR> +% if ( $susp ) { - <% if ( $susp ) { %> <TR> <TD ALIGN="right">Suspension date</TD> - <TD BGCOLOR="#ffffff"><%= time2str($format, $susp) %></TD> + <TD BGCOLOR="#ffffff"><% time2str($format, $susp) %></TD> </TR> - <% } %> +% } + <TR> <TD ALIGN="right">Expiration date</TD> <TD> - <INPUT TYPE="text" NAME="expire" SIZE=32 ID="expire_text" VALUE="<%= ( $expire ? time2str($format, $expire) : "" ) %>"> + <INPUT TYPE="text" NAME="expire" SIZE=32 ID="expire_text" VALUE="<% ( $expire ? time2str($format, $expire) : "" ) %>"> <IMG SRC="../images/calendar.png" ID="expire_button" STYLE="cursor: pointer" TITLE="Select date"> <BR><FONT SIZE=-1>(will <b>cancel</b> this package when the date is reached)</FONT> </TD> </TR> +% if ( $cancel ) { - <% if ( $cancel ) { %> <TR> <TD ALIGN="right">Cancellation date</TD> - <TD BGCOLOR="#ffffff"><%= time2str($format, $cancel) %></TD> + <TD BGCOLOR="#ffffff"><% time2str($format, $cancel) %></TD> </TR> - <% } %> +% } + </TABLE> <SCRIPT TYPE="text/javascript"> -<% - my @cal = qw( setup bill expire ); - push @cal, 'last_bill' - if $cust_pkg->dbdef_table->column('last_bill'); - foreach my $cal (@cal) { -%> +% +% my @cal = qw( setup bill expire ); +% push @cal, 'last_bill' +% if $cust_pkg->dbdef_table->column('last_bill'); +% foreach my $cal (@cal) { +% + Calendar.setup({ - inputField: "<%= $cal %>_text", + inputField: "<% $cal %>_text", ifFormat: "%m/%d/%Y", - button: "<%= $cal %>_button", + button: "<% $cal %>_button", align: "BR" }); -<% } %> +% } + </SCRIPT> <BR><INPUT TYPE="submit" VALUE="Apply Changes"> </FORM> diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html new file mode 100644 index 000000000..d447512c2 --- /dev/null +++ b/httemplate/edit/access_group.html @@ -0,0 +1,46 @@ +<% include( 'elements/edit.html', + 'name' => 'Internal Access Group', + 'table' => 'access_group', + 'labels' => { + 'groupnum' => 'Group number', + 'groupname' => 'Group name', + }, + + 'viewall_dir' => 'browse', + + 'html_bottom' => + sub { + my $access_group = shift; + + "<BR>Group virtualized to customers of agents:<BR>". + ntable("#cccccc",2). + '<TR><TD>'. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_group, + 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + 'name_col' => 'agent', + 'target_link' => $p.'edit/agent.cgi?', + 'disable-able' => 1, + ). + '</TR></TD></TABLE>'. + + "<BR>Group rights:<BR>". + ntable("#cccccc",2). + '<TR><TD>'. + include( '/elements/checkboxes-table-name.html', + 'source_obj' => $access_group, + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => + 'FS::access_group', + }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + ). + '</TR></TD></TABLE>' + + ; + }, + ) +%> diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html new file mode 100644 index 000000000..065e60c4b --- /dev/null +++ b/httemplate/edit/access_user.html @@ -0,0 +1,44 @@ +<% include( 'elements/edit.html', + 'name' => 'Internal User', + 'table' => 'access_user', + 'fields' => [ + 'username', + { field=>'_password', type=>'password' }, + { field=>'_password2', type=>'password' }, + 'last', + 'first', + { field=>'disabled', type=>'checkbox', value=>'Y' }, + ], + 'labels' => { + 'usernum' => 'User number', + 'username' => 'Username', + '_password' => 'Password', + '_password2'=> 'Re-enter Password', + 'last' => 'Last name', + 'first' => 'First name', + 'disabled' => 'Disable employee', + }, + 'edit_callback' => sub { my( $c, $o ) = @_; + $o->set('_password', ''); + }, + 'viewall_dir' => 'browse', + 'html_bottom' => + sub { + my $access_user = shift; + + '<BR>Internal Access Groups<BR>'. + ntable("#cccccc",2). + '<TR><TD>'. + 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, + ). + '</TR></TD></TABLE>' + ; + }, + ) +%> diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi index cb64ad8cd..ce514a680 100755 --- a/httemplate/edit/agent.cgi +++ b/httemplate/edit/agent.cgi @@ -1,109 +1,115 @@ -<% - -my $agent; -if ( $cgi->param('error') ) { - $agent = new FS::agent ( { - map { $_, scalar($cgi->param($_)) } fields('agent') - } ); -} elsif ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $agent = qsearchs( 'agent', { 'agentnum' => $1 } ); -} else { #adding - $agent = new FS::agent {}; -} -my $action = $agent->agentnum ? 'Edit' : 'Add'; -my $hashref = $agent->hashref; - -my $conf = new FS::Conf; - -%> - -<%= header("$action Agent", menubar( +% +% +%my $agent; +%if ( $cgi->param('error') ) { +% $agent = new FS::agent ( { +% map { $_, scalar($cgi->param($_)) } fields('agent') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $agent = qsearchs( 'agent', { 'agentnum' => $1 } ); +%} else { #adding +% $agent = new FS::agent {}; +%} +%my $action = $agent->agentnum ? 'Edit' : 'Add'; +%my $hashref = $agent->hashref; +% +%my $conf = new FS::Conf; +% +% + + +<% include("/elements/header.html","$action Agent", menubar( 'Main Menu' => $p, 'View all agents' => $p. 'browse/agent.cgi', )) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> -<FORM ACTION="<%=popurl(1)%>process/agent.cgi" METHOD=POST> -<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $hashref->{agentnum} %>"> -Agent #<%= $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %> +<FORM ACTION="<%popurl(1)%>process/agent.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $hashref->{agentnum} %>"> +Agent #<% $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %> -<%= &ntable("#cccccc", 2, '') %> +<% &ntable("#cccccc", 2, '') %> <TR> <TH ALIGN="right">Agent</TH> - <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<%= $hashref->{agent} %>"></TD> + <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<% $hashref->{agent} %>"></TD> </TR> <TR> <TH ALIGN="right">Agent type</TH> <TD><SELECT NAME="typenum" SIZE=1> - - <% foreach my $agent_type (qsearch('agent_type',{})) { %> - <OPTION VALUE="<%= $agent_type->typenum %>"<%= ( $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ) ) ? ' SELECTED' : '' %>> - <%= $agent_type->getfield('typenum') %>: <%= $agent_type->getfield('atype') %> - <% } %> +% foreach my $agent_type (qsearch('agent_type',{})) { + + <OPTION VALUE="<% $agent_type->typenum %>"<% ( $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ) ) ? ' SELECTED' : '' %>> + <% $agent_type->getfield('typenum') %>: <% $agent_type->getfield('atype') %> +% } + </SELECT></TD> </TR> <TR> <TD ALIGN="right">Disable</TD> - <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD> + <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD> </TR> <TR> <TD ALIGN="right"><!--Frequency--></TD> - <TD><INPUT TYPE="hidden" NAME="freq" VALUE="<%= $hashref->{freq} %>"></TD> + <TD><INPUT TYPE="hidden" NAME="freq" VALUE="<% $hashref->{freq} %>"></TD> </TR> <TR> <TD ALIGN="right"><!--Program--></TD> - <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<%= $hashref->{prog} %>"></TD> + <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<% $hashref->{prog} %>"></TD> </TR> +% if ( $conf->config('ticket_system') ) { +% my $default_queueid = $conf->config('ticket_system-default_queueid'); +% my $default_queue = FS::TicketSystem->queue($default_queueid); +% $default_queue = "(default) $default_queueid: $default_queue" +% if $default_queueid; +% my %queues = FS::TicketSystem->queues(); +% my @queueids = sort { $a <=> $b } keys %queues; +% - <% if ( $conf->config('ticket_system') ) { - my $default_queueid = $conf->config('ticket_system-default_queueid'); - my $default_queue = FS::TicketSystem->queue($default_queueid); - $default_queue = "(default) $default_queueid: $default_queue" - if $default_queueid; - my %queues = FS::TicketSystem->queues(); - my @queueids = sort { $a <=> $b } keys %queues; - %> <TR> <TD ALIGN="right">Ticketing queue</TD> <TD> <SELECT NAME="ticketing_queueid"> - <OPTION VALUE=""><%= $default_queue %> - <% foreach my $queueid ( @queueids ) { %> - <OPTION VALUE="<%= $queueid %>" <%= $agent->ticketing_queueid == $queueid ? ' SELECTED' : '' %>><%= $queueid %>: <%= $queues{$queueid} %> - <% } %> + <OPTION VALUE=""><% $default_queue %> +% foreach my $queueid ( @queueids ) { + + <OPTION VALUE="<% $queueid %>" <% $agent->ticketing_queueid == $queueid ? ' SELECTED' : '' %>><% $queueid %>: <% $queues{$queueid} %> +% } + </SELECT> </TD> </TR> - <% } %> +% } + <TR> <TD ALIGN="right">Agent interface username</TD> <TD> - <INPUT TYPE="text" NAME="username" VALUE="<%= $hashref->{username} %>"> + <INPUT TYPE="text" NAME="username" VALUE="<% $hashref->{username} %>"> </TD> </TR> <TR> <TD ALIGN="right">Agent interface password</TD> <TD> - <INPUT TYPE="text" NAME="_password" VALUE="<%= $hashref->{_password} %>"> + <INPUT TYPE="text" NAME="_password" VALUE="<% $hashref->{_password} %>"> </TD> </TR> </TABLE> -<BR><INPUT TYPE="submit" VALUE="<%= $hashref->{agentnum} ? "Apply changes" : "Add agent" %>"> +<BR><INPUT TYPE="submit" VALUE="<% $hashref->{agentnum} ? "Apply changes" : "Add agent" %>"> </FORM> </BODY> </HTML> diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html index 61d29e0e9..08a2fa6bf 100644 --- a/httemplate/edit/agent_payment_gateway.html +++ b/httemplate/edit/agent_payment_gateway.html @@ -1,57 +1,63 @@ -<% +% +% +%$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; +%my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +%die "agentnum $1 not found" unless $agent; +% +%#my @agent_payment_gateway; +%if ( $cgi->param('error') ) { +%} +% +%my $action = 'Add'; +% +% -$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; -my $agent = qsearchs('agent', { 'agentnum' => $1 } ); -die "agentnum $1 not found" unless $agent; -#my @agent_payment_gateway; -if ( $cgi->param('error') ) { -} - -my $action = 'Add'; - -%> - -<%= header("$action payment gateway override for ". $agent->agent, menubar( +<% include("/elements/header.html","$action payment gateway override for ". $agent->agent, menubar( 'Main Menu' => $p, #'View all payment gateways' => $p. 'browse/payment_gateway.html', 'View all agents' => $p. 'browse/agent.html', )) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> -<FORM ACTION="<%=popurl(1)%>process/agent_payment_gateway.html" METHOD=POST> -<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agent->agentnum %>"> +<FORM ACTION="<%popurl(1)%>process/agent_payment_gateway.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>"> Use gateway <SELECT NAME="gatewaynum"> -<% foreach my $payment_gateway ( - qsearch('payment_gateway', { 'disabled' => '' } ) - ) { -%> - <OPTION VALUE="<%= $payment_gateway->gatewaynum %>"><%= $payment_gateway->gateway_module %> (<%= $payment_gateway->gateway_username %>) -<% } %> +% foreach my $payment_gateway ( +% qsearch('payment_gateway', { 'disabled' => '' } ) +% ) { +% + + <OPTION VALUE="<% $payment_gateway->gatewaynum %>"><% $payment_gateway->gateway_module %> (<% $payment_gateway->gateway_username %>) +% } + </SELECT> <BR><BR> for <SELECT NAME="cardtype" MULTIPLE> -<% foreach my $cardtype ( - "", - "VISA card", - "MasterCard", - "Discover card", - "American Express card", - "Diner's Club/Carte Blanche", - "enRoute", - "JCB", - "BankCard", - "Switch", - "Solo", - 'ACH', -) { %> - <OPTION VALUE="<%= $cardtype %>"><%= $cardtype || '(Default fallback)' %> -<% } %> +% foreach my $cardtype ( +% "", +% "VISA card", +% "MasterCard", +% "Discover card", +% "American Express card", +% "Diner's Club/Carte Blanche", +% "enRoute", +% "JCB", +% "BankCard", +% "Switch", +% "Solo", +% 'ACH', +%) { + + <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %> +% } + </SELECT> <BR><BR> diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi index 5addbbd4c..5438e5c3b 100755 --- a/httemplate/edit/agent_type.cgi +++ b/httemplate/edit/agent_type.cgi @@ -1,75 +1,57 @@ -<% - -my($agent_type); -if ( $cgi->param('error') ) { - $agent_type = new FS::agent_type ( { - map { $_, scalar($cgi->param($_)) } fields('agent') - } ); -} elsif ( $cgi->keywords ) { #editing - my( $query ) = $cgi->keywords; - $query =~ /^(\d+)$/; - $agent_type=qsearchs('agent_type',{'typenum'=>$1}); -} else { #adding - $agent_type = new FS::agent_type {}; -} -my $action = $agent_type->typenum ? 'Edit' : 'Add'; - -%> - -<%= header("$action Agent Type", menubar( +% +% +%my($agent_type); +%if ( $cgi->param('error') ) { +% $agent_type = new FS::agent_type ( { +% map { $_, scalar($cgi->param($_)) } fields('agent') +% } ); +%} elsif ( $cgi->keywords ) { #editing +% my( $query ) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $agent_type=qsearchs('agent_type',{'typenum'=>$1}); +%} else { #adding +% $agent_type = new FS::agent_type {}; +%} +%my $action = $agent_type->typenum ? 'Edit' : 'Add'; +% +% +<% include("/elements/header.html","$action Agent Type", menubar( 'Main Menu' => "$p", 'View all agent types' => "${p}browse/agent_type.cgi", )) %> +% if ( $cgi->param('error') ) { -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } -<FORM ACTION="<%= popurl(1) %>process/agent_type.cgi" METHOD=POST> -<INPUT TYPE="hidden" NAME="typenum" VALUE="<%= $agent_type->typenum %>"> -Agent Type #<%= $agent_type->typenum || "(NEW)" %> -<BR><BR> + +<FORM ACTION="<% popurl(1) %>process/agent_type.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="typenum" VALUE="<% $agent_type->typenum %>"> +Agent Type #<% $agent_type->typenum || "(NEW)" %> +<BR> Agent Type -<INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="<%= $agent_type->atype %>"> +<INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="<% $agent_type->atype %>"> <BR><BR> Select which packages agents of this type may sell to customers<BR> - -<% 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) %><TR><TD> +<% 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, + + ) %> +</TD></TR></TABLE> +<BR> - <BR> - <INPUT TYPE="checkbox" NAME="pkgpart<%= $part_pkg->pkgpart %>" <%= - qsearchs('type_pkgs',{ - 'typenum' => $agent_type->typenum, - 'pkgpart' => $part_pkg->pkgpart, - }) - ? 'CHECKED ' - : '' - %> VALUE="ON"> - - <A HREF="<%= $p %>edit/part_pkg.cgi?<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkgpart %>: - <%= $part_pkg->pkg %> - <%= $part_pkg->comment %></A> - <%= $part_pkg->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> - -<% } %> - -<BR><BR> - -<INPUT TYPE="submit" VALUE="<%= $agent_type->typenum ? "Apply changes" : "Add agent type" %>"> +<INPUT TYPE="submit" VALUE="<% $agent_type->typenum ? "Apply changes" : "Add agent type" %>"> </FORM> - </BODY> -</HTML> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/bulk-cust_svc.html b/httemplate/edit/bulk-cust_svc.html index 332b5b67c..f2efc3ff9 100644 --- a/httemplate/edit/bulk-cust_svc.html +++ b/httemplate/edit/bulk-cust_svc.html @@ -1,4 +1,4 @@ -<%= header( 'Bulk customer service change', +<% include("/elements/header.html", 'Bulk customer service change', menubar( 'Main Menu' => $p, ), @@ -9,7 +9,7 @@ <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> -<%= include('/elements/progress-init.html', +<% include('/elements/progress-init.html', 'OneTrueForm', [qw( old_svcpart new_svcpart pkgpart )], 'process/bulk-cust_svc.cgi', @@ -18,65 +18,67 @@ %> <FORM NAME="OneTrueForm"> +% +% $cgi->param('svcpart') =~ /^(\d+)$/ +% or die "illegal svcpart: ". $cgi->param('svcpart'); +% +% my $old_svcpart = $1; +% my $src_part_svc = qsearchs('part_svc', { 'svcpart' => $old_svcpart } ) +% or die "unknown svcpart: $old_svcpart"; +% -<% - $cgi->param('svcpart') =~ /^(\d+)$/ - or die "illegal svcpart: ". $cgi->param('svcpart'); - my $old_svcpart = $1; - my $src_part_svc = qsearchs('part_svc', { 'svcpart' => $old_svcpart } ) - or die "unknown svcpart: $old_svcpart"; -%> - -<INPUT NAME="old_svcpart" TYPE="hidden" VALUE="<%= $old_svcpart %>"> +<INPUT NAME="old_svcpart" TYPE="hidden" VALUE="<% $old_svcpart %>"> Change <!-- customer -<B><%= $src_part_svc->svcpart %>: <%= $src_part_svc->svc %></B> services +<B><% $src_part_svc->svcpart %>: <% $src_part_svc->svc %></B> services <BR> --> <SELECT NAME="pkgpart"> +% my $num_cust_svc = $src_part_svc->num_cust_svc; +% if ( $num_cust_svc > 1 ) { + + <OPTION VALUE="">all <% $num_cust_svc %> <% $src_part_svc->svc %> services +% } else { + + <OPTION VALUE="">the <% $num_cust_svc %> <% $src_part_svc->svc %> service +% } +% +% my $num_unlinked = $src_part_svc->num_cust_svc(0); +% if ( $num_unlinked ) { +% + + <OPTION VALUE="0">the <% $num_unlinked %> unlinked <% $src_part_svc->svc %> services +% } +% foreach my $schwartz ( +% grep { $_->[1] } +% map { [ $_, $src_part_svc->num_cust_svc($_->pkgpart) ] } +% qsearch('part_pkg', {} ) +% ) { +% my( $part_pkg, $num_cust_svc ) = @$schwartz; +% + + <OPTION VALUE="<% $part_pkg->pkgpart %>">the <% $num_cust_svc %> + <% $src_part_svc->svc %> service<% $num_cust_svc > 1 ? 's in' : ' in a' %> + <% $part_pkg->pkg %> package<% $num_cust_svc > 1 ? 's' : '' %> +% } -<% my $num_cust_svc = $src_part_svc->num_cust_svc; %> -<% if ( $num_cust_svc > 1 ) { %> - <OPTION VALUE="">all <%= $num_cust_svc %> <%= $src_part_svc->svc %> services -<% } else { %> - <OPTION VALUE="">the <%= $num_cust_svc %> <%= $src_part_svc->svc %> service -<% } %> - -<% - my $num_unlinked = $src_part_svc->num_cust_svc(0); - if ( $num_unlinked ) { -%> - <OPTION VALUE="0">the <%= $num_unlinked %> unlinked <%= $src_part_svc->svc %> services - -<% } %> - -<% foreach my $schwartz ( - grep { $_->[1] } - map { [ $_, $src_part_svc->num_cust_svc($_->pkgpart) ] } - qsearch('part_pkg', {} ) - ) { - my( $part_pkg, $num_cust_svc ) = @$schwartz; -%> - <OPTION VALUE="<%= $part_pkg->pkgpart %>">the <%= $num_cust_svc %> - <%= $src_part_svc->svc %> service<%= $num_cust_svc > 1 ? 's in' : ' in a' %> - <%= $part_pkg->pkg %> package<%= $num_cust_svc > 1 ? 's' : '' %> -<% } %> </SELECT> <BR> to new service definition <SELECT NAME="new_svcpart"> -<% foreach my $dest_part_svc ( - grep { $_->svcpart != $old_svcpart - && $_->svcdb eq $src_part_svc->svcdb - } - qsearch('part_svc', { 'disabled' => '' } ) - ) { -%> - <OPTION VALUE="<%= $dest_part_svc->svcpart %>"><%= $dest_part_svc->svcpart %>: <%= $dest_part_svc->svc %> +% foreach my $dest_part_svc ( +% grep { $_->svcpart != $old_svcpart +% && $_->svcdb eq $src_part_svc->svcdb +% } +% qsearch('part_svc', { 'disabled' => '' } ) +% ) { +% + + <OPTION VALUE="<% $dest_part_svc->svcpart %>"><% $dest_part_svc->svcpart %>: <% $dest_part_svc->svc %> +% } -<% } %> </SELECT> <BR> diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi index 24bce308a..498d477cd 100755 --- a/httemplate/edit/cust_bill_pay.cgi +++ b/httemplate/edit/cust_bill_pay.cgi @@ -1,6 +1,59 @@ -<!-- mason kludge --> -<% +<% header("Apply Payment", '') %> +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_bill_pay.cgi" METHOD=POST> + +Payment #<B><% $paynum %></B> +<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>"> + +<BR>Date: <B><% time2str("%D", $cust_pay->_date) %></B> + +<BR>Amount: $<B><% $cust_pay->paid %></B> + +<BR>Unapplied amount: $<B><% $unapplied %></B> + +<SCRIPT TYPE="text/javascript"> +function changed(what) { + cust_bill = what.options[what.selectedIndex].value; + +% foreach my $cust_bill ( @cust_bill ) { + + if ( cust_bill == <% $cust_bill->invnum %> ) { + what.form.amount.value = "<% min($cust_bill->owed, $unapplied) %>"; + } + +% } + + if ( cust_bill == "Refund" ) { + what.form.amount.value = "<% $unapplied %>"; + } +} +</SCRIPT> + +<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)"> +<OPTION VALUE=""> + +% foreach my $cust_bill ( @cust_bill ) { + <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %> +% } + +<OPTION VALUE="Refund">Refund +</SELECT> + +<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8> + +<BR> +<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%init> my($paynum, $amount, $invnum); if ( $cgi->param('error') ) { $paynum = $cgi->param('paynum'); @@ -18,78 +71,15 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Payment", ''); -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT><BR><BR>" - if $cgi->param('error'); -print <<END; - <FORM ACTION="${p1}process/cust_bill_pay.cgi" METHOD=POST> -END - my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); die "payment $paynum not found!" unless $cust_pay; my $unapplied = $cust_pay->unapplied; -print "Payment # <B>$paynum</B>". - qq!<INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum">!. - '<BR>Date: <B>'. time2str("%D", $cust_pay->_date). '</B>'. - '<BR>Amount: $<B>'. $cust_pay->paid. '</B>'. - "<BR>Unapplied amount: \$<B>$unapplied</B>" - ; - -my @cust_bill = grep $_->owed != 0, +my @cust_bill = sort { $a->_date <=> $b->_date + or $a->invnum <=> $b->invnum + } + grep { $_->owed != 0 } qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } ); +</%init> -print <<END; -<SCRIPT> -function changed(what) { - cust_bill = what.options[what.selectedIndex].value; -END - -foreach my $cust_bill ( @cust_bill ) { - my $invnum = $cust_bill->invnum; - my $changeto = $cust_bill->owed < $unapplied - ? $cust_bill->owed - : $unapplied; - print <<END; - if ( cust_bill == $invnum ) { - what.form.amount.value = "$changeto"; - } -END -} - -print <<END; - if ( cust_bill == "Refund" ) { - what.form.amount.value = "$unapplied"; - } -} -</SCRIPT> -END - -print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!, - '<OPTION VALUE="">'; -foreach my $cust_bill ( @cust_bill ) { - print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ). - ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum. - ' - '. time2str("%D",$cust_bill->_date). - ' - $'. $cust_bill->owed; -} -print qq!<OPTION VALUE="Refund">Refund!; -print "</SELECT>"; - -print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!; - -print <<END; -<BR> -<INPUT TYPE="submit" VALUE="Apply"> -END - -print <<END; - - </FORM> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi index aae0df2fc..13d062c74 100755 --- a/httemplate/edit/cust_credit.cgi +++ b/httemplate/edit/cust_credit.cgi @@ -1,7 +1,60 @@ -<!-- mason kludge --> -<% +<% include('/elements/header-popup.html', 'Enter Credit') %> +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_credit.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="crednum" VALUE=""> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="paybatch" VALUE=""> +<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>"> +<INPUT TYPE="hidden" NAME="credited" VALUE=""> +<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $otaker %>"> + +Credit +<% ntable("#cccccc", 2) %> + + <TR> + <TD ALIGN="right">Date</TD> + <TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD> + </TR> + + <TR> + <TD ALIGN="right">Amount</TD> + <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD> + </TR> + +% +%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!; +% + + <TR> + <TD ALIGN="right">Reason</TD> + <TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="<% $reason %>" SIZE=32></TD> + </TR> + + <TR> + <TD ALIGN="right">Auto-apply<BR>to invoices</TD> + <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD> + </TR> + +</TABLE> + +<BR> + +<CENTER><INPUT TYPE="submit" VALUE="Enter credit"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%once> my $conf = new FS::Conf; +</%once> + +<%init> my($custnum, $amount, $reason); if ( $cgi->param('error') ) { #$cust_credit = new FS::cust_credit ( { @@ -24,40 +77,4 @@ my $_date = time; my $otaker = getotaker; my $p1 = popurl(1); - -print header("Post Credit", ''); -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); -print <<END, small_custview($custnum, $conf->config('countrydefault')); - <FORM ACTION="${p1}process/cust_credit.cgi" METHOD=POST> - <INPUT TYPE="hidden" NAME="crednum" VALUE=""> - <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum"> - <INPUT TYPE="hidden" NAME="paybatch" VALUE=""> - <INPUT TYPE="hidden" NAME="_date" VALUE="$_date"> - <INPUT TYPE="hidden" NAME="credited" VALUE=""> - <INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker"> -END - -print '<BR><BR>Credit'. ntable("#cccccc", 2). - '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. - time2str("%D",$_date). '</TD></TR>'; - -print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8></TD></TR>!; - -#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!; - -print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!; - -print qq!<TR><TD ALIGN="right">Auto-apply<BR>to invoices</TD><TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>!; - -print <<END; -</TABLE> -<BR> -<INPUT TYPE="submit" VALUE="Post credit"> - </FORM> - </BODY> -</HTML> -END - -%> +</%init> diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi index 1a97e1312..249ba31d0 100755 --- a/httemplate/edit/cust_credit_bill.cgi +++ b/httemplate/edit/cust_credit_bill.cgi @@ -1,6 +1,61 @@ -<!-- mason kludge --> -<% +<% header("Apply Credit", '') %> +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_credit_bill.cgi" METHOD=POST> + +Credit #<B><% $crednum %></B> +<INPUT TYPE="hidden" NAME="crednum" VALUE="<% $crednum %>"> + +<BR>Date: <B><% time2str("%D", $cust_credit->_date) %></B> + +<BR>Amount: $<B><% $cust_credit->amount %></B> + +<BR>Unapplied amount: $<B><% $credited %></B> + +<BR>Reason: <B><% $cust_credit->reason %></B> + +<SCRIPT> +function changed(what) { + cust_bill = what.options[what.selectedIndex].value; + +% foreach my $cust_bill ( @cust_bill ) { + + if ( cust_bill == <% $cust_bill->invnum %> ) { + what.form.amount.value = "<% min($cust_bill->owed, $credited) %>"; + } + +% } + + if ( cust_bill == "Refund" ) { + what.form.amount.value = "<% $credited %>"; + } +} +</SCRIPT> + +<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)"> +<OPTION VALUE=""> + +% foreach my $cust_bill ( @cust_bill ) { + <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %> +% } + +<OPTION VALUE="Refund">Refund +</SELECT> + +<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8> + +<BR> +<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%init> my($crednum, $amount, $invnum); if ( $cgi->param('error') ) { #$cust_credit_bill = new FS::cust_credit_bill ( { @@ -23,79 +78,15 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Credit", ''); -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT><BR><BR>" - if $cgi->param('error'); -print <<END; - <FORM ACTION="${p1}process/cust_credit_bill.cgi" METHOD=POST> -END - my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); die "credit $crednum not found!" unless $cust_credit; my $credited = $cust_credit->credited; -print "Credit # <B>$crednum</B>". - qq!<INPUT TYPE="hidden" NAME="crednum" VALUE="$crednum">!. - '<BR>Date: <B>'. time2str("%D", $cust_credit->_date). '</B>'. - '<BR>Amount: $<B>'. $cust_credit->amount. '</B>'. - "<BR>Unapplied amount: \$<B>$credited</B>". - '<BR>Reason: <B>'. $cust_credit->reason. '</B>' - ; - -my @cust_bill = grep $_->owed != 0, +my @cust_bill = sort { $a->_date <=> $b->_date + or $a->invnum <=> $b->invnum + } + grep { $_->owed != 0 } qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } ); +</%init> -print <<END; -<SCRIPT> -function changed(what) { - cust_bill = what.options[what.selectedIndex].value; -END - -foreach my $cust_bill ( @cust_bill ) { - my $invnum = $cust_bill->invnum; - my $changeto = $cust_bill->owed < $cust_credit->credited - ? $cust_bill->owed - : $cust_credit->credited; - print <<END; - if ( cust_bill == $invnum ) { - what.form.amount.value = "$changeto"; - } -END -} - -print <<END; - if ( cust_bill == "Refund" ) { - what.form.amount.value = "$credited"; - } -} -</SCRIPT> -END - -print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!, - '<OPTION VALUE="">'; -foreach my $cust_bill ( @cust_bill ) { - print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ). - ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum. - ' - '. time2str("%D",$cust_bill->_date). - ' - $'. $cust_bill->owed; -} -print qq!<OPTION VALUE="Refund">Refund!; -print "</SELECT>"; - -print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!; - -print <<END; -<BR> -<INPUT TYPE="submit" VALUE="Apply"> -END - -print <<END; - - </FORM> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 144d5405c..a843772d2 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -1,240 +1,279 @@ -<% - - #for misplaced logic below - #use FS::part_pkg; - - #for false laziness below (now more properly lazy) - #use FS::svc_acct_pop; - - #for (other) false laziness below - #use FS::agent; - #use FS::type_pkgs; - -my $conf = new FS::Conf; - -#get record - -my $error = ''; -my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart); -my(@invoicing_list); -my $same = ''; -if ( $cgi->param('error') ) { - $error = $cgi->param('error'); - $cust_main = new FS::cust_main ( { - map { $_, scalar($cgi->param($_)) } fields('cust_main') - } ); - $custnum = $cust_main->custnum; - $saved_pkgpart = $cgi->param('pkgpart_svcpart') || ''; - if ( $saved_pkgpart =~ /^(\d+)_/ ) { - $saved_pkgpart = $1; - } else { - $saved_pkgpart = ''; - } - $username = $cgi->param('username'); - $password = $cgi->param('_password'); - $popnum = $cgi->param('popnum'); - @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') ); - $same = $cgi->param('same'); - $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid'); -} elsif ( $cgi->keywords ) { #editing - my( $query ) = $cgi->keywords; - $query =~ /^(\d+)$/; - $custnum=$1; - $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); - if ( $cust_main->dbdef_table->column('paycvv') - && length($cust_main->paycvv) ) { - my $paycvv = $cust_main->paycvv; - $paycvv =~ s/./*/g; - $cust_main->paycvv($paycvv); - } - $saved_pkgpart = 0; - $username = ''; - $password = ''; - $popnum = 0; - @invoicing_list = $cust_main->invoicing_list; -} else { - $custnum=''; - $cust_main = new FS::cust_main ( {} ); - $cust_main->otaker( &getotaker ); - $cust_main->referral_custnum( $cgi->param('referral_custnum') ); - $saved_pkgpart = 0; - $username = ''; - $password = ''; - $popnum = 0; - @invoicing_list = (); -} -$cgi->delete_all(); -my $action = $custnum ? 'Edit' : 'Add'; +% +% +% #for misplaced logic below +% #use FS::part_pkg; +% +% #for false laziness below (now more properly lazy) +% #use FS::svc_acct_pop; +% +% #for (other) false laziness below +% #use FS::agent; +% #use FS::type_pkgs; +% +%my $conf = new FS::Conf; +% +%#get record +% +%my $error = ''; +%my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart, $saved_domsvc); +%my(@invoicing_list); +%my $payinfo; +%my $same = ''; +%if ( $cgi->param('error') ) { +% $error = $cgi->param('error'); +% $cust_main = new FS::cust_main ( { +% map { $_, scalar($cgi->param($_)) } fields('cust_main') +% } ); +% $custnum = $cust_main->custnum; +% $saved_domsvc = $cgi->param('domsvc') || ''; +% if ( $saved_domsvc =~ /^(\d+)$/ ) { +% $saved_domsvc = $1; +% } else { +% $saved_domsvc = ''; +% } +% $saved_pkgpart = $cgi->param('pkgpart_svcpart') || ''; +% if ( $saved_pkgpart =~ /^(\d+)_/ ) { +% $saved_pkgpart = $1; +% } else { +% $saved_pkgpart = ''; +% } +% $username = $cgi->param('username'); +% $password = $cgi->param('_password'); +% $popnum = $cgi->param('popnum'); +% @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') ); +% $same = $cgi->param('same'); +% $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid'); +% $payinfo = $cust_main->payinfo; # don't mask an entered value on errors +%} elsif ( $cgi->keywords ) { #editing +% my( $query ) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $custnum=$1; +% $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% if ( $cust_main->dbdef_table->column('paycvv') +% && length($cust_main->paycvv) ) { +% my $paycvv = $cust_main->paycvv; +% $paycvv =~ s/./*/g; +% $cust_main->paycvv($paycvv); +% } +% $saved_pkgpart = 0; +% $saved_domsvc = 0; +% $username = ''; +% $password = ''; +% $popnum = 0; +% @invoicing_list = $cust_main->invoicing_list; +% $payinfo = $cust_main->paymask; +%} else { +% $custnum=''; +% $cust_main = new FS::cust_main ( {} ); +% $cust_main->otaker( &getotaker ); +% $cust_main->referral_custnum( $cgi->param('referral_custnum') ); +% $saved_pkgpart = 0; +% $saved_domsvc = 0; +% $username = ''; +% $password = ''; +% $popnum = 0; +% @invoicing_list = (); +% push @invoicing_list, 'POST' +% unless $conf->exists('disablepostalinvoicedefault'); +% $payinfo = ''; +%} +%$cgi->delete_all(); +% +%my $action = $custnum ? 'Edit' : 'Add'; +%$action .= ": ". $cust_main->name if $custnum; +% +%my $r = qq!<font color="#ff0000">*</font> !; +% +% -%> <!-- top --> -<%= header("Customer $action", '', ' onUnload="myclose()"') %> +<% include('/elements/header.html', + "Customer $action", + '', + ' onUnload="myclose()"' +) %> +% if ( $error ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT><BR><BR> +% } -<% if ( $error ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $error %></FONT> -<% } %> <FORM NAME="topform" STYLE="margin-bottom: 0"> -<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>"> -Customer # <%= $custnum ? "<B>$custnum</B>" : " (NEW)" %> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +% if ( $custnum ) { -<!-- agent --> + Customer #<B><% $custnum %></B> - + <B><FONT COLOR="<% $cust_main->statuscolor %>"> + <% ucfirst($cust_main->status) %> + </FONT></B> + <BR><BR> +% } -<% -my $r = qq!<font color="#ff0000">*</font> !; +<% &ntable("#cccccc") %> -my %agent_search = dbdef->table('agent')->column('disabled') - ? ( 'disabled' => '' ) : (); -my @agents = qsearch( 'agent', \%agent_search ); -#die "No agents created!" unless @agents; -eidiot "You have not created any agents (or all agents are disabled). You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents; -my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first +<!-- agent --> +<% include('/elements/tr-select-agent.html', $cust_main->agentnum, + 'label' => "<B>${r}Agent</B>", + 'empty_label' => 'Select agent', + ) %> -<% if ( scalar(@agents) == 1 ) { %> - <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>"> -<% } else { %> - <BR><BR><%=$r%>Agent <SELECT NAME="agentnum" SIZE="1"> - <% foreach my $agent (sort { $a->agent cmp $b->agent; } @agents) { %> - <OPTION VALUE="<%= $agent->agentnum %>"<%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %> - <% } %> - </SELECT> -<% } %> - <!-- referral (advertising source) --> +% +%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0; +%if ( $custnum && ! $conf->exists('editreferrals') ) { +% -<% -my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0; -if ( $custnum && ! $conf->exists('editreferrals') ) { -%> - <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>"> + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>"> +% } else { -<% - } else { - my(@referrals) = qsearch('part_referral',{}); - if ( scalar(@referrals) == 0 ) { - eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources."; - } elsif ( scalar(@referrals) == 1 ) { - $refnum ||= $referrals[0]->refnum; -%> + <% include('/elements/tr-select-part_referral.html', $refnum ) %> +% } - <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>"> -<% } else { %> +<!-- referring customer --> +% +%my $referring_cust_main = ''; +%if ( $cust_main->referral_custnum +% and $referring_cust_main = +% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) +%) { +% - <BR><BR><%=$r%>Advertising source - <SELECT NAME="refnum" SIZE="1"> - <%= $refnum ? '' : '<OPTION VALUE="">' %> - <% foreach my $referral (sort { $a->refnum <=> $b->refnum } @referrals) { %> - <OPTION VALUE="<%= $referral->refnum %>" <%= $referral->refnum == $refnum ? 'SELECTED' : '' %>><%= $referral->refnum %>: <%= $referral->referral %> - <% } %> - </SELECT> -<% } %> -<% } %> + <TR> + <TD ALIGN="right">Referring customer</TD> + <TD> + <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A> + </TD> + </TR> + <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>"> +% } elsif ( ! $conf->exists('disable_customer_referrals') ) { -<!-- referring customer --> -<% -my $referring_cust_main = ''; -if ( $cust_main->referral_custnum - and $referring_cust_main = - qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) -) { -%> + <TR> + <TD ALIGN="right">Referring customer</TD> + <TD> + <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> --> + <% include('/elements/search-cust_main.html', + 'field_name' => 'referral_custnum', + ) + %> + </TD> + </TR> +% } else { + + + <INPUT TYPE="hidden" NAME="referral_custnum" VALUE=""> +% } + - <BR><BR>Referring Customer: - <A HREF="<%= popurl(1) %>/cust_main.cgi?<%= $cust_main->referral_custnum %>"><%= $cust_main->referral_custnum %>: <%= $referring_cust_main->name %></A> - <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<%= $cust_main->referral_custnum %>"> +</TABLE> -<% } elsif ( ! $conf->exists('disable_customer_referrals') ) { %> +<!-- birthdate --> - <BR><BR>Referring customer number: - <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> +% if ( $conf->exists('cust_main-enable_birthdate') ) { -<% } else { %> + <BR> + <% ntable("#cccccc", 2) %> + <% include ('/elements/tr-input-date-field.html', + 'birthdate', + $cust_main->birthdate, + 'Date of Birth', + $conf->config('date_format') || "%m/%d/%Y", + 1) + %> - <INPUT TYPE="hidden" NAME="referral_custnum" VALUE=""> + </TABLE> -<% } %> +% } <!-- contact info --> <BR><BR> Billing address -<%= include('cust_main/contact.html', $cust_main, '', 'bill_changed(this)', '' ) %> +<% include('cust_main/contact.html', $cust_main, '', 'bill_changed(this)', '' ) %> <!-- service address --> +% if ( defined $cust_main->dbdef_table->column('ship_last') ) { -<% if ( defined $cust_main->dbdef_table->column('ship_last') ) { %> <SCRIPT> function bill_changed(what) { if ( what.form.same.checked ) { -<% for (qw( last first company address1 address2 city zip daytime night fax )) { %> - what.form.ship_<%=$_%>.value = what.form.<%=$_%>.value; -<% } %> +% for (qw( last first company address1 address2 city zip daytime night fax )) { + + what.form.ship_<%$_%>.value = what.form.<%$_%>.value; +% } what.form.ship_country.selectedIndex = what.form.country.selectedIndex; + + function fix_ship_county() { + what.form.ship_county.selectedIndex = what.form.county.selectedIndex; + } + function fix_ship_state() { what.form.ship_state.selectedIndex = what.form.state.selectedIndex; + ship_state_changed(what.form.ship_state, fix_ship_county ); } + ship_country_changed(what.form.ship_country, fix_ship_state ); - function fix_ship_county() { - what.form.ship_county.selectedIndex = what.form.county.selectedIndex; - } - ship_state_changed(what.form.ship_state, fix_ship_county ); } } function samechanged(what) { if ( what.checked ) { bill_changed(what); -<% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { %> - what.form.ship_<%=$_%>.disabled = true; - what.form.ship_<%=$_%>.style.backgroundColor = '#dddddd'; -<% } %> +% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { + + what.form.ship_<%$_%>.disabled = true; + what.form.ship_<%$_%>.style.backgroundColor = '#dddddd'; +% } + } else { -<% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { %> - what.form.ship_<%=$_%>.disabled = false; - what.form.ship_<%=$_%>.style.backgroundColor = '#ffffff'; -<% } %> +% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { + + what.form.ship_<%$_%>.disabled = false; + what.form.ship_<%$_%>.style.backgroundColor = '#ffffff'; +% } + } } </SCRIPT> +% +% my $checked = ''; +% my $disabled = ''; +% my $disabledselect = ''; +% unless ( $cust_main->ship_last && $same ne 'Y' ) { +% $checked = 'CHECKED'; +% $disabled = 'DISABLED STYLE="background-color: #dddddd"'; +% foreach ( +% qw( last first company address1 address2 city county state zip country +% daytime night fax ) +% ) { +% $cust_main->set("ship_$_", $cust_main->get($_) ); +% } +% } +% -<% - my $checked = ''; - my $disabled = ''; - my $disabledselect = ''; - unless ( $cust_main->ship_last && $same ne 'Y' ) { - $checked = 'CHECKED'; - $disabled = 'DISABLED style="background-color: #dddddd"'; - foreach ( - qw( last first company address1 address2 city county state zip country - daytime night fax ) - ) { - $cust_main->set("ship_$_", $cust_main->get($_) ); - } - } -%> <BR> Service address -(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%=$checked%>>same as billing address) -<%= include('cust_main/contact.html', $cust_main, 'ship_', '', $disabled ) %> +(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$checked%>>same as billing address) +<% include('cust_main/contact.html', $cust_main, 'ship_', '', $disabled ) %> +% } -<% } %> <!-- billing info --> -<%= include( 'cust_main/billing.html', $cust_main, +<% include( 'cust_main/billing.html', $cust_main, + 'payinfo' => $payinfo, 'invoicing_list' => \@invoicing_list, ) %> @@ -243,6 +282,8 @@ Service address function bottomfixup(what) { var topvars = new Array( + 'birthdate', + 'custnum', 'agentnum', 'refnum', 'referral_custnum', 'last', 'first', 'ss', 'company', @@ -271,7 +312,8 @@ function bottomfixup(what) { var billing_bottomvars = new Array( 'tax', - 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX' + 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX', + 'spool_cdr' ); for ( f=0; f < topvars.length; f++ ) { @@ -322,118 +364,147 @@ function copyelement(from, to) { </SCRIPT> -<FORM ACTION="<%= popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" onSubmit="document.bottomform.submit.disabled=true; bottomfixup(this.form);" STYLE="margin-top: 0; margin-bottom: 0"> - -<% foreach my $hidden ( - 'custnum', 'agentnum', 'refnum', 'referral_custnum', - 'last', 'first', 'ss', 'company', - 'address1', 'address2', 'city', - 'county', 'state', 'zip', 'country', - 'daytime', 'night', 'fax', - - 'same', - - 'ship_last', 'ship_first', 'ship_company', - 'ship_address1', 'ship_address2', 'ship_city', - 'ship_county', 'ship_state', 'ship_zip', 'ship_country', - 'ship_daytime','ship_night', 'ship_fax', - - 'select', #XXX key - - 'payauto', - 'payinfo', 'payinfo1', 'payinfo2', - 'payname', 'exp_month', 'exp_year', 'paycvv', - 'paystart_month', 'paystart_year', 'payissue', - 'payip', - 'paid', - - 'tax', - 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX' - ) { -%> - <INPUT TYPE="hidden" NAME="<%= $hidden %>" VALUE=""> -<% } %> +<FORM ACTION="<% popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" onSubmit="document.bottomform.submit.disabled=true; bottomfixup(this.form);" STYLE="margin-top: 0; margin-bottom: 0"> +% foreach my $hidden ( +% 'birthdate', +% +% 'custnum', 'agentnum', 'refnum', 'referral_custnum', +% 'last', 'first', 'ss', 'company', +% 'address1', 'address2', 'city', +% 'county', 'state', 'zip', 'country', +% 'daytime', 'night', 'fax', +% +% 'same', +% +% 'ship_last', 'ship_first', 'ship_company', +% 'ship_address1', 'ship_address2', 'ship_city', +% 'ship_county', 'ship_state', 'ship_zip', 'ship_country', +% 'ship_daytime','ship_night', 'ship_fax', +% +% 'select', #XXX key +% +% 'payauto', +% 'payinfo', 'payinfo1', 'payinfo2', +% 'payname', 'exp_month', 'exp_year', 'paycvv', +% 'paystart_month', 'paystart_year', 'payissue', +% 'payip', +% 'paid', +% +% 'tax', +% 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX', +% 'spool_cdr' +% ) { +% + + <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE=""> +% } +% +% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly'; +% if (!$ro_comments || $cust_main->comments) { <BR>Comments -<%= &ntable("#cccccc") %> +<% &ntable("#cccccc") %> <TR> <TD> - <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments"><%= $cust_main->comments %></TEXTAREA> + <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments" <%$ro_comments%>><% $cust_main->comments %></TEXTAREA> </TD> </TR> </TABLE> +% +% } +% +%unless ( $custnum ) { +% # pry the wrong place for this logic. also pretty expensive +% #use FS::part_pkg; +% +% #false laziness, copied from FS::cust_pkg::order +% my $pkgpart; +% my @agents = $FS::CurrentUser::CurrentUser->agents; +% if ( scalar(@agents) == 1 ) { +% # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART +% $pkgpart = $agents[0]->pkgpart_hashref; +% } else { +% #can't know (agent not chosen), so, allow all +% my %typenum; +% foreach my $agent ( @agents ) { +% next if $typenum{$agent->typenum}++; +% #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref } +% foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround +% } +% } +% #eslaf +% +% my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } } +% qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case? +% +% if ( @part_pkg ) { +% +% # print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"), +% #apiabuse & undesirable wrapping +% +% + + <BR>First package + <% ntable("#cccccc") %> + + <TR> + <TD COLSPAN=2> + <% include('cust_main/select-domain.html', + 'pkgparts' => \@part_pkg, + 'saved_pkgpart' => $saved_pkgpart, + 'saved_domsvc' => $saved_domsvc, + ) + %> + </TD> + </TR> +% +% #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; +% + + + <TR> + <TD ALIGN="right">Username</TD> + <TD> + <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Domain</TD> + <TD> + <SELECT NAME="domsvc"> + <OPTION>(none)</OPTION> + </SELECT> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Password</TD> + <TD> + <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $passwordmax %>> + (blank to generate) + </TD> + </TR> + + <TR> + <TD ALIGN="right">Access number</TD> + <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> + </TR> + </TABLE> +% } +% } + + +<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>"> +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="<% $custnum ? "Apply Changes" : "Add Customer" %>"> +<BR> +</FORM> -<% - -unless ( $custnum ) { - # pry the wrong place for this logic. also pretty expensive - #use FS::part_pkg; - - #false laziness, copied from FS::cust_pkg::order - my $pkgpart; - if ( scalar(@agents) == 1 ) { - # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART - my($agent)=qsearchs('agent',{'agentnum'=> $agentnum }); - $pkgpart = $agent->pkgpart_hashref; - } else { - #can't know (agent not chosen), so, allow all - my %typenum; - foreach my $agent ( @agents ) { - next if $typenum{$agent->typenum}++; - #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref } - foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround - } - } - #eslaf - - my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } } - qsearch( 'part_pkg', { 'disabled' => '' } ); - - if ( @part_pkg ) { - -# print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"), -#apiabuse & undesirable wrapping - print "<BR>First package", &ntable("#cccccc"), - qq!<TR><TD COLSPAN=2><SELECT NAME="pkgpart_svcpart">!; - - print qq!<OPTION VALUE="">(none)!; - - foreach my $part_pkg ( @part_pkg ) { - print qq!<OPTION VALUE="!, -# $part_pkg->pkgpart. "_". $pkgpart{ $part_pkg->pkgpart }, '"'; - $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct'), '"'; - print " SELECTED" if $saved_pkgpart && ( $part_pkg->pkgpart == $saved_pkgpart ); - print ">", $part_pkg->pkg, " - ", $part_pkg->comment; - } - print "</SELECT></TD></TR>"; - - #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 <<END; -<TR><TD ALIGN="right">Username</TD> -<TD><INPUT TYPE="text" NAME="username" VALUE="$username" SIZE=$ulen2 MAXLENGTH=$ulen></TD></TR> -<TR><TD ALIGN="right">Password</TD> -<TD><INPUT TYPE="text" NAME="_password" VALUE="$password" SIZE=$pmax2 MAXLENGTH=$passwordmax> -(blank to generate)</TD></TR> -END - - print '<TR><TD ALIGN="right">Access number</TD><TD>' - . - &FS::svc_acct_pop::popselector($popnum). - '</TD></TR></TABLE>' - ; - } -} - -my $otaker = $cust_main->otaker; -print qq!<INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker">!, - qq!<BR><INPUT TYPE="submit" NAME="submit" VALUE="!, - $custnum ? "Apply Changes" : "Add Customer", qq!"><BR>!, - "</FORM></DIV></BODY></HTML>", -; +<% include('/elements/footer.html') %> -%> diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index 96f777baa..ba10929ed 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -1,70 +1,54 @@ -<% +%if ( $payby_default eq 'HIDE' ) { +% +% $cust_main->payby('BILL') unless $cust_main->payby; -my( $cust_main, %options ) = @_; -my @invoicing_list = @{ $options{'invoicing_list'} }; -my $conf = new FS::Conf; -my $payby_default = $conf->config('payby-default'); - -my @payby = grep /\w/, $conf->config('payby'); -#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) -@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) - unless @payby; - -if ( $payby_default eq 'HIDE' ) { - - $cust_main->payby('BILL') unless $cust_main->payby; - -%> - - <INPUT TYPE="hidden" NAME="select" VALUE="<%= $cust_main->payby %>"> + <INPUT TYPE="hidden" NAME="select" VALUE="<% $cust_main->payby %>"> </FORM> - <FORM NAME="<%= $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0"> <% # XXX key %> + <FORM NAME="<% $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0"> - <% foreach my $field (qw( payinfo payname paycvv paystart_month paystart_year payissue payip )) { %> + <INPUT TYPE="hidden" NAME="payinfo" VALUE="<% $cust_main->paymask %>"> - <INPUT TYPE="hidden" NAME="<%= $field %>" VALUE="<%= $cust_main->getfield($field) %>"> +% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip )) { - <% } %> + <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $cust_main->getfield($field) %>"> - <% - #false laziness w/elements/select-month_year.html & view/cust_main/billing.html - my( $mon, $year ); - my $date = $cust_main->paydate || '12-2037'; - if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format - ( $mon, $year ) = ( $2, $1 ); - } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { - ( $mon, $year ) = ( $1, $3 ); - } else { - die "unrecognized expiration date format: $date"; - } - %> +% } + +% #false laziness w/elements/select-month_year.html & view/cust_main/billing.html +% my( $mon, $year ); +% my $date = $cust_main->paydate || '12-2037'; +% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +% } else { +% die "unrecognized expiration date format: $date"; +% } - <INPUT TYPE="hidden" NAME="exp_month" VALUE="<%= $mon %>"> - <INPUT TYPE="hidden" NAME="exp_year" VALUE="<%= $year %>"> + <INPUT TYPE="hidden" NAME="exp_month" VALUE="<% $mon %>"> + <INPUT TYPE="hidden" NAME="exp_year" VALUE="<% $year %>"> </FORM> <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0"> - <INPUT TYPE="hidden" NAME="tax" VALUE="<%= $cust_main->tax %>"> + <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>"> - <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<%= join(', ', @invoicing_list) %>"> + <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>"> </FORM> -<% } else { - - my $r = qq!<font color="#ff0000">*</font> !; - -%> +% } else { +% +% my $r = qq!<font color="#ff0000">*</font> !; <BR>Billing information - <%= &ntable("#cccccc") %> + <% &ntable("#cccccc") %> <TR> - <TD ALIGN="right" WIDTH="200"><%=$r%>Billing type</TD> + <TD ALIGN="right" WIDTH="200"><%$r%>Billing type</TD> <SCRIPT> @@ -134,279 +118,271 @@ if ( $payby_default eq 'HIDE' ) { } </SCRIPT> - <% - - my($payby, $payinfo, $payname)=( - $cust_main->payby, - $cust_main->payinfo, - $cust_main->payname, - ); - my( $account, $aba ) = split('@', $payinfo); - - my $disabled = 'DISABLED style="background-color: #dddddd"'; - my $text_disabled = 'style="color: #999999"'; - if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) { - $disabled = 'style="background-color: #ffffff"'; - $text_disabled = 'style="color: #000000";' - } - - my %payby = ( - - 'CARD' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. - '<TD WIDTH="408">'. - - include('/elements/select-month_year.html', - 'prefix' => 'exp', - 'selected_date' => - ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ), - ). - - '</TD></TR>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">CVV2 !. - - qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. - qq!</TD>!. - '<TD WIDTH="408"><INPUT TYPE="text" NAME="paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'. - - - qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!. - '<TD WIDTH="408">'. - - include('/elements/select-month_year.html', - 'prefix' => 'paystart', - 'disabled' => $disabled, - 'empty_option' => 1, - 'start_year' => 2000, - 'end_year' => (localtime())[5] + 1900, - 'selected_date' => ( - ( $payby =~ /^(CARD|DCRD)$/ - && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) - ? $cust_main->paystart_month. '-'. - $cust_main->paystart_year - : '' - ) - ). - - qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!. - '<INPUT TYPE="text" NAME="payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. - - qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'. - - '</TABLE>', - - 'CHEK' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD></TR>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !. - qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. - qq!</TD></TR>!. - - qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. - qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. - - qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'LECB' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!. - - qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. - qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. - qq!<INPUT TYPE="hidden" NAME="payname" VALUE="">!. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'BILL' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!. - - qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. - qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. - - qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'COMP' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE=""></TD></TR>!. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. - '<TD WIDTH="408">'. - - include('/elements/select-month_year.html', - 'prefix' => 'exp', - 'selected_date' => - ( $payby eq 'COMP' ? $cust_main->paydate : '' ), - ). - - '</TD></TR>'. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'CASH' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'WEST' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - 'MCRD' => - - '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. - - qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. - qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!. - - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - '<TR><TD> </TD></TR>'. - - '</TABLE>', - - ); - - - my %allopt = ( - 'CARD' => 'Credit card', - 'CHEK' => 'Electronic check', - 'LECB' => 'Phone bill billing', - 'BILL' => 'Billing', - 'CASH' => 'Cash', # initial payment, then billing', - 'WEST' => 'Western Union', # initial payment, then billing', - 'MCRD' => 'Manual credit card', # initial payment, then billing', - 'COMP' => 'Complimentary', - ); - if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types - # when editing customer - delete $allopt{$_} for qw(CASH WEST MCRD); - } - - tie my %options, 'Tie::IxHash', - map { $_ => $allopt{$_} } - grep { exists $allopt{$_} } - @payby; - - my %payby2option = ( - ( map { $_ => $_ } keys %options ), - 'DCRD' => 'CARD', - 'DCHK' => 'CHEK', - ); - - my $widget = new HTML::Widgets::SelectLayers( - 'options' => \%options, - #'form_name' => 'dummy', - #'form_action' => 'nothingyet', - #chops bottom of page in IE# 'under_position' => 'absolute', - 'html_between' => '</TD></TR></TABLE>', - 'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] }, - 'layer_callback' => sub { my $layer = shift; $payby{$layer}; }, - ); - - %> - - <TD WIDTH="408"><%= $widget->html %> +% my $payby = $cust_main->payby; +% my( $account, $aba ) = split('@', $payinfo); +% +% my $disabled = 'DISABLED style="background-color: #dddddd"'; +% my $text_disabled = 'style="color: #999999"'; +% +% if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) { +% $disabled = 'style="background-color: #ffffff"'; +% $text_disabled = 'style="color: #000000";' +% } +% +% my %payby = ( +% +% 'CARD' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'exp', +% 'selected_date' => +% ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ), +% ). +% +% '</TD></TR>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">CVV2 !. +% +% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. +% qq!</TD>!. +% '<TD WIDTH="408"><INPUT TYPE="text" NAME="paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'. +% +% +% qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'paystart', +% 'disabled' => $disabled, +% 'empty_option' => 1, +% 'start_year' => 2000, +% 'end_year' => (localtime())[5] + 1900, +% 'selected_date' => ( +% ( $payby =~ /^(CARD|DCRD)$/ +% && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) +% ? $cust_main->paystart_month. '-'. +% $cust_main->paystart_year +% : '' +% ) +% ). +% +% qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!. +% '<INPUT TYPE="text" NAME="payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'. +% +% '</TABLE>', +% +% 'CHEK' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=12 NAME="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD></TR>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !. +% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. +% qq!</TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'LECB' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% qq!<INPUT TYPE="hidden" NAME="payname" VALUE="">!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'BILL' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'COMP' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE=""></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'exp', +% 'selected_date' => +% ( $payby eq 'COMP' ? $cust_main->paydate : '' ), +% ). +% +% '</TD></TR>'. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'CASH' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'WEST' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'MCRD' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% ); +% +% #this should use FS::payby +% my %allopt = ( +% 'CARD' => 'Credit card', +% 'CHEK' => 'Electronic check', +% 'LECB' => 'Phone bill billing', +% 'BILL' => 'Billing', +% 'CASH' => 'Cash', # initial payment, then billing', +% 'WEST' => 'Western Union', # initial payment, then billing', +% 'MCRD' => 'Manual credit card', # initial payment, then billing', +% 'COMP' => 'Complimentary', +% ); +% if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types +% # when editing customer +% delete $allopt{$_} for qw(CASH WEST MCRD); +% } +% +% tie my %options, 'Tie::IxHash', +% map { $_ => $allopt{$_} } +% grep { exists $allopt{$_} } +% @payby; +% +% my %payby2option = ( +% ( map { $_ => $_ } keys %options ), +% 'DCRD' => 'CARD', +% 'DCHK' => 'CHEK', +% ); +% +% my $widget = new HTML::Widgets::SelectLayers( +% 'options' => \%options, +% #'form_name' => 'dummy', +% #'form_action' => 'nothingyet', +% #chops bottom of page in IE# 'under_position' => 'absolute', +% 'html_between' => '</TD></TR></TABLE>', +% 'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] }, +% 'layer_callback' => sub { my $layer = shift; $payby{$layer}; }, +% ); +% +% + + + <TD WIDTH="408"><% $widget->html %> <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0"> - <%= &ntable("#cccccc") %> + <% &ntable("#cccccc") %> <TR><TD> </TD></TR> <TR> - <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <%= $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</TD> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</TD> </TR> <TR> - <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <%= + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <% - ( ( ! @invoicing_list - && ! $conf->exists('disablepostalinvoicedefault') - && ! $cust_main->custnum - ) - || grep { $_ eq 'POST' } @invoicing_list ) + ( grep { $_ eq 'POST' } @invoicing_list ) ? 'CHECKED' : '' @@ -417,7 +393,7 @@ if ( $payby_default eq 'HIDE' ) { </TR> <TR> - <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <%= + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'CHECKED' @@ -430,14 +406,37 @@ if ( $payby_default eq 'HIDE' ) { <TR> <TD ALIGN="right" WIDTH="200">Email invoice </TD> - <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD> + <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD> </TR> +% if ( $conf->exists('voip-cust_cdr_spools') ) { + + <TR> + <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD> + </TR> +% } else { + + <INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>"> +% } + </TABLE> </FORM> - <%= $r %> required fields + <% $r %> required fields +% } + +<%init> + +my( $cust_main, %options ) = @_; +my @invoicing_list = @{ $options{'invoicing_list'} }; +my $payinfo = $options{'payinfo'}; +my $conf = new FS::Conf; +my $payby_default = $conf->config('payby-default'); -<% } %> +my @payby = grep /\w/, $conf->config('payby'); +#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) + unless @payby; +</%init> diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html index e0cd06f56..e813986cd 100644 --- a/httemplate/edit/cust_main/contact.html +++ b/httemplate/edit/cust_main/contact.html @@ -1,125 +1,134 @@ -<% - -my( $cust_main, $pre, $onchange, $disabled ) = @_; -my $conf = new FS::Conf; - -#false laziness with ship state -my $countrydefault = $conf->config('countrydefault') || 'US'; -$cust_main->set($pre.'country', $countrydefault ) - unless $cust_main->get($pre.'country'); - -my $statedefault = $conf->config('statedefault') - || ($countrydefault eq 'US' ? 'CA' : ''); -$cust_main->set($pre.'state', $statedefault ) - unless $cust_main->get($pre.'state') - || $cust_main->get($pre.'country') ne $countrydefault; - -#my($county_html, $state_html, $country_html) = -# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'), -# $cust_main->get($pre.'state'), -# $cust_main->get($pre.'country'), -# $pre, -# $onchange, -# $disabled, -# ); - -my %select_hash = ( - 'county' => $cust_main->get($pre.'county'), - 'state' => $cust_main->get($pre.'state'), - 'country' => $cust_main->get($pre.'country'), - 'prefix' => $pre, - 'onchange' => $onchange, - 'disabled' => $disabled, -); - -my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone'; -my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone'; - -my $r = qq!<font color="#ff0000">*</font> !; - -%> - -<%= &ntable("#cccccc") %> +<% &ntable("#cccccc") %> <TR> - <TH ALIGN="right"><%=$r%>Contact name<BR>(last, first)</TH> - <TD COLSPAN=3> - <INPUT TYPE="text" NAME="<%=$pre%>last" VALUE="<%= $cust_main->get($pre.'last') %>" onChange="<%= $onchange %>" <%=$disabled%>> , - <INPUT TYPE="text" NAME="<%=$pre%>first" VALUE="<%= $cust_main->get($pre.'first') %>" onChange="<%= $onchange %>" <%=$disabled%>> + <TH ALIGN="right"><%$r%>Contact name<BR>(last, first)</TH> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>last" VALUE="<% $cust_main->get($pre.'last') %>" onChange="<% $onchange %>" <%$disabled%>> , + <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') %>" onChange="<% $onchange %>" <%$disabled%>> </TD> +% if ( $conf->exists('show_ss') && !$pre ) { -<% if ( $conf->exists('show_ss') && !$pre ) { %> <TD ALIGN="right">SS#</TD> - <TD><INPUT TYPE="text" NAME="ss" VALUE="<%= $cust_main->ss %>" SIZE=11></TD> -<% } elsif ( !$pre ) { %> - <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<%= $cust_main->ss %>"></TD> -<% } %> + <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $cust_main->ss %>" SIZE=11></TD> +% } elsif ( !$pre ) { + + <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $cust_main->ss %>"></TD> +% } + </TR> <TR> <TD ALIGN="right">Company</TD> - <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>company" VALUE="<%= $cust_main->get($pre.'company') %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>> + <TD COLSPAN=7> + <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> - <TH ALIGN="right"><%=$r%>Address</TH> - <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>address1" VALUE="<%= $cust_main->get($pre.'address1') %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>> + <TH ALIGN="right"><%$r%>Address</TH> + <TD COLSPAN=7> + <INPUT TYPE="text" NAME="<%$pre%>address1" VALUE="<% $cust_main->get($pre.'address1') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> <TD ALIGN="right"> </TD> - <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>address2" VALUE="<%= $cust_main->get($pre.'address2') %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>> + <TD COLSPAN=7> + <INPUT TYPE="text" NAME="<%$pre%>address2" VALUE="<% $cust_main->get($pre.'address2') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> - <TH ALIGN="right"><%=$r%>City</TH> + <TH ALIGN="right"><%$r%>City</TH> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>city" VALUE="<% $cust_main->get($pre.'city') %>" onChange="<% $onchange %>" <%$disabled%>> + </TD> + <TH ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</TH> <TD> - <INPUT TYPE="text" NAME="<%=$pre%>city" VALUE="<%= $cust_main->get($pre.'city') %>" onChange="<%= $onchange %>" <%=$disabled%>> + <% include('select-county.html', %select_hash ) %> </TD> - <TH ALIGN="right"><%=$r%>State</TH> + <TH ALIGN="right"><%$r%>State</TH> <TD> - <%= include('select-county.html', %select_hash ) %> - <%= include('select-state.html', %select_hash ) %> + <% include('select-state.html', %select_hash ) %> </TD> - <TH><%=$r%>Zip</TH> + <TH><%$r%>Zip</TH> <TD> - <INPUT TYPE="text" NAME="<%=$pre%>zip" VALUE="<%= $cust_main->get($pre.'zip') %>" SIZE=10 onChange="<%= $onchange %>" <%=$disabled%>> + <INPUT TYPE="text" NAME="<%$pre%>zip" VALUE="<% $cust_main->get($pre.'zip') %>" SIZE=10 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> - <TH ALIGN="right"><%=$r%>Country</TH> - <TD><%= include('select-country.html', %select_hash ) %></TD> + <TH ALIGN="right"><%$r%>Country</TH> + <TD COLSPAN=5><% include('select-country.html', %select_hash ) %></TD> </TR> <TR> - <TD ALIGN="right"><%= $daytime_label %></TD> + <TD ALIGN="right"><% $daytime_label %></TD> <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>daytime" VALUE="<%= $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<%= $onchange %>" <%=$disabled%>> + <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> - <TD ALIGN="right"><%= $night_label %></TD> + <TD ALIGN="right"><% $night_label %></TD> <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>night" VALUE="<%= $cust_main->get($pre.'night') %>" SIZE=18 onChange="<%= $onchange %>" <%=$disabled%>> + <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> <TR> <TD ALIGN="right">Fax</TD> <TD COLSPAN=5> - <INPUT TYPE="text" NAME="<%=$pre%>fax" VALUE="<%= $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<%= $onchange %>" <%=$disabled%>> + <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%>> </TD> </TR> </TABLE> -<%=$r%>required fields<BR> +<%$r%>required fields<BR> + +<%init> + +my( $cust_main, $pre, $onchange, $disabled ) = @_; +my $conf = new FS::Conf; + +#false laziness with ship state +my $countrydefault = $conf->config('countrydefault') || 'US'; +$cust_main->set($pre.'country', $countrydefault ) + unless $cust_main->get($pre.'country'); + +my $statedefault = $conf->config('statedefault') + || ($countrydefault eq 'US' ? 'CA' : ''); +$cust_main->set($pre.'state', $statedefault ) + unless $cust_main->get($pre.'state') + || $cust_main->get($pre.'country') ne $countrydefault; + +#my($county_html, $state_html, $country_html) = +# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'), +# $cust_main->get($pre.'state'), +# $cust_main->get($pre.'country'), +# $pre, +# $onchange, +# $disabled, +# ); + +my %select_hash = ( + 'county' => $cust_main->get($pre.'county'), + 'state' => $cust_main->get($pre.'state'), + 'country' => $cust_main->get($pre.'country'), + 'prefix' => $pre, + 'onchange' => $onchange, + 'disabled' => $disabled, +); + +my @counties = counties( $cust_main->get($pre.'state'), + $cust_main->get($pre.'country'), + ); +my $county_style = scalar(@counties) > 1 ? '' : 'STYLE="visibility:hidden"'; + +my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone'; +my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone'; + +my $r = qq!<font color="#ff0000">*</font> !; +</%init> diff --git a/httemplate/edit/cust_main/select-country.html b/httemplate/edit/cust_main/select-country.html index 014effd66..137f61975 100644 --- a/httemplate/edit/cust_main/select-country.html +++ b/httemplate/edit/cust_main/select-country.html @@ -1,16 +1,5 @@ -<% - my %opt = @_; - foreach my $opt (qw( county state country prefix onchange disabled )) { - $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); - } - - my $conf = new FS::Conf; - my $countrydefault = $conf->config('countrydefault') || 'US'; - -%> - -<%= include('/elements/xmlhttp.html', +<% include('/elements/xmlhttp.html', 'url' => $p.'misc/states.cgi', 'subs' => [ $opt{'prefix'}. 'get_states' ], ) @@ -24,23 +13,23 @@ what.options[length] = optionName; } - function <%= $opt{'prefix'} %>country_changed(what, callback) { + function <% $opt{'prefix'} %>country_changed(what, callback) { - country = what.options[what.selectedIndex].text; + country = what.options[what.selectedIndex].value; - function <%= $opt{'prefix'} %>update_states(states) { + function <% $opt{'prefix'} %>update_states(states) { // blank the current state list - for ( var i = what.form.<%= $opt{'prefix'} %>state.length; i >= 0; i-- ) - what.form.<%= $opt{'prefix'} %>state.options[i] = null; + for ( var i = what.form.<% $opt{'prefix'} %>state.length; i >= 0; i-- ) + what.form.<% $opt{'prefix'} %>state.options[i] = null; // add the new states var statesArray = eval('(' + states + ')' ); - for ( var s = 0; s < statesArray.length; s++ ) { - var stateLabel = statesArray[s]; + for ( var s = 0; s < statesArray.length; s=s+2 ) { + var stateLabel = statesArray[s+1]; if ( stateLabel == "" ) stateLabel = '(n/a)'; - opt(what.form.<%= $opt{'prefix'} %>state, statesArray[s], stateLabel); + opt(what.form.<% $opt{'prefix'} %>state, statesArray[s], stateLabel); } //run the callback @@ -49,24 +38,39 @@ } // go get the new states - <%= $opt{'prefix'} %>get_states( country, <%= $opt{'prefix'} %>update_states ); + <% $opt{'prefix'} %>get_states( country, <% $opt{'prefix'} %>update_states ); } </SCRIPT> -<SELECT NAME="<%= $opt{'prefix'} %>country" onChange="<%= $opt{'prefix'} %>country_changed(this); <%= $opt{'onchange'} %>" <%= $opt{'disabled'} %>> +<SELECT NAME="<% $opt{'prefix'} %>country" onChange="<% $opt{'prefix'} %>country_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> -<% foreach my $country ( - sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b } - map { $_->country } - qsearch( 'cust_main_county',{}, 'DISTINCT ON ( country ) *', ) - ) { -%> +% foreach my $country ( +% sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) +% or code2country($a) cmp code2country($b) } +% map { $_->country } +% qsearch({ +% 'select' => 'country', +% 'table' => 'cust_main_county', +% 'hashref' => {}, +% 'extra_sql' => 'GROUP BY country', +% }) +% ) { - <OPTION VALUE="<%= $country %>"<%= $country eq $opt{'country'} ? ' SELECTED' : '' %>><%= $country %> + <OPTION VALUE="<% $country %>"<% $country eq $opt{'country'} ? ' SELECTED' : '' %>><% code2country($country). " ($country)" %> -<% } %> +% } </SELECT> +<%init> +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} + +my $conf = new FS::Conf; +my $countrydefault = $conf->config('countrydefault') || 'US'; +</%init> + diff --git a/httemplate/edit/cust_main/select-county.html b/httemplate/edit/cust_main/select-county.html index 3de380b31..0dc826896 100644 --- a/httemplate/edit/cust_main/select-county.html +++ b/httemplate/edit/cust_main/select-county.html @@ -1,25 +1,10 @@ -<% +% if ( $countyflag ) { - my %opt = @_; - foreach my $opt (qw( county state country prefix onchange disabled )) { - $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); - } - - my $sql = "SELECT COUNT(*) FROM cust_main_county". - " WHERE county IS NOT NULL AND county != ''"; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute or die $sth->errstr; - my $countyflag = $sth->fetchrow_arrayref->[0]; - -%> - -<% if ( $countyflag ) { %> - - <%= include('/elements/xmlhttp.html', + <% include('/elements/xmlhttp.html', 'url' => $p.'misc/counties.cgi', 'subs' => [ $opt{'prefix'}. 'get_counties' ], ) -%> + %> <SCRIPT TYPE="text/javascript"> @@ -29,16 +14,16 @@ what.options[length] = optionName; } - function <%= $opt{'prefix'} %>state_changed(what, callback) { + function <% $opt{'prefix'} %>state_changed(what, callback) { - state = what.options[what.selectedIndex].text; - country = what.form.<%= $opt{'prefix'} %>country.options[what.form.<%= $opt{'prefix'} %>country.selectedIndex].text; + state = what.options[what.selectedIndex].value; + country = what.form.<% $opt{'prefix'} %>country.options[what.form.<% $opt{'prefix'} %>country.selectedIndex].value; - function <%= $opt{'prefix'} %>update_counties(counties) { + function <% $opt{'prefix'} %>update_counties(counties) { // blank the current county list - for ( var i = what.form.<%= $opt{'prefix'} %>county.length; i >= 0; i-- ) - what.form.<%= $opt{'prefix'} %>county.options[i] = null; + for ( var i = what.form.<% $opt{'prefix'} %>county.length; i >= 0; i-- ) + what.form.<% $opt{'prefix'} %>county.options[i] = null; // add the new counties var countiesArray = eval('(' + counties + ')' ); @@ -46,7 +31,17 @@ var countyLabel = countiesArray[s]; if ( countyLabel == "" ) countyLabel = '(n/a)'; - opt(what.form.<%= $opt{'prefix'} %>county, countiesArray[s], countyLabel); + opt(what.form.<% $opt{'prefix'} %>county, countiesArray[s], countyLabel); + } + + var countyFormLabel = document.getElementById('<% $opt{'prefix'} %>countylabel'); + + if ( countiesArray.length > 1 ) { + what.form.<% $opt{'prefix'} %>county.style.display = ''; + countyFormLabel.style.visibility = 'visible'; + } else { + what.form.<% $opt{'prefix'} %>county.style.display = 'none'; + countyFormLabel.style.visibility = 'hidden'; } //run the callback @@ -55,37 +50,64 @@ } // go get the new counties - <%= $opt{'prefix'} %>get_counties( state, country, <%= $opt{'prefix'} %>update_counties ); + <% $opt{'prefix'} %>get_counties( state, country, <% $opt{'prefix'} %>update_counties ); } </SCRIPT> - <SELECT NAME="<%= $opt{'prefix'} %>county" onChange="<%= $opt{'onchange'} %>" <%= $opt{'disabled'} %>> + <SELECT NAME="<% $opt{'prefix'} %>county" onChange="<% $opt{'onchange'} %>" <% $opt{'disabled'} %>> - <% foreach my $county ( - sort - map { $_->county } - qsearch('cust_main_county', { 'state' => $opt{'state'}, - 'country' => $opt{'country'}, - } - ) - ) { - %> +% foreach my $county ( @counties ) { - <OPTION VALUE="<%= $county %>"<%= $county eq $opt{'county'} ? ' SELECTED' : '' %>><%= $county %> + <OPTION VALUE="<% $county %>"<% $county eq $opt{'county'} ? ' SELECTED' : '' %>><% $county %> - <% } %> +% } </SELECT> -<% } else { %> +% } else { + <SCRIPT TYPE="text/javascript"> - function <%= $opt{'prefix'} %>state_changed(what) { + function <% $opt{'prefix'} %>state_changed(what) { } </SCRIPT> - <INPUT TYPE="hidden" NAME="<%= $opt{'prefix'} %>county" VALUE="<%= $opt{'county'} %>"> + <INPUT TYPE="hidden" NAME="<% $opt{'prefix'} %>county" VALUE="<% $opt{'county'} %>"> + +% } + +<%init> + +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} + +my @counties = (); +if ( $countyflag ) { + + @counties = counties( $opt{'state'}, $opt{'country'} ); + + # this is very hacky + unless ( scalar(@counties) > 1 ) { + if ( $opt{'disabled'} =~ /STYLE=/i ) { + $opt{'disabled'} =~ s/STYLE="([^"]+)"/STYLE="$1; display:none"/i; + } else { + $opt{'disabled'} .= ' STYLE="display:none"'; + } + } + +} + +</%init> +<%once> + +my $sql = "SELECT COUNT(*) FROM cust_main_county". + " WHERE county IS NOT NULL AND county != ''"; +my $sth = dbh->prepare($sql) or die dbh->errstr; +$sth->execute or die $sth->errstr; +my $countyflag = $sth->fetchrow_arrayref->[0]; -<% } %> +</%once> diff --git a/httemplate/edit/cust_main/select-domain.html b/httemplate/edit/cust_main/select-domain.html new file mode 100644 index 000000000..3d42eb8b1 --- /dev/null +++ b/httemplate/edit/cust_main/select-domain.html @@ -0,0 +1,66 @@ + +<% include('/elements/xmlhttp.html', + 'url' => $p.'misc/svc_acct-domains.cgi', + 'subs' => [ $opt{'prefix'}. 'get_domains' ], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + function selopt(what,value,text,selected) { + var optionName = new Option(text, value, false, selected); + var length = what.length; + what.options[length] = optionName; + } + + function <% $opt{'prefix'} %>pkgpart_svcpart_changed(what,selected) { + + pkgpart_svcpart = what.options[what.selectedIndex].value; + + function <% $opt{'prefix'} %>update_domains(domains) { + + // blank the current domain list + for ( var i = what.form.<% $opt{'prefix'} %>domsvc.length; i >= 0; i-- ) + what.form.<% $opt{'prefix'} %>domsvc.options[i] = null; + + // add the new domains + var domainArray = eval('(' + domains + ')' ); + for ( var s = 0; s < domainArray.length; s=s+2 ) { + var domainLabel = domainArray[s+1]; + if ( domainLabel == "" ) + domainLabel = '(n/a)'; + selopt(what.form.<% $opt{'prefix'} %>domsvc, domainArray[s], domainLabel, (domainArray[s] == selected) ? true : false); + } + + } + + // go get the new domains + <% $opt{'prefix'} %>get_domains( pkgpart_svcpart, <% $opt{'prefix'} %>update_domains ); + + } + +</SCRIPT> + +<SELECT NAME="<% $opt{'prefix'} %>pkgpart_svcpart" onchange="<% $opt{'prefix'} %>pkgpart_svcpart_changed(this,0);" > + +% foreach my $part_pkg ( @part_pkg ) { + + <OPTION VALUE="<% $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct') %>"<% ( $opt{saved_pkgpart} && $part_pkg->pkgpart == $opt{saved_pkgpart} ) ? ' SELECTED' : '' %>><% $part_pkg->pkg. " - ". $part_pkg->comment %> + +% } + +</SELECT> +<SCRIPT> + pkgpart_svcpart_changed(document.bottomform.pkgpart_svcpart, <% $opt{saved_domsvc} %>); +</SCRIPT> + +<%init> +my %opt = @_; +foreach my $opt (qw( svc_part pkgparts saved_pkgpart saved_domsvc prefix)) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} +$opt{saved_domsvc} = 0 unless $opt{saved_domsvc}; +my @part_pkg = @{$opt{'pkgparts'}}; + +</%init> + diff --git a/httemplate/edit/cust_main/select-state.html b/httemplate/edit/cust_main/select-state.html index 98e685ab8..87546e5e3 100644 --- a/httemplate/edit/cust_main/select-state.html +++ b/httemplate/edit/cust_main/select-state.html @@ -1,27 +1,20 @@ -<% +<SELECT NAME="<% $opt{'prefix'} %>state" onChange="<% $opt{'prefix'} %>state_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> - my %opt = @_; - foreach my $opt (qw( county state country prefix onchange disabled )) { - $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); - } +% foreach my $state ( keys %states ) { -%> + <OPTION VALUE="<% $state %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' %> -<SELECT NAME="<%= $opt{'prefix'} %>state" onChange="<%= $opt{'prefix'} %>state_changed(this); <%= $opt{'onchange'} %>" <%= $opt{'disabled'} %>> +% } -<% foreach my $state ( - sort - map { $_->state } - qsearch( 'cust_main_county', - { 'country' => $opt{'country'} }, - 'DISTINCT ON ( state ) *', - ) - ) { -%> - <OPTION VALUE="<%= $state %>"<%= $state eq $opt{'state'} ? ' SELECTED' : '' %>><%= $state || '(n/a)' %> +</SELECT> -<% } %> +<%init> +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} -</SELECT> +tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} ); +</%init> diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi index 9f314a457..f56d31941 100755 --- a/httemplate/edit/cust_main_county-expand.cgi +++ b/httemplate/edit/cust_main_county-expand.cgi @@ -1,54 +1,59 @@ <!-- mason kludge --> -<% +% +% +%my($taxnum, $delim, $expansion, $taxclass ); +%my($query) = $cgi->keywords; +%if ( $cgi->param('error') ) { +% $taxnum = $cgi->param('taxnum'); +% $delim = $cgi->param('delim'); +% $expansion = $cgi->param('expansion'); +% $taxclass = $cgi->param('taxclass'); +%} else { +% $query =~ /^(taxclass)?(\d+)$/ +% or die "Illegal taxnum (query $query)"; +% $taxclass = $1 ? 'taxclass' : ''; +% $taxnum = $2; +% $delim = 'n'; +% $expansion = ''; +%} +% +%my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +% or die "cust_main_county.taxnum $taxnum not found"; +%if ( $taxclass ) { +% die "Can't expand entry!" if $cust_main_county->getfield('taxclass'); +%} else { +% die "Can't expand entry!" if $cust_main_county->getfield('county'); +%} +% +%my $p1 = popurl(1); +%print header("Tax Rate (expand)", menubar( +% 'Main Menu' => popurl(2), +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print <<END; +% <FORM ACTION="${p1}process/cust_main_county-expand.cgi" METHOD=POST> +% <INPUT TYPE="hidden" NAME="taxnum" VALUE="$taxnum"> +% <INPUT TYPE="hidden" NAME="taxclass" VALUE="$taxclass"> +% Separate by +%END +%print '<INPUT TYPE="radio" NAME="delim" VALUE="n"'; +%print ' CHECKED' if $delim eq 'n'; +%print '>line (broken on some browsers) or', +% '<INPUT TYPE="radio" NAME="delim" VALUE="s"'; +%print ' CHECKED' if $delim eq 's'; +%print '>whitespace.'; +%print <<END; +% <BR><INPUT TYPE="submit" VALUE="Submit"> +% <BR><TEXTAREA NAME="expansion" ROWS=100>$expansion</TEXTAREA> +% </FORM> +% </CENTER> +% </BODY> +%</HTML> +%END +% +% -my($taxnum, $delim, $expansion, $taxclass ); -my($query) = $cgi->keywords; -if ( $cgi->param('error') ) { - $taxnum = $cgi->param('taxnum'); - $delim = $cgi->param('delim'); - $expansion = $cgi->param('expansion'); - $taxclass = $cgi->param('taxclass'); -} else { - $query =~ /^(taxclass)?(\d+)$/ - or die "Illegal taxnum (query $query)"; - $taxclass = $1 ? 'taxclass' : ''; - $taxnum = $2; - $delim = 'n'; - $expansion = ''; -} - -my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) - or die "cust_main_county.taxnum $taxnum not found"; -die "Can't expand entry!" if $cust_main_county->getfield('county'); - -my $p1 = popurl(1); -print header("Tax Rate (expand)", menubar( - 'Main Menu' => popurl(2), -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print <<END; - <FORM ACTION="${p1}process/cust_main_county-expand.cgi" METHOD=POST> - <INPUT TYPE="hidden" NAME="taxnum" VALUE="$taxnum"> - <INPUT TYPE="hidden" NAME="taxclass" VALUE="$taxclass"> - Separate by -END -print '<INPUT TYPE="radio" NAME="delim" VALUE="n"'; -print ' CHECKED' if $delim eq 'n'; -print '>line (broken on some browsers) or', - '<INPUT TYPE="radio" NAME="delim" VALUE="s"'; -print ' CHECKED' if $delim eq 's'; -print '>whitespace.'; -print <<END; - <BR><INPUT TYPE="submit" VALUE="Submit"> - <BR><TEXTAREA NAME="expansion" ROWS=100>$expansion</TEXTAREA> - </FORM> - </CENTER> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi index 4bcfcbe9b..7d1354d3e 100755 --- a/httemplate/edit/cust_main_county.cgi +++ b/httemplate/edit/cust_main_county.cgi @@ -1,98 +1,99 @@ <!-- mason kludge --> -<% +% +% +%print header("Edit tax rates", menubar( +% 'Main Menu' => popurl(2), +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="!, popurl(1), +% qq!process/cust_main_county.cgi" METHOD=POST>!, &table(), <<END; +% <TR> +% <TH><FONT SIZE=-1>Country</FONT></TH> +% <TH><FONT SIZE=-1>State</FONT></TH> +% <TH><FONT SIZE=-1>County</FONT></TH> +% <TH><FONT SIZE=-1>Taxclass</FONT><BR><FONT SIZE=-2>(per-package classification)</FONT></TH> +%END +% +%if ( dbdef->table('cust_main_county')->column('taxname') ) { +% print '<TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH>'; +%} +% +%print <<END; +% <TH><FONT SIZE=-1>Tax</FONT></TH> +% <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH> +%END +% +%if ( dbdef->table('cust_main_county')->column('setuptax') ) { +% print '<TH><FONT SIZE=-1>Setup<BR>fee<BR>exempt</TH>'; +%} +%if ( dbdef->table('cust_main_county')->column('recurtax') ) { +% print '<TH><FONT SIZE=-1>Recurring<BR>fee<BR>exempt</TH>'; +%} +% +%print '</TR>'; +% +%foreach my $cust_main_county ( sort { $a->country cmp $b->country +% or $a->state cmp $b->state +% or $a->county cmp $b->county +% } qsearch('cust_main_county',{}) ) { +% my($hashref)=$cust_main_county->hashref; +% print <<END; +% <TR> +% <TD BGCOLOR="#ffffff">$hashref->{country}</TD> +%END +% +% print "<TD", $hashref->{state} +% ? ' BGCOLOR="#ffffff">'.$hashref->{state} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print "<TD", $hashref->{county} +% ? ' BGCOLOR="#ffffff">'. $hashref->{county} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print "<TD", $hashref->{taxclass} +% ? ' BGCOLOR="#ffffff">'. $hashref->{taxclass} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum}, +% qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>! +% if dbdef->table('cust_main_county')->column('taxname'); +% +% print qq!<TD><TABLE><TR><TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum}, +% qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6></TD><TD>%</TD></TR></TABLE></TD>!; +% print qq!<TD><TABLE><TR><TD>\$</TD><TD><INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum}, +% qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD></TR></TABLE></TD>!; +% +% print qq!<TD><INPUT TYPE="checkbox" NAME="setuptax!. $hashref->{taxnum}. +% '" VALUE="Y"'. +% ( $hashref->{setuptax} =~ /^Y$/i ? ' CHECKED' : '' ). +% '></TD>' +% if dbdef->table('cust_main_county')->column('setuptax'); +% +% print qq!<TD><INPUT TYPE="checkbox" NAME="recurtax!. $hashref->{taxnum}. +% '" VALUE="Y"'. +% ( $hashref->{recurtax} =~ /^Y$/i ? ' CHECKED' : '' ). +% '></TD>' +% if dbdef->table('cust_main_county')->column('recurtax'); +% +% print '</TR>'; +% +%} +% +%print <<END; +% </TABLE> +% <INPUT TYPE="submit" VALUE="Apply changes"> +% </FORM> +% </CENTER> +% </BODY> +%</HTML> +%END +% +% -print header("Edit tax rates", menubar( - 'Main Menu' => popurl(2), -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="!, popurl(1), - qq!process/cust_main_county.cgi" METHOD=POST>!, &table(), <<END; - <TR> - <TH><FONT SIZE=-1>Country</FONT></TH> - <TH><FONT SIZE=-1>State</FONT></TH> - <TH><FONT SIZE=-1>County</FONT></TH> - <TH><FONT SIZE=-1>Taxclass</FONT><BR><FONT SIZE=-2>(per-package classification)</FONT></TH> -END - -if ( dbdef->table('cust_main_county')->column('taxname') ) { - print '<TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH>'; -} - -print <<END; - <TH><FONT SIZE=-1>Tax</FONT></TH> - <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH> -END - -if ( dbdef->table('cust_main_county')->column('setuptax') ) { - print '<TH><FONT SIZE=-1>Setup<BR>fee<BR>exempt</TH>'; -} -if ( dbdef->table('cust_main_county')->column('recurtax') ) { - print '<TH><FONT SIZE=-1>Recurring<BR>fee<BR>exempt</TH>'; -} - -print '</TR>'; - -foreach my $cust_main_county ( sort { $a->country cmp $b->country - or $a->state cmp $b->state - or $a->county cmp $b->county - } qsearch('cust_main_county',{}) ) { - my($hashref)=$cust_main_county->hashref; - print <<END; - <TR> - <TD BGCOLOR="#ffffff">$hashref->{country}</TD> -END - - print "<TD", $hashref->{state} - ? ' BGCOLOR="#ffffff">'.$hashref->{state} - : ' BGCOLOR="#cccccc">(ALL)' - , "</TD>"; - - print "<TD", $hashref->{county} - ? ' BGCOLOR="#ffffff">'. $hashref->{county} - : ' BGCOLOR="#cccccc">(ALL)' - , "</TD>"; - - print "<TD", $hashref->{taxclass} - ? ' BGCOLOR="#ffffff">'. $hashref->{taxclass} - : ' BGCOLOR="#cccccc">(ALL)' - , "</TD>"; - - print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>! - if dbdef->table('cust_main_county')->column('taxname'); - - print qq!<TD><TABLE><TR><TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6></TD><TD>%</TD></TR></TABLE></TD>!; - print qq!<TD><TABLE><TR><TD>\$</TD><TD><INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD></TR></TABLE></TD>!; - - print qq!<TD><INPUT TYPE="checkbox" NAME="setuptax!. $hashref->{taxnum}. - '" VALUE="Y"'. - ( $hashref->{setuptax} =~ /^Y$/i ? ' CHECKED' : '' ). - '></TD>' - if dbdef->table('cust_main_county')->column('setuptax'); - - print qq!<TD><INPUT TYPE="checkbox" NAME="recurtax!. $hashref->{taxnum}. - '" VALUE="Y"'. - ( $hashref->{recurtax} =~ /^Y$/i ? ' CHECKED' : '' ). - '></TD>' - if dbdef->table('cust_main_county')->column('recurtax'); - - print '</TR>'; - -} - -print <<END; - </TABLE> - <INPUT TYPE="submit" VALUE="Apply changes"> - </FORM> - </CENTER> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/cust_main_note.cgi b/httemplate/edit/cust_main_note.cgi new file mode 100755 index 000000000..303895bd8 --- /dev/null +++ b/httemplate/edit/cust_main_note.cgi @@ -0,0 +1,51 @@ +<% include('/elements/header-popup.html', "$action Customer Note") %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% popurl(1) %>process/cust_main_note.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="notenum" VALUE="<% $notenum %>"> + + +<BR><BR> +<TEXTAREA NAME="comment" ROWS="12" COLS="60"> +<% $comment %> +</TEXTAREA> + +<BR><BR> +<INPUT TYPE="submit" VALUE="<% $notenum ? "Apply Changes" : "Add Note" %>"> + +</FORM> +</BODY> +</HTML> + +<%init> +my($custnum, $comment, $notenum, $action); +$comment = ''; + +if ( $cgi->param('error') ) { + $comment = $cgi->param('comment'); +}elsif ($cgi->param('notenum')) { + $cgi->param('notenum') =~ /^(\d+)$/; + $notenum = $1; + die "illegal query ". $cgi->keywords unless $notenum; + my $note = qsearchs('cust_main_note', { 'notenum' => $notenum }); + die "no such note: ". $notenum unless $note; + $comment = $note->comments; +} + +$cgi->param('notenum') =~ /^(\d+)$/; +$notenum = $1; + +$cgi->param('custnum') =~ /^(\d+)$/; +$custnum = $1; + +die "illegal query ". $cgi->keywords unless $custnum; + +$action = $notenum ? 'Edit' : 'Add'; + +</%init> + diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi index 0370ab726..855fbfcf1 100755 --- a/httemplate/edit/cust_pay.cgi +++ b/httemplate/edit/cust_pay.cgi @@ -1,5 +1,94 @@ -<% +% if ( $link eq 'popup' ) { + <% include('/elements/header-popup.html', $title ) %> +% } else { + <% include("/elements/header.html", $title, '') %> +% } +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + +<FORM ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>"> +<INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>"> + +% unless ( $link eq 'popup' ) { + <% small_custview($custnum, $conf->config('countrydefault')) %> +% } + +<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> + +<BR><BR> +Payment +<% ntable("#cccccc", 2) %> + +<TR> + <TD ALIGN="right">Date</TD> + <TD COLSPAN=2> + <INPUT TYPE="text" NAME="_date" ID="_date_text" VALUE="<% time2str("%m/%d/%Y %r",$_date) %>"> + <IMG SRC="../images/calendar.png" ID="_date_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> +</TR> + +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "_date_text", + ifFormat: "%m/%d/%Y", + button: "_date_button", + align: "BR" + }); +</SCRIPT> + +<TR> + <TD ALIGN="right">Amount</TD> + <TD BGCOLOR="#ffffff" ALIGN="right"><% $money_char %></TD> + <TD><INPUT TYPE="text" NAME="paid" VALUE="<% $paid %>" SIZE=8 MAXLENGTH=8> by <B><% $payby{$payby} %></B></TD> +</TR> + +% if ( $payby eq 'BILL' ) { + <TR> + <TD ALIGN="right">Check #</TD> + <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD> + </TR> +% } + +<TR> +% if ( $link eq 'custnum' || $link eq 'popup' ) { + + <TD ALIGN="right">Auto-apply<BR>to invoices</TD> + <TD COLSPAN=2> + <SELECT NAME="apply"> + <OPTION VALUE="yes" SELECTED>yes + <OPTION>no</SELECT> + </TD> + +% } elsif ( $link eq 'invnum' ) { + + <TD ALIGN="right">Apply to</TD> + <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><% $linknum %></B> only</TD> + <INPUT TYPE="hidden" NAME="apply" VALUE="no"> + +% } +</TR> + +</TABLE> + +<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>"> + +<BR> +<INPUT TYPE="submit" VALUE="Post payment"> + +</FORM> +</BODY> +</HTML> + +<%once> my $conf = new FS::Conf; my %payby = ( @@ -9,22 +98,24 @@ my %payby = ( 'MCRD' => 'Manual credit card', ); -my($link, $linknum, $paid, $payby, $payinfo, $quickpay, $_date); +my $money_char = $conf->config('money_char') || '$'; +</%once> + +<%init> +my($link, $linknum, $paid, $payby, $payinfo, $_date); if ( $cgi->param('error') ) { $link = $cgi->param('link'); $linknum = $cgi->param('linknum'); $paid = $cgi->param('paid'); $payby = $cgi->param('payby'); $payinfo = $cgi->param('payinfo'); - $quickpay = $cgi->param('quickpay'); $_date = $cgi->param('_date') ? str2time($cgi->param('_date')) : time; } elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) { - $link = 'custnum'; + $link = $cgi->param('popup') ? 'popup' : 'custnum'; $linknum = $1; $paid = ''; $payby = $cgi->param('payby') || 'BILL'; $payinfo = ''; - $quickpay = $cgi->param('quickpay'); $_date = time; } elsif ( $cgi->param('invnum') =~ /^(\d+)$/ ) { $link = 'invnum'; @@ -32,7 +123,6 @@ if ( $cgi->param('error') ) { $paid = ''; $payby = $cgi->param('payby') || 'BILL'; $payinfo = ""; - $quickpay = ''; $_date = time; } else { die "illegal query ". $cgi->keywords; @@ -43,29 +133,6 @@ my $paybatch = "webui-$_date-$$-". rand() * 2**32; my $title = 'Post '. $payby{$payby}. ' payment'; $title .= " against Invoice #$linknum" if $link eq 'invnum'; -%> - -<%= header($title, '') %> - -<% if ( $cgi->param('error') ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<BR><BR> -<% } %> - -<%= ntable("#cccccc",2) %> - -<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> - -<FORM ACTION="<%= popurl(1) %>process/cust_pay.cgi" METHOD=POST> -<INPUT TYPE="hidden" NAME="link" VALUE="<%= $link %>"> -<INPUT TYPE="hidden" NAME="linknum" VALUE="<%= $linknum %>"> -<INPUT TYPE="hidden" NAME="quickpay" VALUE="<%= $quickpay %>"> - -<% -my $money_char = $conf->config('money_char') || '$'; my $custnum; if ( $link eq 'invnum' ) { my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } ) @@ -74,62 +141,5 @@ if ( $link eq 'invnum' ) { } elsif ( $link eq 'custnum' ) { $custnum = $linknum; } -%> - -<%= small_custview($custnum, $conf->config('countrydefault')) %> - -<INPUT TYPE="hidden" NAME="payby" VALUE="<%= $payby %>"> - -<BR><BR> -Payment -<%= ntable("#cccccc", 2) %> -<TR> - <TD ALIGN="right">Date</TD> - <TD COLSPAN=2> - <INPUT TYPE="text" NAME="_date" ID="_date_text" VALUE="<%= time2str("%m/%d/%Y %r",$_date) %>"> - <IMG SRC="../images/calendar.png" ID="_date_button" STYLE="cursor: pointer" TITLE="Select date"> - </TD> -</TR> -<SCRIPT TYPE="text/javascript"> - Calendar.setup({ - inputField: "_date_text", - ifFormat: "%m/%d/%Y", - button: "_date_button", - align: "BR" - }); -</SCRIPT> -<TR> - <TD ALIGN="right">Amount</TD> - <TD BGCOLOR="#ffffff" ALIGN="right"><%= $money_char %></TD> - <TD><INPUT TYPE="text" NAME="paid" VALUE="<%= $paid %>" SIZE=8 MAXLENGTH=8> by <B><%= $payby{$payby} %></B></TD> -</TR> - -<% if ( $payby eq 'BILL' ) { %> - - <TR> - <TD ALIGN="right">Check #</TD> - <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<%= $payinfo %>" SIZE=10></TD> - </TR> +</%init> -<% } %> - -<TR> -<% if ( $link eq 'custnum' ) { %> - <TD ALIGN="right">Auto-apply<BR>to invoices</TD> - <TD COLSPAN=2><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD> -<% } elsif ( $link eq 'invnum' ) { %> - <TD ALIGN="right">Apply to</TD> - <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><%= $linknum %></B> only</TD> - <INPUT TYPE="hidden" NAME="apply" VALUE="no"> -<% } %> -</TR> - -</TABLE> - -<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%= $paybatch %>"> - -<BR> -<INPUT TYPE="submit" VALUE="Post payment"> - </FORM> - </BODY> -</HTML> diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi index ce1c86612..7a0432c5d 100755 --- a/httemplate/edit/cust_pkg.cgi +++ b/httemplate/edit/cust_pkg.cgi @@ -1,130 +1,152 @@ -<!-- mason kludge --> -<% - -my %pkg = (); -my %comment = (); -my %all_pkg = (); -my %all_comment = (); -#foreach (qsearch('part_pkg', { 'disabled' => '' })) { -# $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); -# $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); -#} -foreach (qsearch('part_pkg', {} )) { - $all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); - $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); - next if $_->disabled; - $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); - $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); -} - -my($custnum, %remove_pkg); -if ( $cgi->param('error') ) { - $custnum = $cgi->param('custnum'); - %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg'); -} else { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $custnum = $1; - %remove_pkg = (); -} - -my $p1 = popurl(1); -print header("Add/Edit Packages", ''); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/cust_pkg.cgi" METHOD=POST>!; - -print qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!; - -#current packages -my @cust_pkg = qsearch('cust_pkg',{ 'custnum' => $custnum, 'cancel' => '' } ); - -if (@cust_pkg) { - print <<END; -Current packages - select to remove (services are moved to a new package below) -<TABLE> - <TR STYLE="background-color: #cccccc;"> - <TH COLSPAN="2">Pkg #</TH> - <TH>Package description</TH> - </TR> -<BR><BR> -END +% +% +%my %pkg = (); +%my %comment = (); +%my %all_pkg = (); +%my %all_comment = (); +%#foreach (qsearch('part_pkg', { 'disabled' => '' })) { +%# $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +%# $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +%#} +%foreach (qsearch('part_pkg', {} )) { +% $all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +% $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +% next if $_->disabled; +% $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +% $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +%} +% +%my($custnum, %remove_pkg); +%if ( $cgi->param('error') ) { +% $custnum = $cgi->param('custnum'); +% %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $custnum = $1; +% %remove_pkg = (); +%} +% +%my $p1 = popurl(1); +% +% +<% include('/elements/header.html', "Add/Edit Packages", '') %> +% if ( $cgi->param('error') ) { - 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 <<END; - <TR> - <TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="$pkgnum"${checked}></TD> - <TD ALIGN="right">$pkgnum:</TD>\n - <TD>$all_pkg{$pkgpart} - $all_comment{$pkgpart}</TD> - </TR> -END - } - print qq!</TABLE><BR><BR>!; -} + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% $p1 %>process/cust_pkg.cgi" METHOD=POST> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +% +%#current packages +%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); +% +%if (@cust_pkg) { +% + + + Current packages - select to remove (services are moved to a new package below) + <TABLE> + <TR STYLE="background-color: #cccccc;"> + <TH COLSPAN="2">Pkg #</TH> + <TH>Package description</TH> + </TR> + <BR><BR> +% +% +% 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' : ''; +% +% + + + <TR> + <TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="<% $pkgnum %>"<% $checked %>></TD> + <TD ALIGN="right"><% $pkgnum %>:</TD> + <TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD> + </TR> +% } -print <<END; -Order new packages<BR><BR> -END -my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); -my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); + </TABLE> + <BR><BR> +% } + + +Order new packages +<BR><BR> +% +%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +%my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); +% +%my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) } +% qsearch('type_pkgs',{'typenum'=> $agent->typenum }); +% +%my $count = 0; +%my $pkgparts = 0; +% -my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) } - qsearch('type_pkgs',{'typenum'=> $agent->typenum }); -my $count = 0; -my $pkgparts = 0; -print <<END; <TABLE> <TR STYLE="background-color: #cccccc;"> <TH>Qty.</TH> <TH COLSPAN="2">Package Description</TH> </TR> -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) ) { - $pkgparts++; - next unless exists $pkg{$pkgpart}; #skip disabled ones - #print qq!<TR>! if ( $count == 0 ); - my $value = $cgi->param("pkg$pkgpart") || 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) ) { +% $pkgparts++; +% next unless exists $pkg{$pkgpart}; #skip disabled ones +% #print qq!<TR>! if ( $count == 0 ); +% my $value = $cgi->param("pkg$pkgpart") || 0; +% + + <TR> - <TD><INPUT TYPE="text" NAME="pkg$pkgpart" VALUE="$value" SIZE="2" MAXLENGTH="2"></TD> - <TD ALIGN="right">$pkgpart:</TD> - <TD>$pkg{$pkgpart} - $comment{$pkgpart}</TD> + <TD> + <INPUT TYPE="text" NAME="<% "pkg$pkgpart" %>" VALUE="<% $value %>" SIZE="2" MAXLENGTH="2"> + </TD> + <TD ALIGN="right"><% $pkgpart %>:</TD> + <TD><% $pkg{$pkgpart} %> - <% $comment{$pkgpart}%></TD> </TR> -END - $count ++ ; - #if ( $count == 2 ) { - # print qq!</TR>\n! ; - # $count = 0; - #} -} -print qq!</TABLE>!; - -unless ( $pkgparts ) { - my $p2 = popurl(2); - my $typenum = $agent->typenum; - my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); - my $atype = $agent_type->atype; - print <<END; -(No <a href="${p2}browse/part_pkg.cgi">package definitions</a>, or agent type -<a href="${p2}edit/agent_type.cgi?$typenum">$atype</a> not allowed to purchase -any packages.) -END -} - -#submit -print <<END; +% +% $count ++ ; +% #if ( $count == 2 ) { +% # print qq!</TR>\n! ; +% # $count = 0; +% #} +%} +% + + +</TABLE> +% unless ( $pkgparts ) { +% my $p2 = popurl(2); +% my $typenum = $agent->typenum; +% my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); +% my $atype = $agent_type->atype; +% + + + (No <A HREF="<% $p2 %>browse/part_pkg.cgi">package definitions</A>, + or agent type + <A HREF="<% $p2 %>edit/agent_type.cgi?<% $typenum %>"><% $atype %></a> + is not allowed to purchase any packages.) +% } + + <P><INPUT TYPE="submit" VALUE="Order"> - </FORM> - </BODY> -</HTML> -END -%> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 8955c7cee..aa825af94 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -1,94 +1,126 @@ -<!-- mason kludge --> -<% - -my $conf = new FS::Conf; -my $custnum = $cgi->param('custnum'); -my $refund = $cgi->param('refund'); -my $payby = $cgi->param('payby'); -my $reason = $cgi->param('reason'); - -my( $paynum, $cust_pay ) = ( '', '' ); -if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { - $paynum = $1; - $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } ) - or die "unknown payment # $paynum"; - $refund ||= $cust_pay->unrefunded; - if ( $custnum ) { - die "payment # $paynum is not for specified customer # $custnum" - unless $custnum == $cust_pay->custnum; - } else { - $custnum = $cust_pay->custnum; - } -} -die "no custnum or paynum specified!" unless $custnum; - -my $_date = time; - -my $p1 = popurl(1); - -print header('Refund '. ucfirst(lc($payby)). ' payment', ''); -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); -print <<END, small_custview($custnum, $conf->config('countrydefault')); - <FORM ACTION="${p1}process/cust_refund.cgi" METHOD=POST> - <INPUT TYPE="hidden" NAME="refundnum" VALUE=""> - <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum"> - <INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum"> - <INPUT TYPE="hidden" NAME="_date" VALUE="$_date"> - <INPUT TYPE="hidden" NAME="payby" VALUE="$payby"> - <INPUT TYPE="hidden" NAME="payinfo" VALUE=""> - <INPUT TYPE="hidden" NAME="paybatch" VALUE=""> - <INPUT TYPE="hidden" NAME="credited" VALUE=""> - <BR> -END - -if ( $cust_pay ) { - - #false laziness w/FS/FS/cust_pay.pm - my $payby = $cust_pay->payby; - my $payinfo = $cust_pay->payinfo; - $payby =~ s/^BILL$/Check/ if $payinfo; - $payby =~ s/^CHEK$/Electronic check/; - $payinfo = $cust_pay->payinfo_masked if $payby eq 'CARD'; - - print '<BR>Payment'. ntable("#cccccc", 2). - '<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$'. - $cust_pay->paid. '</TD></TR>'. - '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. - time2str("%D",$cust_pay->_date). '</TD></TR>'. - '<TR><TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff">'. - ucfirst(lc($payby)). ' # '. $payinfo. '</TD></TR>'; - #false laziness w/FS/FS/cust_main::realtime_refund_bop - if ( $cust_pay->paybatch =~ /^(\w+):(\w+)(:(\w+))?$/ ) { - my ( $processor, $auth, $order_number ) = ( $1, $2, $4 ); - print '<TR><TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff">'. - $processor. '</TD></TR>'; - print '<TR><TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff">'. - $auth. '</TD></TR>' - if length($auth); - print '<TR><TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff">'. - $order_number. '</TD></TR>' - if length($order_number); - } - print '</TABLE>'; -} - -print '<BR>Refund'. ntable("#cccccc", 2). - '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. - time2str("%D",$_date). '</TD></TR>'; - -print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="refund" VALUE="$refund" SIZE=8 MAXLENGTH=8></TD></TR>!; - -print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!; - -print <<END; +% +% +%my $conf = new FS::Conf; +%my $custnum = $cgi->param('custnum'); +%my $refund = $cgi->param('refund'); +%my $payby = $cgi->param('payby'); +%my $reason = $cgi->param('reason'); +% +%my( $paynum, $cust_pay ) = ( '', '' ); +%if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { +% $paynum = $1; +% $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } ) +% or die "unknown payment # $paynum"; +% $refund ||= $cust_pay->unrefunded; +% if ( $custnum ) { +% die "payment # $paynum is not for specified customer # $custnum" +% unless $custnum == $cust_pay->custnum; +% } else { +% $custnum = $cust_pay->custnum; +% } +%} +%die "no custnum or paynum specified!" unless $custnum; +% +%my $_date = time; +% +%my $p1 = popurl(1); +% +% + + +<% include('/elements/header.html', 'Refund '. ucfirst(lc($payby)). ' payment', '') %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + + +<% small_custview($custnum, $conf->config('countrydefault')) %> + +<FORM NAME="RefundForm" ACTION="<% $p1 %>process/cust_refund.cgi" METHOD=POST onSubmit="document.RefundForm.submit.disabled=true"> +<INPUT TYPE="hidden" NAME="refundnum" VALUE=""> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>"> +<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>"> +<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> +<INPUT TYPE="hidden" NAME="payinfo" VALUE=""> +<INPUT TYPE="hidden" NAME="paybatch" VALUE=""> +<INPUT TYPE="hidden" NAME="credited" VALUE=""> +<BR> +% if ( $cust_pay ) { +% +% #false laziness w/FS/FS/cust_pay.pm +% my $payby = $cust_pay->payby; +% my $paymask = $cust_pay->paymask; +% $payby =~ s/^BILL$/Check/ if $paymask; +% $payby =~ s/^CHEK$/Electronic check/; +% +% + + + <BR>Payment + <% ntable("#cccccc", 2) %> + + <TR> + <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<% $cust_pay->paid %></TD> + </TR> + + <TR> + <TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff"><% time2str("%D",$cust_pay->_date) %></TD> + </TR> + + <TR> + <TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff"><% ucfirst(lc($payby)) %> # <% $paymask %></TD> + </TR> +% +% #false laziness w/FS/FS/cust_main::realtime_refund_bop +% if ( $cust_pay->paybatch =~ /^(\w+):(\w+)(:(\w+))?$/ ) { +% my ( $processor, $auth, $order_number ) = ( $1, $2, $4 ); +% + + + <TR> + <TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff"><% $processor %></TD> + </TR> +% if ( length($auth) ) { + + <TR> + <TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff"><% $auth %></TD> + </TR> +% } +% if ( length($order_number) ) { + + <TR> + <TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff"><% $order_number %></TD> + </TR> +% } +% } + + </TABLE> +% } + + +<BR>Refund +<% ntable("#cccccc", 2) %> + + <TR> + <TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD> + </TR> + + <TR> + <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="refund" VALUE="<% $refund %>" SIZE=8 MAXLENGTH=8></TD> + </TR> + + <TR> + <TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="<% $reason %>"></TD> + </TR> </TABLE> + <BR> -<INPUT TYPE="submit" VALUE="Post refund"> - </FORM> - </BODY> -</HTML> -END +<INPUT TYPE="submit" NAME="submit" VALUE="Post refund"> + +</FORM> + +<% include('/elements/footer.html') %> -%> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html new file mode 100644 index 000000000..17c5ad3eb --- /dev/null +++ b/httemplate/edit/elements/edit.html @@ -0,0 +1,234 @@ +% +% +% # options example... +% # +% # 'name' => +% # 'table' => +% # #? 'primary_key' => #required when the dbdef doesn't know...??? +% # 'labels' => { +% # 'column' => 'Label', +% # } +% # +% # listref - each item is a literal column name (or method) or hashref +% # or (notyet) coderef +% # if not specified all columns (except for the primary key) will be editable +% # 'fields' => [ +% # 'columname', +% # { 'field' => 'another_columname', +% # 'type' => 'text', #text +% # #checkbox +% # #select +% # #hidden - hidden value from object +% # #fixed - display fixed value from here +% # #fixedhidden - hidden value from here +% # 'value' => 'Y', #for checkbox, fixed, fixedhidden +% # }, +% # ] +% # +% # 'menubar' => '', #menubar arrayref +% # +% # #run when re-displaying with an error +% # 'error_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # #run when editing +% # 'edit_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # # returns a hashref for the new object +% # 'new_hashref_callback' +% # +% # #run when adding +% # 'new_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # #XXX describe +% # 'field_callback' => sub { }, +% # +% # #string or coderef of additional HTML to add before </TABLE> +% # 'html_table_bottom' => '', +% # +% # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' +% # +% # 'html_bottom' => '', #string +% # 'html_bottom' => sub { +% # my $object = shift; +% # # ... +% # "html_string"; +% # }, +% # +% # # overrides default popurl(1)."process/$table.html" +% # 'post_url' => popurl(1).'process/something', +% +% my(%opt) = @_; +% +% #false laziness w/process.html +% my $table = $opt{'table'}; +% my $class = "FS::$table"; +% my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || +% 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') ) { +% +% $object = $class->new( { +% map { $_ => scalar($cgi->param($_)) } fields($table) +% }); +% +% &{$opt{'error_callback'}}($cgi, $object) +% if $opt{'error_callback'}; +% +% } elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing +% +% my $value; +% if ( $cgi->param($pkey) ) { +% $value = $cgi->param($pkey) +% } else { +% my( $query ) = $cgi->keywords; +% $value = $query; +% } +% $value =~ /^(\d+)$/ or die "unparsable $pkey"; +% $object = qsearchs( $table, { $pkey => $1 } ); +% warn "$table $pkey => $1" +% if $opt{'debug'}; +% +% &{$opt{'edit_callback'}}($cgi, $object) +% if $opt{'edit_callback'}; +% +% } else { #adding +% +% my $hashref = $opt{'new_hashref_callback'} +% ? &{$opt{'new_hashref_callback'}} +% : {}; +% +% $object = $class->new( $hashref ); +% +% &{$opt{'new_callback'}}($cgi, $object) +% if $opt{'new_callback'}; +% +% } +% +% my $action = $object->$pkey() ? 'Edit' : 'Add'; +% +% my $title = "$action $opt{'name'}"; +% +% my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html"; +% $viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'}; +% +% my @menubar = (); +% if ( $opt{'menubar'} ) { +% @menubar = @{ $opt{'menubar'} }; +% } else { +% @menubar = ( +% 'Main menu' => $p, #eventually get rid of this when the ACL/UI update is done +% #eventually use Lingua::bs to pluralize +% "View all $opt{'name'}s" => $viewall_url, +% ); +% } +% +% +<% include("/elements/header.html", $title, + include( '/elements/menubar.html', @menubar ) + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +% my $url = $opt{'post_url'} || popurl(1)."process/$table.html"; + +<FORM ACTION="<% $url %>" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>"> +<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $object->$pkey() %>"> +<% ( $opt{labels} && exists $opt{labels}->{$pkey} ) + ? $opt{labels}->{$pkey} + : $pkey +%> +#<% $object->$pkey() || "(NEW)" %> + +<% ntable("#cccccc",2) %> +% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} } +% @$fields +% ) { +% +% &{ $opt{'field_callback'} }( $f ) +% if $opt{'field_callback'}; +% +% my $field = $f->{'field'}; +% my $type = $f->{'type'} ||= 'text'; +% +% + + + <TR> + + <TD ALIGN="right"> + <% ( $opt{labels} && exists $opt{labels}->{$field} ) + ? $opt{labels}->{$field} + : $field + %> + </TD> + +% if ( $type eq 'fixed' ) { + + <TD BGCOLOR="#dddddd"><% $f->{'value'} %></TD> + <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $f->{'value'} %>"> + +% } elsif ( $type eq 'fixedhidden' ) { + + <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $f->{'value'} %>"> + +% } elsif ( $type eq 'checkbox' ) { + + <TD> + <INPUT TYPE="checkbox" NAME="<% $field %>" VALUE="<% $f->{'value'} %>" <% $object->$field() eq $f->{'value'} ? ' CHECKED' : '' %>> + </TD> + +% } elsif ( $type eq 'select' ) { + + <TD> + <SELECT NAME="<% $field %>" +% my $aref = $f->{'value'}{'values'}; +% my $vkey = $f->{'value'}{'vcolumn'}; +% my $ckey = $f->{'value'}{'ccolumn'}; +% foreach my $v (@$aref) { + <OPTION <% ($object->$field() eq $v->$vkey) ? 'SELECTED' : '' %> + VALUE="<% $v->$vkey %>"><% $v->$ckey %></OPTION> +% } + </SELECT> + </TD> + +% } else { + + <TD> + <INPUT TYPE="<% $type %>" NAME="<% $field %>" VALUE="<% $object->$field() %>"> + <TD> + +% } + + </TR> + +% } + +<% ref( $opt{'html_table_bottom'} ) + ? &{ $opt{'html_table_bottom'} }( $object ) + : $opt{'html_table_bottom'} +%> + +</TABLE> + +<% ref( $opt{'html_bottom'} ) + ? &{ $opt{'html_bottom'} }( $object ) + : $opt{'html_bottom'} +%> + +<BR> + +<INPUT TYPE="submit" VALUE="<% $object->$pkey() ? "Apply changes" : "Add $opt{'name'}" %>"> + +</FORM> + +<% include("/elements/footer.html") %> + diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html new file mode 100644 index 000000000..1fd66c251 --- /dev/null +++ b/httemplate/edit/elements/svc_Common.html @@ -0,0 +1,101 @@ +% +% my %opt = @_; +% +% #my( $svcnum, $pkgnum, $svcpart, $part_svc ); +% my( $pkgnum, $svcpart, $part_svc ); +% +% #get & untaint pkgnum & svcpart +% if ( ! $cgi->param('error') +% && $cgi->param('pkgnum') && $cgi->param('svcpart') +% ) +% { +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% $cgi->delete_all(); #so edit.html treats this correctly as new?? +% } +% +<% include( 'edit.html', + + 'menubar' => [], + + 'error_callback' => sub { + my( $cgi, $svc_x ) = @_; + #$svcnum = $svc_x->svcnum; + $pkgnum = $cgi->param('pkgnum'); + $svcpart = $cgi->param('svcpart'); + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + }, + + 'edit_callback' => sub { + my( $cgi, $svc_x ) = @_; + #$svcnum = $svc_x->svcnum; + my $cust_svc = $svc_x->cust_svc + or die "Unknown (cust_svc) svcnum!"; + + $pkgnum = $cust_svc->pkgnum; + $svcpart = $cust_svc->svcpart; + + $part_svc = qsearchs ('part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + }, + + 'new_hash_callback' => sub { + #my( $cgi, $svc_x ) = @_; + + { svcpart => $svcpart }; + + }, + + 'new_callback' => sub { + my( $cgi, $svc_x ) = @_;; + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + + #$svcnum=''; + + $svc_x->set_default_and_fixed; + + }, + + 'field_callback' => sub { + my $f = shift; + my $columndef = $part_svc->part_svc_column($f->{'field'}); + my $flag = $columndef->columnflag; + if ( $flag eq 'F' ) { + $f->{'type'} = 'fixed'; + $f->{'value'} = $columndef->columnvalue; + } + }, + + 'html_table_bottom' => sub { + my $svc_x = shift; + my $html = ''; + foreach my $field ($svc_x->virtual_fields) { + if ($part_svc->part_svc_column($field)->columnflag ne 'F'){ + # If the flag is X, it won't even show up + # in $svc_acct->virtual_fields. + $html .= + $svc_x->pvf($field)->widget( 'HTML', + 'edit', + $svc_x->getfield($field) + ); + } + } + $html; + }, + + 'html_bottom' => sub { + qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!. + qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; + }, + + 'debug' => 1, + + %opt #pass through/override params + ) +%> diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html new file mode 100644 index 000000000..beefcd580 --- /dev/null +++ b/httemplate/edit/inventory_class.html @@ -0,0 +1,10 @@ +<% include( 'elements/edit.html', + 'name' => 'Inventory Class', + 'table' => 'inventory_class', + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + }, + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/msgcat.cgi b/httemplate/edit/msgcat.cgi index ee9b1c6b3..b46cdfd46 100755 --- a/httemplate/edit/msgcat.cgi +++ b/httemplate/edit/msgcat.cgi @@ -1,13 +1,20 @@ -<!-- mason kludge --> -<% +<% header("Edit Message catalog" ) %> +<BR> -print header("Edit Message catalog", menubar( -# 'Main Menu' => $p, -)), '<BR>'; +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !. $cgi->param('error'). - '</FONT><BR><BR>' - if $cgi->param('error'); +<% $widget->html %> + + </TABLE> + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => 'en_US', @@ -47,12 +54,4 @@ my $widget = new HTML::Widgets::SelectLayers( ); -print $widget->html; - -print <<END; - </TABLE> - </BODY> -</HTML> -END - -%> +</%init> diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi index 32ca47af4..0921a9577 100755 --- a/httemplate/edit/part_bill_event.cgi +++ b/httemplate/edit/part_bill_event.cgi @@ -1,376 +1,531 @@ -<!-- mason kludge --> -<% - -if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) { - $cgi->param('eventpart', $1); -} else { - $cgi->param('eventpart', ''); -} - -my ($query) = $cgi->keywords; -my $action = ''; -my $part_bill_event = ''; -if ( $cgi->param('error') ) { - $part_bill_event = new FS::part_bill_event ( { - map { $_, scalar($cgi->param($_)) } fields('part_bill_event') - } ); -} -if ( $query && $query =~ /^(\d+)$/ ) { - $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1}); -} else { - $part_bill_event ||= new FS::part_bill_event {}; -} -$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add'; -my $hashref = $part_bill_event->hashref; - -print header("$action Invoice Event Definition", menubar( - 'Main Menu' => popurl(2), - 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi', -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print '<FORM ACTION="', popurl(1), 'process/part_bill_event.cgi" METHOD=POST>'. - '<INPUT TYPE="hidden" NAME="eventpart" VALUE="'. - $part_bill_event->eventpart .'">'; -print "Invoice Event #", $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)"; - -print ntable("#cccccc",2), <<END; -<TR><TD ALIGN="right">Payby</TD><TD><SELECT NAME="payby"> -END - -for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) { - print qq!<OPTION VALUE="$_"!; - if ($part_bill_event->payby eq $_) { - print " SELECTED>$_</OPTION>"; - } else { - print ">$_</OPTION>"; - } -} - -my $days = $hashref->{seconds}/86400; - -print <<END; -</SELECT></TD></TR> -<TR><TD ALIGN="right">Event</TD><TD><INPUT TYPE="text" NAME="event" VALUE="$hashref->{event}"></TD></TR> -<TR><TD ALIGN="right">After</TD><TD><INPUT TYPE="text" NAME="days" VALUE="$days"> days</TD></TR> -END - -print '<TR><TD ALIGN="right">Disabled</TD><TD>'; -print '<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"'; -print ' CHECKED' if $hashref->{disabled} eq "Y"; -print '>'; -print '</TD></TR>'; - -print '<TR><TD ALIGN="right">Action</TD><TD>'; - -#print ntable(); - -sub select_pkgpart { - my $label = shift; - my $plandata = shift; - my %selected = map { $_=>1 } split(/,\s*/, $plandata->{$label}); - qq(<SELECT NAME="$label" MULTIPLE>). - join("\n", map { - '<OPTION VALUE="'. $_->pkgpart. '"'. - ( $selected{$_->pkgpart} ? ' SELECTED' : '' ). - '>'. $_->pkg. ' - '. $_->comment - } qsearch('part_pkg', { 'disabled' => '' } ) ). - '</SELECT>'; -} - -sub select_agentnum { - my $plandata = shift; - #my $agentnum = $plandata->{'agentnum'}; - my %agentnums = map { $_=>1 } split(/,\s*/, $plandata->{'agentnum'}); - '<SELECT NAME="agentnum" MULTIPLE>'. - join("\n", map { - '<OPTION VALUE="'. $_->agentnum. '"'. - ( $agentnums{$_->agentnum} ? ' SELECTED' : '' ). - '>'. $_->agent - } qsearch('agent', { 'disabled' => '' } ) ). - '</SELECT>'; -} - -my $conf = new FS::Conf; -my $money_char = $conf->config('money_char') || '$'; - -#this is pretty kludgy right here. -tie my %events, 'Tie::IxHash', - - 'fee' => { - 'name' => 'Late fee', - 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\' );', - 'html' => - 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'. - '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', - 'weight' => 10, - }, - 'suspend' => { - 'name' => 'Suspend', - 'code' => '$cust_main->suspend();', - 'weight' => 10, - }, - 'suspend' => { - 'name' => 'Suspend if balance (this invoice and previous) over', - 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%% );', - 'html' => " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%">', - 'weight' => 10, - }, - 'suspend-if-pkgpart' => { - 'name' => 'Suspend packages', - 'code' => '$cust_main->suspend_if_pkgpart(%%%if_pkgpart%%%);', - 'html' => sub { &select_pkgpart('if_pkgpart', @_) }, - 'weight' => 10, - }, - 'suspend-unless-pkgpart' => { - 'name' => 'Suspend packages except', - 'code' => '$cust_main->suspend_unless_pkgpart(%%%unless_pkgpart%%%);', - 'html' => sub { &select_pkgpart('unless_pkgpart', @_) }, - 'weight' => 10, - }, - 'cancel' => { - 'name' => 'Cancel', - 'code' => '$cust_main->cancel();', - 'weight' => 10, - }, - - 'addpost' => { - 'name' => 'Add postal invoicing', - 'code' => '$cust_main->invoicing_list_addpost(); "";', - 'weight' => 20, - }, - - 'comp' => { - 'name' => 'Pay invoice with a complimentary "payment"', - 'code' => '$cust_bill->comp();', - 'weight' => 30, - }, - - 'realtime-card' => { - 'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', - 'code' => '$cust_bill->realtime_card();', - 'weight' => 30, - }, - - 'realtime-check' => { - 'name' => 'Run check with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', - 'code' => '$cust_bill->realtime_ach();', - 'weight' => 30, - }, - - 'realtime-lec' => { - 'name' => 'Run phone bill ("LEC") billing with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', - 'code' => '$cust_bill->realtime_lec();', - 'weight' => 30, - }, - - 'batch-card' => { - 'name' => 'Add card to the pending credit card batch', - 'code' => '$cust_bill->batch_card();', - 'weight' => 40, - }, - - 'send' => { - 'name' => 'Send invoice (email/print)', - 'code' => '$cust_bill->send();', - 'weight' => 50, - }, - - 'send_alternate' => { - 'name' => 'Send invoice (email/print) with alternate template', - 'code' => '$cust_bill->send(\'%%%templatename%%%\');', - 'html' => - '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">', - 'weight' => 50, - }, - - 'send_if_newest' => { - 'name' => 'Send invoice (email/print) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)', - 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');', - 'html' => - '<INPUT TYPE="text" NAME="if_newest_templatename" VALUE="%%%if_newest_templatename%%%">', - 'weight' => 50, - }, - - 'send_agent' => { - 'name' => 'Send invoice (email/print) ', - 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', [ %%%agentnum%%% ], \'%%%agent_invoice_from%%%\');', - 'html' => sub { - '<TABLE BORDER=0> - <TR> - <TD ALIGN="right">only for agent(s) </TD> - <TD>'. &select_agentnum(@_). '</TD> - </TR> - <TR> - <TD ALIGN="right">with template </TD> - <TD> - <INPUT TYPE="text" NAME="agent_templatename" VALUE="%%%agent_templatename%%%"> - </TD> - </TR> - <TR> - <TD ALIGN="right">email From: </TD> - <TD> - <INPUT TYPE="text" NAME="agent_invoice_from" VALUE="%%%agent_invoice_from%%%"> - </TD> - </TR> - </TABLE>'; - }, - 'weight' => 50, - }, - - 'send_csv_ftp' => { - 'name' => 'Upload CSV invoice data to an FTP server', - 'code' => '$cust_bill->send_csv( protocol => \'ftp\', - server => \'%%%ftpserver%%%\', - username => \'%%%ftpusername%%%\', - password => \'%%%ftppassword%%%\', - dir => \'%%%ftpdir%%%\', - \'format\' => \'%%%ftpformat%%%\', - );', - 'html' => - '<TABLE BORDER=0>'. - '<TR><TD ALIGN="right">Format ("default" or "billco"): </TD>'. - '<TD>'. - '<!--'. - '<SELECT NAME="ftpformat">'. - '<OPTION VALUE="default">Default'. - '<OPTION VALUE="billco">Billco'. - '</SELECT>'. - '-->'. - '<INPUT TYPE="text" NAME="ftpformat" VALUE="%%%ftpformat%%%">'. - '</TD></TR>'. - '<TR><TD ALIGN="right">FTP server: </TD>'. - '<TD><INPUT TYPE="text" NAME="ftpserver" VALUE="%%%ftpserver%%%">'. - '</TD></TR>'. - '<TR><TD ALIGN="right">FTP username: </TD><TD>'. - '<INPUT TYPE="text" NAME="ftpusername" VALUE="%%%ftpusername%%%">'. - '</TD></TR>'. - '<TR><TD ALIGN="right">FTP password: </TD><TD>'. - '<INPUT TYPE="text" NAME="ftppassword" VALUE="%%%ftppassword%%%">'. - '</TD></TR>'. - '<TR><TD ALIGN="right">FTP directory: </TD>'. - '<TD><INPUT TYPE="text" NAME="ftpdir" VALUE="%%%ftpdir%%%">'. - '</TD></TR>'. - '</TABLE>', - 'weight' => 50, - }, - - 'spool_csv' => { - 'name' => 'Spool CSV invoice data', - 'code' => '$cust_bill->spool_csv( - \'format\' => \'%%%spoolformat%%%\', - \'dest\' => \'%%%spooldest%%%\', - \'agent_spools\' => \'%%%spoolagent_spools%%%\', - );', - 'html' => sub { - my $plandata = shift; - - my $html = - '<TABLE BORDER=0>'. - '<TR><TD ALIGN="right">Format: </TD>'. - '<TD>'. - '<SELECT NAME="spoolformat">'; - - foreach my $option (qw( default billco )) { - $html .= qq(<OPTION VALUE="$option"); - $html .= ' SELECTED' if $option eq $plandata->{'spoolformat'}; - $html .= ">\u$option"; - } - - $html .= - '</SELECT>'. - '</TD></TR>'. - '<TR><TD ALIGN="right">For destination: </TD>'. - '<TD>'. - '<SELECT NAME="spooldest">'; - - tie my %dest, 'Tie::IxHash', - '' => '(all)', - 'POST' => 'Postal Mail', - 'EMAIL' => 'Email', - 'FAX' => 'Fax', - ; - - foreach my $dest (keys %dest) { - $html .= qq(<OPTION VALUE="$dest"); - $html .= ' SELECTED' if $dest eq $plandata->{'spooldest'}; - $html .= '>'. $dest{$dest}; - } - - $html .= - '</SELECT>'. - '</TD></TR>'. - '<TR><TD ALIGN="right">Individual per-agent spools? </TD>'. - '<TD><INPUT TYPE="checkbox" NAME="spoolagent_spools" VALUE="1" '. - ( $plandata->{'spoolagent_spools'} ? 'CHECKED' : '' ). - '>'. - '</TD></TR>'. - '</TABLE>'; - - $html; - }, - 'weight' => 50, - }, - - 'bill' => { - 'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)', - 'code' => '$cust_main->bill();', - 'weight' => 60, - }, - - 'apply' => { - 'name' => 'Apply unapplied payments and credits', - 'code' => '$cust_main->apply_payments; $cust_main->apply_credits; "";', - 'weight' => 70, - }, - - 'collect' => { - 'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)', - 'code' => '$cust_main->collect();', - 'weight' => 80, - }, +<!--mason kludge--> +% +% +%if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) { +% $cgi->param('eventpart', $1); +%} else { +% $cgi->param('eventpart', ''); +%} +% +%my ($creason, $newcreasonT, $newcreason); +%my ($sreason, $newsreasonT, $newsreason); +% +% +%my ($query) = $cgi->keywords; +%my $action = ''; +%my $part_bill_event = ''; +%my $currentreasonclass = ''; +%if ( $cgi->param('error') ) { +% $part_bill_event = new FS::part_bill_event ( { +% map { $_, scalar($cgi->param($_)) } fields('part_bill_event') +% } ); +%} +%if ( $query && $query =~ /^(\d+)$/ ) { +% $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1}); +%} else { +% $part_bill_event ||= new FS::part_bill_event {}; +%} +%$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add'; +%my $hashref = $part_bill_event->hashref; +% +% + + +<% include('/elements/header.html', + "$action Invoice Event Definition", + menubar( + 'Main Menu' => popurl(2), + 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi', + ) + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% popurl(1) %>process/part_bill_event.cgi" NAME="editEvent" METHOD=POST> +<INPUT TYPE="hidden" NAME="eventpart" VALUE="<% $part_bill_event->eventpart %>"> +Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %> + +<% ntable("#cccccc",2) %> + + <TR> + <TD ALIGN="right">Event name </TD> + <TD><INPUT TYPE="text" NAME="event" VALUE="<% $hashref->{event} %>"></TD> + </TR> + + <TR> + <TD ALIGN="right">For </TD> + <TD> + <SELECT NAME="payby"> +% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname; +% foreach my $payby ( keys %payby ) { +% + + + <OPTION VALUE="<% $payby %>"<% ($part_bill_event->payby eq $payby) ? ' SELECTED' : '' %>><% $payby{$payby} %></OPTION> +% } + + + </SELECT> customers + </TD> + </TR> +% my $days = $hashref->{seconds}/86400; + + + <TR> + <TD ALIGN="right">After</TD> + <TD><INPUT TYPE="text" NAME="days" VALUE="<% $days %>"> days</TD> + </TR> + + <TR> + <TD ALIGN="right">Test event</TD> + <TD> + <SELECT NAME="freq"> +% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly'; +% foreach my $freq ( keys %freq ) { +% + + + <OPTION VALUE="<% $freq %>"<% ($part_bill_event->freq eq $freq) ? ' SELECTED' : '' %>><% $freq{$freq} %></OPTION> +% } + + + </SELECT> + </TD> + </TR> + + + <TR> + <TD ALIGN="right">Disabled</TD> + <TD> + <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>> + </TD> + </TR> + + <TR> + <TD VALIGN="top" ALIGN="right">Action</TD> + <TD> +% +% +%#print ntable(); +% +%sub select_pkgpart { +% my $label = shift; +% my $plandata = shift; +% my %selected = map { $_=>1 } split(/,\s*/, $plandata->{$label}); +% qq(<SELECT NAME="$label" MULTIPLE>). +% join("\n", map { +% '<OPTION VALUE="'. $_->pkgpart. '"'. +% ( $selected{$_->pkgpart} ? ' SELECTED' : '' ). +% '>'. $_->pkg. ' - '. $_->comment +% } qsearch('part_pkg', { 'disabled' => '' } ) ). +% '</SELECT>'; +%} +% +%sub select_agentnum { +% my $plandata = shift; +% #my $agentnum = $plandata->{'agentnum'}; +% my %agentnums = map { $_=>1 } split(/,\s*/, $plandata->{'agentnum'}); +% '<SELECT NAME="agentnum" MULTIPLE>'. +% join("\n", map { +% '<OPTION VALUE="'. $_->agentnum. '"'. +% ( $agentnums{$_->agentnum} ? ' SELECTED' : '' ). +% '>'. $_->agent +% } qsearch('agent', { 'disabled' => '' } ) ). +% '</SELECT>'; +%} +% +%my $conf = new FS::Conf; +%my $money_char = $conf->config('money_char') || '$'; +% +%#this is pretty kludgy right here. +%tie my %events, 'Tie::IxHash', +% +% 'fee' => { +% 'name' => 'Late fee (flat)', +% 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\' );', +% 'html' => +% 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'. +% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +% 'weight' => 10, +% }, +% 'fee_percent' => { +% 'name' => 'Late fee (percentage)', +% 'code' => '$cust_main->charge( sprintf(\'%.2f\', $cust_bill->owed * %%%percent%%% / 100 ), \'%%%reason%%%\' );', +% 'html' => +% 'Percent <INPUT TYPE="text" SIZE="2" NAME="percent" VALUE="%%%percent%%%">%'. +% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +% 'weight' => 10, +% }, +% 'suspend' => { +% 'name' => 'Suspend', +% 'code' => '$cust_main->suspend(reason => %%%sreason%%%);', +% 'weight' => 10, +% 'reason' => 'S', +% }, +% 'suspend-if-balance' => { +% 'name' => 'Suspend if balance (this invoice and previous) over', +% 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%%, reason => %%%sreason%%%, );', +% 'html' => " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%">', +% 'weight' => 10, +% 'reason' => 'S', +% }, +% 'suspend-if-pkgpart' => { +% 'name' => 'Suspend packages', +% 'code' => '$cust_main->suspend_if_pkgpart({pkgparts => [%%%if_pkgpart%%%,], reason => %%%sreason%%%,});', +% 'html' => sub { &select_pkgpart('if_pkgpart', @_) }, +% 'weight' => 10, +% 'reason' => 'S', +% }, +% 'suspend-unless-pkgpart' => { +% 'name' => 'Suspend packages except', +% 'code' => '$cust_main->suspend_unless_pkgpart({unless_pkgpart => [%%%unless_pkgpart%%%], reason => %%%sreason%%%,});', +% 'html' => sub { &select_pkgpart('unless_pkgpart', @_) }, +% 'weight' => 10, +% 'reason' => 'S', +% }, +% 'cancel' => { +% 'name' => 'Cancel', +% 'code' => '$cust_main->cancel(reason => %%%creason%%%);', +% 'weight' => 10, +% 'reason' => 'C', +% }, +% +% 'addpost' => { +% 'name' => 'Add postal invoicing', +% 'code' => '$cust_main->invoicing_list_addpost(); "";', +% 'weight' => 20, +% }, +% +% 'comp' => { +% 'name' => 'Pay invoice with a complimentary "payment"', +% 'code' => '$cust_bill->comp();', +% 'weight' => 30, +% }, +% +% 'credit' => { +% 'name' => "Create and apply a credit for the customer's balance (i.e. write off as bad debt)", +% 'code' => '$cust_main->credit( $cust_main->balance, \'%%%reason%%%\' );', +% 'html' => '<INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +% 'weight' => 30, +% }, +% +% 'realtime-card' => { +% 'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_card();', +% 'weight' => 30, +% }, +% +% 'realtime-check' => { +% 'name' => 'Run check with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_ach();', +% 'weight' => 30, +% }, +% +% 'realtime-lec' => { +% 'name' => 'Run phone bill ("LEC") billing with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_lec();', +% 'weight' => 30, +% }, +% +% 'batch-card' => { +% 'name' => 'Add card or check to a pending batch', +% 'code' => '$cust_bill->batch_card(%options);', +% 'weight' => 40, +% }, +% +% +% #'retriable' => { +% # 'name' => 'Mark batched card event as retriable', +% # 'code' => '$cust_pay_batch->retriable();', +% # 'weight' => 60, +% #}, +% +% 'send' => { +% 'name' => 'Send invoice (email/print/fax)', +% 'code' => '$cust_bill->send();', +% 'weight' => 50, +% }, +% +% 'send_email' => { +% 'name' => 'Send invoice (email only)', +% 'code' => '$cust_bill->email();', +% 'weight' => 50, +% }, +% +% 'send_alternate' => { +% 'name' => 'Send invoice (email/print/fax) with alternate template', +% 'code' => '$cust_bill->send(\'%%%templatename%%%\');', +% 'html' => +% '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">', +% 'weight' => 50, +% }, +% +% 'send_if_newest' => { +% 'name' => 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)', +% 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');', +% 'html' => +% '<INPUT TYPE="text" NAME="if_newest_templatename" VALUE="%%%if_newest_templatename%%%">', +% 'weight' => 50, +% }, +% +% 'send_agent' => { +% 'name' => 'Send invoice (email/print/fax) ', +% 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', [ %%%agentnum%%% ], \'%%%agent_invoice_from%%%\');', +% 'html' => sub { +% '<TABLE BORDER=0> +% <TR> +% <TD ALIGN="right">only for agent(s) </TD> +% <TD>'. &select_agentnum(@_). '</TD> +% </TR> +% <TR> +% <TD ALIGN="right">with template </TD> +% <TD> +% <INPUT TYPE="text" NAME="agent_templatename" VALUE="%%%agent_templatename%%%"> +% </TD> +% </TR> +% <TR> +% <TD ALIGN="right">email From: </TD> +% <TD> +% <INPUT TYPE="text" NAME="agent_invoice_from" VALUE="%%%agent_invoice_from%%%"> +% </TD> +% </TR> +% </TABLE>'; +% }, +% 'weight' => 50, +% }, +% +% 'send_csv_ftp' => { +% 'name' => 'Upload CSV invoice data to an FTP server', +% 'code' => '$cust_bill->send_csv( protocol => \'ftp\', +% server => \'%%%ftpserver%%%\', +% username => \'%%%ftpusername%%%\', +% password => \'%%%ftppassword%%%\', +% dir => \'%%%ftpdir%%%\', +% \'format\' => \'%%%ftpformat%%%\', +% );', +% 'html' => +% '<TABLE BORDER=0>'. +% '<TR><TD ALIGN="right">Format ("default" or "billco"): </TD>'. +% '<TD>'. +% '<!--'. +% '<SELECT NAME="ftpformat">'. +% '<OPTION VALUE="default">Default'. +% '<OPTION VALUE="billco">Billco'. +% '</SELECT>'. +% '-->'. +% '<INPUT TYPE="text" NAME="ftpformat" VALUE="%%%ftpformat%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP server: </TD>'. +% '<TD><INPUT TYPE="text" NAME="ftpserver" VALUE="%%%ftpserver%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP username: </TD><TD>'. +% '<INPUT TYPE="text" NAME="ftpusername" VALUE="%%%ftpusername%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP password: </TD><TD>'. +% '<INPUT TYPE="text" NAME="ftppassword" VALUE="%%%ftppassword%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP directory: </TD>'. +% '<TD><INPUT TYPE="text" NAME="ftpdir" VALUE="%%%ftpdir%%%">'. +% '</TD></TR>'. +% '</TABLE>', +% 'weight' => 50, +% }, +% +% 'spool_csv' => { +% 'name' => 'Spool CSV invoice data', +% 'code' => '$cust_bill->spool_csv( +% \'format\' => \'%%%spoolformat%%%\', +% \'dest\' => \'%%%spooldest%%%\', +% \'balanceover\' => \'%%%spoolbalanceover%%%\', +% \'agent_spools\' => \'%%%spoolagent_spools%%%\', +% );', +% 'html' => sub { +% my $plandata = shift; +% +% my $html = +% '<TABLE BORDER=0>'. +% '<TR><TD ALIGN="right">Format: </TD>'. +% '<TD>'. +% '<SELECT NAME="spoolformat">'; +% +% foreach my $option (qw( default billco )) { +% $html .= qq(<OPTION VALUE="$option"); +% $html .= ' SELECTED' if $option eq $plandata->{'spoolformat'}; +% $html .= ">\u$option"; +% } +% +% $html .= +% '</SELECT>'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">For destination: </TD>'. +% '<TD>'. +% '<SELECT NAME="spooldest">'; +% +% tie my %dest, 'Tie::IxHash', +% '' => '(all)', +% 'POST' => 'Postal Mail', +% 'EMAIL' => 'Email', +% 'FAX' => 'Fax', +% ; +% +% foreach my $dest (keys %dest) { +% $html .= qq(<OPTION VALUE="$dest"); +% $html .= ' SELECTED' if $dest eq $plandata->{'spooldest'}; +% $html .= '>'. $dest{$dest}; +% } +% +% $html .= +% '</SELECT>'. +% '</TD></TR>'. +% +% '<TR>'. +% '<TD ALIGN="right">if balance (this invoice and previous) over </TD>'. +% '<TD>'. +% "$money_char ". +% '<INPUT TYPE="text" SIZE="7" NAME="spoolbalanceover" VALUE="%%%spoolbalanceover%%%">'. +% '</TD>'. +% '<TR><TD ALIGN="right">Individual per-agent spools? </TD>'. +% '<TD><INPUT TYPE="checkbox" NAME="spoolagent_spools" VALUE="1" '. +% ( $plandata->{'spoolagent_spools'} ? 'CHECKED' : '' ). +% '>'. +% '</TD></TR>'. +% '</TABLE>'; +% +% $html; +% }, +% 'weight' => 50, +% }, +% +% 'bill' => { +% 'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)', +% 'code' => '$cust_main->bill();', +% 'weight' => 60, +% }, +% +% 'apply' => { +% 'name' => 'Apply unapplied payments and credits', +% 'code' => '$cust_main->apply_payments_and_credits; "";', +% 'weight' => 70, +% }, +% +% 'collect' => { +% 'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)', +% 'code' => '$cust_main->collect();', +% 'weight' => 80, +% }, +% +%; +% +<SCRIPT TYPE="text/javascript">var myreasons = new Array();</SCRIPT> +%foreach my $event ( keys %events ) { +% my %plandata = map { /^(\w+) (.*)$/; ($1, $2); } +% split(/\n/, $part_bill_event->plandata); +% my $html = $events{$event}{html}; +% if ( ref($html) eq 'CODE' ) { +% $html = &{$html}(\%plandata); +% } +% while ( $html =~ /%%%(\w+)%%%/ ) { +% my $field = $1; +% $html =~ s/%%%$field%%%/$plandata{$field}/; +% } +% +<SCRIPT TYPE="text/javascript">myreasons.push('<% $events{$event}{reason} %>'); +</SCRIPT> +% if ($event eq $part_bill_event->plan){ +% $currentreasonclass=$events{$event}{reason}; +% } +% print ntable( "#cccccc", 2). +% qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !; +% print "CHECKED " if $event eq $part_bill_event->plan; +% print qq!onClick="showhide_table()" !; +% print qq!VALUE="!. $event. ":". $events{$event}{weight}. ":". +% encode_entities($events{$event}{code}). +% qq!">$events{$event}{name}</TD>!; +% print '<TD>'. $html. '</TD>' if $html; +% print qq!</TR>!; +% print '</TABLE>'; +%} +% +% if ($currentreasonclass eq 'C'){ +% if ($cgi->param('creason') =~ /^(-?\d+)$/){ +% $creason = $1; +% }else{ +% $creason = $part_bill_event->reason; +% } +% if ($cgi->param('newcreasonT') =~ /^(\d+)$/){ +% $newcreasonT = $1; +% } +% if ($cgi->param('newcreason') =~ /^([\w\s]+)$/){ +% $newcreason = $1; +% } +% }elsif ($currentreasonclass eq 'S'){ +% if ($cgi->param('sreason') =~ /^(-?\d+)$/){ +% $sreason = $1; +% }else{ +% $sreason = $part_bill_event->reason; +% } +% if ($cgi->param('newsreasonT') =~ /^(\d+)$/){ +% $newsreasonT = $1; +% } +% if ($cgi->param('newsreason') =~ /^([\w\s]+)$/){ +% $newsreason = $1; +% } +% } +% -; +</TD></TR> +</TABLE> -foreach my $event ( keys %events ) { - my %plandata = map { /^(\w+) (.*)$/; ($1, $2); } - split(/\n/, $part_bill_event->plandata); - my $html = $events{$event}{html}; - if ( ref($html) eq 'CODE' ) { - $html = &{$html}(\%plandata); - } - while ( $html =~ /%%%(\w+)%%%/ ) { - my $field = $1; - $html =~ s/%%%$field%%%/$plandata{$field}/; +<SCRIPT TYPE="text/javascript"> + function showhide_table() + { + for(i=0;i<document.editEvent.plan_weight_eventcode.length;i++){ + if (document.editEvent.plan_weight_eventcode[i].checked == true){ + currentevent=i; + } + } + if(myreasons[currentevent] == 'C'){ + document.getElementById('Ctable').style.display = 'inline'; + document.getElementById('Stable').style.display = 'none'; + }else if(myreasons[currentevent] == 'S'){ + document.getElementById('Ctable').style.display = 'none'; + document.getElementById('Stable').style.display = 'inline'; + }else{ + document.getElementById('Ctable').style.display = 'none'; + document.getElementById('Stable').style.display = 'none'; + } } +</SCRIPT> - print ntable( "#cccccc", 2). - qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !; - print "CHECKED " if $event eq $part_bill_event->plan; - print qq!VALUE="!. $event. ":". $events{$event}{weight}. ":". - encode_entities($events{$event}{code}). - qq!">$events{$event}{name}</TD>!; - print '<TD>'. $html. '</TD>' if $html; - print qq!</TR>!; - print '</TABLE>'; -} - -#print '</TABLE>'; +<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%"> +<TR><TD> +<TABLE BORDER=0 id="Ctable" style="display:<% $currentreasonclass eq 'C' ? 'inline' : 'none' %>"> +<% include('/elements/tr-select-reason.html', 'creason', 'C', $creason, $newcreasonT, $newcreason) %> +</TABLE> +</TR></TD> +</TABLE> -print <<END; -</TD></TR> +<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%"> +<TR><TD> +<TABLE BORDER=0 id="Stable" style="display:<% $currentreasonclass eq 'S' ? 'inline' : 'none' %>"> +<% include('/elements/tr-select-reason.html', 'sreason', 'S', $sreason, $newsreasonT, $newsreason) %> </TABLE> -END +</TR></TD> +</TABLE> + +% +%print qq!<INPUT TYPE="submit" VALUE="!, +% $hashref->{eventpart} ? "Apply changes" : "Add invoice event", +% qq!">!; +% -print qq!<INPUT TYPE="submit" VALUE="!, - $hashref->{eventpart} ? "Apply changes" : "Add invoice event", - qq!">!; -%> </FORM> </BODY> </HTML> + diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index b3d42bd96..6717471dd 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -1,128 +1,130 @@ <!-- mason kludge --> -<% - -#if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { -# $cgi->param('clone', $1); -#} else { -# $cgi->param('clone', ''); -#} - -my($query) = $cgi->keywords; -my $action = ''; -my $part_export = ''; -if ( $cgi->param('error') ) { - $part_export = new FS::part_export ( { - map { $_, scalar($cgi->param($_)) } fields('part_export') - } ); -} elsif ( $query =~ /^(\d+)$/ ) { - $part_export = qsearchs('part_export', { 'exportnum' => $1 } ); -} else { - $part_export = new FS::part_export; -} -$action ||= $part_export->exportnum ? 'Edit' : 'Add'; - -#my $exports = FS::part_export::export_info($svcdb); -my $exports = FS::part_export::export_info(); - -my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports; -$layers{''}=''; - -my $widget = new HTML::Widgets::SelectLayers( - 'selected_layer' => $part_export->exporttype, - 'options' => \%layers, - 'form_name' => 'dummy', - 'form_action' => 'process/part_export.cgi', - 'form_text' => [qw( exportnum machine )], -# 'form_checkbox' => [qw()], - 'html_between' => "</TD></TR></TABLE>\n", - 'layer_callback' => sub { - my $layer = shift; - my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!. - ntable("#cccccc",2); - - $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. - $exports->{$layer}{notes}. '</TD></TR>' - if $layer; - - foreach my $option ( keys %{$exports->{$layer}{options}} ) { - my $optinfo = $exports->{$layer}{options}{$option}; - die "Retreived non-ref export info option from $layer export: $optinfo" - unless ref($optinfo); - my $label = $optinfo->{label}; - my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text'; - my $value = $cgi->param($option) - || ( $part_export->exportnum && $part_export->option($option) ) - || ( (exists $optinfo->{default} && !$part_export->exportnum) - ? $optinfo->{default} - : '' - ); - $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!; - if ( $type eq 'select' ) { - $html .= qq!<SELECT NAME="$option">!; - foreach my $select_option ( @{$optinfo->{options}} ) { - #if ( ref($select_option) ) { - #} else { - my $selected = $select_option eq $value ? ' SELECTED' : ''; - $html .= qq!<OPTION VALUE="$select_option"$selected>!. - qq!$select_option</OPTION>!; - #} - } - $html .= '</SELECT>'; - } elsif ( $type eq 'textarea' ) { - $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!. - encode_entities($value). '</TEXTAREA>'; - } elsif ( $type eq 'text' ) { - $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!. - encode_entities($value). '" SIZE=64>'; - } elsif ( $type eq 'checkbox' ) { - $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!; - $html .= ' CHECKED' if $value; - $html .= '>'; - } else { - $html .= "unknown type $type"; - } - $html .= '</TD></TR>'; - } - $html .= '</TABLE>'; - - $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'. - join(',', keys %{$exports->{$layer}{options}} ). '">'; - - $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'. - $exports->{$layer}{nodomain}. '">'; - - $html .= '<INPUT TYPE="submit" VALUE="'. - ( $part_export->exportnum ? "Apply changes" : "Add export" ). - '">'; - - $html; - }, -); - -%> -<%= header("$action Export", menubar( +% +% +%#if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { +%# $cgi->param('clone', $1); +%#} else { +%# $cgi->param('clone', ''); +%#} +% +%my($query) = $cgi->keywords; +%my $action = ''; +%my $part_export = ''; +%if ( $cgi->param('error') ) { +% $part_export = new FS::part_export ( { +% map { $_, scalar($cgi->param($_)) } fields('part_export') +% } ); +%} elsif ( $query =~ /^(\d+)$/ ) { +% $part_export = qsearchs('part_export', { 'exportnum' => $1 } ); +%} else { +% $part_export = new FS::part_export; +%} +%$action ||= $part_export->exportnum ? 'Edit' : 'Add'; +% +%#my $exports = FS::part_export::export_info($svcdb); +%my $exports = FS::part_export::export_info(); +% +%my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports; +%$layers{''}=''; +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => $part_export->exporttype, +% 'options' => \%layers, +% 'form_name' => 'dummy', +% 'form_action' => 'process/part_export.cgi', +% 'form_text' => [qw( exportnum machine )], +%# 'form_checkbox' => [qw()], +% 'html_between' => "</TD></TR></TABLE>\n", +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!. +% ntable("#cccccc",2); +% +% $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. +% $exports->{$layer}{notes}. '</TD></TR>' +% if $layer; +% +% foreach my $option ( keys %{$exports->{$layer}{options}} ) { +% my $optinfo = $exports->{$layer}{options}{$option}; +% die "Retreived non-ref export info option from $layer export: $optinfo" +% unless ref($optinfo); +% my $label = $optinfo->{label}; +% my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text'; +% my $value = $cgi->param($option) +% || ( $part_export->exportnum && $part_export->option($option) ) +% || ( (exists $optinfo->{default} && !$part_export->exportnum) +% ? $optinfo->{default} +% : '' +% ); +% $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!; +% if ( $type eq 'select' ) { +% $html .= qq!<SELECT NAME="$option">!; +% foreach my $select_option ( @{$optinfo->{options}} ) { +% #if ( ref($select_option) ) { +% #} else { +% my $selected = $select_option eq $value ? ' SELECTED' : ''; +% $html .= qq!<OPTION VALUE="$select_option"$selected>!. +% qq!$select_option</OPTION>!; +% #} +% } +% $html .= '</SELECT>'; +% } elsif ( $type eq 'textarea' ) { +% $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!. +% encode_entities($value). '</TEXTAREA>'; +% } elsif ( $type eq 'text' ) { +% $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!. +% encode_entities($value). '" SIZE=64>'; +% } elsif ( $type eq 'checkbox' ) { +% $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!; +% $html .= ' CHECKED' if $value; +% $html .= '>'; +% } else { +% $html .= "unknown type $type"; +% } +% $html .= '</TD></TR>'; +% } +% $html .= '</TABLE>'; +% +% $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'. +% join(',', keys %{$exports->{$layer}{options}} ). '">'; +% +% $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'. +% $exports->{$layer}{nodomain}. '">'; +% +% $html .= '<INPUT TYPE="submit" VALUE="'. +% ( $part_export->exportnum ? "Apply changes" : "Add export" ). +% '">'; +% +% $html; +% }, +%); +% +% + +<% include("/elements/header.html","$action Export", menubar( 'Main Menu' => popurl(2), ), ' onLoad="visualize()"') %> +% if ( $cgi->param('error') ) { -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> <BR><BR> -<% } %> +% } + <FORM NAME="dummy"> -<INPUT TYPE="hidden" NAME="exportnum" VALUE="<%= $part_export->exportnum %>"> +<INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>"> -<%= ntable("#cccccc",2) %> +<% ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Export host</TD> <TD> - <INPUT TYPE="text" NAME="machine" VALUE="<%= $part_export->machine %>"> + <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>"> </TD> </TR> <TR> <TD ALIGN="right">Export</TD> - <TD><%= $widget->html %> + <TD><% $widget->html %> </BODY> </HTML> diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 60365f628..77822d7e0 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -1,99 +1,105 @@ -<% - -if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { - $cgi->param('clone', $1); -} else { - $cgi->param('clone', ''); -} -if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) { - $cgi->param('pkgnum', $1); -} else { - $cgi->param('pkgnum', ''); -} - -my ($query) = $cgi->keywords; - -my $part_pkg = ''; -if ( $cgi->param('error') ) { - $part_pkg = new FS::part_pkg ( { - map { $_, scalar($cgi->param($_)) } fields('part_pkg') - } ); -} - -my $action = ''; -my $clone_part_pkg = ''; -my $pkgpart = ''; -if ( $cgi->param('clone') ) { - $pkgpart = $cgi->param('clone'); - $action = 'Custom Pricing'; - $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } ); - $part_pkg ||= $clone_part_pkg->clone; - $part_pkg->disabled('Y'); #isn't sticky on errors -} elsif ( $query && $query =~ /^(\d+)$/ ) { - $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1}); - $pkgpart = $part_pkg->pkgpart; -} else { - unless ( $part_pkg ) { - $part_pkg = new FS::part_pkg {}; - $part_pkg->plan('flat'); - } -} -unless ( $part_pkg->plan ) { #backwards-compat - $part_pkg->plan('flat'); - $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n". - "recur_fee=". $part_pkg->recur. "\n"); -} -$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add'; -my $hashref = $part_pkg->hashref; - -%> - -<%= header("$action Package Definition", menubar( +% +% +%if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { +% $cgi->param('clone', $1); +%} else { +% $cgi->param('clone', ''); +%} +%if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) { +% $cgi->param('pkgnum', $1); +%} else { +% $cgi->param('pkgnum', ''); +%} +% +%my ($query) = $cgi->keywords; +% +%my $part_pkg = ''; +%my @agent_type = (); +%if ( $cgi->param('error') ) { +% $part_pkg = new FS::part_pkg ( { +% map { $_, scalar($cgi->param($_)) } fields('part_pkg') +% } ); +% (@agent_type) = $cgi->param('agent_type'); +%} +% +%my $action = ''; +%my $clone_part_pkg = ''; +%my $pkgpart = ''; +%if ( $cgi->param('clone') ) { +% $pkgpart = $cgi->param('clone'); +% $action = 'Custom Pricing'; +% $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } ); +% $part_pkg ||= $clone_part_pkg->clone; +% $part_pkg->disabled('Y'); #isn't sticky on errors +%} elsif ( $query && $query =~ /^(\d+)$/ ) { +% (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1}) +% unless $part_pkg; +% $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1}); +% $pkgpart = $part_pkg->pkgpart; +%} else { +% unless ( $part_pkg ) { +% $part_pkg = new FS::part_pkg {}; +% $part_pkg->plan('flat'); +% } +%} +%unless ( $part_pkg->plan ) { #backwards-compat +% $part_pkg->plan('flat'); +% $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n". +% "recur_fee=". $part_pkg->recur. "\n"); +%} +%$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add'; +%my $hashref = $part_pkg->hashref; +% +% + + +<% include("/elements/header.html","$action Package Definition", menubar( 'Main Menu' => popurl(2), 'View all packages' => popurl(2). 'browse/part_pkg.cgi', )) %> +% #), ' onLoad="visualize()"'); +% if ( $cgi->param('error') ) { -<% #), ' onLoad="visualize()"'); %> + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> <FORM NAME="dummy"> -<%= itable('',8,1) %><TR><TD VALIGN="top"> +<% itable('',8,1) %><TR><TD VALIGN="top"> Package information -<%= ntable("#cccccc",2) %> +<% ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Package Definition #</TD> <TD BGCOLOR="#ffffff"> - <%= $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %> + <% $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %> </TD> </TR> <TR> <TD ALIGN="right">Package (customer-visible)</TD> <TD> - <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<%= $part_pkg->pkg %>"> + <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<% $part_pkg->pkg %>"> </TD> </TR> <TR> <TD ALIGN="right">Comment (customer-hidden)</TD> <TD> - <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%=$part_pkg->comment%>"> + <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%$part_pkg->comment%>"> </TD> </TR> + <% include( '/elements/tr-select-pkg_class.html', $part_pkg->classnum ) %> <TR> <TD ALIGN="right">Promotional code</TD> <TD> - <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%=$part_pkg->promo_code%>"> + <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%$part_pkg->promo_code%>"> </TD> </TR> <TR> <TD ALIGN="right">Disable new orders</TD> <TD> - <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %> + <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %> </TD> </TR> @@ -102,234 +108,293 @@ Package information </TD><TD VALIGN="top"> Tax information -<%= ntable("#cccccc", 2) %> +<% ntable("#cccccc", 2) %> <TR> <TD ALIGN="right">Setup fee tax exempt</TD> <TD> - <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <%= $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>> + <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>> </TD> </TR> <TR> <TD ALIGN="right">Recurring fee tax exempt</TD> <TD> - <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <%= $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>> + <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <% $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>> </TD> </TR> -<% my $conf = new FS::Conf; %> -<% if ( $conf->exists('enable_taxclasses') ) { %> +% my $conf = new FS::Conf; +% if ( $conf->exists('enable_taxclasses') ) { <TR> <TD align="right">Tax class</TD> <TD> - <%= include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> + <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> </TD> </TR> -<% } else { %> +% } else { - <%= include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> + <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> -<% } %> +% } </TABLE> +<BR> + +Line-item revenue recognition +<% ntable("#cccccc", 2) %> +% tie my %weight, 'Tie::IxHash', +% 'pay_weight' => 'Payment', +% 'credit_weight' => 'Credit' +% ; +% foreach my $weight (keys %weight) { + <TR> + <TD ALIGN="right"><% $weight{$weight} %> weight</TD> + <TD> + <INPUT TYPE="text" NAME="<% $weight %>" SIZE=6 VALUE=<% $hashref->{$weight} || 0 %>> + </TD> + </TR> +% } +</TABLE> -</TD></TR></TABLE> - -<% +</TD><TD VALIGN="top"> -my $thead = "\n\n". ntable('#cccccc', 2). - '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'; -$thead .= '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>' - if dbdef->table('pkg_svc')->column('primary_svc'); -$thead .= '<TH BGCOLOR="#dcdcdc">Service</TH></TR>'; +%#Reseller information # after 1.7.2 +%#<% ntable("#cccccc", 2) %> +%# <TR> +%# <TD ALIGN="right"><% 'Agent Types' %></TD> +%# <TD> +%# <% include( '/elements/select-table.html', +%# 'element_name' => 'agent_type', +%# 'table' => 'agent_type', +%# 'name_col' => 'atype', +%# 'value' => \@agent_type, +%# 'empty_label' => '(none)', +%# 'element_etc' => 'multiple size="10"', +%# ) +%# %> +%# </TD> +%# </TR> +%#</TABLE> +</TD></TR></TABLE> +% +% +%my $thead = "\n\n". ntable('#cccccc', 2). +% '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'; +%$thead .= '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>' +% if dbdef->table('pkg_svc')->column('primary_svc'); +%$thead .= '<TH BGCOLOR="#dcdcdc">Service</TH></TR>'; +% +% -%> -<%= itable('', 4, 1) %><TR><TD VALIGN="top"> <BR><BR>Services included -<%= $thead %> - -<% - -my $where = "WHERE disabled IS NULL OR disabled = ''"; -if ( $pkgpart ) { - $where .= " OR 0 < ( SELECT quantity FROM pkg_svc - WHERE pkg_svc.svcpart = part_svc.svcpart - AND pkgpart = $pkgpart - )"; -} -my @part_svc = qsearch('part_svc', {}, '', $where); -my $q_part_pkg = $clone_part_pkg || $part_pkg; -my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc; - -my @fixups = (); -my $count = 0; -my $columns = 3; -foreach my $part_svc ( @part_svc ) { - my $svcpart = $part_svc->svcpart; - my $pkg_svc = $pkg_svc{$svcpart} - || new FS::pkg_svc ( { - 'pkgpart' => $pkgpart, - 'svcpart' => $svcpart, - 'quantity' => 0, - 'primary_svc' => '', - } ); - - push @fixups, "pkg_svc$svcpart"; - -%> +<% itable('', 4, 1) %><TR><TD VALIGN="top"> +<% $thead %> +% +% +%my $where = "WHERE disabled IS NULL OR disabled = ''"; +%if ( $pkgpart ) { +% $where .= " OR 0 < ( SELECT quantity FROM pkg_svc +% WHERE pkg_svc.svcpart = part_svc.svcpart +% AND pkgpart = $pkgpart +% )"; +%} +%my @part_svc = qsearch('part_svc', {}, '', $where); +%my $q_part_pkg = $clone_part_pkg || $part_pkg; +%my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc; +% +%my @fixups = (); +%my $count = 0; +%my $columns = 3; +%foreach my $part_svc ( @part_svc ) { +% my $svcpart = $part_svc->svcpart; +% my $pkg_svc = $pkg_svc{$svcpart} +% || new FS::pkg_svc ( { +% 'pkgpart' => $pkgpart, +% 'svcpart' => $svcpart, +% 'quantity' => 0, +% 'primary_svc' => '', +% } ); +% +% push @fixups, "pkg_svc$svcpart"; +% +% + <TR> <TD> - <INPUT TYPE="text" NAME="pkg_svc<%= $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<%= $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0 %>"> + <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<% $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0 %>"> </TD> <TD> - <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<%= $svcpart %>" <%= $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>> + <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>> </TD> <TD> - <A HREF="part_svc.cgi?<%= $part_svc->svcpart %>"><%= $part_svc->svc %></A> <%= $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %> + <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %> </TD> </TR> +% foreach ( 1 .. $columns-1 ) { +% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) { +% - <% $count++; - foreach ( 1 .. $columns-1 ) { - if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) { - %> - </TABLE></TD><TD VALIGN="top"><%= $thead %> + </TABLE></TD><TD VALIGN="top"><% $thead %> +% } +% } +% $count++; +% +% } - <% } - } - %> - -<% } %> </TR></TABLE></TD></TR></TABLE> - -<% foreach my $f ( qw( clone pkgnum ) ) { %> - <INPUT TYPE="hidden" NAME="<%= $f %>" VALUE="<%= $cgi->param($f) %>"> -<% } %> -<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<%= $part_pkg->pkgpart %>"> - -<% - -# prolly should be in database -tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() }; - -my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } - split("\n", ($clone_part_pkg||$part_pkg)->plandata ); -#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n"; - -tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans; - -my @form_select = (); -if ( $conf->exists('enable_taxclasses') ) { - push @form_select, 'taxclass'; -} else { - push @fixups, 'taxclass'; #hidden -} - -my @form_radio = (); -if ( dbdef->table('pkg_svc')->column('primary_svc') ) { - push @form_radio, 'pkg_svc_primary'; -} - -tie my %freq, 'Tie::IxHash', %FS::part_pkg::freq; -if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) { - delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq; -} - -my $widget = new HTML::Widgets::SelectLayers( - 'selected_layer' => $part_pkg->plan, - 'options' => \%options, - 'form_name' => 'dummy', - 'form_action' => 'process/part_pkg.cgi', - 'form_text' => [ qw(pkg comment promo_code clone pkgnum pkgpart), - @fixups - ], - 'form_checkbox' => [ qw(setuptax recurtax disabled) ], - 'form_radio' => \@form_radio, - 'form_select' => \@form_select, - 'layer_callback' => sub { - my $layer = shift; - my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!. - ntable("#cccccc",2); - $html .= ' - <TR> - <TD ALIGN="right">Recurring fee frequency </TD> - <TD><SELECT NAME="freq"> - '; - - my @freq = keys %freq; - @freq = grep { /^\d+$/ } @freq - if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm'; - foreach my $freq ( @freq ) { - $html .= qq(<OPTION VALUE="$freq"); - $html .= ' SELECTED' if $freq eq $part_pkg->freq; - $html .= ">$freq{$freq}"; - } - $html .= '</SELECT></TD></TR>'; - - my $href = $plans{$layer}->{'fields'}; - foreach my $field ( exists($plans{$layer}->{'fieldorder'}) - ? @{$plans{$layer}->{'fieldorder'}} - : keys %{ $href } - ) { - - $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>'; - - if ( ! exists($href->{$field}{'type'}) ) { - $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!. - ( exists($plandata{$field}) - ? $plandata{$field} - : $href->{$field}{'default'} ). - qq!" onChange="fchanged(this)">!; - } elsif ( $href->{$field}{'type'} eq 'checkbox' ) { - $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !. - ( exists($plandata{$field}) && $plandata{$field} - ? ' CHECKED' - : '' - ). '>'; - } elsif ( $href->{$field}{'type'} =~ /^select/ ) { - $html .= '<SELECT'; - $html .= ' MULTIPLE' - if $href->{$field}{'type'} eq 'select_multiple'; - $html .= qq! NAME="$field" onChange="fchanged(this)">!; - foreach my $record ( - qsearch( $href->{$field}{'select_table'}, - $href->{$field}{'select_hash'} ) - ) { - my $value = $record->getfield($href->{$field}{'select_key'}); - $html .= qq!<OPTION VALUE="$value"!. - ( $plandata{$field} =~ /(^|, *)$value *(,|$)/ - ? ' SELECTED' - : '' ). - '>'. $record->getfield($href->{$field}{'select_label'}) - } - $html .= '</SELECT>'; - } - - $html .= '</TD></TR>'; - } - $html .= '</TABLE>'; - - $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'. - join(',', keys %{ $href } ). '">'. - '<BR><BR>'; - - $html .= '<INPUT TYPE="submit" VALUE="'. - ( $hashref->{pkgpart} ? "Apply changes" : "Add package" ). - '" onClick="fchanged(this)">'; - - $html; - - }, -); - -%> - -<BR><BR>Price plan <%= $widget->html %> +% foreach my $f ( qw( clone pkgnum ) ) { + + <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cgi->param($f) %>"> +% } + +<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $part_pkg->pkgpart %>"> +% +% +%# prolly should be in database +%tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() }; +% +%my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } +% split("\n", ($clone_part_pkg||$part_pkg)->plandata ); +%#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n"; +% +%tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans; +% +%#my @form_select = ('classnum'); +%#if ( $conf->exists('enable_taxclasses') ) { +%# push @form_select, 'taxclass'; +%#} else { +%# push @fixups, 'taxclass'; #hidden +%#} +%my @form_elements = ( 'classnum', 'taxclass' ); +%# copying non-existant elements is probably harmless, but after 1.7.2 +%#my @form_elements = ( 'classnum', 'taxclass', 'agent_type' ); +% +%my @form_radio = (); +%if ( dbdef->table('pkg_svc')->column('primary_svc') ) { +% push @form_radio, 'pkg_svc_primary'; +%} +% +%tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()}; +%if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) { +% delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq; +%} +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => $part_pkg->plan, +% 'options' => \%options, +% 'form_name' => 'dummy', +% 'form_action' => 'process/part_pkg.cgi', +% 'form_elements' => \@form_elements, +% 'form_text' => [ qw(pkg comment promo_code clone pkgnum pkgpart), +% qw(pay_weight credit_weight), +% @fixups, +% ], +% 'form_checkbox' => [ qw(setuptax recurtax disabled) ], +% 'form_radio' => \@form_radio, +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!. +% ntable("#cccccc",2); +% $html .= ' +% <TR> +% <TD ALIGN="right">Recurring fee frequency </TD> +% <TD><SELECT NAME="freq"> +% '; +% +% my @freq = keys %freq; +% @freq = grep { /^\d+$/ } @freq +% if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm'; +% foreach my $freq ( @freq ) { +% $html .= qq(<OPTION VALUE="$freq"); +% $html .= ' SELECTED' if $freq eq $part_pkg->freq; +% $html .= ">$freq{$freq}"; +% } +% $html .= '</SELECT></TD></TR>'; +% +% my $href = $plans{$layer}->{'fields'}; +% foreach my $field ( exists($plans{$layer}->{'fieldorder'}) +% ? @{$plans{$layer}->{'fieldorder'}} +% : keys %{ $href } +% ) { +% +% $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>'; +% +% if ( ! exists($href->{$field}{'type'}) ) { +% $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!. +% ( exists($plandata{$field}) +% ? $plandata{$field} +% : $href->{$field}{'default'} ). +% qq!" onChange="fchanged(this)">!; #after 1.7.2 +% } elsif ( $href->{$field}{'type'} eq 'checkbox' ) { +% $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !. +% ( exists($plandata{$field}) && $plandata{$field} +% ? ' CHECKED' +% : '' +% ). '>'; +% } elsif ( $href->{$field}{'type'} =~ /^select/ ) { +% $html .= '<SELECT'; +% $html .= ' MULTIPLE' +% if $href->{$field}{'type'} eq 'select_multiple'; +% $html .= qq! NAME="$field" onChange="fchanged(this)">!; # after 1.7.2 +% +% if ( $href->{$field}{'select_table'} ) { +% foreach my $record ( +% qsearch( $href->{$field}{'select_table'}, +% $href->{$field}{'select_hash'} ) +% ) { +% my $value = $record->getfield($href->{$field}{'select_key'}); +% $html .= qq!<OPTION VALUE="$value"!. +% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/ +% ? ' SELECTED' +% : '' +% ). +% '>'. $record->getfield($href->{$field}{'select_label'}); +% } +% } elsif ( $href->{$field}{'select_options'} ) { +% foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) { +% my $value = $href->{$field}{'select_options'}{$key}; +% $html .= qq!<OPTION VALUE="$key"!. +% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/ +% ? ' SELECTED' +% : '' +% ). +% '>'. $value; +% } +% +% } else { +% $html .= '<font color="#ff0000">warning: '. +% "don't know how to retreive options for $field select field". +% '</font>'; +% } +% $html .= '</SELECT>'; +% } +% +% $html .= '</TD></TR>'; +% } +% $html .= '</TABLE>'; +% +% $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'. +% join(',', keys %{ $href } ). '">'. +% '<BR><BR>'; +% +% $html .= '<INPUT TYPE="submit" VALUE="'. +% ( $hashref->{pkgpart} ? "Apply changes" : "Add package" ). +% '" onClick="fchanged(this)">'; #after 1.7.2 +% +% $html; +% +% }, +%); +% +% + + +<BR><BR>Price plan <% $widget->html %> </BODY> </HTML> diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi deleted file mode 100755 index f784dfa3e..000000000 --- a/httemplate/edit/part_referral.cgi +++ /dev/null @@ -1,48 +0,0 @@ -<!-- mason kludge --> -<% - -my $part_referral; -if ( $cgi->param('error') ) { - $part_referral = new FS::part_referral ( { - map { $_, scalar($cgi->param($_)) } fields('part_referral') - } ); -} elsif ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $part_referral = qsearchs( 'part_referral', { 'refnum' => $1 } ); -} else { #adding - $part_referral = new FS::part_referral {}; -} -my $action = $part_referral->refnum ? 'Edit' : 'Add'; -my $hashref = $part_referral->hashref; - -my $p1 = popurl(1); -print header("$action Advertising source", menubar( - 'Main Menu' => popurl(2), - 'View all advertising sources' => popurl(2). "browse/part_referral.cgi", -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/part_referral.cgi" METHOD=POST>!; - -print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$hashref->{refnum}">!; -#print "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)"; - -print <<END; -Advertising source <INPUT TYPE="text" NAME="referral" SIZE=32 VALUE="$hashref->{referral}"> -END - -print qq!<BR><INPUT TYPE="submit" VALUE="!, - $hashref->{refnum} ? "Apply changes" : "Add advertising source", - qq!">!; - -print <<END; - </FORM> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html new file mode 100755 index 000000000..7ce52174d --- /dev/null +++ b/httemplate/edit/part_referral.html @@ -0,0 +1,9 @@ +<% include( 'elements/edit.html', + 'name' => 'Advertising source', + 'table' => 'part_referral', + 'fields' => [ 'referral' ], + 'labels' => { 'referral' => 'Advertising source' }, + 'viewall_dir' => 'browse', + 'html_table_bottom' => include('/elements/tr-select-agent.html'), + ) +%> diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 9749fc12d..6ba9240e3 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -1,29 +1,30 @@ -<% -my $part_svc; -my $clone = ''; -if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone - #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query"; - $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } ) - or die "unknown svcpart: $1"; - $clone = $part_svc->svcpart; - $part_svc->svcpart(''); -} elsif ( $cgi->keywords ) { #edit - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/ or die "malformed query: $query"; - $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } ) - or die "unknown svcpart: $1"; -} else { #adding - $part_svc = new FS::part_svc {}; -} +% +%my $part_svc; +%my $clone = ''; +%if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone +% #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query"; +% $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } ) +% or die "unknown svcpart: $1"; +% $clone = $part_svc->svcpart; +% $part_svc->svcpart(''); +%} elsif ( $cgi->keywords ) { #edit +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "malformed query: $query"; +% $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } ) +% or die "unknown svcpart: $1"; +%} else { #adding +% $part_svc = new FS::part_svc {}; +%} +% +%my $action = $part_svc->svcpart ? 'Edit' : 'Add'; +%my $hashref = $part_svc->hashref; +%# my $p_svcdb = $part_svc->svcdb || 'svc_acct'; +% +% +% #" onLoad=\"visualize()\"" +% -my $action = $part_svc->svcpart ? 'Edit' : 'Add'; -my $hashref = $part_svc->hashref; -# my $p_svcdb = $part_svc->svcdb || 'svc_acct'; - - - #" onLoad=\"visualize()\"" -%> -<%= header("$action Service Definition", +<% include("/elements/header.html","$action Service Definition", menubar( 'Main Menu' => $p, 'View all service definitions' => "${p}browse/part_svc.cgi" ), @@ -32,259 +33,322 @@ my $hashref = $part_svc->hashref; <FORM NAME="dummy"> - Service Part #<%= $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> + Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> <BR><BR> -Service <INPUT TYPE="text" NAME="svc" VALUE="<%= $hashref->{svc} %>"><BR> -Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR> -<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $hashref->{svcpart} %>"> +Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR> +Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>"> <BR> -Services are items you offer to your customers. -<UL><LI>svc_acct - Shell accounts, POP mailboxes, SLIP/PPP and ISDN accounts +Service definitions are the templates for items you offer to your customers. +<UL><LI>svc_acct - Accounts - anything with a username (Mailboxes, PPP accounts, shell accounts, RADIUS entries for broadband, etc.) <LI>svc_domain - Domains <LI>svc_forward - mail forwarding <LI>svc_www - Virtual domain website - <LI>svc_broadband - Broadband/High-speed Internet service + <LI>svc_broadband - Broadband/High-speed Internet service (always-on) + <LI>svc_phone - Customer phone numbers <LI>svc_external - Externally-tracked service <!-- <LI>svc_charge - One-time charges (Partially unimplemented) <LI>svc_wo - Work orders (Partially unimplemented) --> </UL> For the selected table, you can give fields default or fixed (unchangable) -values. For example, a SLIP/PPP account may have a default (or perhaps fixed) -<B>slipip</B> of <B>0.0.0.0</B>, while a POP mailbox will probably have a fixed -blank <B>slipip</B> as well as a fixed shell something like <B>/bin/true</B> or -<B>/usr/bin/passwd</B>. +values, or select an inventory class to manually or automatically fill in +that field. <BR><BR> -<% - -my %vfields; - -#these might belong somewhere else for other user interfaces -#pry need to eventually create stuff that's shared amount UIs -my $conf = new FS::Conf; -my %defs = ( - 'svc_acct' => { - 'dir' => 'Home directory', - 'uid' => 'UID (set to fixed and blank for no UIDs)', - 'slipip' => 'IP address', -# 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, - 'popnum' => { - desc => 'Access number', - type => 'select', - select_table => 'svc_acct_pop', - select_key => 'popnum', - select_label => 'city', - }, - 'username' => { - desc => 'Username', - type => 'disabled', - }, - 'quota' => '', - '_password' => 'Password', - 'gid' => 'GID (when blank, defaults to UID)', - 'shell' => { - desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)', - type =>'select', - select_list => [ $conf->config('shells') ], - }, - 'finger' => 'GECOS', - 'domsvc' => { - desc =>'svcnum from svc_domain', - type =>'select', - select_table => 'svc_domain', - select_key => 'svcnum', - select_label => 'domain', - }, - 'usergroup' => { - desc =>'RADIUS groups', - type =>'radius_usergroup_selector', - }, - }, - 'svc_domain' => { - 'domain' => 'Domain', - }, - 'svc_forward' => { - 'srcsvc' => 'service from which mail is to be forwarded', - 'dstsvc' => 'service to which mail is to be forwarded', - 'dst' => 'someone@another.domain.com to use when dstsvc is 0', - }, -# 'svc_charge' => { -# 'amount' => 'amount', -# }, -# 'svc_wo' => { -# 'worker' => 'Worker', -# '_date' => 'Date', -# }, - 'svc_www' => { - #'recnum' => '', - #'usersvc' => '', - }, - 'svc_broadband' => { - 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', - 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', - 'ip_addr' => 'IP address. Leave blank for automatic assignment.', - 'blocknum' => 'Address block.', - }, - 'svc_external' => { - #'id' => '', - #'title' => '', - }, -); - - foreach my $svcdb (grep dbdef->table($_), keys %defs ) { - my $self = "FS::$svcdb"->new; - $vfields{$svcdb} = {}; - foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them - my $pvf = $self->pvf($field); - my @list = $pvf->list; - if (scalar @list) { - $defs{$svcdb}->{$field} = { desc => $pvf->label, - type => 'select', - select_list => \@list }; - } else { - $defs{$svcdb}->{$field} = $pvf->label; - } #endif - $vfields{$svcdb}->{$field} = $pvf; - warn "\$vfields{$svcdb}->{$field} = $pvf"; - } #next $field - } #next $svcdb - - my @dbs = $hashref->{svcdb} - ? ( $hashref->{svcdb} ) - : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_external ); - - tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; - my $widget = new HTML::Widgets::SelectLayers( - #'selected_layer' => $p_svcdb, - 'selected_layer' => $hashref->{svcdb} || 'svc_acct', - 'options' => \%svcdb, - 'form_name' => 'dummy', - #'form_action' => 'process/part_svc.cgi', - 'form_action' => 'part_svc.cgi', #self - 'form_text' => [ qw( svc svcpart ) ], - 'form_checkbox' => [ 'disabled' ], - 'layer_callback' => sub { - my $layer = shift; - - my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!; - - my $columns = 3; - my $count = 0; - my @part_export = - map { qsearch( 'part_export', {exporttype => $_ } ) } - keys %{FS::part_export::export_info($layer)}; - $html .= '<BR><BR>'. table(). - table(). "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>"; - foreach my $part_export ( @part_export ) { - $html .= '<TD><INPUT TYPE="checkbox"'. - ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" '; - $html .= 'CHECKED' - if ( $clone || $part_svc->svcpart ) #null svcpart search causing error - && qsearchs( 'export_svc', { - exportnum => $part_export->exportnum, - svcpart => $clone || $part_svc->svcpart }); - $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype. - ' to '. $part_export->machine. '</TD>'; - $count++; - $html .= '</TR><TR>' unless $count % $columns; - } - $html .= '</TR></TABLE><BR><BR>'; +% #YUCK. false laziness w/part_svc.pm. go away virtual fields, please +% my %vfields; +% foreach my $svcdb ( FS::part_svc->svc_tables() ) { +% eval "use FS::$svcdb;"; +% my $self = "FS::$svcdb"->new; +% $vfields{$svcdb} = {}; +% foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them +% my $pvf = $self->pvf($field); +% $vfields{$svcdb}->{$field} = $pvf; +% #warn "\$vfields{$svcdb}->{$field} = $pvf"; +% } #next $field +% } #next $svcdb +% +% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm +% # and generalize the subs +% # condition sub is tested to see whether to disable display of this choice +% # params: ( $def, $layer, $field ) (see SUB below) +% my $inv_sub = sub { +% $_[0]->{disable_inventory} +% || $_[0]->{'type'} ne 'text' +% }; +% tie my %flag, 'Tie::IxHash', +% '' => { 'desc' => 'No default', }, +% 'D' => { 'desc' => 'Default', +% 'condition' => +% sub { $_[0]->{disable_default} }, +% }, +% 'F' => { 'desc' => 'Fixed (unchangeable)', +% 'condition' => +% sub { $_[0]->{disable_fixed} }, +% }, +% 'S' => { 'desc' => 'Selectable Choice', +% 'condition' => +% sub { !ref($_[0]) || $_[0]->{disable_select} }, +% }, +%# need to template-ize httemplate/edit/svc_* first +%# 'M' => { 'desc' => 'Manual selection from inventory', +%# 'condition' => $inv_sub, +%# }, +% 'A' => { 'desc' => 'Automatically fill in from inventory', +% 'condition' => $inv_sub, +% }, +% 'X' => { 'desc' => 'Excluded', +% 'condition' => +% sub { ! $vfields{$_[1]}->{$_[2]} }, +% +% }, +% ; +% +% my @dbs = $hashref->{svcdb} +% ? ( $hashref->{svcdb} ) +% : FS::part_svc->svc_tables(); +% +% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; +% my $widget = new HTML::Widgets::SelectLayers( +% #'selected_layer' => $p_svcdb, +% 'selected_layer' => $hashref->{svcdb} || 'svc_acct', +% 'options' => \%svcdb, +% 'form_name' => 'dummy', +% #'form_action' => 'process/part_svc.cgi', +% 'form_action' => 'part_svc.cgi', #self +% 'form_text' => [ qw( svc svcpart ) ], +% 'form_checkbox' => [ 'disabled' ], +% 'layer_callback' => sub { +% my $layer = shift; +% +% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!; +% +% my $columns = 3; +% my $count = 0; +% my @part_export = +% map { qsearch( 'part_export', {exporttype => $_ } ) } +% keys %{FS::part_export::export_info($layer)}; +% $html .= '<BR><BR>'. table(). +% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>"; +% foreach my $part_export ( @part_export ) { +% $html .= '<TD><INPUT TYPE="checkbox"'. +% ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" '; +% $html .= 'CHECKED' +% if ( $clone || $part_svc->svcpart ) #null svcpart search causing error +% && qsearchs( 'export_svc', { +% exportnum => $part_export->exportnum, +% svcpart => $clone || $part_svc->svcpart }); +% $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype. +% ' to '. $part_export->machine. '</TD>'; +% $count++; +% $html .= '</TR><TR>' unless $count % $columns; +% } +% $html .= '</TR></TABLE><BR><BR>'; +% +% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ). +% '<TR>'. +% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'. +% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'. +% '</TR>'; +% +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% #yucky kludge +% my @fields = defined( dbdef->table($layer) ) +% ? grep { $_ ne 'svcnum' } fields($layer) +% : (); +% push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge +% $part_svc->svcpart($clone) if $clone; #haha, undone below +% +% +% foreach my $field (@fields) { +% +% my $part_svc_column = $part_svc->part_svc_column($field); +% my $value = $part_svc_column->columnvalue; +% my $flag = $part_svc_column->columnflag; +% #my $def = $defs{$layer}{$field}; +% my $def = FS::part_svc->svc_table_fields($layer)->{$field}; +% my $label = $def->{'def_label'} || $def->{'label'}; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!. +% ( $label || $field ). +% "</TD>"; +% $flag = '' if $def->{type} eq 'disabled'; +% +% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!; +% +% if ( $def->{type} eq 'disabled' ) { +% +% $html .= 'No default'; +% +% } else { +% +% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!. +% qq! onChange="${layer}__${field}_flag_changed(this)">!; +% +% foreach my $f ( keys %flag ) { +% +% #here is where the SUB from above is called, to skip some choices +% next if $flag{$f}->{condition} +% && &{ $flag{$f}->{condition} }( $def, $layer, $field ); +% +% $html .= qq!<OPTION VALUE="$f"!. +% ' SELECTED'x($flag eq $f ). +% '>'. $flag{$f}->{desc}; +% +% } +% +% $html .= '</SELECT>'; +% +% $html .= join("\n", +% '<SCRIPT>', +% " function ${layer}__${field}_flag_changed(what) {", +% ' var f = what.options[what.selectedIndex].value;', +% ' if ( f == "" || f == "X" ) { //disable', +% " what.form.${layer}__${field}.disabled = true;". +% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = true;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';". +% " }". +% ' } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box', +% " what.form.${layer}__${field}.disabled = false;". +% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". +% " if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge +% " what.form.${layer}__${field}.multiple = true;". +% " } else {". +% " what.form.${layer}__${field}.multiple = false;". +% " }". +% " what.form.${layer}__${field}.style.display = '';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = false;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}_classnum.style.display = 'none';". +% " }". +% ' } else if ( f == "M" || f == "A" ) { //enable, inventory', +% " what.form.${layer}__${field}.disabled = false;". +% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}.style.display = 'none';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = false;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}_classnum.style.display = '';". +% " }". +% ' }', +% ' }', +% '</SCRIPT>', +% ); +% +% } +% +% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!; +% +% my $disabled = $flag ? '' +% : 'DISABLED STYLE="background-color: #dddddd"'; +% +% if ( !$def->{type} || $def->{type} eq 'text' ) { +% +% my $nodisplay = ' STYLE="display:none"'; +% my $is_inv = ( $flag =~ /^[MA]$/ ); +% +% $html .= +% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !. +% $disabled. +% ( $is_inv ? $nodisplay : $disabled ). +% '>'; +% +% $html .= include('/elements/select-table.html', +% 'element_name' => "${layer}__${field}_classnum", +% 'element_etc' => ( $is_inv +% ? $disabled +% : $nodisplay +% ), +% 'table' => 'inventory_class', +% 'name_col' => 'classname', +% 'value' => $value, +% 'empty_label' => 'Select inventory class', +% ); +% +% } elsif ( $def->{type} eq 'select' ) { +% +% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!; +% $html .= ' MULTIPLE' if $flag eq 'S'; +% $html .= '>'; +% $html .= '<OPTION> </OPTION>' unless $value; +% if ( $def->{select_table} ) { +% foreach my $record ( qsearch( $def->{select_table}, {} ) ) { +% my $rvalue = $record->getfield($def->{select_key}); +% $html .= qq!<OPTION VALUE="$rvalue"!. +% (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ). +% $record->getfield($def->{select_label}). '</OPTION>'; +% } #next $record +% } else { # select_list +% foreach my $item ( @{$def->{select_list}} ) { +% $html .= qq!<OPTION VALUE="$item"!. +% (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ). +% $item. '</OPTION>'; +% } #next $item +% } #endif +% $html .= '</SELECT>'; +% +% } elsif ( $def->{type} eq 'radius_usergroup_selector' ) { +% +% #XXX disable the RADIUS usergroup selector? ugh it sure does need +% #an overhaul, people have dum group problems because of it +% +% $html .= FS::svc_acct::radius_usergroup_selector( +% [ split(',', $value) ], "${layer}__${field}" ); +% +% } elsif ( $def->{type} eq 'disabled' ) { +% +% $html .= +% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!; +% +% } else { +% +% $html .= '<font color="#ff0000">unknown type'. $def->{type}; +% +% } +% +% $html .= "</TD></TR>\n"; +% +% } #foreach my $field (@fields) { +% +% $part_svc->svcpart('') if $clone; #undone +% $html .= "</TABLE>"; +% +% $html .= include('/elements/progress-init.html', +% $layer, #form name +% [ qw(svc svcpart disabled exportnum), @fields ], +% 'process/part_svc.cgi', +% $p.'browse/part_svc.cgi', +% $layer, +% ); +% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'. +% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '. +% ' onClick="document.'. "$layer.submit.disabled=true; ". +% "fixup(document.$layer); $layer". 'process();">'; +% +% #$html .= '<BR><INPUT TYPE="submit" VALUE="'. +% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">'; +% +% $html; +% +% }, +% ); +% +% - $html .= table(). "<TH>Field</TH><TH COLSPAN=2>Modifier</TH>"; - #yucky kludge - my @fields = defined( dbdef->table($layer) ) - ? grep { $_ ne 'svcnum' } fields($layer) - : (); - push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge - $part_svc->svcpart($clone) if $clone; #haha, undone below - foreach my $field (@fields) { - my $part_svc_column = $part_svc->part_svc_column($field); - my $value = $part_svc_column->columnvalue; - my $flag = $part_svc_column->columnflag; - my $def = $defs{$layer}{$field}; - my $desc = ref($def) ? $def->{desc} : $def; - - $html .= "<TR><TD>$field"; - $html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc; - $html .= "</TD>"; - $flag = '' if ref($def) && $def->{type} eq 'disabled'; - $html .= - qq!<TD><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE=""!. - ' CHECKED'x($flag eq ''). ">Off</TD>". - '<TD>'; - unless ( ref($def) && $def->{type} eq 'disabled' ) { - $html .= - qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="D"!. - ' CHECKED'x($flag eq 'D'). ">Default ". - qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="F"!. - ' CHECKED'x($flag eq 'F'). ">Fixed "; - $html .= '<BR>'; - } - if ( ref($def) ) { - if ( $def->{type} eq 'select' ) { - $html .= qq!<SELECT NAME="${layer}__${field}">!; - $html .= '<OPTION> </OPTION>' unless $value; - if ( $def->{select_table} ) { - foreach my $record ( qsearch( $def->{select_table}, {} ) ) { - my $rvalue = $record->getfield($def->{select_key}); - $html .= qq!<OPTION VALUE="$rvalue"!. - ( $rvalue==$value ? ' SELECTED>' : '>' ). - $record->getfield($def->{select_label}). '</OPTION>'; - } #next $record - } else { # select_list - foreach my $item ( @{$def->{select_list}} ) { - $html .= qq!<OPTION VALUE="$item"!. - ( $item eq $value ? ' SELECTED>' : '>' ). - $item. '</OPTION>'; - } #next $item - } #endif - $html .= '</SELECT>'; - } elsif ( $def->{type} eq 'radius_usergroup_selector' ) { - $html .= FS::svc_acct::radius_usergroup_selector( - [ split(',', $value) ], "${layer}__${field}" ); - } elsif ( $def->{type} eq 'disabled' ) { - $html .= - qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!; - } else { - $html .= '<font color="#ff0000">unknown type'. $def->{type}; - } - } else { - $html .= - qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value">!; - } - - if($vfields{$layer}->{$field}) { - $html .= qq!<BR><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="X"!. - ' CHECKED'x($flag eq 'X'). ">Excluded "; - } - $html .= "</TD></TR>\n"; - } - $part_svc->svcpart('') if $clone; #undone - $html .= "</TABLE>"; - - $html .= include('/elements/progress-init.html', - $layer, #form name - [ qw(svc svcpart disabled exportnum), @fields ], - 'process/part_svc.cgi', - $p.'browse/part_svc.cgi', - $layer, - ); - $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'. - ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '. - ' onClick="document.'. "$layer.submit.disabled=true; ". - "fixup(document.$layer); $layer". 'process();">'; - - #$html .= '<BR><INPUT TYPE="submit" VALUE="'. - # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">'; - - $html; - - }, - ); - -%> -Table <%= $widget->html %> +Table <% $widget->html %> </BODY> </HTML> diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi index fb10321e8..9dda4ebf9 100644 --- a/httemplate/edit/part_virtual_field.cgi +++ b/httemplate/edit/part_virtual_field.cgi @@ -1,92 +1,103 @@ -<!-- mason kludge --> -<% -my ($vfieldpart, $part_virtual_field); +% +%my ($vfieldpart, $part_virtual_field); +% +%if ( $cgi->param('error') ) { +% $part_virtual_field = new FS::part_virtual_field ( { +% map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')}); +% $vfieldpart = $part_virtual_field->vfieldpart; +%} else { +% my($query) = $cgi->keywords; +% if ( $query =~ /^(\d+)$/ ) { #editing +% $vfieldpart=$1; +% $part_virtual_field=qsearchs('part_virtual_field', +% {'vfieldpart' => $vfieldpart}) +% or die "Unknown vfieldpart!"; +% +% } else { #adding +% $part_virtual_field = new FS::part_virtual_field({}); +% } +%} +%my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +% +<% include('/elements/header.html', "$action Virtual Field Definition") %> +% if ( $cgi->param('error') ) { -if ( $cgi->param('error') ) { - $part_virtual_field = new FS::part_virtual_field ( { - map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')}); - $vfieldpart = $part_virtual_field->vfieldpart; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $vfieldpart=$1; - $part_virtual_field=qsearchs('part_virtual_field', - {'vfieldpart' => $vfieldpart}) - or die "Unknown vfieldpart!"; - - } else { #adding - $part_virtual_field = new FS::part_virtual_field({}); - } -} -my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add'; + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } -my $p1 = popurl(1); -print header("$action Virtual Field Definition", ''); -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); -%> -<FORM ACTION="<%=$p1%>process/generic.cgi" METHOD="POST"> +<FORM ACTION="<%$p1%>process/generic.cgi" METHOD="POST"> <INPUT TYPE="hidden" NAME="table" VALUE="part_virtual_field"> <INPUT TYPE="hidden" NAME="redirect_ok" - VALUE="<%=popurl(2)%>browse/part_virtual_field.cgi"> -<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<%= + VALUE="<%popurl(2)%>browse/part_virtual_field.cgi"> +<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<% $vfieldpart%>"> -Field #<B><%=$vfieldpart or "(NEW)"%></B><BR><BR> +Field #<B><%$vfieldpart or "(NEW)"%></B><BR><BR> -<%=ntable("#cccccc",2)%> +<%ntable("#cccccc",2)%> <TR> <TD ALIGN="right">Name</TD> - <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=15 VALUE="<%= + <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=15 VALUE="<% $part_virtual_field->name%>"></TD> </TR> <TR> <TD ALIGN="right">Table</TD> - <TD><% if ($action eq 'Add') { %> - <SELECT SIZE=1 NAME="dbtable"><% - my $dbdef = dbdef; # ick - foreach my $dbtable (sort { $a cmp $b } $dbdef->tables) { - if ($dbtable !~ /^h_/ - and $dbdef->table($dbtable)->primary_key) { %> - <OPTION VALUE="<%=$dbtable%>"><%=$dbtable%></OPTION><% - } - } - %></SELECT><% - } else { # Edit - %><%=$part_virtual_field->dbtable%> - <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%=$part_virtual_field->dbtable%>"> - <% } %> + <TD> +% if ($action eq 'Add') { + + <SELECT SIZE=1 NAME="dbtable"> +% +% my $dbdef = dbdef; # ick +% #foreach my $dbtable (sort { $a cmp $b } $dbdef->tables) { +% foreach my $dbtable (qw( svc_broadband )) { +% if ($dbtable !~ /^h_/ +% and $dbdef->table($dbtable)->primary_key) { + + <OPTION VALUE="<%$dbtable%>"><%$dbtable%></OPTION> +% +% } +% } +% +</SELECT> +% +% } else { # Edit +% +<%$part_virtual_field->dbtable%> + <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%$part_virtual_field->dbtable%>"> +% } + </TD> <TR> <TD ALIGN="right">Label</TD> - <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="20" VALUE="<%= + <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="20" VALUE="<% $part_virtual_field->label%>"></TD> </TR> <TR> <TD ALIGN="right">Length</TD> - <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<%= + <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<% $part_virtual_field->length%>"></TD> </TR> <TR> <TD ALIGN="right">Check</TD> - <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><%= + <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><% $part_virtual_field->check_block%></TEXTAREA></TD> </TR> <TR> <TD ALIGN="right">List source</TD> - <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><%= + <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><% $part_virtual_field->list_source%></TEXTAREA></TD> </TR> </TABLE><BR><INPUT TYPE="submit" VALUE="Submit"> </FORM> -<BR><BR> +<BR> <FONT SIZE=-2>If you don't understand what <I>check_block</I> and <I>list_source</I> mean, <B>LEAVE THEM BLANK</B>. We mean it.</FONT> - -</BODY> -</HTML> +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index 33cc236d0..59970c3f6 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -1,108 +1,133 @@ -<% - -my $payment_gateway; -if ( $cgi->param('error') ) { - $payment_gateway = new FS::payment_gateway ( { - map { $_, scalar($cgi->param($_)) } fields('payment_gateway') - } ); -} elsif ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } ); -} else { #adding - $payment_gateway = new FS::payment_gateway {}; -} -my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add'; -#my $hashref = $payment_gateway->hashref; - -%> - -<%= header("$action Payment gateway", menubar( +% +% +%my $payment_gateway; +%if ( $cgi->param('error') ) { +% $payment_gateway = new FS::payment_gateway ( { +% map { $_, scalar($cgi->param($_)) } fields('payment_gateway') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } ); +%} else { #adding +% $payment_gateway = new FS::payment_gateway {}; +%} +%my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add'; +%#my $hashref = $payment_gateway->hashref; +% +% + + +<% include("/elements/header.html","$action Payment gateway", menubar( 'Main Menu' => $p, 'View all payment gateways' => $p. 'browse/payment_gateway.html', )) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> -<FORM ACTION="<%=popurl(1)%>process/payment_gateway.html" METHOD=POST> -<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<%= $payment_gateway->gatewaynum %>"> -Gateway #<%= $payment_gateway->gatewaynum || "(NEW)" %> +<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>"> +Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %> -<%= ntable('#cccccc', 2, '') %> +<% ntable('#cccccc', 2, '') %> <TR> <TH ALIGN="right">Gateway: </TH> - <TD><SELECT NAME="gateway_module" SIZE=1> - <% foreach my $module ( qw( - 2CheckOut - AuthorizeNet - BankOfAmerica - Beanstream - Capstone - Cardstream - CashCow - CyberSource - eSec - eSelectPlus - Exact - iAuthorizer - IPaymentTPG - Jettis - LinkPoint - MerchantCommerce - Network1Financial - OCV - OpenECHO - PayConnect - PayflowPro - PaymentsGateway - PXPost - SecureHostingUPG - Skipjack - StGeorge - SurePay - TCLink - TransactionCentral - VirtualNet - ) ) { - %> - <OPTION VALUE="<%= $module %>"><%= $module %> - <% } %> - </SELECT> + <TD> +% if ( $payment_gateway->gatewaynum ) { + + + <% $payment_gateway->gateway_module %> + <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>"> +% } else { + + + <SELECT NAME="gateway_module" SIZE=1> +% foreach my $module ( qw( +% 2CheckOut +% AuthorizeNet +% BankOfAmerica +% Beanstream +% Capstone +% Cardstream +% CashCow +% CyberSource +% eSec +% eSelectPlus +% Exact +% iAuthorizer +% IPaymentTPG +% Jettis +% LinkPoint +% MerchantCommerce +% Network1Financial +% OCV +% OpenECHO +% PayConnect +% PayflowPro +% PaymentsGateway +% PXPost +% SecureHostingUPG +% Skipjack +% StGeorge +% SurePay +% TCLink +% TransactionCentral +% VirtualNet +% ) ) { +% + + <OPTION VALUE="<% $module %>"><% $module %> +% } + + </SELECT> +% } + + </TD> </TR> <TR> <TH ALIGN="right">Username: </TH> - <TD><INPUT TYPE="text" NAME="gateway_username"></TD> + <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD> </TR> <TR> <TH ALIGN="right">Password: </TH> - <TD><INPUT TYPE="text" NAME="gateway_password"></TD> + <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD> </TR> <TR> <TH ALIGN="right">Action: </TH> <TD> <SELECT NAME="gateway_action" SIZE=1> - <OPTION VALUE="Normal Authorization">Normal Authorization - <OPTION VALUE="Authorization Only">Authorization Only - <OPTION VALUE="Authorization Only, Post Authorization">Authorization Only, Post Authorization +% foreach my $action ( +% 'Normal Authorization', +% 'Authorization Only', +% 'Authorization Only, Post Authorization', +% ) { +% + + <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %> +% } + </SELECT> </TD> </TR> <TR> - <TH ALIGN="right">Options: </TH> - <TD><TEXTAREA ROWS="5" NAME="gateway_options"></TEXTAREA></TD> + <TH ALIGN="right">Options: (Name/Value pairs, one element per line)</TH> + <TD> + <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA> + </TD> </TR> </TABLE> -<BR><INPUT TYPE="submit" VALUE="<%= $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>"> +<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>"> </FORM> </BODY> </HTML> diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html new file mode 100644 index 000000000..6f2b072f1 --- /dev/null +++ b/httemplate/edit/pkg_class.html @@ -0,0 +1,16 @@ +<% include( 'elements/edit.html', + 'name' => 'Package Class', + 'table' => 'pkg_class', + 'fields' => [ + 'classname', + { field=>'disabled', type=>'checkbox', value=>'Y', }, + ], + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + 'disabled' => 'Disable class', + }, + 'viewall_dir' => 'browse', + ) + +%> diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi index 9cf0fc6e1..c22904d6c 100644 --- a/httemplate/edit/prepay_credit.cgi +++ b/httemplate/edit/prepay_credit.cgi @@ -1,54 +1,109 @@ -<% -my $agent = ''; -my $agentnum = ''; -if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - $agent = qsearchs('agent', { 'agentnum' => $agentnum=$1 } ); -} +% +%my $agent = ''; +%my $agentnum = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agent = qsearchs('agent', { 'agentnum' => $agentnum=$1 } ); +%} +% +%tie my %multiplier, 'Tie::IxHash', +% 1 => 'seconds', +% 60 => 'minutes', +% 3600 => 'hours', +%; +% +%tie my %bytemultiplier, 'Tie::IxHash', +% 1 => 'bytes', +% 1000 => 'Kbytes', +% 1000000 => 'Mbytes', +% 1000000000 => 'Gbytes', +%; +% +%$cgi->param('multiplier', '60') unless $cgi->param('multiplier'); +%$cgi->param('upmultiplier', '1000000') unless $cgi->param('upmultiplier'); +%$cgi->param('downmultiplier', '1000000') unless $cgi->param('downmultiplier'); +%$cgi->param('totalmultiplier','1000000') unless $cgi->param('totalmultiplier'); +% +% -tie my %multiplier, 'Tie::IxHash', - 1 => 'seconds', - 60 => 'minutes', - 3600 => 'hours', -; -$cgi->param('multiplier', '60') unless $cgi->param('multiplier'); - -%> - -<%= header('Generate prepaid cards'. ($agent ? ' for '. $agent->agent : ''), +<% include("/elements/header.html",'Generate prepaid cards'. ($agent ? ' for '. $agent->agent : ''), menubar( 'Main Menu' => $p, )) %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#FF0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#FF0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> -<FORM ACTION="<%=popurl(1)%>process/prepay_credit.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> +<FORM ACTION="<%popurl(1)%>process/prepay_credit.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> Generate -<INPUT TYPE="text" NAME="num" VALUE="<%= $cgi->param('num') || '(quantity)' %>" SIZE=10 MAXLENGTH=10 onFocus="if ( this.value == '(quantity)' ) { this.value = ''; }"> +<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') || '(quantity)' %>" SIZE=10 MAXLENGTH=10 onFocus="if ( this.value == '(quantity)' ) { this.value = ''; }"> <SELECT NAME="type"> -<% foreach (qw(alpha alphanumeric numeric)) { %> - <OPTION<%= $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><%= $_ %> -<% } %> +% foreach (qw(alpha alphanumeric numeric)) { + + <OPTION<% $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><% $_ %> +% } + </SELECT> prepaid cards <BR>for <SELECT NAME="agentnum"><OPTION>(any agent) -<% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) { %> - <OPTION VALUE="<%= $opt_agent->agentnum %>"<%= $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><%= $opt_agent->agent %> -<% } %> +% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) { + + <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %> +% } + </SELECT> -<BR>Value: -$<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<%= $cgi->param('amount') %>"> -and/or -<INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<%= $cgi->param('seconds') %>"> +<TABLE> +<TR><TD>Value: +$<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<% $cgi->param('amount') %>"> +</TD> +<TD>and/or +<INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('seconds') %>"> <SELECT NAME="multiplier"> -<% foreach my $multiplier ( keys %multiplier ) { %> - <OPTION VALUE="<%= $multiplier %>"<%= $cgi->param('multiplier') eq $multiplier ? ' SELECTED' : '' %>><%= $multiplier{$multiplier} %> -<% } %> +% foreach my $multiplier ( keys %multiplier ) { + + <OPTION VALUE="<% $multiplier %>"<% $cgi->param('multiplier') eq $multiplier ? ' SELECTED' : '' %>><% $multiplier{$multiplier} %> +% } + </SELECT> +</TD></TR> +<TR><TD></TD> +<TD>and/or +<INPUT TYPE="text" NAME="upbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('upbytes') %>"> +<SELECT NAME="upmultiplier"> +% foreach my $multiplier ( keys %bytemultiplier ) { + + <OPTION VALUE="<% $multiplier %>"<% $cgi->param('upmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %> +% } + +</SELECT> upload +</TD></TR> +<TR><TD></TD> +<TD>and/or +<INPUT TYPE="text" NAME="downbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('downbytes') %>"> +<SELECT NAME="downmultiplier"> +% foreach my $multiplier ( keys %bytemultiplier ) { + + <OPTION VALUE="<% $multiplier %>"<% $cgi->param('downmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %> +% } + +</SELECT> download +</TD></TR> +<TR><TD></TD> +<TD>and/or +<INPUT TYPE="text" NAME="totalbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('totalbytes') %>"> +<SELECT NAME="totalmultiplier"> +% foreach my $multiplier ( keys %bytemultiplier ) { + + <OPTION VALUE="<% $multiplier %>"<% $cgi->param('totalmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %> +% } + +</SELECT> total transfer +</TD></TR> +</TABLE> <BR><BR> <INPUT TYPE="submit" NAME="submit" VALUE="Generate" onSubmit="this.disabled = true"> diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi index 84d0cc129..26e234fb0 100755 --- a/httemplate/edit/process/REAL_cust_pkg.cgi +++ b/httemplate/edit/process/REAL_cust_pkg.cgi @@ -1,34 +1,35 @@ -<% +% +% +%my $pkgnum = $cgi->param('pkgnum') or die; +%my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%my %hash = $old->hash; +%$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : ''; +%$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : ''; +%$hash{'last_bill'} = +% $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : ''; +%$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : ''; +% +%my $new; +%my $error; +%if ( $hash{'bill'} != $old->bill # if the next bill date was changed +% && $hash{'bill'} < time # to a date in the past +% && ! $cgi->param('bill_areyousure') # and it wasn't confirmed +% ) +%{ +% $error = '_bill_areyousure'; +%} else { +% $new = new FS::cust_pkg \%hash; +% $error = $new->replace($old); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ); +%} else { +% my $custnum = $new->custnum; +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg$pkgnum" ); +%} +% +% -my $pkgnum = $cgi->param('pkgnum') or die; -my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); -my %hash = $old->hash; -$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : ''; -$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : ''; -$hash{'last_bill'} = - $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : ''; -$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : ''; - -my $new; -my $error; -if ( $hash{'bill'} != $old->bill # if the next bill date was changed - && $hash{'bill'} < time # to a date in the past - && ! $cgi->param('bill_areyousure') # and it wasn't confirmed - ) -{ - $error = '_bill_areyousure'; -} else { - $new = new FS::cust_pkg \%hash; - $error = $new->replace($old); -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ); -} else { - my $custnum = $new->custnum; - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". - "#cust_pkg$pkgnum" ); -} - -%> diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html new file mode 100644 index 000000000..c80311586 --- /dev/null +++ b/httemplate/edit/process/access_group.html @@ -0,0 +1,15 @@ +<% include( 'elements/process.html', + 'table' => 'access_group', + 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + }, + 'process_m2name' => { + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => 'FS::access_group', }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + }, + ) +%> diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html new file mode 100644 index 000000000..9f7c4ddbf --- /dev/null +++ b/httemplate/edit/process/access_user.html @@ -0,0 +1,15 @@ +% if ( $cgi->param('_password') ne $cgi->param('_password2') ) { +% $cgi->param('error', "The passwords do not match"); +% print $cgi->redirect(popurl(2) . "access_user.html?" . $cgi->query_string); +% } else { +<% include( 'elements/process.html', + 'table' => 'access_user', + 'viewall_dir' => 'browse', + 'copy_on_empty' => [ '_password' ], + 'clear_on_error' => [ '_password', '_password2' ], + 'process_m2m' => { 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + }, + ) +%> +% } diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi index 34d799ccd..85780c678 100755 --- a/httemplate/edit/process/addr_block/add.cgi +++ b/httemplate/edit/process/addr_block/add.cgi @@ -1,20 +1,21 @@ -<% +% +% +%my $error = ''; +%my $ip_gateway = $cgi->param('ip_gateway'); +%my $ip_netmask = $cgi->param('ip_netmask'); +% +%my $new = new FS::addr_block { +% ip_gateway => $ip_gateway, +% ip_netmask => $ip_netmask, +% routernum => 0 }; +% +%$error = $new->insert; +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% -my $error = ''; -my $ip_gateway = $cgi->param('ip_gateway'); -my $ip_netmask = $cgi->param('ip_netmask'); - -my $new = new FS::addr_block { - ip_gateway => $ip_gateway, - ip_netmask => $ip_netmask, - routernum => 0 }; - -$error = $new->insert; - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); -} -%> diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi index 85b0d7a7a..a94c0320f 100755 --- a/httemplate/edit/process/addr_block/allocate.cgi +++ b/httemplate/edit/process/addr_block/allocate.cgi @@ -1,25 +1,26 @@ -<% -my $error = ''; -my $blocknum = $cgi->param('blocknum'); -my $routernum = $cgi->param('routernum'); +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +%my $routernum = $cgi->param('routernum'); +% +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +%my $router = qsearchs('router', { routernum => $routernum }); +% +%if($addr_block) { +% if ($router) { +% $error = $addr_block->allocate($router); +% } else { +% $error = "Cannot find router with routernum $routernum"; +% } +%} else { +% $error = "Cannot find block with blocknum $blocknum"; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% -my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); -my $router = qsearchs('router', { routernum => $routernum }); - -if($addr_block) { - if ($router) { - $error = $addr_block->allocate($router); - } else { - $error = "Cannot find router with routernum $routernum"; - } -} else { - $error = "Cannot find block with blocknum $blocknum"; -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); -} else { - print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); -} -%> diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi index cfb7ed04d..494c19f75 100755 --- a/httemplate/edit/process/addr_block/deallocate.cgi +++ b/httemplate/edit/process/addr_block/deallocate.cgi @@ -1,24 +1,25 @@ -<% -my $error = ''; -my $blocknum = $cgi->param('blocknum'); +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +% +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +% +%if($addr_block) { +% my $router = $addr_block->router; +% if ($router) { +% $error = $addr_block->deallocate($router); +% } else { +% $error = "Block is not allocated to a router"; +% } +%} else { +% $error = "Cannot find block with blocknum $blocknum"; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% -my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); - -if($addr_block) { - my $router = $addr_block->router; - if ($router) { - $error = $addr_block->deallocate($router); - } else { - $error = "Block is not allocated to a router"; - } -} else { - $error = "Cannot find block with blocknum $blocknum"; -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); -} else { - print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); -} -%> diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi index bb6d4ba3e..617c3f8ce 100755 --- a/httemplate/edit/process/addr_block/split.cgi +++ b/httemplate/edit/process/addr_block/split.cgi @@ -1,19 +1,20 @@ -<% -my $error = ''; -my $blocknum = $cgi->param('blocknum'); -my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +% +%if ( $addr_block) { +% $error = $addr_block->split_block; +%} else { +% $error = "Unknown blocknum: $blocknum"; +%} +% +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% -if ( $addr_block) { - $error = $addr_block->split_block; -} else { - $error = "Unknown blocknum: $blocknum"; -} - - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); -} -%> diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi index 182eeab41..5128d7ae8 100755 --- a/httemplate/edit/process/agent.cgi +++ b/httemplate/edit/process/agent.cgi @@ -1,28 +1,29 @@ -<% +% +% +%my $agentnum = $cgi->param('agentnum'); +% +%my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum; +% +%my $new = new FS::agent ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('agent') +%} ); +% +%my $error; +%if ( $agentnum ) { +% $error=$new->replace($old); +%} else { +% $error=$new->insert; +% $agentnum=$new->getfield('agentnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/agent.cgi"); +%} +% +% -my $agentnum = $cgi->param('agentnum'); - -my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum; - -my $new = new FS::agent ( { - map { - $_, scalar($cgi->param($_)); - } fields('agent') -} ); - -my $error; -if ( $agentnum ) { - $error=$new->replace($old); -} else { - $error=$new->insert; - $agentnum=$new->getfield('agentnum'); -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/agent.cgi"); -} - -%> diff --git a/httemplate/edit/process/agent_payment_gateway.html b/httemplate/edit/process/agent_payment_gateway.html index c306bfa3f..436317ec4 100644 --- a/httemplate/edit/process/agent_payment_gateway.html +++ b/httemplate/edit/process/agent_payment_gateway.html @@ -1,25 +1,26 @@ -<% - -$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; -my $agent = qsearchs('agent', { 'agentnum' => $1 } ); -die "agentnum $1 not found" unless $agent; - -#my $old - -my @new = map { - my $cardtype = $_; - new FS::agent_payment_gateway { - ( map { $_ => scalar($cgi->param($_)) } - fields('agent_payment_gateway') - ), - 'cardtype' => $cardtype, - }; - } - $cgi->param('cardtype'); - -foreach my $new (@new) { - my $error = $new->insert; - die $error if $error; -} - -%><%= $cgi->redirect(popurl(3). "browse/agent.cgi") %> +% +% +%$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; +%my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +%die "agentnum $1 not found" unless $agent; +% +%#my $old +% +%my @new = map { +% my $cardtype = $_; +% new FS::agent_payment_gateway { +% ( map { $_ => scalar($cgi->param($_)) } +% fields('agent_payment_gateway') +% ), +% 'cardtype' => $cardtype, +% }; +% } +% $cgi->param('cardtype'); +% +%foreach my $new (@new) { +% my $error = $new->insert; +% die $error if $error; +%} +% +% +<% $cgi->redirect(popurl(3). "browse/agent.cgi") %> diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi index 516594573..b8d03705c 100755 --- a/httemplate/edit/process/agent_type.cgi +++ b/httemplate/edit/process/agent_type.cgi @@ -1,55 +1,37 @@ -<% +% +% +%my $typenum = $cgi->param('typenum'); +%my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum; +% +%my $new = new FS::agent_type ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('agent_type') +%} ); +% +%my $error; +%if ( $typenum ) { +% $error = $new->replace($old); +%} else { +% $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 { +% +% 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"); +%} +% +% -my $typenum = $cgi->param('typenum'); -my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum; - -my $new = new FS::agent_type ( { - map { - $_, scalar($cgi->param($_)); - } fields('agent_type') -} ); - -my $error; -if ( $typenum ) { - $error=$new->replace($old); -} else { - $error=$new->insert; - $typenum=$new->getfield('typenum'); -} - -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; - } - - } - - print $cgi->redirect(popurl(3). "browse/agent_type.cgi"); -} - -%> diff --git a/httemplate/edit/process/bulk-cust_svc.cgi b/httemplate/edit/process/bulk-cust_svc.cgi index dd9d1dbd2..ad4d67307 100644 --- a/httemplate/edit/process/bulk-cust_svc.cgi +++ b/httemplate/edit/process/bulk-cust_svc.cgi @@ -1,3 +1,4 @@ -<% - my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process_bulk_cust_svc', $cgi; -%><%= $server->process %> +% +% my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process_bulk_cust_svc', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi index 0025b16b5..962fc4eb9 100755 --- a/httemplate/edit/process/cust_bill_pay.cgi +++ b/httemplate/edit/process/cust_bill_pay.cgi @@ -1,43 +1,54 @@ -<% +% +% +%$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ) +% or die "No such paynum"; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } ) +% or die "Bogus credit: not attached to customer"; +% +%my $custnum = $cust_main->custnum; +% +%my $new; +%if ($cgi->param('invnum') =~ /^Refund$/) { +% $new = new FS::cust_refund ( { +% 'reason' => 'Refunding payment', #enter reason in UI +% 'refund' => $cgi->param('amount'), +% 'payby' => 'BILL', +% #'_date' => $cgi->param('_date'), +% 'payinfo' => 'Cash', #enter payinfo in UI +% 'paynum' => $paynum, +% } ); +%} else { +% $new = new FS::cust_bill_pay ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(custnum _date amount invnum) +% } fields('cust_bill_pay') +% } ); +%} +% +%my $error = $new->insert; +% +%if ( $error ) { +% +% $cgi->param('error', $error); +% +<% $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %> +% +% +%} else { +% +% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +% +% +<% header('Payment application sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% } -$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; -my $paynum = $1; - -my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ) - or die "No such paynum"; - -my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } ) - or die "Bogus credit: not attached to customer"; - -my $custnum = $cust_main->custnum; - -my $new; -if ($cgi->param('invnum') =~ /^Refund$/) { - $new = new FS::cust_refund ( { - 'reason' => 'Refunding payment', #enter reason in UI - 'refund' => $cgi->param('amount'), - 'payby' => 'BILL', - #'_date' => $cgi->param('_date'), - 'payinfo' => 'Cash', #enter payinfo in UI - 'paynum' => $paynum, - } ); -} else { - $new = new FS::cust_bill_pay ( { - map { - $_, scalar($cgi->param($_)); - #} qw(custnum _date amount invnum) - } fields('cust_bill_pay') - } ); -} - -my $error = $new->insert; - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} - - -%> diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi index 85bfd4489..19faca47a 100755 --- a/httemplate/edit/process/cust_credit.cgi +++ b/httemplate/edit/process/cust_credit.cgi @@ -1,26 +1,38 @@ -<% +% +% +%$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; +%my $custnum = $1; +% +%my $new = new FS::cust_credit ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('cust_credit') +%} ); +% +%my $error = $new->insert; +% +%if ( $error ) { +% $cgi->param('error', $error); +% +% +<% $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"); +% +% +<% header('Credit sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> -$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; -my $custnum = $1; + </BODY></HTML> +% } -my $new = new FS::cust_credit ( { - map { - $_, scalar($cgi->param($_)); - } fields('cust_credit') -} ); - -my $error = $new->insert; - -if ( $error ) { - $cgi->param('error', $error); - print $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"); -} - -%> diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi index 28f892f62..7509a3f02 100755 --- a/httemplate/edit/process/cust_credit_bill.cgi +++ b/httemplate/edit/process/cust_credit_bill.cgi @@ -1,44 +1,55 @@ -<% +% +% +%$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!"; +%my $crednum = $1; +% +%my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ) +% or die "No such crednum"; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } ) +% or die "Bogus credit: not attached to customer"; +% +%my $custnum = $cust_main->custnum; +% +%my $new; +%if ($cgi->param('invnum') =~ /^Refund$/) { +% $new = new FS::cust_refund ( { +% 'reason' => ( $cust_credit->reason || 'refund from credit' ), +% 'refund' => $cgi->param('amount'), +% 'payby' => 'BILL', +% #'_date' => $cgi->param('_date'), +% #'payinfo' => 'Cash', +% 'payinfo' => 'Refund', +% 'crednum' => $crednum, +% } ); +%} else { +% $new = new FS::cust_credit_bill ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(custnum _date amount invnum) +% } fields('cust_credit_bill') +% } ); +%} +% +%my $error = $new->insert; +% +%if ( $error ) { +% +% $cgi->param('error', $error); +% +<% $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %> +% +% +%} else { +% +% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +% +% +<% header('Credit application sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% } -$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!"; -my $crednum = $1; - -my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ) - or die "No such crednum"; - -my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } ) - or die "Bogus credit: not attached to customer"; - -my $custnum = $cust_main->custnum; - -my $new; -if ($cgi->param('invnum') =~ /^Refund$/) { - $new = new FS::cust_refund ( { - 'reason' => ( $cust_credit->reason || 'refund from credit' ), - 'refund' => $cgi->param('amount'), - 'payby' => 'BILL', - #'_date' => $cgi->param('_date'), - #'payinfo' => 'Cash', - 'payinfo' => 'Refund', - 'crednum' => $crednum, - } ); -} else { - $new = new FS::cust_credit_bill ( { - map { - $_, scalar($cgi->param($_)); - #} qw(custnum _date amount invnum) - } fields('cust_credit_bill') - } ); -} - -my $error = $new->insert; - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} - - -%> diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index 09a42544c..a4a70ca22 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -1,155 +1,176 @@ -<% - -my $error = ''; - -#unmunge stuff - -$cgi->param('tax','') unless defined $cgi->param('tax'); - -$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] ); - -#my $payby = $cgi->param('payby'); -my $payby = $cgi->param('select'); # XXX key - -my %noauto = ( - 'CARD' => 'DCRD', - 'CHEK' => 'DCHK', -); -$payby = $noauto{$payby} - if ! $cgi->param('payauto') && exists $noauto{$payby}; - -$cgi->param('payby', $payby); - -if ( $payby ) { - if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) { - $cgi->param('payinfo', - $cgi->param('payinfo1'). '@'. $cgi->param('payinfo2') ); - } - $cgi->param('paydate', - $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) ); -} - -my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); -push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); -push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX'); -$cgi->param('invoicing_list', join(',', @invoicing_list) ); - - -#create new record object - -my $new = new FS::cust_main ( { - map { - $_, scalar($cgi->param($_)) -# } qw(custnum agentnum last first ss company address1 address2 city county -# state zip daytime night fax payby payinfo paydate payname tax -# otaker refnum) - } fields('cust_main') -} ); - -if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) { - $new->setfield("ship_$_", '') foreach qw( - last first company address1 address2 city county state zip - country daytime night fax - ); -} - -$new->setfield('paid', $cgi->param('paid') ) - if $cgi->param('paid'); - -#perhaps this stuff should go to cust_main.pm -my $cust_pkg = ''; -my $svc_acct = ''; -if ( $new->custnum eq '' ) { - - if ( $cgi->param('pkgpart_svcpart') ) { - my $x = $cgi->param('pkgpart_svcpart'); - $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n"; - my($pkgpart, $svcpart) = ($1, $2); - #false laziness: copied from FS::cust_pkg::order (which should become a - #FS::cust_main method) - my(%part_pkg); - # generate %part_pkg - # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart - my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum }); - #my($type_pkgs); - #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { - # my($pkgpart)=$type_pkgs->pkgpart; - # $part_pkg{$pkgpart}++; - #} - # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart - my $pkgpart_href = $agent->pkgpart_hashref; - #eslaf - - # this should wind up in FS::cust_pkg! - $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ". - "purchase pkgpart ". $pkgpart - #unless $part_pkg{ $pkgpart }; - unless $pkgpart_href->{ $pkgpart }; - - $cust_pkg = new FS::cust_pkg ( { - #later 'custnum' => $custnum, - 'pkgpart' => $pkgpart, - } ); - #$error ||= $cust_pkg->check; - - #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } ); - - #$error ||= $cust_svc->check; - - $svc_acct = new FS::svc_acct ( { - 'svcpart' => $svcpart, - 'username' => $cgi->param('username'), - '_password' => $cgi->param('_password'), - 'popnum' => $cgi->param('popnum'), - } ); - - my $y = $svc_acct->setdefault; # arguably should be in new method - $error ||= $y unless ref($y); - #and just in case you were silly - $svc_acct->svcpart($svcpart); - $svc_acct->username($cgi->param('username')); - $svc_acct->_password($cgi->param('_password')); - $svc_acct->popnum($cgi->param('popnum')); - - #$error ||= $svc_acct->check; - - } elsif ( $cgi->param('username') ) { #good thing to catch - $error = "Can't assign username without a package!"; - } - - use Tie::RefHash; - tie my %hash, 'Tie::RefHash'; - %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg; - $error ||= $new->insert( \%hash, \@invoicing_list ); - - my $conf = new FS::Conf; - if ( $conf->exists('backend-realtime') && ! $error ) { - - my $berror = $new->bill; - $new->apply_payments; - $new->apply_credits; - $berror ||= $new->collect; - warn "Warning, error billing during backend-realtime: $berror" if $berror; - - } - -} else { #create old record object - - my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } ); - $error ||= "Old record not found!" unless $old; - if ( defined dbdef->table('cust_main')->column('paycvv') - && length($old->paycvv) - && $new->paycvv =~ /^\s*\*+\s*$/ ) { - $new->paycvv($old->paycvv); - } - $error ||= $new->replace($old, \@invoicing_list); - -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum); -} -%> +%my $error = ''; +% +%#unmunge stuff +% +%$cgi->param('tax','') unless defined $cgi->param('tax'); +% +%$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] ); +% +%#my $payby = $cgi->param('payby'); +%my $payby = $cgi->param('select'); # XXX key +% +%my %noauto = ( +% 'CARD' => 'DCRD', +% 'CHEK' => 'DCHK', +%); +%$payby = $noauto{$payby} +% if ! $cgi->param('payauto') && exists $noauto{$payby}; +% +%$cgi->param('payby', $payby); +% +%if ( $payby ) { +% if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) { +% $cgi->param('payinfo', +% $cgi->param('payinfo1'). '@'. $cgi->param('payinfo2') ); +% } +% $cgi->param('paydate', +% $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) ); +%} +% +%my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); +%push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); +%push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX'); +%$cgi->param('invoicing_list', join(',', @invoicing_list) ); +% +% +%#create new record object +% +%my $new = new FS::cust_main ( { +% map { +% $_, scalar($cgi->param($_)) +%# } qw(custnum agentnum last first ss company address1 address2 city county +%# state zip daytime night fax payby payinfo paydate payname tax +%# otaker refnum) +% } fields('cust_main') +%} ); +% +% delete( $new->hashref->{'agent_custid'} ) +% unless $new->hashref->{'agent_custid'}; +% +%if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) { +% $new->setfield("ship_$_", '') foreach qw( +% last first company address1 address2 city county state zip +% country daytime night fax +% ); +%} +% +%if ( $cgi->param('birthdate') && $cgi->param('birthdate') =~ /^([ 0-9\-\/]{0,10})$/) { +% my $conf = new FS::Conf; +% my $format = $conf->config('date_format') || "%m/%d/%Y"; +% my $parser = DateTime::Format::Strptime->new(pattern => $format, +% time_zone => 'floating', +% ); +% my $dt = $parser->parse_datetime($1); +% if ($dt) { +% $new->setfield('birthdate', $dt->epoch); +% $cgi->param('birthdate', $dt->epoch); +% } else { +%# $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg; +% $error ||= "Invalid birthdate: " . $cgi->param('birthdate') . "."; +% $cgi->param('birthdate', ''); +% } +%} +% +%$new->setfield('paid', $cgi->param('paid') ) +% if $cgi->param('paid'); +% +%#perhaps this stuff should go to cust_main.pm +%my $cust_pkg = ''; +%my $svc_acct = ''; +%if ( $new->custnum eq '' ) { +% +% if ( $cgi->param('pkgpart_svcpart') ) { +% my $x = $cgi->param('pkgpart_svcpart'); +% $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n"; +% my($pkgpart, $svcpart) = ($1, $2); +% #false laziness: copied from FS::cust_pkg::order (which should become a +% #FS::cust_main method) +% my(%part_pkg); +% # generate %part_pkg +% # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart +% my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum }); +% #my($type_pkgs); +% #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { +% # my($pkgpart)=$type_pkgs->pkgpart; +% # $part_pkg{$pkgpart}++; +% #} +% # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart +% my $pkgpart_href = $agent->pkgpart_hashref; +% #eslaf +% +% # this should wind up in FS::cust_pkg! +% $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ". +% "purchase pkgpart ". $pkgpart +% #unless $part_pkg{ $pkgpart }; +% unless $pkgpart_href->{ $pkgpart }; +% +% $cust_pkg = new FS::cust_pkg ( { +% #later 'custnum' => $custnum, +% 'pkgpart' => $pkgpart, +% } ); +% #$error ||= $cust_pkg->check; +% +% #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } ); +% +% #$error ||= $cust_svc->check; +% +% my %svc_acct = ( +% 'svcpart' => $svcpart, +% 'username' => $cgi->param('username'), +% '_password' => $cgi->param('_password'), +% 'popnum' => $cgi->param('popnum'), +% ); +% $svc_acct{'domsvc'} = $cgi->param('domsvc') +% if $cgi->param('domsvc'); +% +% $svc_acct = new FS::svc_acct \%svc_acct; +% +% #and just in case you were silly +% $svc_acct->svcpart($svcpart); +% $svc_acct->username($cgi->param('username')); +% $svc_acct->_password($cgi->param('_password')); +% $svc_acct->popnum($cgi->param('popnum')); +% +% #$error ||= $svc_acct->check; +% +% } elsif ( $cgi->param('username') ) { #good thing to catch +% $error = "Can't assign username without a package!"; +% } +% +% use Tie::RefHash; +% tie my %hash, 'Tie::RefHash'; +% %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg; +% $error ||= $new->insert( \%hash, \@invoicing_list ); +% +% my $conf = new FS::Conf; +% if ( $conf->exists('backend-realtime') && ! $error ) { +% +% my $berror = $new->bill; +% $new->apply_payments_and_credits; +% $berror ||= $new->collect( 'realtime' => 1 ); +% warn "Warning, error billing during backend-realtime: $berror" if $berror; +% +% } +% +%} else { #create old record object +% +% my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } ); +% $error ||= "Old record not found!" unless $old; +% if ( defined dbdef->table('cust_main')->column('paycvv') +% && length($old->paycvv) +% && $new->paycvv =~ /^\s*\*+\s*$/ ) { +% $new->paycvv($old->paycvv); +% } +% if ($new->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ && $new->payinfo =~ /xx/) { +% $new->payinfo($old->payinfo); +% } +% $error ||= $new->replace($old, \@invoicing_list); +% +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum); +%} diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi index 5da9dea80..4bcaf1de3 100755 --- a/httemplate/edit/process/cust_main_county-collapse.cgi +++ b/httemplate/edit/process/cust_main_county-collapse.cgi @@ -1,35 +1,36 @@ -<% +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ or die "Illegal taxnum!"; +%my $taxnum = $1; +%my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } ) +% or die "Unknown taxnum $taxnum"; +% +%#really should do this in a .pm & start transaction +% +%foreach my $delete ( qsearch('cust_main_county', { +% 'country' => $cust_main_county->country, +% 'state' => $cust_main_county->state +% } ) ) { +%# unless ( qsearch('cust_main',{ +%# 'state' => $cust_main_county->getfield('state'), +%# 'county' => $cust_main_county->getfield('county'), +%# 'country' => $cust_main_county->getfield('country'), +%# } ) ) { +% my $error = $delete->delete; +% die $error if $error; +%# } else { +% #should really fix the $cust_main record +%# } +% +%} +% +%$cust_main_county->taxnum(''); +%$cust_main_county->county(''); +%my $error = $cust_main_county->insert; +%die $error if $error; +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ or die "Illegal taxnum!"; -my $taxnum = $1; -my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } ) - or die "Unknown taxnum $taxnum"; - -#really should do this in a .pm & start transaction - -foreach my $delete ( qsearch('cust_main_county', { - 'country' => $cust_main_county->country, - 'state' => $cust_main_county->state - } ) ) { -# unless ( qsearch('cust_main',{ -# 'state' => $cust_main_county->getfield('state'), -# 'county' => $cust_main_county->getfield('county'), -# 'country' => $cust_main_county->getfield('country'), -# } ) ) { - my $error = $delete->delete; - die $error if $error; -# } else { - #should really fix the $cust_main record -# } - -} - -$cust_main_county->taxnum(''); -$cust_main_county->county(''); -my $error = $cust_main_county->insert; -die $error if $error; - -print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); - -%> diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi index a452711c1..e550e8b4a 100755 --- a/httemplate/edit/process/cust_main_county-expand.cgi +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -1,58 +1,59 @@ -<% +% +% +%$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!"; +%my $taxnum = $1; +%my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +% or die ("Unknown taxnum!"); +% +%my @expansion; +%if ( $cgi->param('delim') eq 'n' ) { +% @expansion=split(/\n/,$cgi->param('expansion')); +%} elsif ( $cgi->param('delim') eq 's' ) { +% @expansion=split(' ',$cgi->param('expansion')); +%} else { +% die "Illegal delim!"; +%} +% +%@expansion=map { +% unless ( /^\s*([\w\- ]+)\s*$/ ) { +% $cgi->param('error', "Illegal item in expansion"); +% print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string ); +% myexit(); +% } +% $1; +%} @expansion; +% +%foreach ( @expansion) { +% my(%hash)=$cust_main_county->hash; +% my($new)=new FS::cust_main_county \%hash; +% $new->setfield('taxnum',''); +% if ( $cgi->param('taxclass') ) { +% $new->setfield('taxclass', $_); +% } elsif ( ! $cust_main_county->state ) { +% $new->setfield('state',$_); +% } else { +% $new->setfield('county',$_); +% } +% #if (datasrc =~ m/Pg/) +% #{ +% # $new->setfield('tax',0.0); +% #} +% my($error)=$new->insert; +% die $error if $error; +%} +% +%unless ( qsearch( 'cust_main', { +% 'state' => $cust_main_county->state, +% 'county' => $cust_main_county->county, +% 'country' => $cust_main_county->country, +% } ) +% || ! @expansion +%) { +% my($error)=($cust_main_county->delete); +% die $error if $error; +%} +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% -$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!"; -my $taxnum = $1; -my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) - or die ("Unknown taxnum!"); - -my @expansion; -if ( $cgi->param('delim') eq 'n' ) { - @expansion=split(/\n/,$cgi->param('expansion')); -} elsif ( $cgi->param('delim') eq 's' ) { - @expansion=split(' ',$cgi->param('expansion')); -} else { - die "Illegal delim!"; -} - -@expansion=map { - unless ( /^\s*([\w\- ]+)\s*$/ ) { - $cgi->param('error', "Illegal item in expansion"); - print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string ); - myexit(); - } - $1; -} @expansion; - -foreach ( @expansion) { - my(%hash)=$cust_main_county->hash; - my($new)=new FS::cust_main_county \%hash; - $new->setfield('taxnum',''); - if ( $cgi->param('taxclass') ) { - $new->setfield('taxclass', $_); - } elsif ( ! $cust_main_county->state ) { - $new->setfield('state',$_); - } else { - $new->setfield('county',$_); - } - #if (datasrc =~ m/Pg/) - #{ - # $new->setfield('tax',0.0); - #} - my($error)=$new->insert; - die $error if $error; -} - -unless ( qsearch( 'cust_main', { - 'state' => $cust_main_county->state, - 'county' => $cust_main_county->county, - 'country' => $cust_main_county->country, - } ) - || ! @expansion -) { - my($error)=($cust_main_county->delete); - die $error if $error; -} - -print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); - -%> diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi index 9287ed150..2c3ebe866 100755 --- a/httemplate/edit/process/cust_main_county.cgi +++ b/httemplate/edit/process/cust_main_county.cgi @@ -1,30 +1,31 @@ -<% +% +% +%foreach ( grep { /^tax\d+$/ } $cgi->param ) { +% /^tax(\d+)$/ or die "Illegal form $_!"; +% my $taxnum = $1; +% my $old = qsearchs('cust_main_county', { 'taxnum' => $taxnum }) +% or die "Couldn't find taxnum $taxnum!"; +% next unless $old->tax != $cgi->param("tax$taxnum") +% || $old->exempt_amount != $cgi->param("exempt_amount$taxnum") +% || $old->taxname ne $cgi->param("taxname$taxnum") +% || $old->setuptax ne $cgi->param("setuptax$taxnum") +% || $old->recurtax ne $cgi->param("recurtax$taxnum"); +% my %hash = $old->hash; +% $hash{tax} = $cgi->param("tax$taxnum"); +% $hash{exempt_amount} = $cgi->param("exempt_amount$taxnum"); +% $hash{taxname} = $cgi->param("taxname$taxnum"); +% $hash{setuptax} = $cgi->param("setuptax$taxnum"); +% $hash{recurtax} = $cgi->param("recurtax$taxnum"); +% my $new = new FS::cust_main_county \%hash; +% my $error = $new->replace($old); +% if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_main_county.cgi?". $cgi->query_string ); +% myexit(); +% } +%} +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% -foreach ( grep { /^tax\d+$/ } $cgi->param ) { - /^tax(\d+)$/ or die "Illegal form $_!"; - my $taxnum = $1; - my $old = qsearchs('cust_main_county', { 'taxnum' => $taxnum }) - or die "Couldn't find taxnum $taxnum!"; - next unless $old->tax != $cgi->param("tax$taxnum") - || $old->exempt_amount != $cgi->param("exempt_amount$taxnum") - || $old->taxname ne $cgi->param("taxname$taxnum") - || $old->setuptax ne $cgi->param("setuptax$taxnum") - || $old->recurtax ne $cgi->param("recurtax$taxnum"); - my %hash = $old->hash; - $hash{tax} = $cgi->param("tax$taxnum"); - $hash{exempt_amount} = $cgi->param("exempt_amount$taxnum"); - $hash{taxname} = $cgi->param("taxname$taxnum"); - $hash{setuptax} = $cgi->param("setuptax$taxnum"); - $hash{recurtax} = $cgi->param("recurtax$taxnum"); - my $new = new FS::cust_main_county \%hash; - my $error = $new->replace($old); - if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_main_county.cgi?". $cgi->query_string ); - myexit(); - } -} - -print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); - -%> diff --git a/httemplate/edit/process/cust_main_note.cgi b/httemplate/edit/process/cust_main_note.cgi new file mode 100755 index 000000000..8b9105bd8 --- /dev/null +++ b/httemplate/edit/process/cust_main_note.cgi @@ -0,0 +1,52 @@ +% +% +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die "Illegal custnum: ". $cgi->param('custnum'); +%my $custnum = $1; +% +%$cgi->param('notenum') =~ /^(\d*)$/ +% or die "Illegal notenum: ". $cgi->param('notenum'); +%my $notenum = $1; +% +%my $otaker = $FS::CurrentUser::CurrentUser->name; +%$otaker = $FS::CurrentUser::CurrentUser->username +% if ($otaker eq "User, Legacy"); +% +%my $new = new FS::cust_main_note ( { +% notenum => $notenum, +% custnum => $custnum, +% _date => time, +% otaker => $otaker, +% comments => $cgi->param('comment'), +%} ); +% +%my $error; +%if ($notenum){ +% my $old = qsearchs('cust_main_note', { 'notenum' => $notenum }); +% $error = "No such note: $notenum" unless $old; +% unless($error){ +% map { $new->$_($old->$_) } ('_date', 'otaker'); +% $error = $new->replace($old); +% } +%}else{ +% $error = $new->insert; +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). 'cust_main_note.cgi?'. $cgi->query_string ); +%} +% +% +<% header('Note ' . ($notenum ? 'updated' : 'added') ) %> + <SCRIPT TYPE="text/javascript"> + parent.cust_main_notes.location.reload(); + try{parent.cust_main_notes.cClick()} + catch(err){} + try{parent.cClick()} + catch(err){} + </SCRIPT> + </BODY></HTML> +% +% + diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi index 87d6011e7..a34c88aba 100755 --- a/httemplate/edit/process/cust_pay.cgi +++ b/httemplate/edit/process/cust_pay.cgi @@ -1,42 +1,56 @@ -<% +% +% +%$cgi->param('linknum') =~ /^(\d+)$/ +% or die "Illegal linknum: ". $cgi->param('linknum'); +%my $linknum = $1; +% +%$cgi->param('link') =~ /^(custnum|invnum|popup)$/ +% or die "Illegal link: ". $cgi->param('link'); +%my $field = my $link = $1; +%$field = 'custnum' if $field eq 'popup'; +% +%my $_date = str2time($cgi->param('_date')); +% +%my $new = new FS::cust_pay ( { +% $field => $linknum, +% _date => $_date, +% map { +% $_, scalar($cgi->param($_)); +% } qw(paid payby payinfo paybatch) +% #} fields('cust_pay') +%} ); +% +%my $error = $new->insert( 'manual' => 1 ); +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ); +%} elsif ( $field eq 'invnum' ) { +% print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum"); +%} elsif ( $field eq 'custnum' ) { +% if ( $cgi->param('apply') eq 'yes' ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum }) +% or die "unknown custnum $linknum"; +% $cust_main->apply_payments; +% } +% if ( $link eq 'popup' ) { +% +% +<% header('Payment entered') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> -$cgi->param('linknum') =~ /^(\d+)$/ - or die "Illegal linknum: ". $cgi->param('linknum'); -my $linknum = $1; + </BODY></HTML> +% +% +% } elsif ( $link eq 'custnum' ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum"); +% } else { +% die "unknown link $link"; +% } +% +%} +% +% -$cgi->param('link') =~ /^(custnum|invnum)$/ - or die "Illegal link: ". $cgi->param('link'); -my $link = $1; - -my $_date = str2time($cgi->param('_date')); - -my $new = new FS::cust_pay ( { - $link => $linknum, - _date => $_date, - map { - $_, scalar($cgi->param($_)); - } qw(paid payby payinfo paybatch) - #} fields('cust_pay') -} ); - -my $error = $new->insert; - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ); -} elsif ( $link eq 'invnum' ) { - print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum"); -} elsif ( $link eq 'custnum' ) { - if ( $cgi->param('apply') eq 'yes' ) { - my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum }) - or die "unknown custnum $linknum"; - $cust_main->apply_payments; - } - if ( $cgi->param('quickpay') eq 'yes' ) { - print $cgi->redirect(popurl(3). "search/cust_main-quickpay.html"); - } else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum"); - } -} - -%> diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi index df8471c27..817c88087 100755 --- a/httemplate/edit/process/cust_pkg.cgi +++ b/httemplate/edit/process/cust_pkg.cgi @@ -1,43 +1,44 @@ -<% +% +% +%my $error = ''; +% +%#untaint custnum +%$cgi->param('custnum') =~ /^(\d+)$/; +%my $custnum = $1; +% +%my @remove_pkgnums = map { +% /^(\d+)$/ or die "Illegal remove_pkg value!"; +% $1; +%} $cgi->param('remove_pkg'); +% +%my $error_redirect; +%my @pkgparts; +%if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi +% $error_redirect = "misc/change_pkg.cgi"; +% @pkgparts = ($1); +%} else { #came from edit/cust_pkg.cgi +% $error_redirect = "edit/cust_pkg.cgi"; +% foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) { +% if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) { +% my $num_pkgs = $1; +% while ( $num_pkgs-- ) { +% push @pkgparts,$pkgpart; +% } +% } else { +% $error = "Illegal quantity"; +% last; +% } +% } +%} +% +%$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums); +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} +% +% -my $error = ''; - -#untaint custnum -$cgi->param('custnum') =~ /^(\d+)$/; -my $custnum = $1; - -my @remove_pkgnums = map { - /^(\d+)$/ or die "Illegal remove_pkg value!"; - $1; -} $cgi->param('remove_pkg'); - -my $error_redirect; -my @pkgparts; -if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi - $error_redirect = "misc/change_pkg.cgi"; - @pkgparts = ($1); -} else { #came from edit/cust_pkg.cgi - $error_redirect = "edit/cust_pkg.cgi"; - foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) { - if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) { - my $num_pkgs = $1; - while ( $num_pkgs-- ) { - push @pkgparts,$pkgpart; - } - } else { - $error = "Illegal quantity"; - last; - } - } -} - -$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums); - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} - -%> diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 7055d8ea6..a579a02d8 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -1,42 +1,34 @@ -<% - -$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; -my $custnum = $1; -my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or die "unknown custnum $custnum"; - -my $error = ''; -if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { - my %payby2bop = ( - 'CARD' => 'CC', - 'CHEK' => 'ECHECK', - ); - my $bop = $payby2bop{$1}; - $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/ - or die "illegal refund amount ". $cgi->param('refund'); - my $refund = "$1$2"; - $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; - my $paynum = $1; - my $reason = $cgi->param('reason'); - $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, - 'paynum' => $paynum, - 'reason' => $reason, ); -} else { - die 'unimplemented'; - #my $new = new FS::cust_refund ( { - # map { - # $_, scalar($cgi->param($_)); - # } ( fields('cust_refund'), 'paynum' ) - #} ); - #$error = $new->insert; -} - - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} - -%> +%$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; +%my $custnum = $1; +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) +% or die "unknown custnum $custnum"; +% +%my $error = ''; +%if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { +% my $bop = $FS::payby::payby2bop{$1}; +% $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/ +% or die "illegal refund amount ". $cgi->param('refund'); +% my $refund = "$1$2"; +% $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; +% my $paynum = $1; +% my $reason = $cgi->param('reason'); +% $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, +% 'paynum' => $paynum, +% 'reason' => $reason, ); +%} else { +% die 'unimplemented'; +% #my $new = new FS::cust_refund ( { +% # map { +% # $_, scalar($cgi->param($_)); +% # } ( fields('cust_refund'), 'paynum' ) +% #} ); +% #$error = $new->insert; +%} +% +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi index 187ede5e5..3a07d1e7a 100644 --- a/httemplate/edit/process/cust_svc.cgi +++ b/httemplate/edit/process/cust_svc.cgi @@ -1,30 +1,30 @@ -<% - -my $svcnum = $cgi->param('svcnum'); - -my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}) if $svcnum; - -my $new = new FS::cust_svc ( { - map { - $_, scalar($cgi->param($_)); - } fields('cust_svc') -} ); - -my $error; -if ( $svcnum ) { - $error=$new->replace($old); -} else { - $error=$new->insert; - $svcnum=$new->getfield('svcnum'); -} - -if ( $error ) { - #$cgi->param('error', $error); - #print $cgi->redirect(popurl(2). "cust_svc.cgi?". $cgi->query_string ); - eidiot($error); -} else { - my $svcdb = $new->part_svc->svcdb; - print $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum"); -} - - +% +% +%my $svcnum = $cgi->param('svcnum'); +% +%my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::cust_svc ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('cust_svc') +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error=$new->replace($old); +%} else { +% $error=$new->insert; +% $svcnum=$new->getfield('svcnum'); +%} +% +%if ( $error ) { +% #$cgi->param('error', $error); +% #print $cgi->redirect(popurl(2). "cust_svc.cgi?". $cgi->query_string ); +% eidiot($error); +%} else { +% my $svcdb = $new->part_svc->svcdb; +% print $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum"); +%} +% +% diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi index b8c3f62a1..87bdf6835 100755 --- a/httemplate/edit/process/domain_record.cgi +++ b/httemplate/edit/process/domain_record.cgi @@ -1,34 +1,36 @@ -<% +% +% +%my $recnum = $cgi->param('recnum'); +% +%my $old = qsearchs('agent',{'recnum'=>$recnum}) if $recnum; +% +%my $new = new FS::domain_record ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('domain_record') +%} ); +% +%my $error; +%if ( $recnum ) { +% $error=$new->replace($old); +%} else { +% $error=$new->insert; +% $recnum=$new->getfield('recnum'); +%} +% +%if ( $error ) { +%# $cgi->param('error', $error); +%# print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); +% #no edit screen to send them back to +% -my $recnum = $cgi->param('recnum'); - -my $old = qsearchs('agent',{'recnum'=>$recnum}) if $recnum; - -my $new = new FS::domain_record ( { - map { - $_, scalar($cgi->param($_)); - } fields('domain_record') -} ); - -my $error; -if ( $recnum ) { - $error=$new->replace($old); -} else { - $error=$new->insert; - $recnum=$new->getfield('recnum'); -} - -if ( $error ) { -# $cgi->param('error', $error); -# print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); - #no edit screen to send them back to -%> <!-- mason kludge --> -<% - eidiot($error); -} else { - my $svcnum = $new->svcnum; - print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); -} +% +% eidiot($error); +%} else { +% my $svcnum = $new->svcnum; +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% -%> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html new file mode 100644 index 000000000..e388c678b --- /dev/null +++ b/httemplate/edit/process/elements/process.html @@ -0,0 +1,111 @@ +% +% +% # options example... +% # +% ### +% ##req +% ## +% # +% # 'table' => +% # +% # #? 'primary_key' => #required when the dbdef doesn't know...??? +% # #? 'fields' => [] +% # +% ### +% ##opt +% ### +% # +% # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' +% # OR +% # 'redirect' => 'view/table.cgi?', # value of primary key is appended +% # +% # 'error_redirect' => popurl(2).'edit/table.cgi?', #query string appended +% # +% # 'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the +% # #naming is still inconsistent +% # +% # 'copy_on_empty' => [ 'old_field_name', 'another_old_field', ... ], +% # +% # 'clear_on_error' => [ 'form_field1', 'form_field2', ... ], +% # +% # 'process_m2m' => { 'link_table' => 'link_table_name', +% # 'target_table' => 'target_table_name', +% # }, +% # 'process_m2name' => { 'link_table' => 'link_table_name', +% # 'link_static' => { 'column' => 'value' }, +% # 'num_col' => 'column', #if column name is different in +% # #link_table than source_table +% # 'name_col' => 'name_column', +% # 'names_list' => [ 'list', 'names' ], +% # }, +% +% my(%opt) = @_; +% +% #false laziness w/edit.html +% my $table = $opt{'table'}; +% my $class = "FS::$table"; +% my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || +% my $fields = $opt{'fields'} +% #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; +% || [ fields($table) ]; +% +% my $pkeyvalue = $cgi->param($pkey); +% +% my $old = qsearchs( $table, { $pkey => $pkeyvalue } ) if $pkeyvalue; +% +% my $new = $class->new( { +% map { +% $_, scalar($cgi->param($_)); +% } @$fields +% } ); +% +% if ($old && exists($opt{'copy_on_empty'})) { +% foreach my $field (@{$opt{'copy_on_empty'}}) { +% $new->set($field, $old->get($field)) +% unless scalar($cgi->param($field)); +% } +% } +% +% my $error; +% if ( $pkeyvalue ) { +% $error = $new->replace($old); +% } else { +% $error = $new->insert; +% $pkeyvalue = $new->getfield($pkey); +% } +% +% if ( !$error && $opt{'process_m2m'} ) { +% $error = $new->process_m2m( %{ $opt{'process_m2m'} }, +% 'params' => scalar($cgi->Vars), +% ); +% } +% +% if ( !$error && $opt{'process_m2name'} ) { +% $error = $new->process_m2name( %{ $opt{'process_m2name'} }, +% 'params' => scalar($cgi->Vars), +% ); +% } +% +% # XXX print?!?! +% +% if ( $error ) { +% $cgi->param('error', $error); +% if (scalar(@{$opt{'clear_on_error'}})) { +% foreach my $field (@{$opt{'clear_on_error'}}) { +% $cgi->param($field, '') +% } +% } +% my $edit_ext = $opt{'edit_ext'} || 'html'; +% my $url = $opt{'error_redirect'} || popurl(2)."$table.$edit_ext?"; +% print $cgi->redirect($url. $cgi->query_string ); +% } elsif ( $opt{'redirect'} ) { +% print $cgi->redirect( $opt{'redirect'}. $pkeyvalue ); +% } else { +% print $cgi->redirect( popurl(3). +% ( $opt{'viewall_dir'} || 'search' ). +% "/$table.html" +% ); +% } +% +% + diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html new file mode 100644 index 000000000..8e8c99a42 --- /dev/null +++ b/httemplate/edit/process/elements/svc_Common.html @@ -0,0 +1,15 @@ +% +% +% my %opt = @_; +% my $table = $opt{'table'}; +% $opt{'fields'} ||= [ fields($table) ]; +% push @{ $opt{'fields'} }, qw( pkgnum svcpart ); +% +% +<% include( 'process.html', + 'edit_ext' => 'cgi', + 'redirect' => popurl(3)."view/$table.cgi?", + %opt, + ) +%> + diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi index 9c54feb1d..e3ac113ae 100644 --- a/httemplate/edit/process/generic.cgi +++ b/httemplate/edit/process/generic.cgi @@ -1,70 +1,73 @@ -<% +%# Welcome to generic.cgi. +%# +%# This script provides a generic edit/process/ backend for simple table +%# editing. All it knows how to do is take the values entered into +%# the script and insert them into the table specified by $cgi->param('table'). +%# If there's an existing record with the same primary key, it will be +%# replaced. (Deletion will be added in the future.) +%# +%# also see elements/process.html, newer and somewhat along the same lines, +%# though it still makes you setup a process file for the table. +%# perhaps safer, perhaps more of a pain in the ass. +%# +%# Special cgi params for this script: +%# table: the name of the table to be edited. The script will die horribly +%# if it can't find the table. +%# redirect_ok: URL to be displayed after a successful edit. The value of +%# the record's primary key will be passed as a keyword. +%# Defaults to (freeside root)/view/$table.cgi. +%# redirect_error: URL to be displayed if there's an error. The original +%# query string, plus the error message, will be passed. +%# Defaults to $cgi->referer() (i.e. go back where you +%# came from). +% +% +%use FS::Record qw(qsearchs dbdef); +%use DBIx::DBSchema; +%use DBIx::DBSchema::Table; +% +% +%my $error; +%my $p2 = popurl(2); +%my $p3 = popurl(3); +%my $table = $cgi->param('table'); +%my $dbdef = dbdef or die "Cannot fetch dbdef!"; +% +%my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; +% +%my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; +%my $pkey_val = $cgi->param($pkey); +% +% +%#warn "new FS::Record ( $table, (hashref) )"; +%my $new = FS::Record::new ( "FS::$table", { +% map { $_, scalar($cgi->param($_)) } fields($table) +%} ); +% +%#warn 'created $new of class '.ref($new); +% +%if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) { +% # edit +% $error = $new->replace($old); +%} else { +% #add +% $error = $new->insert; +% $pkey_val = $new->getfield($pkey); +% # New records usually don't have their primary keys set until after +% # they've been checked/inserted, so grab the new $pkey_val so we can +% # redirect to it. +%} +% +%my $redirect_ok = (($cgi->param('redirect_ok')) ? +% $cgi->param('redirect_ok') : $p3."browse/generic.cgi?$table"); +%my $redirect_error = (($cgi->param('redirect_error')) ? +% $cgi->param('redirect_error') : $cgi->referer()); +% +%if($error) { +% $cgi->param('error', $error); +% print $cgi->redirect($redirect_error . '?' . $cgi->query_string); +%} else { +% print $cgi->redirect($redirect_ok); +%} +% -# Welcome to generic.cgi. -# -# This script provides a generic edit/process/ backend for simple table -# editing. All it knows how to do is take the values entered into -# the script and insert them into the table specified by $cgi->param('table'). -# If there's an existing record with the same primary key, it will be -# replaced. (Deletion will be added in the future.) -# -# Special cgi params for this script: -# table: the name of the table to be edited. The script will die horribly -# if it can't find the table. -# redirect_ok: URL to be displayed after a successful edit. The value of -# the record's primary key will be passed as a keyword. -# Defaults to (freeside root)/view/$table.cgi. -# redirect_error: URL to be displayed if there's an error. The original -# query string, plus the error message, will be passed. -# Defaults to $cgi->referer() (i.e. go back where you -# came from). - - -use FS::Record qw(qsearchs dbdef); -use DBIx::DBSchema; -use DBIx::DBSchema::Table; - - -my $error; -my $p2 = popurl(2); -my $p3 = popurl(3); -my $table = $cgi->param('table'); -my $dbdef = dbdef or die "Cannot fetch dbdef!"; - -my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; - -my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; -my $pkey_val = $cgi->param($pkey); - - -#warn "new FS::Record ( $table, (hashref) )"; -my $new = FS::Record::new ( "FS::$table", { - map { $_, scalar($cgi->param($_)) } fields($table) -} ); - -#warn 'created $new of class '.ref($new); - -if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) { - # edit - $error = $new->replace($old); -} else { - #add - $error = $new->insert; - $pkey_val = $new->getfield($pkey); - # New records usually don't have their primary keys set until after - # they've been checked/inserted, so grab the new $pkey_val so we can - # redirect to it. -} - -my $redirect_ok = (($cgi->param('redirect_ok')) ? - $cgi->param('redirect_ok') : $p3."browse/generic.cgi?$table"); -my $redirect_error = (($cgi->param('redirect_error')) ? - $cgi->param('redirect_error') : $cgi->referer()); - -if($error) { - $cgi->param('error', $error); - print $cgi->redirect($redirect_error . '?' . $cgi->query_string); -} else { - print $cgi->redirect($redirect_ok); -} -%> diff --git a/httemplate/edit/process/inventory_class.html b/httemplate/edit/process/inventory_class.html new file mode 100644 index 000000000..c7be9e8dd --- /dev/null +++ b/httemplate/edit/process/inventory_class.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'inventory_class', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/msgcat.cgi b/httemplate/edit/process/msgcat.cgi index 1f94f6668..9711143d6 100644 --- a/httemplate/edit/process/msgcat.cgi +++ b/httemplate/edit/process/msgcat.cgi @@ -1,20 +1,21 @@ -<% +% +% +%my $error; +%foreach my $param ( grep { /^\d+$/ } $cgi->param ) { +% my $old = qsearchs('msgcat', { msgnum=>$param } ); +% next if $old->msg eq $cgi->param($param); #no need to update identical records +% my $new = new FS::msgcat { $old->hash }; +% $new->msg($cgi->param($param)); +% $error = $new->replace($old); +% last if $error; +%} +% +%if ( $error ) { +% $cgi->param('error',$error); +% print $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/msgcat.cgi"); +%} +% +% -my $error; -foreach my $param ( grep { /^\d+$/ } $cgi->param ) { - my $old = qsearchs('msgcat', { msgnum=>$param } ); - next if $old->msg eq $cgi->param($param); #no need to update identical records - my $new = new FS::msgcat { $old->hash }; - $new->msg($cgi->param($param)); - $error = $new->replace($old); - last if $error; -} - -if ( $error ) { - $cgi->param('error',$error); - print $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/msgcat.cgi"); -} - -%> diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi index 77dcd242a..af594f264 100755 --- a/httemplate/edit/process/part_bill_event.cgi +++ b/httemplate/edit/process/part_bill_event.cgi @@ -1,54 +1,89 @@ -<% - -my $eventpart = $cgi->param('eventpart'); - -my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart; - -#s/days/seconds/ -$cgi->param('seconds', int( $cgi->param('days') * 86400 ) ); - -my $error; -if ( ! $cgi->param('plan_weight_eventcode') ) { - $error = "Must select an action"; -} else { - - $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s - or die "illegal plan_weight_eventcode:". - $cgi->param('plan_weight_eventcode'); - $cgi->param('plan', $1); - $cgi->param('weight', $2); - my $eventcode = $3; - my $plandata = ''; - while ( $eventcode =~ /%%%(\w+)%%%/ ) { - my $field = $1; - my $value = join(', ', $cgi->param($field) ); - $cgi->param($field, $value); #in case it errors out - $eventcode =~ s/%%%$field%%%/$value/; - $plandata .= "$field $value\n"; - } - $cgi->param('eventcode', $eventcode); - $cgi->param('plandata', $plandata); - - my $new = new FS::part_bill_event ( { - map { - $_, scalar($cgi->param($_)); - } fields('part_bill_event'), - } ); - - if ( $eventpart ) { - $error = $new->replace($old); - } else { - $error = $new->insert; - $eventpart = $new->getfield('eventpart'); - } -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3)."browse/part_bill_event.cgi"); -} - -%> - +% +%my $eventpart = $cgi->param('eventpart'); +% +%my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart; +% +%#s/days/seconds/ +%$cgi->param('seconds', int( $cgi->param('days') * 86400 ) ); +% +%my $error; +%if ( ! $cgi->param('plan_weight_eventcode') ) { +% $error = "Must select an action"; +%} else { +% +% $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s +% or die "illegal plan_weight_eventcode:". +% $cgi->param('plan_weight_eventcode'); +% $cgi->param('plan', $1); +% $cgi->param('weight', $2); +% my $eventcode = $3; +% my $plandata = ''; +% +% my $rnum; +% my $rtype; +% my $reasonm; +% my $class = ''; +% $class='c' if ($eventcode =~ /cancel/); +% $class='s' if ($eventcode =~ /suspend/); +% if ($class) { +% $cgi->param("${class}reason") =~ /^(-?\d+)$/ +% or $error = "Invalid ${class}reason"; +% $rnum = $1; +% if ($rnum == -1) { +% $cgi->param("new${class}reasonT") =~ /^(\d+)$/ +% or $error = "Invalid new${class}reasonT"; +% $rtype = $1; +% $cgi->param("new${class}reason") =~ /^([\s\w]+)$/ +% or $error = "Invalid new${class}reason"; +% $reasonm = $1; +% } +% } +% +% if ($rnum == -1 && !$error) { +% my $reason = new FS::reason ({ 'reason' => $reasonm, +% 'reason_type' => $rtype, +% }); +% $error = $reason->insert; +% unless ($error) { +% $rnum = $reason->reasonnum; +% $cgi->param("${class}reason", $rnum); +% $cgi->param("new${class}reason", ''); +% $cgi->param("new${class}reasonT", ''); +% } +% } +% +% while ( $eventcode =~ /%%%(\w+)%%%/ ) { +% my $field = $1; +% my $value = join(', ', $cgi->param($field) ); +% $cgi->param($field, $value); #in case it errors out +% $eventcode =~ s/%%%$field%%%/$value/; +% $plandata .= "$field $value\n"; +% } +% $cgi->param('eventcode', $eventcode); +% $cgi->param('plandata', $plandata); +% +% unless($error){ +% my $new = new FS::part_bill_event ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('part_bill_event'), +% } ); +% $new->setfield('reason', $rnum); +% +% if ( $eventpart ) { +% $error = $new->replace($old); +% } else { +% $error = $new->insert; +% $eventpart = $new->getfield('eventpart'); +% } +% } +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3)."browse/part_bill_event.cgi"); +%} +% +% diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi index fa009edbb..0dd9eabae 100644 --- a/httemplate/edit/process/part_export.cgi +++ b/httemplate/edit/process/part_export.cgi @@ -1,39 +1,40 @@ -<% +% +% +%my $exportnum = $cgi->param('exportnum'); +% +%my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum; +% +%#fixup options +%#warn join('-', split(',',$cgi->param('options'))); +%my %options = map { +% my $value = $cgi->param($_); +% $value =~ s/\r\n/\n/g; #browsers? (textarea) +% $_ => $value; +%} split(',', $cgi->param('options')); +% +%my $new = new FS::part_export ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('part_export') +%} ); +% +%my $error; +%if ( $exportnum ) { +% #warn $old; +% #warn $exportnum; +% #warn $new->machine; +% $error = $new->replace($old,\%options); +%} else { +% $error = $new->insert(\%options); +%# $exportnum = $new->exportnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error ); +% print $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/part_export.cgi"); +%} +% +% -my $exportnum = $cgi->param('exportnum'); - -my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum; - -#fixup options -#warn join('-', split(',',$cgi->param('options'))); -my %options = map { - my $value = $cgi->param($_); - $value =~ s/\r\n/\n/g; #browsers? (textarea) - $_ => $value; -} split(',', $cgi->param('options')); - -my $new = new FS::part_export ( { - map { - $_, scalar($cgi->param($_)); - } fields('part_export') -} ); - -my $error; -if ( $exportnum ) { - #warn $old; - #warn $exportnum; - #warn $new->machine; - $error = $new->replace($old,\%options); -} else { - $error = $new->insert(\%options); -# $exportnum = $new->exportnum; -} - -if ( $error ) { - $cgi->param('error', $error ); - print $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/part_export.cgi"); -} - -%> diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 0d0a13491..55e7e05ae 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -1,61 +1,75 @@ -<% +% +% +%my $dbh = dbh; +% +%my $pkgpart = $cgi->param('pkgpart'); +% +%my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart; +% +%#fixup plandata +%my $plandata = $cgi->param('plandata'); +%my @plandata = split(',', $plandata); +%$cgi->param('plandata', +% join('', map { "$_=". join(', ', $cgi->param($_)). "\n" } @plandata ) +%); +% +%foreach (qw( setuptax recurtax disabled )) { +% $cgi->param($_, '') unless defined $cgi->param($_); +%} +% +%my @agents; +%foreach ($cgi->param('agent_type')) { +% /^(\d+)$/; +% push @agents, $1 if $1; +%} +% +%my $new = new FS::part_pkg ( { +% map { +% $_ => scalar($cgi->param($_)); +% } fields('part_pkg') +%} ); +% +%my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) } +% map { $_->svcpart } +% qsearch('part_svc', {} ); +% +%my $error; +%my $custnum = ''; +%if ( $cgi->param('taxclass') eq '(select)' ) { +% +% $error = 'Must select a tax class'; +% +%} elsif ( $pkgpart ) { +% +% $error = $new->replace( $old, +% pkg_svc => \%pkg_svc, +% primary_svc => scalar($cgi->param('pkg_svc_primary')), +% ); +%} else { +% +% $error = $new->insert( pkg_svc => \%pkg_svc, +% primary_svc => scalar($cgi->param('pkg_svc_primary')), +% cust_pkg => $cgi->param('pkgnum'), +% custnum_ref => \$custnum, +% ); +% $pkgpart = $new->pkgpart; +%} +% +%unless (1 || $error) { # after 1.7.2 +% my $error = $new->process_m2m( +% 'link_table' => 'type_pkgs', +% 'target_table' => 'agent_type', +% 'params' => \@agents, +% ); +%} +%if ( $error ) { +% $cgi->param('error', $error ); +% print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ); +%} elsif ( $custnum ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} else { +% print $cgi->redirect(popurl(3). "browse/part_pkg.cgi"); +%} +% +% -my $dbh = dbh; - -my $pkgpart = $cgi->param('pkgpart'); - -my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart; - -#fixup plandata -my $plandata = $cgi->param('plandata'); -my @plandata = split(',', $plandata); -$cgi->param('plandata', - join('', map { "$_=". join(', ', $cgi->param($_)). "\n" } @plandata ) -); - -foreach (qw( setuptax recurtax disabled )) { - $cgi->param($_, '') unless defined $cgi->param($_); -} - -my $new = new FS::part_pkg ( { - map { - $_ => scalar($cgi->param($_)); - } fields('part_pkg') -} ); - -my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) } - map { $_->svcpart } - qsearch('part_svc', {} ); - -my $error; -my $custnum = ''; -if ( $cgi->param('taxclass') eq '(select)' ) { - - $error = 'Must select a tax class'; - -} elsif ( $pkgpart ) { - - $error = $new->replace( $old, - pkg_svc => \%pkg_svc, - primary_svc => scalar($cgi->param('pkg_svc_primary')), - ); -} else { - - $error = $new->insert( pkg_svc => \%pkg_svc, - primary_svc => scalar($cgi->param('pkg_svc_primary')), - cust_pkg => $cgi->param('pkgnum'), - custnum_ref => \$custnum, - ); - $pkgpart = $new->pkgpart; -} - -if ( $error ) { - $cgi->param('error', $error ); - print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ); -} elsif ( $custnum ) { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} else { - print $cgi->redirect(popurl(3). "browse/part_pkg.cgi"); -} - -%> diff --git a/httemplate/edit/process/part_referral.cgi b/httemplate/edit/process/part_referral.cgi deleted file mode 100755 index fd2c01506..000000000 --- a/httemplate/edit/process/part_referral.cgi +++ /dev/null @@ -1,28 +0,0 @@ -<% - -my $refnum = $cgi->param('refnum'); - -my $new = new FS::part_referral ( { - map { - $_, scalar($cgi->param($_)); - } fields('part_referral') -} ); - -my $error; -if ( $refnum ) { - my $old = qsearchs( 'part_referral', { 'refnum' =>$ refnum } ); - die "(Old) Record not found!" unless $old; - $error = $new->replace($old); -} else { - $error = $new->insert; -} -$refnum=$new->refnum; - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "part_referral.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/part_referral.cgi"); -} - -%> diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html new file mode 100755 index 000000000..14c1b7001 --- /dev/null +++ b/httemplate/edit/process/part_referral.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'part_referral', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi index b92b62739..97abc5baf 100755 --- a/httemplate/edit/process/part_svc.cgi +++ b/httemplate/edit/process/part_svc.cgi @@ -1,3 +1,4 @@ -<% - my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process', $cgi; -%><%= $server->process %> +% +% my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html index b9e4d47da..0b7e31395 100644 --- a/httemplate/edit/process/payment_gateway.html +++ b/httemplate/edit/process/payment_gateway.html @@ -1,33 +1,34 @@ -<% +% +% +%my $gatewaynum = $cgi->param('gatewaynum'); +% +%my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum; +% +%my $new = new FS::payment_gateway ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('payment_gateway') +%} ); +% +%my @options = split(/\r?\n/, $cgi->param('gateway_options') ); +%pop @options +% if scalar(@options) % 2 && $options[-1] =~ /^\s*$/; +%my %options = @options; +% +%my $error; +%if ( $gatewaynum ) { +% $error=$new->replace($old, \%options); +%} else { +% $error=$new->insert(\%options); +% $gatewaynum=$new->getfield('gatewaynum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/payment_gateway.html"); +%} +% +% -my $gatewaynum = $cgi->param('gatewaynum'); - -my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum; - -my $new = new FS::payment_gateway ( { - map { - $_, scalar($cgi->param($_)); - } fields('payment_gateway') -} ); - -my @options = split(/\r?\n/, $cgi->param('gateway_options') ); -pop @options - if scalar(@options) % 2 && $options[-1] =~ /^\s*$/; -my %options = @options; - -my $error; -if ( $gatewaynum ) { - $error=$new->replace($old); -} else { - $error=$new->insert(\%options); - $gatewaynum=$new->getfield('gatewaynum'); -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/payment_gateway.html"); -} - -%> diff --git a/httemplate/edit/process/pkg_class.html b/httemplate/edit/process/pkg_class.html new file mode 100644 index 000000000..183da805c --- /dev/null +++ b/httemplate/edit/process/pkg_class.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'pkg_class', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/prepay_credit.cgi b/httemplate/edit/process/prepay_credit.cgi index 25ecbe079..6bf46bf7c 100644 --- a/httemplate/edit/process/prepay_credit.cgi +++ b/httemplate/edit/process/prepay_credit.cgi @@ -1,51 +1,63 @@ +% +%my $hashref = {}; +% +%my $agent = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agent = qsearchs('agent', { 'agentnum' => $hashref->{agentnum}=$1 } ); +%} +% +%my $error = ''; +% +%my $num = 0; +%if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { +% $num = $1; +%} else { +% $error = 'Illegal number of prepaid cards: '. $cgi->param('num'); +%} +% +%$hashref->{amount} = $cgi->param('amount'); +%$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier'); +%$hashref->{upbytes} = $cgi->param('upbytes') * $cgi->param('upmultiplier'); +%$hashref->{downbytes} = $cgi->param('downbytes') * $cgi->param('downmultiplier'); +%$hashref->{totalbytes} = $cgi->param('totalbytes') * $cgi->param('totalmultiplier'); +% +%$error ||= FS::prepay_credit::generate( $num, +% scalar($cgi->param('type')), +% $hashref +% ); +% +%unless ( ref($error) ) { +% $cgi->param('error', $error ); +% <% -my $hashref = {}; - -my $agent = ''; -if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - $agent = qsearchs('agent', { 'agentnum' => $hashref->{agentnum}=$1 } ); -} - -my $error = ''; - -my $num = 0; -if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { - $num = $1; -} else { - $error = 'Illegal number of prepaid cards: '. $cgi->param('num'); -} - -$hashref->{amount} = $cgi->param('amount'); -$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier'); - -$error ||= FS::prepay_credit::generate( $num, - scalar($cgi->param('type')), - $hashref - ); - -unless ( ref($error) ) { - $cgi->param('error', $error ); -%><%= $cgi->redirect(popurl(3). "edit/prepay_credit.cgi?". $cgi->query_string ) -%><% } else { %> +%> +% } else { + -<%= header( "$num prepaid cards generated". +<% include("/elements/header.html", "$num prepaid cards generated". ( $agent ? ' for '.$agent->agent : '' ), menubar( 'Main menu' => popurl(3) ) ) %> <FONT SIZE="+1"> -<% foreach my $card ( @$error ) { %> - <code><%= $card %></code> +% foreach my $card ( @$error ) { + + <code><% $card %></code> - - <%= $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %> - <%= $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %> - <%= $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %> + <% $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %> + <% $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %> + <% $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %> + <% $hashref->{upbytes} ? FS::UI::Web::bytecount_unexact($hashref->{upbytes}) : '' %> + <% $hashref->{downbytes} ? FS::UI::Web::bytecount_unexact($hashref->{downbytes}) : '' %> + <% $hashref->{totalbytes} ? FS::UI::Web::bytecount_unexact($hashref->{totalbytes}) : '' %> <br> -<% } %> +% } + </FONT> </BODY></HTML> -<% } %> +% } + diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi index 928e3daad..024a281e0 100644 --- a/httemplate/edit/process/quick-charge.cgi +++ b/httemplate/edit/process/quick-charge.cgi @@ -1,41 +1,47 @@ -<% - -#untaint custnum -$cgi->param('custnum') =~ /^(\d+)$/ - or die 'illegal custnum '. $cgi->param('custnum'); -my $custnum = $1; - -$cgi->param('amount') =~ /^\s*(\d+(\.\d{1,2})?)\s*$/ - or die 'illegal amount '. $cgi->param('amount'); -my $amount = $1; - -my( $error, $cust_main); -if ( $cgi->param('taxclass') eq '(select)' ) { - - - $error = 'Must select a tax class'; -} else { - - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or die "unknown custnum $custnum"; - - $error = $cust_main->charge( - $amount, - $cgi->param('pkg'), - '$'. sprintf("%.2f",$amount), - $cgi->param('taxclass') - ); - -} - -if ($error) { -%> -<!-- mason kludge --> -<% - eidiot($error); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum" ); -} - -%> +% +% my $error = ''; +% my $param = $cgi->Vars; +% +% my @description = (); +% for ( my $row = 0; exists($param->{"description$row"}); $row++ ) { +% push @description, $param->{"description$row"} +% if ($param->{"description$row"} =~ /\S/); +% } +% +% $param->{"custnum"} =~ /^(\d+)$/ +% or $error .= "Illegal customer number " . $param->{"custnum"} . " "; +% my $custnum = $1; +% +% $param->{"amount"} =~ /^\s*(\d+(\.\d{1,2})?)\s*$/ +% or $error .= "Illegal amount " . $param->{"amount"} . " "; +% my $amount = $1; +% +% if ( $param->{'taxclass'} eq '(select)' ) { +% $error .= "Must select a tax class. "; +% } +% +% unless ( $error ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) +% or $error .= "Unknown customer number $custnum. "; +% +% $error ||= $cust_main->charge({ 'amount' => $amount, +% 'pkg' => $cgi->param('pkg'), +% 'taxclass' => $cgi->param('taxclass'), +% 'additional' => \@description, +% } +% ); +% } +% +% if ( $error ) { +% +% $cgi->param('error', "$error" ); +% +<% $cgi->redirect($p.'quick-charge.html?'. $cgi->query_string) %> +% +% } +<% header("One-time charge added") %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY></HTML> diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index fd9e59472..7afc9f2bb 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -1,25 +1,27 @@ -<% +% +% +%#untaint custnum +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die 'illegal custnum '. $cgi->param('custnum'); +%my $custnum = $1; +%$cgi->param('pkgpart') =~ /^(\d+)$/ +% or die 'illegal pkgpart '. $cgi->param('pkgpart'); +%my $pkgpart = $1; +% +%my @cust_pkg = (); +%my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, ); +% +%if ($error) { +% -#untaint custnum -$cgi->param('custnum') =~ /^(\d+)$/ - or die 'illegal custnum '. $cgi->param('custnum'); -my $custnum = $1; -$cgi->param('pkgpart') =~ /^(\d+)$/ - or die 'illegal pkgpart '. $cgi->param('pkgpart'); -my $pkgpart = $1; - -my @cust_pkg = (); -my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, ); - -if ($error) { -%> <!-- mason kludge --> -<% - eidiot($error); -} else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". - "#cust_pkg". $cust_pkg[0]->pkgnum ); -} +% +% eidiot($error); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg". $cust_pkg[0]->pkgnum ); +%} +% +% -%> diff --git a/httemplate/edit/process/rate.cgi b/httemplate/edit/process/rate.cgi index 87c082d64..c81f883b7 100755 --- a/httemplate/edit/process/rate.cgi +++ b/httemplate/edit/process/rate.cgi @@ -1,3 +1,4 @@ -<% - my $server = new FS::UI::Web::JSRPC 'FS::rate::process', $cgi; -%><%= $server->process %> +% +% my $server = new FS::UI::Web::JSRPC 'FS::rate::process', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/rate_region.cgi b/httemplate/edit/process/rate_region.cgi index 09d3d2c42..753224565 100755 --- a/httemplate/edit/process/rate_region.cgi +++ b/httemplate/edit/process/rate_region.cgi @@ -1,51 +1,52 @@ -<% +% +% +%my $regionnum = $cgi->param('regionnum'); +% +%my $old = qsearchs('rate_region', { 'regionnum' => $regionnum } ) if $regionnum; +% +%my $new = new FS::rate_region ( { +% map { +% $_, scalar($cgi->param($_)); +% } ( fields('rate_region') ) +%} ); +% +%my $countrycode = $cgi->param('countrycode'); +%my @npa = split(/\s*,\s*/, $cgi->param('npa')); +%$npa[0] = '' unless @npa; +%my @rate_prefix = map { +% new FS::rate_prefix { +% 'countrycode' => $countrycode, +% 'npa' => $_, +% } +% } @npa; +% +%my @dest_detail = map { +% my $ratenum = $_->ratenum; +% new FS::rate_detail { +% 'ratenum' => $ratenum, +% map { $_ => $cgi->param("$_$ratenum") } +% qw( min_included min_charge sec_granularity ) +% }; +%} qsearch('rate', {} ); +% +% +%my $error; +%if ( $regionnum ) { +% $error = $new->replace($old, 'rate_prefix' => \@rate_prefix, +% 'dest_detail' => \@dest_detail, ); +%} else { +% $error = $new->insert( 'rate_prefix' => \@rate_prefix, +% 'dest_detail' => \@dest_detail, ); +% $regionnum = $new->getfield('regionnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "rate_region.cgi?". $cgi->query_string ); +%} else { +% #print $cgi->redirect(popurl(3). "browse/rate_region.cgi"); +% print $cgi->redirect(popurl(3). "browse/rate.cgi"); +%} +% +% -my $regionnum = $cgi->param('regionnum'); - -my $old = qsearchs('rate_region', { 'regionnum' => $regionnum } ) if $regionnum; - -my $new = new FS::rate_region ( { - map { - $_, scalar($cgi->param($_)); - } ( fields('rate_region') ) -} ); - -my $countrycode = $cgi->param('countrycode'); -my @npa = split(/\s*,\s*/, $cgi->param('npa')); -$npa[0] = '' unless @npa; -my @rate_prefix = map { - new FS::rate_prefix { - 'countrycode' => $countrycode, - 'npa' => $_, - } - } @npa; - -my @dest_detail = map { - my $ratenum = $_->ratenum; - new FS::rate_detail { - 'ratenum' => $ratenum, - map { $_ => $cgi->param("$_$ratenum") } - qw( min_included min_charge sec_granularity ) - }; -} qsearch('rate', {} ); - - -my $error; -if ( $regionnum ) { - $error = $new->replace($old, 'rate_prefix' => \@rate_prefix, - 'dest_detail' => \@dest_detail, ); -} else { - $error = $new->insert( 'rate_prefix' => \@rate_prefix, - 'dest_detail' => \@dest_detail, ); - $regionnum = $new->getfield('regionnum'); -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "rate_region.cgi?". $cgi->query_string ); -} else { - #print $cgi->redirect(popurl(3). "browse/rate_region.cgi"); - print $cgi->redirect(popurl(3). "browse/rate.cgi"); -} - -%> diff --git a/httemplate/edit/process/reason.html b/httemplate/edit/process/reason.html new file mode 100644 index 000000000..55c1ea958 --- /dev/null +++ b/httemplate/edit/process/reason.html @@ -0,0 +1,6 @@ +<% include( 'elements/process.html', + 'table' => 'reason', + 'redirect' => popurl(3) . 'browse/reason.html?class=' . + $cgi->param('class') . '&', + ) +%> diff --git a/httemplate/edit/process/reason_type.html b/httemplate/edit/process/reason_type.html new file mode 100644 index 000000000..4ccccaddd --- /dev/null +++ b/httemplate/edit/process/reason_type.html @@ -0,0 +1,6 @@ +<% include( 'elements/process.html', + 'table' => 'reason_type', + 'redirect' => popurl(3) . 'browse/reason_type.html?class=' . + $cgi->param('class') . '&', + ) +%> diff --git a/httemplate/edit/process/reg_code.cgi b/httemplate/edit/process/reg_code.cgi index 4658257f3..4fdea60fc 100644 --- a/httemplate/edit/process/reg_code.cgi +++ b/httemplate/edit/process/reg_code.cgi @@ -1,44 +1,50 @@ +% +% +%$cgi->param('agentnum') =~ /^(\d+)$/ +% or eidiot 'illegal agentnum '. $cgi->param('agentnum'); +%my $agentnum = $1; +%my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +%my $error = ''; +% +%my $num = 0; +%if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { +% $num = $1; +%} else { +% $error = 'Illegal number of codes: '. $cgi->param('num'); +%} +% +%my @pkgparts = +% map { /^pkgpart(.*)$/; $1 } +% grep { $cgi->param($_) } +% grep { /^pkgpart/ } +% $cgi->param; +% +%$error ||= $agent->generate_reg_codes($num, \@pkgparts); +% +%unless ( ref($error) ) { +% $cgi->param('error'. $error ); +% <% - -$cgi->param('agentnum') =~ /^(\d+)$/ - or eidiot 'illegal agentnum '. $cgi->param('agentnum'); -my $agentnum = $1; -my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); - -my $error = ''; - -my $num = 0; -if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { - $num = $1; -} else { - $error = 'Illegal number of codes: '. $cgi->param('num'); -} - -my @pkgparts = - map { /^pkgpart(.*)$/; $1 } - grep { $cgi->param($_) } - grep { /^pkgpart/ } - $cgi->param; - -$error ||= $agent->generate_reg_codes($num, \@pkgparts); - -unless ( ref($error) ) { - $cgi->param('error'. $error ); -%><%= $cgi->redirect(popurl(3). "edit/reg_code.cgi?". $cgi->query_string ) -%><% } else { %> +%> +% } else { + -<%= header("$num registration codes generated for ". $agent->agent, menubar( +<% include("/elements/header.html","$num registration codes generated for ". $agent->agent, menubar( 'Main menu' => popurl(3), 'View all agents' => popurl(3). 'browse/agent.cgi', ) ) %> <PRE><FONT SIZE="+1"> -<% foreach my $code ( @$error ) { %> - <%= $code %> -<% } %> +% foreach my $code ( @$error ) { + + <% $code %> +% } + </FONT></PRE> </BODY></HTML> -<% } %> +% } + diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi index a2fa46dd9..c69114ea4 100644 --- a/httemplate/edit/process/router.cgi +++ b/httemplate/edit/process/router.cgi @@ -1,67 +1,68 @@ -<% +% +% +%local $FS::UID::AutoCommit=0; +% +%sub check { +% my $error = shift; +% if($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string); +% dbh->rollback; +% exit; +% } +%} +% +%my $error = ''; +%my $routernum = $cgi->param('routernum'); +%my $routername = $cgi->param('routername'); +%my $old = qsearchs('router', { routernum => $routernum }); +%my @old_psr; +% +%my $new = new FS::router { +% map { +% ($_, scalar($cgi->param($_))); +% } fields('router') +%}; +% +%if($old) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $routernum = $new->routernum; +%} +% +%check($error); +% +%if ($old) { +% @old_psr = $old->part_svc_router; +% foreach my $psr (@old_psr) { +% if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') { +% # do nothing +% } else { +% $error = $psr->delete; +% } +% } +% check($error); +%} +% +%foreach($cgi->param) { +% if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) { +% my $svcpart = $1; +% if(grep {$_->svcpart == $svcpart} @old_psr) { +% # do nothing +% } else { +% my $new_psr = new FS::part_svc_router { svcpart => $svcpart, +% routernum => $routernum }; +% $error = $new_psr->insert; +% } +% check($error); +% } +%} +% +% +%# Yay, everything worked! +%dbh->commit or die dbh->errstr; +%print $cgi->redirect(popurl(3). "browse/router.cgi"); +% +% -local $FS::UID::AutoCommit=0; - -sub check { - my $error = shift; - if($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string); - dbh->rollback; - exit; - } -} - -my $error = ''; -my $routernum = $cgi->param('routernum'); -my $routername = $cgi->param('routername'); -my $old = qsearchs('router', { routernum => $routernum }); -my @old_psr; - -my $new = new FS::router { - map { - ($_, scalar($cgi->param($_))); - } fields('router') -}; - -if($old) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $routernum = $new->routernum; -} - -check($error); - -if ($old) { - @old_psr = $old->part_svc_router; - foreach my $psr (@old_psr) { - if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') { - # do nothing - } else { - $error = $psr->delete; - } - } - check($error); -} - -foreach($cgi->param) { - if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) { - my $svcpart = $1; - if(grep {$_->svcpart == $svcpart} @old_psr) { - # do nothing - } else { - my $new_psr = new FS::part_svc_router { svcpart => $svcpart, - routernum => $routernum }; - $error = $new_psr->insert; - } - check($error); - } -} - - -# Yay, everything worked! -dbh->commit or die dbh->errstr; -print $cgi->redirect(popurl(3). "browse/router.cgi"); - -%> diff --git a/httemplate/edit/process/svc_Common.html b/httemplate/edit/process/svc_Common.html new file mode 100644 index 000000000..f5c869a12 --- /dev/null +++ b/httemplate/edit/process/svc_Common.html @@ -0,0 +1,13 @@ +<%init> + +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +my $table = $1; +require "FS/$table.pm"; + +</%init> +<% include( 'elements/svc_Common.html', + 'table' => $table, + 'redirect' => popurl(3)."view/svc_Common.html?svcdb=$table;svcnum=", + 'error_redirect' => popurl(3)."edit/svc_Common.html?svcdb=$table;", + ) +%> diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi index 950a8602f..30552c846 100755 --- a/httemplate/edit/process/svc_acct.cgi +++ b/httemplate/edit/process/svc_acct.cgi @@ -1,49 +1,50 @@ -<% +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find account (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%#unmunge popnum +%$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] ); +% +%#unmunge passwd +%if ( $cgi->param('_password') eq '*HIDDEN*' ) { +% die "fatal: no previous account to recall hidden password from!" unless $old; +% $cgi->param('_password',$old->getfield('_password')); +%} +% +%#unmunge usergroup +%$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] ); +% +%my %hash = $svcnum ? $old->hash : (); +%map { +% $hash{$_} = scalar($cgi->param($_)); +% #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir +% # shell quota slipip) +% } (fields('svc_acct'), qw ( pkgnum svcpart usergroup )); +%my $new = new FS::svc_acct ( \%hash ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ); +%} +% +% -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum = $1; - -my $old; -if ( $svcnum ) { - $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) - or die "fatal: can't find account (svcnum $svcnum)!"; -} else { - $old = ''; -} - -#unmunge popnum -$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] ); - -#unmunge passwd -if ( $cgi->param('_password') eq '*HIDDEN*' ) { - die "fatal: no previous account to recall hidden password from!" unless $old; - $cgi->param('_password',$old->getfield('_password')); -} - -#unmunge usergroup -$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] ); - -my $new = new FS::svc_acct ( { - map { - $_, scalar($cgi->param($_)); - #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir - # shell quota slipip) - } ( fields('svc_acct'), qw( pkgnum svcpart usergroup ) ) -} ); - -my $error; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->svcnum; -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ); -} - -%> diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi index 46ad74d62..9e9df7bf0 100755 --- a/httemplate/edit/process/svc_acct_pop.cgi +++ b/httemplate/edit/process/svc_acct_pop.cgi @@ -1,28 +1,29 @@ -<% +% +% +%my $popnum = $cgi->param('popnum'); +% +%my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum; +% +%my $new = new FS::svc_acct_pop ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('svc_acct_pop') +%} ); +% +%my $error = ''; +%if ( $popnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $popnum=$new->getfield('popnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi"); +%} +% +% -my $popnum = $cgi->param('popnum'); - -my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum; - -my $new = new FS::svc_acct_pop ( { - map { - $_, scalar($cgi->param($_)); - } fields('svc_acct_pop') -} ); - -my $error = ''; -if ( $popnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $popnum=$new->getfield('popnum'); -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi"); -} - -%> diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi index a009ba218..cf4604639 100644 --- a/httemplate/edit/process/svc_broadband.cgi +++ b/httemplate/edit/process/svc_broadband.cgi @@ -1,36 +1,37 @@ -<% +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find broadband service (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%my $new = new FS::svc_broadband ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_broadband'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% $cgi->param('ip_addr', $new->ip_addr); +% print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum ); +%} +% +% -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum = $1; - -my $old; -if ( $svcnum ) { - $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } ) - or die "fatal: can't find broadband service (svcnum $svcnum)!"; -} else { - $old = ''; -} - -my $new = new FS::svc_broadband ( { - map { - ($_, scalar($cgi->param($_))); - } ( fields('svc_broadband'), qw( pkgnum svcpart ) ) -} ); - -my $error; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->svcnum; -} - -if ( $error ) { - $cgi->param('error', $error); - $cgi->param('ip_addr', $new->ip_addr); - print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum ); -} - -%> diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi index 19f8eb4f8..773143fe3 100755 --- a/httemplate/edit/process/svc_domain.cgi +++ b/httemplate/edit/process/svc_domain.cgi @@ -1,31 +1,32 @@ -<% +% +% +%#remove this to actually test the domains! +%$FS::svc_domain::whois_hack = 1; +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $new = new FS::svc_domain ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(svcnum pkgnum svcpart domain action purpose) +% } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) ) +%} ); +% +%my $error = ''; +%if ($cgi->param('svcnum')) { +% $error="Can't modify a domain!"; +%} else { +% $error=$new->insert; +% $svcnum=$new->svcnum; +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% -#remove this to actually test the domains! -$FS::svc_domain::whois_hack = 1; - -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum = $1; - -my $new = new FS::svc_domain ( { - map { - $_, scalar($cgi->param($_)); - #} qw(svcnum pkgnum svcpart domain action purpose) - } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) ) -} ); - -my $error = ''; -if ($cgi->param('svcnum')) { - $error="Can't modify a domain!"; -} else { - $error=$new->insert; - $svcnum=$new->svcnum; -} - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); -} - -%> diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi index 728cd2189..97da6ba87 100755 --- a/httemplate/edit/process/svc_external.cgi +++ b/httemplate/edit/process/svc_external.cgi @@ -1,29 +1,30 @@ -<% +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum =$1; +% +%my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::svc_external ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_external'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error = ''; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->getfield('svcnum'); +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum"); +%} +% +% -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum =$1; - -my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum; - -my $new = new FS::svc_external ( { - map { - ($_, scalar($cgi->param($_))); - } ( fields('svc_external'), qw( pkgnum svcpart ) ) -} ); - -my $error = ''; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->getfield('svcnum'); -} - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum"); -} - -%> diff --git a/httemplate/edit/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi index bb066d8a6..3205312f1 100755 --- a/httemplate/edit/process/svc_forward.cgi +++ b/httemplate/edit/process/svc_forward.cgi @@ -1,29 +1,30 @@ -<% +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum =$1; +% +%my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::svc_forward ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_forward'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error = ''; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->getfield('svcnum'); +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum"); +%} +% +% -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum =$1; - -my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum; - -my $new = new FS::svc_forward ( { - map { - ($_, scalar($cgi->param($_))); - } ( fields('svc_forward'), qw( pkgnum svcpart ) ) -} ); - -my $error = ''; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->getfield('svcnum'); -} - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum"); -} - -%> diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html new file mode 100644 index 000000000..44235de63 --- /dev/null +++ b/httemplate/edit/process/svc_phone.html @@ -0,0 +1,4 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_phone', + ) +%> diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi index 40913145a..e9a52aff2 100644 --- a/httemplate/edit/process/svc_www.cgi +++ b/httemplate/edit/process/svc_www.cgi @@ -1,36 +1,37 @@ -<% +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_www', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find website (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%my $new = new FS::svc_www ( { +% map { +% ($_, scalar($cgi->param($_))); +% #} qw(svcnum pkgnum svcpart recnum usersvc) +% } ( fields('svc_www'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ); +%} +% +% -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum = $1; - -my $old; -if ( $svcnum ) { - $old = qsearchs('svc_www', { 'svcnum' => $svcnum } ) - or die "fatal: can't find website (svcnum $svcnum)!"; -} else { - $old = ''; -} - -my $new = new FS::svc_www ( { - map { - ($_, scalar($cgi->param($_))); - #} qw(svcnum pkgnum svcpart recnum usersvc) - } ( fields('svc_www'), qw( pkgnum svcpart ) ) -} ); - -my $error; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->svcnum; -} - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ); -} - -%> diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html new file mode 100644 index 000000000..94682d0a6 --- /dev/null +++ b/httemplate/edit/quick-charge.html @@ -0,0 +1,166 @@ +<% include("/elements/header-popup.html", 'One-time charge entry', '', + ( $cgi->param('error') ? '' : 'onload="addRow()"' ), + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000"><% $cgi->param('error') %></FONT><BR><BR> +% } + +<SCRIPT TYPE="text/javascript"> + +function enable_quick_charge () { + if ( document.QuickChargeForm.amount.value + && document.QuickChargeForm.pkg.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} + +function enable_quick_charge_desc () { + if ( document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} + +function enable_quick_charge_amount () { + if ( document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} + +function validate_quick_charge () { + var pkg = document.QuickChargeForm.pkg.value; + var pkg_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ; + var amount = document.QuickChargeForm.amount.value; + var amount_regex = /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ; + var rval = true; + + if ( ! amount_regex.test(amount) ) { + alert('Illegal amount - enter an amount to charge, for example, "5" or "43" or "21.46".'); + return false; + } + if ( String(pkg).length < 1 ) { + rval = false; + } + if ( ! pkg_regex.test(pkg) ) { + rval = false; + } + var i=0; + for (i=0; i < rownum; i++) { + if (! eval('pkg_regex.test(document.QuickChargeForm.description' + i + '.value)')){ + rval = false; + break; + } + } + if (rval == true) { + return true; + } + + if ( ! pkg ) { + alert('Enter a description for the one-time charge'); + return false; + } + + alert('Illegal description - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' ); + return false; +} + +</SCRIPT> + + + +<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();"> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cgi->param('custnum') %>"> +<TABLE ID="QuickChargeTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc"> + +<TR> + <TD ALIGN="right">Amount:</TD> + <TD> + $<INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $cgi->param('amount') %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_amount()"> + </TD> +<% include('/elements/tr-select-taxclass.html') %> +</TR> + <TD>Description:</TD> + <TD> + <INPUT TYPE="text" NAME="pkg" SIZE="60" MAXLENGTH="65" VALUE="<% $cgi->param('pkg') %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_desc()"> + </TD> +</TR> +<TR> + <TD></TD> + <TD><FONT SIZE="-1">Optional additional description: </FONT></TD> +</TR> + +% my $row = 0; +% if ( $cgi->param('error') ) { +% my $param = $cgi->Vars; +% +% for ( $row = 0; exists($param->{"description$row"}); $row++ ) { + + <TR> + <TD></TD> + <TD> + <INPUT TYPE="text" NAME="description<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $param->{"description$row"} %>" rownum="<% $row %>" onkeyup = "possiblyAddRow;" > + </TD> + </TR> +% } +% } + + +</TABLE> + +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="Add one-time charge" <% $cgi->param('error') ? '' :' DISABLED' %>> + +</FORM> + + +<SCRIPT TYPE="text/javascript"> + + var rownum = <% $row %>; + + function possiblyAddRow() { + if ( ( rownum - this.getAttribute('rownum') ) == 1 ) { + addRow(); + } + } + + function addRow() { + + var table = document.getElementById('QuickChargeTable'); + var tablebody = table.getElementsByTagName('tbody').item(0); + + var row = document.createElement('TR'); + + var empty_cell = document.createElement('TD'); + row.appendChild(empty_cell); + + var description_cell = document.createElement('TD'); + + var description_input = document.createElement('INPUT'); + description_input.setAttribute('name', 'description'+rownum); + description_input.setAttribute('id', 'description'+rownum); + description_input.setAttribute('size', 60); + description_input.setAttribute('maxlength', 65); + description_input.setAttribute('rownum', rownum); + description_input.onkeyup = possiblyAddRow; + description_cell.appendChild(description_input); + + row.appendChild(description_cell); + + tablebody.appendChild(row); + + rownum++; + + } + +</SCRIPT> + +</BODY> +</HTML> diff --git a/httemplate/edit/rate.cgi b/httemplate/edit/rate.cgi index 1771f0105..72a04c339 100644 --- a/httemplate/edit/rate.cgi +++ b/httemplate/edit/rate.cgi @@ -1,38 +1,41 @@ -<% - -my $rate; -if ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $rate = qsearchs( 'rate', { 'ratenum' => $1 } ); -} else { #adding - $rate = new FS::rate {}; -} -my $action = $rate->ratenum ? 'Edit' : 'Add'; - -my $p1 = popurl(1); - -my %granularity = ( - '6' => '6 second', - '60' => 'minute', -); - -#my $nous = <<END; -# WHERE 0 < ( SELECT COUNT(*) FROM rate_prefix -# WHERE rate_region.regionnum = rate_prefix.regionnum -# AND countrycode != '1' -# ) -#END - -%> - -<%= header("$action Rate plan", menubar( +% +% +%my $rate; +%if ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $rate = qsearchs( 'rate', { 'ratenum' => $1 } ); +%} else { #adding +% $rate = new FS::rate {}; +%} +%my $action = $rate->ratenum ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +%my %granularity = ( +% '1', => '1 second', +% '6' => '6 second', +% '30' => '30 second', # '1/2 minute', +% '60' => 'minute', +%); +% +%#my $nous = <<END; +%# WHERE 0 < ( SELECT COUNT(*) FROM rate_prefix +%# WHERE rate_region.regionnum = rate_prefix.regionnum +%# AND countrycode != '1' +%# ) +%#END +% +% + + +<% include("/elements/header.html","$action Rate plan", menubar( 'Main Menu' => $p, 'View all rate plans' => "${p}browse/rate.cgi", )) %> -<%= include('/elements/progress-init.html', +<% include('/elements/progress-init.html', 'OneTrueForm', [ 'rate', 'min_', 'sec_' ], 'process/rate.cgi', @@ -40,13 +43,13 @@ my %granularity = ( ) %> <FORM NAME="OneTrueForm"> -<INPUT TYPE="hidden" NAME="ratenum" VALUE="<%= $rate->ratenum %>"> +<INPUT TYPE="hidden" NAME="ratenum" VALUE="<% $rate->ratenum %>"> Rate plan -<INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="<%= $rate->ratename %>"> +<INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="<% $rate->ratename %>"> <BR><BR> -<%= table() %> +<% table() %> <TR> <TH>Region</TH> <TH>Prefix(es)</TH> @@ -54,53 +57,56 @@ Rate plan <TH><FONT SIZE=-1>Charge per<BR>minute</FONT></TH> <TH><FONT SIZE=-1>Granularity</FONT></TH> </TR> +% foreach my $rate_region ( +% sort { lc($a->regionname) cmp lc($b->regionname) } +% qsearch({ +% 'select' => 'DISTINCT ON ( regionnum ) rate_region.*', +% 'table' => 'rate_region', +% 'hashref' => {}, +% #'addl_from' => 'INNER JOIN rate_prefix USING ( regionnum )', +% #'extra_sql' => "WHERE countrycode != '1'", +% +% # 'ORDER BY regionname' +% # ERROR: SELECT DISTINCT ON expressions must +% # match initial ORDER BY expressions +% }) +% ) { +% my $n = $rate_region->regionnum; +% my $rate_detail = +% $rate->dest_detail($rate_region) +% || new FS::rate_detail { 'min_included' => 0, +% 'min_charge' => 0, +% 'sec_granularity' => '60' +% }; +% -<% foreach my $rate_region ( - sort { lc($a->regionname) cmp lc($b->regionname) } - qsearch({ - 'select' => 'DISTINCT ON ( regionnum ) rate_region.*', - 'table' => 'rate_region', - 'addl_from' => 'INNER JOIN rate_prefix USING ( regionnum )', - 'hashref' => {}, - 'extra_sql' => "WHERE countrycode != '1'", - # 'ORDER BY regionname' - # ERROR: SELECT DISTINCT ON expressions must - # match initial ORDER BY expressions - }) - ) { - my $n = $rate_region->regionnum; - my $rate_detail = - $rate->dest_detail($rate_region) - || new FS::rate_detail { 'min_included' => 0, - 'min_charge' => 0, - 'sec_granularity' => '60' - }; -%> <TR> - <TD><A HREF="<%=$p%>edit/rate_region.cgi?<%= $rate_region->regionnum %>"><%= $rate_region->regionname %></A></TD> - <TD><%= $rate_region->prefixes_short %></TD> - <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%=$n%>" VALUE="<%= $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> - <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%=$n%>" VALUE="<%= sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> + <TD><A HREF="<%$p%>edit/rate_region.cgi?<% $rate_region->regionnum %>"><% $rate_region->regionname %></A></TD> + <TD><% $rate_region->prefixes_short %></TD> + <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> + <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> <TD> - <SELECT NAME="sec_granularity<%=$n%>"> - <% foreach my $granularity ( keys %granularity ) { %> - <OPTION VALUE="<%=$granularity%>"<%= $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%=$granularity{$granularity}%> - <% } %> + <SELECT NAME="sec_granularity<%$n%>"> +% foreach my $granularity ( keys %granularity ) { + + <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%> +% } + </SELECT> </TR> +% } -<% } %> <TR> <TD COLSPAN=5 ALIGN="center"> - <A HREF="<%=$p%>edit/rate_region.cgi"><I>Add a region</I></A> + <A HREF="<%$p%>edit/rate_region.cgi"><I>Add a region</I></A> </TD> </TR> </TABLE> -<BR><INPUT NAME="submit" TYPE="button" VALUE="<%= +<BR><INPUT NAME="submit" TYPE="button" VALUE="<% $rate->ratenum ? "Apply changes" : "Add rate plan" %>" onClick="document.OneTrueForm.submit.disabled=true; process();"> diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi index cc14dd37d..12cb180de 100644 --- a/httemplate/edit/rate_region.cgi +++ b/httemplate/edit/rate_region.cgi @@ -1,109 +1,114 @@ <!-- mason kludge --> -<% - -my $rate_region; -if ( $cgi->param('error') ) { - $rate_region = new FS::rate_region ( { - map { $_, scalar($cgi->param($_)) } fields('rate_region') - } ); -} elsif ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } ); -} else { #adding - $rate_region = new FS::rate_region {}; -} -my $action = $rate_region->regionnum ? 'Edit' : 'Add'; - -my $p1 = popurl(1); - -my %granularity = ( - '6' => '6 second', - '60' => 'minute', -); - -my @rate_prefix = $rate_region->rate_prefix; -my $countrycode = ''; -if ( @rate_prefix ) { - $countrycode = $rate_prefix[0]->countrycode; - foreach my $rate_prefix ( @rate_prefix ) { - eidiot 'multiple country codes per region not yet supported by web UI' - unless $rate_prefix->countrycode eq $countrycode; - } -} - -%> - -<%= header("$action Region", menubar( +% +% +%my $rate_region; +%if ( $cgi->param('error') ) { +% $rate_region = new FS::rate_region ( { +% map { $_, scalar($cgi->param($_)) } fields('rate_region') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } ); +%} else { #adding +% $rate_region = new FS::rate_region {}; +%} +%my $action = $rate_region->regionnum ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +%my %granularity = ( +% '6' => '6 second', +% '60' => 'minute', +%); +% +%my @rate_prefix = $rate_region->rate_prefix; +%my $countrycode = ''; +%if ( @rate_prefix ) { +% $countrycode = $rate_prefix[0]->countrycode; +% foreach my $rate_prefix ( @rate_prefix ) { +% eidiot 'multiple country codes per region not yet supported by web UI' +% unless $rate_prefix->countrycode eq $countrycode; +% } +%} +% +% + + +<% include("/elements/header.html","$action Region", menubar( 'Main Menu' => $p, #'View all regions' => "${p}browse/rate_region.cgi", )) %> +% if ( $cgi->param('error') ) { -<% if ( $cgi->param('error') ) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT><BR> -<% } %> +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT><BR> +% } -<FORM ACTION="<%=$p1%>process/rate_region.cgi" METHOD=POST> -<INPUT TYPE="hidden" NAME="regionnum" VALUE="<%= $rate_region->regionnum %>"> +<FORM ACTION="<%$p1%>process/rate_region.cgi" METHOD=POST> -<%= ntable('#cccccc') %> +<INPUT TYPE="hidden" NAME="regionnum" VALUE="<% $rate_region->regionnum %>"> + +<% ntable('#cccccc') %> <TR> <TH ALIGN="right">Region name</TH> - <TD><INPUT TYPE="text" NAME="regionname" SIZE=32 VALUE="<%= $rate_region->regionname %>"></TR> + <TD><INPUT TYPE="text" NAME="regionname" SIZE=32 VALUE="<% $rate_region->regionname %>"></TR> </TR> <TR> <TH ALIGN="right">Country code</TH> - <TD><INPUT TYPE="text" NAME="countrycode" SIZE=4 MAXLENGTH=3 VALUE="<%= $countrycode %>"></TR> + <TD><INPUT TYPE="text" NAME="countrycode" SIZE=4 MAXLENGTH=3 VALUE="<% $countrycode %>"></TR> </TR> <TR> <TH ALIGN="right">Prefixes</TH> <TD> - <TEXTAREA NAME="npa" WRAP=SOFT><%= join(', ', map $_->npa, @rate_prefix ) %></TEXTAREA> + <TEXTAREA NAME="npa" WRAP=SOFT><% join(', ', map $_->npa, @rate_prefix ) %></TEXTAREA> </TD> </TR> </TABLE> <BR> -<%= table() %> +<% table() %> <TR> <TH>Rate plan</TH> <TH><FONT SIZE=-1>Included<BR>minutes</FONT></TH> <TH><FONT SIZE=-1>Charge per<BR>minute</FONT></TH> <TH><FONT SIZE=-1>Granularity</FONT></TH> </TR> +% foreach my $rate ( qsearch('rate', {}) ) { +% +% my $n = $rate->ratenum; +% my $rate_detail = $rate->dest_detail($rate_region) +% || new FS::rate_region { 'min_included' => 0, +% 'min_charge' => 0, +% 'sec_granularity' => '60' +% }; +% +% -<% foreach my $rate ( qsearch('rate', {}) ) { - - my $n = $rate->ratenum; - my $rate_detail = $rate->dest_detail($rate_region) - || new FS::rate_region { 'min_included' => 0, - 'min_charge' => 0, - 'sec_granularity' => '60' - }; - -%> <TR> - <TD><A HREF="<%=$p%>edit/rate.cgi?<%= $rate->ratenum %>"><%= $rate->ratename %></TD> - <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%=$n%>" VALUE="<%= $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> - <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%=$n%>" VALUE="<%= sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> + <TD><A HREF="<%$p%>edit/rate.cgi?<% $rate->ratenum %>"><% $rate->ratename %></TD> + <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> + <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> <TD> - <SELECT NAME="sec_granularity<%=$n%>"> - <% foreach my $granularity ( keys %granularity ) { %> - <OPTION VALUE="<%=$granularity%>"<%= $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%=$granularity{$granularity}%> - <% } %> + <SELECT NAME="sec_granularity<%$n%>"> +% foreach my $granularity ( keys %granularity ) { + + <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%> +% } + </SELECT> </TR> -<% } %> +% } + </TABLE> -<BR><BR><INPUT TYPE="submit" VALUE="<%= +<BR><BR><INPUT TYPE="submit" VALUE="<% $rate_region->regionnum ? "Apply changes" : "Add region" %>"> diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html new file mode 100644 index 000000000..7c0722cea --- /dev/null +++ b/httemplate/edit/reason.html @@ -0,0 +1,45 @@ +% +% $cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +% my $class=$1; +% +% my %classmap = ('C' => 'cancel', +% 'S' => 'suspend', +% ); +% my $classname = $classmap{$class}; +% +% my (@types) = qsearch( 'reason_type', { 'class' => $class } ); +% +% unless (scalar(@types)) { +% print $cgi->redirect( "reason_type.html?class=$class" ); +% } +<% include( 'elements/edit.html', + 'name' => ucfirst($classname) . ' Reason', + 'table' => 'reason', + 'labels' => { + 'reasonnum' => ucfirst($classname) . ' Reason', + 'reason_type' => ucfirst($classname) . ' Reason type', + 'reason' => ucfirst($classname) . ' Reason', + 'disabled' => 'Disabled', + 'class' => '', + }, + 'fields' => [ + { 'field' => 'reason_type', + 'type' => 'select', + 'value' => { 'vcolumn' => 'typenum', + 'ccolumn' => 'type', + 'values' => \@types, + }, + }, + 'reason', + { 'field' => 'class', + 'type' => 'fixedhidden', + 'value' => $class, + }, + { 'field' => 'disabled', + 'type' => 'checkbox', + 'value' => 'Y' + }, + ], + 'viewall_url' => $p . "browse/reason.html?class=$class", + ) +%> diff --git a/httemplate/edit/reason_type.html b/httemplate/edit/reason_type.html new file mode 100644 index 000000000..970529e35 --- /dev/null +++ b/httemplate/edit/reason_type.html @@ -0,0 +1,28 @@ +% +%$cgi->param('class') =~ /^(\w)$/; +%my $class = $1; +% +%my %classmap = ( 'C' => 'Cancel', +% 'S' => 'Suspend', +% ); +% +%my $classname = $classmap{$class}; +% +<% include( 'elements/edit.html', + 'name' => $classname . ' Reason Type', + 'table' => 'reason_type', + 'labels' => { + 'typenum' => $classname . ' reason type', + 'type' => $classname . ' reason type name', + 'class' => '', + }, + 'fields' => [ + 'type', + { 'field' => 'class', + 'type' => 'hidden', + }, + ], + 'viewall_url' => $p . "browse/reason_type.html?class=$class", + 'new_hashref_callback' => sub {{ 'class' => $class }}, + ) +%> diff --git a/httemplate/edit/reg_code.cgi b/httemplate/edit/reg_code.cgi index 899d1ec45..06bef4879 100644 --- a/httemplate/edit/reg_code.cgi +++ b/httemplate/edit/reg_code.cgi @@ -1,33 +1,36 @@ -<% -my $agentnum = $cgi->param('agentnum'); -$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; -$agentnum = $1; -my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +%my $agentnum = $cgi->param('agentnum'); +%$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +%$agentnum = $1; +%my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +% -%> -<%= header('Generate registration codes for '. $agent->agent, menubar( +<% include("/elements/header.html",'Generate registration codes for '. $agent->agent, menubar( 'Main Menu' => $p, )) %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#FF0000">Error: <% $cgi->param('error') %></FONT> +% } -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#FF0000">Error: <%= $cgi->param('error') %></FONT> -<% } %> -<FORM ACTION="<%=popurl(1)%>process/reg_code.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> -<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agent->agentnum %>"> +<FORM ACTION="<%popurl(1)%>process/reg_code.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>"> Generate -<INPUT TYPE="text" NAME="num" VALUE="<%= $cgi->param('num') %>" SIZE=5 MAXLENGTH=4> -registration codes for <B><%= $agent->agent %></B> allowing the following packages: +<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') %>" SIZE=5 MAXLENGTH=4> +registration codes for <B><% $agent->agent %></B> allowing the following packages: <BR><BR> +% foreach my $part_pkg ( qsearch('part_pkg', { 'disabled' => '' } ) ) { -<% foreach my $part_pkg ( qsearch('part_pkg', { 'disabled' => '' } ) ) { %> - <INPUT TYPE="checkbox" NAME="pkgpart<%= $part_pkg->pkgpart %>"> - <%= $part_pkg->pkg %> - <%= $part_pkg->comment %> + <INPUT TYPE="checkbox" NAME="pkgpart<% $part_pkg->pkgpart %>"> + <% $part_pkg->pkg %> - <% $part_pkg->comment %> <BR> -<% } %> +% } + <BR> <INPUT TYPE="submit" NAME="submit" VALUE="Generate"> diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi index a573c6504..0da45c00e 100755 --- a/httemplate/edit/router.cgi +++ b/httemplate/edit/router.cgi @@ -1,75 +1,76 @@ <HTML><BODY> - -<% - -my $router; -if ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $router = qsearchs('router', { routernum => $1 }) - or print $cgi->redirect(popurl(2)."browse/router.cgi") ; -} else { - $router = new FS::router ( { - map { $_, scalar($cgi->param($_)) } fields('router') - } ); -} - -my $routernum = $router->routernum; -my $action = $routernum ? 'Edit' : 'Add'; - -print header("$action Router", menubar( - 'Main Menu' => "$p", - 'View all routers' => "${p}browse/router.cgi", -)); - -my $p3 = popurl(3); - -if($cgi->param('error')) { -%> <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> -<% } %> - -<FORM ACTION="<%=popurl(1)%>process/router.cgi" METHOD=POST> +% +% +%my $router; +%if ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $router = qsearchs('router', { routernum => $1 }) +% or print $cgi->redirect(popurl(2)."browse/router.cgi") ; +%} else { +% $router = new FS::router ( { +% map { $_, scalar($cgi->param($_)) } fields('router') +% } ); +%} +% +%my $routernum = $router->routernum; +%my $action = $routernum ? 'Edit' : 'Add'; +% +%print header("$action Router", menubar( +% 'Main Menu' => "$p", +% 'View all routers' => "${p}browse/router.cgi", +%)); +% +%my $p3 = popurl(3); +% +%if($cgi->param('error')) { +% + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/router.cgi" METHOD=POST> <INPUT TYPE="hidden" NAME="table" VALUE="router"> - <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%=$p3%>/browse/router.cgi"> - <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%=$p3%>/edit/router.cgi"> - <INPUT TYPE="hidden" NAME="routernum" VALUE="<%=$routernum%>"> - <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$router->svcnum%>"> - Router #<%=$routernum or "(NEW)"%> + <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%$p3%>/browse/router.cgi"> + <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%$p3%>/edit/router.cgi"> + <INPUT TYPE="hidden" NAME="routernum" VALUE="<%$routernum%>"> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$router->svcnum%>"> + Router #<%$routernum or "(NEW)"%> -<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%=$router->routername%>"> +<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%$router->routername%>"> <BR><BR> Custom fields: <BR> -<%=table() %> +<%table() %> +% +%foreach my $field ($router->virtual_fields) { +% print $router->pvf($field)->widget('HTML', 'edit', +% $router->getfield($field)); +%} +% -<% -foreach my $field ($router->virtual_fields) { - print $router->pvf($field)->widget('HTML', 'edit', - $router->getfield($field)); -} -%> </TABLE> +% +%unless ($router->svcnum) { +% - -<% -unless ($router->svcnum) { -%> <BR><BR>Select the service types available on this router<BR> -<% +% +% +% foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', +% disabled => '' }) ) { +% - foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', - disabled => '' }) ) { - %> <BR> - <INPUT TYPE="checkbox" NAME="svcpart_<%=$part_svc->svcpart%>"<%= + <INPUT TYPE="checkbox" NAME="svcpart_<%$part_svc->svcpart%>"<% qsearchs('part_svc_router', { svcpart => $part_svc->svcpart, routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON"> - <A HREF="<%=${p}%>edit/part_svc.cgi?<%=$part_svc->svcpart%>"> - <%=$part_svc->svcpart%>: <%=$part_svc->svc%></A> - <% } %> + <A HREF="<%${p}%>edit/part_svc.cgi?<%$part_svc->svcpart%>"> + <%$part_svc->svcpart%>: <%$part_svc->svc%></A> +% } +% } -<% } %> <BR><BR><INPUT TYPE="submit" VALUE="Apply changes"> </FORM> diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html new file mode 100644 index 000000000..6393f9ebc --- /dev/null +++ b/httemplate/edit/svc_Common.html @@ -0,0 +1,30 @@ +<%init> + +# false laziness w/view/svc_Common.html + +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +my $table = $1; +require "FS/$table.pm"; + +my %opt; +if ( UNIVERSAL::can("FS::$table", 'table_info') ) { + $opt{'name'} = "FS::$table"->table_info->{'name'}; + + my $fields = "FS::$table"->table_info->{'fields'}; + my %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } + keys %$fields; + $opt{'labels'} = \%labels; + +} + +</%init> +<% include('elements/svc_Common.html', + 'table' => $table, + 'post_url' => popurl(1). "process/svc_Common.html", + %opt, + ) +%> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index e74d84d53..ebaa4b2ba 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -1,442 +1,452 @@ -<% - -my $conf = new FS::Conf; -my @shells = $conf->config('shells'); - -my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct, @groups); -if ( $cgi->param('error') ) { - - $svc_acct = new FS::svc_acct ( { - map { $_, scalar($cgi->param($_)) } fields('svc_acct') - } ); - $svcnum = $svc_acct->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); - die "No part_svc entry for svcpart $svcpart!" unless $part_svc; - @groups = $cgi->param('radius_usergroup'); - -} else { - - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) - or die "Unknown (svc_acct) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); - die "No part_svc entry for svcpart $svcpart!" unless $part_svc; - - @groups = $svc_acct->radius_groups; - - } else { #adding - - foreach $_ (split(/-/,$query)) { - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); - die "No part_svc entry for svcpart $svcpart!" unless $part_svc; - - $svc_acct = new FS::svc_acct({svcpart => $svcpart}); - - $svcnum=''; - - } -} - -my( $cust_pkg, $cust_main ) = ( '', '' ); -if ( $pkgnum ) { - $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } ); - $cust_main = $cust_pkg->cust_main; -} - -unless ( $svcnum || $cgi->param('error') ) { #adding - - #set gecos - if ($cust_main) { - unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) { - $svc_acct->setfield('finger', - $cust_main->getfield('first') . " " . $cust_main->getfield('last') - ); - } - } - - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - if ( $part_svc_column->columnname eq 'usergroup' ) { - @groups = split(',', $part_svc_column->columnvalue); - } else { - $svc_acct->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } - } - -} - -#fixed radius groups always override & display -if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { - @groups = split(',', $part_svc->part_svc_column('usergroup')->columnvalue); -} - -my $action = $svcnum ? 'Edit' : 'Add'; - -my $svc = $part_svc->getfield('svc'); - -my $otaker = getotaker; - -my $username = $svc_acct->username; -my $password; -if ( $svc_acct->_password ) { - if ( $conf->exists('showpasswords') || ! $svcnum ) { - $password = $svc_acct->_password; - } else { - $password = "*HIDDEN*"; - } -} else { - $password = ''; -} - -my $ulen = - $conf->exists('usernamemax') - ? $conf->config('usernamemax') - : dbdef->table('svc_acct')->column('username')->length; -my $ulen2 = $ulen+2; - -my $pmax = $conf->config('passwordmax') || 8; -my $pmax2 = $pmax+2; - -my $p1 = popurl(1); - -%> - -<%= header("$action $svc account") %> - -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> +% +% +%my $conf = new FS::Conf; +%my @shells = $conf->config('shells'); +% +%my $curuser = $FS::CurrentUser::CurrentUser; +% +%my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct, @groups); +%if ( $cgi->param('error') ) { +% +% $svc_acct = new FS::svc_acct ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_acct') +% } ); +% $svcnum = $svc_acct->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% @groups = $cgi->param('radius_usergroup'); +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_acct = new FS::svc_acct({svcpart => $svcpart}); +% +% $svcnum=''; +% +%} else { #editing +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_acct) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% +% @groups = $svc_acct->radius_groups; +% +%} +% +%my( $cust_pkg, $cust_main ) = ( '', '' ); +%if ( $pkgnum ) { +% $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } ); +% $cust_main = $cust_pkg->cust_main; +%} +% +%unless ( $svcnum || $cgi->param('error') ) { #adding +% +% #set gecos +% if ($cust_main) { +% unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) { +% $svc_acct->setfield('finger', +% $cust_main->getfield('first') . " " . $cust_main->getfield('last') +% ); +% } +% } +% +% $svc_acct->set_default_and_fixed( { +% #false laziness w/svc-acct::_fieldhandlers +% 'usergroup' => sub { +% my( $self, $groups ) = @_; +% if ( ref($groups) eq 'ARRAY' ) { +% @groups = @$groups; +% $groups; +% } elsif ( length($groups) ) { +% @groups = split(/\s*,\s*/, $groups); +% [ @groups ]; +% } else { +% @groups = (); +% []; +% } +% } +% } ); +% +%} +% +%#fixed radius groups always override & display +%if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { +% @groups = split(',', $part_svc->part_svc_column('usergroup')->columnvalue); +%} +% +%my $action = $svcnum ? 'Edit' : 'Add'; +% +%my $svc = $part_svc->getfield('svc'); +% +%my $otaker = getotaker; +% +%my $username = $svc_acct->username; +%my $password; +%if ( $svc_acct->_password ) { +% if ( $conf->exists('showpasswords') || ! $svcnum ) { +% $password = $svc_acct->_password; +% } else { +% $password = "*HIDDEN*"; +% } +%} else { +% $password = ''; +%} +% +%my $ulen = +% $conf->exists('usernamemax') +% ? $conf->config('usernamemax') +% : dbdef->table('svc_acct')->column('username')->length; +%my $ulen2 = $ulen+2; +% +%my $pmax = $conf->config('passwordmax') || 8; +%my $pmax2 = $pmax+2; +% +%my $p1 = popurl(1); +% +% + + +<% include("/elements/header.html","$action $svc account") %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> <BR><BR> -<% } %> +% } +% if ( $cust_main ) { -<% if ( $cust_main ) { %> - <%= include( '/elements/small_custview.html', $cust_main, '', 1 ) %> + <% include( '/elements/small_custview.html', $cust_main, '', 1, + popurl(2) . "view/cust_main.cgi") %> <BR> -<% } %> +% } + -<FORM NAME="OneTrueForm" ACTION="<%= $p1 %>process/svc_acct.cgi" METHOD=POST> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> -<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>"> +<FORM NAME="OneTrueForm" ACTION="<% $p1 %>process/svc_acct.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> -Service # <%= $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> +Service # <% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> -<%= ntable("#cccccc",2) %> +<% ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Service</TD> - <TD BGCOLOR="#eeeeee"><%= $part_svc->svc %></TD> + <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD> </TR> <TR> <TD ALIGN="right">Username</TD> <TD> - <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>" SIZE=<%= $ulen2 %> MAXLENGTH=<%= $ulen %>> + <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>> </TD> </TR> <TR> <TD ALIGN="right">Password</TD> <TD> - <INPUT TYPE="text" NAME="_password" VALUE="<%= $password %>" SIZE=<%= $pmax2 %> MAXLENGTH=<%= $pmax %>> + <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> (blank to generate) </TD> </TR> +% +%my $sec_phrase = $svc_acct->sec_phrase; +%if ( $conf->exists('security_phrase') ) { +% -<% -my $sec_phrase = $svc_acct->sec_phrase; -if ( $conf->exists('security_phrase') ) { -%> - <TR> <TD ALIGN="right">Security phrase</TD> <TD> - <INPUT TYPE="text" NAME="sec_phrase" VALUE="<%= $sec_phrase %>" SIZE=32> + <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32> (for forgotten passwords) </TD> </TD> +% } else { + + + <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="<% $sec_phrase %>"> +% } +% +%#domain +%my $domsvc = $svc_acct->domsvc || 0; +%if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) { +% + + + <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>"> +% } else { +% +% my %svc_domain = (); +% +% if ( $domsvc ) { +% my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } ); +% if ( $svc_domain ) { +% $svc_domain{$svc_domain->svcnum} = $svc_domain; +% } else { +% warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc"; +% } +% } +% +% if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'D' ) { +% my $svc_domain = qsearchs('svc_domain', { +% 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue, +% } ); +% if ( $svc_domain ) { +% $svc_domain{$svc_domain->svcnum} = $svc_domain; +% } else { +% warn "unknown svc_domain.svcnum for part_svc_column domsvc: ". +% $part_svc->part_svc_column('domsvc')->columnvalue; +% } +% } +% +% if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'S' ) { +% foreach my $domain +% (split(',',$part_svc->part_svc_column('domsvc')->columnvalue)) { +% my $svc_domain = +% qsearchs('svc_domain', { 'svcnum' => $domain } ); +% $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain; +% } +% }elsif ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) { +% my @cust_svc = +% map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) } +% qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } ); +% foreach my $cust_svc ( @cust_svc ) { +% my $svc_domain = +% qsearchs('svc_domain', { 'svcnum' => $cust_svc->svcnum } ); +% $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain; +% } +% } else { +% %svc_domain = map { $_->svcnum => $_ } qsearch('svc_domain', {} ); +% } +% +% -<% } else { %> - - <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="<%= $sec_phrase %>"> - -<% } %> - - -<% -#domain -my $domsvc = $svc_acct->domsvc || 0; -if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) { -%> - - <INPUT TYPE="hidden" NAME="domsvc" VALUE="<%= $domsvc %>"> - -<% } else { - - my %svc_domain = (); - - if ( $domsvc ) { - my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } ); - if ( $svc_domain ) { - $svc_domain{$svc_domain->svcnum} = $svc_domain; - } else { - warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc"; - } - } - - if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'D' ) { - my $svc_domain = qsearchs('svc_domain', { - 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue, - } ); - if ( $svc_domain ) { - $svc_domain{$svc_domain->svcnum} = $svc_domain; - } else { - warn "unknown svc_domain.svcnum for part_svc_column domsvc: ". - $part_svc->part_svc_column('domsvc')->columnvalue; - } - } - - if ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) { - my @cust_svc = - map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) } - qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } ); - foreach my $cust_svc ( @cust_svc ) { - my $svc_domain = - qsearchs('svc_domain', { 'svcnum' => $cust_svc->svcnum } ); - $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain; - } - } else { - %svc_domain = map { $_->svcnum => $_ } qsearch('svc_domain', {} ); - } - -%> <TR> <TD ALIGN="right">Domain</TD> <TD> <SELECT NAME="domsvc" SIZE=1> +% foreach my $svcnum ( +% sort { $svc_domain{$a}->domain cmp $svc_domain{$b}->domain } +% keys %svc_domain +% ) { +% my $svc_domain = $svc_domain{$svcnum}; +% - <% foreach my $svcnum ( - sort { $svc_domain{$a}->domain cmp $svc_domain{$b}->domain } - keys %svc_domain - ) { - my $svc_domain = $svc_domain{$svcnum}; - %> - <OPTION VALUE="<%= $svc_domain->svcnum %>" <%= $svc_domain->svcnum == $domsvc ? ' SELECTED' : '' %>><%= $svc_domain->domain %> + <OPTION VALUE="<% $svc_domain->svcnum %>" <% $svc_domain->svcnum == $domsvc ? ' SELECTED' : '' %>><% $svc_domain->domain %> +% } - <% } %> </SELECT> </TD> </TR> +% } +% +%#pop +%my $popnum = $svc_acct->popnum || 0; +%if ( $part_svc->part_svc_column('popnum')->columnflag eq 'F' ) { +% -<% } %> - - -<% -#pop -my $popnum = $svc_acct->popnum || 0; -if ( $part_svc->part_svc_column('popnum')->columnflag eq 'F' ) { -%> - - <INPUT TYPE="hidden" NAME="popnum" VALUE="<%= $popnum %>"> + <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>"> +% } else { -<% } else { %> <TR> <TD ALIGN="right">Access number</TD> - <TD><%= FS::svc_acct_pop::popselector($popnum) %></TD> + <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> </TR> +% } +% #uid/gid +% foreach my $xid (qw( uid gid )) { +% +% if ( $part_svc->part_svc_column($xid)->columnflag =~ /^[FA]$/ +% || ! $conf->exists("svc_acct-edit_$xid") +% ) { +% +% if ( length($svc_acct->$xid()) ) { -<% } %> - - -<% #uid/gid %> -<% foreach my $xid (qw( uid gid )) { %> - - <% - if ( $part_svc->part_svc_column($xid)->columnflag eq 'F' - || ! $conf->exists("svc_acct-edit_$xid") - ) { - %> - - <% if ( length($svc_acct->$xid()) ) { %> <TR> - <TD ALIGN="right"><%= uc($xid) %></TD> - <TD BGCOLOR="#eeeeee"><%= $svc_acct->$xid() %></TD> + <TD ALIGN="right"><% uc($xid) %></TD> + <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD> <TD> </TD> </TR> +% } + - <% } %> - - <INPUT TYPE="hidden" NAME="<%= $xid %>" VALUE="<%= $svc_acct->$xid() %>"> - - <% } else { %> + <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>"> +% } else { + <TR> - <TD ALIGN="right"><%= uc($xid) %></TD> + <TD ALIGN="right"><% uc($xid) %></TD> <TD> - <INPUT TYPE="text" NAME="<%= $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<%= $svc_acct->$xid() %>"> + <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>"> </TD> </TR> - - <% } %> - -<% } %> +% } +% } +% +%#finger +%if ( $part_svc->part_svc_column('uid')->columnflag eq 'F' +% && ! $svc_acct->finger ) { +% -<% -#finger -if ( $part_svc->part_svc_column('uid')->columnflag eq 'F' - && ! $svc_acct->finger ) { -%> - <INPUT TYPE="hidden" NAME="finger" VALUE=""> +% } else { -<% } else { %> <TR> <TD ALIGN="right">GECOS</TD> <TD> - <INPUT TYPE="text" NAME="finger" VALUE="<%= $svc_acct->finger %>"> + <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>"> </TD> </TR> - -<% } %> +% } -<INPUT TYPE="hidden" NAME="dir" VALUE="<%= $svc_acct->dir %>"> +<INPUT TYPE="hidden" NAME="dir" VALUE="<% $svc_acct->dir %>"> +% +%#shell +%my $shell = $svc_acct->shell; +%if ( $part_svc->part_svc_column('shell')->columnflag eq 'F' +% || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) +% ) { +% -<% -#shell -my $shell = $svc_acct->shell; -if ( $part_svc->part_svc_column('shell')->columnflag eq 'F' - || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) - ) { -%> - <INPUT TYPE="hidden" NAME="shell" VALUE="<%= $shell %>"> + <INPUT TYPE="hidden" NAME="shell" VALUE="<% $shell %>"> +% } else { -<% } else { %> <TR> <TD ALIGN="right">Shell</TD> <TD> <SELECT NAME="shell" SIZE=1> +% +% my($etc_shell); +% foreach $etc_shell (@shells) { +% - <% - my($etc_shell); - foreach $etc_shell (@shells) { - %> - <OPTION<%= $etc_shell eq $shell ? ' SELECTED' : '' %>><%= $etc_shell %> + <OPTION<% $etc_shell eq $shell ? ' SELECTED' : '' %>><% $etc_shell %> +% } - <% } %> </SELECT> </TD> </TR> +% } +% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) { -<% } %> + <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>"> +% } else { -<% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) { %> - - <INPUT TYPE="hidden" NAME="quota" VALUE="<%= $svc_acct->quota %>"> - -<% } else { %> <TR> <TD ALIGN="right">Quota:</TD> - <TD><INPUT TYPE="text" NAME="quota" VALUE="<%= $svc_acct->quota %>"></TD> + <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD> </TR> - -<% } %> - +% } +% if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { -<% if ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) { %> - <INPUT TYPE="hidden" NAME="slipip" VALUE="<%= $svc_acct->slipip %>"> + <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>"> +% } else { -<% } else { %> <TR> <TD ALIGN="right">IP</TD> - <TD><INPUT TYPE="text" NAME="slipip" VALUE="<%= $svc_acct->slipip %>"></TD> + <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD> </TR> +% } +% +% if ( $curuser->access_right('Edit usage') ) { +% my %label = ( seconds => 'Seconds', +% upbytes => 'Upload bytes', +% downbytes => 'Download bytes', +% totalbytes => 'Total bytes', +% ); +% foreach my $uf (keys %label) { +% my $tf = $uf . "_threshold"; +% if ( $svc_acct->$tf ne '' ) { -<% } %> - - -<% -foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) { - $r =~ /^^r(adius|[cr])_(.+)$/ or next; #? - my $a = $2; -%> + <TR> + <TD ALIGN="right"><% $label{$uf} %> remaining</TD> + <TD><INPUT TYPE="text" NAME="<% $uf %>" VALUE="<% $svc_acct->$uf %>"></TD> + </TR> +% } +% } +% } +% +%foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) { +% $r =~ /^^r(adius|[cr])_(.+)$/ or next; #? +% my $a = $2; +% +% if ( $part_svc->part_svc_column($r)->columnflag =~ /^[FA]$/ ) { - <% if ( $part_svc->part_svc_column($r)->columnflag eq 'F' ) { %> - <INPUT TYPE="hidden" NAME="<%= $r %>" VALUE="<%= $svc_acct->getfield($r) %>"> + <INPUT TYPE="hidden" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"> +% } else { - <% } else { %> <TR> - <TD ALIGN="right"><%= $FS::raddb::attrib{$a} %></TD> - <TD><INPUT TYPE="text" NAME="<%= $r %>" VALUE="<%= $svc_acct->getfield($r) %>"></TD> + <TD ALIGN="right"><% $FS::raddb::attrib{$a} %></TD> + <TD><INPUT TYPE="text" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"></TD> </TR> +% } +% } - <% } %> - -<% } %> <TR> <TD ALIGN="right">RADIUS groups</TD> +% if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { - <% if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { %> - <TD BGCOLOR="#eeeeee"><%= join('<BR>', @groups) %></TD> + <TD BGCOLOR="#eeeeee"><% join('<BR>', @groups) %></TD> +% } else { - <% } else { %> - <TD><%= FS::svc_acct::radius_usergroup_selector( \@groups ) %></TD> + <TD><% FS::svc_acct::radius_usergroup_selector( \@groups ) %></TD> +% } - <% } %> </TR> +% foreach my $field ($svc_acct->virtual_fields) { +% # If the flag is X, it won't even show up in $svc_acct->virtual_fields. +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { -<% foreach my $field ($svc_acct->virtual_fields) { %> - - <% # If the flag is X, it won't even show up in $svc_acct->virtual_fields. %> - <% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { %> - - <%= $svc_acct->pvf($field)->widget('HTML', 'edit', $svc_acct->getfield($field)) %> - <% } %> + <% $svc_acct->pvf($field)->widget('HTML', 'edit', $svc_acct->getfield($field)) %> +% } +% } -<% } %> </TABLE> <BR> diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi index 399502a70..641aa0378 100755 --- a/httemplate/edit/svc_acct_pop.cgi +++ b/httemplate/edit/svc_acct_pop.cgi @@ -1,56 +1,57 @@ <!-- mason kludge --> -<% +% +% +%my $svc_acct_pop; +%if ( $cgi->param('error') ) { +% $svc_acct_pop = new FS::svc_acct_pop ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop') +% } ); +%} elsif ( $cgi->keywords ) { #editing +% my($query)=$cgi->keywords; +% $query =~ /^(\d+)$/; +% $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1}); +%} else { #adding +% $svc_acct_pop = new FS::svc_acct_pop {}; +%} +%my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add'; +%my $hashref = $svc_acct_pop->hashref; +% +%my $p1 = popurl(1); +%print header("$action Access Number", menubar( +% 'Main Menu' => popurl(2), +% 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi", +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/svc_acct_pop.cgi" METHOD=POST>!; +% +%#display +% +%print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$hashref->{popnum}">!, +% "POP #", $hashref->{popnum} ? $hashref->{popnum} : "(NEW)"; +% +%print <<END; +%<PRE> +%City <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="$hashref->{city}"> +%State <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="$hashref->{state}"> +%Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{ac}"> +%Exchange <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{exch}"> +%Local <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="$hashref->{loc}"> +%</PRE> +%END +% +%print qq!<BR><INPUT TYPE="submit" VALUE="!, +% $hashref->{popnum} ? "Apply changes" : "Add Access Number", +% qq!">!; +% +%print <<END; +% </FORM> +% </BODY> +%</HTML> +%END +% +% -my $svc_acct_pop; -if ( $cgi->param('error') ) { - $svc_acct_pop = new FS::svc_acct_pop ( { - map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop') - } ); -} elsif ( $cgi->keywords ) { #editing - my($query)=$cgi->keywords; - $query =~ /^(\d+)$/; - $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1}); -} else { #adding - $svc_acct_pop = new FS::svc_acct_pop {}; -} -my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add'; -my $hashref = $svc_acct_pop->hashref; - -my $p1 = popurl(1); -print header("$action Access Number", menubar( - 'Main Menu' => popurl(2), - 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi", -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/svc_acct_pop.cgi" METHOD=POST>!; - -#display - -print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$hashref->{popnum}">!, - "POP #", $hashref->{popnum} ? $hashref->{popnum} : "(NEW)"; - -print <<END; -<PRE> -City <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="$hashref->{city}"> -State <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="$hashref->{state}"> -Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{ac}"> -Exchange <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{exch}"> -Local <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="$hashref->{loc}"> -</PRE> -END - -print qq!<BR><INPUT TYPE="submit" VALUE="!, - $hashref->{popnum} ? "Apply changes" : "Add Access Number", - qq!">!; - -print <<END; - </FORM> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 9e064c5c8..2a5a6509a 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -1,171 +1,250 @@ -<!-- mason kludge --> -<% - -# If it's stupid but it works, it's still stupid. -# -Kristian - - -use HTML::Widgets::SelectLayers; -use Tie::IxHash; - -my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband ); -if ( $cgi->param('error') ) { - $svc_broadband = new FS::svc_broadband ( { - map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart) - } ); - $svcnum = $svc_broadband->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $svc_broadband->svcpart; - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum}) - or die "Unknown (svc_broadband) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { #adding - - foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart }); +%# If it's stupid but it works, it's still stupid. +%# -Kristian +% +%use HTML::Widgets::SelectLayers; +%use Tie::IxHash; +% +%my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband ); +%if ( $cgi->param('error') ) { +% +% $svc_broadband = new FS::svc_broadband ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart) +% } ); +% $svcnum = $svc_broadband->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $svc_broadband->svcpart; +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart }); +% +% $svcnum=''; +% +% $svc_broadband->set_default_and_fixed; +% +%} else { #editing +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_broadband) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} +%my $action = $svc_broadband->svcnum ? 'Edit' : 'Add'; +% +%if ($pkgnum) { +% +% #Nothing? +% +%} elsif ( $action eq 'Edit' ) { +% +% #Nothing? +% +%} else { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +%my $p1 = popurl(1); +% +%my ($ip_addr, $speed_up, $speed_down, $blocknum, $mac_addr, +% $latitude, $longitude, $altitude, $vlan_profile, $auth_key, +% $description) = +% ($svc_broadband->ip_addr, +% $svc_broadband->speed_up, +% $svc_broadband->speed_down, +% $svc_broadband->blocknum, +% $svc_broadband->mac_addr, +% $svc_broadband->latitude, +% $svc_broadband->longitude, +% $svc_broadband->altitude, +% $svc_broadband->vlan_profile, +% $svc_broadband->auth_key, +% $svc_broadband->description, +% ); +% +% + + +<%include("/elements/header.html","Broadband Service $action", '')%> +% if ($cgi->param('error')) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT><BR> +% } + + +Service #<B><%$svcnum ? $svcnum : "(NEW)"%></B><BR><BR> + +<FORM ACTION="<%${p1}%>process/svc_broadband.cgi" METHOD=POST> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> + <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$pkgnum%>"> + <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$svcpart%>"> + + <%&ntable("#cccccc",2)%> + <TR> + <TD ALIGN="right">Description</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('description')->columnflag eq 'F' ) { - $svcnum=''; + <INPUT TYPE="hidden" NAME="description" VALUE="<%$description%>"><%$description%> +% } else { - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - $svc_broadband->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } + <INPUT TYPE="text" NAME="description" VALUE="<%$description%>"> +% } - } -} -my $action = $svc_broadband->svcnum ? 'Edit' : 'Add'; + </TD> + </TR> + <TR> + <TD ALIGN="right">IP Address</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { -if ($pkgnum) { + <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%$ip_addr%>"><%$ip_addr%> +% } else { - #Nothing? + <INPUT TYPE="text" NAME="ip_addr" VALUE="<%$ip_addr%>"> +% } -} elsif ( $action eq 'Edit' ) { + </TD> + </TR> + <TR> + <TD ALIGN="right">Download speed</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { - #Nothing? + <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%$speed_down%>"><%$speed_down%>Kbps +% } else { -} else { - die "\$action eq Add, but \$pkgnum is null!\n"; -} + <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%$speed_down%>">Kbps +% } -my $p1 = popurl(1); + </TD> + </TR> + <TR> + <TD ALIGN="right">Upload speed</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { -my ($ip_addr, $speed_up, $speed_down, $blocknum) = - ($svc_broadband->ip_addr, - $svc_broadband->speed_up, - $svc_broadband->speed_down, - $svc_broadband->blocknum); + <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%$speed_up%>"><%$speed_up%>Kbps +% } else { -%> + <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%$speed_up%>">Kbps +% } -<%=header("Broadband Service $action", '')%> + </TD> + </TR> +% if ($action eq 'Add') { -<% if ($cgi->param('error')) { %> -<FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT><BR> -<% } %> + <TR> + <TD ALIGN="right">Router/Block</TD> + <TD BGCOLOR="#ffffff"> + <SELECT NAME="blocknum"> +% +% warn $svc_broadband->svcpart; +% foreach my $router ($svc_broadband->allowed_routers) { +% warn $router->routername; +% foreach my $addr_block ($router->addr_block) { +% + + <OPTION VALUE="<%$addr_block->blocknum%>"<%($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>> + <%$router->routername%>:<%$addr_block->ip_gateway%>/<%$addr_block->ip_netmask%></OPTION> +% +% } +% } +% -Service #<B><%=$svcnum ? $svcnum : "(NEW)"%></B><BR><BR> + </SELECT> + </TD> + </TR> +% } else { -<FORM ACTION="<%=${p1}%>process/svc_broadband.cgi" METHOD=POST> - <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>"> - <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%=$pkgnum%>"> - <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%=$svcpart%>"> - <%=&ntable("#cccccc",2)%> <TR> - <TD ALIGN="right">IP Address</TD> + <TD ALIGN="right">Router/Block</TD> <TD BGCOLOR="#ffffff"> -<% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { %> - <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%=$ip_addr%>"><%=$ip_addr%> -<% } else { %> - <INPUT TYPE="text" NAME="ip_addr" VALUE="<%=$ip_addr%>"> -<% } %> + <%$svc_broadband->addr_block->router->routername%>:<%$svc_broadband->addr_block->NetAddr%> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$svc_broadband->blocknum%>"> </TD> </TR> +% } <TR> - <TD ALIGN="right">Download speed</TD> + <TD ALIGN="right">MAC Address</TD> <TD BGCOLOR="#ffffff"> -<% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { %> - <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%=$speed_down%>"><%=$speed_down%>Kbps -<% } else { %> - <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%=$speed_down%>">Kbps -<% } %> + <INPUT TYPE="text" NAME="mac_addr" VALUE="<%$mac_addr%>"> </TD> </TR> <TR> - <TD ALIGN="right">Upload speed</TD> + <TD ALIGN="right">Latitude</TD> <TD BGCOLOR="#ffffff"> -<% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { %> - <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%=$speed_up%>"><%=$speed_up%>Kbps -<% } else { %> - <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%=$speed_up%>">Kbps -<% } %> + <INPUT TYPE="text" NAME="latitude" VALUE="<%$latitude%>"> </TD> </TR> -<% if ($action eq 'Add') { %> <TR> - <TD ALIGN="right">Router/Block</TD> + <TD ALIGN="right">Longitude</TD> <TD BGCOLOR="#ffffff"> - <SELECT NAME="blocknum"> -<% - warn $svc_broadband->svcpart; - foreach my $router ($svc_broadband->allowed_routers) { - warn $router->routername; - foreach my $addr_block ($router->addr_block) { -%> - <OPTION VALUE="<%=$addr_block->blocknum%>"<%=($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>> - <%=$router->routername%>:<%=$addr_block->ip_gateway%>/<%=$addr_block->ip_netmask%></OPTION> -<% - } - } -%> - </SELECT> + <INPUT TYPE="text" NAME="longitude" VALUE="<%$longitude%>"> </TD> </TR> -<% } else { %> - <TR> - <TD ALIGN="right">Router/Block</TD> + <TD ALIGN="right">Altitude</TD> <TD BGCOLOR="#ffffff"> - <%=$svc_broadband->addr_block->router->routername%>:<%=$svc_broadband->addr_block->NetAddr%> - <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$svc_broadband->blocknum%>"> + <INPUT TYPE="text" NAME="altitude" VALUE="<%$altitude%>"> + </TD> + </TR> + <TR> + <TD ALIGN="right">VLAN Profile</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('vlan_profile')->columnflag eq 'F' ) { + + <INPUT TYPE="hidden" NAME="vlan_profile" VALUE="<%$vlan_profile%>"><%$vlan_profile%> +% } else { + + <INPUT TYPE="text" NAME="vlan_profile" VALUE="<%$vlan_profile%>"> +% } + </TD> </TR> + <TR> + <TD ALIGN="right">Authentication Key</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('auth_key')->columnflag eq 'F' ) { + + <INPUT TYPE="hidden" NAME="auth_key" VALUE="<%$auth_key%>"><%$auth_key%> +% } else { -<% } %> + <INPUT TYPE="text" NAME="auth_key" VALUE="<%$auth_key%>"> +% } + + </TD> + </TR> +% +%foreach my $field ($svc_broadband->virtual_fields) { +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' && +% $part_svc->part_svc_column($field)->columnflag ne 'X') { +% print $svc_broadband->pvf($field)->widget('HTML', 'edit', +% $svc_broadband->getfield($field)); +% } +%} -<% -foreach my $field ($svc_broadband->virtual_fields) { - if ( $part_svc->part_svc_column($field)->columnflag ne 'F' && - $part_svc->part_svc_column($field)->columnflag ne 'X') { - print $svc_broadband->pvf($field)->widget('HTML', 'edit', - $svc_broadband->getfield($field)); - } -} %> </TABLE> <BR> <INPUT TYPE="submit" NAME="submit" VALUE="Submit"> diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi index ca0e3398f..5ec074bda 100755 --- a/httemplate/edit/svc_domain.cgi +++ b/httemplate/edit/svc_domain.cgi @@ -1,98 +1,90 @@ -<!-- mason kludge --> -<% +%my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, +% $svc_domain); +%if ( $cgi->param('error') ) { +% +% $svc_domain = new FS::svc_domain ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_domain') +% } ); +% $svcnum = $svc_domain->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $kludge_action = $cgi->param('action'); +% $purpose = $cgi->param('purpose'); +% $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry!" unless $part_svc; +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_domain = new FS::svc_domain({}); +% +% $svcnum=''; +% +% $svc_domain->set_default_and_fixed; +% +%} else { #editing +% +% $kludge_action = ''; +% $purpose = ''; +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_domain) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} +%my $action = $svcnum ? 'Edit' : 'Add'; +% +%my $svc = $part_svc->getfield('svc'); +% +%my $otaker = getotaker; +% +%my $domain = $svc_domain->domain; +% +%my $p1 = popurl(1); +% +% + + +<% include('/elements/header.html', "$action $svc", '') %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% $p1 %>process/svc_domain.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> + +<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>New +<BR> + +<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer + +<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63> + +<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="<% $purpose %>" SIZE=64> -my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, - $svc_domain); -if ( $cgi->param('error') ) { - $svc_domain = new FS::svc_domain ( { - map { $_, scalar($cgi->param($_)) } fields('svc_domain') - } ); - $svcnum = $svc_domain->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $kludge_action = $cgi->param('action'); - $purpose = $cgi->param('purpose'); - $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); - die "No part_svc entry!" unless $part_svc; -} else { - $kludge_action = ''; - $purpose = ''; - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) - or die "Unknown (svc_domain) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { #adding - - $svc_domain = new FS::svc_domain({}); - - foreach $_ (split(/-/,$query)) { - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - $svcnum=''; - - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - $svc_domain->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } - - } - -} -my $action = $svcnum ? 'Edit' : 'Add'; - -my $svc = $part_svc->getfield('svc'); - -my $otaker = getotaker; - -my $domain = $svc_domain->domain; - -my $p1 = popurl(1); -print header("$action $svc", ''); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print <<END; - <FORM ACTION="${p1}process/svc_domain.cgi" METHOD=POST> - <INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum"> - <INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> - <INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart"> -END - -print qq!<INPUT TYPE="radio" NAME="action" VALUE="N"!; -print ' CHECKED' if $kludge_action eq 'N'; -print qq!>New!; -print qq!<BR><INPUT TYPE="radio" NAME="action" VALUE="M"!; -print ' CHECKED' if $kludge_action eq 'M'; -print qq!>Transfer!; - -print <<END; -<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="$domain" SIZE=28 MAXLENGTH=63> -<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="$purpose" SIZE=64> <P><INPUT TYPE="submit" VALUE="Submit"> - </FORM> - </BODY> -</HTML> -END -%> +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi index bcfc85e3f..393e71c38 100644 --- a/httemplate/edit/svc_external.cgi +++ b/httemplate/edit/svc_external.cgi @@ -1,102 +1,96 @@ -<!-- mason kludge --> -<% +%my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external ); +%if ( $cgi->param('error') ) { +% +% $svc_external = new FS::svc_external ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_external') +% } ); +% $svcnum = $svc_external->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_external = new FS::svc_external { svcpart => $svcpart }; +% +% $svcnum=''; +% +% $svc_external->set_default_and_fixed; +% +%} else { #adding +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_external) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} +%my $action = $svc_external->svcnum ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +%print header("External service $action", ''); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/svc_external.cgi" METHOD=POST>!; +% +%#display +% +% +%#svcnum +%print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +%print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>"; +% +%#pkgnum +%print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; +% +%#svcpart +%print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; +% +%my($id,$title)=( +% $svc_external->id, +% $svc_external->title, +%); +% +%print &ntable("#cccccc",2), +% '<TR><TD ALIGN="right">External ID</TD><TD>'. +% qq!<INPUT TYPE="text" NAME="id" VALUE="$id">!. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">Title</TD><TD>'. +% qq!<INPUT TYPE="text" NAME="title" VALUE="$title">!. +% '</TD></TR>'; +% +%foreach my $field ($svc_external->virtual_fields) { +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { +% # If the flag is X, it won't even show up in $svc_acct->virtual_fields. +% print $svc_external->pvf($field)->widget('HTML', 'edit', +% $svc_external->getfield($field)); +% } +%} +% +% -my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external ); -if ( $cgi->param('error') ) { - $svc_external = new FS::svc_external ( { - map { $_, scalar($cgi->param($_)) } fields('svc_external') - } ); - $svcnum = $svc_external->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum}) - or die "Unknown (svc_external) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { #adding - - foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $svc_external = new FS::svc_external { svcpart => $svcpart }; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - $svcnum=''; - - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - $svc_external->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } - - } -} -my $action = $svc_external->svcnum ? 'Edit' : 'Add'; - -my $p1 = popurl(1); -print header("External service $action", ''); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/svc_external.cgi" METHOD=POST>!; - -#display - - -#svcnum -print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; -print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>"; - -#pkgnum -print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; - -#svcpart -print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; - -my($id,$title)=( - $svc_external->id, - $svc_external->title, -); - -print &ntable("#cccccc",2), - '<TR><TD ALIGN="right">External ID</TD><TD>'. - qq!<INPUT TYPE="text" NAME="id" VALUE="$id">!. - '</TD></TR>'. - '<TR><TD ALIGN="right">Title</TD><TD>'. - qq!<INPUT TYPE="text" NAME="title" VALUE="$title">!. - '</TD></TR>'; - -foreach my $field ($svc_external->virtual_fields) { - if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { - # If the flag is X, it won't even show up in $svc_acct->virtual_fields. - print $svc_external->pvf($field)->widget('HTML', 'edit', - $svc_external->getfield($field)); - } -} - -%> </TABLE><BR><INPUT TYPE="submit" VALUE="Submit"> </FORM> diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi index 2b9d35ad1..ef08ffc16 100755 --- a/httemplate/edit/svc_forward.cgi +++ b/httemplate/edit/svc_forward.cgi @@ -1,129 +1,125 @@ <!-- mason kludge --> -<% - -my $conf = new FS::Conf; - -my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward); -if ( $cgi->param('error') ) { - $svc_forward = new FS::svc_forward ( { - map { $_, scalar($cgi->param($_)) } fields('svc_forward') - } ); - $svcnum = $svc_forward->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; -} else { - - my($query) = $cgi->keywords; - - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) - or die "Unknown (svc_forward) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { #adding - - $svc_forward = new FS::svc_forward({}); - - foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - $svcnum=''; - - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - $svc_forward->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } - } - -} -my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; - -my %email; - -#starting with those currently attached -foreach my $method (qw( srcsvc_acct dstsvc_acct )) { - my $svc_acct = $svc_forward->$method(); - $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; -} - -if ($pkgnum) { - - #find all possible user svcnums (and emails) - - #and including the rest for this customer - my($u_part_svc,@u_acct_svcparts); - foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { - push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); - } - - my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - my($custnum)=$cust_pkg->getfield('custnum'); - my($i_cust_pkg); - foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { - my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); - my($acct_svcpart); - foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding - #record(s) in cust_svc ( for this - #pkgnum ! ) - foreach my $i_cust_svc ( - qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, - 'svcpart' => $acct_svcpart } ) - ) { - my $svc_acct = - qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); - $email{$svc_acct->svcnum} = $svc_acct->email; - } - } - } - -} elsif ( $action eq 'Add' ) { - die "\$action eq Add, but \$pkgnum is null!\n"; -} - -my($srcsvc,$dstsvc,$dst)=( - $svc_forward->srcsvc, - $svc_forward->dstsvc, - $svc_forward->dst, -); -my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; - -#display - -%> - -<%= header("Mail Forward $action") %> - -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> +% +% +%my $conf = new FS::Conf; +% +%my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward); +%if ( $cgi->param('error') ) { +% $svc_forward = new FS::svc_forward ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_forward') +% } ); +% $svcnum = $svc_forward->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_forward = new FS::svc_forward({}); +% +% $svcnum=''; +% +% $svc_forward->set_default_and_fixed; +% +%} else { #editing +% +% my($query) = $cgi->keywords; +% +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_forward) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} +%my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; +% +%my %email; +% +%#starting with those currently attached +%foreach my $method (qw( srcsvc_acct dstsvc_acct )) { +% my $svc_acct = $svc_forward->$method(); +% $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; +%} +% +%if ($pkgnum) { +% +% #find all possible user svcnums (and emails) +% +% #and including the rest for this customer +% my($u_part_svc,@u_acct_svcparts); +% foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { +% push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% foreach my $i_cust_svc ( +% qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, +% 'svcpart' => $acct_svcpart } ) +% ) { +% my $svc_acct = +% qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); +% $email{$svc_acct->svcnum} = $svc_acct->email; +% } +% } +% } +% +%} elsif ( $action eq 'Add' ) { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +%my($srcsvc,$dstsvc,$dst)=( +% $svc_forward->srcsvc, +% $svc_forward->dstsvc, +% $svc_forward->dst, +%); +%my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; +% +%#display +% +% + + +<% include("/elements/header.html","Mail Forward $action") %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> <BR><BR> -<% } %> +% } + -Service #<%= $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> -Service: <B><%= $part_svc->svc %></B><BR><BR> +Service #<% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> +Service: <B><% $part_svc->svc %></B><BR><BR> <FORM ACTION="process/svc_forward.cgi" METHOD="POST"> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> -<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> <SCRIPT TYPE="text/javascript"> function srcchanged(what) { @@ -146,29 +142,36 @@ function dstchanged(what) { } </SCRIPT> -<%= ntable("#cccccc",2) %> +<% ntable("#cccccc",2) %> <TR><TD ALIGN="right">Email to</TD> <TD><SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)"> -<% foreach $_ (keys %email) { %> - <OPTION<%= $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION> -<% } %> -<% if ( $svc_forward->dbdef_table->column('src') ) { %> - <OPTION <%= $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> -<% } %> +% foreach $_ (keys %email) { + + <OPTION<% $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION> +% } +% if ( $svc_forward->dbdef_table->column('src') ) { + + <OPTION <% $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> +% } + </SELECT> -<% if ( $svc_forward->dbdef_table->column('src') ) { %> -<INPUT TYPE="text" NAME="src" VALUE="<%= $src %>" <%= ( $src || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> -<% } %> +% if ( $svc_forward->dbdef_table->column('src') ) { + +<INPUT TYPE="text" NAME="src" VALUE="<% $src %>" <% ( $src || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> +% } + </TD></TR> <TR><TD ALIGN="right">Forwards to</TD> <TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)"> -<% foreach $_ (keys %email) { %> - <OPTION<%= $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION> -<% } %> -<OPTION <%= $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> +% foreach $_ (keys %email) { + + <OPTION<% $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION> +% } + +<OPTION <% $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> </SELECT> -<INPUT TYPE="text" NAME="dst" VALUE="<%= $dst %>" <%= ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> +<INPUT TYPE="text" NAME="dst" VALUE="<% $dst %>" <% ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> </TD></TR> </TABLE> <BR><INPUT TYPE="submit" VALUE="Submit"> diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi new file mode 100644 index 000000000..ca62b6416 --- /dev/null +++ b/httemplate/edit/svc_phone.cgi @@ -0,0 +1,11 @@ +<% include( 'elements/svc_Common.html', + 'name' => 'Phone number', + 'table' => 'svc_phone', + 'fields' => [qw( countrycode phonenum )], #pin + 'labels' => { + 'countrycode' => 'Country code', + 'phonenum' => 'Phone number', + 'pin' => 'PIN', + }, + ) +%> diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi index 3cb752850..4b27752ff 100644 --- a/httemplate/edit/svc_www.cgi +++ b/httemplate/edit/svc_www.cgi @@ -1,222 +1,220 @@ -<!-- mason kludge --> -<% +%my $conf = new FS::Conf; +% +%my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www ); +% +%if ( $cgi->param('error') ) { +% +% $svc_www = new FS::svc_www ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_www') +% } ); +% $svcnum = $svc_www->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_www = new FS::svc_www { svcpart => $svcpart }; +% +% $svcnum=''; +% +% $svc_www->set_default_and_fixed; +% +%} else { #editing +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_www) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +%} +%my $action = $svc_www->svcnum ? 'Edit' : 'Add'; +% +%my( %svc_acct, %arec ); +%if ($pkgnum) { +% +% my @u_acct_svcparts; +% foreach my $svcpart ( +% map { $_->svcpart } qsearch( 'part_svc', { 'svcdb' => 'svc_acct' } ) +% ) { +% next if $conf->exists('svc_www-usersvc_svcpart') +% && ! grep { $svcpart == $_ } +% $conf->config('svc_www-usersvc_svcpart'); +% push @u_acct_svcparts, $svcpart; +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% my($i_cust_svc); +% foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); +% $svc_acct{$svc_acct->getfield('svcnum')}= +% $svc_acct->cust_svc->part_svc->svc. ': '. $svc_acct->email; +% } +% } +% } +% +% +% my($d_part_svc,@d_acct_svcparts); +% foreach $d_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_domain'}) ) { +% push @d_acct_svcparts,$d_part_svc->getfield('svcpart'); +% } +% +% foreach $i_cust_pkg ( qsearch( 'cust_pkg', { 'custnum' => $custnum } ) ) { +% my $cust_pkgnum = $i_cust_pkg->pkgnum; +% +% foreach my $acct_svcpart (@d_acct_svcparts) { +% +% foreach my $i_cust_svc ( +% qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, +% 'svcpart' => $acct_svcpart } ) +% ) { +% my $svc_domain = +% qsearchs( 'svc_domain', { 'svcnum' => $i_cust_svc->svcnum } ); +% +% my $extra_sql = "AND ( rectype = 'A' OR rectype = 'CNAME' )"; +% unless ( $conf->exists('svc_www-enable_subdomains') ) { +% $extra_sql .= " AND ( reczone = '\@' OR reczone = '". +% $svc_domain->domain. ".' )"; +% } +% +% foreach my $domain_rec ( +% qsearch( 'domain_record', +% { +% 'svcnum' => $svc_domain->svcnum, +% }, +% '', +% $extra_sql, +% ) +% ) { +% $arec{$domain_rec->recnum} = $domain_rec->zone; +% } +% +% if ( $conf->exists('svc_www-enable_subdomains') ) { +% $arec{'www.'. $svc_domain->domain} = 'www.'. $svc_domain->domain +% unless qsearchs( 'domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => 'www', +% } ) +% || qsearchs( 'domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => 'www.'.$svc_domain->domain.'.', +% } ); +% } +% +% $arec{'@.'. $svc_domain->domain} = $svc_domain->domain +% unless qsearchs('domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => '@', +% } ) +% || qsearchs('domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => $svc_domain->domain.'.', +% } ); +% +% } +% +% } +% } +% +%} elsif ( $action eq 'Edit' ) { +% +% my($domain_rec) = qsearchs('domain_record', { 'recnum'=>$svc_www->recnum }); +% $arec{$svc_www->recnum} = join '.', $domain_rec->recdata, $domain_rec->reczone; +% +%} else { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +% +%my $p1 = popurl(1); +%print header("Web Hosting $action", ''); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/svc_www.cgi" METHOD=POST>!; +% +%#display +% +% +% +%#svcnum +%print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +%print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>"; +% +%#pkgnum +%print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; +% +%#svcpart +%print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; +% +%my($recnum,$usersvc)=( +% $svc_www->recnum, +% $svc_www->usersvc, +%); +% +%print &ntable("#cccccc",2), +% '<TR><TD ALIGN="right">Zone</TD><TD><SELECT NAME="recnum" SIZE=1>'; +%foreach $_ (keys %arec) { +% print "<OPTION", $_ eq $recnum ? " SELECTED" : "", +% qq! VALUE="$_">$arec{$_}!; +%} +%print "</SELECT></TD></TR>"; +% +%if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F' +% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) { +% print '<TR><TD ALIGN="right">Username</TD><TD><SELECT NAME="usersvc" SIZE=1>'; +% print '<OPTION VALUE="">(none)'; +% foreach $_ (keys %svc_acct) { +% print "<OPTION", ($_ eq $usersvc) ? " SELECTED" : "", +% qq! VALUE="$_">$svc_acct{$_}!; +% } +% print "</SELECT></TD></TR>"; +%} +% +%foreach my $field ($svc_www->virtual_fields) { +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { +% # If the flag is X, it won't even show up in $svc_acct->virtual_fields. +% print $svc_www->pvf($field)->widget('HTML', 'edit', +% $svc_www->getfield($field)); +% } +%} +% +%print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">'; +% +%print <<END; +% +% </FORM> +% </BODY> +%</HTML> +%END +% -my $conf = new FS::Conf; - -my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www ); -if ( $cgi->param('error') ) { - $svc_www = new FS::svc_www ( { - map { $_, scalar($cgi->param($_)) } fields('svc_www') - } ); - $svcnum = $svc_www->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum}) - or die "Unknown (svc_www) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { #adding - - foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; - } - $svc_www = new FS::svc_www { svcpart => $svcpart }; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - $svcnum=''; - - #set fixed and default fields from part_svc - foreach my $part_svc_column ( - grep { $_->columnflag } $part_svc->all_part_svc_column - ) { - $svc_www->setfield( $part_svc_column->columnname, - $part_svc_column->columnvalue, - ); - } - - } -} -my $action = $svc_www->svcnum ? 'Edit' : 'Add'; - -my( %svc_acct, %arec ); -if ($pkgnum) { - - my @u_acct_svcparts; - foreach my $svcpart ( - map { $_->svcpart } qsearch( 'part_svc', { 'svcdb' => 'svc_acct' } ) - ) { - next if $conf->exists('svc_www-usersvc_svcpart') - && ! grep { $svcpart == $_ } - $conf->config('svc_www-usersvc_svcpart'); - push @u_acct_svcparts, $svcpart; - } - - my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - my($custnum)=$cust_pkg->getfield('custnum'); - my($i_cust_pkg); - foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { - my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); - my($acct_svcpart); - foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding - #record(s) in cust_svc ( for this - #pkgnum ! ) - my($i_cust_svc); - foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { - my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); - $svc_acct{$svc_acct->getfield('svcnum')}= - $svc_acct->cust_svc->part_svc->svc. ': '. $svc_acct->email; - } - } - } - - - my($d_part_svc,@d_acct_svcparts); - foreach $d_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_domain'}) ) { - push @d_acct_svcparts,$d_part_svc->getfield('svcpart'); - } - - foreach $i_cust_pkg ( qsearch( 'cust_pkg', { 'custnum' => $custnum } ) ) { - my $cust_pkgnum = $i_cust_pkg->pkgnum; - - foreach my $acct_svcpart (@d_acct_svcparts) { - - foreach my $i_cust_svc ( - qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, - 'svcpart' => $acct_svcpart } ) - ) { - my $svc_domain = - qsearchs( 'svc_domain', { 'svcnum' => $i_cust_svc->svcnum } ); - - my $extra_sql = "AND ( rectype = 'A' OR rectype = 'CNAME' )"; - unless ( $conf->exists('svc_www-enable_subdomains') ) { - $extra_sql .= " AND ( reczone = '\@' OR reczone = '". - $svc_domain->domain. ".' )"; - } - - foreach my $domain_rec ( - qsearch( 'domain_record', - { - 'svcnum' => $svc_domain->svcnum, - }, - '', - $extra_sql, - ) - ) { - $arec{$domain_rec->recnum} = $domain_rec->zone; - } - - if ( $conf->exists('svc_www-enable_subdomains') ) { - $arec{'www.'. $svc_domain->domain} = 'www.'. $svc_domain->domain - unless qsearchs( 'domain_record', { - svcnum => $svc_domain->svcnum, - reczone => 'www', - } ) - || qsearchs( 'domain_record', { - svcnum => $svc_domain->svcnum, - reczone => 'www.'.$svc_domain->domain.'.', - } ); - } - - $arec{'@.'. $svc_domain->domain} = $svc_domain->domain - unless qsearchs('domain_record', { - svcnum => $svc_domain->svcnum, - reczone => '@', - } ) - || qsearchs('domain_record', { - svcnum => $svc_domain->svcnum, - reczone => $svc_domain->domain.'.', - } ); - - } - - } - } - -} elsif ( $action eq 'Edit' ) { - - my($domain_rec) = qsearchs('domain_record', { 'recnum'=>$svc_www->recnum }); - $arec{$svc_www->recnum} = join '.', $domain_rec->recdata, $domain_rec->reczone; - -} else { - die "\$action eq Add, but \$pkgnum is null!\n"; -} - - -my $p1 = popurl(1); -print header("Web Hosting $action", ''); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/svc_www.cgi" METHOD=POST>!; - -#display - - - -#svcnum -print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; -print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>"; - -#pkgnum -print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; - -#svcpart -print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; - -my($recnum,$usersvc)=( - $svc_www->recnum, - $svc_www->usersvc, -); - -print &ntable("#cccccc",2), - '<TR><TD ALIGN="right">Zone</TD><TD><SELECT NAME="recnum" SIZE=1>'; -foreach $_ (keys %arec) { - print "<OPTION", $_ eq $recnum ? " SELECTED" : "", - qq! VALUE="$_">$arec{$_}!; -} -print "</SELECT></TD></TR>"; - -print '<TR><TD ALIGN="right">Username</TD><TD><SELECT NAME="usersvc" SIZE=1>'; -print '<OPTION VALUE="">(none)'; -foreach $_ (keys %svc_acct) { - print "<OPTION", ($_ eq $usersvc) ? " SELECTED" : "", - qq! VALUE="$_">$svc_acct{$_}!; -} -print "</SELECT></TD></TR>"; - -foreach my $field ($svc_www->virtual_fields) { - if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { - # If the flag is X, it won't even show up in $svc_acct->virtual_fields. - print $svc_www->pvf($field)->widget('HTML', 'edit', - $svc_www->getfield($field)); - } -} - -print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">'; - -print <<END; - - </FORM> - </BODY> -</HTML> -END -%> diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js index e9d6a222e..0dbde793d 100644 --- a/httemplate/elements/calendar-en.js +++ b/httemplate/elements/calendar-en.js @@ -1,7 +1,7 @@ // ** I18N // Calendar EN language -// Author: Mihai Bazon, <mishoo@infoiasi.ro> +// Author: Mihai Bazon, <mihai_bazon@yahoo.com> // Encoding: any // Distributed under the same terms as the calendar itself. @@ -43,6 +43,10 @@ Calendar._SDN = new Array "Sat", "Sun"); +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + // full month names Calendar._MN = new Array ("January", @@ -79,8 +83,8 @@ Calendar._TT["INFO"] = "About the calendar"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + -"(c) dynarch.com 2002-2003\n" + // don't translate this this ;-) -"For latest version visit: http://dynarch.com/mishoo/calendar.epl\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js index 55e22b933..b27d9bed0 100644 --- a/httemplate/elements/calendar-setup.js +++ b/httemplate/elements/calendar-setup.js @@ -19,7 +19,7 @@ * than modifying calendar.js itself). */ -// $Id: calendar-setup.js,v 1.4 2004-09-22 11:04:41 ivan Exp $ +// $Id: calendar-setup.js,v 1.5 2006-02-09 07:18:08 ivan Exp $ /** * This function "patches" an input field (or other element) to use a calendar @@ -71,7 +71,8 @@ Calendar.setup = function (params) { param_default("singleClick", true); param_default("disableFunc", null); param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined - param_default("firstDay", 0); // defaults to "Sunday" first + param_default("dateText", null); + param_default("firstDay", null); param_default("align", "Br"); param_default("range", [1900, 2999]); param_default("weekNumbers", true); @@ -88,6 +89,7 @@ Calendar.setup = function (params) { param_default("position", null); param_default("cache", false); param_default("showOthers", false); + param_default("multiple", null); var tmp = ["inputField", "displayArea", "button"]; for (var i in tmp) { @@ -95,7 +97,7 @@ Calendar.setup = function (params) { params[tmp[i]] = document.getElementById(params[tmp[i]]); } } - if (!(params.flat || params.inputField || params.displayArea || params.button)) { + if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); return false; } @@ -103,13 +105,6 @@ Calendar.setup = function (params) { function onSelect(cal) { var p = cal.params; var update = (cal.dateClicked || p.electric); - if (update && p.flat) { - if (typeof p.flatCallback == "function") - p.flatCallback(cal); - else - alert("No flatCallback given -- doing nothing."); - return false; - } if (update && p.inputField) { p.inputField.value = cal.date.print(p.ifFormat); if (typeof p.inputField.onchange == "function") @@ -117,10 +112,14 @@ Calendar.setup = function (params) { } if (update && p.displayArea) p.displayArea.innerHTML = cal.date.print(p.daFormat); - if (update && p.singleClick && cal.dateClicked) - cal.callCloseHandler(); if (update && typeof p.onUpdate == "function") p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); }; if (params.flat != null) { @@ -131,12 +130,20 @@ Calendar.setup = function (params) { return false; } var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; cal.showsTime = params.showsTime; cal.time24 = (params.timeFormat == "24"); cal.params = params; cal.weekNumbers = params.weekNumbers; cal.setRange(params.range[0], params.range[1]); cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } cal.create(params.flat); cal.show(); return false; @@ -148,6 +155,8 @@ Calendar.setup = function (params) { var dateFmt = params.inputField ? params.ifFormat : params.daFormat; var mustCreate = false; var cal = window.calendar; + if (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); if (!(cal && params.cache)) { window.calendar = cal = new Calendar(params.firstDay, params.date, @@ -162,15 +171,23 @@ Calendar.setup = function (params) { cal.setDate(params.date); cal.hide(); } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } cal.showsOtherMonths = params.showOthers; cal.yearStep = params.step; cal.setRange(params.range[0], params.range[1]); cal.params = params; cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; cal.setDateFormat(dateFmt); if (mustCreate) cal.create(); - cal.parseDate(dateEl.value || dateEl.innerHTML); cal.refresh(); if (!params.position) cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); @@ -178,4 +195,6 @@ Calendar.setup = function (params) { cal.showAt(params.position[0], params.position[1]); return false; }; + + return cal; }; diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css index 6001cfaa4..6f37b7dcd 100644 --- a/httemplate/elements/calendar-win2k-2.css +++ b/httemplate/elements/calendar-win2k-2.css @@ -206,6 +206,7 @@ background: #e4d8e0; font-size: 90%; padding: 1px; + z-index: 100; } .calendar .combo .label, diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js index ec18d80ce..f5c74f608 100644 --- a/httemplate/elements/calendar.js +++ b/httemplate/elements/calendar.js @@ -1,16 +1,18 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ - * ------------------------------------------------------------------ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- * - * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze" + * The DHTML Calendar, version 1.0 "It is happening again" * * Details and latest version at: - * http://dynarch.com/mishoo/calendar.epl + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. * * This script is distributed under the GNU Lesser General Public License. * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html */ -// $Id: calendar.js,v 1.4 2004-09-22 11:04:41 ivan Exp $ +// $Id: calendar.js,v 1.5 2006-02-09 07:18:08 ivan Exp $ /** The Calendar object constructor. */ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { @@ -18,6 +20,8 @@ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { this.activeDiv = null; this.currentDateEl = null; this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; this.timeout = null; this.onSelected = onSelected || null; this.onClose = onClose || null; @@ -29,13 +33,15 @@ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; this.isPopup = true; this.weekNumbers = true; - this.firstDayOfWeek = firstDayOfWeek; // 0 for Sunday, 1 for Monday, etc. + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. this.showsOtherMonths = false; this.dateStr = dateStr; this.ar_days = null; this.showsTime = false; this.time24 = true; this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; // HTML elements this.table = null; this.element = null; @@ -146,20 +152,19 @@ Calendar.addClass = function(el, className) { el.className += " " + className; }; +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. Calendar.getElement = function(ev) { - if (Calendar.is_ie) { - return window.event.srcElement; - } else { - return ev.currentTarget; - } + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; }; Calendar.getTargetElement = function(ev) { - if (Calendar.is_ie) { - return window.event.srcElement; - } else { - return ev.target; - } + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; }; Calendar.stopEvent = function(ev) { @@ -295,7 +300,7 @@ Calendar.showYearsCombo = function (fwd) { var show = false; for (var i = 12; i > 0; --i) { if (Y >= cal.minYear && Y <= cal.maxYear) { - yr.firstChild.data = Y; + yr.innerHTML = Y; yr.year = Y; yr.style.display = "block"; show = true; @@ -416,7 +421,7 @@ Calendar.tableMouseOver = function (ev) { } else if ( ++i >= range.length ) i = 0; var newval = range[i]; - el.firstChild.data = newval; + el.innerHTML = newval; cal.onUpdateTime(); } @@ -504,7 +509,7 @@ Calendar.dayMouseDown = function(ev) { Calendar._C = cal; if (el.navtype != 300) with (Calendar) { if (el.navtype == 50) { - el._current = el.firstChild.data; + el._current = el.innerHTML; addEvent(document, "mousemove", tableMouseOver); } else addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); @@ -541,7 +546,7 @@ Calendar.dayMouseOver = function(ev) { if (el.ttip.substr(0, 1) == "_") { el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); } - el.calendar.tooltips.firstChild.data = el.ttip; + el.calendar.tooltips.innerHTML = el.ttip; } if (el.navtype != 300) { Calendar.addClass(el, "hilite"); @@ -555,14 +560,13 @@ Calendar.dayMouseOver = function(ev) { Calendar.dayMouseOut = function(ev) { with (Calendar) { var el = getElement(ev); - if (isRelated(el, ev) || _C || el.disabled) { + if (isRelated(el, ev) || _C || el.disabled) return false; - } removeClass(el, "hilite"); - if (el.caldate) { + if (el.caldate) removeClass(el.parentNode, "rowhilite"); - } - el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"]; + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; return stopEvent(ev); } }; @@ -577,17 +581,23 @@ Calendar.cellClick = function(el, ev) { var newdate = false; var date = null; if (typeof el.navtype == "undefined") { - Calendar.removeClass(cal.currentDateEl, "selected"); - Calendar.addClass(el, "selected"); - closing = (cal.currentDateEl == el); - if (!closing) { - cal.currentDateEl = el; + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } } - cal.date = new Date(el.caldate); + cal.date.setDateOnly(el.caldate); date = cal.date; - newdate = true; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; // a date was clicked - if (!(cal.dateClicked = !el.otherMonth)) + if (other_month) cal._init(cal.firstDayOfWeek, date); } else { if (el.navtype == 200) { @@ -595,7 +605,9 @@ Calendar.cellClick = function(el, ev) { cal.callCloseHandler(); return; } - date = (el.navtype == 0) ? new Date() : new Date(cal.date); + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY // unless "today" was clicked, we assume no date was clicked so // the selected handler will know not to close the calenar when // in single-click mode. @@ -622,7 +634,7 @@ Calendar.cellClick = function(el, ev) { text = "Help and about box text is not translated into this language.\n" + "If you know this language and you feel generous please update\n" + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + - "and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n" + + "and send it back to <mihai_bazon@yahoo.com> to get it into the distribution ;-)\n\n" + "Thank you!\n" + "http://dynarch.com/mishoo/calendar.epl\n"; } @@ -659,7 +671,7 @@ Calendar.cellClick = function(el, ev) { return; case 50: var range = el._range; - var current = el.firstChild.data; + var current = el.innerHTML; for (var i = range.length; --i >= 0;) if (range[i] == current) break; @@ -669,15 +681,13 @@ Calendar.cellClick = function(el, ev) { } else if ( ++i >= range.length ) i = 0; var newval = range[i]; - el.firstChild.data = newval; + el.innerHTML = newval; cal.onUpdateTime(); return; case 0: // TODAY will bring us here - if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { - // remember, "date" was previously set to new - // Date() if TODAY was clicked; thus, it - // contains today date. + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { return false; } break; @@ -685,14 +695,15 @@ Calendar.cellClick = function(el, ev) { if (!date.equalsTo(cal.date)) { cal.setDate(date); newdate = true; - } + } else if (el.navtype == 0) + newdate = closing = true; } if (newdate) { - cal.callHandler(); + ev && cal.callHandler(); } if (closing) { Calendar.removeClass(el, "hilite"); - cal.callCloseHandler(); + ev && cal.callCloseHandler(); } }; @@ -749,13 +760,7 @@ Calendar.prototype.create = function (_par) { Calendar._add_evs(cell); cell.calendar = cal; cell.navtype = navtype; - if (text.substr(0, 1) != "&") { - cell.appendChild(document.createTextNode(text)); - } - else { - // FIXME: dirty hack for entities - cell.innerHTML = text; - } + cell.innerHTML = "<div unselectable='on'>" + text + "</div>"; return cell; }; @@ -797,11 +802,10 @@ Calendar.prototype.create = function (_par) { if (this.weekNumbers) { cell = Calendar.createElement("td", row); cell.className = "name wn"; - cell.appendChild(document.createTextNode(Calendar._TT["WK"])); + cell.innerHTML = Calendar._TT["WK"]; } for (var i = 7; i > 0; --i) { cell = Calendar.createElement("td", row); - cell.appendChild(document.createTextNode("")); if (!i) { cell.navtype = 100; cell.calendar = this; @@ -818,11 +822,9 @@ Calendar.prototype.create = function (_par) { row = Calendar.createElement("tr", tbody); if (this.weekNumbers) { cell = Calendar.createElement("td", row); - cell.appendChild(document.createTextNode("")); } for (var j = 7; j > 0; --j) { cell = Calendar.createElement("td", row); - cell.appendChild(document.createTextNode("")); cell.calendar = this; Calendar._add_evs(cell); } @@ -845,7 +847,7 @@ Calendar.prototype.create = function (_par) { function makeTimePart(className, init, range_start, range_end) { var part = Calendar.createElement("span", cell); part.className = className; - part.appendChild(document.createTextNode(init)); + part.innerHTML = init; part.calendar = cal; part.ttip = Calendar._TT["TIME_PART"]; part.navtype = 50; @@ -870,7 +872,7 @@ Calendar.prototype.create = function (_par) { if (t12 && pm) hrs -= 12; var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); var span = Calendar.createElement("span", cell); - span.appendChild(document.createTextNode(":")); + span.innerHTML = ":"; span.className = "colon"; var M = makeTimePart("minute", mins, 0, 59); var AP = null; @@ -883,30 +885,32 @@ Calendar.prototype.create = function (_par) { cell.innerHTML = " "; cal.onSetTime = function() { - var hrs = this.date.getHours(); - var mins = this.date.getMinutes(); - var pm = (hrs > 12); - if (pm && t12) hrs -= 12; - H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs; - M.firstChild.data = (mins < 10) ? ("0" + mins) : mins; - if (t12) - AP.firstChild.data = pm ? "pm" : "am"; + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; }; cal.onUpdateTime = function() { var date = this.date; - var h = parseInt(H.firstChild.data, 10); + var h = parseInt(H.innerHTML, 10); if (t12) { - if (/pm/i.test(AP.firstChild.data) && h < 12) + if (/pm/i.test(AP.innerHTML) && h < 12) h += 12; - else if (/am/i.test(AP.firstChild.data) && h == 12) + else if (/am/i.test(AP.innerHTML) && h == 12) h = 0; } var d = date.getDate(); var m = date.getMonth(); var y = date.getFullYear(); date.setHours(h); - date.setMinutes(parseInt(M.firstChild.data, 10)); + date.setMinutes(parseInt(M.innerHTML, 10)); date.setFullYear(y); date.setMonth(m); date.setDate(d); @@ -938,7 +942,7 @@ Calendar.prototype.create = function (_par) { var mn = Calendar.createElement("div"); mn.className = Calendar.is_ie ? "label-IEfix" : "label"; mn.month = i; - mn.appendChild(document.createTextNode(Calendar._SMN[i])); + mn.innerHTML = Calendar._SMN[i]; div.appendChild(mn); } @@ -948,7 +952,6 @@ Calendar.prototype.create = function (_par) { for (i = 12; i > 0; --i) { var yr = Calendar.createElement("div"); yr.className = Calendar.is_ie ? "label-IEfix" : "label"; - yr.appendChild(document.createTextNode("")); div.appendChild(yr); } @@ -958,14 +961,14 @@ Calendar.prototype.create = function (_par) { /** keyboard navigation, only for popup calendars */ Calendar._keyEvent = function(ev) { - if (!window.calendar) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) return false; - } (Calendar.is_ie) && (ev = window.event); - var cal = window.calendar; - var act = (Calendar.is_ie || ev.type == "keypress"); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; if (ev.ctrlKey) { - switch (ev.keyCode) { + switch (K) { case 37: // KEY left act && Calendar.cellClick(cal._nav_pm); break; @@ -981,7 +984,7 @@ Calendar._keyEvent = function(ev) { default: return false; } - } else switch (ev.keyCode) { + } else switch (K) { case 32: // KEY space (now) Calendar.cellClick(cal._nav_now); break; @@ -993,48 +996,78 @@ Calendar._keyEvent = function(ev) { case 39: // KEY right case 40: // KEY down if (act) { - var date = cal.date.getDate() - 1; - var el = cal.currentDateEl; - var ne = null; - var prev = (ev.keyCode == 37) || (ev.keyCode == 38); - switch (ev.keyCode) { - case 37: // KEY left - (--date >= 0) && (ne = cal.ar_days[date]); - break; - case 38: // KEY up - date -= 7; - (date >= 0) && (ne = cal.ar_days[date]); - break; - case 39: // KEY right - (++date < cal.ar_days.length) && (ne = cal.ar_days[date]); - break; - case 40: // KEY down - date += 7; - (date < cal.ar_days.length) && (ne = cal.ar_days[date]); + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } break; } - if (!ne) { - if (prev) { - Calendar.cellClick(cal._nav_pm); - } else { - Calendar.cellClick(cal._nav_nm); - } - date = (prev) ? cal.date.getMonthDays() : 1; - el = cal.currentDateEl; - ne = cal.ar_days[date - 1]; + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); } - Calendar.removeClass(el, "selected"); - Calendar.addClass(ne, "selected"); - cal.date = new Date(ne.caldate); - cal.callHandler(); - cal.currentDateEl = ne; } break; case 13: // KEY enter - if (act) { - cal.callHandler(); - cal.hide(); - } + if (act) + Calendar.cellClick(cal.currentDateEl, ev); break; default: return false; @@ -1046,7 +1079,10 @@ Calendar._keyEvent = function(ev) { * (RE)Initializes the calendar to the given date and firstDayOfWeek */ Calendar.prototype._init = function (firstDayOfWeek, date) { - var today = new Date(); + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); this.table.style.visibility = "hidden"; var year = date.getFullYear(); if (year < this.minYear) { @@ -1074,21 +1110,24 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { var row = this.tbody.firstChild; var MN = Calendar._SMN[month]; - var ar_days = new Array(); + var ar_days = this.ar_days = new Array(); var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; for (var i = 0; i < 6; ++i, row = row.nextSibling) { var cell = row.firstChild; if (this.weekNumbers) { cell.className = "day wn"; - cell.firstChild.data = date.getWeekNumber(); + cell.innerHTML = date.getWeekNumber(); cell = cell.nextSibling; } row.className = "daysrow"; - var hasdays = false; - for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(date.getDate() + 1)) { - var iday = date.getDate(); + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); var wday = date.getDay(); cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; var current_month = (date.getMonth() == month); if (!current_month) { if (this.showsOtherMonths) { @@ -1105,9 +1144,16 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { hasdays = true; } cell.disabled = false; - cell.firstChild.data = iday; - if (typeof this.getDateStatus == "function") { + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } if (status === true) { cell.className += " disabled"; cell.disabled = true; @@ -1118,33 +1164,66 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { } } if (!cell.disabled) { - ar_days[ar_days.length] = cell; cell.caldate = new Date(date); cell.ttip = "_"; - if (current_month && iday == mday) { + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { cell.className += " selected"; this.currentDateEl = cell; } - if (date.getFullYear() == today.getFullYear() && - date.getMonth() == today.getMonth() && - iday == today.getDate()) { + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { cell.className += " today"; cell.ttip += Calendar._TT["PART_TODAY"]; } - if (weekend.indexOf(wday.toString()) != -1) { + if (weekend.indexOf(wday.toString()) != -1) cell.className += cell.otherMonth ? " oweekend" : " weekend"; - } } } if (!(hasdays || this.showsOtherMonths)) row.className = "emptyrow"; } - this.ar_days = ar_days; - this.title.firstChild.data = Calendar._MN[month] + ", " + year; + this.title.innerHTML = Calendar._MN[month] + ", " + year; this.onSetTime(); this.table.style.visibility = "visible"; + this._initMultipleDates(); // PROFILE - // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms"; + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; }; /** @@ -1209,7 +1288,7 @@ Calendar.prototype.destroy = function () { var el = this.element.parentNode; el.removeChild(this.element); Calendar._C = null; - window.calendar = null; + window._dynarch_popupCalendar = null; }; /** @@ -1226,14 +1305,15 @@ Calendar.prototype.reparent = function (new_parent) { // document, if the calendar is shown. If the click was outside the open // calendar this function closes it. Calendar._checkCalendar = function(ev) { - if (!window.calendar) { + var calendar = window._dynarch_popupCalendar; + if (!calendar) { return false; } var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); for (; el != null && el != calendar.element; el = el.parentNode); if (el == null) { // calls closeHandler which should hide the calendar. - window.calendar.callCloseHandler(); + window._dynarch_popupCalendar.callCloseHandler(); return Calendar.stopEvent(ev); } }; @@ -1254,7 +1334,7 @@ Calendar.prototype.show = function () { this.element.style.display = "block"; this.hidden = false; if (this.isPopup) { - window.calendar = this; + window._dynarch_popupCalendar = this; Calendar.addEvent(document, "keydown", Calendar._keyEvent); Calendar.addEvent(document, "keypress", Calendar._keyEvent); Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); @@ -1344,8 +1424,8 @@ Calendar.prototype.showAtElement = function (el, opts) { case "L": p.x -= w; break; case "R": p.x += el.offsetWidth; break; case "C": p.x += (el.offsetWidth - w) / 2; break; - case "r": p.x += el.offsetWidth - w; break; - case "l": break; // already there + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there } p.width = w; p.height = h + 40; @@ -1373,14 +1453,140 @@ Calendar.prototype.setTtDateFormat = function (str) { * Tries to identify the date represented in a string. If successful it also * calls this.setDate which moves the calendar to the given date. */ -Calendar.prototype.parseDate = function (str, fmt) { +Calendar.prototype.parseDate = function(str, fmt) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = this.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +Date.parseDate = function(str, fmt) { + var today = new Date(); var y = 0; var m = -1; var d = 0; var a = str.split(/\W+/); - if (!fmt) { - fmt = this.dateFormat; - } var b = fmt.match(/%./g); var i = 0, j = 0; var hr = 0; @@ -1422,6 +1628,8 @@ Calendar.prototype.parseDate = function (str, fmt) { case "%p": if (/pm/i.test(a[i]) && hr < 12) hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; break; case "%M": @@ -1429,10 +1637,13 @@ Calendar.prototype.parseDate = function (str, fmt) { break; } } - if (y != 0 && m != -1 && d != 0) { - this.setDate(new Date(y, m, d, hr, min, 0)); - return; - } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); y = 0; m = -1; d = 0; for (i = 0; i < a.length; ++i) { if (a[i].search(/[a-zA-Z]+/) != -1) { @@ -1455,142 +1666,13 @@ Calendar.prototype.parseDate = function (str, fmt) { d = a[i]; } } - if (y == 0) { - var today = new Date(); + if (y == 0) y = today.getFullYear(); - } - if (m != -1 && d != 0) { - this.setDate(new Date(y, m, d, hr, min, 0)); - } -}; - -Calendar.prototype.hideShowCovered = function () { - var self = this; - Calendar.continuation_for_the_fucking_khtml_browser = function() { - function getVisib(obj){ - var value = obj.style.visibility; - if (!value) { - if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C - if (!Calendar.is_khtml) - value = document.defaultView. - getComputedStyle(obj, "").getPropertyValue("visibility"); - else - value = ''; - } else if (obj.currentStyle) { // IE - value = obj.currentStyle.visibility; - } else - value = ''; - } - return value; - }; - - var tags = new Array("applet", "iframe", "select"); - var el = self.element; - - var p = Calendar.getAbsolutePos(el); - var EX1 = p.x; - var EX2 = el.offsetWidth + EX1; - var EY1 = p.y; - var EY2 = el.offsetHeight + EY1; - - for (var k = tags.length; k > 0; ) { - var ar = document.getElementsByTagName(tags[--k]); - var cc = null; - - for (var i = ar.length; i > 0;) { - cc = ar[--i]; - - p = Calendar.getAbsolutePos(cc); - var CX1 = p.x; - var CX2 = cc.offsetWidth + CX1; - var CY1 = p.y; - var CY2 = cc.offsetHeight + CY1; - - if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = cc.__msh_save_visibility; - } else { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = "hidden"; - } - } - } - }; - if (Calendar.is_khtml) - setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); - else - Calendar.continuation_for_the_fucking_khtml_browser(); -}; - -/** Internal function; it displays the bar with the names of the weekday. */ -Calendar.prototype._displayWeekdays = function () { - var fdow = this.firstDayOfWeek; - var cell = this.firstdayname; - var weekend = Calendar._TT["WEEKEND"]; - for (var i = 0; i < 7; ++i) { - cell.className = "day name"; - var realday = (i + fdow) % 7; - if (i) { - cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); - cell.navtype = 100; - cell.calendar = this; - cell.fdow = realday; - Calendar._add_evs(cell); - } - if (weekend.indexOf(realday.toString()) != -1) { - Calendar.addClass(cell, "weekend"); - } - cell.firstChild.data = Calendar._SDN[(i + fdow) % 7]; - cell = cell.nextSibling; - } -}; - -/** Internal function. Hides all combo boxes that might be displayed. */ -Calendar.prototype._hideCombos = function () { - this.monthsCombo.style.display = "none"; - this.yearsCombo.style.display = "none"; -}; - -/** Internal function. Starts dragging the element. */ -Calendar.prototype._dragStart = function (ev) { - if (this.dragging) { - return; - } - this.dragging = true; - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posY = ev.clientY + window.scrollY; - posX = ev.clientX + window.scrollX; - } - var st = this.element.style; - this.xOffs = posX - parseInt(st.left); - this.yOffs = posY - parseInt(st.top); - with (Calendar) { - addEvent(document, "mousemove", calDragIt); - addEvent(document, "mouseup", calDragEnd); - } + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; }; -// BEGIN: DATE OBJECT PATCHES - -/** Adds the number of days array to the Date object. */ -Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); - -/** Constants used for time computations */ -Date.SECOND = 1000 /* milliseconds */; -Date.MINUTE = 60 * Date.SECOND; -Date.HOUR = 60 * Date.MINUTE; -Date.DAY = 24 * Date.HOUR; -Date.WEEK = 7 * Date.DAY; - /** Returns the number of days in the current month */ Date.prototype.getMonthDays = function(month) { var year = this.getFullYear(); @@ -1623,7 +1705,7 @@ Date.prototype.getWeekNumber = function() { return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; }; -/** Checks dates equality (ignores time) */ +/** Checks date and time equality */ Date.prototype.equalsTo = function(date) { return ((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && @@ -1632,6 +1714,15 @@ Date.prototype.equalsTo = function(date) { (this.getMinutes() == date.getMinutes())); }; +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + /** Prints the date in a string according to the given format. */ Date.prototype.print = function (str) { var m = this.getMonth(); @@ -1684,7 +1775,7 @@ Date.prototype.print = function (str) { s["%%"] = "%"; // a literal '%' character var re = /%./g; - if (!Calendar.is_ie5) + if (!Calendar.is_ie5 && !Calendar.is_khtml) return str.replace(re, function (par) { return s[par] || par; }); var a = str.match(re); @@ -1712,4 +1803,4 @@ Date.prototype.setFullYear = function(y) { // global object that remembers the calendar -window.calendar = null; +window._dynarch_popupCalendar = null; diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js index 6a8e326af..4fe03f1ea 100644 --- a/httemplate/elements/calendar_stripped.js +++ b/httemplate/elements/calendar_stripped.js @@ -1,12 +1,14 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ - * ------------------------------------------------------------------ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- * - * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze" + * The DHTML Calendar, version 1.0 "It is happening again" * * Details and latest version at: - * http://dynarch.com/mishoo/calendar.epl + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. * * This script is distributed under the GNU Lesser General Public License. * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html */ - Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=firstDayOfWeek;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.firstChild.data;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date=new Date(el.caldate);date=cal.date;newdate=true;if(!(cal.dateClicked=!el.otherMonth))cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setFirstDayOfWeek(el.fdow);return;case 50:var range=el._range;var current=el.firstChild.data;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}}if(newdate){cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;if(text.substr(0,1)!="&"){cell.appendChild(document.createTextNode(text));}else{cell.innerHTML=text;}return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.appendChild(document.createTextNode(Calendar._TT["WK"]));}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.appendChild(document.createTextNode(init));part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.appendChild(document.createTextNode(":"));span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var hrs=this.date.getHours();var mins=this.date.getMinutes();var pm=(hrs>12);if(pm&&t12)hrs-=12;H.firstChild.data=(hrs<10)?("0"+hrs):hrs;M.firstChild.data=(mins<10)?("0"+mins):mins;if(t12)AP.firstChild.data=pm?"pm":"am";};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.firstChild.data,10);if(t12){if(/pm/i.test(AP.firstChild.data)&&h<12)h+=12;else if(/am/i.test(AP.firstChild.data)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.firstChild.data,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.appendChild(document.createTextNode(Calendar._SMN[i]));div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;case 40:date+=7;(date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;}if(!ne){if(prev){Calendar.cellClick(cal._nav_pm);}else{Calendar.cellClick(cal._nav_nm);}date=(prev)?cal.date.getMonthDays():1;el=cal.currentDateEl;ne=cal.ar_days[date-1];}Calendar.removeClass(el,"selected");Calendar.addClass(ne,"selected");cal.date=new Date(ne.caldate);cal.callHandler();cal.currentDateEl=ne;}break;case 13:if(act){cal.callHandler();cal.hide();}break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date();this.table.style.visibility="hidden";var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false;for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(date.getDate()+1)){var iday=date.getDate();var wday=date.getDay();cell.className="day";var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){var status=this.getDateStatus(date,year,month,iday);if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=new Date(date);cell.ttip="_";if(current_month&&iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==today.getFullYear()&&date.getMonth()==today.getMonth()&&iday==today.getDate()){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1){cell.className+=cell.otherMonth?" oweekend":" weekend";}}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window.calendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=fmt.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(!a[i])continue;switch(b[i]){case "%d":case "%e":d=parseInt(a[i],10);break;case "%m":m=parseInt(a[i],10)-1;break;case "%Y":case "%y":y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;break;case "%M":min=parseInt(a[i],10);break;}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));return;}y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));}};Calendar.prototype.hideShowCovered=function(){var self=this;Calendar.continuation_for_the_fucking_khtml_browser=function(){function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=self.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(self.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype._displayWeekdays=function(){var fdow=this.firstDayOfWeek;var cell=this.firstdayname;var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<7;++i){cell.className="day name";var realday=(i+fdow)%7;if(i){cell.ttip=Calendar._TT["DAY_FIRST"].replace("%s",Calendar._DN[realday]);cell.navtype=100;cell.calendar=this;cell.fdow=realday;Calendar._add_evs(cell);}if(weekend.indexOf(realday.toString())!=-1){Calendar.addClass(cell,"weekend");}cell.firstChild.data=Calendar._SDN[(i+fdow)%7];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i<a.length;i++){var tmp=s[a[i]];if(tmp){re=new RegExp(a[i],'g');str=str.replace(re,tmp);}}return str;};Date.prototype.__msh_oldSetFullYear=Date.prototype.setFullYear;Date.prototype.setFullYear=function(y){var d=new Date(this);d.__msh_oldSetFullYear(y);if(d.getMonth()!=this.getMonth())this.setDate(28);this.__msh_oldSetFullYear(y);};window.calendar=null;
\ No newline at end of file + Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mihai_bazon@yahoo.com> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setFirstDayOfWeek(el.fdow);return;case 50:var range=el._range;var current=el.innerHTML;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="<div unselectable='on'>"+text+"</div>";return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.innerHTML=init;part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.innerHTML=":";span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.innerHTML=Calendar._SMN[i];div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++y<cal.ar_days.length)ne=cal.ar_days[y][x];else{nextMonth();setVars();}break;}break;}if(ne){if(!ne.disabled)Calendar.cellClick(ne);else if(prev)prevMonth();else nextMonth();}}break;case 13:if(act)Calendar.cellClick(cal.currentDateEl,ev);break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date(),TY=today.getFullYear(),TM=today.getMonth(),TD=today.getDate();this.table.style.visibility="hidden";var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&¤t_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window._dynarch_popupCalendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window._dynarch_popupCalendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=this.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(this.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};Calendar.prototype._displayWeekdays=function(){var fdow=this.firstDayOfWeek;var cell=this.firstdayname;var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<7;++i){cell.className="day name";var realday=(i+fdow)%7;if(i){cell.ttip=Calendar._TT["DAY_FIRST"].replace("%s",Calendar._DN[realday]);cell.navtype=100;cell.calendar=this;cell.fdow=realday;Calendar._add_evs(cell);}if(weekend.indexOf(realday.toString())!=-1){Calendar.addClass(cell,"weekend");}cell.innerHTML=Calendar._SDN[(i+fdow)%7];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.parseDate=function(str,fmt){var today=new Date();var y=0;var m=-1;var d=0;var a=str.split(/\W+/);var b=fmt.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(!a[i])continue;switch(b[i]){case "%d":case "%e":d=parseInt(a[i],10);break;case "%m":m=parseInt(a[i],10)-1;break;case "%Y":case "%y":y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i<a.length;i++){var tmp=s[a[i]];if(tmp){re=new RegExp(a[i],'g');str=str.replace(re,tmp);}}return str;};Date.prototype.__msh_oldSetFullYear=Date.prototype.setFullYear;Date.prototype.setFullYear=function(y){var d=new Date(this);d.__msh_oldSetFullYear(y);if(d.getMonth()!=this.getMonth())this.setDate(28);this.__msh_oldSetFullYear(y);};window._dynarch_popupCalendar=null;
\ No newline at end of file diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html new file mode 100644 index 000000000..0a92e4548 --- /dev/null +++ b/httemplate/elements/checkboxes-table-name.html @@ -0,0 +1,85 @@ +% +% +% ## +% # required +% ## +% # 'link_table' => 'table_name', +% # +% # 'name_col' => 'name_column', +% # #or +% # 'name_callback' => sub { }, +% # +% # 'names_list' => [ 'value', 'other value' ], +% # +% ## +% # 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 +% ## +% # 'num_col' => 'col_name' #if column name is different in link_table than +% # #source_table +% # 'link_static' => { 'column' => 'value' }, +% +% my( %opt ) = @_; +% +% 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'}; +% } +% +% $source_pkey = $opt{'num_col'} || $source_pkey; +% +% my $link_static = $opt{'link_static'} || {}; +% +% +% foreach my $name ( @{ $opt{'names_list'} } ) { +% +% my $checked; +% if ( $cgi->param('error') ) { +% +% $checked = $cgi->param($opt{'link_table'}. ".$name" ) +% ? 'CHECKED' +% : ''; +% +% } else { +% +% $checked = +% qsearchs( $opt{'link_table'}, { +% $source_pkey => $sourcenum, +% $opt{'name_col'} => $name, +% %$link_static, +% } ) +% ? 'CHECKED' +% : '' +% +% } +% +% + + + <INPUT TYPE="checkbox" NAME="<% $opt{'link_table'}. ".$name" %>" <% $checked %> VALUE="ON"> + + <% $name %> + + <BR> +% } + + diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html new file mode 100644 index 000000000..cdfa58eca --- /dev/null +++ b/httemplate/elements/checkboxes-table.html @@ -0,0 +1,123 @@ +% +% +% ## +% # 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(); +% +% my $checked; +% if ( $cgi->param('error') ) { +% +% $checked = $cgi->param($target_pkey.$targetnum) +% ? 'CHECKED' +% : ''; +% +% } else { +% +% $checked = qsearchs( $opt{'link_table'}, { +% $source_pkey => $sourcenum, +% $target_pkey => $targetnum, +% } ) +% ? 'CHECKED' +% : '' +% +% } +% +% + + + <INPUT TYPE="checkbox" NAME="<% $target_pkey. $targetnum %>" <% $checked %> VALUE="ON"> +% if ( $opt{'target_link'} ) { + + + <A HREF="<% $opt{'target_link'} %><% $targetnum %>"> +% +% +% } +% +<% $targetnum %>: +% if ( $opt{'name_callback'} ) { + + + <% &{ $opt{'name_callback'} }( $target_obj ) %><% $opt{'target_link'} ? '</A>' : '' %> +% } else { +% my $name_col = $opt{'name_col'}; +% + + + <% $target_obj->$name_col() %><% $opt{'target_link'} ? '</A>' : '' %> +% } +% if ( $opt{'disable-able'} ) { + + + <% $target_obj->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> +% } + + + <BR> +% } + + 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/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html new file mode 100644 index 000000000..7ee6f2d43 --- /dev/null +++ b/httemplate/elements/dashboard-toplist.html @@ -0,0 +1,109 @@ +% if ( $conf->exists('dashboard-toplist') ) { + + <% include('/elements/table-grid.html') %> + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = $bgcolor2; + +% foreach my $line ( $conf->config('dashboard-toplist') ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + +% if ( $line =~ /^\s*cust_main:\s*(\d+)\s*$/ ) { #customer line +% my $custnum = $1; +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% if ( $cust_main ) { + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="view/cust_main.cgi?<% $custnum %>"><% $cust_main->name %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <FONT SIZE="-1"><A HREF="<% FS::TicketSystem->href_new_ticket($cust_main, join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) ) %>">(new ticket)</A></FONT> + </TD> + +% foreach my $priority ( @custom_priorities, '' ) { +% my $num = +% FS::TicketSystem->num_customer_tickets($custnum,$priority); +% my $ahref = ''; +% $ahref= '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum,$priority). +% '">' +% if $num; + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $ahref.$num %></A> + </TD> +% } + </TR> + +% } else { + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + Unknown customer number <% $custnum %> + </TD> + </TR> + +% } +% +% } elsif ( $line =~ /^\-\-+$/ ) { #divider +% + <TR> + <TH CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 3 %>"></TH> + </TR> + +% next; +% +% } elsif ( $line =~ /^\s*$/ ) { + + <TR> + <TD CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 3 %>" BGCOLOR="<% $bgcolor %>"> </TD> + </TR> + +% } elsif ( $line =~ /^\S/ ) { #label line + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc"><% $line %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> +% foreach my $priority ( @custom_priorities, '' ) { + <TH CLASS="grid" BGCOLOR="#cccccc"> + <% $priority || '<i>(none)</i>'%> + </TH> +% } + </TR> + +% } else { #regular line + + <TR> + <TD CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 3 %>" BGCOLOR="<% $bgcolor %>"><% $line %></TD> + </TR> + +% } + +% +% } + + </TABLE> + <BR> + +% } +<%init> + +my $conf = new FS::Conf; + +#false laziness w/httemplate/search/cust_main.cgi... care if +# custom_priority_field becomes anything but a local hack... +my @custom_priorities = (); +if ( $conf->config('ticket_system-custom_priority_field') + && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { + @custom_priorities = + $conf->config('ticket_system-custom_priority_field-values'); +} + +</%init> diff --git a/httemplate/elements/error.html b/httemplate/elements/error.html new file mode 100644 index 000000000..e8ba93010 --- /dev/null +++ b/httemplate/elements/error.html @@ -0,0 +1,4 @@ +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html new file mode 100644 index 000000000..32d121996 --- /dev/null +++ b/httemplate/elements/footer.html @@ -0,0 +1,5 @@ + </TD> + </TR> + </TABLE> + </BODY> +</HTML> diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css new file mode 100644 index 000000000..6f7cadb2e --- /dev/null +++ b/httemplate/elements/freeside.css @@ -0,0 +1,15 @@ +* { + font-family: Arial, Verdana, Helvetica, sans-serif; +} + +A:link IMG, A:visited { border-style: none } +/* A:focus {text-decoration: underline } */ + +a:link, a:visited { + /* text-decoration: none; */ + color: #000000; +} +/* a:hover { text-decoration: underline } */ + +/* a:focus { background-color: #ccccee } */ + diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html new file mode 100644 index 000000000..43d9bc3af --- /dev/null +++ b/httemplate/elements/header-popup.html @@ -0,0 +1,23 @@ +% +% my($title, $menubar) = ( shift, shift ); #$menubar is unused here though +% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. +% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section +% my $conf = new FS::Conf; +% + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<HTML> + <HEAD> + <TITLE> + <% $title %> + </TITLE> + <META HTTP-Equiv="Cache-Control" Content="no-cache"> + <META HTTP-Equiv="Pragma" Content="no-cache"> + <META HTTP-Equiv="Expires" Content="0"> + <% $head %> + </HEAD> + <BODY BGCOLOR="#e8e8e8" <% $etc %>> + <FONT SIZE=6> + <CENTER><% $title %></CENTER> + </FONT> + <BR><!--<BR>--> diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 10e4e40f1..1f5674885 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -1,21 +1,244 @@ -<% - 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 <HEAD> section -%> - <HTML> - <HEAD> - <TITLE> - <%= $title %> - </TITLE> - <META HTTP-Equiv="Cache-Control" Content="no-cache"> - <META HTTP-Equiv="Pragma" Content="no-cache"> - <META HTTP-Equiv="Expires" Content="0"> - <%= $head %> - </HEAD> - <BODY BGCOLOR="#e8e8e8"<%= $etc %>> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<HTML> + <HEAD> + <TITLE> + <% $title %> + </TITLE> + <META HTTP-Equiv="Cache-Control" Content="no-cache"> + <META HTTP-Equiv="Pragma" Content="no-cache"> + <META HTTP-Equiv="Expires" Content="0"> + + <% include('menu.html', 'freeside_baseurl' => $fsurl, + 'position' => $menu_position, + ) + %> + + <SCRIPT TYPE="text/javascript"> + function clearhint_search_cust (what) { + if ( what.value == '(cust #, name, company or phone)' ) + what.value = ''; + } + + function clearhint_search_invoice (what) { + if ( what.value == '(inv #)' ) + what.value = ''; + } + + function clearhint_search_svc (what) { + if ( what.value == '(user, user@domain or domain)' ) + what.value = ''; + } + + function clearhint_search_ticket (what) { + if ( what.value == '(ticket # or subject string)' ) + what.value = ''; + } + </SCRIPT> + + <% $head %> + + </HEAD> + <BODY <% $menu_position eq 'left' ? qq( BACKGROUND="${fsurl}images/background-cheat.png" ) : ' BGCOLOR="#e8e8e8" ' %> <% $etc %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"> + <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0; padding-right:4"> + <tr> + <td rowspan=2 BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" SRC="<%$fsurl%>images/small-logo.png"></td> + <td align=left rowspan=2 BGCOLOR="#ffffff"> <!-- valign="top" --> + <font size=6><% $conf->config('company_name') || 'ExampleCo' %></font> + </td> + <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %> </b><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html">Preferences</a> <BR></FONT> + </td> + </tr> + <tr> + <td align=right valign=bottom BGCOLOR="#ffffff"> + + <table> + <tr> + <td align=right BGCOLOR="#ffffff"> + <FONT SIZE="-2"> + <A HREF="http://www.sisd.com/freeside">Freeside</A> v<% $FS::VERSION %><BR> + <A HREF="<% $conf->config('support-key') ? "http://www.sisd.com/mediawiki/index.php/Supported:Documentation" : "http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation" %>">Documentation</A><BR> + </FONT> + </td> +% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { +% eval "use RT;"; + + <td bgcolor=#000000></td> + <td align=left> + <FONT SIZE="-2"> + <A HREF="http://www.bestpractical.com/rt">RT<A> v<% $RT::VERSION %><BR> + <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR> + </FONT> + </td> +% } + + + </tr> + </table> + + </td> + </tr> + </table> + +<style type="text/css"> +input.fsblackbutton { + background-color:#333333; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666') +} + +input.fsblackbuttonselected { + background-color:#7e0079; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079') +} +</style> + + <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0> + <TR> + <TD COLSPAN=5 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD> + </TR> + +% if ( $menu_position eq 'top' ) { + + <TR> + + <TD COLSPAN="5" WIDTH="100%" STYLE="padding:0"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar); + </SCRIPT> + </TD> + + </TR> + + <TR> + <TD COLSPAN="5" WIDTH="100%" HEIGHT="2px" STYLE="padding:0" BGCOLOR="#000000"> + </TD> + </TR> + + <TR> + <TD COLSPAN="5" WIDTH="100%" HEIGHT="4px" STYLE="padding:0" BGCOLOR="#000000"> + </TD> + </TR> + +% } + + <TR> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>edit/cust_main.cgi" METHOD="GET" STYLE="margin:0"> + <INPUT TYPE="submit" VALUE="New customer" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="vertical-align:bottom"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A NOTYET="<%$fsurl%>search/cust_main.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> +% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) { + + <FORM ACTION="<%$fsurl%>search/cust_bill.html" METHOD="GET" STYLE="margin:0;display:inline"> + <INPUT NAME="invnum" TYPE="text" VALUE="(inv #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px"> +% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { + + <A HREF="<%$fsurl%>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A> +% } + + <BR> + <INPUT TYPE="submit" VALUE="Search invoices" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> +% } + + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>search/cust_svc.html" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="search_svc" TYPE="text" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search services" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right" STYLE="padding-right:4px"> + <FORM ACTION="<%$fsurl%>rt/index.html" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="q" TYPE="text" VALUE="(ticket # or subject string)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A HREF="<%$fsurl%>rt/Search/Build.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search tickets" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px"> + </FORM> + </TD> + + </TR> + </TABLE> + + <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4> + + <TR> + +% if ( $menu_position eq 'left' ) { + + <TD BGCOLOR="#000000" STYLE="padding:0" WIDTH="154"></TD> + <TD STYLE="padding:0" WIDTH="13"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-corner.png"></TD> + +% } + + <TD STYLE="padding:0" WIDTH="100%"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD> + + </TR> + + <TR HEIGHT="100%"> + +% if ( $menu_position eq 'left' ) { + + <TD BGCOLOR="#000000" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar); + </SCRIPT> + <BR> + <IMG SRC="<%$fsurl%>images/32clear.gif" HEIGHT="1" WIDTH="154"> + + </TD> + <TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-side.png"></TD> + +% } + + <TD BGCOLOR="#e8e8e8" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> --> + <FONT SIZE=6> - <%= $title %> + <% $title %> </FONT> + <BR><BR> - <%= $menubar ? "$menubar<BR><BR>" : '' %> + <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %> +<%init> + +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 <HEAD> section +my $conf = new FS::Conf; + +my $menu_position = $FS::CurrentUser::CurrentUser->option('menu_position') + || 'left'; + +</%init> diff --git a/httemplate/elements/iframecontentmws.js b/httemplate/elements/iframecontentmws.js new file mode 100644 index 000000000..c80998957 --- /dev/null +++ b/httemplate/elements/iframecontentmws.js @@ -0,0 +1,20 @@ +/*
+ iframecontentmws.js - Foteos Macrides
+ Initial: October 10, 2004 - Last Revised: May 9, 2005
+ Simple script for using an HTML file as iframe content in overlibmws popups.
+ Include WRAP and TEXTPADDING,0 in the overlib call to ensure that the width
+ arg is respected (unless the CAPTION plus CLOSETEXT widths add up to more than
+ the width arg, in which case you should increase the width arg). The name arg
+ should be a unique string for each popup with iframe content in the document.
+ The frameborder arg should be 1 (browser default if omitted) or 0.
+
+ See http://www.macridesweb.com/oltest/IFRAME.html for demonstration.
+*/
+
+function OLiframeContent(src, width, height, name, frameborder) {
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+ +(name!=null?' name="'+name+'" id="'+name+'"':'')
+ +(frameborder!=null?' frameborder="'+frameborder+'"':'')
+ +' scrolling="auto">'
+ +'<div>[iframe not supported]</div></iframe>');
+}
diff --git a/httemplate/elements/jsrsServer.html b/httemplate/elements/jsrsServer.html index fd6dc5465..f37b0aaee 100644 --- a/httemplate/elements/jsrsServer.html +++ b/httemplate/elements/jsrsServer.html @@ -1,3 +1,4 @@ -<% - my $server = new FS::UI::Web::JSRPC '', $cgi; -%><%= $server->process %> +% +% my $server = new FS::UI::Web::JSRPC '', $cgi; +% +<% $server->process %> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html new file mode 100644 index 000000000..9565ff2d0 --- /dev/null +++ b/httemplate/elements/menu.html @@ -0,0 +1,353 @@ +<script type="text/javascript" src="<%$fsurl%>elements/cssexpr.js"></script> + +% if ( $opt{'position'} eq 'top' ) { + + <script type="text/javascript" src="<%$fsurl%>elements/xmenu.top.js"></script> + <link href="<%$fsurl%>elements/xmenu.top.css" type="text/css" rel="stylesheet"> + +% } else { # elsif ( $opt{'position'} eq 'left' ) { + + <script type="text/javascript" src="<%$fsurl%>elements/xmenu.js"></script> + <link href="<%$fsurl%>elements/xmenu.css" type="text/css" rel="stylesheet"> + +% } + +<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet"> + +<SCRIPT TYPE="text/javascript"> + + webfxMenuImagePath = "<%$fsurl%>images/"; + webfxMenuUseHover = 1; + webfxMenuShowTime = 300; + webfxMenuHideTime = 500; + + var myBar = new WebFXMenuBar; + +% foreach my $item ( keys %menu ) { +% +% my( $url_or_submenu, $tooltip ) = @{ $menu{$item} }; +% +% if ( ref($url_or_submenu) ) { +% +% #warn $item; +% +% my( $subhtml, $submenuname ) = submenu($url_or_submenu, $item); + + <% $subhtml %> + myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname %> )); + +% } else { + + myBar.add(new WebFXMenuButton("<% $item %>", "<% $url_or_submenu %>", "<% $tooltip %>" )); + +% } +% +% } + + myBar.show( null, 'vertical' ); + myBar.width = 154; + +</SCRIPT> + +<%init> +my( %opt ) = @_; +my $conf = new FS::Conf; +my $fsurl = $opt{'freeside_baseurl'}; + +my $curuser = $FS::CurrentUser::CurrentUser; + +#Active tickets not assigned to a customer + +tie my %report_customers_lists, 'Tie::IxHash', + 'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ], + 'by last name' => [ $fsurl. 'search/cust_main.cgi?browse=last', '' ], + 'by company name' => [ $fsurl. 'search/cust_main.cgi?browse=company', '' ], +; +$report_customers_lists{'by active trouble tickets'} = [ $fsurl. 'search/cust_main.cgi?browse=tickets', '' ] + if $conf->config('ticket_system'); + +tie my %report_customers_search, 'Tie::IxHash'; +$report_customers_search{'by ordering employee'} = [ $fsurl. 'search/cust_main-otaker.cgi' ] + if $curuser->access_right('Configuration'); + +tie my %report_customers, 'Tie::IxHash', + 'List customers' => [ \%report_customers_lists, 'List customers' ], +; +$report_customers{'Search customers'} = [ \%report_customers_search, 'Search customers' ] + if keys %report_customers_search; +$report_customers{'Zip code distribution'} = [ $fsurl.'search/report_cust_main-zip.html', 'Zip codes by number of customers' ]; + +tie my %report_invoices_open, 'Tie::IxHash', + 'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ], + '15 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN15_date', 'Invoices 15 days or older with an unpaid balance' ], + '30 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN30_date', 'Invoices 30 days or older with an unpaid balance' ], + '60 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN60_date', 'Invoices 60 days or older with an unpaid balance' ], + '90 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN90_date', 'Invoices 90 days or older with an unpaid balance' ], + '120 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN120_date', 'Invoices 120 days or older with an unpaid balance' ], +; + +tie my %report_invoices, 'Tie::IxHash', + 'Open invoices' => [ \%report_invoices_open, 'Open invoices' ], + 'All invoices' => [ $fsurl. 'search/cust_bill.html?date', 'List all invoices' ], + 'Advanced invoice reports' => [ $fsurl.'search/report_cust_bill.html', 'by agent, date range, etc.' ], +; + +tie my %report_services, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration') ) { + $report_services{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi?orderby=active', 'Service definitions by number of active packages' ]; + $report_services{'separator'} = ''; +} +foreach my $svcdb ( FS::part_svc->svc_tables() ) { + + my $name = "FS::$svcdb"->table_info->{'name_plural'} + || PL( "FS::$svcdb"->table_info->{'name'} ); + my $lcname = lc($name); + my $longname = "FS::$svcdb"->table_info->{'longname_plural'} || $name; + my $lclongname = lc($longname); + my $sorts = "FS::$svcdb"->table_info->{'sorts'} || [ 'svcnum' ]; + $sorts = [ $sorts ] unless ref($sorts); + my %svc_url = ( 'm' => $m, + 'action' => 'search', + 'svcdb' => $svcdb, + ); + + tie my %report_svc, 'Tie::IxHash'; + + foreach my $sort ( @$sorts ) { + + my $title = "All $lcname"; + $title .= " by ". FS::part_svc->svc_table_fields($svcdb)->{$sort}->{'label'} + if scalar(@$sorts) > 1; + + $report_svc{$title} = + [ FS::UI::Web::svc_url( %svc_url, 'query' => "magic=all;sortby=$sort" ), + '', + ]; + } + + if ( $curuser->access_right('View/link unlinked services') ) { + $report_svc{"Unlinked $lcname"} = + [ FS::UI::Web::svc_url( %svc_url, 'query' => "magic=unlinked;sortby=". $sorts->[0] ), + "Pre-Freeside $lcname without a customer record", + ]; + } + + $report_services{$name} = [ \%report_svc, $longname ]; + +} + +tie my %report_packages, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration') ) { + $report_packages{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ]; + $report_packages{'separator'} = ''; +} +$report_packages{'All customer packages'} = [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ]; +$report_packages{'Suspended customer packages'} = [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ]; +$report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ]; +$report_packages{'Advanced package reports'} = [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ]; + +tie my %report_rating, 'Tie::IxHash', + 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ], + 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ], +; + +tie my %report_bill_event, 'Tie::IxHash', + 'All billing events' => [ $fsurl.'search/cust_bill_event.html', 'All billing events for a date range' ], + 'Invoice event errors' => [ $fsurl.'search/cust_bill_event.html?failed=1', 'failed credit cards, processor or printer problems, etc.' ], +; + +tie my %report_financial, 'Tie::IxHash', + 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ], + 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ], + 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ], + 'Payment Report' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ], +; +$report_financial{'Payment Batch Report'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ] + if $conf->exists('batch-enable'); +$report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ]; +$report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ]; +$report_financial{'Sales Tax Liability'} = [ $fsurl.'search/report_tax.html', 'Sales tax liability report' ]; +; + +tie my %report_menu, 'Tie::IxHash'; +$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ] + if $curuser->access_right('List customers'); +$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ] + if $curuser->access_right('List invoices'); +$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ] + if $curuser->access_right('List packages'); +$report_menu{'Services'} = [ \%report_services, 'Services reports' ] + if $curuser->access_right('List services'); +$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ] + if $curuser->access_right('List rating data'); +$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ] + if $curuser->access_right('Billing event reports'); +$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ] + if $curuser->access_right('Financial reports'); + +tie my %tools_importing, 'Tie::IxHash', + 'Import customers from CSV file' => [ $fsurl.'misc/cust_main-import.cgi', '' ], + 'Import one-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ], + 'Import Call Detail Records (CDRs) from CSV file' => [ $fsurl.'misc/cdr-import.html', '' ], +; + +tie my %tools_exporting, 'Tie::IxHash', + 'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ], +; + +# <!-- <BR>View active NAS ports: +# <A HREF="browse/nas.cgi">session server</A> --> +# <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A> +# <BR> --> + +tie my %tools_menu, 'Tie::IxHash', (); +$tools_menu{'Quick payment entry'} = [ $fsurl.'misc/batch-cust_pay.html', 'Enter multiple payments in a batch' ] + if $curuser->access_right('Post payment batch'); +$tools_menu{'Process payment batches'} = [ $fsurl.'search/pay_batch.cgi?magic=_date;open=1;intransit=1', 'Process credit card and electronic check batches' ] + if $conf->exists('batch-enable') && $curuser->access_right('Process batches'); +$tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queue' ] + if $curuser->access_right('Job queue'); +$tools_menu{'Importing'} = [ \%tools_importing, 'Import tools' ] + if $curuser->access_right('Import'); +$tools_menu{'Exporting'} = [ \%tools_exporting, 'Export tools' ] + if $curuser->access_right('Export'); + +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.' ], + 'View/Edit cancel reason types' => [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ], + 'View/Edit cancel reasons' => [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ], + 'View/Edit suspend reason types' => [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ], + 'View/Edit suspend reasons' => [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ], +; + +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.'search/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'; +$config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service. Tracked for informational purposes' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); +if ( $curuser->access_right('Configuration') ) { + $config_misc{'View/Edit virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ]; + $config_misc{'View/Edit message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ]; + $config_misc{'View/Edit inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]; +} + +tie my %config_menu, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration' ) ) { + %config_menu = ( + 'Settings' => [ $fsurl.'config/config-view.cgi', '' ], + 'separator' => '', #its a separator! + 'Employees' => [ \%config_employees, '' ], + 'Provisioning, services and packages' + => [ \%config_export_svc_pkg, '' ], + 'Resellers' => [ \%config_agent, '' ], + 'Billing' => [ \%config_billing, '' ], + 'Dialup' => [ \%config_dialup, '' ], + 'Fixed (username-less) broadband' + => [ \%config_broadband, '' ], + ); +} +$config_menu{'Miscellaneous'} = [ \%config_misc, '' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); + +tie my %menu, 'Tie::IxHash', + 'Billing Main' => [ $fsurl, 'Billing start page', ], +; +if ( $conf->config('ticket_system') ) { + $menu{'Ticketing Main'} = + [ + ( $conf->config('ticket_system') eq 'RT_External' + ? FS::TicketSystem->baseurl() + : $fsurl.'rt/' + ), + 'Ticketing start page', + ], +} +$menu{'Reports'} = [ \%report_menu, 'Lists, reporting and graphing' ] + if keys %report_menu; +$menu{'Tools'} = [ \%tools_menu, 'Tools' ] + if keys %tools_menu; +$menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); + +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 = 280;\n", + + "myMenu$menunum"; + +} + +</%init> + diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html index 87a50312c..ec6c13fea 100644 --- a/httemplate/elements/menubar.html +++ b/httemplate/elements/menubar.html @@ -1,8 +1,10 @@ -<% - my($item, $url, @html); - while (@_) { - ($item, $url) = splice(@_,0,2); - push @html, qq!<A HREF="$url">$item</A>!; - } -%> -<%= join(' | ', @html) %> +% +% my($item, $url, @html); +% while (@_) { +% ($item, $url) = splice(@_,0,2); +% next if $item =~ /^\s*Main\s+Menu\s*$/i; +% push @html, qq!<A HREF="$url">$item</A>!; +% } +% + +<% join(' | ', @html) %> diff --git a/httemplate/elements/overlibmws.js b/httemplate/elements/overlibmws.js index fba1105b5..5ef50d68f 100644 --- a/httemplate/elements/overlibmws.js +++ b/httemplate/elements/overlibmws.js @@ -1,7 +1,7 @@ /*
Do not remove or change this notice.
overlibmws.js core module - Copyright Foteos Macrides 2002-2005. All rights reserved.
- Initial: August 18, 2002 - Last Revised: February 10, 2005
+ Initial: August 18, 2002 - Last Revised: May 30, 2006
This module is subject to the same terms of usage as for Erik Bosrup's overLIB,
though only a minority of the code and API now correspond with Erik's version.
See the overlibmws Change History and Command Reference via:
@@ -14,30 +14,30 @@ */
// PRE-INIT -- Ignore these lines, configuration is below.
-var OLloaded=0,pmCnt=1,pMtr=new Array(),OLv,OLudf,OLrefXY;
-var OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%");
-var OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0;
-var OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0;
+var OLloaded=0,pmCnt=1,pMtr=new Array(),OLcmdLine=new Array(),OLrunTime=new Array(),OLv,OLudf,
+OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%"),OLrefXY,
+OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0,
+OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0,OLprintPI=0;
if(typeof OLgateOK=='undefined')var OLgateOK=1;
-OLregCmds(
- 'inarray,caparray,caption,sticky,nofollow,background,noclose,mouseoff,offdelay,right,left,'
-+'center,offsetx,offsety,fgcolor,bgcolor,cgcolor,textcolor,capcolor,closecolor,width,wrap,'
-+'wrapmax,height,border,base,status,autostatus,autostatuscap,snapx,snapy,fixx,fixy,relx,rely,'
-+'midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,cgbackground,padx,pady,fullhtml,'
-+'below,above,vcenter,capicon,textfont,captionfont,closefont,textsize,captionsize,closesize,'
-+'timeout,delay,hauto,vauto,nojustx,nojusty,closetext,closeclick,closetitle,fgclass,bgclass,'
-+'cgclass,capbelow,textpadding,textfontclass,captionpadding,captionfontclass,closefontclass,'
-+'label,donothing');
-
+var OLp1or2c='inarray,caparray,caption,closetext,right,left,center,autostatuscap,padx,pady,'
++'below,above,vcenter,donothing',OLp1or2co='nofollow,background,offsetx,offsety,fgcolor,'
++'bgcolor,cgcolor,textcolor,capcolor,width,wrap,wrapmax,height,border,base,status,autostatus,'
++'snapx,snapy,fixx,fixy,relx,rely,midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,'
++'cgbackground,fullhtml,capicon,textfont,captionfont,textsize,captionsize,timeout,delay,hauto,'
++'vauto,nojustx,nojusty,fgclass,bgclass,cgclass,capbelow,textpadding,textfontclass,'
++'captionpadding,captionfontclass,sticky,noclose,mouseoff,offdelay,closecolor,closefont,'
++'closesize,closeclick,closetitle,closefontclass,decode',OLp1or2o='text,cap,close,hpos,vpos,'
++'padxl,padxr,padyt,padyb',OLp1co='label',OLp1or2=OLp1or2co+','+OLp1or2o,OLp1=OLp1co+','+'frame';
+OLregCmds(OLp1or2c+','+OLp1or2co+','+OLp1co);
function OLud(v){return eval('typeof ol_'+v+'=="undefined"')?1:0;}
-// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions.
-if(OLud('fgcolor'))var ol_fgcolor="#CCCCFF";
+// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions
+if(OLud('fgcolor'))var ol_fgcolor="#ccccff";
if(OLud('bgcolor'))var ol_bgcolor="#333399";
if(OLud('cgcolor'))var ol_cgcolor="#333399";
if(OLud('textcolor'))var ol_textcolor="#000000";
-if(OLud('capcolor'))var ol_capcolor="#FFFFFF";
-if(OLud('closecolor'))var ol_closecolor="#EEEEFF";
+if(OLud('capcolor'))var ol_capcolor="#ffffff";
+if(OLud('closecolor'))var ol_closecolor="#eeeeff";
if(OLud('textfont'))var ol_textfont="Verdana,Arial,Helvetica";
if(OLud('captionfont'))var ol_captionfont="Verdana,Arial,Helvetica";
if(OLud('closefont'))var ol_closefont="Verdana,Arial,Helvetica";
@@ -106,43 +106,41 @@ if(OLud('vauto'))var ol_vauto=0; if(OLud('nojustx'))var ol_nojustx=0;
if(OLud('nojusty'))var ol_nojusty=0;
if(OLud('label'))var ol_label="";
+if(OLud('decode'))var ol_decode=0;
// ARRAY CONFIGURATION - See overlibConfig.txt for descriptions.
if(OLud('texts'))var ol_texts=new Array("Text 0","Text 1");
if(OLud('caps'))var ol_caps=new Array("Caption 0","Caption 1");
// END CONFIGURATION -- Don't change anything below, all configuration is above.
// INIT -- Runtime variables.
-var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0;
-var o3_offdelay=300,o3_hpos=RIGHT,o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="";
-var o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",o3_width=200,o3_wrap=0;
-var o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0;
-var o3_snapy=0,o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null;
-var o3_ref="",o3_refc='UL',o3_refp='UL',o3_refx=0,o3_refy=0,o3_fgbackground="";
-var o3_bgbackground="",o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0;
-var o3_fullhtml=0,o3_vpos=BELOW,o3_capicon="",o3_textfont="Verdana,Arial,Helvetica";
-var o3_captionfont="Verdana,Arial,Helvetica",o3_closefont="Verdana,Arial,Helvetica";
-var o3_textsize=1,o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0;
-var o3_hauto=0,o3_vauto=0,o3_nojustx=0,o3_nojusty=0,o3_close="Close",o3_closeclick=0;
-var o3_closetitle="",o3_fgclass="",o3_bgclass="",o3_cgclass="",o3_textpadding=2;
-var o3_textfontclass="",o3_captionpadding=2,o3_captionfontclass="",o3_closefontclass="";
-var o3_capbelow=0,o3_label="",CSSOFF=DONOTHING,CSSCLASS=DONOTHING;
-var OLx=0,OLy=0,OLshowingsticky=0,OLallowmove=0,OLremovecounter=0;
-var OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0;
-var over=null,OLfnRef="",OLhover=0;
-var OLua=navigator.userAgent.toLowerCase();
-var OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4);
-var OLns6=(document.getElementById)?1:0;
-var OLie4=(document.all)?1:0;
-var OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0;
-var OLmac=(OLua.indexOf('mac')>=0)?1:0;
-var OLsaf=(OLua.indexOf('safari')>=0)?1:0;
-var OLkon=(OLua.indexOf('konqueror')>=0)?1:0;
-var OLkht=(OLsaf||OLkon)?1:0;
-var OLopr=(OLua.indexOf('opera')>=0)?1:0;
-var OLop7=(OLopr&&document.createTextNode)?1:0;
+var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0,
+o3_offdelay=300,o3_hpos=RIGHT,o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="",
+o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",o3_width=200,o3_wrap=0,
+o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0,
+o3_snapy=0,o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null,o3_ref="",
+o3_refc='UL',o3_refp='UL',o3_refx=0,o3_refy=0,o3_fgbackground="",o3_bgbackground="",
+o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0,o3_fullhtml=0,o3_vpos=BELOW,
+o3_capicon="",o3_textfont="Verdana,Arial,Helvetica",o3_captionfont="",o3_closefont="",
+o3_textsize=1,o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0,o3_hauto=0,
+o3_vauto=0,o3_nojustx=0,o3_nojusty=0,o3_close="",o3_closeclick=0,o3_closetitle="",o3_fgclass="",
+o3_bgclass="",o3_cgclass="",o3_textpadding=2,o3_textfontclass="",o3_captionpadding=2,
+o3_captionfontclass="",o3_closefontclass="",o3_capbelow=0,o3_label="",o3_decode=0,
+CSSOFF=DONOTHING,CSSCLASS=DONOTHING,OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0,over=null,
+OLfnRef="",OLhover=0,OLx=0,OLy=0,OLshowingsticky=0,OLallowmove=0,OLcC=null,
+OLua=navigator.userAgent.toLowerCase(),
+OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4),
+OLns6=(document.getElementById)?1:0,
+OLie4=(document.all)?1:0,
+OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0,
+OLmac=(OLua.indexOf('mac')>=0)?1:0,
+OLsaf=(OLua.indexOf('safari')>=0)?1:0,
+OLkon=(OLua.indexOf('konqueror')>=0)?1:0,
+OLkht=(OLsaf||OLkon)?1:0,
+OLopr=(OLua.indexOf('opera')>=0)?1:0,
+OLop7=(OLopr&&document.createTextNode)?1:0;
if(OLopr){OLns4=OLns6=0;if(!OLop7)OLie4=0;}
-var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0;
-var OLie5=0,OLie55=0;if(OLie4&&!OLop7){
+var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0,
+OLie5=0,OLie55=0;if(OLie4&&!OLop7){
if((OLv=OLua.match(/msie (\d\.\d+)\.*/i))&&(OLv=parseFloat(OLv[1]))>=5.0){
OLie5=1;OLns6=0;if(OLv>=5.5)OLie55=1;}if(OLns6)OLie4=0;}
if(OLns4)window.onresize=function(){location.reload();}
@@ -156,14 +154,16 @@ else{overlib=nd=cClick=OLpageDefaults=no_overlib;} // Loads defaults then args into runtime variables.
function overlib(){
if(!(OLloaded&&OLgateOK))return;
-if((OLexclusivePI)&&OLisExclusive(overlib.arguments))return true;
+if((OLexclusivePI)&&OLisExclusive(arguments))return true;
if(OLchkMh)OLmh();
if(OLndt&&!OLtimerid)OLndt=0;if(over)cClick();
-OLloadP1or2();OLload('close,closeclick,closetitle,noclose,mouseoff,offdelay,sticky,'
-+'closecolor,closefont,closesize,closefontclass,frame,label');OLfnRef="";OLhover=0;
+OLload(OLp1or2);OLload(OLp1);
+OLfnRef="";OLhover=0;
OLsetRunTimeVar();
-OLparseTokens('o3_',overlib.arguments);
+OLparseTokens('o3_',arguments);
if(!(over=OLmkLyr()))return false;
+if(o3_decode)OLdecode();
+if(OLprintPI)OLchkPrint();
if(OLbubblePI)OLchkForBubbleEffect();
if(OLdebugPI)OLsetDebugCanShow();
if(OLshadowPI)OLinitShadow();
@@ -172,7 +172,7 @@ if(OLfilterPI)OLinitFilterLyr(); if(OLexclusivePI&&o3_exclusive&&o3_exclusivestatus!="")o3_status=o3_exclusivestatus;
else if(o3_autostatus==2&&o3_cap!="")o3_status=o3_cap;
else if(o3_autostatus==1&&o3_text!="")o3_status=o3_text;
-if(o3_delay==0){return OLmain();
+if(!o3_delay){return OLmain();
}else{OLdelayid=setTimeout("OLmain()",o3_delay);
if(o3_status!=""){self.status=o3_status;return true;}
else if(!(OLop7&&event&&event.type=='mouseover'))return false;}
@@ -180,25 +180,24 @@ else if(!(OLop7&&event&&event.type=='mouseover'))return false;} // Clears popups if appropriate
function nd(time){
-if(!(OLloaded&&OLgateOK))return;
-if((OLexclusivePI)&&OLisExclusive())return true;
+if(OLloaded&&OLgateOK){if(!((OLexclusivePI)&&OLisExclusive())){
if(time&&over&&!o3_delay){if(OLtimerid>0)clearTimeout(OLtimerid);
OLtimerid=(OLhover&&o3_frame==self&&!OLcursorOff())?0:
-setTimeout("cClick()",(o3_timeout=OLndt=time));
-}else{if(OLremovecounter>=1)OLshowingsticky=0;if(!OLshowingsticky){
-OLallowmove=0;if(over)OLhideObject(over);}else{OLremovecounter++;}}
-return true;
+setTimeout("cClick()",(o3_timeout=OLndt=time));}else{
+if(!OLshowingsticky){OLallowmove=0;if(over)OLhideObject(over);}}}}
+return false;
}
// Close function for stickies
function cClick(){
-if(OLloaded&&OLgateOK){OLhover=0;if(over)OLhideObject(over);OLshowingsticky=0;}
+if(OLloaded&&OLgateOK){OLhover=0;if(over){
+if(OLovertwoPI&&over==over2)cClick2();OLhideObject(over);OLshowingsticky=0;}}
return false;
}
// Sets page-specific defaults.
function OLpageDefaults(){
-OLparseTokens('ol_',OLpageDefaults.arguments);
+OLparseTokens('ol_',arguments);
}
// For unsupported browsers.
@@ -210,24 +209,14 @@ function no_overlib(){return false;} function OLmain(){
o3_delay=0;
if(o3_frame==self){if(o3_noclose)OLoptMOUSEOFF(0);else if(o3_mouseoff)OLoptMOUSEOFF(1);}
-OLdoLyr();
-OLallowmove=0;if(o3_timeout>0){
+if(o3_sticky)OLshowingsticky=1;OLdoLyr();OLallowmove=0;if(o3_timeout>0){
if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_timeout);}
if(o3_ref){OLrefXY=OLgetRefXY(o3_ref);if(OLrefXY[0]==null){o3_ref="";o3_midx=0;o3_midy=0;}}
-OLdisp(o3_status);
-if(OLdraggablePI)OLcheckDrag();
+OLdisp(o3_status);if(OLdraggablePI)OLcheckDrag();
if(o3_status!="")return true;else if(!(OLop7&&event&&event.type=='mouseover'))return false;
}
-// Loads defaults for primaries or secondaries
-function OLloadP1or2(){
-OLload('text,cap,capbelow,textpadding,captionpadding,border,base,status,autostatus,nofollow,'
-+'width,wrap,wrapmax,height,hpos,vpos,offsetx,offsety,snapx,snapy,relx,rely,midx,midy,ref,'
-+'refc,refp,refx,refy,fixx,fixy,nojustx,nojusty,hauto,vauto,timeout,delay,fgcolor,bgcolor,'
-+'cgcolor,textcolor,capcolor,textfont,captionfont,textsize,captionsize,fgbackground,'
-+'bgbackground,cgbackground,capicon,background,padxl,padxr,padyt,padyb,fullhtml,fgclass,'
-+'bgclass,cgclass,textfontclass,captionfontclass');
-}
+// Loads o3_ variables
function OLload(c){var i,m=c.split(',');for(i=0;i<m.length;i++)eval('o3_'+m[i]+'=ol_'+m[i]);}
// Chooses LGF
@@ -258,8 +247,7 @@ if(o3_bgcolor!='')o3_bgcolor=' bgcolor="'+o3_bgcolor+'"'; if(o3_cgcolor!='')o3_cgcolor=' bgcolor="'+o3_cgcolor+'"';
if(o3_height>0)o3_height=' height="'+o3_height+'"';else o3_height='';}
if(!OLns4)OLrepositionTo(over,(OLns6?20:0),0);var lyrHtml=OLdoLGF();
-if(o3_sticky){if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}
-OLshowingsticky=1;OLremovecounter=0;}
+if(o3_sticky&&OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}
if(o3_wrap&&!o3_fullhtml){OLlayerWrite(lyrHtml);
o3_width=(OLns4?over.clip.width:over.offsetWidth);
if(OLns4&&o3_wrapmax<1)o3_wrapmax=o3_frame.innerWidth-40;
@@ -279,19 +267,19 @@ OLsetBackground('');return t; // Makes table with caption and optional close link
function OLcontentCaption(txt,title,close){
-var closing='',closeevent='onmouseover',caption,t;
-if(o3_closeclick==1)closeevent=(o3_closetitle?'title="'+o3_closetitle+'" ':'')+'onclick';
+var closing=(OLprintPI?OLprintCapLGF():''),closeevent='onmouseover',caption,t,
+cC='javascript:return '+OLfnRef+(OLovertwoPI&&over==over2?'cClick2();':'cClick();');
+if(o3_closeclick)closeevent=(o3_closetitle?'title="'+o3_closetitle+'" ':'')+'onclick';
if(o3_capicon!='')o3_capicon='<img src="'+o3_capicon+'" /> ';
-if(close!=''){closing='<td align="right"><a href="javascript:return '+OLfnRef+'cClick();" '
-+closeevent+'="return '+OLfnRef+'cClick();"'+(o3_closefontclass?' class="'+o3_closefontclass
-+'">':'>'+OLlgfUtil(0,'','span',o3_closecolor,o3_closefont,o3_closesize))+close
-+(o3_closefontclass?'':OLlgfUtil(1,'','span'))+'</a></td>';}
+if(close){closing+='<td align="right"><a href="'+cC+'" '
++closeevent+'="'+cC+'"'+(o3_closefontclass?' class="'+o3_closefontclass
++'">':'>'+OLlgfUtil(0,0,'','span',o3_closecolor,o3_closefont,o3_closesize))+close
++(o3_closefontclass?'':OLlgfUtil(1,0,'','span'))+'</a></td>';}
caption='<table'+OLwd(0)+' border="0" cellpadding="'+o3_captionpadding+'" cellspacing="0"'
+(o3_cgclass?' class="'+o3_cgclass+'"':o3_cgcolor+o3_cgbackground)+'><tr><td'+OLwd(0)
+(o3_cgclass?' class="'+o3_cgclass+'">':'>')+(o3_captionfontclass?'<div class="'
-+o3_captionfontclass+'">':'<strong>'
-+OLlgfUtil(0,'','div',o3_capcolor,o3_captionfont,o3_captionsize))+o3_capicon+title
-+OLlgfUtil(1,'','div')+(o3_captionfontclass?'':'</strong>')+'</td>'+closing+'</tr></table>';
++o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',o3_capcolor,o3_captionfont,
+o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+'</td>'+closing+'</tr></table>';
t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF();
OLsetBackground('');return t;
}
@@ -302,8 +290,8 @@ var t;if(hasfullhtml){t=txt;}else{t='<table'+OLwd(1) +' border="0" cellpadding="0" cellspacing="0" '+'height="'+o3_height
+'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'
+o3_padxl+'"></td><td valign="top"'+OLwd(2)+'>'
-+OLlgfUtil(0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+
-OLlgfUtil(1,'','div')+'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+
+OLlgfUtil(1,0,'','div')+'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'
+o3_padyb+'"></td></tr></table>';}
OLsetBackground(image);return t;
}
@@ -317,14 +305,15 @@ function OLfgLGF(t){ return '<table'+OLwd(0)+o3_height+' border="0" cellpadding="'+o3_textpadding
+'" cellspacing="0"'+(o3_fgclass?' class="'+o3_fgclass+'"':o3_fgcolor+o3_fgbackground)
+'><tr><td valign="top"'+(o3_fgclass?' class="'+o3_fgclass+'"':'')+'>'
-+OLlgfUtil(0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t
-+OLlgfUtil(1,'','div')+'</td></tr></table>';
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t
++(OLprintPI?OLprintFgLGF():'')+OLlgfUtil(1,0,'','div')+'</td></tr></table>';
}
-function OLlgfUtil(end,tfc,ele,col,fac,siz){
-if(end)return ('</'+(OLns4?'font':ele)+'>');else return (tfc?'<div class="'+tfc+'">':
-('<'+(OLns4?'font color="'+col+'" face="'+OLquoteMultiNameFonts(fac)+'" size="'+siz:ele
-+' style="color:'+col+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:'+siz+';'
-+(ele=='span'?'text-decoration:underline;':''))+'">'));
+function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){
+if(end)return ('</'+(OLns4?'font'+(stg?'></strong':''):ele)+'>');else return (tfc?'<div '
++'class="' +tfc +'">':('<'+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="'
++OLquoteMultiNameFonts(fac)+'" size="'+siz:ele+' style="color:'+col
++(stg?';font-weight:bold':'')+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:'
++siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">'));
}
function OLquoteMultiNameFonts(f){
var i,v,pM=f.split(',');
@@ -354,34 +343,33 @@ else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i */
// Displays layer
function OLdisp(s){
-if(OLallowmove==0){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer();
+if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer();
if(OLndt)OLshowObject(over);else OLshowid=setTimeout("OLshowObject(over)",1);
OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s;
}
// Decides placement of layer.
function OLplaceLayer(){
-var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0;
-var o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;
+var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0,
+o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;
if(!OLkht&&o&&o.clientWidth)iWd=o.clientWidth;
else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth));
if(SB>20)SB=20;iWd=o3_frame.innerWidth;}
pgLeft=(OLie4)?o.scrollLeft:o3_frame.pageXOffset;
-if(OLie55&&OLfilterPI&&o3_filtershadow)SB=CX=5;else
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else
if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0);
LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);}
if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){
-if(o3_ref!=""){
-X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filtershadow){if(o3_refp=='UR'||o3_refp=='LR')X -= 5;}
-else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){
-if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X += o3_shadowx;
-else if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X -= o3_shadowx;}
+if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='UR'||o3_refp=='LR')X-=5;}
+else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){
+if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else
+if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;}
}else{if(o3_midx!=null){
X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx);
}else{if(o3_relx!=null){
if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB;
-}else{
-X=o3_fixx+LM;}}}
+}else{X=o3_fixx+LM;}}}
}else{
if(o3_hauto){
if(o3_hpos==LEFT&&OLx-pgLeft<iWd/2&&OLx-pWd-o3_offsetx<pgLeft+LM)o3_hpos=RIGHT;else
@@ -398,13 +386,12 @@ if(!OLkht&&!nsb&&o&&o.clientHeight)iHt=o.clientHeight; else if(o3_frame.innerHeight)iHt=o3_frame.innerHeight;
if(OLbubblePI&&o3_bubble)pHt=OLbubbleHt;else pHt=OLns4?over.clip.height:over.offsetHeight;
if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){TM=(o3_shadowy<0)?Math.abs(o3_shadowy):0;
-if(OLie55&&OLfilterPI&&o3_filtershadow)BM=CY=5;else
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)BM=CY=5;else
BM=(o3_shadowy>0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);}
if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){
-if(o3_ref!=""){
-Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filtershadow){if(o3_refp=='LL'||o3_refp=='LR')Y -= 5;}
-else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){
-if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y+=o3_shadowy;else
+if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){
+if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else
if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;}
}else{if(o3_midy!=null){
Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy);
@@ -434,54 +421,56 @@ return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement }
// Gets location of REFerence object
-function OLgetRefXY(r){
-var mn=r,mr=OLgetRef(mn),o,of,rXY;
-if(!mr)return [null,null];
-o=mr;rXY=[o3_refx,o3_refy];
-if(OLns4){if(typeof mr.length!='undefined'&&mr.length>1){
-o=mr[0];rXY[0]+=mr[0].x+mr[1].pageX;rXY[1]+=mr[0].y+mr[1].pageY;
-}else{if((mr.toString().indexOf('Image')!= -1)||(mr.toString().indexOf('Anchor')!= -1)){
-rXY[0]+=mr.x;rXY[1]+=mr.y;}else{rXY[0]+=mr.pageX;rXY[1]+=mr.pageY;}}
-}else{rXY[0]+=OLpageLoc(mr,'Left');rXY[1]+=OLpageLoc(mr,'Top');}
-of=OLgetRefOffsets(o);rXY[0]+=of[0];rXY[1]+=of[1];
+function OLgetRefXY(r,d){
+var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of;
+if(!o)return [null,null];
+if(OLns4){if(typeof o.length!='undefined'&&o.length>1){
+ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY;
+}else{if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){
+rXY[0]+=o.x;rXY[1]+=o.y;}else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}}
+}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');}
+of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1];
return rXY;
}
-function OLgetRef(l){var r=OLgetRefById(l);return (r)?r:OLgetRefByName(l);}
+function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);}
// Seeks REFerence by id
function OLgetRefById(l,d){
-var r="",j;l=(l||'overDiv');d=(d||o3_frame.document);
-if(OLie4&&d.all){return d.all[l];}else if(d.getElementById){return d.getElementById(l);
-}else if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];
+l=(l||'overDiv');d=(d||o3_frame.document);var j,r;
+if(OLie4&&d.all)return d.all[l];if(d.getElementById)return d.getElementById(l);
+if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];
for(j=0;j<d.layers.length;j++){r=OLgetRefById(l,d.layers[j].document);if(r)return r;}}
return null;
}
-// Seeks REFerence by name (for img and a)
+// Seeks REFerence by name
function OLgetRefByName(l,d){
-var r=null,j;d=(d||o3_frame.document);
-if(typeof d.images[l]!='undefined'&&d.images[l]){return d.images[l];
-}else if(typeof d.anchors[l]!='undefined'&&d.anchors[l]){return d.anchors[l];
-}else if(d.layers&&d.layers.length>0){
-for(j=0;j<d.layers.length;j++){r=OLgetRefByName(l,d.layers[j].document);
-if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}}
+d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'):
+OLns6?d.getElementsByTagName('iframe'):null;
+if(typeof d.images!='undefined'&&d.images[l])return d.images[l];
+if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l];
+if(v)for(j=0;j<v.length;j++)if(v[j].name==l)return v[j];
+if(d.layers&&d.layers.length>0)for(j=0;j<d.layers.length;j++){
+r=OLgetRefByName(l,d.layers[j].document);
+if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}
return null;
}
// Gets layer vs REFerence offsets
function OLgetRefOffsets(o){
-var mc=o3_refc.toUpperCase(),mp=o3_refp.toUpperCase(),mW=0,mH=0,pW=0,pH=0,off=[0,0];
+var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0];
pW=(OLbubblePI&&o3_bubble)?o3_width:OLns4?over.clip.width:over.offsetWidth;
pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?over.clip.height:over.offsetHeight;
-if((!OLop7)&&o.toString().indexOf('Image')!= -1){mW=o.width;mH=o.height;
-}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){mc=o3_refc='UL';}else{
-mW=(OLns4)?o.clip.width:o.offsetWidth;mH=(OLns4)?o.clip.height:o.offsetHeight;}
-if(mc=='UL'){off=(mp=='UR')?[-pW,0]:(mp=='LL')?[0,-pH]:(mp=='LR')?[-pW,-pH]:[0,0];
-}else if(mc=='UR'){off=(mp=='UR')?[mW-pW,0]:(mp=='LL')?[mW,-pH]:(mp=='LR')?[mW-pW,-pH]:[mW,0];
-}else if(mc=='LL'){off=(mp=='UR')?[-pW,mH]:(mp=='LL')?[0,mH-pH]:(mp=='LR')?[-pW,mH-pH]:[0,mH];
-}else if(mc=='LR'){off=(mp=='UR')?[mW-pW,mH]:(mp=='LL')?[mW,mH-pH]:(mp=='LR')?[mW-pW,mH-pH]:
-[mW,mH];}
-return off;
+if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height;
+}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{
+W=(OLns4)?o.clip.width:o.offsetWidth;H=(OLns4)?o.clip.height:o.offsetHeight;}
+if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);H+=2*parseInt(o.border);}
+if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0];
+}else if(c=='UR'){of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0];
+}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:(p=='LR')?[-pW,H-pH]:[0,H];
+}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]:
+[W,H];}
+return of;
}
// Gets x or y location of object
@@ -494,11 +483,13 @@ return l; // Moves layer
function OLmouseMove(e){
var e=(e||event);
+OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick);
OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
-if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById())){
+if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById()
+||(OLovertwoPI&&over2==over&&over==OLgetRefById('overDiv2')))){
OLplaceLayer();if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}
-if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)cClick();else
-{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_offdelay);}
+if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)OLcC();else
+{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("OLcC()",o3_offdelay);}
}
// Capture mouse and chain other scripts.
@@ -515,12 +506,12 @@ OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE); PARSING
*/
function OLparseTokens(pf,ar){
-var i,v,md= -1,par=(pf!='ol_'),e=eval,p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);
+var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);
for(i=0;i< ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;}
else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0;
}else{
-if(ar[i]==INARRAY){OLudf=0;e(pf+'text=ol_texts['+ar[++i]+']');continue;}
-if(ar[i]==CAPARRAY){e(pf+'cap=ol_caps['+ar[++i]+']');continue;}
+if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[++i]+']');continue;}
+if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[++i]+']');continue;}
if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;}
if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;}
if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;}
@@ -545,9 +536,9 @@ if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;} if(ar[i]==BASE){p(ar[++i],pf+'base');continue;}
if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;}
if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus';
-e(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}
+eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}
if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus';
-e(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}
+eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}
if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;}
if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;}
if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;}
@@ -594,6 +585,7 @@ if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;} if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;}
if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;}
if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;}
+if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;}
if(ar[i]==DONOTHING){continue;}
i=OLparseCmdLine(pf,i,ar);}}
if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function();
@@ -611,6 +603,14 @@ if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else
if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLprintPI)OLprintDims();
+}
+function OLdecode(){
+var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826)
+&&typeof decodeURIComponent?decodeURIComponent:u;if(typeof(window.TypeError)=='function'){
+if(re.test(t)){eval(new Array('try{','o3_text=d(t);','}catch(e){','o3_text=u(t);',
+'}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){',
+'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);}
}
/*
@@ -625,6 +625,7 @@ if(OLns4){over.document.write(t);over.document.close(); domfrag=range.createContextualFragment(t);
while(over.hasChildNodes()){over.removeChild(over.lastChild);}
over.appendChild(domfrag);}
+if(OLprintPI)over.print=o3_print?t:null;
}
// Makes object visible
@@ -661,10 +662,9 @@ if(!c)o3_close=""; over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}}
}
function OLcursorOff(){
-if(OLovertwoPI&&over==over2)return false;
-var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight;
-var left=parseInt(o.left),top=parseInt(o.top);
-var right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);
+var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight,
+left=parseInt(o.left),top=parseInt(o.top),
+right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);
if(OLx<left||OLx>right||OLy<top||OLy>bottom)return true;
return false;
}
@@ -672,38 +672,26 @@ return false; /*
REGISTRATION
*/
-var OLcmdLine=null,OLrunTime=null;
function OLsetRunTimeVar(){
-if(OLrunTime&&OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();
+if(OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();
}
function OLparseCmdLine(pf,i,ar){
-if(OLcmdLine&&OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){
+if(OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){
var j=OLcmdLine[k](pf,i,ar);if(j>-1){i=j;break;}}}
return i;
}
-function OLisFunc(f){
-var r=1;
-if(typeof f=='object'){for(var i=0;i<f.length;i++){
-if(typeof f[i]=='function')continue;r=0;break;}
-}else if(typeof f!='function')r=0;
-return r;
-}
function OLregCmds(c){
if(typeof c!='string')return;
var pM=c.split(',');pMtr=pMtr.concat(pM);
for(var i=0;i<pM.length;i++)eval(pM[i].toUpperCase()+'='+pmCnt++);
}
function OLregRunTimeFunc(f){
-if(OLisFunc(f)){
-if(!OLrunTime)OLrunTime=new Array();
if(typeof f=='object')OLrunTime=OLrunTime.concat(f);
-else OLrunTime[OLrunTime.length++]=f;}
+else OLrunTime[OLrunTime.length++]=f;
}
function OLregCmdLineFunc(f){
-if(OLisFunc(f)){
-if(!OLcmdLine)OLcmdLine=new Array();
if(typeof f=='object')OLcmdLine=OLcmdLine.concat(f);
-else OLcmdLine[OLcmdLine.length++]=f;}
+else OLcmdLine[OLcmdLine.length++]=f;
}
OLloaded=1;
diff --git a/httemplate/elements/overlibmws_crossframe.js b/httemplate/elements/overlibmws_crossframe.js new file mode 100644 index 000000000..6b21c42e8 --- /dev/null +++ b/httemplate/elements/overlibmws_crossframe.js @@ -0,0 +1,44 @@ +/*
+ overlibmws_crossframe.js plug-in module - Copyright Foteos Macrides 2003-2006
+ For support of FRAME.
+ Initial: August 3, 2003 - Last Revised: November 2, 2004
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+OLregCmds('frame');
+
+function OLparseCrossframe(pf,i,ar){
+var k=i,v;
+if(k<ar.length){
+if(ar[k]==FRAME){v=ar[++k];if(pf=='ol_')ol_frame=v;else OLoptFRAME(v);return k;}}
+return -1;
+}
+
+function OLgetFrameRef(thisFrame,ofrm){
+var i,v,retVal='';for(i=0;i<thisFrame.length;i++){if((((thisFrame[i].length>0)))&&(((OLns4))||
+((OLie4)&&(v=thisFrame[i].document.all.tags('iframe'))!=null&&v.length==0)||
+((OLns6)&&(v=thisFrame[i].document.getElementsByTagName('iframe'))!=null&&v.length==0))){
+retVal=OLgetFrameRef(thisFrame[i],ofrm);if(retVal=='')continue;}
+else if(thisFrame[i]!=ofrm)continue;retVal='['+i+']'+retVal;break;}
+return retVal;
+}
+
+function OLoptFRAME(frm){
+o3_frame=OLmkLyr('overDiv',frm)?frm:self;if(o3_frame!=self){
+var l,tFrm=OLgetFrameRef(top.frames,o3_frame),sFrm=OLgetFrameRef(top.frames,ol_frame);
+if(sFrm.length==tFrm.length) {l=tFrm.lastIndexOf('[');if(l){
+while(sFrm.substring(0,l)!=tFrm.substring(0,l))l=tFrm.lastIndexOf('[',l-1);
+tFrm=tFrm.substr(l);sFrm=sFrm.substr(l);}}var i,k,cnt=0,p='',str=tFrm;
+while((k=str.lastIndexOf('['))!= -1){cnt++;str=str.substring(0,k);}
+for(i=0;i<cnt;i++)p=p+'parent.';OLfnRef=p+'frames'+sFrm+'.';}
+}
+
+OLregCmdLineFunc(OLparseCrossframe);
+
+OLcrossframePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_draggable.js b/httemplate/elements/overlibmws_draggable.js index 14e4a6062..0d25f842e 100644 --- a/httemplate/elements/overlibmws_draggable.js +++ b/httemplate/elements/overlibmws_draggable.js @@ -1,7 +1,7 @@ /*
overlibmws_draggable.js plug-in module - Copyright Foteos Macrides 2002=2005
For support of the DRAGGABLE feature.
- Initial: August 24, 2002 - Last Revised: January 12, 2005
+ Initial: August 24, 2002 - Last Revised: March 2, 2006
See the Change History and Command Reference for overlibmws via:
http://www.macridesweb.com/oltest/
@@ -37,7 +37,7 @@ function OLsetDrgCur(d){if(!OLns4)over.style.cursor=(d?'move':'auto');} function OLgrabEl(e){
var e=(e||event);
-var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(!OLop7?e.altKey:e.ctrlKey));o3_dragging=1;
+var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(e.altKey||(OLop7&&e.ctrlKey)));o3_dragging=1;
if(cKy){OLsetDrgCur(0);document.onmouseup=function(){OLsetDrgCur(1);o3_dragging=0;}
return(OLns4?routeEvent(e):true);}
OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html index 0510d327d..a53300f53 100644 --- a/httemplate/elements/pager.html +++ b/httemplate/elements/pager.html @@ -1,42 +1,55 @@ -<% - - my %opt = @_; - - my $pager = ''; - if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) { - unless ( $opt{'offset'} == 0 ) { - $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'}); -%> - - <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A> - -<% - } - my $page = 0; - for ( my $poff = 0; $poff < $opt{'total'}; $poff += $opt{'maxrecords'} ) { - $page++; - if ( $opt{'offset'} == $poff ) { -%> - - <FONT SIZE="+2"><%= $page %></FONT> - -<% - } else { - $cgi->param('offset', $poff); -%> - - <A HREF="<%= $cgi->self_url %>"><%= $page %></A> - -<% - } - } - unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) { - $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'}); -%> - - <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A> - -<% - } - } -%> +% my %opt = @_; +% my $pager = ''; +% +% if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) { +% +% unless ( $opt{'offset'} == 0 ) { +% $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'}); + + <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A> + +% } +% +% my $page = 0; +% my $prevpage = 0; +% my $over = 0; +% my $step = $opt{total} / 10; # 10 evenly spaced +% for ( my $poff = 0; $poff < $opt{total}; $poff += $opt{maxrecords} ) { +% $page++; +% +% next unless +% $page <= 4 #first four +% || $page >= ( $opt{total} / $opt{maxrecords} ) - 3 #last four +% || abs( ($opt{offset}-$poff) / $opt{maxrecords} ) <= 3 #w/i 3 of current +% || $poff > $over # evenly spaced +% ; +% +% $over += $step if $poff > $over; +% +% if ( $opt{'offset'} == $poff ) { + + <FONT SIZE="+2"><% $page %></FONT> + +% } else { +% $cgi->param('offset', $poff); +% +% if ( $page > $prevpage+1 ) { + ... +% } + + <A HREF="<% $cgi->self_url %>"><% $page %></A> + +% } +% +% $prevpage = $page; +% +% } +% +% unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) { +% $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'}); + + <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A> +% +% } +% +% } diff --git a/httemplate/elements/phonenumber.html b/httemplate/elements/phonenumber.html new file mode 100644 index 000000000..ffbd8c100 --- /dev/null +++ b/httemplate/elements/phonenumber.html @@ -0,0 +1,22 @@ +% +% my( $number, %opt ) = @_; +% my $conf = new FS::Conf; +% ( my $snumber = $number ) =~ s/\D//g; +% + +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/iframecontentmws.js"></SCRIPT> +% if ( length($number) ) { + + <% $number %> +% if ( $opt{'callable'} && $conf->config('vonage-username') ) { + + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('https://secure.click2callu.com/tpcc/makecall?username=<% $conf->config('vonage-username') %>&password=<% $conf->config('vonage-password') %>&fromnumber=<% $conf->config('vonage-fromnumber')%>&tonumber=1<% $snumber %>', 240, 64, 'call_popup'), CAPTION, 'Initiating call', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE, WIDTH, 240, HEIGHT, 64 ); return false;" TITLE="Call this number"><IMG SRC="<%$fsurl%>images/red_telephone_mimooh_01.png" BORDER=0 ALT="Call this number"></A> +% } +% } else { + + +% } + diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index 7844f5678..1c96a54ac 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -1,24 +1,26 @@ -<% - my( $formname, $fields, $action, $url_or_message, $key ) = @_; - $key = '' unless defined $key; +% +% my( $formname, $fields, $action, $url_or_message, $key ) = @_; +% $key = '' unless defined $key; +% +% my $url_or_message_link; +% if ( ref($url_or_message) ) { #its a message or something +% $url_or_message_link = +% 'message='. uri_escape( $url_or_message->{'message'} ) +% } else { +% $url_or_message_link = "url=$url_or_message"; +% } +% - my $url_or_message_link; - if ( ref($url_or_message) ) { #its a message or something - $url_or_message_link = - 'message='. uri_escape( $url_or_message->{'message'} ) - } else { - $url_or_message_link = "url=$url_or_message"; - } -%> -<%= include('/elements/xmlhttp.html', +<% include('/elements/xmlhttp.html', 'method' => 'POST', 'url' => $action, 'subs' => [ 'start_job' ], + 'key' => $key, ) %> -<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_iframe.js"></SCRIPT> <SCRIPT TYPE="text/javascript"> function OLiframeContent(src, width, height, name) { return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"' @@ -26,20 +28,22 @@ function OLiframeContent(src, width, height, name) { +'<div>[iframe not supported]</div></iframe>'); } -function <%=$key%>process () { +function <%$key%>process () { - //alert('<%=$key%>process for form <%=$formname%>'); + //alert('<%$key%>process for form <%$formname%>'); - document.<%=$formname%>.submit.disabled=true; + if ( document.<%$formname%>.submit.disabled == false ) { + document.<%$formname%>.submit.disabled=true; + } - overlib( 'Submitting job to server...', WIDTH, 432, HEIGHT, 136, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); var Hash = new Array(); var x = 0; var fieldName; - for (var i = 0; i<document.<%=$formname%>.elements.length; i++) { - field = document.<%=$formname%>.elements[i]; - if ( <%= join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %> + for (var i = 0; i<document.<%$formname%>.elements.length; i++) { + field = document.<%$formname%>.elements[i]; + if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %> ) { if ( field.type == 'select-multiple' ) { @@ -53,7 +57,7 @@ function <%=$key%>process () { } } else if ( ( field.type != 'radio' && field.type != 'checkbox' ) || ( ( field.type == 'radio' || field.type == 'checkbox' ) - && document.<%=$formname%>.elements[i].checked + && document.<%$formname%>.elements[i].checked ) ) { @@ -64,17 +68,17 @@ function <%=$key%>process () { } // jsrsPOST = true; - // jsrsExecute( '<%= $action %>', <%=$key%>myCallback, 'start_job', Hash ); + // jsrsExecute( '<% $action %>', <%$key%>myCallback, 'start_job', Hash ); - //alert('start_job( ' + Hash + ', <%=$key%>myCallback )' ); + //alert('start_job( ' + Hash + ', <%$key%>myCallback )' ); //alert('start_job()' ); - start_job( Hash, <%=$key%>myCallback ); + <%$key%>start_job( Hash, <%$key%>myCallback ); } -function <%=$key%>myCallback( jobnum ) { +function <%$key%>myCallback( jobnum ) { - overlib( OLiframeContent('<%=$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%=$url_or_message_link%>;formname=<%=$formname%>' , 432, 136, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); } diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html index 200f97d9b..cda704a12 100644 --- a/httemplate/elements/progress-popup.html +++ b/httemplate/elements/progress-popup.html @@ -1,28 +1,29 @@ -<% - my $jobnum = $cgi->param('jobnum'); - my $url = $cgi->param('url'); - my $message = $cgi->param('message'); - my $formname = scalar($cgi->param('formname')); -%> +% +% my $jobnum = $cgi->param('jobnum'); +% my $url = $cgi->param('url'); +% my $message = $cgi->param('message'); +% my $formname = scalar($cgi->param('formname')); +% + <HTML> <HEAD> <TITLE></TITLE> </HEAD> <BODY BGCOLOR="#ccccff" onLoad="refreshStatus()"> -<%= include('/elements/xmlhttp.html', +<% include('/elements/xmlhttp.html', 'url' => $p.'elements/jsrsServer.html', 'subs' => [ 'job_status' ], ) %> -<SCRIPT TYPE="text/javascript" src="../elements/qlib/control.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" src="../elements/qlib/imagelist.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" src="../elements/qlib/progress.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/control.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/imagelist.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/progress.js"></SCRIPT> <SCRIPT TYPE="text/javascript"> function refreshStatus () { - //jsrsExecute( '<%=$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<%= $jobnum %>' ); + //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' ); - job_status( '<%= $jobnum %>', updateStatus ); + job_status( '<% $jobnum %>', updateStatus ); } function updateStatus( status_statustext ) { @@ -37,26 +38,34 @@ function updateStatus( status_statustext ) { document.getElementById("progress_percent").innerHTML = statustext + '%'; bar1.set(statustext); bar1.update; - //jsrsExecute( '<%=$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<%= $jobnum %>' ); - job_status( '<%= $jobnum %>', updateStatus ); + //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' ); + job_status( '<% $jobnum %>', updateStatus ); } else if ( status.indexOf('complete') > -1 ) { -<% if ( $message ) { %> - document.getElementById("progress_message").innerHTML = "<%= $message %>"; +% if ( $message ) { + + document.getElementById("progress_message").innerHTML = "<% $message %>"; document.getElementById("progress_bar").innerHTML = ''; document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">'; document.getElementById("progress_jobnum").innerHTML = ''; - parent.document.<%=$formname%>.submit.disabled=false; -<% } elsif ( $url ) { %> - window.top.location.href = '<%= $url %>'; -<% } else { %> + if ( parent.document.<%$formname%>.submit.disabled == true ) { + parent.document.<%$formname%>.submit.disabled=false; + } +% } elsif ( $url ) { + + window.top.location.href = '<% $url %>'; +% } else { + alert('job done but no url or message specified'); -<% } %> +% } + } else if ( status.indexOf('error') > -1 ) { document.getElementById("progress_message").innerHTML = '<FONT SIZE="+1" COLOR="#FF0000">Error: ' + statustext + '</FONT>'; document.getElementById("progress_bar").innerHTML = ''; document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">'; document.getElementById("progress_jobnum").innerHTML = ''; - parent.document.<%=$formname%>.submit.disabled=false; + if ( parent.document.<%$formname%>.submit.disabled == true ) { + parent.document.<%$formname%>.submit.disabled=false; + } } else { alert('XXX unknown status returned from server: ' + status); } @@ -64,7 +73,7 @@ function updateStatus( status_statustext ) { } </SCRIPT> - <TABLE> + <TABLE WIDTH="100%"> <TR> <TD ALIGN="center" ID="progress_message"> Server processing job... @@ -73,7 +82,7 @@ function updateStatus( status_statustext ) { <TD ALIGN="center" ID="progress_bar"> <SCRIPT TYPE="text/javascript"> // Create imagelist - SEGS = new QImageList(4, 23, "../images/progressbar-empty.png", "../images/progressbar-full.png"); + SEGS = new QImageList(4, 23, "<%$fsurl%>images/progressbar-empty.png", "<%$fsurl%>images/progressbar-full.png"); // Create bars bar1 = new QProgress(null, "bar1", SEGS, 100); // bar1.set(0); @@ -86,7 +95,7 @@ function updateStatus( status_statustext ) { </TD> </TR><TR> <TD ALIGN="center" ID="progress_jobnum"> - (progress of job #<%= $jobnum %>) + (progress of job #<% $jobnum %>) </TD> </TR> </TABLE> diff --git a/httemplate/elements/search-cust_main.html b/httemplate/elements/search-cust_main.html new file mode 100644 index 000000000..f2b17eacb --- /dev/null +++ b/httemplate/elements/search-cust_main.html @@ -0,0 +1,164 @@ +% +% my( %opt ) = @_; +% $opt{'field_name'} ||= 'custnum'; +% +% my $cust_main = ''; +% if ( $opt{'value'} ) { +% $cust_main = qsearchs( +% 'table' => 'cust_main', +% 'hashref' => { 'custnum' => $opt{'value'} }, +% 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql, +% ); +% } +% + + +<INPUT TYPE="hidden" NAME="<% $opt{'field_name'} %>" VALUE="<% $opt{'value'} %>"> + +<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... --> + +<INPUT TYPE="text" NAME="<% $opt{'field_name'} %>_search" ID="<% $opt{'field_name'} %>_search" SIZE="32" VALUE="<% $cust_main ? $cust_main->name : '(cust #, name or company)' %>" onFocus="clearhint_<% $opt{'field_name'} %>_search(this);" onClick="clearhint_<% $opt{'field_name'} %>_search(this);" onChange="smart_<% $opt{'field_name'} %>_search(this);"> + +<SELECT NAME="<% $opt{'field_name'} %>_select" ID="<% $opt{'field_name'} %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $opt{'field_name'} %>(this);"> +</SELECT> + +<% include('/elements/xmlhttp.html', + 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi', + 'subs' => [ 'smart_search' ], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + function clearhint_<% $opt{'field_name'} %>_search (what) { + + what.style.color = '#000000'; + + if ( what.value == '(cust #, name or company)' ) + what.value = ''; + + if ( what.value.indexOf('Customer not found: ') == 0 ) + what.value = what.value.substr(20); + + } + + function smart_<% $opt{'field_name'} %>_search(what) { + + var customer = what.value; + + if ( customer == 'searching...' || customer == '' + || customer.indexOf('Customer not found: ') == 0 ) + return; + + if ( what.getAttribute('magic') == 'nosearch' ) { + what.setAttribute('magic', ''); + return; + } + + //what.value = 'searching...' + what.disabled = true; + what.style.color= '#000000'; + what.style.backgroundColor = '#dddddd'; + + var customer_select = document.getElementById('<% $opt{'field_name'} %>_select'); + + //alert("search for customer " + customer); + + function <% $opt{'field_name'} %>_search_update(customers) { + + //alert('customers returned: ' + customers); + + var customerArray = eval('(' + customers + ')'); + + what.disabled = false; + what.style.backgroundColor = '#ffffff'; + + if ( customerArray.length == 0 ) { + + what.form.<% $opt{'field_name'} %>.value = ''; + + what.value = 'Customer not found: ' + what.value; + what.style.color = '#ff0000'; + + what.style.display = ''; + customer_select.style.display = 'none'; + + } else if ( customerArray.length == 1 ) { + + //alert('one customer found: ' + customerArray[0]); + + what.form.<% $opt{'field_name'} %>.value = customerArray[0][0]; + what.value = customerArray[0][1]; + + what.style.display = ''; + customer_select.style.display = 'none'; + + } else { + + //alert('multiple customers found, have to create select dropdown'); + + //blank the current list + for ( var i = customer_select.length; i >= 0; i-- ) + customer_select.options[i] = null; + + opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000'); + + //add the multiple customers + for ( var s = 0; s < customerArray.length; s++ ) + opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000'); + + opt(customer_select, 'cancel', '(Edit search string)', '#000000'); + + what.style.display = 'none'; + customer_select.style.display = ''; + + } + + } + + smart_search( customer, <% $opt{'field_name'} %>_search_update ); + + + } + + function select_<% $opt{'field_name'} %> (what) { + + var custnum = what.options[what.selectedIndex].value; + var customer = what.options[what.selectedIndex].text; + + var customer_obj = document.getElementById('<% $opt{'field_name'} %>_search'); + + if ( custnum == '' ) { + //what.style.color = '#ff0000'; + + } else if ( custnum == 'cancel' ) { + + customer_obj.style.color = '#000000'; + + what.style.display = 'none'; + customer_obj.style.display = ''; + customer_obj.focus(); + + } else { + + what.form.<% $opt{'field_name'} %>.value = custnum; + + customer_obj.value = customer; + customer_obj.style.color = '#000000'; + + what.style.display = 'none'; + customer_obj.style.display = ''; + + } + + } + + function opt(what,value,text,color) { + var optionName = new Option(text, value, false, false); + optionName.style.color = color; + var length = what.length; + what.options[length] = optionName; + } + +</SCRIPT> + diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html new file mode 100644 index 000000000..299a66a45 --- /dev/null +++ b/httemplate/elements/select-access_group.html @@ -0,0 +1,16 @@ +% +% 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/select-agent.html b/httemplate/elements/select-agent.html index c2a5e4bde..3107ff9d4 100644 --- a/httemplate/elements/select-agent.html +++ b/httemplate/elements/select-agent.html @@ -1,24 +1,19 @@ -<% - my( $agentnum, %opt ) = @_; - - my @agents; - if ( $opt{'agents'} ) { - @agents = @{ $opt{'agents'} }; - } else { - @agents = qsearch( 'agent', { disabled=>'' } ); - } - +% +% my( $agentnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'agents'} +% if $opt{'agents'}; +% +% +<% include( '/elements/select-table.html', + 'table' => 'agent', + 'name_col' => 'agent', + 'value' => $agentnum || '', + 'empty_label' => 'all', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + $FS::CurrentUser::CurrentUser->agentnums_sql. + ' ORDER BY agent', + %opt, + ) %> - -<SELECT NAME="agentnum"> - - <OPTION VALUE="">all</OPTION> - - <% foreach my $agent ( sort { $a->agent cmp $b->agent } @agents ) { %> - - <OPTION VALUE="<%= $agent->agentnum %>"<%= $agentnum == $agent->agentnum ? ' SELECTED' : '' %>><%= $agent->agent %> - - <% } %> - -</SELECT> - diff --git a/httemplate/elements/select-cust-fields.html b/httemplate/elements/select-cust-fields.html new file mode 100644 index 000000000..98feaf85c --- /dev/null +++ b/httemplate/elements/select-cust-fields.html @@ -0,0 +1,24 @@ +% +% my( $cust_fields, %opt ) = @_; +% +% use FS::ConfDefaults; +% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ]; +% +% tie my %hash, 'Tie::IxHash', @{ $opt{'avail_fields'} }; +% +% + + +<SELECT NAME="cust_fields"> + + <OPTION VALUE="">(configured default) +% +% foreach my $value ( keys %hash ) { + + + <OPTION VALUE="<% $value %>"><% $hash{$value} %> +% } + + +</SELECT> + diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html new file mode 100644 index 000000000..71aaa84b6 --- /dev/null +++ b/httemplate/elements/select-cust_pkg-status.html @@ -0,0 +1,21 @@ +<SELECT NAME="status" <% $opt{'onchange'} %>> + + <OPTION VALUE="">all + +% foreach my $option ( @{ $opt{'statuses'} } ) { + <OPTION VALUE="<% $option %>" <% $option eq $status ? 'SELECTED' : '' %>><% $option %> +% } + +</SELECT> +<%init> + my( $status, %opt ) = @_; + + $opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } ) + + if ( exists $opt{'onchange'} && $opt{'onchange'} ) { + $opt{'onchange'} = ' onChange="' . $opt{'onchange'}. '"'; + } else { + $opt{'onchange'} = ''; + } + +</%init> diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html index a0ea74ddd..34476bc94 100644 --- a/httemplate/elements/select-month_year.html +++ b/httemplate/elements/select-month_year.html @@ -1,50 +1,62 @@ -<% +% +% +% my %opt = @_; +% +% my $prefix = $opt{'prefix'} || ''; +% my $disabled = $opt{'disabled'} || ''; +% my $empty = $opt{'empty_option'} || ''; +% my $start_year = $opt{'start_year'}; +% my $end_year = $opt{'end_year'} || '2037'; +% +% my @mon; +% if ( $opt{'show_month_abbr'} ) { +% @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +% } else { +% @mon = ( 1 .. 12 ); +% } +% +% my $date = $opt{'selected_date'} || ''; +% $date = '' if $date eq '-'; +% #$date ||= '01-2000' unless $empty; +% +% my $mon = $opt{'selected_mon'} || 0; +% my $year = $opt{'selected_year'} || 0; +% if ( $date ) { +% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +% } else { +% die "unrecognized expiration date format: $date"; +% } +% } +% +% unless ( $start_year ) { +% my @t = localtime; +% $start_year = $t[5] + 1900; +% } +% $start_year = $year if $start_year > $year && $year > 0; +% +% + + +<SELECT NAME="<% $prefix %>_month" SIZE="1" <% $disabled%>> + +<% $empty ? '<OPTION VALUE="">' : '' %> +% foreach ( 1 .. 12 ) { + + <OPTION<% $_ == $mon ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $mon[$_-1] %> +% } + + +</SELECT>/<SELECT NAME="<% $prefix %>_year" SIZE="1" <% $disabled%>> + +<% $empty ? '<OPTION VALUE="">' : '' %> +% for ( $start_year .. $end_year ) { + + <OPTION<% $_ == $year ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $_ %> +% } - my %opt = @_; - - my $prefix = $opt{'prefix'} || ''; - my $disabled = $opt{'disabled'} || ''; - my $empty = $opt{'empty_option'} || ''; - my $date = $opt{'selected_date'} || ''; - $date = '' if $date eq '-'; - #$date ||= '01-2000' unless $empty; - my $start_year = $opt{'start_year'}; - my $end_year = $opt{'end_year'} || '2037'; - - my( $mon, $year ) = (0, 0); - if ( $date ) { - if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format - ( $mon, $year ) = ( $2, $1 ); - } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { - ( $mon, $year ) = ( $1, $3 ); - } else { - die "unrecognized expiration date format: $date"; - } - } - - unless ( $start_year ) { - my @t = localtime; - $start_year = $t[5] + 1900; - } - $start_year = $year if $start_year > $year && $year > 0; - -%> - -<SELECT NAME="<%= $prefix %>_month" SIZE="1" <%= $disabled%>> - -<%= $empty ? '<OPTION VALUE="">' : '' %> - -<% for ( 1 .. 12 ) { %> - <OPTION<%= $_ == $mon ? ' SELECTED' : '' %> VALUE="<%= $_ %>"><%= $_ %> -<% } %> - -</SELECT>/<SELECT NAME="<%= $prefix %>_year" SIZE="1" <%= $disabled%>> - -<%= $empty ? '<OPTION VALUE="">' : '' %> - -<% for ( $start_year .. $end_year ) { %> - <OPTION<%= $_ == $year ? ' SELECTED' : '' %> VALUE="<%= $_ %>"><%= $_ %> -<% } %> </SELECT> diff --git a/httemplate/elements/select-part_referral.html b/httemplate/elements/select-part_referral.html new file mode 100644 index 000000000..670b70c7c --- /dev/null +++ b/httemplate/elements/select-part_referral.html @@ -0,0 +1,18 @@ +% +% my( $refnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'part_referrals'} +% if $opt{'part_referrals'}; +% +% +<% include( '/elements/select-table.html', + 'table' => 'part_referral', + 'name_col' => 'referral', + 'value' => $refnum, + 'empty_label' => 'Select advertising source', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + FS::part_referral->acl_agentnum_sql(1), + %opt, + ) +%> diff --git a/httemplate/elements/select-pkg_class.html b/httemplate/elements/select-pkg_class.html new file mode 100644 index 000000000..8e873ed66 --- /dev/null +++ b/httemplate/elements/select-pkg_class.html @@ -0,0 +1,16 @@ +% my( $classnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'pkg_class'} +% if $opt{'pkg_class'}; +% +% #warn "***** select-pkg-class: \n". Dumper(%opt); +% +<% include( '/elements/select-table.html', + 'table' => 'pkg_class', + 'name_col' => 'classname', + 'value' => $classnum, + 'empty_label' => '(none)', + 'hashref' => { 'disabled' => '' }, + %opt, + ) +%> diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html new file mode 100644 index 000000000..3235ef627 --- /dev/null +++ b/httemplate/elements/select-table.html @@ -0,0 +1,85 @@ +<SELECT NAME="<% $opt{'element_name'} || $key %>" <% $opt{'element_etc'} %>> + +% while ( @pre_options ) { + + <OPTION VALUE="<% shift(@pre_options) %>"><% shift(@pre_options) %> + +% } + + <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %> + +% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col() } @records ) { +% my $matches = 0; +% ref($opt{'value'}) ? (exists($opt{'value'}->{$record->$key()}) and $matches=1) +% : ($opt{'value'} == $record->$key() and $matches=1); + + <OPTION VALUE="<% $record->$key() %>"<% $matches ? ' SELECTED' : '' %>><% $record->$name_col() %> + +% } + +</SELECT> +<%init> + +##required +# 'table' => 'table_name', +# 'name_col' => 'name_column', +# +##strongly recommended (you want your forms to be "sticky" on errors, right?) +# 'value' => 'current_value', +# +##opt +# 'empty_label' => '', #better specify it though, the default might change +# 'hashref' => {}, +# 'extra_sql' => '', +# 'records' => \@records, #instead of hashref +# 'pre_options' => [ 'value' => 'option' ], #before normal options +# 'element_name' => '', #HTML element name, defaults to the name of +# # the primary key column +# 'element_etc' => '', #additional attributes (i.e. "DISABLED") for the +# #<SELECT> element +# 'debug' => 0, #set true to enable + +my( %opt ) = @_; + +warn "elements/select-table.html: \n". Dumper(%opt) + if exists $opt{debug} && $opt{debug}; + +my $key = dbdef->table($opt{table})->primary_key; #? $opt{primary_key} || + +my $name_col = $opt{name_col}; + +$opt{hashref} ||= {}; + +my @records = (); +if ( $opt{records} ) { + @records = @{ $opt{records} }; +} else { + @records = qsearch( { + 'table' => $opt{table}, + 'hashref' => $opt{hashref}, + 'extra_sql' => ( $opt{extra_sql} || '' ), + }); +} + +unless ( ! $opt{value} + or ref($opt{value}) + or ! exists( $opt{hashref}->{disabled} ) #?? + or grep { $opt{value} == $_->$key() } @records + ) { + delete $opt{hashref}->{disabled}; + $opt{hashref}->{$key} = $opt{value}; + my $record = qsearchs( { + 'table' => $opt{table}, + 'hashref' => $opt{hashref}, + 'extra_sql' => ( $opt{extra_sql} || '' ), + }); + push @records, $record if $record; +} + +if ( ref( $opt{value} ) eq 'ARRAY' ) { + $opt{value} = { map { $_ => 1 } @{$opt{value}} }; +} + +my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : (); + +</%init> diff --git a/httemplate/elements/select-taxclass.html b/httemplate/elements/select-taxclass.html index e5a1abba1..3c1558b72 100644 --- a/httemplate/elements/select-taxclass.html +++ b/httemplate/elements/select-taxclass.html @@ -1,42 +1,38 @@ -<% - my $conf = new FS::Conf; - my $selected_taxclass = scalar(@_) ? shift : ''; -%> - -<% if ( $conf->exists('enable_taxclasses') ) { %> +% if ( $conf->exists('enable_taxclasses') ) { <SELECT NAME="taxclass"> - <% if ( $conf->exists('require_taxclasses') ) { %> - +% if ( $conf->exists('require_taxclasses') ) { <OPTION VALUE="(select)">Select tax class - - <% } else { %> - +% } else { <OPTION VALUE=""> +% } - <% } %> - - <% - my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county') - or die dbh->errstr; - $sth->execute or die $sth->errstr; - my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref}; - my @taxclasses = grep $_, keys %taxclasses; - %> - - <% foreach my $taxclass ( @taxclasses ) { %> - - <OPTION VALUE="<%= $taxclass %>"<%= $taxclass eq $selected_taxclass ? ' SELECTED' : '' %>><%= $taxclass %> - - <% } %> +% foreach my $taxclass ( @{ $opt{'taxclasses'} } ) { + <OPTION VALUE="<% $taxclass %>"<% $taxclass eq $selected_taxclass ? ' SELECTED' : '' %>><% $taxclass %> +% } </SELECT> -<% } else { %> +% } else { + + <INPUT TYPE="hidden" NAME="taxclass" VALUE="<% $selected_taxclass %>"> + +% } + +<%init> + +my( $selected_taxclass, %opt ) = @_; +my $conf = new FS::Conf; + +unless ( $opt{'taxclasses'} ) { + + my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county') + or die dbh->errstr; + $sth->execute or die $sth->errstr; + my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref}; + @{ $opt{'taxclasses'} } = grep $_, keys %taxclasses; - <INPUT TYPE="hidden" NAME="taxclass" VALUE="<%= $selected_taxclass %>"> - -<% } %> - +} +</%init> diff --git a/httemplate/elements/small_custview.html b/httemplate/elements/small_custview.html index e0c22e0c4..9060d897d 100644 --- a/httemplate/elements/small_custview.html +++ b/httemplate/elements/small_custview.html @@ -1,2 +1,3 @@ -<% my $conf = new FS::Conf; %> -<%= small_custview( shift, shift || scalar($conf->config('countrydefault')), @_ ) %> +% my $conf = new FS::Conf; + +<% small_custview( shift, shift || scalar($conf->config('countrydefault')), @_ ) %> diff --git a/httemplate/elements/table-grid.html b/httemplate/elements/table-grid.html index 80611f511..0f532e86b 100644 --- a/httemplate/elements/table-grid.html +++ b/httemplate/elements/table-grid.html @@ -1,8 +1,21 @@ +% +% my %opt = @_; +% $opt{cellspacing} ||= 0; +% $opt{cellpadding} ||= 0; +% +% + <STYLE TYPE="text/css"> + .grid table { border: solid; empty-cells: show } .grid TH { padding-left: 3px; padding-right: 3px; border: 1px solid #dddddd; border-bottom: dashed 1px black; border-right: none } .grid TD { padding-left: 3px; padding-right: 3px; empty-cells: show; border: 1px solid #cccccc; border-bottom: none; border-right: none } + +.inv table { border: none } +.inv TH { border: none } +.inv TD { border: none } + </STYLE> -<TABLE CLASS="grid" CELLSPACING=0 CELLPADDING=0 BORDER=1 BORDERCOLOR="#000000" STYLE="border: solid 1px black; empty-cells: show"> +<TABLE CLASS="grid" CELLSPACING=<% $opt{cellspacing} %> CELLPADDING=<% $opt{cellpadding} %> BORDER=1 BORDERCOLOR="#000000" STYLE="border: solid 1px black; empty-cells: show"> diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html index 3b6108719..8152b65d8 100644 --- a/httemplate/elements/table.html +++ b/httemplate/elements/table.html @@ -1,8 +1,11 @@ -<% - my $color = shift; - if ( $color ) { -%> - <TABLE BGCOLOR="<%= $color %>" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> -<% } else { %> +% +% my $color = shift; +% if ( $color ) { +% + + <TABLE BGCOLOR="<% $color %>" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> +% } else { + <TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> -<% } %> +% } + diff --git a/httemplate/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html index 9fa936bca..9c067dbea 100644 --- a/httemplate/elements/tr-input-beginning_ending.html +++ b/httemplate/elements/tr-input-beginning_ending.html @@ -1,39 +1,63 @@ -<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> - +<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2"> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT> <TR> - <TD ALIGN="right">From: </TD> - <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> + <TD ALIGN="right">From date: </TD> + <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>beginning" ID="<% $opt{prefix} %>beginning_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>beginning_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>beginning_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD> <SCRIPT TYPE="text/javascript"> Calendar.setup({ - inputField: "beginning_text", - ifFormat: "%m/%d/%Y", - button: "beginning_button", + inputField: "<% $opt{prefix} %>beginning_text", + ifFormat: "%m/%d/%Y<% $time_format %>", + button: "<% $opt{prefix} %>beginning_button", align: "BR" + <% $input_time %> }); </SCRIPT> -</TR> +% unless ( $opt{layout} =~ /^h/i ) { #horizontal + +</TR> <TR> - <TD ALIGN="right">To: </TD> - <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> + +% } + + <TD ALIGN="right">To date: </TD> + <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>ending" ID="<% $opt{prefix} %>ending_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>ending_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>ending_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD> <SCRIPT TYPE="text/javascript"> Calendar.setup({ - inputField: "ending_text", - ifFormat: "%m/%d/%Y", - button: "ending_button", + inputField: "<% $opt{prefix} %>ending_text", + ifFormat: "%m/%d/%Y<% $time_format %>", + button: "<% $opt{prefix} %>ending_button", align: "BR" + <% $input_time %> }); </SCRIPT> </TR> <TR> <TD></TD> - <TD> + <TD COLSPAN=<% $opt{layout} =~ /^h/i ? 3 : 1 %>> <FONT SIZE="-1">(leave one or both dates blank for an open-ended search)</FONT> </TD> </TR> +<%init> + +my %opt = @_; + +$opt{prefix} = '' unless defined $opt{prefix}; +$opt{prefix} .= '_' if $opt{prefix}; + +my( $input_time, $time_format, $time_hint ) = ( '', '', '' ); +my( $size, $maxlength ) = ( 11, 10 ); +if ( $opt{'input_time'} ) { + $input_time = ', showsTime: true, timeFormat: "12"'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3 + $time_format = ' %k:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5 + $time_hint = ' h:m:s'; + $size = 21; + $maxlength = 27; +} + +</%init> diff --git a/httemplate/elements/tr-input-date-field.html b/httemplate/elements/tr-input-date-field.html new file mode 100644 index 000000000..11581d5bc --- /dev/null +++ b/httemplate/elements/tr-input-date-field.html @@ -0,0 +1,40 @@ + +<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2"> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT> + +<TR> + <TD ALIGN="right"><% $label %></TD> + <TD> + <INPUT TYPE="text" NAME="<% $name %>" ID="<% $name %>_text" VALUE="<% $value %>"> + <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $name %>_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> +</TR> + +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "<% $name %>_text", + ifFormat: "<% $format %>", + button: "<% $name %>_button", + align: "BR" + }); +</SCRIPT> + + +<%init> +my($name, $value, $label, $format, $usedatetime) = @_; + +$format = "%m/%d/%Y" unless $format; +$label = $name unless $label; + +if ($usedatetime) { + my $dt = DateTime->from_epoch(epoch => $value, time_zone => 'floating'); + $value = $dt->strftime($format) + unless $value eq ''; +}else{ + $value = time2str($format, $value); +} + +</%init> + diff --git a/httemplate/elements/tr-input-lessthan_greaterthan.html b/httemplate/elements/tr-input-lessthan_greaterthan.html new file mode 100644 index 000000000..16c2ed9fc --- /dev/null +++ b/httemplate/elements/tr-input-lessthan_greaterthan.html @@ -0,0 +1,13 @@ +<TR> + <TD ALIGN="right"><% $opt{label} %> less than: </TD> + <TD><INPUT TYPE="text" NAME="<% $opt{field} %>_lt" SIZE=7></TD> +</TR> + +<TR> + <TD ALIGN="right"><% $opt{label} %> greater than: </TD> + <TD><INPUT TYPE="text" NAME="<% $opt{field} %>_gt" SIZE=7></TD> +</TR> + +<%init> + my %opt = @_; +</%init> diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html new file mode 100644 index 000000000..e443ad26a --- /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 ) { + + + <INPUT TYPE="hidden" NAME="groupnum" VALUE=""> +% } else { + + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Access group' %></TD> + <TD> + <% include( '/elements/select-access_group.html', $groupnum, %opt ) %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html index 2227262b6..37b1c1e88 100644 --- a/httemplate/elements/tr-select-agent.html +++ b/httemplate/elements/tr-select-agent.html @@ -1,29 +1,34 @@ -<% - my( $agentnum, %opt ) = @_; +% +% my( $agentnum, %opt ) = @_; +% +% my @agents; +% if ( $opt{'agents'} ) { +% #@agents = @{ $opt{'agents'} }; +% #here is the agent virtualization +% my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href; +% @agents = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agents'} }; +% delete $opt{'agents'}; +% } else { +% @agents = $FS::CurrentUser::CurrentUser->agents; +% } +% +% +% if ( scalar(@agents) == 1 ) { - my @agents; - if ( $opt{'agents'} ) { - @agents = @{ $opt{'agents'} }; - } else { - @agents = qsearch( 'agent', { disabled=>'' } ); - } -%> + <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agents[0]->agentnum %>"> +% } else { -<% if ( scalar(@agents) == 1 ) { %> - - <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agents[0]->agentnum %>"> - -<% } else { %> <TR> - <TD ALIGN="right"><%= $opt{'label'} || 'Agent: ' %></TD> + <TD ALIGN="right"><% $opt{'label'} || 'Agent' %></TD> <TD> - <%= include( '/elements/select-agent.html', $agentnum, + <% include( '/elements/select-agent.html', $agentnum, 'agents' => \@agents, + %opt, ) %> </TD> </TR> +% } -<% } %> diff --git a/httemplate/elements/tr-select-cust-fields.html b/httemplate/elements/tr-select-cust-fields.html new file mode 100644 index 000000000..80562fe3d --- /dev/null +++ b/httemplate/elements/tr-select-cust-fields.html @@ -0,0 +1,15 @@ +% +% my( $cust_fields, %opt ) = @_; +% +% use FS::ConfDefaults; +% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ]; +% +% + + +<TR> + <TD ALIGN="right"><% $opt{'label'} || 'Customer fields' %></TD> + <TD> + <% include( '/elements/select-cust-fields.html', $cust_fields, %opt ) %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-cust_pkg-status.html b/httemplate/elements/tr-select-cust_pkg-status.html new file mode 100644 index 000000000..22ee146cd --- /dev/null +++ b/httemplate/elements/tr-select-cust_pkg-status.html @@ -0,0 +1,14 @@ +% +% my( $status, %opt ) = @_; +% +% $opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } ) +% +% + + +<TR> + <TD ALIGN="right"><% $opt{'label'} || 'Status' %></TD> + <TD> + <% include( '/elements/select-cust_pkg-status.html', $status, %opt ) %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html new file mode 100644 index 000000000..083243d40 --- /dev/null +++ b/httemplate/elements/tr-select-from_to.html @@ -0,0 +1,52 @@ +% +% +% #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); +% my ($curmon,$curyear) = (localtime(time))[4,5]; +% +% #find first month +% my $syear = 1899+$curyear; +% my $smonth = $curmon+1; +% +% #want 12 month by default, not 13 +% $smonth++; +% if ( $smonth > 12 ) { $smonth-=12; $syear++ } +% +% #find last month +% my $eyear = 1900+$curyear; +% my $emonth = $curmon+1; +% +% my %hash = ( +% 'show_month_abbr' => 1, +% 'start_year' => '1999', +% 'end_year' => '2012', #haha, well... +% @_, +% ); +% +% + + +<TR> + <TD ALIGN="right">From: </TD> + <TD> + <% include('/elements/select-month_year.html', + 'prefix' => 'start', + 'selected_mon' => $smonth, + 'selected_year' => $syear, + %hash, + ) + %> + </TD> +</TR> + +<TR> + <TD ALIGN="right">To: </TD> + <TD> + <% include('/elements/select-month_year.html', + 'prefix' => 'end', + 'selected_mon' => $emonth, + 'selected_year' => $eyear, + %hash, + ) + %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html new file mode 100644 index 000000000..35c5b8047 --- /dev/null +++ b/httemplate/elements/tr-select-part_referral.html @@ -0,0 +1,30 @@ +% +% my( $refnum, %opt ) = @_; +% +% $opt{'part_referrals'} ||= +% [ FS::part_referral->all_part_referral( 1 ) ]; #1: include global +% +% my $r = qq!<font color="#ff0000">*</font> !; +% +% +% if ( scalar( @{$opt{'part_referrals'}} ) == 0 ) { +% eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.html and create one or more advertising sources."; +% } elsif ( scalar( @{$opt{'part_referrals'}} ) == 1 ) { +% + + + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $opt{'part_referrals'}->[0]->refnum %>"> +% } else { + + + <TR> + <TH ALIGN="right"><%$r%>Advertising source</TH> + <TD> + <% include( '/elements/select-part_referral.html', $refnum, + 'part_referrals' => $opt{'part_referrals'}, + ) + %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/tr-select-pkg_class.html b/httemplate/elements/tr-select-pkg_class.html new file mode 100644 index 000000000..542142466 --- /dev/null +++ b/httemplate/elements/tr-select-pkg_class.html @@ -0,0 +1,20 @@ +% my( $classnum, %opt ) = @_; +% +% $opt{'pkg_class'} ||= [ qsearch( 'pkg_class', { disabled=>'' } ) ]; +% +% #warn "***** tr-select-pkg-class: \n". Dumper(%opt); +% +% if ( scalar(@{ $opt{'pkg_class'} }) == 0 ) { + + <INPUT TYPE="hidden" NAME="classnum" VALUE=""> + +% } else { + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Package class' %></TD> + <TD> + <% include( '/elements/select-pkg_class.html', $classnum, %opt ) %> + </TD> + </TR> + +% } diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html new file mode 100755 index 000000000..2f8f3a109 --- /dev/null +++ b/httemplate/elements/tr-select-reason.html @@ -0,0 +1,101 @@ + +<SCRIPT TYPE="text/javascript"> + function sh_add<% $name %>() + { + + if (document.getElementById('<% $name %>').selectedIndex == 0){ + <% $controlledbutton ? $controlledbutton.'.disabled = true;' : ';' %> + }else{ + <% $controlledbutton ? $controlledbutton.'.disabled = false;' : ';' %> + } + +%if ($curuser->access_right($access_right)){ + + if (document.getElementById('<% $name %>').selectedIndex == + (document.getElementById('<% $name %>').length - 1)) { + document.getElementById('new<% $name %>').disabled = false; + document.getElementById('new<% $name %>').style.display = 'inline'; + document.getElementById('new<% $name %>Label').style.display = 'inline'; + document.getElementById('new<% $name %>T').disabled = false; + document.getElementById('new<% $name %>T').style.display = 'inline'; + document.getElementById('new<% $name %>TLabel').style.display = 'inline'; + }else{ + document.getElementById('new<% $name %>').disabled = true; + document.getElementById('new<% $name %>').style.display = 'none'; + document.getElementById('new<% $name %>Label').style.display = 'none'; + document.getElementById('new<% $name %>T').disabled = true; + document.getElementById('new<% $name %>T').style.display = 'none'; + document.getElementById('new<% $name %>TLabel').style.display = 'none'; + } + +%} + + } +</SCRIPT> + +<TR> + <TD ALIGN="right">Reason</TD> + <TD> + <SELECT id="<% $name %>" name="<% $name %>" onFocus="sh_add<% $name %>()" onChange="sh_add<% $name %>()"> +% my @reasons = qsearch( { table =>'reason', +% hashref => {}, +% extra_sql => $extra_sql, +% addl_from => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type', +% }); + <OPTION VALUE="" <% ($init_reason eq "") ? 'SELECTED' : '' %>>Select Reason...</OPTION> +% foreach my $reason (@reasons) { + <OPTION VALUE="<% $reason->reasonnum %>" <% ($init_reason == $reason->reasonnum) ? 'SELECTED' : '' %>><% $reason->reason %></OPTION> +% } +% if ($curuser->access_right($access_right)) { + <OPTION VALUE="-1" <% ($init_reason == -1) ? 'SELECTED' : '' %>>Add new reason</OPTION> +% } +% + </SELECT> + </TD> +</TR> + +<TR> + <TD ALIGN="right"> + <P id="new<% $name %>TLabel" style="display:<% $display %>">Reason Type</P> + </TD> + <TD> + <SELECT id="new<% $name %>T" name="new<% $name %>T" disabled="<% $disabled %>" style="display:<% $display %>"> +% for my $type (qsearch( 'reason_type', { 'class' => $class } )){ + <OPTION VALUE="<% $type->typenum %>" <% ($init_type == $type->typenum) ? 'SELECTED' : '' %>><% $type->type %></OPTION> +% } + </SELECT> + </TD> +</TR> + +<TR> + <TD ALIGN="right"> + <P id="new<% $name %>Label" style="display:<% $display %>">New Reason</P> + </TD> + <TD><INPUT id="new<% $name %>" name="new<% $name %>" type="text" value="<% $init_newreason %>" disabled="<% $disabled %>" style="display:<% $display %>"></TD> +</TR> + +<%init> +my($name, $class, $init_reason, $init_type, $init_newreason, $controlledbutton) = @_; +my($extra_sql, $curuser, $access_right, $display, $disabled); + +if ($class eq 'C') { + $access_right='Add on-the-fly cancel reason'; +}elsif ($class eq 'S') { + $access_right='Add on-the-fly suspend reason'; +}else{ + print "illegal class: $class"; +} + +if ($init_reason == -1){ + $display = 'inline'; + $disabled = 'false'; +}else{ + $display = 'none'; + $disabled = 'true'; +} + +$extra_sql = "WHERE class = '$class' ORDER BY reason_type"; +$curuser = $FS::CurrentUser::CurrentUser; + +</%init> + diff --git a/httemplate/elements/tr-select-taxclass.html b/httemplate/elements/tr-select-taxclass.html new file mode 100644 index 000000000..424d5ad02 --- /dev/null +++ b/httemplate/elements/tr-select-taxclass.html @@ -0,0 +1,32 @@ +% if ( ! $conf->exists('enable_taxclasses') +% || scalar(@{ $opt{'taxclasses'} }) == 0 +% ) { + + <INPUT TYPE="hidden" NAME="taxclass" VALUE="<% $taxclass %>"> + +% } else { + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Tax class: ' %></TD> + <TD> + <% include( '/elements/select-taxclass.html', $taxclass, %opt ) %> + </TD> + </TR> + +% } +<%init> + +my( $taxclass, %opt ) = @_; +my $conf = new FS::Conf; + +unless ( $opt{'taxclasses'} ) { + + my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county') + or die dbh->errstr; + $sth->execute or die $sth->errstr; + my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref}; + @{ $opt{'taxclasses'} } = grep $_, keys %taxclasses; + +} + +</%init> diff --git a/httemplate/elements/tr-selectmultiple-part_pkg.html b/httemplate/elements/tr-selectmultiple-part_pkg.html new file mode 100644 index 000000000..bd96d1c3a --- /dev/null +++ b/httemplate/elements/tr-selectmultiple-part_pkg.html @@ -0,0 +1,19 @@ +% +% my( %opt ) = @_; +% + + +<TR> + <TD ALIGN="right"><% $opt{'label'} || 'Packages' %></TD> + <TD> + <% include( '/elements/select-table.html', + 'table' => 'part_pkg', + 'name_col' => 'pkg', + 'value' => '', + 'empty_label' => '(none)', + 'element_etc' => 'multiple', + %opt, + ) + %> + </TD> +</TR> diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css new file mode 100644 index 000000000..97c7da8bb --- /dev/null +++ b/httemplate/elements/xmenu.css @@ -0,0 +1,196 @@ + +.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; + 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 */ + width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */ + overflow: visible; + /* padding: 2px 0px 2px 5px; */ + padding: 1px 0px 1px 5px; + font-size: 14px; +/* font-family: Verdana, Arial, Helvetica, sans-serif; */ + font-weight: bold; + text-decoration: none; + vertical-align: center; + color: black; + border: 1px solid white; +} + +.webfx-menu a:visited { + color: black; + border: 1px solid white; +} + +.webfx-menu a:hover { + color: black; + border: 1px solid #7e0079; +} + +.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 */ + color: white; + + padding: 2px; + + /* 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:link { + color: white; +} + +.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 { + float: right; + border: 0; +/* 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; */ + padding: 3px 1px 3px 5px; + display: block; + font-size: 16px; +/* font-family: Verdana, Arial, Helvetica, sans-serif; */ + font-weight: bold; + text-decoration: none; + color: white; +/* border: 1px solid white; */ + border-bottom: 1px solid white; + width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */ +} + 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 @@ +//<script> +/* + * This script was created by Erik Arvidsson (erik@eae.net) + * for WebFX (http://webfx.eae.net) + * Copyright 2001 + * + * For usage see license at http://webfx.eae.net/license.html + * + * Created: 2001-01-12 + * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks + * 2001-12-20 Added auto positioning and some properties to support this + * 2002-08-13 toString used ' for attributes. Changed to " to allow in args + */ + +// check browsers +var ua = navigator.userAgent; +var opera = /opera [56789]|opera\/[56789]/i.test(ua); +var ie = !opera && /MSIE/.test(ua); +var ie50 = ie && /MSIE 5\.[01234]/.test(ua); +var ie6 = ie && /MSIE [6789]/.test(ua); +var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat"); +var moz = !opera && /gecko/i.test(ua); +var nn6 = !opera && /netscape.*6\./i.test(ua); +var khtml = /KHTML/i.test(ua); + +// define the default values + +webfxMenuDefaultWidth = 154; + +webfxMenuDefaultBorderLeft = 1; +webfxMenuDefaultBorderRight = 1; +webfxMenuDefaultBorderTop = 1; +webfxMenuDefaultBorderBottom = 1; + +webfxMenuDefaultPaddingLeft = 1; +webfxMenuDefaultPaddingRight = 1; +webfxMenuDefaultPaddingTop = 1; +webfxMenuDefaultPaddingBottom = 1; + +webfxMenuDefaultShadowLeft = 0; +webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0; +webfxMenuDefaultShadowTop = 0; +webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0; + + +webfxMenuItemDefaultHeight = 18; +webfxMenuItemDefaultText = "Untitled"; +webfxMenuItemDefaultHref = "javascript:void(0)"; + +webfxMenuSeparatorDefaultHeight = 6; + +webfxMenuDefaultEmptyText = "Empty"; + +webfxMenuDefaultUseAutoPosition = nn6 ? false : true; + + + +// other global constants + +webfxMenuImagePath = ""; + +webfxMenuUseHover = opera ? true : false; +webfxMenuHideTime = 500; +webfxMenuShowTime = 200; + + + +var webFXMenuHandler = { + idCounter : 0, + idPrefix : "webfx-menu-object-", + all : {}, + getId : function () { return this.idPrefix + this.idCounter++; }, + overMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuShowTime <= 0) + this._over(jsItem); + else if ( jsItem ) + //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime); + // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object + this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime); + }, + outMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuHideTime <= 0) + this._out(jsItem); + else if ( jsItem ) + //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime); + this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime); + }, + blurMenu : function (oMenuItem) { + window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime); + }, + _over : function (jsItem) { + if (jsItem.subMenu) { + jsItem.parentMenu.hideAllSubs(); + jsItem.subMenu.show(); + } + else + jsItem.parentMenu.hideAllSubs(); + }, + _out : function (jsItem) { + // find top most menu + var root = jsItem; + var m; + if (root instanceof WebFXMenuButton) + m = root.subMenu; + else { + m = jsItem.parentMenu; + while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar)) + m = m.parentMenu; + } + if (m != null) + m.hide(); + }, + hideMenu : function (menu) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime); + }, + showMenu : function (menu, src, dir) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + if (arguments.length < 3) + dir = "vertical"; + + menu.show(src, dir); + } +}; + +function WebFXMenu() { + this._menuItems = []; + this._subMenus = []; + this.id = webFXMenuHandler.getId(); + this.top = 0; + this.left = 0; + this.shown = false; + this.parentMenu = null; + webFXMenuHandler.all[this.id] = this; +} + +WebFXMenu.prototype.width = webfxMenuDefaultWidth; +WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText; +WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition; + +WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft; +WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight; +WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop; +WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom; + +WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft; +WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight; +WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop; +WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom; + +WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft; +WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight; +WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop; +WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom; + + + +WebFXMenu.prototype.add = function (menuItem) { + this._menuItems[this._menuItems.length] = menuItem; + if (menuItem.subMenu) { + this._subMenus[this._subMenus.length] = menuItem.subMenu; + menuItem.subMenu.parentMenu = this; + } + + menuItem.parentMenu = this; +}; + +WebFXMenu.prototype.show = function (relObj, sDir) { + if (this.useAutoPosition) + this.position(relObj, sDir); + + var divElement = document.getElementById(this.id); + if ( divElement ) { + + divElement.style.left = opera ? this.left : this.left + "px"; + divElement.style.top = opera ? this.top : this.top + "px"; + divElement.style.visibility = "visible"; + + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.width = divElement.offsetWidth; + shimElement.style.height = divElement.offsetHeight; + shimElement.style.top = divElement.style.top; + shimElement.style.left = divElement.style.left; + /*shimElement.style.zIndex = divElement.style.zIndex - 1; */ + shimElement.style.display = "block"; + shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; + } + } + + } + + this.shown = true; + + if (this.parentMenu) + this.parentMenu.show(); +}; + +WebFXMenu.prototype.hide = function () { + this.hideAllSubs(); + var divElement = document.getElementById(this.id); + if ( divElement ) { + divElement.style.visibility = "hidden"; + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.display = "none"; + } + } + } + + this.shown = false; +}; + +WebFXMenu.prototype.hideAllSubs = function () { + for (var i = 0; i < this._subMenus.length; i++) { + if (this._subMenus[i].shown) + this._subMenus[i].hide(); + } +}; + +WebFXMenu.prototype.toString = function () { + var top = this.top + this.borderTop + this.paddingTop; + var str = "<div id='" + this.id + "' class='webfx-menu' style='" + + "width:" + (!ieBox ? + this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight : + this.width) + "px;" + + (this.useAutoPosition ? + "left:" + this.left + "px;" + "top:" + this.top + "px;" : + "") + + (ie50 ? "filter: none;" : "") + + "'>"; + + if (this._menuItems.length == 0) { + str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>"; + } + else { + str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + '>' + this.emptyText + '</span>'; + // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>'; + // loop through all menuItems + for (var i = 0; i < this._menuItems.length; i++) { + var mi = this._menuItems[i]; + str += mi; + if (!this.useAutoPosition) { + if (mi.subMenu && !mi.subMenu.useAutoPosition) + mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop; + top += mi.height; + } + } + + } + + str += "</div>"; + + if ( ie ) { + str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>"; + } + + for (var i = 0; i < this._subMenus.length; i++) { + this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft; + str += this._subMenus[i]; + } + + return str; +}; +// WebFXMenu.prototype.position defined later + +function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) { + this.text = sText || webfxMenuItemDefaultText; + this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref; + this.subMenu = oSubMenu; + if (oSubMenu) + oSubMenu.parentMenuItem = this; + this.toolTip = sToolTip; + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight; +WebFXMenuItem.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href=\"" + this.href + "\"" + + (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") + + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + (this.subMenu ? " unselectable='on' tabindex='-1'" : "") + + ">" + + (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") + + this.text + + "</a>"; +}; + + +function WebFXMenuSeparator() { + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight; +WebFXMenuSeparator.prototype.toString = function () { + return "<div" + + " id='" + this.id + "'" + + (webfxMenuUseHover ? + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'" + : + "") + + "></div>" +}; + +function WebFXMenuBar() { + this._parentConstructor = WebFXMenu; + this._parentConstructor(); +} +WebFXMenuBar.prototype = new WebFXMenu; +WebFXMenuBar.prototype.toString = function () { + var str = "<div id='" + this.id + "' class='webfx-menu-bar'>"; + + // loop through all menuButtons + for (var i = 0; i < this._menuItems.length; i++) + str += this._menuItems[i]; + + str += "</div>"; + + for (var i = 0; i < this._subMenus.length; i++) + str += this._subMenus[i]; + + return str; +}; + +function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) { + this._parentConstructor = WebFXMenuItem; + this._parentConstructor(sText, sHref, sToolTip, oSubMenu); +} +WebFXMenuButton.prototype = new WebFXMenuItem; +WebFXMenuButton.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href='" + this.href + "'" + + (this.toolTip ? " title='" + this.toolTip + "'" : "") + + (webfxMenuUseHover ? + (" onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'") : + ( + " onfocus='webFXMenuHandler.overMenuItem(this)'" + + (this.subMenu ? + " onblur='webFXMenuHandler.blurMenu(this)'" : + "" + ) + )) + + ">" + + (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.right.png'>" : "") + + this.text + + "</a>"; +}; + + + + + +/* Position functions */ + + +function getInnerLeft(el) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) ); + +} + + + +function getLeft(el, debug) { + + if (el == null) return 0; + + //if ( debug ) + // alert ( el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) ); + + return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) ); + +} + + + +function getInnerTop(el) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + return parseInt( getTop(el) + parseInt(getBorderTop(el)) ); + +} + + + +function getTop(el) { + + if (el == null) return 0; + + return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) ); + +} + + + +function getBorderLeft(el) { + + return ie ? + + el.clientLeft : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width")) + ); + +} + + + +function getBorderTop(el) { + + return ie ? + + el.clientTop : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width")) + ); + +} + + + +function opera_getLeft(el) { + + if (el == null) return 0; + + return el.offsetLeft + opera_getLeft(el.offsetParent); + +} + + + +function opera_getTop(el) { + + if (el == null) return 0; + + return el.offsetTop + opera_getTop(el.offsetParent); + +} + + + +function getOuterRect(el, debug) { + + return { + + left: (opera ? opera_getLeft(el) : getLeft(el, debug)), + + top: (opera ? opera_getTop(el) : getTop(el)), + + width: el.offsetWidth, + + height: el.offsetHeight + + }; + +} + + + +// mozilla bug! scrollbars not included in innerWidth/height + +function getDocumentRect(el) { + + return { + + left: 0, + + top: 0, + + width: (ie ? + + (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) : + + window.innerWidth + + ), + + height: (ie ? + + (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) : + + window.innerHeight + + ) + + }; + +} + + + +function getScrollPos(el) { + + return { + + left: (ie ? + + (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) : + + window.pageXOffset + + ), + + top: (ie ? + + (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) : + + window.pageYOffset + + ) + + }; + +} + + +/* end position functions */ + +WebFXMenu.prototype.position = function (relEl, sDir) { + var dir = sDir; + // find parent item rectangle, piRect + var piRect; + if (!relEl) { + var pi = this.parentMenuItem; + if (!this.parentMenuItem) + return; + + relEl = document.getElementById(pi.id); + if (dir == null) + dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal"; + //alert('created RelEl from parent: ' + pi.id); + piRect = getOuterRect(relEl, 1); + } + else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect + //alert('passed a Rect as RelEl: ' + typeof(relEl)); + + piRect = relEl; + } + else { + //alert('passed an element as RelEl: ' + typeof(relEl)); + piRect = getOuterRect(relEl); + } + + var menuEl = document.getElementById(this.id); + var menuRect = getOuterRect(menuEl); + var docRect = getDocumentRect(); + var scrollPos = getScrollPos(); + var pMenu = this.parentMenu; + + if (dir == "vertical") { + if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) { + //alert('piRect.left: ' + piRect.left); + this.left = piRect.left; + if ( ! ie ) + this.left = this.left + 138; + } else if (docRect.width >= menuRect.width) { + //konq (not safari though) winds up here by accident and positions the menus all weird + //alert('docRect.width + scrollPos.left - menuRect.width'); + + this.left = docRect.width + scrollPos.left - menuRect.width; + } else { + //alert('scrollPos.left: ' + scrollPos.left); + this.left = scrollPos.left; + } + + if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top) + + this.top = piRect.top + piRect.height; + + else if (piRect.top - menuRect.height >= scrollPos.top) + + this.top = piRect.top - menuRect.height; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + } + else { + if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top) + + this.top = piRect.top - this.borderTop - this.paddingTop; + + else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0) + + this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + + + + var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0; + + var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0; + + var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0; + + var pMenuBorderRight = pMenu ? pMenu.borderRight : 0; + + + + if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight + + + pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left) + + this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft; + + else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0) + + this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight; + + else if (docRect.width >= menuRect.width) + + this.left = docRect.width + scrollPos.left - menuRect.width; + + else + + this.left = scrollPos.left; + } +}; diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css new file mode 100644 index 000000000..75917031b --- /dev/null +++ b/httemplate/elements/xmenu.top.css @@ -0,0 +1,211 @@ + +.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; + 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 */ + width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */ + overflow: visible; + /* padding: 2px 0px 2px 5px; */ + padding: 1px 0px 1px 5px; + font-size: 14px; +/* font-family: Verdana, Arial, Helvetica, sans-serif; */ + font-weight: bold; + text-decoration: none; + vertical-align: center; + color: black; + border: 1px solid white; +} + +.webfx-menu a:visited { + color: black; + border: 1px solid white; +} + +.webfx-menu a:hover { + color: black; + border: 1px solid #7e0079; +} + +.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 { + /* background: rgb(120,172,255);/*rgb(255,128,0);*/ + /* background: #a097ed; */ + background: #000000; + /* border: 1px solid #7E0079; */ + /* border: 1px solid #000000; */ + /* border: none */ + color: white; + + padding: 2px; + + /* 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")); + + background-color:#333333; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + + margin-right: 4px + +} + +.webfx-menu-bar a:link { + color: white; +} + +.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); */ + + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + +} + +.webfx-menu-bar a .arrow { + /* float: right; */ + border: 0; +/* vertical-align: top; */ +/* width: 3px; */ +/* margin-right: 3px; */ + margin-bottom: 2px; + +} + +.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; */ + padding: 3px 1px 3px 5px; + display: block; + font-size: 16px; +/* font-family: Verdana, Arial, Helvetica, sans-serif; */ + font-weight: bold; + text-decoration: none; + color: white; +/* border: 1px solid white; */ + border-bottom: 1px solid white; + width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */ +} + diff --git a/httemplate/elements/xmenu.top.js b/httemplate/elements/xmenu.top.js new file mode 100644 index 000000000..8d81035a2 --- /dev/null +++ b/httemplate/elements/xmenu.top.js @@ -0,0 +1,671 @@ +//<script> +/* + * This script was created by Erik Arvidsson (erik@eae.net) + * for WebFX (http://webfx.eae.net) + * Copyright 2001 + * + * For usage see license at http://webfx.eae.net/license.html + * + * Created: 2001-01-12 + * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks + * 2001-12-20 Added auto positioning and some properties to support this + * 2002-08-13 toString used ' for attributes. Changed to " to allow in args + */ + +// check browsers +var ua = navigator.userAgent; +var opera = /opera [56789]|opera\/[56789]/i.test(ua); +var ie = !opera && /MSIE/.test(ua); +var ie50 = ie && /MSIE 5\.[01234]/.test(ua); +var ie6 = ie && /MSIE [6789]/.test(ua); +var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat"); +var moz = !opera && /gecko/i.test(ua); +var nn6 = !opera && /netscape.*6\./i.test(ua); +var khtml = /KHTML/i.test(ua); + +// define the default values + +webfxMenuDefaultWidth = 154; + +webfxMenuDefaultBorderLeft = 1; +webfxMenuDefaultBorderRight = 1; +webfxMenuDefaultBorderTop = 1; +webfxMenuDefaultBorderBottom = 1; + +webfxMenuDefaultPaddingLeft = 1; +webfxMenuDefaultPaddingRight = 1; +webfxMenuDefaultPaddingTop = 1; +webfxMenuDefaultPaddingBottom = 1; + +webfxMenuDefaultShadowLeft = 0; +webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0; +webfxMenuDefaultShadowTop = 0; +webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0; + + +webfxMenuItemDefaultHeight = 18; +webfxMenuItemDefaultText = "Untitled"; +webfxMenuItemDefaultHref = "javascript:void(0)"; + +webfxMenuSeparatorDefaultHeight = 6; + +webfxMenuDefaultEmptyText = "Empty"; + +webfxMenuDefaultUseAutoPosition = nn6 ? false : true; + + + +// other global constants + +webfxMenuImagePath = ""; + +webfxMenuUseHover = opera ? true : false; +webfxMenuHideTime = 500; +webfxMenuShowTime = 200; + + + +var webFXMenuHandler = { + idCounter : 0, + idPrefix : "webfx-menu-object-", + all : {}, + getId : function () { return this.idPrefix + this.idCounter++; }, + overMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuShowTime <= 0) + this._over(jsItem); + else if ( jsItem ) + //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime); + // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object + this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime); + }, + outMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuHideTime <= 0) + this._out(jsItem); + else if ( jsItem ) + //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime); + this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime); + }, + blurMenu : function (oMenuItem) { + window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime); + }, + _over : function (jsItem) { + if (jsItem.subMenu) { + jsItem.parentMenu.hideAllSubs(); + jsItem.subMenu.show(); + } + else + jsItem.parentMenu.hideAllSubs(); + }, + _out : function (jsItem) { + // find top most menu + var root = jsItem; + var m; + if (root instanceof WebFXMenuButton) + m = root.subMenu; + else { + m = jsItem.parentMenu; + while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar)) + m = m.parentMenu; + } + if (m != null) + m.hide(); + }, + hideMenu : function (menu) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime); + }, + showMenu : function (menu, src, dir) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + if (arguments.length < 3) + dir = "vertical"; + + menu.show(src, dir); + } +}; + +function WebFXMenu() { + this._menuItems = []; + this._subMenus = []; + this.id = webFXMenuHandler.getId(); + this.top = 0; + this.left = 0; + this.shown = false; + this.parentMenu = null; + webFXMenuHandler.all[this.id] = this; +} + +WebFXMenu.prototype.width = webfxMenuDefaultWidth; +WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText; +WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition; + +WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft; +WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight; +WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop; +WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom; + +WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft; +WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight; +WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop; +WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom; + +WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft; +WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight; +WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop; +WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom; + + + +WebFXMenu.prototype.add = function (menuItem) { + this._menuItems[this._menuItems.length] = menuItem; + if (menuItem.subMenu) { + this._subMenus[this._subMenus.length] = menuItem.subMenu; + menuItem.subMenu.parentMenu = this; + } + + menuItem.parentMenu = this; +}; + +WebFXMenu.prototype.show = function (relObj, sDir) { + if (this.useAutoPosition) + this.position(relObj, sDir); + + var divElement = document.getElementById(this.id); + if ( divElement ) { + + divElement.style.left = opera ? this.left : this.left + "px"; + divElement.style.top = opera ? this.top : this.top + "px"; + divElement.style.visibility = "visible"; + + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.width = divElement.offsetWidth; + shimElement.style.height = divElement.offsetHeight; + shimElement.style.top = divElement.style.top; + shimElement.style.left = divElement.style.left; + /*shimElement.style.zIndex = divElement.style.zIndex - 1; */ + shimElement.style.display = "block"; + shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; + } + } + + } + + this.shown = true; + + if (this.parentMenu) + this.parentMenu.show(); +}; + +WebFXMenu.prototype.hide = function () { + this.hideAllSubs(); + var divElement = document.getElementById(this.id); + if ( divElement ) { + divElement.style.visibility = "hidden"; + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.display = "none"; + } + } + } + + this.shown = false; +}; + +WebFXMenu.prototype.hideAllSubs = function () { + for (var i = 0; i < this._subMenus.length; i++) { + if (this._subMenus[i].shown) + this._subMenus[i].hide(); + } +}; + +WebFXMenu.prototype.toString = function () { + var top = this.top + this.borderTop + this.paddingTop; + var str = "<div id='" + this.id + "' class='webfx-menu' style='" + + "width:" + (!ieBox ? + this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight : + this.width) + "px;" + + (this.useAutoPosition ? + "left:" + this.left + "px;" + "top:" + this.top + "px;" : + "") + + (ie50 ? "filter: none;" : "") + + "'>"; + + if (this._menuItems.length == 0) { + str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>"; + } + else { + str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + '>' + this.emptyText + '</span>'; + // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>'; + // loop through all menuItems + for (var i = 0; i < this._menuItems.length; i++) { + var mi = this._menuItems[i]; + str += mi; + if (!this.useAutoPosition) { + if (mi.subMenu && !mi.subMenu.useAutoPosition) + mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop; + top += mi.height; + } + } + + } + + str += "</div>"; + + if ( ie ) { + str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>"; + } + + for (var i = 0; i < this._subMenus.length; i++) { + this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft; + str += this._subMenus[i]; + } + + return str; +}; +// WebFXMenu.prototype.position defined later + +function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) { + this.text = sText || webfxMenuItemDefaultText; + this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref; + this.subMenu = oSubMenu; + if (oSubMenu) + oSubMenu.parentMenuItem = this; + this.toolTip = sToolTip; + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight; +WebFXMenuItem.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href=\"" + this.href + "\"" + + (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") + + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + (this.subMenu ? " unselectable='on' tabindex='-1'" : "") + + ">" + + (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") + + this.text + + "</a>"; +}; + + +function WebFXMenuSeparator() { + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight; +WebFXMenuSeparator.prototype.toString = function () { + return "<div" + + " id='" + this.id + "'" + + (webfxMenuUseHover ? + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'" + : + "") + + "></div>" +}; + +function WebFXMenuBar() { + this._parentConstructor = WebFXMenu; + this._parentConstructor(); +} +WebFXMenuBar.prototype = new WebFXMenu; +WebFXMenuBar.prototype.toString = function () { + var str = "<div id='" + this.id + "' class='webfx-menu-bar'>"; + + // loop through all menuButtons + for (var i = 0; i < this._menuItems.length; i++) + str += this._menuItems[i]; + + str += "</div>"; + + for (var i = 0; i < this._subMenus.length; i++) + str += this._subMenus[i]; + + return str; +}; + +function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) { + this._parentConstructor = WebFXMenuItem; + this._parentConstructor(sText, sHref, sToolTip, oSubMenu); +} +WebFXMenuButton.prototype = new WebFXMenuItem; +WebFXMenuButton.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href='" + this.href + "'" + + (this.toolTip ? " title='" + this.toolTip + "'" : "") + + (webfxMenuUseHover ? + (" onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'") : + ( + " onfocus='webFXMenuHandler.overMenuItem(this)'" + + (this.subMenu ? + " onblur='webFXMenuHandler.blurMenu(this)'" : + "" + ) + )) + + ">" + + this.text + + (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.down.png'>" : "") + + "</a>"; +}; + + + + + +/* Position functions */ + + +function getInnerLeft(el, debug) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + //if ( debug ) + // alert ( 'getInnerLeft: ' + getLeft(el) + ' - ' + getBorderLeft(el) ); + + return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) ); + +} + + + +function getLeft(el, debug) { + + if (el == null) return 0; + + //if ( debug ) + // alert ( el + ': ' + el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) ); + + return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) ); + +} + + + +function getInnerTop(el) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + return parseInt( getTop(el) + parseInt(getBorderTop(el)) ); + +} + + + +function getTop(el) { + + if (el == null) return 0; + + return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) ); + +} + + + +function getBorderLeft(el) { + + return ie ? + + el.clientLeft : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width")) + ); + +} + + + +function getBorderTop(el) { + + return ie ? + + el.clientTop : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width")) + ); + +} + + + +function opera_getLeft(el) { + + if (el == null) return 0; + + return el.offsetLeft + opera_getLeft(el.offsetParent); + +} + + + +function opera_getTop(el) { + + if (el == null) return 0; + + return el.offsetTop + opera_getTop(el.offsetParent); + +} + + + +function getOuterRect(el, debug) { + + return { + + left: (opera ? opera_getLeft(el) : getLeft(el, debug)), + + top: (opera ? opera_getTop(el) : getTop(el)), + + width: el.offsetWidth, + + height: el.offsetHeight + + }; + +} + + + +// mozilla bug! scrollbars not included in innerWidth/height + +function getDocumentRect(el) { + + return { + + left: 0, + + top: 0, + + width: (ie ? + + (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) : + + window.innerWidth + + ), + + height: (ie ? + + (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) : + + window.innerHeight + + ) + + }; + +} + + + +function getScrollPos(el) { + + return { + + left: (ie ? + + (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) : + + window.pageXOffset + + ), + + top: (ie ? + + (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) : + + window.pageYOffset + + ) + + }; + +} + + +/* end position functions */ + +WebFXMenu.prototype.position = function (relEl, sDir) { + var dir = sDir; + // find parent item rectangle, piRect + var piRect; + if (!relEl) { + var pi = this.parentMenuItem; + if (!this.parentMenuItem) + return; + + relEl = document.getElementById(pi.id); + if (dir == null) + dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal"; + //alert('created RelEl from parent: ' + pi.id); + piRect = getOuterRect(relEl, 1); + } + else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect + //alert('passed a Rect as RelEl: ' + typeof(relEl)); + + piRect = relEl; + } + else { + //alert('passed an element as RelEl: ' + typeof(relEl)); + piRect = getOuterRect(relEl); + } + + var menuEl = document.getElementById(this.id); + var menuRect = getOuterRect(menuEl); + var docRect = getDocumentRect(); + var scrollPos = getScrollPos(); + var pMenu = this.parentMenu; + + if (dir == "vertical") { + if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) { + //alert('piRect.left: ' + piRect.left); + this.left = piRect.left; +// if ( ! ie ) +// this.left = this.left + 138; + } else if (docRect.width >= menuRect.width) { + //konq (not safari though) winds up here by accident and positions the menus all weird + //alert('docRect.width + scrollPos.left - menuRect.width'); + + this.left = docRect.width + scrollPos.left - menuRect.width; + } else { + //alert('scrollPos.left: ' + scrollPos.left); + this.left = scrollPos.left; + } + + if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top) + + this.top = piRect.top + piRect.height; + + else if (piRect.top - menuRect.height >= scrollPos.top) + + this.top = piRect.top - menuRect.height; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + } + else { + if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top) + + this.top = piRect.top - this.borderTop - this.paddingTop; + + else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0) + + this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + + + + var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0; + + var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0; + + var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0; + + var pMenuBorderRight = pMenu ? pMenu.borderRight : 0; + + + + if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight + + + pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left) + + this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft; + + else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0) + + this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight; + + else if (docRect.width >= menuRect.width) + + this.left = docRect.width + scrollPos.left - menuRect.width; + + else + + this.left = scrollPos.left; + } +}; diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html index 28130e501..6efc395f7 100644 --- a/httemplate/elements/xmlhttp.html +++ b/httemplate/elements/xmlhttp.html @@ -1,14 +1,16 @@ -<% - my ( %opt ) = @_; +% +% my ( %opt ) = @_; +% +% my $url = $opt{'url'}; +% my $method = exists($opt{'method'}) ? $opt{'method'} : 'GET'; +% #my @subs = @{ $opt{'subs'}; +% my $key = exists($opt{'key'}) ? $opt{'key'} : ''; +% +% $url .= ( ($url =~ /\?/) ? '&' : '?' ) +% if $method eq 'GET'; +% +% - my $url = $opt{'url'}; - my $method = exists($opt{'method'}) ? $opt{'method'} : 'GET'; - #my @subs = @{ $opt{'subs'}; - - $url .= ( ($url =~ /\?/) ? '&' : '?' ) - if $method eq 'GET'; - -%> <SCRIPT TYPE="text/javascript"> @@ -30,22 +32,22 @@ return A; } +% foreach my $func ( @{$opt{'subs'}} ) { +% +% my $furl = $url; +% $furl =~ s/\"/\\\\\"/; #javascript escape +% +% - <% foreach my $func ( @{$opt{'subs'}} ) { - - my $furl = $url; - $furl =~ s/\"/\\\\\"/; #javascript escape - - %> - function <%=$func%>() { + function <%$key%><%$func%>() { // count args; build URL - var url = "<%=$furl%>"; - var a = <%=$func%>.arguments; + var url = "<%$furl%>"; + var a = <%$key%><%$func%>.arguments; var args; var len; - var content = 'sub=<%= uri_escape($func) %>'; + var content = 'sub=<% uri_escape($func) %>'; if ( a && typeof a == 'object' && a[0].constructor == Array ) { args = a[0]; len = args.length @@ -57,14 +59,14 @@ content = content + "&arg=" + escape(args[i]); content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs - if ( '<%=$method%>' == 'GET' ) { + if ( '<%$method%>' == 'GET' ) { url = url + content; } - //alert('<%=$method%> ' + url); + //alert('<%$method%> ' + url); var xmlhttp = rs_init_object(); - xmlhttp.open("<%=$method%>", url, true); + xmlhttp.open("<%$method%>", url, true); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState != 4) @@ -88,7 +90,7 @@ } } - if ( '<%=$method%>' == 'POST' ) { + if ( '<%$method%>' == 'POST' ) { xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xmlhttp.send(content); @@ -103,7 +105,7 @@ //rs_debug("x_$func_name url = " + url); //rs_debug("x_$func_name waiting.."); } +% } - <% } %> </SCRIPT> diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi new file mode 100644 index 000000000..4070069ac --- /dev/null +++ b/httemplate/graph/cust_bill_pkg.cgi @@ -0,0 +1,121 @@ +<% include('elements/monthly.html', + 'title' => $title. 'Sales Report (Gross)', + 'graph_type' => 'Mountain', + 'items' => \@items, + 'params' => \@params, + 'labels' => \@labels, + 'graph_labels' => \@labels, + 'colors' => \@colors, + 'links' => \@links, + 'remove_empty' => 1, + 'bottom_total' => 1, + 'bottom_link' => "$link;", + 'start_month' => $smonth, + 'start_year' => $syear, + 'end_month' => $emonth, + 'end_year' => $eyear, + 'agentnum' => $agentnum, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +#find first month +my $syear = $cgi->param('start_year'); # || 1899+$curyear; +my $smonth = $cgi->param('start_month'); # || $curmon+1; + +#find last month +my $eyear = $cgi->param('end_year'); # || 1900+$curyear; +my $emonth = $cgi->param('end_month'); # || $curmon+1; + +#XXX or virtual +my( $agentnum, $sel_agent ) = ('', ''); +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + die "agentnum $agentnum not found!" unless $sel_agent; +} +my $title = $sel_agent ? $sel_agent->agent.' ' : ''; + +#false lazinessish w/search/cust_pkg.cgi +my $classnum = 0; +my @pkg_class = (); +if ( $cgi->param('classnum') =~ /^(\d*)$/ ) { + $classnum = $1; + if ( $classnum ) { + @pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); + die "classnum $classnum not found!" unless $pkg_class[0]; + $title .= $pkg_class[0]->classname.' '; + } elsif ( $classnum eq '' ) { + $title .= 'Empty class '; + @pkg_class = ( '(empty class)' ); + } elsif ( $classnum eq '0' ) { + @pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); + push @pkg_class, '(empty class)'; + } +} +#eslaf + +my $hue = 0; +#my $hue_increment = 170; +#my $hue_increment = 145; +my $hue_increment = 125; + +my @items = (); +my @params = (); +my @labels = (); +my @colors = (); +my @links = (); + +my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1"; + +foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) { + + my $col_scheme = Color::Scheme->new + ->from_hue($hue) #->from_hex($agent->color) + ->scheme('analogic') + ; + my @recur_colors = (); + my @onetime_colors = (); + + ### fixup the color handling for package classes... + my $n = 0; + + foreach my $pkg_class ( @pkg_class ) { + + push @items, 'cust_bill_pkg'; + + + push @labels, + ( $sel_agent ? '' : $agent->agent.' ' ). + ( $classnum eq '0' + ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class ) + : '' + ); + + my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0; + my $row_agentnum = $agent->agentnum; + push @params, [ 'classnum' => $row_classnum, + 'agentnum' => $row_agentnum, + ]; + + push @links, "$link;agentnum=$row_agentnum;classnum=$row_classnum;"; + + @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9] + unless @recur_colors; + @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11] + unless @onetime_colors; + push @colors, shift @recur_colors; + + } + + $hue += $hue_increment; + +} + +#use Data::Dumper; +#warn Dumper(\@items); + +</%init> diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html new file mode 100644 index 000000000..f5789a2a2 --- /dev/null +++ b/httemplate/graph/elements/monthly.html @@ -0,0 +1,207 @@ +% +% +% # options example... +% # +% # 'title' => 'Page title', +% # 'items' => \@items, +% # 'params' => \@params, # opt, +% # 'labels' => \@labels, # or \%labels (keys are items) +% # 'graph_labels' => \@graph_labels, # or \%graph_labels, +% # 'colors' => \@colors, # or \%colors, +% # 'links => \@links, # or \%link, #opt +% # 'start_month' => $smonth, +% # 'start_year' => $syear, +% # 'end_month' => $emonth, +% # 'end_year' => $eyear, +% # 'agentnum' => $agentnum, #opt +% # 'nototal' => 1, #opt, +% # 'graph_type' => 'LinesPoints', #opt +% # 'remove_empty' => 1, #opt, +% # 'bottom_total' => 1, #opt, +% +% my(%opt) = @_; +% my @items = @{ $opt{'items'} }; +% +% foreach my $other (qw( labels graph_labels colors links )) { +% #foreach my $other (qw( labels graph_labels colors )) { +% if ( ref($opt{$other}) eq 'HASH' ) { +% $opt{$other} = [ map $opt{$other}{$_}, @items ]; +% } +% } +% +% my $report = new FS::Report::Table::Monthly ( +% +% #'items' => $opt{'items'}, +% 'items' => \@items, +% 'params' => $opt{'params'}, +% 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/ +% ? $opt{'graph_labels'} +% : $opt{'labels'} +% ), +% 'colors' => $opt{'colors'}, +% 'links' => $opt{'links'}, +% +% 'start_month' => $opt{'start_month'}, +% 'start_year' => $opt{'start_year'}, +% 'end_month' => $opt{'end_month'}, +% 'end_year' => $opt{'end_year'}, +% +% 'agentnum' => $opt{'agentnum'}, +% 'remove_empty' => $opt{'remove_empty'}, +% ); +% my $data = $report->data; +% +% if ( $cgi->param('_type') =~ /^(png)$/ ) { +% +% #my $chart = Chart::LinesPoints->new(1024,480); +% #my $chart = Chart::LinesPoints->new(768,480); +% +% my $graph_type = 'LinesPoints'; +% if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain)$/ ) { +% $graph_type = $1; +% } +% my $class = "Chart::$graph_type"; +% +% my $chart = $class->new(976,384); +% +% my $d = 0; +% $chart->set( +% #'min_val' => 0, +% 'legend' => 'bottom', +% 'colors' => { ( +% map { my $color = $_; +% 'dataset'.$d++ => +% [ map hex($_), unpack 'a2a2a2', $color ] +% } +% #@{ $opt{'colors'} } +% @{ $data->{'colors'} } +% ), +% #'grey_background' => [ 211, 211, 211 ], +% 'grey_background' => 'white', +% 'background' => [ 0xe8, 0xe8, 0xe8 ], #grey +% }, +% #'grey_background' => 'false', +% 'legend_labels' => $data->{'item_labels'}, +% 'brush_size' => 4, +% #'pt_size' => 12, +% ); +% +% #my @data = map { $data->{$_} } ( 'label', @items ); +% my @data = @{ $data->{data} }; +% unshift @data, $data->{'label'}; +% +% http_header('Content-Type' => 'image/png' ); +% +% $chart->_set_colors(); +% +% +<% $chart->scalar_png(\@data) %> +% +% +% } else { +% +% my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +% +% +<% include('/elements/header.html', $opt{'title'} ) %> +% $cgi->param('_type', 'png'); + +<IMG SRC="<% $cgi->self_url %>" WIDTH="976" HEIGHT="384"> +<BR> + +<% table('e8e8e8') %> + +<TR> + + <TD></TD> +% foreach my $column ( @{$data->{label}} ) { +% #$column =~ s/^(\d+)\//$mon[$1-1]<BR>/e; +% $column =~ s/^(\d+)\//$mon[$1-1]<BR>/; +% + + <TH><% $column %></TH> +% } +% unless ( $opt{'nototal'} ) { + + <TH>Total</TH> +% } + + +</TR> +% my @bottom_total = (); +% foreach my $row ( @{ $data->{'items'} } ) { +% +% #my $color = shift( @{ $opt{'colors'} } ); +% my $color = shift( @{ $data->{'colors'} } ); +% my $link = shift( @{ $data->{'links'} } ); +% $link = $link ? qq(<A HREF="$link) : ''; +% + + + <TR> + + <TH><FONT COLOR="#<% $color %>"><% shift( @{ $data->{'item_labels'} } ) %></FONT></TH> +% #my $link = exists($opt{'links'}{$row}) +% # ? qq(<A HREF="$opt{'links'}{$row}) +% # : ''; +% my @speriod = @{$data->{speriod}}; +% my @eperiod = @{$data->{eperiod}}; +% my $total = 0; +% +% my $col = 0; +% foreach my $column ( @{ shift( @{$data->{data}} ) } ) { # ( @{$data->{$row}} ) { +% + + + <TD ALIGN="right" BGCOLOR="#ffffff"> + <% $link ? $link. 'begin='. shift(@speriod). ';end='. shift(@eperiod). '">' : '' %><FONT COLOR="#<% $color %>">$<% sprintf("%.2f", $column) %></FONT><% $link ? '</A>' : '' %> + </TD> +% +% $total += $column; +% $bottom_total[$col++] += $column; +% +% } +% unless ( $opt{'nototal'} ) { + + + <TD ALIGN="right" BGCOLOR="#f5f6be"> + <% $link ? $link. 'begin='. ${$data->{speriod}}[0]. ';end='. ${$data->{eperiod}}[-1]. '">' : '' %><FONT COLOR="#<% $color %>">$<% sprintf("%.2f", $total) %></FONT><% $link ? '</A>' : '' %> + </TD> +% $bottom_total[$col++] += $total; +% } + + + </TR> +% } +% if ( $opt{'bottom_total'} ) { +% my @speriod = ( @{$data->{speriod}}, ${$data->{speriod}}[0] ); +% my @eperiod = ( @{$data->{eperiod}}, ${$data->{eperiod}}[-1] ); +% + + + <TR> + <TH>Total</TH> +% foreach my $total ( @bottom_total ) { + + + <TD ALIGN="right" BGCOLOR="#f5f6be"> + <% $opt{'bottom_link'} + ? '<A HREF="'. $opt{'bottom_link'}. + 'begin='. shift(@speriod). + ';end='. shift(@eperiod). '">' + : '' + %>$<% sprintf("%.2f", $total) %><% $opt{'bottom_link'} ? '</A>' : '' %> + + </TD> +% } + + + </TR> +% } + + +</TABLE> + +<% include('/elements/footer.html') %> +% } + diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi deleted file mode 100755 index bb3d23aae..000000000 --- a/httemplate/graph/money_time-graph.cgi +++ /dev/null @@ -1,68 +0,0 @@ -<% - -#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); -my ($curmon,$curyear) = (localtime(time))[4,5]; - -#find first month -my $syear = $cgi->param('syear') || 1899+$curyear; -my $smonth = $cgi->param('smonth') || $curmon+1; - -#find last month -my $eyear = $cgi->param('eyear') || 1900+$curyear; -my $emonth = $cgi->param('emonth') || $curmon+1; -#if ( $emonth++>12 ) { $emonth-=12; $eyear++; } - -#my @labels; -#my %data; - -my @items = qw( invoiced netsales credits payments receipts ); -my %label = ( - 'invoiced' => 'Gross Sales (invoiced)', - 'netsales' => 'Net Sales (invoiced - applied credits)', - 'credits' => 'Credits', - 'payments' => 'Gross Receipts (payments)', - 'receipts' => 'Net Receipts/Cashflow (payments - refunds)', -); -my %color = ( - 'invoiced' => [ 153, 153, 255 ], #light blue - 'netsales' => [ 0, 0, 204 ], #blue - 'credits' => [ 204, 0, 0 ], #red - 'payments' => [ 153, 204, 153 ], #light green - 'receipts' => [ 0, 204, 0 ], #green -); - -my $report = new FS::Report::Table::Monthly ( - 'items' => \@items, - 'start_month' => $smonth, - 'start_year' => $syear, - 'end_month' => $emonth, - 'end_year' => $eyear, -); -my %data = %{$report->data}; - -#my $chart = Chart::LinesPoints->new(1024,480); -#my $chart = Chart::LinesPoints->new(768,480); -my $chart = Chart::LinesPoints->new(976,384); - -my $d = 0; -$chart->set( - #'min_val' => 0, - 'legend' => 'bottom', - 'colors' => { ( map { 'dataset'.$d++ => $color{$_} } @items ), - #'grey_background' => [ 211, 211, 211 ], - 'grey_background' => 'white', - 'background' => [ 0xe8, 0xe8, 0xe8 ], #grey - }, - #'grey_background' => 'false', - 'legend_labels' => [ map { $label{$_} } @items ], - 'brush_size' => 4, - #'pt_size' => 12, -); - -my @data = map { $data{$_} } ( 'label', @items ); - -http_header('Content-Type' => 'image/png' ); - -$chart->_set_colors(); - -%><%= $chart->scalar_png(\@data) %> diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index 1c7d54266..2b98af838 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -1,31 +1,47 @@ -<!-- mason kludge --> -<% +<% include('elements/monthly.html', + 'title' => $agentname. + 'Sales, Credits and Receipts Summary', + 'items' => \@items, + 'labels' => \%label, + 'graph_labels' => \%graph_label, + 'colors' => \%color, + 'links' => \%link, + 'start_month' => $smonth, + 'start_year' => $syear, + 'end_month' => $emonth, + 'end_year' => $eyear, + 'agentnum' => $agentnum, + 'nototal' => scalar($cgi->param('12mo')), + ) +%> +<%init> -#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); -my ($curmon,$curyear) = (localtime(time))[4,5]; +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); #find first month -my $syear = $cgi->param('syear') || 1899+$curyear; -my $smonth = $cgi->param('smonth') || $curmon+1; +my $syear = $cgi->param('start_year'); # || 1899+$curyear; +my $smonth = $cgi->param('start_month'); # || $curmon+1; #find last month -my $eyear = $cgi->param('eyear') || 1900+$curyear; -my $emonth = $cgi->param('emonth') || $curmon+1; - -%> +my $eyear = $cgi->param('end_year'); # || 1900+$curyear; +my $emonth = $cgi->param('end_month'); # || $curmon+1; -<HTML> - <HEAD> - <TITLE>Sales, Credits and Receipts Summary</TITLE> - </HEAD> -<BODY BGCOLOR="#e8e8e8"> -<IMG SRC="money_time-graph.cgi?<%= $cgi->query_string %>" WIDTH="976" HEIGHT="384"> -<BR> +#XXX or virtual +my( $agentnum, $agent ) = ('', ''); +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + die "agentnum $agentnum not found!" unless $agent; +} -<%= table('e8e8e8') %> -<% +my $agentname = $agent ? $agent->agent.' ' : ''; my @items = qw( invoiced netsales credits payments receipts ); +if ( $cgi->param('12mo') == 1 ) { + @items = map $_.'_12mo', @items; +} + my %label = ( 'invoiced' => 'Gross Sales', 'netsales' => 'Net Sales', @@ -33,6 +49,22 @@ my %label = ( 'payments' => 'Gross Receipts', 'receipts' => 'Net Receipts', ); + +my %graph_suffix = ( + 'invoiced' => ' (invoiced)', + 'netsales' => ' (invoiced - applied credits)', + 'credits' => '', + 'payments' => ' (payments)', + 'receipts' => '/Cashflow (payments - refunds)', +); +my %graph_label = map { $_ => $label{$_}.$graph_suffix{$_} } keys %label; + +$label{$_.'_12mo'} = $label{$_}. " (previous 12 months)" + foreach keys %label; + +$graph_label{$_.'_12mo'} = $graph_label{$_}. " (previous 12 months)" + foreach keys %graph_label; + my %color = ( 'invoiced' => '9999ff', #light blue 'netsales' => '0000cc', #blue @@ -40,86 +72,14 @@ my %color = ( 'payments' => '99cc99', #light green 'receipts' => '00cc00', #green ); -my %link = ( - 'invoiced' => "${p}search/cust_bill.html?", - 'credits' => "${p}search/cust_credit.html?", - 'payments' => "${p}search/cust_pay.cgi?magic=_date;", -); +$color{$_.'_12mo'} = $color{$_} + foreach keys %color; -my $report = new FS::Report::Table::Monthly ( - 'items' => \@items, - 'start_month' => $smonth, - 'start_year' => $syear, - 'end_month' => $emonth, - 'end_year' => $eyear, +my %link = ( + 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;", + 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", + 'payments' => "${p}search/cust_pay.cgi?magic=_date;agentnum=$agentnum;", ); -my $data = $report->data; - -my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); - -%> - -<TR><TD></TD> -<% foreach my $column ( @{$data->{label}} ) { - #$column =~ s/^(\d+)\//$mon[$1-1]<BR>/e; - $column =~ s/^(\d+)\//$mon[$1-1]<BR>/; - %> - <TH><%= $column %></TH> -<% } %> -</TR> - -<% foreach my $row (@items) { %> - <TR><TH><FONT COLOR="#<%= $color{$row} %>"><%= $label{$row} %></FONT></TH> - <% my $link = exists($link{$row}) - ? qq(<A HREF="$link{$row}) - : ''; - my @speriod = @{$data->{speriod}}; - my @eperiod = @{$data->{eperiod}}; - %> - <% foreach my $column ( @{$data->{$row}} ) { %> - <TD ALIGN="right" BGCOLOR="#ffffff"> - <%= $link ? $link. 'begin='. shift(@speriod). ';end='. shift(@eperiod). '">' : '' %><FONT COLOR="#<%= $color{$row} %>">$<%= sprintf("%.2f", $column) %></FONT><%= $link ? '</A>' : '' %> - </TD> - <% } %> - </TR> -<% } %> -</TABLE> - -<BR> -<FORM METHOD="POST"> -<!-- -<INPUT TYPE="checkbox" NAME="ar"> - Accounts receivable (invoices - applied credits)<BR> -<INPUT TYPE="checkbox" NAME="charged"> - Just Invoices<BR> -<INPUT TYPE="checkbox" NAME="defer"> - Accounts receivable, with deferred revenue (invoices - applied credits, with charges for annual/semi-annual/quarterly/etc. services deferred over applicable time period) (there has got to be a shorter description for this)<BR> -<INPUT TYPE="checkbox" NAME="cash"> - Cashflow (payments - refunds)<BR> -<BR> ---> -From <SELECT NAME="smonth"> -<% foreach my $mon ( 1..12 ) { %> -<OPTION VALUE="<%= $mon %>"<%= $mon == $smonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %> -<% } %> -</SELECT> -<SELECT NAME="syear"> -<% foreach my $y ( 1999 .. 2010 ) { %> -<OPTION VALUE="<%= $y %>"<%= $y == $syear ? ' SELECTED' : '' %>><%= $y %> -<% } %> -</SELECT> - to <SELECT NAME="emonth"> -<% foreach my $mon ( 1..12 ) { %> -<OPTION VALUE="<%= $mon %>"<%= $mon == $emonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %> -<% } %> -</SELECT> -<SELECT NAME="eyear"> -<% foreach my $y ( 1999 .. 2010 ) { %> -<OPTION VALUE="<%= $y %>"<%= $y == $eyear ? ' SELECTED' : '' %>><%= $y %> -<% } %> -</SELECT> +# XXX link 12mo? -<INPUT TYPE="submit" VALUE="Redisplay"> -</FORM> -</BODY> -</HTML> +</%init> diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html new file mode 100644 index 000000000..c18e94d5d --- /dev/null +++ b/httemplate/graph/report_cust_bill_pkg.html @@ -0,0 +1,35 @@ +<% include('/elements/header.html', 'Sales Report' ) %> + +<FORM ACTION="cust_bill_pkg.cgi" METHOD="GET"> + +<TABLE> + +<% include('/elements/tr-select-from_to.html' ) %> + +<% include('/elements/tr-select-agent.html', '', 'label' => 'For agent: ' ) %> + +<% include('/elements/tr-select-pkg_class.html', '', + 'pre_options' => [ '0' => 'all' ], + 'empty_label' => '(empty class)', + ) +%> + +<!-- +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="separate_0freq" VALUE="1"></TD> + <TD>Separate one-time vs. recurring sales</TD> +</TR> +--> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html new file mode 100644 index 000000000..652d7152e --- /dev/null +++ b/httemplate/graph/report_money_time.html @@ -0,0 +1,39 @@ +<% include('/elements/header.html', 'Sales, Credits and Receipts Summary' ) %> + +<FORM ACTION="money_time.cgi" METHOD="GET"> + +<!-- +<INPUT TYPE="checkbox" NAME="ar"> + Accounts receivable (invoices - applied credits)<BR> +<INPUT TYPE="checkbox" NAME="charged"> + Just Invoices<BR> +<INPUT TYPE="checkbox" NAME="defer"> + Accounts receivable, with deferred revenue (invoices - applied credits, with charges for annual/semi-annual/quarterly/etc. services deferred over applicable time period) (there has got to be a shorter description for this)<BR> +<INPUT TYPE="checkbox" NAME="cash"> + Cashflow (payments - refunds)<BR> +<BR> +--> + +<TABLE> + +<% include('/elements/tr-select-from_to.html' ) %> + +<% include('/elements/tr-select-agent.html', '', 'label' => 'For agent: ' ) %> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="12mo" VALUE="1"></TD> + <TD>Show 12 month totals instead of monthly values</TD> +</TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/images/32clear.gif b/httemplate/images/32clear.gif Binary files differnew file mode 100644 index 000000000..5fdcea204 --- /dev/null +++ b/httemplate/images/32clear.gif diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png Binary files differnew file mode 100644 index 000000000..34cb0286a --- /dev/null +++ b/httemplate/images/arrow.down.png diff --git a/httemplate/images/arrow.right.black.png b/httemplate/images/arrow.right.black.png Binary files differnew file mode 100644 index 000000000..933c25894 --- /dev/null +++ b/httemplate/images/arrow.right.black.png diff --git a/httemplate/images/arrow.right.png b/httemplate/images/arrow.right.png Binary files differnew file mode 100644 index 000000000..60bcb76ab --- /dev/null +++ b/httemplate/images/arrow.right.png diff --git a/httemplate/images/background-cheat.png b/httemplate/images/background-cheat.png Binary files differnew file mode 100644 index 000000000..ad332f675 --- /dev/null +++ b/httemplate/images/background-cheat.png diff --git a/httemplate/images/black-gradient.png b/httemplate/images/black-gradient.png Binary files differnew file mode 100644 index 000000000..225732d16 --- /dev/null +++ b/httemplate/images/black-gradient.png diff --git a/httemplate/images/black-gray-corner.png b/httemplate/images/black-gray-corner.png Binary files differnew file mode 100644 index 000000000..17954cdd7 --- /dev/null +++ b/httemplate/images/black-gray-corner.png diff --git a/httemplate/images/black-gray-gradient.png b/httemplate/images/black-gray-gradient.png Binary files differnew file mode 100644 index 000000000..f5c318fe7 --- /dev/null +++ b/httemplate/images/black-gray-gradient.png diff --git a/httemplate/images/black-gray-side.png b/httemplate/images/black-gray-side.png Binary files differnew file mode 100644 index 000000000..f7a98a43d --- /dev/null +++ b/httemplate/images/black-gray-side.png diff --git a/httemplate/images/black-gray-top.png b/httemplate/images/black-gray-top.png Binary files differnew file mode 100644 index 000000000..ed0707573 --- /dev/null +++ b/httemplate/images/black-gray-top.png diff --git a/httemplate/images/calendar-disabled.png b/httemplate/images/calendar-disabled.png Binary files differnew file mode 100644 index 000000000..81816bcd6 --- /dev/null +++ b/httemplate/images/calendar-disabled.png diff --git a/httemplate/images/menu-left-example.png b/httemplate/images/menu-left-example.png Binary files differnew file mode 100644 index 000000000..375725cb9 --- /dev/null +++ b/httemplate/images/menu-left-example.png diff --git a/httemplate/images/menu-top-example.png b/httemplate/images/menu-top-example.png Binary files differnew file mode 100644 index 000000000..bd9bea883 --- /dev/null +++ b/httemplate/images/menu-top-example.png diff --git a/httemplate/images/red_telephone_mimooh_01.png b/httemplate/images/red_telephone_mimooh_01.png Binary files differnew file mode 100644 index 000000000..2212ff0e8 --- /dev/null +++ b/httemplate/images/red_telephone_mimooh_01.png diff --git a/httemplate/index.html b/httemplate/index.html index b8f300d2d..60ab26f86 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -1,293 +1,54 @@ -<!-- mason kludge --> -<% my $conf = new FS::Conf; %> -<HTML> - <HEAD> - <TITLE> - Freeside Main Menu - </TITLE> - </HEAD> - <BODY BGCOLOR="#FFFFFF"> - <table width="100%"> - <tr> - <td rowspan=2> - <IMG BORDER=0 ALT="freeside" SRC="images/small-logo.png"> - </td> - <td align=left rowspan=2> <!-- valign="top" --> - <font size=6><%= $conf->config('company_name') %> Billing</font> - </td> - <td align=right valign=top>Logged in as <b><%= getotaker %></b> - </td> - </tr> - <tr> - <td align=right valign=bottom> - - <table> - <tr> - <td align=right> - <FONT SIZE="-2"> - <A HREF="http://www.sisd.com/freeside">Freeside</A> v<%= $FS::VERSION %><BR> - <A HREF="docs/">Documentation</A><BR> - </FONT> - </td> - <% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { %> - <% eval "use RT;"; %> - <td bgcolor=#000000></td> - <td align=left> - <FONT SIZE="-2"> - <A HREF="http://www.bestpractical.com/rt">RT<A> v<%= $RT::VERSION %><BR> - <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR> - </FONT> - </td> - <% } %> - - </tr> - </table> - - </td> - </tr> - </table> - - <BR> - - -[<A NAME="customer_service" style="background-color: #cccccc"> Sales / Customer service </A>] -<% if ( $conf->config('ticket_system') ) { %> - [ <A HREF="#ticketing">Support / Ticketing</A> ] -<% } %> -[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ] -[ <A HREF="#reports">Reports</A> ] -[ <A HREF="#sysadmin">Sysadmin</A> ] - <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0" WIDTH="100%" BGCOLOR="#eeeeee"> - <TR><TH BGCOLOR="#cccccc">Sales / Customer service</TH></TR> - <TR><TD> - <BR><FONT SIZE="+1"><A HREF="edit/cust_main.cgi">New Customer</A></FONT> - <BR> - <BR><FORM ACTION="search/cust_main.cgi" METHOD="GET"><INPUT TYPE="hidden" NAME="custnum_on" VALUE="1">Customer # <INPUT TYPE="text" NAME="custnum_text"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=custnum">all customers by customer number</A></FORM> - <FORM ACTION="search/cust_main.cgi" METHOD="GET"><INPUT TYPE="hidden" NAME="last_on" VALUE="1">Last name <INPUT TYPE="text" NAME="last_text"><SELECT NAME="last_type"><OPTION SELECTED VALUE="All">(all)</OPTION><OPTION>Fuzzy<OPTION>Substring</OPTION><OPTION>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=last">all customers by last name</A></FORM> - <FORM ACTION="search/cust_main.cgi" METHOD="GET"><INPUT TYPE="hidden" NAME="company_on" VALUE="1">Company <INPUT TYPE="text" NAME="company_text"><SELECT NAME="company_type"><OPTION SELECTED VALUE="All">(all)</OPTION><OPTION>Fuzzy<OPTION>Substring</OPTION><OPTION>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=company">all customers by company</A></FORM> -<% if ( $conf->exists('address2-search') ) { %> - <FORM ACTION="search/cust_main.cgi" METHOD="GET"><INPUT TYPE="hidden" NAME="address2_on" VALUE="1">Unit <INPUT TYPE="text" NAME="address2_text"><INPUT TYPE="submit" VALUE="Search"></FORM> -<% } %> - <FORM ACTION="search/cust_main.cgi" METHOD="GET"><INPUT TYPE="hidden" NAME="phone_on" VALUE="1">Phone # <INPUT TYPE="text" NAME="phone_text"><INPUT TYPE="submit" VALUE="Search"></FORM> - <BR><FORM ACTION="search/svc_acct.cgi" METHOD="GET">Username <INPUT TYPE="text" NAME="username"><SELECT NAME="username_type"><OPTION VALUE="All">(all)</OPTION><OPTION>Fuzzy</OPTION><OPTION>Substring</OPTION><OPTION SELECTED>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_acct.cgi?username">all accounts by username</A> or <A HREF="search/svc_acct.cgi?uid">uid</A></FORM> - <BR><FORM ACTION="search/svc_domain.cgi" METHOD="GET">Domain <INPUT TYPE="text" NAME="domain"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_domain.cgi?domain">all domains</A></FORM> - <BR><FORM ACTION="search/svc_broadband.cgi" METHOD="GET">IP Address <INPUT TYPE="text" NAME="ip_addr"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_broadband.cgi?svcnum">all services by svcnum</A> or <A HREF="search/svc_broadband.cgi?blocknum">address block</A></FORM> - <BR><A HREF="search/svc_forward.cgi?svcnum">all mail forwards by svcnum</A><BR> - <BR><A HREF="search/svc_www.cgi?svcnum">all virtual hosts by svcnum</A><BR> - <BR><A HREF="search/svc_external.cgi?svcnum">all external services by svcnum</A><BR> - <BR> - </TD></TR> - </TABLE> - - <BR><BR><BR> - -<% if ( $conf->config('ticket_system') ) { %> - -[ <A HREF="#customer_service">Sales / Customer service</A> ] -[<A NAME="ticketing" style="background-color: #cccccc"> Support / Ticketing </A>] -[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ] -[ <A HREF="#reports">Reports</A> ] -[ <A HREF="#sysadmin">Sysadmin</A> ] - <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0" WIDTH="100%" BGCOLOR="#eeeeee"> - <TR><TH BGCOLOR="#cccccc">Support/Ticketing</TH></TR> - <TR><TD> - <% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { %> - <BR><FONT SIZE="+1"><A HREF="rt/">Ticketing Main</A></FONT> - <BR><BR> - Reports - <UL> - <LI><A HREF="search/cust_main.cgi?browse=tickets">Customers sorted by active tickets</A> - <!-- <LI><A HREF="">Active tickets not assigned to a customer</A> --> - <% } else { %> - <BR><FONT SIZE="+1"><A HREF="<%=FS::TicketSystem->baseurl()%>">Ticketing Main</A></FONT> - <BR><BR> - <% } %> - </TD></TR> - </TABLE> - - <BR><BR><BR> - -<% } %> - - -[ <A HREF="#customer_service">Sales / Customer service</A> ] -<% if ( $conf->config('ticket_system') ) { %> - [ <A HREF="#ticketing">Support / Ticketing</A> ] -<% } %> -[<A NAME="bookkeeping" style="background-color: #cccccc"> Bookkeeping / Collections </A>] -[ <A HREF="#reports">Reports</A> ] -[ <A HREF="#sysadmin">Sysadmin</A> ] - <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee"> - <TR><TH BGCOLOR="#cccccc">Bookkeeping / Collections</TH></TR> - <TR><TD> - <BR><A HREF="misc/batch-cust_pay.html">Quick payment entry</A> - <BR> - <BR><FORM ACTION="search/cust_main.cgi" METHOD="GET">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM> - <FORM ACTION="search/cust_bill.html" METHOD="GET">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM> - <FORM ACTION="search/cust_pay.cgi" METHOD="GET">Check # <INPUT TYPE="text" NAME="payinfo" SIZE="8"><INPUT TYPE="hidden" NAME="payby" VALUE="BILL"><INPUT TYPE="submit" VALUE="Search"></FORM> - <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg_report.cgi">Packages (by next bill date range)</A> - <BR><BR>Invoice reports - <UL> - <LI>open invoices (<A HREF="search/cust_bill.html?OPEN_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN_custnum">by customer number</A>) - <LI>15 day open invoices (<A HREF="search/cust_bill.html?OPEN15_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN15_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN15_custnum">by customer number</A>) - <LI>30 day open invoices (<A HREF="search/cust_bill.html?OPEN30_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN30_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN30_custnum">by customer number</A>) - <LI>60 day open invoices (<A HREF="search/cust_bill.html?OPEN60_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN60_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN60_custnum">by customer number</A>) - <LI>90 day open invoices (<A HREF="search/cust_bill.html?OPEN90_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN90_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN90_custnum">by customer number</A>) - <LI>120 day open invoices (<A HREF="search/cust_bill.html?OPEN120_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN120_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN120_custnum">by customer number</A>) - <LI>all invoices (<A HREF="search/cust_bill.html?invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?date">by date</A>) (<A HREF="search/cust_bill.html?custnum">by customer number</A>) - </UL> - <A HREF="search/report_cust_bill.html">Advanced invoice reports</A><BR><BR> - Invoice event reports - <UL> - <LI><a href="search/cust_bill_event.html">All invoice events for a date range</a> - <LI><a href="search/cust_bill_event.html?failed=1">Invoice event errors for a date range (failed credit cards, processor or printer problems, etc.)</a> - </UL> - <A HREF="search/report_cust_pay.html">Payment report (by type and/or date range)</A> - <BR><BR><A HREF="search/report_cust_credit.html">Credit report (by employee and/or date range)</A> - <BR><BR><A HREF="graph/money_time.cgi">Sales, Credits and Receipts Summary</A> - <BR><BR><A HREF="search/report_receivables.cgi">Accounts Receivable Aging Summary</A> - <BR><BR><A HREF="search/report_prepaid_income.html">Prepaid Income (Unearned Revenue) Report</A> - <BR><BR><A HREF="search/report_tax.html">Sales Tax Liability Report</A> - <BR><BR> - </TD></TR> - </TABLE> - - - - <BR><BR><BR> - - - -[ <A HREF="#customer_service">Sales / Customer service</A> ] -<% if ( $conf->config('ticket_system') ) { %> - [ <A HREF="#ticketing">Support / Ticketing</A> ] -<% } %> -[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ] -[<A NAME="reports" style="background-color: #cccccc"> Reports </A>] -[ <A HREF="#sysadmin">Sysadmin</A> ] - <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee"> - <TR><TH BGCOLOR="#cccccc">Reports</TH></TR> - <TR><TD> - <BR> - <A HREF="search/sqlradius.html">RADIUS sessions</A><BR><BR> - Auditing pre-Freeside services with no customer record - <UL> - <LI>unlinked accounts (<A HREF="search/svc_acct.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_acct.cgi?UN_username">by username</A>) (<A HREF="search/svc_acct.cgi?UN_uid">by uid</A>) - <LI>unlinked mail forwards (<A HREF="search/svc_forward.cgi?UN_svcnum">by service number</A>) - <LI>unlinked domains (<A HREF="search/svc_domain.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_domain.cgi?UN_domain">by domain</A>) - <LI>unlinked externals (<A HREF="search/svc_external.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_external.cgi?UN_id">by id</A>) - </UL> - Packages - <UL> - <LI><A HREF="search/cust_pkg.cgi?pkgnum">all packages (by package number)</A> - <LI><A HREF="search/cust_pkg.cgi?magic=suspended">suspended packages (by package number)</A> - <LI><A HREF="search/cust_pkg.cgi?APKG_pkgnum">packages with unconfigured services (by package number)</A> - <LI><A HREF="search/cust_pkg_report.cgi">packages (by next bill date range)</A> - </UL> - <A HREF="browse/part_pkg.cgi?active=1">Package definitions (by number of active packages)</A><BR><BR> - <A HREF="browse/part_svc.cgi?orderby=active">Service definitions (by number of active services)</A><BR><BR> - Customers - <UL> - <LI><A HREF="search/cust_main-otaker.cgi">Search customers by ordering employee</A> - </UL> - <FORM ACTION="search/sql.html" METHOD="GET">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM> - - <BR> - </TD></TR> - </TABLE> - - - - <BR><BR><BR> - - -[ <A HREF="#customer_service">Sales / Customer service</A> ] -<% if ( $conf->config('ticket_system') ) { %> - [ <A HREF="#ticketing">Support / Ticketing</A> ] -<% } %> -[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ] -[ <A HREF="#reports">Reports</A> ] -[<A NAME="sysadmin" style="background-color: #cccccc"> Sysadmin </A>] - <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee"> - <TR><TH BGCOLOR="#cccccc">Sysadmin</TH></TR> - <TR><TD> - <BR> - <!-- <BR>View active NAS ports: - <A HREF="browse/nas.cgi">session server</A> - <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A> - <BR> --> - <A HREF="browse/queue.cgi">View pending job queue</A> - <BR><A HREF="misc/cust_main-import.cgi">Batch import customers from CSV file</A> - <BR><A HREF="misc/cust_main-import_charges.cgi">Batch import charges from CSV file</A> - <BR><A HREF="misc/dump.cgi">Download database dump</A> - <BR><BR><CENTER><HR WIDTH="94%" NOSHADE></CENTER><BR> - <A NAME="config" HREF="config/config-view.cgi">Configuration</a><!-- - <font size="+2" color="#ff0000">start here</font> --> - <BR><BR><A NAME="admin">Provisioning, services and packages</a> - <ul> - <LI><A HREF="browse/part_export.cgi">View/Edit exports</A> - - Provisioning services to external machines, databases and APIs. - <LI><A HREF="browse/part_svc.cgi">View/Edit service definitions</A> - - Services are items you offer to your customers. - <LI><A HREF="browse/part_pkg.cgi">View/Edit package definitions</A> - - One or more services are grouped together into a package and - given pricing information. Customers purchase packages, not - services. - </ul> - <A NAME="admin_agent">Resellers</a> - <ul> - <LI><A HREF="browse/agent_type.cgi">View/Edit agent types</A> - - Agent types define groups of package definitions that you can - then assign to particular agents. - <LI><A HREF="browse/agent.cgi">View/Edit agents</A> - - Agents are resellers of your service. Agents may be limited - to a subset of your full offerings (via their type). - </ul> - <A NAME="admin_billing">Billing</a> - <ul> - <LI><A HREF="browse/payment_gateway.html">View/Edit payment gateways</A> - - Credit card and electronic check processors - <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - - Actions for overdue invoices - <LI><A HREF="search/prepay_credit.html">View/Edit prepaid cards</A> - - View outstanding cards, generate new cards - <LI><A HREF="browse/rate.cgi">View/Edit call rates and regions</A> - - Manage rate plans, regions and prefixes for VoIP and call billing. - <LI><A HREF="browse/cust_main_county.cgi">View/Edit locales and tax rates</A> - - Change tax rates, or break down a country into states, or a state - into counties and assign different tax rates to each. - </ul> - <A NAME="admin_svc_acct">Dialup</a> - <ul> - <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit access numbers</A> - - Points of Presence - </ul> - <A NAME="admin_svc_broadband">Fixed (username-less) broadband</a> - <ul> - <LI><A HREF="browse/router.cgi">View/Edit routers</A> - - Broadband access routers - <LI><A HREF="browse/addr_block.cgi">View/Edit address blocks</A> - - Manage address blocks and block assignments to broadband routers. - </ul> - <A NAME="admin_misc">Miscellaneous</a> - <ul> - <LI><A HREF="browse/part_referral.cgi">View/Edit advertising sources</A> - - Where a customer heard about your service. Tracked for - informational purposes. - <LI><A HREF="browse/part_virtual_field.cgi">View/Edit virtual fields</A> - - Locally defined fields - <LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - - Change error messages and other customizable labels. - </ul> - <BR> - </TD></TR> - </TABLE> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR> - </BODY> -</HTML> +% my $conf = new FS::Conf; + +<% include('/elements/header.html', 'Billing Main' ) %> + +<% include('/elements/dashboard-toplist.html') %> + +% my $sth = dbh->prepare( +% #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) +% "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) +% WHERE ( history_action = 'insert' OR history_action = 'replace_new' ) +% AND history_user = ? +% ORDER BY history_date desc" # LIMIT 10 +% ) or die dbh->errstr; +% +% $sth->execute( getotaker() ) or die $sth->errstr; +% +% my %saw = (); +% my @custnums = grep { !$saw{$_}++ } map $_->[0], @{ $sth->fetchall_arrayref }; +% +% @custnums = splice(@custnums, 0, 10); +% +% if ( @custnums ) { + + <% include('/elements/table-grid.html') %> + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = $bgcolor2; + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=1>Customers I recently added or modified</TH> + </TR> + +% foreach my $custnum ( @custnums ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% next unless $cust_main; + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="view/cust_main.cgi?<% $custnum %>"><% $custnum %>: <% $cust_main->name %></A></TD> + </TR> + +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% } + + </TABLE> + +% } + +<% include('/elements/footer.html') %> diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 20cc89045..d85f3b6c3 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -1,16 +1,15 @@ -<%= header( 'Quick payment entry', +<% include("/elements/header.html", 'Quick payment entry', menubar( 'Main Menu' => $p, #popurl(1), - 'Old-style quick payment entry' => - $p. 'search/cust_main-quickpay.html', ), ( $cgi->param('error') ? '' : 'onload="addRow()"' ), ) %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000"><% $cgi->param('error') %></FONT><BR><BR> +% } -<% if ( $cgi->param('error') ) { %> - <FONT SIZE="+1" COLOR="#ff0000"><%= $cgi->param('error') %></FONT><BR><BR> -<% } %> <FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.submit.disabled=true;"> @@ -239,60 +238,60 @@ <TH>Check #</TH> <TH BGCOLOR="#e8e8e8"></TH> </TR> +% my $row = 0; +% if ( $cgi->param('error') ) { +% my $param = $cgi->Vars; +% +% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { -<% my $row = 0; - if ( $cgi->param('error') ) { - my $param = $cgi->Vars; -%> - - <% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { %> <TR> <TD> - <INPUT TYPE="text" NAME="custnum<%= $row %>" ID="custnum<%= $row %>" SIZE=8 MAXLENGTH=12 VALUE="<%= $param->{"custnum$row"} %>" rownum="<%= $row %>"> + <INPUT TYPE="text" NAME="custnum<% $row %>" ID="custnum<% $row %>" SIZE=8 MAXLENGTH=12 VALUE="<% $param->{"custnum$row"} %>" rownum="<% $row %>"> <SCRIPT TYPE="text/javascript"> - var custnum_input<%= $row %> = document.getElementById("custnum<%= $row %>"); - custnum_input<%= $row %>.onfocus = clearhint_custnum; - custnum_input<%= $row %>.onchange = search_custnum; + var custnum_input<% $row %> = document.getElementById("custnum<% $row %>"); + custnum_input<% $row %>.onfocus = clearhint_custnum; + custnum_input<% $row %>.onchange = search_custnum; </SCRIPT> </TD> <TD> - <INPUT TYPE="text" NAME="customer<%= $row %>" ID="customer<%= $row %>" SIZE=64 VALUE="<%= $param->{"customer$row"} %>" rownum="<%= $row %>"> + <INPUT TYPE="text" NAME="customer<% $row %>" ID="customer<% $row %>" SIZE=64 VALUE="<% $param->{"customer$row"} %>" rownum="<% $row %>"> <SCRIPT TYPE="text/javascript"> - var customer_input<%= $row %> = document.getElementById("customer<%= $row %>"); - customer_input<%= $row %>.onfocus = clearhint_customer; - customer_input<%= $row %>.onclick = clearhint_customer; - customer_input<%= $row %>.onchange = search_customer; + var customer_input<% $row %> = document.getElementById("customer<% $row %>"); + customer_input<% $row %>.onfocus = clearhint_customer; + customer_input<% $row %>.onclick = clearhint_customer; + customer_input<% $row %>.onchange = search_customer; </SCRIPT> - <SELECT NAME="cust_select<%= $row %>" ID="cust_select<%= $row %>" rownum="<%= $row %>" STYLE="color:#ff0000; display:none""> + <SELECT NAME="cust_select<% $row %>" ID="cust_select<% $row %>" rownum="<% $row %>" STYLE="color:#ff0000; display:none"> </SELECT> <SCRIPT TYPE="text/javascript"> - var customer_select<%= $row %> = document.getElementById("cust_select<%= $row %>"); - customer_select<%= $row %>.onchange = select_customer; + var customer_select<% $row %> = document.getElementById("cust_select<% $row %>"); + customer_select<% $row %>.onchange = select_customer; </SCRIPT> </TD> <TD> - $<INPUT TYPE="text" NAME="paid<%= $row %>" SIZE=8 MAXLENGTH=8 VALUE="<%= $param->{"paid$row"} %>" > + $<INPUT TYPE="text" NAME="paid<% $row %>" SIZE=8 MAXLENGTH=8 VALUE="<% $param->{"paid$row"} %>" > </TD> <TD> - <INPUT TYPE="text" NAME="payinfo<%= $row %>" SIZE=10 VALUE="<%= $param->{"payinfo$row"} %>" > + <INPUT TYPE="text" NAME="payinfo<% $row %>" SIZE=10 VALUE="<% $param->{"payinfo$row"} %>" > </TD> <TD BGCOLOR="#e8e8e8"> - <% if ( $param->{"error$row"} ) { %> - <FONT SIZE="-1" COLOR="#ff0000">Error: <%= $param->{"error$row"} %></FONT> - <% } %> +% if ( $param->{"error$row"} ) { + + <FONT SIZE="-1" COLOR="#ff0000">Error: <% $param->{"error$row"} %></FONT> +% } + </TD> </TR> +% } +% } - <% } %> - -<% } %> </TABLE> @@ -305,7 +304,7 @@ </FORM> -<%= include('/elements/xmlhttp.html', +<% include('/elements/xmlhttp.html', 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi', 'subs' => [qw( custnum_search smart_search )], ) @@ -313,7 +312,7 @@ <SCRIPT TYPE="text/javascript"> - var rownum = <%= $row %>; + var rownum = <% $row %>; function addRow() { diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi index 44d85b880..8d4b4ac65 100755 --- a/httemplate/misc/bill.cgi +++ b/httemplate/misc/bill.cgi @@ -1,38 +1,41 @@ -<% +% +%#untaint custnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d*)$/; +%my $custnum = $1; +%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +%die "Can't find customer!\n" unless $cust_main; +% +%my $conf = new FS::Conf; +% +%my $error = $cust_main->bill( +%# 'time'=>$time +% ); +%#&eidiot($error) if $error; +% +%unless ( $error ) { +% $cust_main->apply_payments_and_credits; +% +% $error = $cust_main->collect( +% # 'invoice-time'=>$time, +% #'batch_card'=> 'yes', +% #'batch_card'=> 'no', +% #'report_badcard'=> 'yes', +% #'retry_card' => 'yes', +% 'retry' => 'yes', +% 'realtime' => $conf->exists('realtime-backend'), +% ); +%} +%#&eidiot($error) if $error; +% +%if ( $error ) { +% -#untaint custnum -my($query) = $cgi->keywords; -$query =~ /^(\d*)$/; -my $custnum = $1; -my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); -die "Can't find customer!\n" unless $cust_main; - -my $error = $cust_main->bill( -# 'time'=>$time - ); -#&eidiot($error) if $error; - -unless ( $error ) { - $cust_main->apply_payments; - $cust_main->apply_credits; - - $error = $cust_main->collect( - # 'invoice-time'=>$time, - #'batch_card'=> 'yes', - #'batch_card'=> 'no', - #'report_badcard'=> 'yes', - #'retry_card' => 'yes', - 'retry' => 'yes', - ); -} -#&eidiot($error) if $error; - -if ( $error ) { -%> <!-- mason kludge --> -<% - &idiot($error); -} else { - print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum"); -} -%> +% +% &idiot($error); +%} else { +% print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum"); +%} +% + diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi index 43e439b58..6f070a444 100755 --- a/httemplate/misc/cancel-unaudited.cgi +++ b/httemplate/misc/cancel-unaudited.cgi @@ -1,34 +1,36 @@ -<% +% +% +%my $dbh = dbh; +% +%#untaint svcnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +% +%#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +%#die "Unknown svcnum!" unless $svc_acct; +% +%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "Unknown svcnum!" unless $cust_svc; +%my $cust_pkg = $cust_svc->cust_pkg; +%if ( $cust_pkg ) { +% &eidiot( 'This account has already been audited. Cancel the '. +% qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum. +% '#cust_pkg'. $cust_pkg->pkgnum. '">'. +% 'package</A> instead.'); +%} +% +%my $error = $cust_svc->cancel; +% +%if ( $error ) { +% -my $dbh = dbh; - -#untaint svcnum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; - -#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); -#die "Unknown svcnum!" unless $svc_acct; - -my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -die "Unknown svcnum!" unless $cust_svc; -my $cust_pkg = $cust_svc->cust_pkg; -if ( $cust_pkg ) { - &eidiot( 'This account has already been audited. Cancel the '. - qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum. - '#cust_pkg'. $cust_pkg->pkgnum. '">'. - 'package</A> instead.'); -} - -my $error = $cust_svc->cancel; - -if ( $error ) { - %> <!-- mason kludge --> -<% - &eidiot($error); -} else { - print $cgi->redirect(popurl(2)); -} +% +% &eidiot($error); +%} else { +% print $cgi->redirect(popurl(2)); +%} +% +% -%> diff --git a/httemplate/misc/cancel_pkg.cgi b/httemplate/misc/cancel_pkg.cgi deleted file mode 100755 index 0487677df..000000000 --- a/httemplate/misc/cancel_pkg.cgi +++ /dev/null @@ -1,15 +0,0 @@ -<% - -#untaint pkgnum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal pkgnum"; -my $pkgnum = $1; - -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - -my $error = $cust_pkg->cancel; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); - -%> diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html new file mode 100755 index 000000000..e61000618 --- /dev/null +++ b/httemplate/misc/cancel_pkg.html @@ -0,0 +1,95 @@ +%# if ( $link eq 'popup' ) { + <% include('/elements/header-popup.html', $title ) %> +%# } else { +%# <% include("/elements/header.html", $title, '') %> +%# } + +<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM NAME="sc_popup" ACTION="<% popurl(1) %>process/cancel_pkg.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="method" VALUE="<% $method %>"> + + +<BR><BR> +<% ucfirst($method) . " $pkgnum: " .$part_pkg->pkg. ' - ' .$part_pkg->comment %> +<% ntable("#cccccc", 2) %> + +% if ($method eq 'expire') { +<TR> + <TD>Cancel package on </TD> + <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<% $date %>"> + <IMG SRC="<% $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date"> + <BR><I>m/d/y</I> + </TD> +</TR> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "expire_date", + ifFormat: "%m/%d/%Y", + button: "expire_button", + align: "BR" + }); +</SCRIPT> +%} +% + +<% include('/elements/tr-select-reason.html', 'reasonnum', $class, '', '', '', 'document.sc_popup.submit' ) %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="<% $submit %>" disabled='true'> + +</FORM> +</BODY> +</HTML> + +<%init> +my($method, $pkgnum, $reasonnum, $submit, $cust_pkg, $part_pkg, + $date, $curuser, $class); +$date = time2str("%m/%d/%Y", time); +if ( $cgi->param('error') ) { + $method = $cgi->param('method'); + $pkgnum = $cgi->param('pkgnum'); + $reasonnum = $cgi->param('reasonnum'); + $date = $cgi->param('date'); +} elsif ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) { + $pkgnum = $1; +} else { + die "illegal query ". $cgi->keywords; +} + +$method = $cgi->param('method'); +if ($method eq 'cancel') { + $class = 'C'; + $submit = "Cancel Now"; +}elsif ($method eq 'expire') { + $class = 'C'; + $submit = "Cancel Later"; +}elsif ($method eq 'suspend') { + $class = 'S'; + $submit = "Suspend"; +}else{ + die "illegal query ". $cgi->keywords; +} + +my $title = ucfirst($method) . ' Package'; + +$cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}); +die "No such package: $pkgnum" unless $cust_pkg; + +$part_pkg = $cust_pkg->part_pkg; + +$curuser = $FS::CurrentUser::CurrentUser; + +</%init> + diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi index 3402b61e6..8881746d1 100755 --- a/httemplate/misc/catchall.cgi +++ b/httemplate/misc/catchall.cgi @@ -1,133 +1,134 @@ <!-- mason kludge --> -<% +% +% +%my $conf = new FS::Conf; +% +%my($svc_domain, $svcnum, $pkgnum, $svcpart, $part_svc); +%if ( $cgi->param('error') ) { +% $svc_domain = new FS::svc_domain ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_domain') +% } ); +% $svcnum = $svc_domain->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +%} else { +% my($query) = $cgi->keywords; +% if ( $query =~ /^(\d+)$/ ) { #editing +% $svcnum=$1; +% $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_domain) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% } else { +% +% die "Invalid (svc_domain) svcnum!"; +% +% } +%} +% +%my %email; +%if ($pkgnum) { +% +% #find all possible user svcnums (and emails) +% +% #starting with that currently attached +% if ($svc_domain->catchall) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +% $email{$svc_domain->catchall} = $svc_acct->email; +% } +% +% #and including the rest for this customer +% my($u_part_svc,@u_acct_svcparts); +% foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { +% push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% my($i_cust_svc); +% foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); +% $email{$svc_acct->getfield('svcnum')}=$svc_acct->email; +% } +% } +% } +% +%} else { +% +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +% $email{$svc_domain->catchall} = $svc_acct->email; +%} +% +%# add an absence of a catchall +%$email{''} = "(none)"; +% +%my $p1 = popurl(1); +%print header("Domain Catchall Edit", ''); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/catchall.cgi" METHOD=POST>!; +% +%#display +% +% #formatting +% print "<PRE>"; +% +%#svcnum +%print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +%print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>"; +% +%#pkgnum +%print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; +% +%#svcpart +%print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; +% +%my($domain,$catchall)=( +% $svc_domain->domain, +% $svc_domain->catchall, +%); +% +%print qq!<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">!; +% +%#catchall +%print qq!\n\nMail to <I>(anything)</I>@<B>$domain</B> forwards to <SELECT NAME="catchall" SIZE=1>!; +%foreach $_ (keys %email) { +% print "<OPTION", $_ eq $catchall ? " SELECTED" : "", +% qq! VALUE="$_">$email{$_}!; +%} +%print "</SELECT>"; +% +% #formatting +% print "</PRE>\n"; +% +%print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!; +% +%print <<END; +% +% </FORM> +% </BODY> +%</HTML> +%END +% +% -my $conf = new FS::Conf; - -my($svc_domain, $svcnum, $pkgnum, $svcpart, $part_svc); -if ( $cgi->param('error') ) { - $svc_domain = new FS::svc_domain ( { - map { $_, scalar($cgi->param($_)) } fields('svc_domain') - } ); - $svcnum = $svc_domain->svcnum; - $pkgnum = $cgi->param('pkgnum'); - $svcpart = $cgi->param('svcpart'); - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $svcnum=$1; - $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) - or die "Unknown (svc_domain) svcnum!"; - - my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) - or die "Unknown (cust_svc) svcnum!"; - - $pkgnum=$cust_svc->pkgnum; - $svcpart=$cust_svc->svcpart; - - $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); - die "No part_svc entry!" unless $part_svc; - - } else { - - die "Invalid (svc_domain) svcnum!"; - - } -} - -my %email; -if ($pkgnum) { - - #find all possible user svcnums (and emails) - - #starting with that currently attached - if ($svc_domain->catchall) { - my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); - $email{$svc_domain->catchall} = $svc_acct->email; - } - - #and including the rest for this customer - my($u_part_svc,@u_acct_svcparts); - foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { - push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); - } - - my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - my($custnum)=$cust_pkg->getfield('custnum'); - my($i_cust_pkg); - foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { - my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); - my($acct_svcpart); - foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding - #record(s) in cust_svc ( for this - #pkgnum ! ) - my($i_cust_svc); - foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { - my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); - $email{$svc_acct->getfield('svcnum')}=$svc_acct->email; - } - } - } - -} else { - - my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); - $email{$svc_domain->catchall} = $svc_acct->email; -} - -# add an absence of a catchall -$email{''} = "(none)"; - -my $p1 = popurl(1); -print header("Domain Catchall Edit", ''); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print qq!<FORM ACTION="${p1}process/catchall.cgi" METHOD=POST>!; - -#display - - #formatting - print "<PRE>"; - -#svcnum -print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; -print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>"; - -#pkgnum -print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; - -#svcpart -print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; - -my($domain,$catchall)=( - $svc_domain->domain, - $svc_domain->catchall, -); - -print qq!<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">!; - -#catchall -print qq!\n\nMail to <I>(anything)</I>@<B>$domain</B> forwards to <SELECT NAME="catchall" SIZE=1>!; -foreach $_ (keys %email) { - print "<OPTION", $_ eq $catchall ? " SELECTED" : "", - qq! VALUE="$_">$email{$_}!; -} -print "</SELECT>"; - - #formatting - print "</PRE>\n"; - -print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!; - -print <<END; - - </FORM> - </BODY> -</HTML> -END - -%> diff --git a/httemplate/misc/cdr-import.html b/httemplate/misc/cdr-import.html new file mode 100644 index 000000000..5e9e2690d --- /dev/null +++ b/httemplate/misc/cdr-import.html @@ -0,0 +1,16 @@ +<% include("/elements/header.html",'Call Detail Record Import') %> +<FORM ACTION="process/cdr-import.html" METHOD="POST" ENCTYPE="multipart/form-data"> +Import a CSV file containing Call Detail Records (CDRs).<BR><BR> +CDR Format: <SELECT NAME="format"> +<OPTION VALUE="asterisk">Asterisk (untested)</OPTION> +<OPTION VALUE="unitel">Unitel/RSLCOM</OPTION> +<OPTION VALUE="ams">AMS</OPTION> +</SELECT><BR><BR> + +Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR> + +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi index 5346fd9d8..655799fc1 100755 --- a/httemplate/misc/change_pkg.cgi +++ b/httemplate/misc/change_pkg.cgi @@ -1,5 +1,42 @@ -<!-- mason kludge --> -<% +<% include('/elements/header.html', "Change Package") %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<% small_custview( $cust_main, $conf->config('countrydefault') || '' , '', + "${p}view/cust_main.cgi") +%> + +<FORM ACTION="<% $p %>edit/process/cust_pkg.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="<% $pkgnum %>"> + +<BR> +Current package: <% $part_pkg->pkg %> - <% $part_pkg->comment %> + +<BR> +New package: <SELECT NAME="new_pkgpart"><OPTION VALUE=0></OPTION> + +%foreach my $part_pkg ( +% grep { ! $_->disabled && $_->pkgpart != $cust_pkg->pkgpart } +% map { $_->part_pkg } $agent->agent_type->type_pkgs +%) { +% my $pkgpart = $part_pkg->pkgpart; + + <OPTION VALUE="<% $pkgpart %>" <% ( $cgi->param('error') && $cgi->param('new_pkgpart') == $pkgpart ) ? ' SELECTED' : '' %>> + <% $pkgpart %>: <% $part_pkg->pkg %> - <% $part_pkg->comment %> + </OPTION> + +%} + +</SELECT> +<BR><BR><INPUT TYPE="submit" VALUE="Change package"> + </FORM> + </BODY> +</HTML> +<%init> my $pkgnum; if ( $cgi->param('error') ) { @@ -27,40 +64,6 @@ my $cust_main = $cust_pkg->cust_main " ( pkgnum ". cust_pkg->pkgnum. ")"; my $agent = $cust_main->agent; -print header("Change Package", menubar( - "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - 'Main Menu' => $p, -)); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT><BR><BR>" - if $cgi->param('error'); - my $part_pkg = $cust_pkg->part_pkg; -print small_custview( $cust_main, $conf->config('countrydefault') ). - qq!<FORM ACTION="${p}edit/process/cust_pkg.cgi" METHOD=POST>!. - qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!. - qq!<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="$pkgnum">!. - '<BR>Current package: '. $part_pkg->pkg. ' - '. $part_pkg->comment. - qq!<BR>New package: <SELECT NAME="new_pkgpart"><OPTION VALUE=0></OPTION>!; - -foreach my $part_pkg ( - grep { ! $_->disabled && $_->pkgpart != $cust_pkg->pkgpart } - map { $_->part_pkg } $agent->agent_type->type_pkgs -) { - my $pkgpart = $part_pkg->pkgpart; - print qq!<OPTION VALUE="$pkgpart"!; - print ' SELECTED' if $cgi->param('error') - && $cgi->param('new_pkgpart') == $pkgpart; - print qq!>$pkgpart: !. $part_pkg->pkg. ' - '. $part_pkg->comment. '</OPTION>'; -} - -print <<END; -</SELECT> -<BR><BR><INPUT TYPE="submit" VALUE="Change package"> - </FORM> - </BODY> -</HTML> -END -%> +</%init> diff --git a/httemplate/misc/counties.cgi b/httemplate/misc/counties.cgi index 80ae616c9..c022a27d9 100644 --- a/httemplate/misc/counties.cgi +++ b/httemplate/misc/counties.cgi @@ -1,17 +1,7 @@ -<% +[ <% join(', ', map { qq("$_") } @counties) %> ] +<%init> - my( $state, $country ) = $cgi->param('arg'); +my( $state, $country ) = $cgi->param('arg'); +my @counties = counties($state, $country); - my @counties = - sort - map { s/[\n\r]//g; $_; } - map { $_->county; } - qsearch( 'cust_main_county', - { 'state' => $state, - 'country' => $country, - }, - ) - ; - - -%>[ <%= join(', ', map { qq("$_") } @counties) %> ] +</%init> diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi index 519e6c2b2..d29e4f5fc 100755 --- a/httemplate/misc/cust_main-cancel.cgi +++ b/httemplate/misc/cust_main-cancel.cgi @@ -1,22 +1,23 @@ -<% +% +% +%my $custnum; +%my $ban = ''; +%if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { +% $custnum = $1; +% $ban = $cgi->param('ban'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ || die "Illegal custnum"; +% $custnum = $1; +%} +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% +%my @errors = $cust_main->cancel( 'ban' => $ban ); +%eidiot(join(' / ', @errors)) if scalar(@errors); +% +%#print $cgi->redirect($p. "view/cust_main.cgi?". $cust_main->custnum); +%print $cgi->redirect($p); +% +% -my $custnum; -my $ban = ''; -if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { - $custnum = $1; - $ban = $cgi->param('ban'); -} else { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/ || die "Illegal custnum"; - $custnum = $1; -} - -my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); - -my @errors = $cust_main->cancel( 'ban' => $ban ); -eidiot(join(' / ', @errors)) if scalar(@errors); - -#print $cgi->redirect($p. "view/cust_main.cgi?". $cust_main->custnum); -print $cgi->redirect($p); - -%> diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi index 6b36f478d..73efd3705 100644 --- a/httemplate/misc/cust_main-import.cgi +++ b/httemplate/misc/cust_main-import.cgi @@ -1,51 +1,76 @@ -<!-- mason kludge --> -<%= header('Batch Customer Import') %> +<% include("/elements/header.html",'Batch Customer Import') %> + <FORM ACTION="process/cust_main-import.cgi" METHOD="post" ENCTYPE="multipart/form-data"> -Import a CSV file containing customer records.<BR><BR> -Default file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i><BR><BR> -<% - #false laziness with edit/cust_main.cgi - my @agents = qsearch( 'agent', {} ); - die "No agents created!" unless @agents; - my $agentnum = $agents[0]->agentnum; #default to first +Import a CSV file containing customer records. +<BR><BR> - if ( scalar(@agents) == 1 ) { -%> - <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>"> -<% } else { %> - <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1"> - <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %> - <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION> - <% } %> - </SELECT><BR><BR> -<% } %> - -<% - my @referrals = qsearch('part_referral',{}); - die "No advertising sources created!" unless @referrals; - my $refnum = $referrals[0]->refnum; #default to first - - if ( scalar(@referrals) == 1 ) { +<!-- Simple file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i> +<BR><BR> --> + +Extended file format is CSV, with the following field order: <i>agent_custid, refnum[1]<%$req%>, last<%$req%>, first<%$req%>, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo<%$req%>, paycvv, paydate<%$req%>, invoicing_list, pkgpart, username[2], _password[2]</i> +<BR><BR> + +<%$req%> Required fields +<BR><BR> + +[1] This field has special treatment upon import: If a string is passed instead +of an integer, the string is searched for and if necessary auto-created in the +target table. +<BR><BR> + +[2] <i>username</i> and <i>_password</i> are required if <i>pkgpart</i> is specified. +<BR><BR> + +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "<B>Agent</B>", + 'empty_label' => 'Select agent', + ) %> - <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>"> -<% } else { %> - <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1"> - <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %> - <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION> - <% } %> - </SELECT><BR><BR> -<% } %> - - First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION> -<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %> - <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> -<% } %> -</SELECT><BR><BR> - - CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR> - <INPUT TYPE="submit" VALUE="Import"> - </FORM> - </BODY> -<HTML> +<TR> + <TH ALIGN="right">Format</TH> + <TD> + <SELECT NAME="format"> +<!-- <OPTION VALUE="simple">Simple --> + <OPTION VALUE="extended" SELECTED>Extended + </SELECT> + </TD> +</TR> + +<TR> + <TH ALIGN="right">CSV filename</TH> + <TD><INPUT TYPE="file" NAME="csvfile"></TD> +</TR> +% #include('/elements/tr-select-part_referral.html') +% + + +<!-- +<TR> + <TH>First package</TH> + <TD> + <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION> +% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { + + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> +% } + + </SELECT> + </TD> +</TR> +--> + +</TABLE> +<BR><BR> + +<INPUT TYPE="submit" VALUE="Import"> +</FORM> + +<% include('/elements/footer.html') %> + +<%once> +my $req = qq!<font color="#ff0000">*</font>!; +</%once> diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi index 0822b9eb6..cd4441e0b 100644 --- a/httemplate/misc/cust_main-import_charges.cgi +++ b/httemplate/misc/cust_main-import_charges.cgi @@ -1,5 +1,5 @@ <!-- mason kludge --> -<%= header('Batch Customer Charge') %> +<% include("/elements/header.html",'Batch Customer Charge') %> <FORM ACTION="process/cust_main-import_charges.cgi" METHOD="post" ENCTYPE="multipart/form-data"> Import a CSV file containing customer charges.<BR><BR> Default file format is CSV, with the following field order: <i>custnum, amount, description</i><BR><BR> diff --git a/httemplate/misc/delete-agent_payment_gateway.cgi b/httemplate/misc/delete-agent_payment_gateway.cgi new file mode 100644 index 000000000..fb0f1e4b8 --- /dev/null +++ b/httemplate/misc/delete-agent_payment_gateway.cgi @@ -0,0 +1,15 @@ +% die "you don't have the 'Configuration' access right" +% unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ || die "Illegal agentgatewaynum"; +% my $agentgatewaynum = $1; +% +% my $agent_payment_gateway = qsearchs('agent_payment_gateway', { +% 'agentgatewaynum' => $agentgatewaynum, +% }); +% +% my $error = $agent_payment_gateway->delete; +% eidiot($error) if $error; +% +% print $cgi->redirect($p. "browse/agent.cgi"); diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi index 30de04d27..e4756a922 100755 --- a/httemplate/misc/delete-cust_credit.cgi +++ b/httemplate/misc/delete-cust_credit.cgi @@ -1,16 +1,17 @@ -<% +% +% +%#untaint crednum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal crednum"; +%my $crednum = $1; +% +%my $cust_credit = qsearchs('cust_credit',{'crednum'=>$crednum}); +%my $custnum = $cust_credit->custnum; +% +%my $error = $cust_credit->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint crednum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal crednum"; -my $crednum = $1; - -my $cust_credit = qsearchs('cust_credit',{'crednum'=>$crednum}); -my $custnum = $cust_credit->custnum; - -my $error = $cust_credit->delete; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/delete-cust_pay.cgi b/httemplate/misc/delete-cust_pay.cgi index 3efd918ab..1fda82e2a 100755 --- a/httemplate/misc/delete-cust_pay.cgi +++ b/httemplate/misc/delete-cust_pay.cgi @@ -1,16 +1,17 @@ -<% +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); +%my $custnum = $cust_pay->custnum; +% +%my $error = $cust_pay->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint paynum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal paynum"; -my $paynum = $1; - -my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); -my $custnum = $cust_pay->custnum; - -my $error = $cust_pay->delete; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/delete-cust_refund.cgi b/httemplate/misc/delete-cust_refund.cgi new file mode 100755 index 000000000..3e44560d0 --- /dev/null +++ b/httemplate/misc/delete-cust_refund.cgi @@ -0,0 +1,17 @@ +% +% +%#untaint refundnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal refundnum"; +%my $refundnum = $1; +% +%my $cust_refund = qsearchs('cust_refund',{'refundnum'=>$refundnum}); +%my $custnum = $cust_refund->custnum; +% +%my $error = $cust_refund->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi index 430231737..378f69e61 100755 --- a/httemplate/misc/delete-customer.cgi +++ b/httemplate/misc/delete-customer.cgi @@ -1,60 +1,61 @@ <!-- mason kludge --> -<% +% +% +%my $conf = new FS::Conf; +%die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); +% +%my($custnum, $new_custnum); +%if ( $cgi->param('error') ) { +% $custnum = $cgi->param('custnum'); +% $new_custnum = $cgi->param('new_custnum'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "Illegal query: $query"; +% $custnum = $1; +% $new_custnum = ''; +%} +%my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +% or die "Customer not found: $custnum"; +% +%print header('Delete customer'); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print +% qq!<form action="!, popurl(1), qq!process/delete-customer.cgi" method=post>!, +% qq!<input type="hidden" name="custnum" value="$custnum">!; +% +%if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) { +% print "Move uncancelled packages to customer number ", +% qq!<input type="text" name="new_custnum" value="$new_custnum"><br><br>!; +%} +% +%print <<END; +%This will <b>completely remove</b> all traces of this customer record. This +%is <B>not</B> what you want if this is a real customer who has simply +%canceled service with you. For that, cancel all of the customer's packages. +%(you can optionally hide cancelled customers with the <a href="../config/config-view.cgi#hidecancelledcustomers">hidecancelledcustomers</a> configuration option) +%<br> +%<br>Are you <b>absolutely sure</b> you want to delete this customer? +%<br><input type="submit" value="Yes"> +%</form></body></html> +%END +% +%#Deleting a customer you have financial records on (i.e. credits) is +%#typically considered fraudulant bookkeeping. Remember, deleting +%#customers should ONLY be used for completely bogus records. You should +%#NOT delete real customers who simply discontinue service. +%# +%#For real customers who simply discontinue service, cancel all of the +%#customer's packages. Customers with all cancelled packages are not +%#billed. There is no need to take further action to prevent billing on +%#customers with all cancelled packages. +%# +%#Also see the "hidecancelledcustomers" and "hidecancelledpackages" +%#configuration options, which will allow you to surpress the display of +%#cancelled customers and packages, respectively. +% +% -my $conf = new FS::Conf; -die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); - -my($custnum, $new_custnum); -if ( $cgi->param('error') ) { - $custnum = $cgi->param('custnum'); - $new_custnum = $cgi->param('new_custnum'); -} else { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/ or die "Illegal query: $query"; - $custnum = $1; - $new_custnum = ''; -} -my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) - or die "Customer not found: $custnum"; - -print header('Delete customer'); - -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); - -print - qq!<form action="!, popurl(1), qq!process/delete-customer.cgi" method=post>!, - qq!<input type="hidden" name="custnum" value="$custnum">!; - -if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) { - print "Move uncancelled packages to customer number ", - qq!<input type="text" name="new_custnum" value="$new_custnum"><br><br>!; -} - -print <<END; -This will <b>completely remove</b> all traces of this customer record. This -is <B>not</B> what you want if this is a real customer who has simply -canceled service with you. For that, cancel all of the customer's packages. -(you can optionally hide cancelled customers with the <a href="../config/config-view.cgi#hidecancelledcustomers">hidecancelledcustomers</a> configuration option) -<br> -<br>Are you <b>absolutely sure</b> you want to delete this customer? -<br><input type="submit" value="Yes"> -</form></body></html> -END - -#Deleting a customer you have financial records on (i.e. credits) is -#typically considered fraudulant bookkeeping. Remember, deleting -#customers should ONLY be used for completely bogus records. You should -#NOT delete real customers who simply discontinue service. -# -#For real customers who simply discontinue service, cancel all of the -#customer's packages. Customers with all cancelled packages are not -#billed. There is no need to take further action to prevent billing on -#customers with all cancelled packages. -# -#Also see the "hidecancelledcustomers" and "hidecancelledpackages" -#configuration options, which will allow you to surpress the display of -#cancelled customers and packages, respectively. - -%> diff --git a/httemplate/misc/delete-domain_record.cgi b/httemplate/misc/delete-domain_record.cgi index dcc2d5022..cccce357e 100755 --- a/httemplate/misc/delete-domain_record.cgi +++ b/httemplate/misc/delete-domain_record.cgi @@ -1,15 +1,16 @@ -<% +% +% +%#untaint recnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal recnum"; +%my $recnum = $1; +% +%my $domain_record = qsearchs('domain_record',{'recnum'=>$recnum}); +% +%my $error = $domain_record->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/svc_domain.cgi?". $domain_record->svcnum); +% +% -#untaint recnum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal recnum"; -my $recnum = $1; - -my $domain_record = qsearchs('domain_record',{'recnum'=>$recnum}); - -my $error = $domain_record->delete; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/svc_domain.cgi?". $domain_record->svcnum); - -%> diff --git a/httemplate/misc/delete-part_export.cgi b/httemplate/misc/delete-part_export.cgi index 7c4ab8b9d..16389a90c 100755 --- a/httemplate/misc/delete-part_export.cgi +++ b/httemplate/misc/delete-part_export.cgi @@ -1,15 +1,16 @@ -<% +% +% +%#untaint exportnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal exportnum"; +%my $exportnum = $1; +% +%my $part_export = qsearchs('part_export',{'exportnum'=>$exportnum}); +% +%my $error = $part_export->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "browse/part_export.cgi"); +% +% -#untaint exportnum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal exportnum"; -my $exportnum = $1; - -my $part_export = qsearchs('part_export',{'exportnum'=>$exportnum}); - -my $error = $part_export->delete; -eidiot($error) if $error; - -print $cgi->redirect($p. "browse/part_export.cgi"); - -%> diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 306ef5d63..8d6c94969 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -1,16 +1,171 @@ -<% +%if ($format eq "BoM") { +% +% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = +% $conf->config("batchconfig-$format"); +% +<% sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""). + sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) + %> +% +%}elsif ($format eq "PAP"){ +% +% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = +% $conf->config("batchconfig-$format"); +% +<% sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"") %> +% +% +%}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ +%# 1; +%}elsif ($format eq "csv-chase_canada-E-xactBatch"){ +% +% my($origid) = $conf->config("batchconfig-$format"); +<% sprintf( '$$E-xactBatchFileV1.0$$%s:%03u$$%s',$sdate,$pay_batch->batchnum, $origid) + %> +% +%}else{ +% die "Unknown format for batch in batchconfig. \n"; +%} +% +% +%for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } +% qsearch('cust_pay_batch', +% {'batchnum'=>$pay_batch->batchnum} ) +%) { +% +% $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; +% my( $mon, $y ) = ( $2, $1 ); +% if ( $conf->exists('batch-increment_expiration') ) { +% my( $curmon, $curyear ) = (localtime(time))[4,5]; +% $curmon++; $curyear-=100; +% $y++ while $y < $curyear || ( $y == $curyear && $mon < $curmon ); +% } +% $mon = "0$mon" if $mon =~ /^\d$/; +% $y = "0$y" if $y =~ /^\d$/; +% my $exp = "$mon$y"; +% +% if ( $first_download ) { +% my $balance = $cust_pay_batch->cust_main->balance; +% if ( $balance <= 0 ) { +% my $error = $cust_pay_batch->delete; +% if ( $error ) { +% $dbh->rollback or die $dbh->errstr if $oldAutoCommit; +% die $error; +% } +% next; +% } elsif ( $balance < $cust_pay_batch->amount ) { +% $cust_pay_batch->amount($balance); +% my $error = $cust_pay_batch->replace; +% if ( $error ) { +% $dbh->rollback or die $dbh->errstr if $oldAutoCommit; +% die $error; +% } +% #} elsif ( $balance > $cust_pay_batch->amount ) { +% } +% } +% +% $batchcount++; +% $batchtotal += $cust_pay_batch->amount; +% +% if ($format eq "BoM") { +% +% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); +% +<% sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->paybatchnum) %> +% +% +% } elsif ($format eq "PAP"){ +% +% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); +% +<% sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %> +% +% +% } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") { +% +% +,,,,<% $cust_pay_batch->payinfo %>,<% $exp %>,<% $cust_pay_batch->amount %>,<% $cust_pay_batch->paybatchnum %> +% +% +% } elsif ($format eq "csv-chase_canada-E-xactBatch"){ +% +% my $payname=$cust_pay_batch->payname; $payname =~ tr/",/ /; #payinfo too? :P +<% $cust_pay_batch->paybatchnum %>,<% $cust_pay_batch->custnum %>,<% $cust_pay_batch->invnum %>,"<% $payname %>",00,<% $cust_pay_batch->payinfo %>,<% $cust_pay_batch->amount %>,<% $exp %>,, +% +% +% } else { +% die "I'm already dead, but you did not know that.\n"; +% } +% +%} +% +%if ($format eq "BoM") { +% +% +<% sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ). + sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %> +% +% +%} elsif ($format eq "PAP"){ +% +% +<% sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %> +% +% +%} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ +% #1; +%} elsif ($format eq "csv-chase_canada-E-xactBatch"){ +% #1; +%} else { +% die "I'm already dead (again), but you did not know that.\n"; +%} +% +%$dbh->commit or die $dbh->errstr if $oldAutoCommit; +<%init> + +my $conf=new FS::Conf; #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); -for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } - qsearch('cust_pay_batch', {} ) -) { +my $batchnum; +if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) { + $batchnum = $1; +} else { + die "No batch number (bad URL) \n"; +} + +my $format; +if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { + $format = $1; +} else { + $format = $conf->config('batch-default_format'); +} + +my $oldAutoCommit = $FS::UID::AutoCommit; +local $FS::UID::AutoCommit = 0; +my $dbh = dbh; + +my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} ); +my $first_download = 1; +unless ($pay_batch) { + $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'I'} ) + if $FS::CurrentUser::CurrentUser->access_right('Reprocess batches'); + $first_download = 0; +} +die "No pending batch. \n" unless $pay_batch; + +my $error = $pay_batch->set_status('I'); +die "error updating batch status: $error\n" if $error; + +my $batchtotal=0; +my $batchcount=0; -$cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; -my( $mon, $y ) = ( $2, $1 ); -$mon = "0$mon" if $mon < 10; -my $exp = "$mon$y"; +my (@date)=localtime($pay_batch->download); +my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1); +my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1). + sprintf("%02d", $date[5] % 100); +my $sdate = sprintf("%02d", $date[5] % 100).'/'.sprintf("%02d", $date[4] + 1). + '/'.sprintf("%02d", $date[3]); -%>,,,,<%= $cust_pay_batch->cardnum %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %> -<% } %> +</%init> diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi index dc1323bb3..e8f4b6f38 100644 --- a/httemplate/misc/dump.cgi +++ b/httemplate/misc/dump.cgi @@ -1,19 +1,20 @@ -<% - if ( driver_name =~ /^Pg$/ ) { - my $dbname = (split(':', datasrc))[2]; - if ( $dbname =~ /[;=]/ ) { - my %elements = map { /^(\w+)=(.*)$/; $1=>$2 } split(';', $dbname); - $dbname = $elements{'dbname'}; - } - open(DUMP,"pg_dump $dbname |"); - } else { - eidiot "don't (yet) know how to dump ". driver_name. " databases\n"; - } +% +% if ( driver_name =~ /^Pg$/ ) { +% my $dbname = (split(':', datasrc))[2]; +% if ( $dbname =~ /[;=]/ ) { +% my %elements = map { /^(\w+)=(.*)$/; $1=>$2 } split(';', $dbname); +% $dbname = $elements{'dbname'}; +% } +% open(DUMP,"pg_dump $dbname |"); +% } else { +% eidiot "don't (yet) know how to dump ". driver_name. " databases\n"; +% } +% +% http_header('Content-Type' => 'text/plain' ); +% +% while (<DUMP>) { +% print $_; +% } +% close DUMP; +% - http_header('Content-Type' => 'text/plain' ); - - while (<DUMP>) { - print $_; - } - close DUMP; -%> diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi index ad9ba1abb..8a3dd90b1 100755 --- a/httemplate/misc/email-invoice.cgi +++ b/httemplate/misc/email-invoice.cgi @@ -1,17 +1,18 @@ -<% +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->email($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% -#untaint invnum -my($query) = $cgi->keywords; -$query =~ /^((.+)-)?(\d+)$/; -my $template = $2; -my $invnum = $3; -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); -die "Can't find invoice!\n" unless $cust_bill; - -$cust_bill->email($template); - -my $custnum = $cust_bill->getfield('custnum'); - -print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); - -%> diff --git a/httemplate/misc/email_invoice_events.cgi b/httemplate/misc/email_invoice_events.cgi index 12d58d608..ba6e72c1a 100644 --- a/httemplate/misc/email_invoice_events.cgi +++ b/httemplate/misc/email_invoice_events.cgi @@ -1,6 +1,4 @@ -<% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail'; -$server->process; - -%> +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/email_invoices.cgi b/httemplate/misc/email_invoices.cgi index 0a3978395..6c2103f7b 100644 --- a/httemplate/misc/email_invoices.cgi +++ b/httemplate/misc/email_invoices.cgi @@ -1,6 +1,4 @@ -<% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail'; -$server->process; - -%> +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/expire_pkg.cgi b/httemplate/misc/expire_pkg.cgi deleted file mode 100755 index b59674a69..000000000 --- a/httemplate/misc/expire_pkg.cgi +++ /dev/null @@ -1,55 +0,0 @@ -<!-- mason kludge --> -<% - -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $pkgnum = $1; - -#get package record -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); -die "Unknown pkgnum $pkgnum" unless $cust_pkg; -my $part_pkg = $cust_pkg->part_pkg; - -my $custnum = $cust_pkg->getfield('custnum'); - -my $date = $cust_pkg->expire ? time2str('%D', $cust_pkg->expire) : ''; - -%> - -<%= header('Expire package', menubar( - "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - 'Main Menu' => popurl(2) -)) %> - -<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> - -<%= $pkgnum %>: <%= $part_pkg->pkg. ' - '. $part_pkg->comment %> - -<FORM NAME="formname" ACTION="process/expire_pkg.cgi" METHOD="post"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> -<TABLE> - <TR> - <TD>Cancel package on </TD> - <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<%= $date %>"> - <IMG SRC="<%= $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date"> - <BR><I>m/d/y</I> - </TD> - </TR> -</TABLE> - -<SCRIPT TYPE="text/javascript"> - Calendar.setup({ - inputField: "expire_date", - ifFormat: "%m/%d/%Y", - button: "expire_button", - align: "BR" - }); -</SCRIPT> - -<INPUT TYPE="submit" VALUE="Cancel later"> -</FORM> -</BODY> -</HTML> diff --git a/httemplate/misc/fax-invoice.cgi b/httemplate/misc/fax-invoice.cgi index 94fee2cf2..1ddc23ece 100755 --- a/httemplate/misc/fax-invoice.cgi +++ b/httemplate/misc/fax-invoice.cgi @@ -1,17 +1,18 @@ -<% +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->fax($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% -#untaint invnum -my($query) = $cgi->keywords; -$query =~ /^((.+)-)?(\d+)$/; -my $template = $2; -my $invnum = $3; -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); -die "Can't find invoice!\n" unless $cust_bill; - -$cust_bill->fax($template); - -my $custnum = $cust_bill->getfield('custnum'); - -print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); - -%> diff --git a/httemplate/misc/fax_invoice_events.cgi b/httemplate/misc/fax_invoice_events.cgi index a8ded0550..deb78d456 100644 --- a/httemplate/misc/fax_invoice_events.cgi +++ b/httemplate/misc/fax_invoice_events.cgi @@ -1,6 +1,4 @@ -<% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax'; -$server->process; - -%> +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/fax_invoices.cgi b/httemplate/misc/fax_invoices.cgi index f16ba8b5e..4bdac970c 100644 --- a/httemplate/misc/fax_invoices.cgi +++ b/httemplate/misc/fax_invoices.cgi @@ -1,6 +1,4 @@ -<% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax'; -$server->process; - -%> +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/inventory_item-import.html b/httemplate/misc/inventory_item-import.html new file mode 100644 index 000000000..87c6af34c --- /dev/null +++ b/httemplate/misc/inventory_item-import.html @@ -0,0 +1,21 @@ +% +% +%my $classnum = $cgi->param('classnum'); +%$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; +%$classnum = $1; +%my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } ); +% +% +<% include("/elements/header.html", $inventory_class->classname. 's') %> + +<FORM ACTION="process/inventory_item-import.html" METHOD="POST" ENCTYPE="multipart/form-data"> +<INPUT TYPE="hidden" NAME="classnum" VALUE="<% $classnum %>"> +Import a file containing <% $inventory_class->classname %>s, one per line.<BR><BR> + +Filename: <INPUT TYPE="file" NAME="filename"><BR><BR> + +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi index 18cd378d3..ef72b4a5c 100755 --- a/httemplate/misc/link.cgi +++ b/httemplate/misc/link.cgi @@ -1,73 +1,76 @@ -<!-- mason kludge --> -<% +%my %link_field = ( +% 'svc_acct' => 'username', +% 'svc_domain' => 'domain', +%); +% +%my %link_field2 = ( +% 'svc_acct' => { label => 'Domain', +% field => 'domsvc', +% type => 'select', +% select_table => 'svc_domain', +% select_key => 'svcnum', +% select_label => 'domain' +% }, +%); +% +%$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +%my $pkgnum = $1; +%$cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +%my $svcpart = $1; +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); +%my $svc = $part_svc->getfield('svc'); +%my $svcdb = $part_svc->getfield('svcdb'); +%my $link_field = $link_field{$svcdb}; +%my $link_field2 = $link_field2{$svcdb}; +% -my %link_field = ( - 'svc_acct' => 'username', - 'svc_domain' => 'domain', -); +<% include("/elements/header.html","Link to existing $svc") %> +<FORM ACTION="<% popurl(1) %>process/link.cgi" METHOD=POST> +% if ( $link_field ) { -my %link_field2 = ( - 'svc_acct' => { label => 'Domain', - field => 'domsvc', - type => 'select', - select_table => 'svc_domain', - select_key => 'svcnum', - select_label => 'domain' - }, -); - -my($query) = $cgi->keywords; -my($pkgnum, $svcpart) = ('', ''); -foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart - $pkgnum=$1 if /^pkgnum(\d+)$/; - $svcpart=$1 if /^svcpart(\d+)$/; -} - -my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); -my $svc = $part_svc->getfield('svc'); -my $svcdb = $part_svc->getfield('svcdb'); -my $link_field = $link_field{$svcdb}; -my $link_field2 = $link_field2{$svcdb}; - -%> - -<%= header("Link to existing $svc") %> -<FORM ACTION="<%= popurl(1) %>process/link.cgi" METHOD=POST> - -<% if ( $link_field ) { %> <INPUT TYPE="hidden" NAME="svcnum" VALUE=""> - <INPUT TYPE="hidden" NAME="link_field" VALUE="<%= $link_field %>"> - <%= $link_field %> of existing service: <INPUT TYPE="text" NAME="link_value"> + <INPUT TYPE="hidden" NAME="link_field" VALUE="<% $link_field %>"> + <% $link_field %> of existing service: <INPUT TYPE="text" NAME="link_value"> <BR> - <% if ( $link_field2 ) { %> - <INPUT TYPE="hidden" NAME="link_field2" VALUE="<%= $link_field2->{field} %>"> - <%= $link_field2->{'label'} %> of existing service: - <% if ( $link_field2->{'type'} eq 'select' ) { %> - <% if ( $link_field2->{'select_table'} ) { %> +% if ( $link_field2 ) { + + <INPUT TYPE="hidden" NAME="link_field2" VALUE="<% $link_field2->{field} %>"> + <% $link_field2->{'label'} %> of existing service: +% if ( $link_field2->{'type'} eq 'select' ) { +% if ( $link_field2->{'select_table'} ) { + <SELECT NAME="link_value2"> <OPTION> </OPTION> - <% foreach my $r ( qsearch( $link_field2->{'select_table'}, {})) { %> - <% my $key = $link_field2->{'select_key'}; %> - <% my $label = $link_field2->{'select_label'}; %> - <OPTION VALUE="<%= $r->$key() %>"><%= $r->$label() %></OPTION> - <% } %> +% foreach my $r ( qsearch( $link_field2->{'select_table'}, {})) { +% my $key = $link_field2->{'select_key'}; +% my $label = $link_field2->{'select_label'}; + + <OPTION VALUE="<% $r->$key() %>"><% $r->$label() %></OPTION> +% } + </SELECT> - <% } else { %> - Don't know how to process secondary link field for <%= $svcdb %> +% } else { + + Don't know how to process secondary link field for <% $svcdb %> (type=>select but no select_table) - <% } %> - <% } else { %> - Don't know how to process secondary link field for <%= $svcdb %> - (unknown type <%= $link_field2->{'type'} %>) - <% } %> +% } +% } else { + + Don't know how to process secondary link field for <% $svcdb %> + (unknown type <% $link_field2->{'type'} %>) +% } + <BR> - <% } %> -<% } else { %> +% } +% } else { + Service # of existing service: <INPUT TYPE="text" NAME="svcnum" VALUE=""> -<% } %> +% } + -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> -<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> <BR><INPUT TYPE="submit" VALUE="Link"> </FORM> </BODY> diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi index 2f3b7380d..fc249a2ab 100644 --- a/httemplate/misc/meta-import.cgi +++ b/httemplate/misc/meta-import.cgi @@ -1,45 +1,54 @@ <!-- mason kludge --> -<%= header('Import') %> +<% include("/elements/header.html",'Import') %> <FORM ACTION="process/meta-import.cgi" METHOD="post" ENCTYPE="multipart/form-data"> Import data from a DBI data source<BR><BR> +% +% #false laziness with edit/cust_main.cgi +% my @agents = qsearch( 'agent', {} ); +% die "No agents created!" unless @agents; +% my $agentnum = $agents[0]->agentnum; #default to first +% +% if ( scalar(@agents) == 1 ) { +% -<% - #false laziness with edit/cust_main.cgi - my @agents = qsearch( 'agent', {} ); - die "No agents created!" unless @agents; - my $agentnum = $agents[0]->agentnum; #default to first + <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>"> +% } else { - if ( scalar(@agents) == 1 ) { -%> - <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>"> -<% } else { %> <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1"> - <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %> - <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION> - <% } %> +% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { + + <OPTION VALUE="<% $agent->agentnum %>" <% " SELECTED"x($agent->agentnum==$agentnum) %>><% $agent->agent %></OPTION> +% } + </SELECT><BR><BR> -<% } %> +% } +% +% my @referrals = qsearch('part_referral',{}); +% die "No advertising sources created!" unless @referrals; +% my $refnum = $referrals[0]->refnum; #default to first +% +% if ( scalar(@referrals) == 1 ) { +% -<% - my @referrals = qsearch('part_referral',{}); - die "No advertising sources created!" unless @referrals; - my $refnum = $referrals[0]->refnum; #default to first + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>"> +% } else { - if ( scalar(@referrals) == 1 ) { -%> - <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>"> -<% } else { %> <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1"> - <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %> - <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION> - <% } %> +% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { + + <OPTION VALUE="<% $referral->refnum %>" <% " SELECTED"x($referral->refnum==$refnum) %>><% $referral->refnum %>: <% $referral->referral %></OPTION> +% } + </SELECT><BR><BR> -<% } %> +% } + First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION> -<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %> - <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> -<% } %> +% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { + + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> +% } + </SELECT><BR><BR> <table> diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index d4fb4a2be..728eba7b9 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -1,108 +1,104 @@ -<% - my %type = ( 'CARD' => 'credit card', - 'CHEK' => 'electronic check (ACH)', - ); +% +% my %type = ( 'CARD' => 'credit card', +% 'CHEK' => 'electronic check (ACH)', +% ); +% +% $cgi->param('payby') =~ /^(CARD|CHEK)$/ +% or die "unknown payby ". $cgi->param('payby'); +% my $payby = $1; +% +% $cgi->param('custnum') =~ /^(\d+)$/ +% or die "illegal custnum ". $cgi->param('custnum'); +% my $custnum = $1; +% +% my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); +% die "unknown custnum $custnum" unless $cust_main; +% +% my $balance = $cust_main->balance; +% +% my $payinfo = ''; +% +% #false laziness w/selfservice make_payment.html shortcut for one-country +% my $conf = new FS::Conf; +% my %states = map { $_->state => 1 } +% qsearch('cust_main_county', { +% 'country' => $conf->config('countrydefault') || 'US' +% } ); +% my @states = sort { $a cmp $b } keys %states; +% +% my $paybatch = "webui-payment-". time. "-$$-". rand() * 2**32; +% +% - $cgi->param('payby') =~ /^(CARD|CHEK)$/ - or die "unknown payby ". $cgi->param('payby'); - my $payby = $1; - - $cgi->param('custnum') =~ /^(\d+)$/ - or die "illegal custnum ". $cgi->param('custnum'); - my $custnum = $1; - - my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); - die "unknown custnum $custnum" unless $cust_main; - - my $balance = $cust_main->balance; - - my $payinfo = ''; - - #false laziness w/selfservice make_payment.html shortcut for one-country - my $conf = new FS::Conf; - my %states = map { $_->state => 1 } - qsearch('cust_main_county', { - 'country' => $conf->config('countrydefault') || 'US' - } ); - my @states = sort { $a cmp $b } keys %states; - - my $paybatch = "webui-payment-". time. "-$$-". rand() * 2**32; - -%> -<%= include( '/elements/header.html', "Process $type{$payby} payment" ) %> -<%= include( '/elements/small_custview.html', $cust_main ) %> +<% include( '/elements/header.html', "Process $type{$payby} payment" ) %> +<% include( '/elements/small_custview.html', $cust_main, '', '', popurl(2) . "view/cust_main.cgi" ) %> <FORM NAME="OneTrueForm" ACTION="process/payment.cgi" METHOD="POST" onSubmit="document.OneTrueForm.process.disabled=true"> -<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>"> -<INPUT TYPE="hidden" NAME="payby" VALUE="<%= $payby %>"> -<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%= $paybatch %>"> -<SCRIPT> -var mywindow = -1; -function myopen(filename,windowname,properties) { - myclose(); - mywindow = window.open(filename,windowname,properties); -} -function myclose() { - if ( mywindow != -1 ) - mywindow.close(); - mywindow = -1; -} -var achwindow = -1; -function achopen(filename,windowname,properties) { - achclose(); - achwindow = window.open(filename,windowname,properties); -} -function achclose() { - if ( achwindow != -1 ) - achwindow.close(); - achwindow = -1; +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> +<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>"> + +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript"> +function OLiframeContent(src, width, height, name) { + return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"' + +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">' + +'<div>[iframe not supported]</div></iframe>'); } </SCRIPT> -<% #include( '/elements/table.html', '#cccccc' ) %> -<%= ntable('#cccccc') %> +% #include( '/elements/table.html', '#cccccc' ) + +<% ntable('#cccccc') %> <TR> <TD ALIGN="right">Payment amount</TD> <TD> <TABLE><TR><TD BGCOLOR="#ffffff"> - $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<%= $balance > 0 ? sprintf("%.2f", $balance) : '' %>"> + $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<% $balance > 0 ? sprintf("%.2f", $balance) : '' %>"> </TD></TR></TABLE> </TD> </TR> -<% if ( $payby eq 'CARD' ) { - my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' ); - my $payname = $cust_main->first. ' '. $cust_main->getfield('last'); - my $address1 = $cust_main->address1; - my $address2 = $cust_main->address2; - my $city = $cust_main->city; - my $state = $cust_main->state; - my $zip = $cust_main->zip; - if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { - $payinfo = $cust_main->payinfo; - $paycvv = $cust_main->paycvv; - ( $month, $year ) = $cust_main->paydate_monthyear; - $payname = $cust_main->payname if $cust_main->payname; - } -%> +% if ( $payby eq 'CARD' ) { +% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' ); +% my $payname = $cust_main->first. ' '. $cust_main->getfield('last'); +% my $address1 = $cust_main->address1; +% my $address2 = $cust_main->address2; +% my $city = $cust_main->city; +% my $state = $cust_main->state; +% my $zip = $cust_main->zip; +% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { +% $payinfo = $cust_main->paymask; +% $paycvv = $cust_main->paycvv; +% ( $month, $year ) = $cust_main->paydate_monthyear; +% $payname = $cust_main->payname if $cust_main->payname; +% } +% + <TR> <TD ALIGN="right">Card number</TD> <TD> <TABLE> <TR> <TD> - <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%=$payinfo%>"> </TD> + <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%$payinfo%>"> </TD> <TD>Exp.</TD> <TD> <SELECT NAME="month"> - <% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { %> - <OPTION<%= $_ == $month ? ' SELECTED' : '' %>><%= $_ %> - <% } %> +% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { + + <OPTION<% $_ == $month ? ' SELECTED' : '' %>><% $_ %> +% } + </SELECT> </TD> <TD> / </TD> <TD> <SELECT NAME="year"> - <% my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { %> - <OPTION<%= $_ == $year ? ' SELECTED' : '' %>><%= $_ %> - <% } %> +% my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { + + <OPTION<% $_ == $year ? ' SELECTED' : '' %>><% $_ %> +% } + </SELECT> </TD> </TR> @@ -111,22 +107,22 @@ function achclose() { </TR> <TR> <TD ALIGN="right">CVV2</TD> - <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<%= $paycvv %>" SIZE=4 MAXLENGTH=4> - (<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>) + <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<% $paycvv %>" SIZE=4 MAXLENGTH=4> + (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>) </TD> </TR> <TR> <TD ALIGN="right">Exact name on card</TD> - <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD> + <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%$payname%>"></TD> </TR><TR> <TD ALIGN="right">Card billing address</TD> <TD> - <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%=$address1%>"> + <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%$address1%>"> </TD> </TR><TR> <TD ALIGN="right">Address line 2</TD> <TD> - <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%=$address2%>"> + <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%$address2%>"> </TD> </TR><TR> <TD ALIGN="right">City</TD> @@ -134,61 +130,63 @@ function achclose() { <TABLE> <TR> <TD> - <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%=$city%>"> + <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%$city%>"> </TD> <TD>State</TD> <TD> <SELECT NAME="state"> - <% for ( @states ) { %> - <OPTION<%= $_ eq $state ? ' SELECTED' : '' %>><%= $_ %> - <% } %> +% for ( @states ) { + + <OPTION<% $_ eq $state ? ' SELECTED' : '' %>><% $_ %> +% } + </SELECT> </TD> <TD>Zip</TD> <TD> - <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%=$zip%>"> + <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%$zip%>"> </TD> </TR> </TABLE> </TD> </TR> +% } elsif ( $payby eq 'CHEK' ) { +% my( $payinfo1, $payinfo2, $payname, $ss ) = ( '', '', '', '' ); +% if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { +% $cust_main->paymask =~ /^([\dx]+)\@([\dx]+)$/i +% or die "unparsable payinfo ". $cust_main->payinfo; +% ($payinfo1, $payinfo2) = ($1, $2); +% $payname = $cust_main->payname; +% $ss = $cust_main->ss; +% } +% -<% } elsif ( $payby eq 'CHEK' ) { - my( $payinfo1, $payinfo2, $payname, $ss ) = ( '', '', '', '' ); - if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { - $cust_main->payinfo =~ /^(\d+)\@(\d+)$/ - or die "unparsable payinfo ". $cust_main->payinfo; - ($payinfo1, $payinfo2) = ($1, $2); - $payname = $cust_main->payname; - $ss = $cust_main->ss; - } -%> <INPUT TYPE="hidden" NAME="month" VALUE="12"> <INPUT TYPE="hidden" NAME="year" VALUE="2037"> <TR> <TD ALIGN="right">Account number</TD> - <TD><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="<%=$payinfo1%>"></TD> + <TD><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="<%$payinfo1%>"></TD> </TR> <TR> <TD ALIGN="right">ABA/Routing number</TD> <TD> - <INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="<%=$payinfo2%>"> - (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>) + <INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="<%$payinfo2%>"> + (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>) </TD> </TR> <TR> <TD ALIGN="right">Bank name</TD> - <TD><INPUT TYPE="text" NAME="payname" VALUE="<%=$payname%>"></TD> + <TD><INPUT TYPE="text" NAME="payname" VALUE="<%$payname%>"></TD> </TR> <TR> <TD ALIGN="right"> Account holder<BR> Social security or tax ID # </TD> - <TD><INPUT TYPE="text" NAME="ss" VALUE="<%=$ss%>"></TD> + <TD><INPUT TYPE="text" NAME="ss" VALUE="<%$ss%>"></TD> </TR> +% } -<% } %> <TR> <TD COLSPAN=2> @@ -197,13 +195,13 @@ function achclose() { </TD> </TR><TR> <TD COLSPAN=2> - <INPUT TYPE="checkbox"<%= ( ( $payby eq 'CARD' && $cust_main->payby ne 'DCRD' ) || ( $payby eq 'CHEK' && $cust_main->payby eq 'CHEK' ) ) ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }"> - Charge future payments to this <%= $type{$payby} %> automatically + <INPUT TYPE="checkbox"<% ( ( $payby eq 'CARD' && $cust_main->payby ne 'DCRD' ) || ( $payby eq 'CHEK' && $cust_main->payby eq 'CHEK' ) ) ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }"> + Charge future payments to this <% $type{$payby} %> automatically </TD> </TR> </TABLE> <BR> <INPUT TYPE="submit" NAME="process" VALUE="Process payment"> </FORM> -</BODY> -</HTML> + +<% include('/elements/footer.html') %> diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi index 6a4c2d7f1..511bdce19 100755 --- a/httemplate/misc/print-invoice.cgi +++ b/httemplate/misc/print-invoice.cgi @@ -1,17 +1,18 @@ -<% +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->print($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% -#untaint invnum -my($query) = $cgi->keywords; -$query =~ /^((.+)-)?(\d+)$/; -my $template = $2; -my $invnum = $3; -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); -die "Can't find invoice!\n" unless $cust_bill; - -$cust_bill->print($template); - -my $custnum = $cust_bill->getfield('custnum'); - -print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); - -%> diff --git a/httemplate/misc/print_invoice_events.cgi b/httemplate/misc/print_invoice_events.cgi index c6a7885a4..913e2683f 100644 --- a/httemplate/misc/print_invoice_events.cgi +++ b/httemplate/misc/print_invoice_events.cgi @@ -1,6 +1,4 @@ -<% +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi; -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint'; -$server->process; - -%> +<% $server->process %> diff --git a/httemplate/misc/print_invoices.cgi b/httemplate/misc/print_invoices.cgi index d7b271c37..826a081fd 100644 --- a/httemplate/misc/print_invoices.cgi +++ b/httemplate/misc/print_invoices.cgi @@ -1,6 +1,4 @@ -<% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint'; -$server->process; - -%> +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi index 12d72e8a0..e4d1bbff5 100644 --- a/httemplate/misc/process/batch-cust_pay.cgi +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -1,42 +1,45 @@ -<% - my $param = $cgi->Vars; +% +% my $param = $cgi->Vars; +% +% #my $paybatch = $param->{'paybatch'}; +% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); +% +% my @cust_pay = (); +% #my $row = 0; +% #while ( exists($param->{"custnum$row"}) ) { +% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { +% push @cust_pay, new FS::cust_pay { +% 'custnum' => $param->{"custnum$row"}, +% 'paid' => $param->{"paid$row"}, +% 'payby' => 'BILL', +% 'payinfo' => $param->{"payinfo$row"}, +% 'paybatch' => $paybatch, +% } +% if $param->{"custnum$row"} +% || $param->{"paid$row"} +% || $param->{"payinfo$row"}; +% #$row++; +% } +% +% my @errors = FS::cust_pay->batch_insert(@cust_pay); +% my $num_errors = scalar(grep $_, @errors); +% +% if ( $num_errors ) { +% +% $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : ''). +% ' - Batch not processed, correct and resubmit' +% ); +% +% my $erow=0; +% $cgi->param('error'. $erow++, shift @errors) while @errors; +% +% +<% $cgi->redirect($p.'batch-cust_pay.html?'. $cgi->query_string) + + %> +% } else { +% +% +<% $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %> +% } - #my $paybatch = $param->{'paybatch'}; - my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); - - my @cust_pay = (); - #my $row = 0; - #while ( exists($param->{"custnum$row"}) ) { - for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { - push @cust_pay, new FS::cust_pay { - 'custnum' => $param->{"custnum$row"}, - 'paid' => $param->{"paid$row"}, - 'payby' => 'BILL', - 'payinfo' => $param->{"payinfo$row"}, - 'paybatch' => $paybatch, - } - if $param->{"custnum$row"} - || $param->{"paid$row"} - || $param->{"payinfo$row"}; - #$row++; - } - - my @errors = FS::cust_pay->batch_insert(@cust_pay); - my $num_errors = scalar(grep $_, @errors); - - if ( $num_errors ) { - - $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : ''). - ' - Batch not processed, correct and resubmit' - ); - - my $erow=0; - $cgi->param('error'. $erow++, shift @errors) while @errors; - - %><%= $cgi->redirect($p.'batch-cust_pay.html?'. $cgi->query_string) - - %><% } else { - - %><%= $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %> - - <% } %> diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html new file mode 100755 index 000000000..cd533be10 --- /dev/null +++ b/httemplate/misc/process/cancel_pkg.html @@ -0,0 +1,78 @@ +<%init> +#untaint method +my $method = $cgi->param('method'); +$method =~ /^(cancel|expire|suspend)$/ || die "Illegal method"; +$method = $1; + +#untaint pkgnum +my $pkgnum = $cgi->param('pkgnum'); +$pkgnum =~ /^(\d+)$/ || die "Illegal pkgnum"; +$pkgnum = $1; + +#untaint reasonnum +my $reasonnum = $cgi->param('reasonnum'); +$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum"; +$reasonnum = $1; + +my $date = time; +if ($method eq 'expire'){ + #untaint date + $date = $cgi->param('date'); + str2time($cgi->param('date')) =~ /^(\d+)$/ || die "Illegal date"; + $date = $1; +} + +my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); + +my $oldAutoCommit = $FS::UID::AutoCommit; +local $FS::UID::AutoCommit = 0; +my $dbh = dbh; + +my $otaker = $FS::CurrentUser::CurrentUser->name; +$otaker = $FS::CurrentUser::CurrentUser->username + if ($otaker eq "User, Legacy"); + +my $error = ''; +if ($reasonnum == -1) { + + $error = 'Enter a new reason (or select an existing one)' + unless $cgi->param('newreasonnum') !~ /^\s*$/; + + my $reason = new FS::reason({ 'reason_type' => $cgi->param('newreasonnumT'), + 'reason' => $cgi->param('newreasonnum'), + }); + $error ||= $reason->insert; + $reasonnum = $reason->reasonnum + unless $error; +} + +unless ($error) { + if ($method eq 'expire'){ + my %hash = $cust_pkg->hash; + $hash{'expire'}=$date; + my $new = new FS::cust_pkg (\%hash); + $error = $new->replace($cust_pkg, 'reason' => $reasonnum); + }else{ + $error = $cust_pkg->$method( 'reason' => $reasonnum ); + } +} + +if ($error) { + $cgi->param('error', $error); + $dbh->rollback if $oldAutoCommit; + print $cgi->redirect(popurl(2). "cancel_pkg.html?". $cgi->query_string ); +} + +$dbh->commit or die $dbh->errstr if $oldAutoCommit; + + my %past = ( 'cancel' => 'cancelled', + 'expire' => 'expired', + 'suspend' => 'suspended', + ); +</%init> +<% header("Package $past{$method}") %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY></HTML> + diff --git a/httemplate/misc/process/catchall.cgi b/httemplate/misc/process/catchall.cgi index 44a63f9f8..f2899c720 100755 --- a/httemplate/misc/process/catchall.cgi +++ b/httemplate/misc/process/catchall.cgi @@ -1,33 +1,34 @@ -<% +% +% +%$FS::svc_domain::whois_hack=1; +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum =$1; +% +%my $old = qsearchs('svc_domain',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::svc_domain ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_domain'), qw( pkgnum svcpart ) ) +%} ); +% +%$new->setfield('action' => 'M'); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->getfield('svcnum'); +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "catchall.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% -$FS::svc_domain::whois_hack=1; - -$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; -my $svcnum =$1; - -my $old = qsearchs('svc_domain',{'svcnum'=>$svcnum}) if $svcnum; - -my $new = new FS::svc_domain ( { - map { - ($_, scalar($cgi->param($_))); - } ( fields('svc_domain'), qw( pkgnum svcpart ) ) -} ); - -$new->setfield('action' => 'M'); - -my $error; -if ( $svcnum ) { - $error = $new->replace($old); -} else { - $error = $new->insert; - $svcnum = $new->getfield('svcnum'); -} - -if ($error) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "catchall.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); -} - -%> diff --git a/httemplate/misc/process/cdr-import.html b/httemplate/misc/process/cdr-import.html new file mode 100644 index 000000000..68edaa21c --- /dev/null +++ b/httemplate/misc/process/cdr-import.html @@ -0,0 +1,30 @@ +% +% +% my $fh = $cgi->upload('csvfile'); +% +% my $error = defined($fh) +% ? FS::cdr::batch_import( { +% 'filehandle' => $fh, +% 'format' => $cgi->param('format'), +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> + <!-- XXX redirect to batch search like the payment entry... --> + <% include("/elements/footer.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi index 9e1adce54..a5ede99a1 100644 --- a/httemplate/misc/process/cust_main-import.cgi +++ b/httemplate/misc/process/cust_main-import.cgi @@ -1,30 +1,35 @@ -<% +% +% +% my $fh = $cgi->upload('csvfile'); +% #warn $cgi; +% #warn $fh; +% +% my $error = defined($fh) +% ? FS::cust_main::batch_import( { +% filehandle => $fh, +% agentnum => scalar($cgi->param('agentnum')), +% refnum => scalar($cgi->param('refnum')), +% pkgpart => scalar($cgi->param('pkgpart')), +% #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2 +% # city state zip comments )], +% 'format' => scalar($cgi->param('format')), +% } ) +% : 'No file'; +% +% if ( $error ) { +% - my $fh = $cgi->upload('csvfile'); - #warn $cgi; - #warn $fh; - - my $error = defined($fh) - ? FS::cust_main::batch_import( { - filehandle => $fh, - agentnum => scalar($cgi->param('agentnum')), - refnum => scalar($cgi->param('refnum')), - pkgpart => scalar($cgi->param('pkgpart')), - 'fields' => [qw( cust_pkg.setup dayphone first last address1 address2 - city state zip comments )], - } ) - : 'No file'; - - if ( $error ) { - %> <!-- mason kludge --> - <% - eidiot($error); -# $cgi->param('error', $error); -# print $cgi->redirect( "${p}cust_main-import.cgi - } else { - %> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + <!-- mason kludge --> - <%= header('Import sucessful') %> <% - } -%> + <% include("/elements/header.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/cust_main-import_charges.cgi b/httemplate/misc/process/cust_main-import_charges.cgi index 14df1bd8d..e0ede576b 100644 --- a/httemplate/misc/process/cust_main-import_charges.cgi +++ b/httemplate/misc/process/cust_main-import_charges.cgi @@ -1,26 +1,30 @@ -<% +% +% +% my $fh = $cgi->upload('csvfile'); +% #warn $cgi; +% #warn $fh; +% +% my $error = defined($fh) +% ? FS::cust_main::batch_charge( { +% filehandle => $fh, +% 'fields' => [qw( custnum amount pkg )], +% } ) +% : 'No file'; +% +% if ( $error ) { +% - my $fh = $cgi->upload('csvfile'); - #warn $cgi; - #warn $fh; - - my $error = defined($fh) - ? FS::cust_main::batch_charge( { - filehandle => $fh, - 'fields' => [qw( custnum amount pkg )], - } ) - : 'No file'; - - if ( $error ) { - %> <!-- mason kludge --> - <% - eidiot($error); -# $cgi->param('error', $error); -# print $cgi->redirect( "${p}cust_main-import_charges.cgi - } else { - %> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import_charges.cgi +% } else { +% + <!-- mason kludge --> - <%= header('Import sucessful') %> <% - } -%> + <% include("/elements/header.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi index 16bdbaea8..d0d237ee8 100755 --- a/httemplate/misc/process/delete-customer.cgi +++ b/httemplate/misc/process/delete-customer.cgi @@ -1,29 +1,30 @@ -<% +% +% +%my $conf = new FS::Conf; +%die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); +% +%$cgi->param('custnum') =~ /^(\d+)$/; +%my $custnum = $1; +%my $new_custnum; +%if ( $cgi->param('new_custnum') ) { +% $cgi->param('new_custnum') =~ /^(\d+)$/ +% or die "Illegal new customer number: ". $cgi->param('new_custnum'); +% $new_custnum = $1; +%} else { +% $new_custnum = ''; +%} +%my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +% or die "Customer not found: $custnum"; +% +%my $error = $cust_main->delete($new_custnum); +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ); +%} elsif ( $new_custnum ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum"); +%} else { +% print $cgi->redirect(popurl(3)); +%} +% -my $conf = new FS::Conf; -die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); - -$cgi->param('custnum') =~ /^(\d+)$/; -my $custnum = $1; -my $new_custnum; -if ( $cgi->param('new_custnum') ) { - $cgi->param('new_custnum') =~ /^(\d+)$/ - or die "Illegal new customer number: ". $cgi->param('new_custnum'); - $new_custnum = $1; -} else { - $new_custnum = ''; -} -my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) - or die "Customer not found: $custnum"; - -my $error = $cust_main->delete($new_custnum); - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ); -} elsif ( $new_custnum ) { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum"); -} else { - print $cgi->redirect(popurl(3)); -} -%> diff --git a/httemplate/misc/process/expire_pkg.cgi b/httemplate/misc/process/expire_pkg.cgi deleted file mode 100755 index dc35592ce..000000000 --- a/httemplate/misc/process/expire_pkg.cgi +++ /dev/null @@ -1,25 +0,0 @@ -<% - -#untaint date & pkgnum - -my $date; -if ( $cgi->param('date') ) { - str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date"; - $date=$1; -} else { - $date=''; -} - -$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum"; -my $pkgnum = $1; - -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); -my %hash = $cust_pkg->hash; -$hash{expire}=$date; -my $new = new FS::cust_pkg ( \%hash ); -my $error = $new->replace($cust_pkg); -&eidiot($error) if $error; - -print $cgi->redirect(popurl(3). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); - -%> diff --git a/httemplate/misc/process/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html new file mode 100644 index 000000000..f6159dc99 --- /dev/null +++ b/httemplate/misc/process/inventory_item-import.html @@ -0,0 +1,31 @@ +% +% +% my $fh = $cgi->upload('filename'); +% +% my $error = defined($fh) +% ? FS::inventory_item::batch_import( { +% 'filehandle' => $fh, +% 'classnum' => $cgi->param('classnum'), +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> + <!-- XXX redirect to batch search like the payment entry... --> + <% include("/elements/footer.html",'Import successful') %> +% +% } +% + + diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi index c3d79e22c..fd3d8bb13 100755 --- a/httemplate/misc/process/link.cgi +++ b/httemplate/misc/process/link.cgi @@ -1,76 +1,78 @@ -<% +% +% +%my $DEBUG = 0; +% +%$cgi->param('pkgnum') =~ /^(\d+)$/; +%my $pkgnum = $1; +%$cgi->param('svcpart') =~ /^(\d+)$/; +%my $svcpart = $1; +%$cgi->param('svcnum') =~ /^(\d*)$/; +%my $svcnum = $1; +% +%unless ( $svcnum ) { +% my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); +% my $svcdb = $part_svc->getfield('svcdb'); +% $cgi->param('link_field') =~ /^(\w+)$/; +% my $link_field = $1; +% my %search = ( $link_field => $cgi->param('link_value') ); +% if ( $cgi->param('link_field2') =~ /^(\w+)$/ ) { +% $search{$1} = $cgi->param('link_value2'); +% } +% +% my @svc_x = ( sort { ($a->cust_svc->pkgnum > 0) <=> ($b->cust_svc->pkgnum > 0) +% or ($b->cust_svc->svcpart == $svcpart) +% <=> ($a->cust_svc->svcpart == $svcpart) +% } +% qsearch( $svcdb, \%search ) +% ); +% +% if ( $DEBUG ) { +% warn scalar(@svc_x). " candidate accounts found for linking ". +% "(svcpart $svcpart):\n"; +% foreach my $svc_x ( @svc_x ) { +% warn " ". $svc_x->email. +% " (svcnum ". $svc_x->svcnum. ",". +% " pkgnum ". $svc_x->cust_svc->pkgnum. ",". +% " svcpart ". $svc_x->cust_svc->svcpart. ")\n"; +% } +% } +% +% my $svc_x = $svc_x[0]; +% +% eidiot("$link_field not found!") unless $svc_x; +% +% $svcnum = $svc_x->svcnum; +% +%} +% +%my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "svcnum not found!" unless $old; +%my $conf = new FS::Conf; +%my($error, $new); +%if ( $old->pkgnum && ! $conf->exists('legacy_link-steal') ) { +% $error = "svcnum $svcnum already linked to package ". $old->pkgnum; +%} else { +% $new = new FS::cust_svc ({ +% 'svcnum' => $svcnum, +% 'pkgnum' => $pkgnum, +% 'svcpart' => $svcpart, +% }); +% +% $error = $new->replace($old); +%} +% +%unless ($error) { +% #no errors, so let's view this customer. +% my $custnum = $new->cust_pkg->custnum; +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg$pkgnum" ); +%} else { +% -my $DEBUG = 0; - -$cgi->param('pkgnum') =~ /^(\d+)$/; -my $pkgnum = $1; -$cgi->param('svcpart') =~ /^(\d+)$/; -my $svcpart = $1; -$cgi->param('svcnum') =~ /^(\d*)$/; -my $svcnum = $1; - -unless ( $svcnum ) { - my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); - my $svcdb = $part_svc->getfield('svcdb'); - $cgi->param('link_field') =~ /^(\w+)$/; - my $link_field = $1; - my %search = ( $link_field => $cgi->param('link_value') ); - if ( $cgi->param('link_field2') =~ /^(\w+)$/ ) { - $search{$1} = $cgi->param('link_value2'); - } - - my @svc_x = ( sort { ($a->cust_svc->pkgnum > 0) <=> ($b->cust_svc->pkgnum > 0) - or ($b->cust_svc->svcpart == $svcpart) - <=> ($a->cust_svc->svcpart == $svcpart) - } - qsearch( $svcdb, \%search ) - ); - - if ( $DEBUG ) { - warn scalar(@svc_x). " candidate accounts found for linking ". - "(svcpart $svcpart):\n"; - foreach my $svc_x ( @svc_x ) { - warn " ". $svc_x->email. - " (svcnum ". $svc_x->svcnum. ",". - " pkgnum ". $svc_x->cust_svc->pkgnum. ",". - " svcpart ". $svc_x->cust_svc->svcpart. ")\n"; - } - } - - my $svc_x = $svc_x[0]; - - eidiot("$link_field not found!") unless $svc_x; - - $svcnum = $svc_x->svcnum; - -} - -my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -die "svcnum not found!" unless $old; -my $conf = new FS::Conf; -my($error, $new); -if ( $old->pkgnum && ! $conf->exists('legacy_link-steal') ) { - $error = "svcnum $svcnum already linked to package ". $old->pkgnum; -} else { - $new = new FS::cust_svc ({ - 'svcnum' => $svcnum, - 'pkgnum' => $pkgnum, - 'svcpart' => $svcpart, - }); - - $error = $new->replace($old); -} - -unless ($error) { - #no errors, so let's view this customer. - my $custnum = $new->cust_pkg->custnum; - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". - "#cust_pkg$pkgnum" ); -} else { -%> <!-- mason kludge --> -<% - idiot($error); -} +% +% idiot($error); +%} +% +% -%> diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi index 59d236f64..5a97d1160 100644 --- a/httemplate/misc/process/meta-import.cgi +++ b/httemplate/misc/process/meta-import.cgi @@ -1,5 +1,5 @@ <!-- mason kludge --> -<%= header('Map tables') %> +<% include("/elements/header.html",'Map tables') %> <SCRIPT> var gSafeOnload = new Array(); @@ -22,157 +22,164 @@ function SafeOnsubmit() { </SCRIPT> <FORM NAME="OneTrueForm" METHOD="POST" ACTION="meta-import.cgi"> +% +% #use DBIx::DBSchema; +% my $schema = new_native DBIx::DBSchema +% map { $cgi->param($_) } qw( data_source username password ); +% foreach my $field (qw( data_source username password )) { + + <INPUT TYPE="hidden" NAME=<% $field %> VALUE="<% $cgi->param($field) %>"> +% } +% +% my %schema; +% use Tie::DxHash; +% tie %schema, 'Tie::DxHash'; +% if ( $cgi->param('schema') ) { +% my $schema_string = $cgi->param('schema'); +% + <INPUT TYPE="hidden" NAME="schema" VALUE="<%$schema_string%>"> +% +% %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ +% or die "guru meditation #420: $_"; +% ( $1 => $2 ); +% } +% split( /\n/, $schema_string ); +% } +% +% #first page +% unless ( $cgi->param('magic') ) { -<% - #use DBIx::DBSchema; - my $schema = new_native DBIx::DBSchema - map { $cgi->param($_) } qw( data_source username password ); - foreach my $field (qw( data_source username password )) { %> - <INPUT TYPE="hidden" NAME=<%= $field %> VALUE="<%= $cgi->param($field) %>"> - <% } - - my %schema; - use Tie::DxHash; - tie %schema, 'Tie::DxHash'; - if ( $cgi->param('schema') ) { - my $schema_string = $cgi->param('schema'); - %> <INPUT TYPE="hidden" NAME="schema" VALUE="<%=$schema_string%>"> <% - %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ - or die "guru meditation #420: $_"; - ( $1 => $2 ); - } - split( /\n/, $schema_string ); - } - - #first page - unless ( $cgi->param('magic') ) { %> <INPUT TYPE="hidden" NAME="magic" VALUE="process"> - <%= hashmaker('schema', [ $schema->tables ], + <% hashmaker('schema', [ $schema->tables ], [ grep !/^h_/, dbdef->tables ], ) %> <br><INPUT TYPE="submit" VALUE="done"> - <% +% +% +% #second page +% } elsif ( $cgi->param('magic') eq 'process' ) { - #second page - } elsif ( $cgi->param('magic') eq 'process' ) { %> <INPUT TYPE="hidden" NAME="magic" VALUE="process2"> - <% - - my %unique; - foreach my $table ( keys %schema ) { - - my @from_columns = $schema->table($table)->columns; - my @fs_columns = dbdef->table($schema{$table})->columns; - - %> - <%= hashmaker( $table.'__'.$unique{$table}++, +% +% +% my %unique; +% foreach my $table ( keys %schema ) { +% +% my @from_columns = $schema->table($table)->columns; +% my @fs_columns = dbdef->table($schema{$table})->columns; +% +% + + <% hashmaker( $table.'__'.$unique{$table}++, \@from_columns => \@fs_columns, $table => $schema{$table}, ) %> <br><hr><br> - <% - - } +% +% +% } +% +% - %> <br><INPUT TYPE="submit" VALUE="done"> - <% +% +% +% #third (results) +% } elsif ( $cgi->param('magic') eq 'process2' ) { +% +% print "<pre>\n"; +% +% my %unique; +% foreach my $table ( keys %schema ) { +% ( my $spaces = $table ) =~ s/./ /g; +% print "'$table' => { 'table' => '$schema{$table}',\n". +% #(length($table) x ' '). " 'map' => {\n"; +% "$spaces 'map' => {\n"; +% my %map = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ +% or die "guru meditation #420: $_"; +% ( $1 => $2 ); +% } +% split( /\n/, $cgi->param($table.'__'.$unique{$table}++) ); +% foreach ( keys %map ) { +% print "$spaces '$_' => '$map{$_}',\n"; +% } +% print "$spaces },\n"; +% print "$spaces },\n"; +% +% } +% print "\n</pre>"; +% +% } else { +% warn "unrecognized magic: ". $cgi->param('magic'); +% } +% +% - #third (results) - } elsif ( $cgi->param('magic') eq 'process2' ) { - - print "<pre>\n"; - - my %unique; - foreach my $table ( keys %schema ) { - ( my $spaces = $table ) =~ s/./ /g; - print "'$table' => { 'table' => '$schema{$table}',\n". - #(length($table) x ' '). " 'map' => {\n"; - "$spaces 'map' => {\n"; - my %map = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ - or die "guru meditation #420: $_"; - ( $1 => $2 ); - } - split( /\n/, $cgi->param($table.'__'.$unique{$table}++) ); - foreach ( keys %map ) { - print "$spaces '$_' => '$map{$_}',\n"; - } - print "$spaces },\n"; - print "$spaces },\n"; - - } - print "\n</pre>"; - - } else { - warn "unrecognized magic: ". $cgi->param('magic'); - } - - %> </FORM> </BODY> </HTML> +% +% #hashmaker widget +% sub hashmaker { +% my($name, $from, $to, $labelfrom, $labelto) = @_; +% my $fromsize = scalar(@$from); +% my $tosize = scalar(@$to); +% "<TABLE><TR><TH>$labelfrom</TH><TH>$labelto</TH></TR><TR><TD>". +% qq!<SELECT NAME="${name}_from" SIZE=$fromsize>\n!. +% join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$from ). +% "</SELECT>\n<BR>". +% qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_from()">!. +% '</TD><TD>'. +% qq!<SELECT NAME="${name}_to" SIZE=$tosize>\n!. +% join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$to ). +% "</SELECT>\n<BR>". +% qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_to()">!. +% '</TD></TR>'. +% '<TR><TD COLSPAN=2>'. +% qq!<INPUT TYPE="button" VALUE="map" onClick="toke_$name(this.form)">!. +% '</TD></TR><TR><TD COLSPAN=2>'. +% qq!<TEXTAREA NAME="$name" COLS=80 ROWS=8></TEXTAREA>!. +% '</TD></TR></TABLE>'. +% "<script> +% function toke_$name() { +% fromObject = document.OneTrueForm.${name}_from; +% for (var i=fromObject.options.length-1;i>-1;i--) { +% if (fromObject.options[i].selected) +% fromname = deleteOption_$name(fromObject,i); +% } +% toObject = document.OneTrueForm.${name}_to; +% for (var i=toObject.options.length-1;i>-1;i--) { +% if (toObject.options[i].selected) +% toname = deleteOption_$name(toObject,i); +% } +% document.OneTrueForm.$name.value = document.OneTrueForm.$name.value + fromname + ' => ' + toname + '\\n'; +% } +% function deleteOption_$name(object,index) { +% value = object.options[index].value; +% object.options[index] = null; +% return value; +% } +% function repack_${name}_from() { +% var object = document.OneTrueForm.${name}_from; +% object.options.length = 0; +% ". join("\n", +% map { "addOption_$name(object, '$_');\n" } +% ( sort { $a cmp $b } @$from ) ). " +% } +% function repack_${name}_to() { +% var object = document.OneTrueForm.${name}_to; +% object.options.length = 0; +% ". join("\n", +% map { "addOption_$name(object, '$_');\n" } +% ( sort { $a cmp $b } @$to ) ). " +% } +% function addOption_$name(object,value) { +% var length = object.length; +% object.options[length] = new Option(value, value, false, false); +% } +% </script>". +% ''; +% } +% +% - <% - #hashmaker widget - sub hashmaker { - my($name, $from, $to, $labelfrom, $labelto) = @_; - my $fromsize = scalar(@$from); - my $tosize = scalar(@$to); - "<TABLE><TR><TH>$labelfrom</TH><TH>$labelto</TH></TR><TR><TD>". - qq!<SELECT NAME="${name}_from" SIZE=$fromsize>\n!. - join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$from ). - "</SELECT>\n<BR>". - qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_from()">!. - '</TD><TD>'. - qq!<SELECT NAME="${name}_to" SIZE=$tosize>\n!. - join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$to ). - "</SELECT>\n<BR>". - qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_to()">!. - '</TD></TR>'. - '<TR><TD COLSPAN=2>'. - qq!<INPUT TYPE="button" VALUE="map" onClick="toke_$name(this.form)">!. - '</TD></TR><TR><TD COLSPAN=2>'. - qq!<TEXTAREA NAME="$name" COLS=80 ROWS=8></TEXTAREA>!. - '</TD></TR></TABLE>'. - "<script> - function toke_$name() { - fromObject = document.OneTrueForm.${name}_from; - for (var i=fromObject.options.length-1;i>-1;i--) { - if (fromObject.options[i].selected) - fromname = deleteOption_$name(fromObject,i); - } - toObject = document.OneTrueForm.${name}_to; - for (var i=toObject.options.length-1;i>-1;i--) { - if (toObject.options[i].selected) - toname = deleteOption_$name(toObject,i); - } - document.OneTrueForm.$name.value = document.OneTrueForm.$name.value + fromname + ' => ' + toname + '\\n'; - } - function deleteOption_$name(object,index) { - value = object.options[index].value; - object.options[index] = null; - return value; - } - function repack_${name}_from() { - var object = document.OneTrueForm.${name}_from; - object.options.length = 0; - ". join("\n", - map { "addOption_$name(object, '$_');\n" } - ( sort { $a cmp $b } @$from ) ). " - } - function repack_${name}_to() { - var object = document.OneTrueForm.${name}_to; - object.options.length = 0; - ". join("\n", - map { "addOption_$name(object, '$_');\n" } - ( sort { $a cmp $b } @$to ) ). " - } - function addOption_$name(object,value) { - var length = object.length; - object.options[length] = new Option(value, value, false, false); - } - </script>". - ''; - } - -%> diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index fa0ede89c..a5f4d4208 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -1,148 +1,148 @@ -<% - -#some false laziness w/MyAccount::process_payment - -$cgi->param('custnum') =~ /^(\d+)$/ - or die "illegal custnum ". $cgi->param('custnum'); -my $custnum = $1; - -my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); -die "unknown custnum $custnum" unless $cust_main; - -$cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/ - or eidiot "illegal amount ". $cgi->param('amount'); -my $amount = $1; -eidiot "amount <= 0" unless $amount > 0; - -$cgi->param('year') =~ /^(\d+)$/ - or die "illegal year ". $cgi->param('year'); -my $year = $1; - -$cgi->param('month') =~ /^(\d+)$/ - or die "illegal month ". $cgi->param('month'); -my $month = $1; - -$cgi->param('payby') =~ /^(CARD|CHEK)$/ - or die "illegal payby ". $cgi->param('payby'); -my $payby = $1; -my %payby2bop = ( - 'CARD' => 'CC', - 'CHEK' => 'ECHECK', -); -my %payby2fields = ( - 'CARD' => [ qw( address1 address2 city state zip ) ], - 'CHEK' => [ qw( ss ) ], -); -my %type = ( 'CARD' => 'credit card', - 'CHEK' => 'electronic check (ACH)', - ); - -$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ - or eidiot gettext('illegal_name'). " payname: ". $cgi->param('payname'); -my $payname = $1; - -$cgi->param('paybatch') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ - or eidiot gettext('illegal_text'). " paybatch: ". $cgi->param('paybatch'); -my $paybatch = $1; - -my $payinfo; -my $paycvv = ''; -if ( $payby eq 'CHEK' ) { - - $cgi->param('payinfo1') =~ /^(\d+)$/ - or eidiot "illegal account number ". $cgi->param('payinfo1'); - my $payinfo1 = $1; - $cgi->param('payinfo2') =~ /^(\d+)$/ - or eidiot "illegal ABA/routing number ". $cgi->param('payinfo2'); - my $payinfo2 = $1; - $payinfo = $payinfo1. '@'. $payinfo2; - -} elsif ( $payby eq 'CARD' ) { - - $payinfo = $cgi->param('payinfo'); - $payinfo =~ s/\D//g; - $payinfo =~ /^(\d{13,16})$/ - or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; - $payinfo = $1; - validate($payinfo) - or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; - eidiot gettext('unknown_card_type') - if cardtype($payinfo) eq "Unknown"; - - if ( defined $cust_main->dbdef_table->column('paycvv') ) { - if ( length($cgi->param('paycvv') ) ) { - if ( cardtype($payinfo) eq 'American Express card' ) { - $cgi->param('paycvv') =~ /^(\d{4})$/ - or eidiot "CVV2 (CID) for American Express cards is four digits."; - $paycvv = $1; - } else { - $cgi->param('paycvv') =~ /^(\d{3})$/ - or eidiot "CVV2 (CVC2/CID) is three digits."; - $paycvv = $1; - } - } - } - -} else { - die "unknown payby $payby"; -} - -my $error = $cust_main->realtime_bop( $payby2bop{$payby}, $amount, - 'quiet' => 1, - 'payinfo' => $payinfo, - 'paydate' => "$year-$month-01", - 'payname' => $payname, - 'paybatch' => $paybatch, - 'paycvv' => $paycvv, - map { $_ => $cgi->param($_) } @{$payby2fields{$payby}} -); -eidiot($error) if $error; - -$cust_main->apply_payments; - -if ( $cgi->param('save') ) { - my $new = new FS::cust_main { $cust_main->hash }; - if ( $payby eq 'CARD' ) { - $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) ); - } elsif ( $payby eq 'CHEK' ) { - $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) ); - } else { - die "unknown payby $payby"; - } - $new->set( 'payinfo' => $payinfo ); - $new->set( 'paydate' => "$year-$month-01" ); - $new->set( 'payname' => $payname ); - - #false laziness w/FS:;cust_main::realtime_bop - check both to make sure - # working correctly - my $conf = new FS::Conf; - if ( $payby eq 'CARD' && - grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) { - $new->set( 'paycvv' => $paycvv ); - } else { - $new->set( 'paycvv' => ''); - } - - $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; - - my $error = $new->replace($cust_main); - eidiot "payment processed sucessfully, but error saving info: $error" - if $error; - $cust_main = $new; -} - -#success! - -%> -<%= include( '/elements/header.html', ucfirst($type{$payby}). ' processing sucessful', - include('/elements/menubar.html', - 'Main menu' => popurl(3), - "View this customer (#$custnum)" => - popurl(3). "view/cust_main.cgi?$custnum", - ), +% +%#some false laziness w/MyAccount::process_payment +% +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die "illegal custnum ". $cgi->param('custnum'); +%my $custnum = $1; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +%die "unknown custnum $custnum" unless $cust_main; +% +%$cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/ +% or eidiot "illegal amount ". $cgi->param('amount'); +%my $amount = $1; +%eidiot "amount <= 0" unless $amount > 0; +% +%$cgi->param('year') =~ /^(\d+)$/ +% or die "illegal year ". $cgi->param('year'); +%my $year = $1; +% +%$cgi->param('month') =~ /^(\d+)$/ +% or die "illegal month ". $cgi->param('month'); +%my $month = $1; +% +%$cgi->param('payby') =~ /^(CARD|CHEK)$/ +% or die "illegal payby ". $cgi->param('payby'); +%my $payby = $1; +%my %payby2fields = ( +% 'CARD' => [ qw( address1 address2 city state zip ) ], +% 'CHEK' => [ qw( ss ) ], +%); +%my %type = ( 'CARD' => 'credit card', +% 'CHEK' => 'electronic check (ACH)', +% ); +% +%$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ +% or eidiot gettext('illegal_name'). " payname: ". $cgi->param('payname'); +%my $payname = $1; +% +%$cgi->param('paybatch') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot gettext('illegal_text'). " paybatch: ". $cgi->param('paybatch'); +%my $paybatch = $1; +% +%my $payinfo; +%my $paycvv = ''; +%if ( $payby eq 'CHEK' ) { +% +% if ($cgi->param('payinfo1') =~ /xx/i || $cgi->param('payinfo2') =~ /xx/i ) { +% $payinfo = $cust_main->payinfo; +% } else { +% $cgi->param('payinfo1') =~ /^(\d+)$/ +% or eidiot "illegal account number ". $cgi->param('payinfo1'); +% my $payinfo1 = $1; +% $cgi->param('payinfo2') =~ /^(\d+)$/ +% or eidiot "illegal ABA/routing number ". $cgi->param('payinfo2'); +% my $payinfo2 = $1; +% $payinfo = $payinfo1. '@'. $payinfo2; +% } +% +%} elsif ( $payby eq 'CARD' ) { +% +% $payinfo = $cgi->param('payinfo'); +% if ($payinfo eq $cust_main->paymask) { +% $payinfo = $cust_main->payinfo; +% } +% $payinfo =~ s/\D//g; +% $payinfo =~ /^(\d{13,16})$/ +% or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; +% $payinfo = $1; +% validate($payinfo) +% or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; +% eidiot gettext('unknown_card_type') +% if cardtype($payinfo) eq "Unknown"; +% +% if ( defined $cust_main->dbdef_table->column('paycvv') ) { +% if ( length($cgi->param('paycvv') ) ) { +% if ( cardtype($payinfo) eq 'American Express card' ) { +% $cgi->param('paycvv') =~ /^(\d{4})$/ +% or eidiot "CVV2 (CID) for American Express cards is four digits."; +% $paycvv = $1; +% } else { +% $cgi->param('paycvv') =~ /^(\d{3})$/ +% or eidiot "CVV2 (CVC2/CID) is three digits."; +% $paycvv = $1; +% } +% } +% } +% +%} else { +% die "unknown payby $payby"; +%} +% +%my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount, +% 'quiet' => 1, +% 'manual' => 1, +% 'payinfo' => $payinfo, +% 'paydate' => "$year-$month-01", +% 'payname' => $payname, +% 'paybatch' => $paybatch, +% 'paycvv' => $paycvv, +% map { $_ => $cgi->param($_) } @{$payby2fields{$payby}} +%); +%eidiot($error) if $error; +% +%$cust_main->apply_payments; +% +%if ( $cgi->param('save') ) { +% my $new = new FS::cust_main { $cust_main->hash }; +% if ( $payby eq 'CARD' ) { +% $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) ); +% } elsif ( $payby eq 'CHEK' ) { +% $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) ); +% } else { +% die "unknown payby $payby"; +% } +% $new->set( 'payinfo' => $payinfo ); +% $new->set( 'paydate' => "$year-$month-01" ); +% $new->set( 'payname' => $payname ); +% +% #false laziness w/FS:;cust_main::realtime_bop - check both to make sure +% # working correctly +% my $conf = new FS::Conf; +% if ( $payby eq 'CARD' && +% grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) { +% $new->set( 'paycvv' => $paycvv ); +% } else { +% $new->set( 'paycvv' => ''); +% } +% +% $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; +% +% my $error = $new->replace($cust_main); +% eidiot "payment processed successfully, but error saving info: $error" +% if $error; +% $cust_main = $new; +%} +% +%#success! +% +% + +<% include( '/elements/header.html', ucfirst($type{$payby}). ' processing successful', + include('/elements/menubar.html'), ) %> -<%= include( '/elements/small_custview.html', $cust_main ) %> +<% include( '/elements/small_custview.html', $cust_main, '', '', popurl(3). "view/cust_main.cgi" ) %> </BODY> </HTML> diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html new file mode 100755 index 000000000..d9fa2070e --- /dev/null +++ b/httemplate/misc/process/recharge_svc.html @@ -0,0 +1,46 @@ +% +% +%#untaint svcnum +%my $svcnum = $cgi->param('svcnum'); +%$svcnum =~ /^(\d+)$/ || die "Illegal svcnum"; +%$svcnum = $1; +% +%#untaint prepaid +%my $prepaid = $cgi->param('prepaid'); +%$prepaid =~ /^(\w*)$/; +%$prepaid = $1; +% +%my $error = ''; +%my $svc_acct = qsearchs( 'svc_acct', {'svcnum'=>$svcnum} ); +%$error = "Can't recharge service $svcnum. " unless $svc_acct; +% +%my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main; +% +%my $oldAutoCommit = $FS::UID::AutoCommit; +%local $FS::UID::AutoCommit = 0; +%my $dbh = dbh; +% +% +%unless ($error) { +% +%my ($amount, $seconds, $up, $down, $total) = (0, 0, 0, 0, 0); +%$error = $cust_main->get_prepay($prepaid, \$amount, \$seconds, \$up, \$down, \$total) +% || $svc_acct->increment_seconds($seconds) +% || $svc_acct->increment_upbytes($up) +% || $svc_acct->increment_downbytes($down) +% || $svc_acct->increment_totalbytes($total) +% || $cust_main->insert_cust_pay_prepay( $amount, $prepaid ); +%} +% +%if ($error) { +% $cgi->param('error', $error); +% $dbh->rollback if $oldAutoCommit; +% print $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string ); +%} +% +<% header("Package recharged") %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY></HTML> + diff --git a/httemplate/misc/queue.cgi b/httemplate/misc/queue.cgi index ce9c8fbd3..7370aabe1 100644 --- a/httemplate/misc/queue.cgi +++ b/httemplate/misc/queue.cgi @@ -1,47 +1,48 @@ -<% +% +% +%$cgi->param('action') =~ /^(new|del|(retry|remove) selected)$/ +% or die "Illegal action"; +%my $action = $1; +% +%my $job; +%if ( $action eq 'new' || $action eq 'del' ) { +% $cgi->param('jobnum') =~ /^(\d+)$/ or die "Illegal jobnum"; +% my $jobnum = $1; +% $job = qsearchs('queue', { 'jobnum' => $1 }) +% or die "unknown jobnum $jobnum - ". +% "it probably completed normally or was removed by another user"; +%} +% +%if ( $action eq 'new' ) { +% my %hash = $job->hash; +% $hash{'status'} = 'new'; +% $hash{'statustext'} = ''; +% my $new = new FS::queue \%hash; +% my $error = $new->replace($job); +% die $error if $error; +%} elsif ( $action eq 'del' ) { +% my $error = $job->delete; +% die $error if $error; +%} elsif ( $action =~ /^(retry|remove) selected$/ ) { +% foreach my $jobnum ( +% map { /^jobnum(\d+)$/; $1; } grep /^jobnum\d+$/, $cgi->param +% ) { +% my $job = qsearchs('queue', { 'jobnum' => $jobnum }); +% if ( $action eq 'retry selected' && $job ) { #new +% my %hash = $job->hash; +% $hash{'status'} = 'new'; +% $hash{'statustext'} = ''; +% my $new = new FS::queue \%hash; +% my $error = $new->replace($job); +% die $error if $error; +% } elsif ( $action eq 'remove selected' && $job ) { #del +% my $error = $job->delete; +% die $error if $error; +% } +% } +%} +% +%print $cgi->redirect(popurl(2). "search/queue.html"); +% +% -$cgi->param('action') =~ /^(new|del|(retry|remove) selected)$/ - or die "Illegal action"; -my $action = $1; - -my $job; -if ( $action eq 'new' || $action eq 'del' ) { - $cgi->param('jobnum') =~ /^(\d+)$/ or die "Illegal jobnum"; - my $jobnum = $1; - $job = qsearchs('queue', { 'jobnum' => $1 }) - or die "unknown jobnum $jobnum - ". - "it probably completed normally or was removed by another user"; -} - -if ( $action eq 'new' ) { - my %hash = $job->hash; - $hash{'status'} = 'new'; - $hash{'statustext'} = ''; - my $new = new FS::queue \%hash; - my $error = $new->replace($job); - die $error if $error; -} elsif ( $action eq 'del' ) { - my $error = $job->delete; - die $error if $error; -} elsif ( $action =~ /^(retry|remove) selected$/ ) { - foreach my $jobnum ( - map { /^jobnum(\d+)$/; $1; } grep /^jobnum\d+$/, $cgi->param - ) { - my $job = qsearchs('queue', { 'jobnum' => $jobnum }); - if ( $action eq 'retry selected' && $job ) { #new - my %hash = $job->hash; - $hash{'status'} = 'new'; - $hash{'statustext'} = ''; - my $new = new FS::queue \%hash; - my $error = $new->replace($job); - die $error if $error; - } elsif ( $action eq 'remove selected' && $job ) { #del - my $error = $job->delete; - die $error if $error; - } - } -} - -print $cgi->redirect(popurl(2). "browse/queue.cgi"); - -%> diff --git a/httemplate/misc/recharge_svc.html b/httemplate/misc/recharge_svc.html new file mode 100755 index 000000000..61f738455 --- /dev/null +++ b/httemplate/misc/recharge_svc.html @@ -0,0 +1,48 @@ +<% include('/elements/header-popup.html', 'Recharge Service' ) %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM NAME="recharge_popup" ACTION="<% popurl(1) %>process/recharge_svc.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> + +<BR><BR> +<% "Recharge $svcnum: $label - $value" %> +<% ntable("#cccccc", 2) %> + +<TR> + <TD>Enter prepaid card: </TD> + <TD><INPUT TYPE="text" NAME="prepaid" VALUE="<% $prepaid %>"></TD> +</TR> + +</TABLE> + +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="Recharge"> + +</FORM> +</BODY> +</HTML> + +<%init> +my($svcnum, $cust_svc, $label, $value, $prepaid); +if ( $cgi->param('error') ) { + $svcnum = $cgi->param('svcnum'); + $prepaid = $cgi->param('prepaid'); +} elsif ( $cgi->param('svcnum') =~ /^(\d+)$/ ) { + $svcnum = $1; +} else { + die "illegal query ". $cgi->keywords; +} + +my $title = 'Recharge Service'; + +$cust_svc = qsearchs('cust_svc', {'svcnum' => $svcnum}); +die "No such service: $svcnum" unless $cust_svc; + +($label, $value) = $cust_svc->label; + +</%init> + diff --git a/httemplate/misc/states.cgi b/httemplate/misc/states.cgi index cff2c9774..cf2b46ee2 100644 --- a/httemplate/misc/states.cgi +++ b/httemplate/misc/states.cgi @@ -1,16 +1,7 @@ -<% - - my $country = $cgi->param('arg'); - - my @states = - sort - map { s/[\n\r]//g; $_; } - map { $_->state; } - qsearch( 'cust_main_county', - { 'country' => $country }, - 'DISTINCT ON ( state ) *', - ) - ; - - -%>[ <%= join(', ', map { qq("$_") } @states) %> ] +% +% +% my $country = $cgi->param('arg'); +% my @output = states_hash($country); +% +% +[ <% join(', ', map { qq("$_") } @output) %> ] diff --git a/httemplate/misc/susp_pkg.cgi b/httemplate/misc/susp_pkg.cgi deleted file mode 100755 index 4a19fa830..000000000 --- a/httemplate/misc/susp_pkg.cgi +++ /dev/null @@ -1,15 +0,0 @@ -<% - -#untaint pkgnum -my ($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal pkgnum"; -my $pkgnum = $1; - -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - -my $error = $cust_pkg->suspend; -&eidiot($error) if $error; - -print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); - -%> diff --git a/httemplate/misc/svc_acct-domains.cgi b/httemplate/misc/svc_acct-domains.cgi new file mode 100644 index 000000000..3f9599e30 --- /dev/null +++ b/httemplate/misc/svc_acct-domains.cgi @@ -0,0 +1,31 @@ +% +% +% my $pkgpart_svcpart = $cgi->param('arg'); +% $pkgpart_svcpart =~ /^\d+_(\d+)$/; +% my $part_svc = qsearchs('part_svc', { 'svcpart' => $1 }) if $1; +% my $part_svc_column = $part_svc->part_svc_column('domsvc'); +% +% my @output = split /,/, $part_svc_column->columnvalue; +% my @svc_domain = (); +% my %seen = (); +% +% foreach (@output) { +% my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $_ }) +% or warn "unknown svc_domain.svcnum $_ for part_svc_column domsvc; ". +% "svcpart = " . $part_svc->svcpart; +% push @svc_domain, [ $_ => $svc_domain->domain ]; +% $seen{$_}++; +% } +% if ($conf->exists('svc_acct-alldomains') +% && ( $part_svc_column->columnflag eq 'D' +% || $part_svc_column->columnflag eq '' ) +% ) { +% foreach (grep { $_->svcnum ne $output[0] } qsearch('svc_domain', {}) ){ +% push @svc_domain, [ $_->svcnum => $_->domain ]; +% } +% } +% +[ <% join(', ', map { qq("$_->[0]", "$_->[1]") } @svc_domain) %> ] +<%init> +my $conf = new FS::Conf; +</%init> diff --git a/httemplate/misc/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi index c658d2acc..56a3ff854 100755 --- a/httemplate/misc/unapply-cust_credit.cgi +++ b/httemplate/misc/unapply-cust_credit.cgi @@ -1,18 +1,19 @@ -<% +% +% +%#untaint crednum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal crednum"; +%my $crednum = $1; +% +%my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); +%my $custnum = $cust_credit->custnum; +% +%foreach my $cust_credit_bill ( $cust_credit->cust_credit_bill ) { +% my $error = $cust_credit_bill->delete; +% eidiot($error) if $error; +%} +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint crednum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal crednum"; -my $crednum = $1; - -my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); -my $custnum = $cust_credit->custnum; - -foreach my $cust_credit_bill ( $cust_credit->cust_credit_bill ) { - my $error = $cust_credit_bill->delete; - eidiot($error) if $error; -} - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi index 28643ef6e..b28f61b0f 100755 --- a/httemplate/misc/unapply-cust_pay.cgi +++ b/httemplate/misc/unapply-cust_pay.cgi @@ -1,18 +1,19 @@ -<% +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); +%my $custnum = $cust_pay->custnum; +% +%foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) { +% my $error = $cust_bill_pay->delete; +% eidiot($error) if $error; +%} +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint paynum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal paynum"; -my $paynum = $1; - -my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); -my $custnum = $cust_pay->custnum; - -foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) { - my $error = $cust_bill_pay->delete; - eidiot($error) if $error; -} - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi index 3c92a4e2e..e42feda8a 100755 --- a/httemplate/misc/unprovision.cgi +++ b/httemplate/misc/unprovision.cgi @@ -1,29 +1,31 @@ -<% +% +% +%my $dbh = dbh; +% +%#untaint svcnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +% +%#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +%#die "Unknown svcnum!" unless $svc_acct; +% +%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "Unknown svcnum!" unless $cust_svc; +% +%my $custnum = $cust_svc->cust_pkg->custnum; +% +%my $error = $cust_svc->cancel; +% +%if ( $error ) { +% -my $dbh = dbh; - -#untaint svcnum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; - -#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); -#die "Unknown svcnum!" unless $svc_acct; - -my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -die "Unknown svcnum!" unless $cust_svc; - -my $custnum = $cust_svc->cust_pkg->custnum; - -my $error = $cust_svc->cancel; - -if ( $error ) { - %> <!-- mason kludge --> -<% - &eidiot($error); -} else { - print $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum"); -} +% +% &eidiot($error); +%} else { +% print $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum"); +%} +% +% -%> diff --git a/httemplate/misc/unsusp_pkg.cgi b/httemplate/misc/unsusp_pkg.cgi index 500872983..79c07a72a 100755 --- a/httemplate/misc/unsusp_pkg.cgi +++ b/httemplate/misc/unsusp_pkg.cgi @@ -1,15 +1,16 @@ -<% +% +% +%#untaint pkgnum +%my ($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +%my $pkgnum = $1; +% +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% +%my $error = $cust_pkg->unsuspend; +%&eidiot($error) if $error; +% +%print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); +% +% -#untaint pkgnum -my ($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal pkgnum"; -my $pkgnum = $1; - -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - -my $error = $cust_pkg->unsuspend; -&eidiot($error) if $error; - -print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); - -%> diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi index 539cd4a23..75c3edc06 100755 --- a/httemplate/misc/unvoid-cust_pay_void.cgi +++ b/httemplate/misc/unvoid-cust_pay_void.cgi @@ -1,16 +1,17 @@ -<% +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay_void = qsearchs('cust_pay_void', { 'paynum' => $paynum } ); +%my $custnum = $cust_pay_void->custnum; +% +%my $error = $cust_pay_void->unvoid; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint paynum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal paynum"; -my $paynum = $1; - -my $cust_pay_void = qsearchs('cust_pay_void', { 'paynum' => $paynum } ); -my $custnum = $cust_pay_void->custnum; - -my $error = $cust_pay_void->unvoid; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi index 5d0150177..69d3ca6b1 100644 --- a/httemplate/misc/upload-batch.cgi +++ b/httemplate/misc/upload-batch.cgi @@ -1,30 +1,39 @@ -<% - - my $fh = $cgi->upload('batch_results'); - my $filename = $cgi->param('batch_results'); - $filename =~ /^(.*[\/\\])?([^\/\\]+)$/ - or die "unparsable filename: $filename\n"; - my $paybatch = $2; - - my $error = defined($fh) - ? FS::cust_pay_batch::import_results( { - 'filehandle' => $fh, - 'format' => $cgi->param('format'), - 'paybatch' => $paybatch, - } ) - : 'No file'; - - if ( $error ) { - %> +% if ( $error ) { + <!-- mason kludge --> - <% - eidiot($error); -# $cgi->param('error', $error); -# print $cgi->redirect( "${p}cust_main-import.cgi + +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { + + <% include("/elements/header.html",'Batch results upload successful') %> + +% } +<%init> + +my $error; + +my $fh = $cgi->upload('batch_results'); +$error = 'No file uploaded' unless defined($fh); + +unless ( $error ) { + + $cgi->param('batchnum') =~ /^(\d+)$/; + my $batchnum = $1; + + my $pay_batch = qsearchs( 'pay_batch', { 'batchnum' => $batchnum } ); + if ( ! $pay_batch ) { + $error = "batchnum $batchnum not found"; + } elsif ( $pay_batch->status ne 'I' ) { + $error = "batch $batchnum is not in transit"; } else { - %> - <!-- mason kludge --> - <%= header('Batch results upload sucessful') %> <% + $error = $pay_batch->import_results( + 'filehandle' => $fh, + 'format' => $cgi->param('format'), + ); } -%> +} + +</%init> diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi index 4eec60892..b55d22c41 100755 --- a/httemplate/misc/void-cust_pay.cgi +++ b/httemplate/misc/void-cust_pay.cgi @@ -1,16 +1,17 @@ -<% +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); +%my $custnum = $cust_pay->custnum; +% +%my $error = $cust_pay->void; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% -#untaint paynum -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/ || die "Illegal paynum"; -my $paynum = $1; - -my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); -my $custnum = $cust_pay->custnum; - -my $error = $cust_pay->void; -eidiot($error) if $error; - -print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); - -%> diff --git a/httemplate/misc/whois.cgi b/httemplate/misc/whois.cgi index dd7851dc2..d3d9649fd 100644 --- a/httemplate/misc/whois.cgi +++ b/httemplate/misc/whois.cgi @@ -1,10 +1,11 @@ -<% - my $svcnum = $cgi->param('svcnum'); - my $custnum = $cgi->param('custnum'); - my $domain = $cgi->param('domain'); +% +% my $svcnum = $cgi->param('svcnum'); +% my $custnum = $cgi->param('custnum'); +% my $domain = $cgi->param('domain'); +% +% -%> -<%= header("Whois $domain", menubar( +<% include("/elements/header.html","Whois $domain", menubar( ( $custnum ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", ) @@ -13,13 +14,14 @@ "View this domain (#$svcnum)" => "${p}view/svc_domain.cgi?$svcnum", "Main menu" => $p, )) %> -<% my $whois = eval { whois($domain) }; - if ( $@ ) { - ( $whois = $@ ) =~ s/ at \/.*Net\/Whois\/Raw\.pm line \d+.*$//s; - } else { - $whois =~ s/^\n+//; - } -%> -<PRE><%= $whois %></PRE> +% my $whois = eval { whois($domain) }; +% if ( $@ ) { +% ( $whois = $@ ) =~ s/ at \/.*Net\/Whois\/Raw\.pm line \d+.*$//s; +% } else { +% $whois =~ s/^\n+//; +% } +% + +<PRE><% $whois %></PRE> </BODY> </HTML> diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi index 8dbd5a4f2..67512fad9 100644 --- a/httemplate/misc/xmlhttp-cust_main-search.cgi +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -1,21 +1,22 @@ -<% - my $sub = $cgi->param('sub'); - - if ( $sub eq 'custnum_search' ) { - - my $custnum = $cgi->param('arg'); - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% +% my $sub = $cgi->param('sub'); +% +% if ( $sub eq 'custnum_search' ) { +% +% my $custnum = $cgi->param('arg'); +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% +% +"<% $cust_main ? $cust_main->name : '' %>" +% } elsif ( $sub eq 'smart_search' ) { +% +% my $string = $cgi->param('arg'); +% my @cust_main = smart_search( 'search' => $string ); +% my $return = [ map [ $_->custnum, $_->name ], @cust_main ]; +% +% +<% objToJson($return) %> +% } - %>"<%= $cust_main ? $cust_main->name : '' %>" - -<% } elsif ( $sub eq 'smart_search' ) { - - my $string = $cgi->param('arg'); - my @cust_main = smart_search( 'search' => $string ); - my $return = [ map [ $_->custnum, $_->name ], @cust_main ]; - - %><%= objToJson($return) %> - -<% } %> diff --git a/httemplate/misc/xmlrpc.cgi b/httemplate/misc/xmlrpc.cgi index 53ef8fb80..1d0383f2a 100644 --- a/httemplate/misc/xmlrpc.cgi +++ b/httemplate/misc/xmlrpc.cgi @@ -1,17 +1,18 @@ -<% +% +% +% my $request_xml = $cgi->param('POSTDATA'); +% +% #$r->log_error($request_xml); +% +% my $fsxmlrpc = new FS::XMLRPC; +% my ($error, $response_xml) = $fsxmlrpc->serve($request_xml); +% +% #$r->log_error($error) if $error; +% +% http_header('Content-Type' => 'text/xml', +% 'Content-Length' => length($response_xml)); +% +% print $response_xml; +% +% - my $request_xml = $cgi->param('POSTDATA'); - - #$r->log_error($request_xml); - - my $fsxmlrpc = new FS::XMLRPC; - my ($error, $response_xml) = $fsxmlrpc->serve($request_xml); - - #$r->log_error($error) if $error; - - http_header('Content-Type' => 'text/xml', - 'Content-Length' => length($response_xml)); - - print $response_xml; - -%> diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html new file mode 100644 index 000000000..ed3350971 --- /dev/null +++ b/httemplate/pref/pref-process.html @@ -0,0 +1,44 @@ +% my $error = ''; +% +% my $access_user; +% if ( grep { $cgi->param($_) !~ /^\s*$/ } +% qw(_password new_password new_password2) +% ) { +% +% $access_user = qsearchs( 'access_user', { +% 'username' => getotaker, +% '_password' => $cgi->param('_password'), +% } ); +% +% $error = 'Current password incorrect; password not changed' +% unless $access_user; +% +% $error ||= "New passwords don't match" +% unless $cgi->param('new_password') eq $cgi->param('new_password2'); +% +% $error ||= "No new password entered" +% unless length($cgi->param('new_password')); +% +% $access_user->_password($cgi->param('new_password')) unless $error; +% +% } else { +% +% $access_user = $FS::CurrentUser::CurrentUser; +% +% } +% +% $error ||= $access_user->replace( { +% map { $_ => scalar($cgi->param($_)) } +% #XXX autogen +% qw( menu_position +% height width availHeight availWidth colorDepth +% ) +% } ); +% +% if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string ); +% } else { +<% include('/elements/header.html', 'Preferences updated') %> +<% include('/elements/footer.html') %> +% } diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html new file mode 100644 index 000000000..507a897d7 --- /dev/null +++ b/httemplate/pref/pref.html @@ -0,0 +1,61 @@ +<% include('/elements/header.html', 'Preferences for '. getotaker ) %> + +<FORM METHOD="POST" NAME="pref_form" ACTION="pref-process.html"> + +<% include('/elements/error.html') %> + + +Change password (leave blank for no change) +<% ntable("#cccccc",2) %> + +<TR> + <TD ALIGN="right">Current password: </TD> + <TD><INPUT TYPE="password" NAME="_password"></TD> +</TR> + +<TR> + <TD ALIGN="right">New password: </TD> + <TD><INPUT TYPE="password" NAME="new_password"></TD> +</TR> + +<TR> + <TD ALIGN="right">Re-enter new password: </TD> + <TD><INPUT TYPE="password" NAME="new_password2"></TD> +</TR> + +</TABLE> +<BR> + +Interface +<% ntable("#cccccc",2) %> + +<TR> + <TD>Menu location: </TD> + <TD> + <INPUT TYPE="radio" NAME="menu_position" VALUE="left" onClick="document.images['menu_example'].src='../images/menu-left-example.png';" <% $menu_position eq 'left' ? ' CHECKED' : ''%>> Left<BR> + <INPUT TYPE="radio" NAME="menu_position" VALUE="top"onClick="document.images['menu_example'].src='../images/menu-top-example.png';" <% $menu_position eq 'top' ? ' CHECKED' : ''%>> Top <BR> + </TD> + <TD><IMG NAME="menu_example" SRC="../images/menu-<% $menu_position %>-example.png"></TD> +</TR> + +</TABLE> +<BR> + +% foreach my $prop (qw( height width availHeight availWidth colorDepth )) { + <INPUT TYPE="hidden" NAME="<% $prop %>" VALUE=""> + <SCRIPT TYPE="text/javascript"> + document.pref_form.<% $prop %>.value = screen.<% $prop %>; + </script> +% } + +<INPUT TYPE="submit" VALUE="Update preferences"> + +<% include('/elements/footer.html') %> +<%init> + +# XSS via your own preferences? seems unlikely, but nice try anyway... +( $FS::CurrentUser::CurrentUser->option('menu_position') || 'left' ) + =~ /^(\w+)$/ or die "illegal menu_position"; +my $menu_position = $1; + +</%init> diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html new file mode 100644 index 000000000..54c804c1a --- /dev/null +++ b/httemplate/search/cdr.html @@ -0,0 +1,41 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'call detail records', + 'query' => { 'table' => 'cdr', + 'hashref' => $hashref + }, + 'count_query' => $count_query, + 'header' => [ fields('cdr') ], #XXX fill in some nice names + 'fields' => [ fields('cdr') ], #XXX fill in some pretty-print + # processing, etc. + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +my $title = 'Call Detail Records'; +my $hashref = {}; +my $count_query = 'SELECT COUNT(*) FROM cdr'; + +#process params for CDR search, populate $hashref... +# and fixup $count_query + +if ( $cgi->param('freesidestatus') eq 'NULL' ) { + + my $title = "Unprocessed $title"; + $hashref->{'freesidestatus'} = ''; # Record.pm will take care of it + #$count_query .= " AND ( freesidestatus IS NULL OR freesidestatus = '' )"; + $count_query .= " WHERE ( freesidestatus IS NULL OR freesidestatus = '' )"; + +} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) { + + my $title = "Processed $title"; + $hashref->{'freesidestatus'} = $1; + #$count_query .= " AND freesidestatus = '$1'"; + $count_query .= " WHERE freesidestatus = '$1'"; + +} + +</%init> 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 ) { -%> -<!-- mason kludge --> -<% - eidiot("Invoice not found."); -} else { -%> -<!-- mason kludge --> -<% - - #begin pager - my $pager = ''; - if ( $total != scalar(@cust_bill) && $maxrecords ) { - unless ( $offset == 0 ) { - $cgi->param('offset', $offset - $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Previous</FONT></B></A> '; - } - my $poff; - my $page; - for ( $poff = 0; $poff < $total; $poff += $maxrecords ) { - $page++; - if ( $offset == $poff ) { - $pager .= qq!<FONT SIZE="+2">$page</FONT> !; - } else { - $cgi->param('offset', $poff); - $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !; - } - } - unless ( $offset + $maxrecords > $total ) { - $cgi->param('offset', $offset + $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Next</FONT></B></A> '; - } - } - #end pager - - print header("Invoice Search Results", menubar( - 'Main Menu', popurl(2) - )). - "$total matching invoices found<BR>". - "\$$tot_balance total balance<BR>". - "\$$tot_amount total amount<BR>". - "<BR>$pager". table(). <<END; - <TR> - <TH></TH> - <TH>Balance</TH> - <TH>Amount</TH> - <TH>Date</TH> - <TH>Contact name</TH> - <TH>Company</TH> - </TR> -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; - <TR> - <TD ROWSPAN=$rowspan><A HREF="$view">$invnum</A></TD> - <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view">\$$owed</A></TD> - <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view">\$$charged</A></TD> - <TD ROWSPAN=$rowspan><A HREF="$view">$pdate</A></TD> -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 <<END; - <TD ROWSPAN=$rowspan><A HREF="$cview">$name</A></TD> - <TD ROWSPAN=$rowspan><A HREF="$cview">$company</A></TD> -END - } else { - print <<END - <TD ROWSPAN=$rowspan COLSPAN=2>WARNING: couldn't find cust_main.custnum $custnum (cust_bill.invnum $invnum)</TD> -END - } - - print "</TR>"; - } - $tot_balance = sprintf("%.2f", $tot_balance); - $tot_amount = sprintf("%.2f", $tot_amount); - print "</TABLE>$pager<BR>". table(). <<END; - <TR><TD> </TD><TH>Total<BR>Balance</TH><TH>Total<BR>Amount</TH></TR> - <TR><TD></TD><TD ALIGN="right">\$$tot_balance</TD><TD ALIGN="right">\$$tot_amount</TD></TD></TR> - </TABLE> - </BODY> -</HTML> -END - -} - -%> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 2108653a8..b65608eab 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -1,150 +1,4 @@ -<% - my( $count_query, $sql_query ); - my( $count_addl ) = ( '' ); - my( $distinct ) = ( '' ); - my($begin, $end) = ( '', '' ); - my($agentnum) = ( '' ); - my($open, $days) = ( '', '' ); - if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { - $count_query = "SELECT COUNT(*) FROM cust_bill WHERE invnum = $2"; - $sql_query = { - 'table' => 'cust_bill', - 'hashref' => { 'invnum' => $2 }, - #'select' => '*', - }; - } else { - #if ( $cgi->param('begin') || $cgi->param('end') - # || $cgi->param('beginning') || $cgi->param('ending') - # || $cgi->keywords - # ) - #{ - - #some false laziness w/cust_bill::re_X - my @where; - my $orderby = 'ORDER BY cust_bill._date'; - - if ( $cgi->param('beginning') - && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $begin = str2time($1); - push @where, "cust_bill._date >= $begin"; - } - if ( $cgi->param('ending') - && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $end = str2time($1) + 86399; - push @where, "cust_bill._date < $end"; - } - - if ( $cgi->param('begin') =~ /^(\d+)$/ ) { - $begin = $1; - push @where, "cust_bill._date >= $begin"; - } - if ( $cgi->param('end') =~ /^(\d+)$/ ) { - $end = $1; - push @where, "cust_bill._date < $end"; - } - - if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - $agentnum = $1; - push @where, "cust_main.agentnum = $agentnum"; - } - - 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 )"; - - if ( $cgi->param('open') ) { - push @where, "0 != $owed"; - $open = 1; - } - - my($query) = $cgi->keywords; - if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { - ($open, $days, my $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; - } - - my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; - - my $addl_from = 'left join cust_main using ( custnum )'; - - if ( $cgi->param('newest_percust') ) { - $distinct = 'DISTINCT ON ( cust_bill.custnum )'; - $orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC'; - #$count_query = "SELECT 'N/A', 'N/A', 'N/A'"; #XXXXXXX fix - $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; - } - - unless ( $count_query ) { - $count_query = "SELECT COUNT(*), sum(charged), sum($owed)"; - $count_addl = [ '$%.2f total invoiced', - '$%.2f total outstanding balance', - ]; - } - $count_query .= " FROM cust_bill $addl_from $extra_sql"; - - $sql_query = { - 'table' => 'cust_bill', - 'addl_from' => $addl_from, - 'hashref' => {}, - 'select' => "$distinct ". join(', ', - 'cust_bill.*', - #( map "cust_main.$_", qw(custnum last first company) ), - 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - "$owed as owed", - ), - 'extra_sql' => "$extra_sql $orderby" - }; - - } - - my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ]; - my $clink = sub { - my $cust_bill = shift; - $cust_bill->cust_main_custnum - ? [ "${p}view/cust_main.cgi?", 'custnum' ] - : ''; - }; - - my $conf = new FS::Conf; - my $money_char = $conf->config('money_char') || '$'; - - my $html_init = join("\n", map { - ( my $action = $_ ) =~ s/_$//; - include('/elements/progress-init.html', - $_.'form', - [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], - "../misc/${_}invoices.cgi", - { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... - $_, #key - ), - qq!<FORM NAME="${_}form">!, - qq!<INPUT TYPE="hidden" NAME="begin" VALUE="$begin">!, - qq!<INPUT TYPE="hidden" NAME="end" VALUE="$end">!, - qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!, - qq!<INPUT TYPE="hidden" NAME="open" VALUE="$open">!, - qq!<INPUT TYPE="hidden" NAME="days" VALUE="$days">!, - qq!</FORM>! - } qw( print_ email_ fax_ ) ); - - my $menubar = [ - 'Main menu' => $p, - 'Print these invoices' => - "javascript:print_process()", - 'Email these invoices' => - "javascript:email_process()", - ]; - - push @$menubar, 'Fax these invoices' => - "javascript:fax_process()" - if $conf->exists('hylafax'); - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Invoice Search Results', 'html_init' => $html_init, 'menubar' => $menubar, @@ -166,14 +20,197 @@ sub { time2str('%b %d %Y', shift->_date ) }, \&FS::UI::Web::cust_fields, ], - 'align' => 'rrrrll', + 'align' => 'rrrr'.FS::UI::Web::cust_aligns(), 'links' => [ $link, $link, $link, $link, - ( map { $clink } FS::UI::Web::cust_header() ), + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); + +my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )'; +#here is the agent virtualization +my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; + +my( $count_query, $sql_query ); +my( $count_addl ) = ( '' ); +my( $distinct ) = ( '' ); +my($begin, $end) = ( '', '' ); +my($agentnum) = ( '' ); +my($open, $days) = ( '', '' ); +if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { + $count_query = + "SELECT COUNT(*) FROM cust_bill $join_cust_main". + " WHERE invnum = $2 AND $agentnums_sql"; #agent virtualization + $sql_query = { + 'table' => 'cust_bill', + 'addl_from' => $join_cust_main, + 'hashref' => { 'invnum' => $2 }, + #'select' => '*', + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + }; +} else { +#if ( $cgi->param('begin') || $cgi->param('end') +# || $cgi->param('beginning') || $cgi->param('ending') +# || $cgi->keywords +# ) +#{ + + #some false laziness w/cust_bill::re_X + my @where; + my $orderby = 'ORDER BY cust_bill._date'; + + if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $begin = str2time($1); + push @where, "cust_bill._date >= $begin"; + } + if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $end = str2time($1) + 86399; + push @where, "cust_bill._date < $end"; + } + + if ( $cgi->param('begin') =~ /^(\d+)$/ ) { + $begin = $1; + push @where, "cust_bill._date >= $begin"; + } + if ( $cgi->param('end') =~ /^(\d+)$/ ) { + $end = $1; + push @where, "cust_bill._date < $end"; + } + + if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) { + push @where, "cust_bill.invnum >= $1"; + } + if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) { + push @where, "cust_bill.invnum <= $1"; + } + + if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + push @where, "cust_main.agentnum = $agentnum"; + } + + 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 )"; + + if ( $cgi->param('open') ) { + push @where, "0 != $owed"; + $open = 1; + } + + my($query) = $cgi->keywords; + if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { + ($open, $days, my $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; + } + + #here is the agent virtualization + push @where, $agentnums_sql; + my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; + + if ( $cgi->param('newest_percust') ) { + $distinct = 'DISTINCT ON ( cust_bill.custnum )'; + $orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC'; + #$count_query = "SELECT 'N/A', 'N/A', 'N/A'"; #XXXXXXX fix + $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; + } + + unless ( $count_query ) { + $count_query = "SELECT COUNT(*), sum(charged), sum($owed)"; + $count_addl = [ '$%.2f total invoiced', + '$%.2f total outstanding balance', + ]; + } + $count_query .= " FROM cust_bill $join_cust_main $extra_sql"; + + $sql_query = { + 'table' => 'cust_bill', + 'addl_from' => $join_cust_main, + 'hashref' => {}, + 'select' => "$distinct ". join(', ', + 'cust_bill.*', + #( map "cust_main.$_", qw(custnum last first company) ), + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + "$owed as owed", + ), + 'extra_sql' => "$extra_sql $orderby" + }; + +} + +my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ]; +my $clink = sub { + my $cust_bill = shift; + $cust_bill->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $html_init = join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], + "../misc/${_}invoices.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $_, #key + ), + qq!<FORM NAME="${_}form">!, + qq!<INPUT TYPE="hidden" NAME="begin" VALUE="$begin">!, + qq!<INPUT TYPE="hidden" NAME="end" VALUE="$end">!, + qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!, + qq!<INPUT TYPE="hidden" NAME="open" VALUE="$open">!, + qq!<INPUT TYPE="hidden" NAME="days" VALUE="$days">!, + qq!</FORM>! +} qw( print_ email_ fax_ ) ); + +my $menubar = [ + 'Main menu' => $p, + 'Print these invoices' => + "javascript:print_process()", + 'Email these invoices' => + "javascript:email_process()", + ]; + +push @$menubar, 'Fax these invoices' => + "javascript:fax_process()" + if $conf->exists('hylafax'); + +</%init> diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi index d82a83368..ada7e4362 100644 --- a/httemplate/search/cust_bill_event.cgi +++ b/httemplate/search/cust_bill_event.cgi @@ -1,52 +1,123 @@ -<% +<% include( 'elements/search.html', + 'title' => $title, + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'billing events', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Event', + 'Date', + 'Status', + #'Inv #', 'Inv Date', 'Cust #', + 'Invoice', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'event', + sub { time2str("%b %d %Y %T", $_[0]->_date) }, + sub { + #my $cust_bill_event = shift; + my $status = $_[0]->status; + $status .= ': '.$_[0]->statustext + if $_[0]->statustext; + $status; + }, + sub { + #my $cust_bill_event = shift; + 'Invoice #'. $_[0]->invnum. + ' ('. + time2str("%D", $_[0]->cust_bill_date). + ')'; + }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'lrlr'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + '', + sub { + my $part_bill_event = shift; + my $template = $part_bill_event->templatename; + $template .= '-' if $template; + [ "${p}view/cust_bill.cgi?$template", 'invnum']; + }, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> -my $title = $cgi->param('failed') ? 'Failed invoice events' : 'Invoice events'; +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports'); -my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +my $title = $cgi->param('failed') + ? 'Failed invoice events' + : 'Invoice events'; + +my @search = (); -##tie my %hash, 'Tie::DxHash', -#my %hash = ( -# _date => { op=> '>=', value=>$beginning }, -## i wish... -## _date => { op=> '<=', value=>$ending }, -#); -#$hash{'statustext'} = { op=> '!=', value=>'' } -# if $cgi->param('failed'); +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + #my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + #die "unknown agentnum $1" unless $agent; +} -my $where = " WHERE cust_bill_event._date >= $beginning". - " AND cust_bill_event._date <= $ending"; +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "cust_bill_event._date >= $beginning", + "cust_bill_event._date <= $ending"; if ( $cgi->param('failed') ) { - $where .= " AND statustext != '' ". - " AND statustext IS NOT NULL ". - " AND statustext != 'N/A' " + push @search, "statustext != ''", + "statustext IS NOT NULL", + "statustext != 'N/A'"; } if ( $cgi->param('part_bill_event.payby') =~ /^(\w+)$/ ) { - $where .= " AND part_bill_event.payby = '$1' "; + push @search, "part_bill_event.payby = '$1'"; } +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search ); + +my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '. + 'LEFT JOIN cust_bill USING ( invnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) '; + my $sql_query = { 'table' => 'cust_bill_event', - #'hashref' => \%hash, - 'hashref' => {}, 'select' => join(', ', - 'cust_bill_event.*', - 'part_bill_event.event', - 'cust_bill.custnum', - 'cust_bill._date AS cust_bill_date', - 'cust_main.custnum AS cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - ), + 'cust_bill_event.*', + 'part_bill_event.event', + 'cust_bill.custnum', + 'cust_bill._date AS cust_bill_date', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, 'extra_sql' => "$where ORDER BY _date ASC", - 'addl_from' => 'LEFT JOIN part_bill_event USING ( eventpart ) '. - 'LEFT JOIN cust_bill USING ( invnum ) '. - 'LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $join, }; -my $count_sql = "SELECT COUNT(*) FROM cust_bill_event ". - "LEFT JOIN part_bill_event USING ( eventpart ) ". - $where; +my $count_sql = "SELECT COUNT(*) FROM cust_bill_event $join $where"; my $conf = new FS::Conf; @@ -70,7 +141,6 @@ my $html_init = join("\n", map { } qw( print_ email_ fax_ ) ); my $menubar = [ - 'Main menu' => $p, 'Re-print these events' => "javascript:print_process()", 'Re-email these events' => @@ -88,50 +158,4 @@ my $link_cust = sub { : ''; }; -%><%= include( 'elements/search.html', - 'title' => $title, - 'html_init' => $html_init, - 'menubar' => $menubar, - 'name' => 'billing events', - 'query' => $sql_query, - 'count_query' => $count_sql, - 'header' => [ 'Event', - 'Date', - 'Status', - #'Inv #', 'Inv Date', 'Cust #', - 'Invoice', - FS::UI::Web::cust_header(), - ], - 'fields' => [ - 'event', - sub { time2str("%b %d %Y %T", $_[0]->_date) }, - sub { - #my $cust_bill_event = shift; - my $status = $_[0]->status; - $status .= ': '.$_[0]->statustext - if $_[0]->statustext; - $status; - }, - sub { - #my $cust_bill_event = shift; - 'Invoice #'. $_[0]->invnum. - ' ('. - time2str("%D", $_[0]->cust_bill_date). - ')'; - }, - \&FS::UI::Web::cust_fields, - ], - 'links' => [ - '', - '', - '', - sub { - my $part_bill_event = shift; - my $template = $part_bill_event->templatename; - $template .= '-' if $template; - [ "${p}view/cust_bill.cgi?$template", 'invnum']; - }, - ( map { $link_cust } FS::UI::Web::cust_header() ), - ], - ) -%> +</%init> diff --git a/httemplate/search/cust_bill_event.html b/httemplate/search/cust_bill_event.html index 197f28028..334bda3d3 100755 --- a/httemplate/search/cust_bill_event.html +++ b/httemplate/search/cust_bill_event.html @@ -1,16 +1,15 @@ -<%= include( +<% include( '/elements/header.html', ( $cgi->param('failed') ? 'Failed invoice events' : 'Invoice events' ), - include('/elements/menubar.html', - 'Main menu' => $p, # popurl(2), - ), - - ) + ) %> <FORM ACTION="cust_bill_event.cgi" METHOD="GET"> - <INPUT TYPE="hidden" NAME="failed" VALUE="<%= $cgi->param('failed') %>"> + <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') %>"> <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + <!--<TR> <TD ALIGN="right">Customer type</TD> <TD><SELECT MULTIPLE NAME="perhaps_payby"> @@ -23,15 +22,16 @@ </TD> </TR> --> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> + <% include( '/elements/tr-input-beginning_ending.html' ) %> <!-- <TR> <TD ALIGN="right">Events: </TD> <TD> <SELECT NAME="eventpart"> - <OPTION SELECTED VALUE=""><%= $cgi->param('failed') ? '(all failed events)' : '(all events)' %> - <% foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) { %> - <% } %> + <OPTION SELECTED VALUE=""><% $cgi->param('failed') ? '(all failed events)' : '(all events)' %> +% #foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) { +% #} + </SELECT> </TD> </TR> @@ -54,5 +54,11 @@ </TABLE> <BR><INPUT TYPE="submit" VALUE="Get Report"> </FORM> - </BODY> -</HTML> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports'); + +</%init> diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 082ccc893..17b4bc240 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -1,10 +1,74 @@ -<% +<% include( 'elements/search.html', + 'title' => 'Line items', + 'name' => 'line items', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Description', + 'Setup charge', + 'Recurring charge', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'billpkgnum', + sub { $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + }, + #strikethrough or "N/A ($amount)" or something these when + # they're not applicable to pkg_tax search + sub { sprintf($money_char.'%.2f', shift->setup ) }, + sub { sprintf($money_char.'%.2f', shift->recur ) }, + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + '', + $ilink, + $ilink, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); my $join_cust = " JOIN cust_bill USING ( invnum ) - JOIN cust_main USING ( custnum ) + LEFT JOIN cust_main USING ( custnum ) "; my $join_pkg = " @@ -12,10 +76,22 @@ my $join_pkg = " LEFT JOIN part_pkg USING ( pkgpart ) "; -my $where = " - WHERE _date >= $beginning AND _date <= $ending - AND payby != 'COMP' -"; +my $where = " WHERE _date >= $beginning AND _date <= $ending "; + +$where .= " AND payby != 'COMP' " + unless $cgi->param('include_comp_cust'); + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $where .= " AND agentnum = $1 "; +} + +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + if ( $1 == 0 ) { + $where .= " AND classnum IS NULL "; + } else { + $where .= " AND classnum = $1 "; + } +} if ( $cgi->param('out') ) { @@ -62,19 +138,27 @@ my $count_query; if ( $cgi->param('pkg_tax') ) { $count_query = - "SELECT COUNT(*), SUM( ( CASE WHEN part_pkg.setuptax = 'Y' - THEN cust_bill_pkg.setup - ELSE 0 ) - + - ( CASE WHEN part_pkg.recurtax = 'Y' - THEN cust_bill_pkg.recur - ELSE 0 ) - )"; + "SELECT COUNT(*), SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + "; $where .= " AND ( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) - )"; + ) + AND ( tax != 'Y' OR tax IS NULL ) + "; } else { @@ -104,45 +188,4 @@ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; -%><%= include( 'elements/search.html', - 'title' => 'Line items', - 'name' => 'line items', - 'query' => $query, - 'count_query' => $count_query, - 'count_addl' => [ $money_char. '%.2f total', ], - 'header' => [ - '#', - 'Description', - 'Setup charge', - 'Recurring charge', - 'Invoice', - 'Date', - FS::UI::Web::cust_header(), - ], - 'fields' => [ - 'billpkgnum', - sub { $_[0]->pkgnum > 0 - ? $_[0]->get('pkg') - : $_[0]->get('itemdesc') - }, - #strikethrough or "N/A ($amount)" or something these when - # they're not applicable to pkg_tax search - sub { sprintf($money_char.'%.2f', shift->setup ) }, - sub { sprintf($money_char.'%.2f', shift->recur ) }, - 'invnum', - sub { time2str('%b %d %Y', shift->_date ) }, - \&FS::UI::Web::cust_fields, - ], - 'links' => [ - '', - '', - '', - '', - $ilink, - $ilink, - ( map { $clink } FS::UI::Web::cust_header() ), - ], - 'align' => 'rlrrrc', - ) -%> - +</%init> diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html index 279d682cd..e4975c8de 100755 --- a/httemplate/search/cust_credit.html +++ b/httemplate/search/cust_credit.html @@ -1,69 +1,4 @@ -<% - my $title = 'Credit Search Results'; - #my( $count_query, $sql_query ); - - my @search = (); - - if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { - push @search, "cust_credit.otaker = '$1'"; - } - - if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { - push @search, "agentnum = $1"; - my $agent = qsearchs('agent', { 'agentnum' => $1 } ); - die "unknown agentnum $1" unless $agent; - $title = $agent->agent. " $title"; - } - - #false laziness with cust_pkg.cgi and cust_pay.cgi - if ( $cgi->param('beginning') - && $cgi->param('beginning') =~ /^([ 0-9\-\/]{1,10})$/ ) { - my $beginning = str2time($1); - push @search, "_date >= $beginning "; - } - if ( $cgi->param('ending') - && $cgi->param('ending') =~ /^([ 0-9\-\/]{1,10})$/ ) { - my $ending = str2time($1) + 86399; - push @search, " _date <= $ending "; - } - - if ( $cgi->param('begin') - && $cgi->param('begin') =~ /^(\d+)$/ ) { - push @search, "_date >= $1 "; - } - if ( $cgi->param('end') - && $cgi->param('end') =~ /^(\d+)$/ ) { - push @search, " _date < $1 "; - } - - my $where = scalar(@search) - ? 'WHERE '. join(' AND ', @search) - : ''; - - my $count_query = 'SELECT COUNT(*), SUM(amount) '. - 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '. - $where; - - my $sql_query = { - 'table' => 'cust_credit', - 'select' => join(', ', - 'cust_credit.*', - 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'hashref' => {}, - 'extra_sql' => $where, - 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', - }; - - my $clink = sub { - my $cust_bill = shift; - $cust_bill->cust_main_custnum - ? [ "${p}view/cust_main.cgi?", 'custnum' ] - : ''; - }; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => $title, 'name' => 'credits', 'query' => $sql_query, @@ -85,13 +20,85 @@ 'reason', ], #'align' => 'rrrllll', - 'align' => 'rr', + 'align' => 'rr'.FS::UI::Web::cust_aligns().'ll', 'links' => [ '', '', - ( map { $clink } FS::UI::Web::cust_header() ), + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), '', '', ], + 'color' => [ + '', + '', + FS::UI::Web::cust_colors(), + '', + '', + ], + 'style' => [ + '', + '', + FS::UI::Web::cust_styles(), + '', + '', + ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $title = 'Credit Search Results'; +#my( $count_query, $sql_query ); + +my @search = (); + +if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { + push @search, "cust_credit.otaker = '$1'"; +} + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "_date >= $beginning ", + "_date <= $ending"; + +push @search, FS::UI::Web::parse_lt_gt($cgi, 'amount' ); + +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search); + +my $count_query = 'SELECT COUNT(*), SUM(amount) '. + 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '. + $where; + +my $sql_query = { + 'table' => 'cust_credit', + 'select' => join(', ', + 'cust_credit.*', + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => $where, + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', +}; + + my $clink = sub { + my $cust_bill = shift; + $cust_bill->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; + }; + +</%init> diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi index 03c2619af..0c252e44b 100755 --- a/httemplate/search/cust_main-otaker.cgi +++ b/httemplate/search/cust_main-otaker.cgi @@ -1,28 +1,31 @@ -<HTML> - <HEAD> - <TITLE>Customer Search</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Customer Search - </FONT> - <BR> - <FORM ACTION="cust_main.cgi" METHOD="GET"> - Search for <B>Order taker</B>: - <INPUT TYPE="hidden" NAME="otaker_on" VALUE="TRUE"> - <% 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}; - %> - <SELECT NAME="otaker"> - <% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) { %> - <OPTION><%= $otaker->[0] %></OTAKER> - <% } %> - </SELECT> - <P><INPUT TYPE="submit" VALUE="Search"> - - </FORM> - </BODY> -</HTML> +<% include('/elements/header.html', 'Customer Search' ) %> +<FORM ACTION="cust_main.cgi" METHOD="GET"> + +Search for <B>Order taker</B>: + <INPUT TYPE="hidden" NAME="otaker_on" VALUE="TRUE"> +% 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}; +% + +<SELECT NAME="otaker"> +% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) { + + <OPTION><% $otaker->[0] %> +% } + +</SELECT> + +<P><INPUT TYPE="submit" VALUE="Search"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> 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 @@ -<HTML> - <HEAD> - <TITLE>Customer Search</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Customer Search - </FONT> - <BR> - <FORM ACTION="cust_main.cgi" METHOD="GET"> - Search for <B>Credit card #</B>: - <INPUT TYPE="hidden" NAME="card_on" VALUE="TRUE"> - <INPUT TYPE="text" NAME="card"> - - <P><INPUT TYPE="submit" VALUE="Search"> - - </FORM> - </BODY> -</HTML> - 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 @@ -<HTML> - <HEAD> - <TITLE>Quick payment entry</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Quick payment entry - </FONT> - <BR><BR> - <A HREF="../">Main Menu</A><BR><BR> - <FORM ACTION="cust_main.cgi" METHOD="GET"> - <INPUT TYPE="hidden" NAME="quickpay" VALUE="yes"> - <INPUT TYPE="checkbox" NAME="last_on" CHECKED> Search for <B>last name</B>: - <INPUT TYPE="text" NAME="last_text"> - using search method: <SELECT NAME="last_type"> - <OPTION SELECTED>All - <OPTION>Fuzzy - <OPTION>Substring - <OPTION>Exact - </SELECT> - - <P><INPUT TYPE="checkbox" NAME="company_on" CHECKED> Search for <B>company</B>: - <INPUT TYPE="text" NAME="company_text"> - using search method: <SELECT NAME="company_type"> - <OPTION SELECTED>All - <OPTION>Fuzzy - <OPTION>Substring - <OPTION>Exact - </SELECT> - - <P><INPUT TYPE="submit" VALUE="Search"> - - </FORM> - - <HR>Explanation of search methods: - <UL> - <LI><B>All</B> - Try all search methods. - <LI><B>Fuzzy</B> - Searches for matches that are close to your text. - <LI><B>Substring</B> - Searches for matches that contain your text. - <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods. - </UL> - </BODY> -</HTML> - diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html new file mode 100644 index 000000000..56df924bc --- /dev/null +++ b/httemplate/search/cust_main-zip.html @@ -0,0 +1,99 @@ +<% include( 'elements/search.html', + 'title' => 'Zip code Search Results', + 'name' => 'zip codes', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Zip code', 'Customers', ], + #'fields' => [ 'zip', 'num_cust', ], + 'links' => [ '', sub { 'somewhere'; } ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List zip codes'); + +# XXX link to customers + +my @where = (); + +# select status + +if ( $cgi->param('status') =~ /^(prospect|uncancel|active|susp|cancel)$/ ) { + my $method = $1.'_sql'; + push @where, FS::cust_main->$method(); +} + +# select agent +# XXX this needs to be virtualized by agent too (like lots of stuff) + +my $agentnum = ''; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + push @where, "cust_main.agentnum = $agentnum"; +} +my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; + +# bill zip vs ship zip + +sub fieldorempty { + my $field = shift; + "CASE WHEN $field IS NULL THEN '' ELSE $field END"; +} + +sub strip_plus4 { + my $field = shift; + "CASE WHEN $field is NULL + THEN '' + ELSE CASE WHEN $field LIKE '_____-____' + THEN SUBSTRING($field FROM 1 FOR 5) + ELSE $field + END + END"; +} + +my( $zip, $czip); +if ( $cgi->param('column') eq 'ship_zip' ) { + + my $casewhen_noship = + "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN "; + + $czip = "$casewhen_noship zip ELSE ship_zip END"; + + if ( $cgi->param('ignore_plus4') ) { + $zip = $casewhen_noship. strip_plus4('zip'). + " ELSE ". strip_plus4('ship_zip'). ' END'; + + } else { + $zip = $casewhen_noship. fieldorempty('zip'). + " ELSE ". fieldorempty('ship_zip'). ' END'; + } + +} else { + + $czip = 'zip'; + + if ( $cgi->param('ignore_plus4') ) { + $zip = strip_plus4('zip'); + } else { + $zip = fieldorempty('zip'); + } + +} + +# construct the queries and send 'em off + +my $sql_query = + "SELECT $zip AS zipcode, + COUNT(*) AS num_cust + FROM cust_main + $where + GROUP BY zipcode + ORDER BY num_cust DESC + "; + +my $count_sql = "select count(distinct $czip) from cust_main $where"; + +# XXX should link... + +</%init> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 665f5637d..e87fe36d7 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -1,685 +1,728 @@ -<% +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List customers'); +% +%my $conf = new FS::Conf; +%my $maxrecords = $conf->config('maxsearchrecordsperpage'); +% +%#my $cache; +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join svc_acct using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join ( +%# svc_acct left outer join ( +%# select svcnum, domain, catchall from svc_domain +%# ) as svc_acct_domsvc ( +%# svc_acct_svcnum, svc_acct_domain, svc_acct_catchall +%# ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum +%# ) using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%my $limit = ''; +%$limit .= "LIMIT $maxrecords" if $maxrecords; +% +%my $offset = $cgi->param('offset') || 0; +%$limit .= " OFFSET $offset" if $offset; +% +%my $total = 0; +% +%my(@cust_main, $sortby, $orderby); +%my @select = (); +%my @addl_headers = (); +%my @addl_cols = (); +%if ( $cgi->param('browse') +% || $cgi->param('otaker_on') +% || $cgi->param('agentnum_on') +%) { +% +% my %search = (); +% +% if ( $cgi->param('browse') ) { +% my $query = $cgi->param('browse'); +% if ( $query eq 'custnum' ) { +% $sortby=\*custnum_sort; +% $orderby = "ORDER BY custnum"; +% } elsif ( $query eq 'last' ) { +% $sortby=\*last_sort; +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; +% } elsif ( $query eq 'company' ) { +% $sortby=\*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% } elsif ( $query eq 'tickets' ) { +% $sortby = \*tickets_sort; +% $orderby = "ORDER BY tickets DESC"; +% push @select, FS::TicketSystem->sql_num_customer_tickets. " as tickets"; +% push @addl_headers, 'Tickets'; +% push @addl_cols, 'tickets'; +% } else { +% die "unknown browse field $query"; +% } +% } else { +% $sortby = \*last_sort; #?? +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; #?? +% } +% +% if ( $cgi->param('otaker_on') ) { +% die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +% $cgi->param('otaker') =~ /^(\w{1,32})$/ or eidiot "Illegal otaker\n"; +% $search{otaker} = $1; +% } elsif ( $cgi->param('agentnum_on') ) { +% $cgi->param('agentnum') =~ /^(\d+)$/ or eidiot "Illegal agentnum\n"; +% $search{agentnum} = $1; +%# } else { +%# die "unknown query..."; +% } +% +% my @qual = (); +% +% my $ncancelled = ''; +% +% if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') ) +% ) { +% #grep { $_->ncancelled_pkgs || ! $_->all_pkgs } +% push @qual, FS::cust_main->uncancel_sql; +% +% } +% +% push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled'); +% push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect'); +% push @qual, FS::cust_main->active_sql if $cgi->param('active'); +% push @qual, FS::cust_main->inactive_sql if $cgi->param('inactive'); +% push @qual, FS::cust_main->susp_sql if $cgi->param('suspended'); +% +% #EWWWWWW +% my $qual = join(' AND ', +% map { "$_ = ". dbh->quote($search{$_}) } keys %search ); +% +% my $addl_qual = join(' AND ', @qual); +% +% #here is the agent virtualization +% $addl_qual .= ( $addl_qual ? ' AND ' : '' ). +% $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% if ( $addl_qual ) { +% $qual .= ' AND ' if $qual; +% $qual .= $addl_qual; +% } +% +% $qual = " WHERE $qual" if $qual; +% my $statement = "SELECT COUNT(*) FROM cust_main $qual"; +% my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; +% $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; +% +% $total = $sth->fetchrow_arrayref->[0]; +% +% if ( $addl_qual ) { +% if ( %search ) { +% $addl_qual = " AND $addl_qual"; +% } else { +% $addl_qual = " WHERE $addl_qual"; +% } +% } +% +% my $select; +% if ( @select ) { +% $select = 'cust_main.*, '. join (', ', @select); +% } else { +% $select = '*'; +% } +% +% @cust_main = qsearch('cust_main', \%search, $select, +% "$addl_qual $orderby $limit" ); +% +%# foreach my $cust_main ( @just_cust_main ) { +%# +%# my @one_cust_main; +%# $FS::Record::DEBUG=1; +%# ( $cache, @one_cust_main ) = jsearch( +%# "$monsterjoin", +%# { 'custnum' => $cust_main->custnum }, +%# '', +%# '', +%# 'cust_main', +%# 'custnum', +%# ); +%# push @cust_main, @one_cust_main; +%# } +% +%} else { +% @cust_main=(); +% $sortby = \*last_sort; +% +% push @cust_main, @{&custnumsearch} +% if $cgi->param('custnum_on') && $cgi->param('custnum_text'); +% push @cust_main, @{&cardsearch} +% if $cgi->param('card_on') && $cgi->param('card'); +% push @cust_main, @{&lastsearch} +% if $cgi->param('last_on') && $cgi->param('last_text'); +% push @cust_main, @{&companysearch} +% if $cgi->param('company_on') && $cgi->param('company_text'); +% push @cust_main, @{&address2search} +% if $cgi->param('address2_on') && $cgi->param('address2_text'); +% push @cust_main, @{&phonesearch} +% if $cgi->param('phone_on') && $cgi->param('phone_text'); +% push @cust_main, @{&referralsearch} +% if $cgi->param('referral_custnum'); +% +% if ( $cgi->param('company_on') && $cgi->param('company_text') ) { +% $sortby = \*company_sort; +% push @cust_main, @{&companysearch}; +% } +% +% if ( $cgi->param('search_cust') ) { +% $sortby = \*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% push @cust_main, smart_search( 'search' => $cgi->param('search_cust') ); +% } +% +% @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main +% if ! $cgi->param('cancelled') +% && ( +% $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') ) +% ); +% +% my %saw = (); +% @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; +%} +% +%my %all_pkgs; +%if ( $conf->exists('hidecancelledpackages' ) ) { +% %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main; +%} else { +% %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main; +%} +%#%all_pkgs = (); +% +%if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { +% if ( $cgi->param('quickpay') eq 'yes' ) { +% print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum); +% } else { +% print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum); +% } +% #exit; +%} elsif ( scalar(@cust_main) == 0 ) { +% -my $conf = new FS::Conf; -my $maxrecords = $conf->config('maxsearchrecordsperpage'); - -#my $cache; - -#my $monsterjoin = <<END; -#cust_main left outer join ( -# ( cust_pkg left outer join part_pkg using(pkgpart) -# ) left outer join ( -# ( -# ( -# ( cust_svc left outer join part_svc using (svcpart) -# ) left outer join svc_acct using (svcnum) -# ) left outer join svc_domain using(svcnum) -# ) left outer join svc_forward using(svcnum) -# ) using (pkgnum) -#) using (custnum) -#END - -#my $monsterjoin = <<END; -#cust_main left outer join ( -# ( cust_pkg left outer join part_pkg using(pkgpart) -# ) left outer join ( -# ( -# ( -# ( cust_svc left outer join part_svc using (svcpart) -# ) left outer join ( -# svc_acct left outer join ( -# select svcnum, domain, catchall from svc_domain -# ) as svc_acct_domsvc ( -# svc_acct_svcnum, svc_acct_domain, svc_acct_catchall -# ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum -# ) using (svcnum) -# ) left outer join svc_domain using(svcnum) -# ) left outer join svc_forward using(svcnum) -# ) using (pkgnum) -#) using (custnum) -#END - -my $limit = ''; -$limit .= "LIMIT $maxrecords" if $maxrecords; - -my $offset = $cgi->param('offset') || 0; -$limit .= " OFFSET $offset" if $offset; - -my $total = 0; - -my(@cust_main, $sortby, $orderby); -my @select = (); -my @addl_headers = (); -my @addl_cols = (); -if ( $cgi->param('browse') - || $cgi->param('otaker_on') - || $cgi->param('agentnum_on') -) { - - my %search = (); - - if ( $cgi->param('browse') ) { - my $query = $cgi->param('browse'); - if ( $query eq 'custnum' ) { - $sortby=\*custnum_sort; - $orderby = "ORDER BY custnum"; - } elsif ( $query eq 'last' ) { - $sortby=\*last_sort; - $orderby = "ORDER BY LOWER(last || ' ' || first)"; - } elsif ( $query eq 'company' ) { - $sortby=\*company_sort; - $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; - } elsif ( $query eq 'tickets' ) { - $sortby = \*tickets_sort; - $orderby = "ORDER BY tickets DESC"; - push @select, FS::TicketSystem->sql_num_customer_tickets. " as tickets"; - push @addl_headers, 'Tickets'; - push @addl_cols, 'tickets'; - } else { - die "unknown browse field $query"; - } - } else { - $sortby = \*last_sort; #?? - $orderby = "ORDER BY LOWER(last || ' ' || first)"; #?? - } - - if ( $cgi->param('otaker_on') ) { - $cgi->param('otaker') =~ /^(\w{1,32})$/ or eidiot "Illegal otaker\n"; - $search{otaker} = $1; - } elsif ( $cgi->param('agentnum_on') ) { - $cgi->param('agentnum') =~ /^(\d+)$/ or eidiot "Illegal agentnum\n"; - $search{agentnum} = $1; -# } else { -# die "unknown query..."; - } - - my @qual = (); - - my $ncancelled = ''; - - if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledcustomers') - && ! $cgi->param('showcancelledcustomers') ) - ) { - #grep { $_->ncancelled_pkgs || ! $_->all_pkgs } - push @qual, " - ( 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ( cust_pkg.cancel IS NULL - OR cust_pkg.cancel = 0 - ) - ) - OR 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) - ) - "; - } - - push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled'); - push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect'); - push @qual, FS::cust_main->active_sql if $cgi->param('active'); - push @qual, FS::cust_main->susp_sql if $cgi->param('suspended'); - - #EWWWWWW - my $qual = join(' AND ', - map { "$_ = ". dbh->quote($search{$_}) } keys %search ); - - my $addl_qual = join(' AND ', @qual); - - if ( $addl_qual ) { - $qual .= ' AND ' if $qual; - $qual .= $addl_qual; - } - - $qual = " WHERE $qual" if $qual; - my $statement = "SELECT COUNT(*) FROM cust_main $qual"; - my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; - $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; - - $total = $sth->fetchrow_arrayref->[0]; - - if ( $addl_qual ) { - if ( %search ) { - $addl_qual = " AND $addl_qual"; - } else { - $addl_qual = " WHERE $addl_qual"; - } - } - - my $select; - if ( @select ) { - $select = 'cust_main.*, '. join (', ', @select); - } else { - $select = '*'; - } - - @cust_main = qsearch('cust_main', \%search, $select, - "$addl_qual $orderby $limit" ); - -# foreach my $cust_main ( @just_cust_main ) { -# -# my @one_cust_main; -# $FS::Record::DEBUG=1; -# ( $cache, @one_cust_main ) = jsearch( -# "$monsterjoin", -# { 'custnum' => $cust_main->custnum }, -# '', -# '', -# 'cust_main', -# 'custnum', -# ); -# push @cust_main, @one_cust_main; -# } - -} else { - @cust_main=(); - $sortby = \*last_sort; - - push @cust_main, @{&custnumsearch} - if $cgi->param('custnum_on') && $cgi->param('custnum_text'); - push @cust_main, @{&cardsearch} - if $cgi->param('card_on') && $cgi->param('card'); - push @cust_main, @{&lastsearch} - if $cgi->param('last_on') && $cgi->param('last_text'); - push @cust_main, @{&companysearch} - if $cgi->param('company_on') && $cgi->param('company_text'); - push @cust_main, @{&address2search} - if $cgi->param('address2_on') && $cgi->param('address2_text'); - push @cust_main, @{&phonesearch} - if $cgi->param('phone_on') && $cgi->param('phone_text'); - push @cust_main, @{&referralsearch} - if $cgi->param('referral_custnum'); - - if ( $cgi->param('company_on') && $cgi->param('company_text') ) { - $sortby = \*company_sort; - push @cust_main, @{&companysearch}; - } - - @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main - if ! $cgi->param('cancelled') - && ( - $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledcustomers') - && ! $cgi->param('showcancelledcustomers') ) - ); - - my %saw = (); - @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; -} - -my %all_pkgs; -if ( $conf->exists('hidecancelledpackages' ) ) { - %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main; -} else { - %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main; -} -#%all_pkgs = (); - -if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { - if ( $cgi->param('quickpay') eq 'yes' ) { - print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum); - } else { - print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum); - } - #exit; -} elsif ( scalar(@cust_main) == 0 ) { -%> -<!-- mason kludge --> -<% - eidiot "No matching customers found!\n"; -} else { -%> <!-- mason kludge --> -<% - - $total ||= scalar(@cust_main); - print header("Customer Search Results",menubar( - 'Main Menu', popurl(2) - )), "$total matching customers found "; - - #begin pager - my $pager = ''; - if ( $total != scalar(@cust_main) && $maxrecords ) { - unless ( $offset == 0 ) { - $cgi->param('offset', $offset - $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Previous</FONT></B></A> '; - } - my $poff; - my $page; - for ( $poff = 0; $poff < $total; $poff += $maxrecords ) { - $page++; - if ( $offset == $poff ) { - $pager .= qq!<FONT SIZE="+2">$page</FONT> !; - } else { - $cgi->param('offset', $poff); - $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !; - } - } - unless ( $offset + $maxrecords > $total ) { - $cgi->param('offset', $offset + $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Next</FONT></B></A> '; - } - } - #end pager - - unless ( $cgi->param('cancelled') ) { - if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledcustomers') - && ! $cgi->param('showcancelledcustomers') - ) - ) { - $cgi->param('showcancelledcustomers', 1); - $cgi->param('offset', 0); - print qq!( <a href="!. $cgi->self_url. qq!">show!; - } else { - $cgi->param('showcancelledcustomers', 0); - $cgi->param('offset', 0); - print qq!( <a href="!. $cgi->self_url. qq!">hide!; - } - print ' cancelled customers</a> )'; - } - - if ( $cgi->param('referral_custnum') ) { - $cgi->param('referral_custnum') =~ /^(\d+)$/ - or eidiot "Illegal referral_custnum\n"; - my $referral_custnum = $1; - my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } ); - print '<FORM METHOD="GET">'. - qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!. - 'referrals of <A HREF="'. popurl(2). - "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ". - ( $cust_main->company - || $cust_main->last. ', '. $cust_main->first ). - '</A>'; - print "\n",<<END; - <SCRIPT> - function changed(what) { - what.form.submit(); - } - </SCRIPT> -END - print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">'; - my $max = 8; #config file - $cgi->param('referral_depth') =~ /^(\d*)$/ - or eidiot "Illegal referral_depth"; - my $referral_depth = $1; - - foreach my $depth ( 1 .. $max ) { - print '<OPTION', - ' SELECTED'x($depth == $referral_depth), - ">$depth"; - } - print "</SELECT> levels deep". - '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'. - '</FORM>'; - } - - my @custom_priorities = (); - if ( $conf->config('ticket_system-custom_priority_field') - && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { - @custom_priorities = - $conf->config('ticket_system-custom_priority_field-values'); - } - - print "<BR><BR>". $pager. &table(). <<END; - <TR> - <TH></TH> - <TH>(bill) name</TH> - <TH>company</TH> -END +% +% eidiot "No matching customers found!\n"; +%} else { +% + +<% include('/elements/header.html', "Customer Search Results", '' ) %> +% $total ||= scalar(@cust_main); + + + <% $total %> matching customers found + +% my $pager = include( '/elements/pager.html', +% 'offset' => $offset, +% 'num_rows' => scalar(@cust_main), +% 'total' => $total, +% 'maxrecords' => $maxrecords, +% ); +% +% unless ( $cgi->param('cancelled') ) { +% if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') +% ) +% ) { +% $cgi->param('showcancelledcustomers', 1); +% $cgi->param('offset', 0); +% print qq!( <a href="!. $cgi->self_url. qq!">show!; +% } else { +% $cgi->param('showcancelledcustomers', 0); +% $cgi->param('offset', 0); +% print qq!( <a href="!. $cgi->self_url. qq!">hide!; +% } +% print ' cancelled customers</a> )'; +% } +% +% if ( $cgi->param('referral_custnum') ) { +% $cgi->param('referral_custnum') =~ /^(\d+)$/ +% or eidiot "Illegal referral_custnum\n"; +% my $referral_custnum = $1; +% my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } ); +% print '<FORM METHOD="GET">'. +% qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!. +% 'referrals of <A HREF="'. popurl(2). +% "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ". +% ( $cust_main->company +% || $cust_main->last. ', '. $cust_main->first ). +% '</A>'; +% print "\n",<<END; +% <SCRIPT> +% function changed(what) { +% what.form.submit(); +% } +% </SCRIPT> +%END +% print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">'; +% my $max = 8; #config file +% $cgi->param('referral_depth') =~ /^(\d*)$/ +% or eidiot "Illegal referral_depth"; +% my $referral_depth = $1; +% +% foreach my $depth ( 1 .. $max ) { +% print '<OPTION', +% ' SELECTED'x($depth == $referral_depth), +% ">$depth"; +% } +% print "</SELECT> levels deep". +% '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'. +% '</FORM>'; +% } +% +% my @custom_priorities = (); +% if ( $conf->config('ticket_system-custom_priority_field') +% && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { +% @custom_priorities = +% $conf->config('ticket_system-custom_priority_field-values'); +% } +% +% print "<BR><BR>". $pager. include('/elements/table-grid.html'). <<END; +% <TR> +% <TH CLASS="grid" BGCOLOR="#cccccc">#</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">(bill) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +% +%if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">(service) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +%} +% +%foreach my $addl_header ( @addl_headers ) { +% print '<TH CLASS="grid" BGCOLOR="#cccccc">'. "$addl_header</TH>"; +%} +% +%print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">Packages</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Services</TH> +% </TR> +%END +% +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% my(%saw,$cust_main); +% foreach $cust_main ( +% sort $sortby grep(!$saw{$_->custnum}++, @cust_main) +% ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my($custnum,$last,$first,$company)=( +% $cust_main->custnum, +% $cust_main->getfield('last'), +% $cust_main->getfield('first'), +% $cust_main->company, +% ); +% +% my(@lol_cust_svc); +% my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} ); +% foreach ( @{$all_pkgs{$custnum}} ) { +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my @cust_svc = $_->cust_svc; +% push @lol_cust_svc, \@cust_svc; +% $rowspan += scalar(@cust_svc) || 1; +% } +% +% #my($rowspan) = scalar(@{$all_pkgs{$custnum}}); +% my $view; +% if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) { +% $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum; +% } else { +% $view = $p. 'view/cust_main.cgi?'. $custnum; +% } +% my $pcompany = $company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% +% my $status = $cust_main->status; +% my $statuscol = $FS::cust_main::statuscolor{$status}; -if ( defined dbdef->table('cust_main')->column('ship_last') ) { - print <<END; - <TH>(service) name</TH> - <TH>company</TH> -END -} - -foreach my $addl_header ( @addl_headers ) { - print "<TH>$addl_header</TH>"; -} - -print <<END; - <TH>Packages</TH> - <TH COLSPAN=2>Services</TH> - </TR> -END - - my(%saw,$cust_main); - foreach $cust_main ( - sort $sortby grep(!$saw{$_->custnum}++, @cust_main) - ) { - my($custnum,$last,$first,$company)=( - $cust_main->custnum, - $cust_main->getfield('last'), - $cust_main->getfield('first'), - $cust_main->company, - ); - - my(@lol_cust_svc); - my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} ); - foreach ( @{$all_pkgs{$custnum}} ) { - #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); - my @cust_svc = $_->cust_svc; - push @lol_cust_svc, \@cust_svc; - $rowspan += scalar(@cust_svc) || 1; - } - - #my($rowspan) = scalar(@{$all_pkgs{$custnum}}); - my $view; - if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) { - $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum; - } else { - $view = $p. 'view/cust_main.cgi?'. $custnum; - } - my $pcompany = $company - ? qq!<A HREF="$view"><FONT SIZE=-1>$company</FONT></A>! - : '<FONT SIZE=-1> </FONT>'; - print <<END; <TR> - <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$custnum</FONT></A></TD> - <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$last, $first</FONT></A></TD> - <TD ROWSPAN=$rowspan>$pcompany</TD> -END - if ( defined dbdef->table('cust_main')->column('ship_last') ) { - my($ship_last,$ship_first,$ship_company)=( - $cust_main->ship_last || $cust_main->getfield('last'), - $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, - $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, - ); - my $pship_company = $ship_company - ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>! - : '<FONT SIZE=-1> </FONT>'; - print <<END; - <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$ship_last, $ship_first</FONT></A></TD> - <TD ROWSPAN=$rowspan>$pship_company</A></TD> -END - } - - foreach my $addl_col ( @addl_cols ) { - print "<TD ROWSPAN=$rowspan ALIGN=right><FONT SIZE=-1>"; - if ( $addl_col eq 'tickets' ) { - if ( @custom_priorities ) { - print &itable('', 0); - foreach my $priority ( @custom_priorities, '' ) { - - my $num = - FS::TicketSystem->num_customer_tickets($custnum,$priority); - my $ahref = ''; - $ahref= '<A HREF="'. - FS::TicketSystem->href_customer_tickets($custnum,$priority). - '">' - if $num; - - print '<TR>'. - " <TD ALIGN=right><FONT SIZE=-1>$ahref$num</A></FONT></TD>". - "<TD ALIGN=left><FONT SIZE=-1>$ahref". - ( $priority || '<i>(none)</i>' ). - "</A></FONT></TD></TR>"; - - } - print '<TR><TD BGCOLOR="#000000" COLSPAN=2></TD></TR>'. - '<TR><TD ALIGN=right><FONT SIZE=-1>'; - } - - my $ahref = ''; - $ahref = '<A HREF="'. - FS::TicketSystem->href_customer_tickets($custnum). - '">' - if $cust_main->get($addl_col); - - print $ahref. $cust_main->get($addl_col). '</A>'; - print "</FONT></TD><TD ALIGN=left>". - "<FONT SIZE=-1>${ahref}Total</A><FONT>". - "</TD></TR></TABLE>" - if @custom_priorities; - - } else { - print $cust_main->get($addl_col); - } - print "</FONT></TD>"; - } - - my($n1)=''; - foreach ( @{$all_pkgs{$custnum}} ) { - my $pkgnum = $_->pkgnum; -# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } ); - my $part_pkg = $_->part_pkg; + <TD CLASS="grid" ALIGN="right" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% $custnum %></FONT></A></TD> + <TD CLASS="grid" ALIGN="center" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><FONT SIZE=-1 COLOR=<% $statuscol %>><B><% ucfirst($status) %></B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$last, $first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pcompany %></TD> +% +% if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% my($ship_last,$ship_first,$ship_company)=( +% $cust_main->ship_last || $cust_main->getfield('last'), +% $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, +% $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, +% ); +% my $pship_company = $ship_company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pship_company %></A></TD> +% } +% +% foreach my $addl_col ( @addl_cols ) { +% if ( $addl_col eq 'tickets' ) { +% if ( @custom_priorities ) { + + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> +% foreach my $priority ( @custom_priorities, '' ) { +% +% my $num = +% FS::TicketSystem->num_customer_tickets($custnum,$priority); +% my $ahref = ''; +% $ahref= '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum,$priority). +% '">' +% if $num; +% + + + <TR> + <TD ALIGN=right> + <FONT SIZE=-1><% $ahref.$num %></A></FONT> + </TD> + <TD ALIGN=left> + <FONT SIZE=-1><% $ahref %><% $priority || '<i>(none)</i>' %></A></FONT> + </TD> + </TR> +% } + + + <TR> + <TH ALIGN=right STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> +% } +% +% my $ahref = ''; +% $ahref = '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum). +% '">' +% if $cust_main->get($addl_col); +% + + + <% $ahref %><% $cust_main->get($addl_col) %></A> +% if ( @custom_priorities ) { + + + </FONT></TH> + <TH ALIGN=left STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1><% ${ahref} %>Total</A><FONT> + </TH> + </TR> + </TABLE> +% } + + + </FONT></TD> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + <% $cust_main->get($addl_col) %> + </FONT></TD> +% +% } +% } +% +% my($n1)=''; +% foreach ( @{$all_pkgs{$custnum}} ) { +% my $pkgnum = $_->pkgnum; +%# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } ); +% my $part_pkg = $_->part_pkg; +% +% my $pkg = $part_pkg->pkg; +% my $comment = $part_pkg->comment; +% my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum"; +% my @cust_svc = @{shift @lol_cust_svc}; +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my $rowspan = scalar(@cust_svc) || 1; +% +% print $n1, qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!; +% +% my($n2)=''; +% foreach my $cust_svc ( @cust_svc ) { +% my($label, $value, $svcdb) = $cust_svc->label; +% my($svcnum) = $cust_svc->svcnum; +% my($sview) = $p.'view'; +% print $n2, +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !. +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !; +% $n2="</TR><TR>"; +% } +% +% unless ( @cust_svc ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=2> </TD>!; +% } +% +% #print qq!</TR><TR>\n!; +% $n1="</TR><TR>"; +% } +% +% unless ( @{$all_pkgs{$custnum}} ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=3> </TD>!; +% } +% +% print "</TR>"; +% } +% +% - my $pkg = $part_pkg->pkg; - my $comment = $part_pkg->comment; - my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum"; - my @cust_svc = @{shift @lol_cust_svc}; - #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); - my $rowspan = scalar(@cust_svc) || 1; - - print $n1, qq!<TD ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!; - my($n2)=''; - foreach my $cust_svc ( @cust_svc ) { - my($label, $value, $svcdb) = $cust_svc->label; - my($svcnum) = $cust_svc->svcnum; - my($sview) = $p.'view'; - print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, - qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; - $n2="</TR><TR>"; - } - #print qq!</TR><TR>\n!; - $n1="</TR><TR>"; - } - print "</TR>"; - } - print "</TABLE>$pager</BODY></HTML>"; - -} - -#undef $cache; #does this help? - -# - -sub last_sort { - lc($a->getfield('last')) cmp lc($b->getfield('last')) - || lc($a->first) cmp lc($b->first); -} - -sub company_sort { - return -1 if $a->company && ! $b->company; - return 1 if ! $a->company && $b->company; - lc($a->company) cmp lc($b->company) - || lc($a->getfield('last')) cmp lc($b->getfield('last')) - || lc($a->first) cmp lc($b->first);; -} - -sub custnum_sort { - $a->getfield('custnum') <=> $b->getfield('custnum'); -} - -sub tickets_sort { - $b->getfield('tickets') <=> $a->getfield('tickets'); -} - -sub custnumsearch { - - my $custnum = $cgi->param('custnum_text'); - $custnum =~ s/\D//g; - $custnum =~ /^(\d{1,23})$/ or eidiot "Illegal customer number\n"; - $custnum = $1; - - [ qsearchs('cust_main', { 'custnum' => $custnum } ) ]; -} - -sub cardsearch { - - my($card)=$cgi->param('card'); - $card =~ s/\D//g; - $card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n"; - my($payinfo)=$1; - - [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}), - qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'}) - ]; -} - -sub referralsearch { - $cgi->param('referral_custnum') =~ /^(\d+)$/ - or eidiot "Illegal referral_custnum"; - my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } ) - or eidiot "Customer $1 not found"; - my $depth; - if ( $cgi->param('referral_depth') ) { - $cgi->param('referral_depth') =~ /^(\d+)$/ - or eidiot "Illegal referral_depth"; - $depth = $1; - } else { - $depth = 1; - } - [ $cust_main->referral_cust_main($depth) ]; -} - -sub lastsearch { - my(%last_type); - my @cust_main; - foreach ( $cgi->param('last_type') ) { - $last_type{$_}++; - } - - $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/ - or eidiot "Illegal last name"; - my($last)=$1; - - if ( $last_type{'Exact'} || $last_type{'Fuzzy'} ) { - push @cust_main, qsearch( 'cust_main', - { 'last' => { 'op' => 'ILIKE', - 'value' => $last } } ); - - push @cust_main, qsearch( 'cust_main', - { 'ship_last' => { 'op' => 'ILIKE', - 'value' => $last } } ) - if defined dbdef->table('cust_main')->column('ship_last'); - } - - if ( $last_type{'Substring'} || $last_type{'All'} ) { - - push @cust_main, qsearch( 'cust_main', - { 'last' => { 'op' => 'ILIKE', - 'value' => "%$last%" } } ); - - push @cust_main, qsearch( 'cust_main', - { 'ship_last' => { 'op' => 'ILIKE', - 'value' => "%$last%" } } ) - if defined dbdef->table('cust_main')->column('ship_last'); - - } - - if ( $last_type{'Fuzzy'} || $last_type{'All'} ) { - push @cust_main, FS::cust_main->fuzzy_search( { 'last' => $last } ); - } - - #if ($last_type{'Sound-alike'}) { - #} - - \@cust_main; -} - -sub companysearch { - - my(%company_type); - my @cust_main; - foreach ( $cgi->param('company_type') ) { - $company_type{$_}++ - }; - - $cgi->param('company_text') =~ - /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ - or eidiot "Illegal company"; - my $company = $1; - - if ( $company_type{'Exact'} || $company_type{'Fuzzy'} ) { - push @cust_main, qsearch( 'cust_main', - { 'company' => { 'op' => 'ILIKE', - 'value' => $company } } ); - - push @cust_main, qsearch( 'cust_main', - { 'ship_company' => { 'op' => 'ILIKE', - 'value' => $company } } ) - if defined dbdef->table('cust_main')->column('ship_last'); - } - - if ( $company_type{'Substring'} || $company_type{'All'} ) { - - push @cust_main, qsearch( 'cust_main', - { 'company' => { 'op' => 'ILIKE', - 'value' => "%$company%" } } ); - - push @cust_main, qsearch( 'cust_main', - { 'ship_company' => { 'op' => 'ILIKE', - 'value' => "%$company%" } }) - if defined dbdef->table('cust_main')->column('ship_last'); - - } - - if ( $company_type{'Fuzzy'} || $company_type{'All'} ) { - push @cust_main, FS::cust_main->fuzzy_search( { 'company' => $company } ); - } - - if ($company_type{'Sound-alike'}) { - } - - \@cust_main; -} - -sub address2search { - my @cust_main; - - $cgi->param('address2_text') =~ - /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ - or eidiot "Illegal address2"; - my $address2 = $1; - - push @cust_main, qsearch( 'cust_main', - { 'address2' => { 'op' => 'ILIKE', - 'value' => $address2 } } ); - push @cust_main, qsearch( 'cust_main', - { 'address2' => { 'op' => 'ILIKE', - 'value' => $address2 } } ) - if defined dbdef->table('cust_main')->column('ship_last'); - - \@cust_main; -} - -sub phonesearch { - my @cust_main; - - my $phone = $cgi->param('phone_text'); - - #(no longer really) false laziness with Record::ut_phonen - #only works with US/CA numbers... - $phone =~ s/\D//g; - if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) { - $phone = "$1-$2-$3"; - $phone .= " x$4" if $4; - } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) { - $phone = "$1-$2"; - } elsif ( $phone =~ /^(\d{3,4})$/ ) { - $phone = $1; - } else { - eidiot gettext('illegal_phone'). ": $phone"; - } - - my @fields = qw(daytime night fax); - push @fields, qw(ship_daytime ship_night ship_fax) - if defined dbdef->table('cust_main')->column('ship_last'); - - for my $field ( @fields ) { - push @cust_main, qsearch ( 'cust_main', - { $field => { 'op' => 'LIKE', - 'value' => "%$phone%" } } ); - } - - \@cust_main; -} - -%> + </TABLE><% $pager %> + + <% include('/elements/footer.html') %> +% } +% +%#undef $cache; #does this help? +% +%# +% +%sub last_sort { +% lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first); +%} +% +%sub company_sort { +% return -1 if $a->company && ! $b->company; +% return 1 if ! $a->company && $b->company; +% lc($a->company) cmp lc($b->company) +% || lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first);; +%} +% +%sub custnum_sort { +% $a->getfield('custnum') <=> $b->getfield('custnum'); +%} +% +%sub tickets_sort { +% $b->getfield('tickets') <=> $a->getfield('tickets'); +%} +% +%sub custnumsearch { +% +% my $custnum = $cgi->param('custnum_text'); +% $custnum =~ s/\D//g; +% $custnum =~ /^(\d{1,23})$/ or eidiot "Illegal customer number\n"; +% $custnum = $1; +% +% [ qsearchs('cust_main', { 'custnum' => $custnum } ) ]; +%} +% +%sub cardsearch { +% +% my($card)=$cgi->param('card'); +% $card =~ s/\D//g; +% $card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n"; +% my($payinfo)=$1; +% +% [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}), +% qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'}) +% ]; +%} +% +%sub referralsearch { +% $cgi->param('referral_custnum') =~ /^(\d+)$/ +% or eidiot "Illegal referral_custnum"; +% my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } ) +% or eidiot "Customer $1 not found"; +% my $depth; +% if ( $cgi->param('referral_depth') ) { +% $cgi->param('referral_depth') =~ /^(\d+)$/ +% or eidiot "Illegal referral_depth"; +% $depth = $1; +% } else { +% $depth = 1; +% } +% [ $cust_main->referral_cust_main($depth) ]; +%} +% +%sub lastsearch { +% my(%last_type); +% my @cust_main; +% foreach ( $cgi->param('last_type') ) { +% $last_type{$_}++; +% } +% +% $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/ +% or eidiot "Illegal last name"; +% my($last)=$1; +% +% if ( $last_type{'Exact'} || $last_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => $last } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => $last } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $last_type{'Substring'} || $last_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $last_type{'Fuzzy'} || $last_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'last' => $last } ); +% } +% +% #if ($last_type{'Sound-alike'}) { +% #} +% +% \@cust_main; +%} +% +%sub companysearch { +% +% my(%company_type); +% my @cust_main; +% foreach ( $cgi->param('company_type') ) { +% $company_type{$_}++ +% }; +% +% $cgi->param('company_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot "Illegal company"; +% my $company = $1; +% +% if ( $company_type{'Exact'} || $company_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => $company } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => $company } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $company_type{'Substring'} || $company_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } }) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $company_type{'Fuzzy'} || $company_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'company' => $company } ); +% } +% +% if ($company_type{'Sound-alike'}) { +% } +% +% \@cust_main; +%} +% +%sub address2search { +% my @cust_main; +% +% $cgi->param('address2_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot "Illegal address2"; +% my $address2 = $1; +% +% push @cust_main, qsearch( 'cust_main', +% { 'address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ); +% push @cust_main, qsearch( 'cust_main', +% { 'address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% \@cust_main; +%} +% +%sub phonesearch { +% my @cust_main; +% +% my $phone = $cgi->param('phone_text'); +% +% #(no longer really) false laziness with Record::ut_phonen +% #only works with US/CA numbers... +% $phone =~ s/\D//g; +% if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) { +% $phone = "$1-$2-$3"; +% $phone .= " x$4" if $4; +% } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) { +% $phone = "$1-$2"; +% } elsif ( $phone =~ /^(\d{3,4})$/ ) { +% $phone = $1; +% } else { +% eidiot gettext('illegal_phone'). ": $phone"; +% } +% +% my @fields = qw(daytime night fax); +% push @fields, qw(ship_daytime ship_night ship_fax) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% for my $field ( @fields ) { +% push @cust_main, qsearch ( 'cust_main', +% { $field => { 'op' => 'LIKE', +% 'value' => "%$phone%" } } ); +% } +% +% \@cust_main; +%} diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html index 4f7508447..8ef1ecb9a 100755 --- a/httemplate/search/cust_main.html +++ b/httemplate/search/cust_main.html @@ -39,4 +39,9 @@ </UL> </BODY> </HTML> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List customers'); + +</%init> diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi index 99ffc3d20..6215b4b2f 100755 --- a/httemplate/search/cust_pay.cgi +++ b/httemplate/search/cust_pay.cgi @@ -1,150 +1,4 @@ -<% - my $title = 'Payment Search Results'; - my( $count_query, $sql_query ); - if ( $cgi->param('magic') ) { - - my @search = (); - my $orderby; - if ( $cgi->param('magic') eq '_date' ) { - - - if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { - push @search, "agentnum = $1"; # $search{'agentnum'} = $1; - my $agent = qsearchs('agent', { 'agentnum' => $1 } ); - die "unknown agentnum $1" unless $agent; - $title = $agent->agent. " $title"; - } - - if ( $cgi->param('payby') ) { - $cgi->param('payby') =~ - /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ - or die "illegal payby ". $cgi->param('payby'); - push @search, "cust_pay.payby = '$1'"; - if ( $3 ) { - if ( $3 eq 'VisaMC' ) { - #avoid posix regexes for portability - push @search, - " ( ( substring(cust_pay.payinfo from 1 for 1) = '4' ". - " AND substring(cust_pay.payinfo from 1 for 4) != '4936' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49030[2-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49033[5-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49110[1-2]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49117[4-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49118[1-2]' ". - " )". - " OR substring(cust_pay.payinfo from 1 for 2) = '51' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '52' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '53' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '55' ". - " ) "; - } elsif ( $3 eq 'Amex' ) { - push @search, - " ( substring(cust_pay.payinfo from 1 for 2 ) = '34' ". - " OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ". - " ) "; - } elsif ( $3 eq 'Discover' ) { - push @search, - " ( substring(cust_pay.payinfo from 1 for 4 ) = '6011' ". - " OR substring(cust_pay.payinfo from 1 for 3 ) = '650' ". - " ) "; - } elsif ( $3 eq 'Maestro' ) { - push @search, - " ( substring(cust_pay.payinfo from 1 for 2 ) = '63' ". - " OR substring(cust_pay.payinfo from 1 for 2 ) = '67' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ". - " OR substring(cust_pay.payinfo from 1 for 4 ) = '4936' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49030[2-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49033[5-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49110[1-2]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49117[4-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49118[1-2]' ". - " ) "; - } else { - die "unknown card type $3"; - } - } - } - - my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); - push @search, "_date >= $beginning ", - "_date <= $ending"; - - $orderby = '_date'; - - } elsif ( $cgi->param('magic') eq 'paybatch' ) { - - $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ - or die "illegal paybatch: ". $cgi->param('paybatch'); - - push @search, "paybatch = '$1'"; - - $orderby = "LOWER(company || ' ' || last || ' ' || first )"; - - } else { - die "unknown search magic: ". $cgi->param('magic'); - } - - my $search = ''; - if ( @search ) { - $search = ' WHERE '. join(' AND ', @search); - } - - $count_query = "SELECT COUNT(*), SUM(paid) ". - "FROM cust_pay LEFT JOIN cust_main USING ( custnum )". - $search; - - $sql_query = { - 'table' => 'cust_pay', - 'select' => join(', ', - 'cust_pay.*', - 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'hashref' => {}, - 'extra_sql' => "$search ORDER BY $orderby", - 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', - }; - - } else { - - $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; - my $payinfo = $1; - - $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; - my $payby = $1; - - $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay ". - "WHERE payinfo = '$payinfo' AND payby = '$payby'"; - - $sql_query = { - 'table' => 'cust_pay', - 'hashref' => { 'payinfo' => $payinfo, - 'payby' => $payby }, - 'extra_sql' => "ORDER BY _date", - }; - - } - - my $link = sub { - my $cust_pay = shift; - $cust_pay->cust_main_custnum - ? [ "${p}view/cust_main.cgi?", 'custnum' ] - : ''; - }; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => $title, 'name' => 'payments', 'query' => $sql_query, @@ -159,7 +13,7 @@ sub { my $cust_pay = shift; if ( $cust_pay->payby eq 'CARD' ) { - 'Card #'. $cust_pay->payinfo_masked; + 'Card #'. $cust_pay->paymask; } elsif ( $cust_pay->payby eq 'CHEK' ) { 'E-check acct#'. $cust_pay->payinfo; } elsif ( $cust_pay->payby eq 'BILL' ) { @@ -181,12 +35,194 @@ \&FS::UI::Web::cust_fields, ], #'align' => 'lrrrll', - 'align' => 'rrr', + 'align' => 'rrr'.FS::UI::Web::cust_aligns(), 'links' => [ '', '', '', - ( map { $link } FS::UI::Web::cust_header() ), + ( map { $_ ne 'Cust. Status' ? $link : '' } + FS::UI::Web::cust_header() + ), ], + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $title = 'Payment Search Results'; +my( $count_query, $sql_query ); +if ( $cgi->param('magic') ) { + + my @search = (); + my $orderby; + if ( $cgi->param('magic') eq '_date' ) { + + + if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; # $search{'agentnum'} = $1; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; + } + + if ( $cgi->param('payby') ) { + $cgi->param('payby') =~ + /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ + or die "illegal payby ". $cgi->param('payby'); + push @search, "cust_pay.payby = '$1'"; + if ( $3 ) { + + my $cardtype = $3; + + my $search; + if ( $cardtype eq 'VisaMC' ) { + #avoid posix regexes for portability + $search = + " ( ( substring(cust_pay.payinfo from 1 for 1) = '4' ". + " AND substring(cust_pay.payinfo from 1 for 4) != '4936' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49030[2-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49033[5-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49110[1-2]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49117[4-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49118[1-2]' ". + " )". + " OR substring(cust_pay.payinfo from 1 for 2) = '51' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '52' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '53' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '55' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US + " ) "; + } elsif ( $cardtype eq 'Amex' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 2 ) = '34' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ". + " ) "; + } elsif ( $cardtype eq 'Discover' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 4 ) = '6011' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '65' ". + " OR substring(cust_pay.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN + " ) "; + } elsif ( $cardtype eq 'Maestro' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 2 ) = '63' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '67' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ". + " OR substring(cust_pay.payinfo from 1 for 4 ) = '4936' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49030[2-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49033[5-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49110[1-2]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49117[4-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49118[1-2]' ". + " ) "; + } else { + die "unknown card type $cardtype"; + } + + my $masksearch = $search; + $masksearch =~ s/cust_pay\.payinfo/cust_pay.paymask/gi; + + push @search, + "( $search OR ( cust_pay.paymask IS NOT NULL AND $masksearch ) )"; + + } + } + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + push @search, "_date >= $beginning ", + "_date <= $ending"; + + push @search, FS::UI::Web::parse_lt_gt($cgi, 'paid' ); + + $orderby = '_date'; + + } elsif ( $cgi->param('magic') eq 'paybatch' ) { + + $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ + or die "illegal paybatch: ". $cgi->param('paybatch'); + + push @search, "paybatch = '$1'"; + + $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + + } else { + die "unknown search magic: ". $cgi->param('magic'); + } + + #here is the agent virtualization + push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + + my $search = ' WHERE '. join(' AND ', @search); + + $count_query = "SELECT COUNT(*), SUM(paid) ". + "FROM cust_pay LEFT JOIN cust_main USING ( custnum )". + $search; + + $sql_query = { + 'table' => 'cust_pay', + 'select' => join(', ', + 'cust_pay.*', + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$search ORDER BY $orderby", + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + }; + +} else { + + $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; + my $payinfo = $1; + + $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; + my $payby = $1; + + $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay". + " WHERE payinfo = '$payinfo' AND payby = '$payby'". + " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql; + + $sql_query = { + 'table' => 'cust_pay', + 'hashref' => { 'payinfo' => $payinfo, + 'payby' => $payby }, + 'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql. + " ORDER BY _date", + }; + +} + +my $link = sub { + my $cust_pay = shift; + $cust_pay->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +</%init> 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 @@ -<HTML> - <HEAD> - <TITLE>Check # Search</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Check # Search - </FONT> - <BR><BR> - <FORM ACTION="cust_pay.cgi" METHOD="GET"> - Search for <B>check #</B>: - <INPUT TYPE="text" NAME="payinfo"> - <INPUT TYPE="hidden" NAME="payby" VALUE="BILL"> - <BR><BR><INPUT TYPE="submit" VALUE="Search"> - </FORM> - </BODY> -</HTML> - diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi new file mode 100755 index 000000000..e378ffae7 --- /dev/null +++ b/httemplate/search/cust_pay_batch.cgi @@ -0,0 +1,190 @@ +<% include('elements/search.html', + 'title' => 'Batch payment details', + 'name' => 'batch details', + 'menubar' => ['Main Menu' => $p,], + 'query' => $sql_query, + 'count_query' => $count_query, + 'html_init' => $pay_batch ? $html_init : '', + 'header' => [ '#', + 'Inv #', + 'Customer', + 'Customer', + 'Card Name', + 'Card', + 'Exp', + 'Amount', + 'Status', + ], + 'fields' => [ sub { + shift->[0]; + }, + sub { + shift->[1]; + }, + sub { + shift->[2]; + }, + sub { + my $cpb = shift; + $cpb->[3] . ', ' . $cpb->[4]; + }, + sub { + shift->[5]; + }, + sub { + my $cardnum = shift->[6]; + 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); + }, + sub { + shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my( $mon, $year ) = ( $2, $1 ); + $mon = "0$mon" if length($mon) == 1; + "$mon/$year"; + }, + sub { + shift->[8]; + }, + sub { + shift->[9]; + }, + ], + 'align' => 'lllllllrl', + 'links' => [ ['', sub{'#';}], + ["${p}view/cust_bill.cgi?", sub{shift->[1];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ], + ) +%> +<%init> + +my $conf = new FS::Conf; + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports') + || $FS::CurrentUser::CurrentUser->access_right('Process batches') + || ( $cgi->param('custnum') + && $conf->exists('batch-enable') + #&& $FS::CurrentUser::CurrentUser->access_right('View customer batched payments') + ); + +my( $count_query, $sql_query ); +my $hashref = {}; +my @search = (); +my $orderby = 'paybatchnum'; + +my( $pay_batch, $batchnum ) = ( '', ''); +if ( $cgi->param('batchnum') && $cgi->param('batchnum') =~ /^(\d+)$/ ) { + push @search, "batchnum = $1"; + $pay_batch = qsearchs('pay_batch', { 'batchnum' => $1 } ); + die "Batch $1 not found!" unless $pay_batch; + $batchnum = $pay_batch->batchnum; +} + +if ( $cgi->param('custnum') && $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @search, "custnum = $1"; +} + +if ( $cgi->param('status') && $cgi->param('status') =~ /^(\w)$/ ) { + push @search, "pay_batch.status = '$1'"; +} + +if ( $cgi->param('payby') ) { + $cgi->param('payby') =~ /^(CARD|CHEK)$/ + or die "illegal payby " . $cgi->param('payby'); + + push @search, "cust_pay_batch.payby = '$1'"; +} + +if ( not $cgi->param('dcln') ) { + push @search, "cpb.status IS DISTINCT FROM 'Approved'"; +} + +my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +unless ($pay_batch){ + push @search, "pay_batch.upload >= $beginning" if ($beginning); + push @search, "pay_batch.upload <= $ending" if ($ending < 4294967295);#2^32-1 + $orderby = "pay_batch.download,paybatchnum"; +} + +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; +my $search = ' WHERE ' . join(' AND ', @search); + +$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' . + 'LEFT JOIN cust_main USING ( custnum ) ' . + 'LEFT JOIN pay_batch USING ( batchnum )' . + $search; + +#grr +$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," . + "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " . + "FROM cust_pay_batch AS cpb " . + 'LEFT JOIN cust_main USING ( custnum ) ' . + 'LEFT JOIN pay_batch USING ( batchnum ) ' . + "$search ORDER BY $orderby"; + +my $html_init = ''; +if ( $pay_batch ) { + my $fixed = $conf->config('batch-fixed_format-'. $pay_batch->payby); + if ( + $pay_batch->status eq 'O' + || ( $pay_batch->status eq 'I' + && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') + ) + ) { + $html_init .= qq!<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">!; + if ( $fixed ) { + $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; + } else { + $html_init .= qq!Download batch in format <SELECT NAME="format">!. + qq!<OPTION VALUE="">Default batch mode</OPTION>!. + qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>!. + qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!. + qq!<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>!. + qq!<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>!. + qq!</SELECT>!; + } + $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR>!; + } + + if ( + $pay_batch->status eq 'I' + || ( $pay_batch->status eq 'R' + && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') + ) + ) { + $html_init .= qq!<FORM ACTION="$p/misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">!. + qq!Upload results<BR>!. + qq!Filename <INPUT TYPE="file" NAME="batch_results"><BR>!; + if ( $fixed ) { + $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; + } else { + $html_init .= qq!Format <SELECT NAME="format">!. + qq!<OPTION VALUE="">Default batch mode</OPTION>!. + qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>!. + qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!. + qq!<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>!. + qq!<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>!. + qq!</SELECT><BR>!; + } + $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum">!; + $html_init .= '<INPUT TYPE="submit" VALUE="Upload"></FORM><BR>'; + } + +} + +if ($pay_batch) { + my $sth = dbh->prepare($count_query) or die dbh->errstr. "doing $count_query"; + $sth->execute or die "Error executing \"$count_query\": ". $sth->errstr; + my $cards = $sth->fetchrow_arrayref->[0]; + + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". $batchnum; + $sth = dbh->prepare($st) or die dbh->errstr. "doing $st"; + $sth->execute or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + + $html_init .= "$cards credit card payments batched<BR>\$" . + sprintf("%.2f", $total) ." total in batch<BR>"; +} + +</%init> diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 5da4d82fb..84b083a3d 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -1,149 +1,12 @@ -<% - -my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); - -my($query) = $cgi->keywords; - -my $orderby; -my @where; -my $cjoin = ''; - -if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { - $cjoin = "LEFT JOIN cust_main USING ( custnum )"; - push @where, - "agentnum = $1"; -} - -if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { - $orderby = 'ORDER BY bill'; - - my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); - push @where, - "bill >= $beginning ", - "bill <= $ending", - '( cancel IS NULL OR cancel = 0 )'; - -} else { - - if ( $cgi->param('magic') && - $cgi->param('magic') =~ /^(active|suspended|cancell?ed)$/ - ) { - - $orderby = 'ORDER BY pkgnum'; - - if ( $cgi->param('magic') eq 'active' ) { - - #push @where, - # '( susp IS NULL OR susp = 0 )', - # '( cancel IS NULL OR cancel = 0)'; - push @where, FS::cust_pkg->active_sql(); - - } elsif ( $cgi->param('magic') eq 'suspended' ) { - - push @where, - 'susp IS NOT NULL', - 'susp != 0', - '( cancel IS NULL OR cancel = 0)'; - - } elsif ( $cgi->param('magic') =~ /^cancell?ed$/ ) { - - push @where, - 'cancel IS NOT NULL', - 'cancel != 0'; - - } else { - die "guru meditation #420"; - } - - if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) { - push @where, "pkgpart = $1"; - } - - } elsif ( $query eq 'pkgnum' ) { - - $orderby = 'ORDER BY pkgnum'; - - } elsif ( $query eq 'APKG_pkgnum' ) { - - $orderby = 'ORDER BY pkgnum'; - - push @where, '0 < ( - SELECT count(*) FROM pkg_svc - WHERE pkg_svc.pkgpart = cust_pkg.pkgpart - AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc - WHERE cust_svc.pkgnum = cust_pkg.pkgnum - AND cust_svc.svcpart = pkg_svc.svcpart - ) - )'; - - } else { - die "Empty or unknown QUERY_STRING!"; - } - -} - -my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; - -my $count_query = "SELECT COUNT(*) FROM cust_pkg $cjoin $extra_sql"; - -my $sql_query = { - 'table' => 'cust_pkg', - 'hashref' => {}, - 'select' => join(', ', - 'cust_pkg.*', - 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ', - #' LEFT JOIN part_pkg USING ( pkgpart ) ' -}; - -my $link = sub { - [ "${p}view/cust_main.cgi?".shift->custnum.'#cust_pkg', 'pkgnum' ]; -}; - -my $clink = sub { - my $cust_pkg = shift; - $cust_pkg->cust_main_custnum - ? [ "${p}view/cust_main.cgi?", 'custnum' ] - : ''; -}; - -#if ( scalar(@cust_pkg) == 1 ) { -# print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum. -# "#cust_pkg". $cust_pkg[0]->pkgnum ); - -# my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); -# my $rowspan = scalar(@cust_svc) || 1; - -# my $n2 = ''; -# foreach my $cust_svc ( @cust_svc ) { -# my($label, $value, $svcdb) = $cust_svc->label; -# my $svcnum = $cust_svc->svcnum; -# my $sview = $p. "view"; -# print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, -# qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; -# $n2="</TR><TR>"; -# } - -sub time_or_blank { - my $column = shift; - return sub { - my $record = shift; - my $value = $record->get($column); #mmm closures - $value ? time2str('%b %d %Y', $value ) : ''; - }; -} - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Package Search Results', 'name' => 'packages', 'query' => $sql_query, 'count_query' => $count_query, - 'redirect' => $link, + #'redirect' => $link, 'header' => [ '#', 'Package', + 'Class', 'Status', 'Freq.', 'Setup', @@ -152,18 +15,25 @@ sub time_or_blank { 'Susp.', 'Expire', 'Cancel', - FS::UI::Web::cust_header(), + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), 'Services', ], 'fields' => [ 'pkgnum', - sub { my $part_pkg = $part_pkg{shift->pkgpart}; - $part_pkg->pkg; # ' - '. $part_pkg->comment; + sub { #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->pkg; # ' - '. $part_pkg->comment; + $_[0]->pkg; # ' - '. $_[0]->comment; }, + 'classname', sub { ucfirst(shift->status); }, sub { #shift->part_pkg->freq_pretty; - my $part_pkg = $part_pkg{shift->pkgpart}; - $part_pkg->freq_pretty; + + #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->freq_pretty; + + FS::part_pkg::freq_pretty(shift); }, #sub { time2str('%b %d %Y', shift->setup); }, @@ -202,6 +72,7 @@ sub time_or_blank { 'color' => [ '', '', + '', sub { shift->statuscolor; }, '', '', @@ -210,12 +81,13 @@ sub time_or_blank { '', '', '', - ( map { '' } FS::UI::Web::cust_header() ), + FS::UI::Web::cust_colors(), '', ], - 'style' => [ '', '', 'b' ], - 'size' => [ '', '', '-1', ], - 'align' => 'rlclrrrrrr', + 'style' => [ '', '', '', 'b', '', '', '', '', '', '', '', + FS::UI::Web::cust_styles() ], + 'size' => [ '', '', '', '-1', ], + 'align' => 'rllclrrrrrr'. FS::UI::Web::cust_aligns(). 'r', 'links' => [ $link, $link, @@ -227,8 +99,241 @@ sub time_or_blank { '', '', '', - ( map { $clink } FS::UI::Web::cust_header() ), + '', + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), '', ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); + +# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); + +my($query) = $cgi->keywords; + +my @where = (); + +## +# parse agent +## + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { + push @where, + "agentnum = $1"; +} + +## +# parse status +## + +if ( $cgi->param('magic') eq 'active' + || $cgi->param('status') eq 'active' ) { + + push @where, FS::cust_pkg->active_sql(); + +} elsif ( $cgi->param('magic') eq 'inactive' + || $cgi->param('status') eq 'inactive' ) { + + push @where, FS::cust_pkg->inactive_sql(); + + +} elsif ( $cgi->param('magic') eq 'suspended' + || $cgi->param('status') eq 'suspended' ) { + + push @where, FS::cust_pkg->suspended_sql(); + +} elsif ( $cgi->param('magic') =~ /^cancell?ed$/ + || $cgi->param('status') =~ /^cancell?ed$/ ) { + + push @where, FS::cust_pkg->cancelled_sql(); + +} elsif ( $cgi->param('status') =~ /^(one-time charge|inactive)$/ ) { + + push @where, FS::cust_pkg->inactive_sql(); + +} + +### +# parse package class +### + +#false lazinessish w/graph/cust_bill_pkg.cgi +my $classnum = 0; +my @pkg_class = (); +if ( exists($cgi->Vars->{'classnum'}) + && $cgi->param('classnum') =~ /^(\d*)$/ + ) +{ + $classnum = $1; + if ( $classnum ) { #a specific class + push @where, "classnum = $classnum"; + + #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); + #die "classnum $classnum not found!" unless $pkg_class[0]; + #$title .= $pkg_class[0]->classname.' '; + + } elsif ( $classnum eq '' ) { #the empty class + + push @where, "classnum IS NULL"; + #$title .= 'Empty class '; + #@pkg_class = ( '(empty class)' ); + } elsif ( $classnum eq '0' ) { + #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); + #push @pkg_class, '(empty class)'; + } else { + die "illegal classnum"; + } +} +#eslaf + +### +# parse part_pkg +### + +my $pkgpart = join (' OR pkgpart=', + grep {$_} map { /^(\d+)$/; } ($cgi->param('pkgpart'))); +push @where, '(pkgpart=' . $pkgpart . ')' if $pkgpart; + +### +# parse dates +### + +my $orderby = ''; + +#false laziness w/report_cust_pkg.html +my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, +); + +foreach my $field (qw( setup last_bill bill susp expire cancel )) { + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295 + or $disable{$cgi->param('status')}->{$field}; + + push @where, + "$field IS NOT NULL", + "$field >= $beginning", + "$field <= $ending"; + + $orderby ||= "ORDER BY $field"; + +} + +$orderby ||= 'ORDER BY bill'; + +### +# parse magic, legacy, etc. +### + +if ( $cgi->param('magic') && + $cgi->param('magic') =~ /^(active|inactive|suspended|cancell?ed)$/ +) { + + $orderby = 'ORDER BY pkgnum'; + + if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) { + push @where, "pkgpart = $1"; + } + +} elsif ( $query eq 'pkgnum' ) { + + $orderby = 'ORDER BY pkgnum'; + +} elsif ( $query eq 'APKG_pkgnum' ) { + + $orderby = 'ORDER BY pkgnum'; + + push @where, '0 < ( + SELECT count(*) FROM pkg_svc + WHERE pkg_svc.pkgpart = cust_pkg.pkgpart + AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc + WHERE cust_svc.pkgnum = cust_pkg.pkgnum + AND cust_svc.svcpart = pkg_svc.svcpart + ) + )'; + +} + +## +# setup queries, links, subs, etc. for the search +## + +# here is the agent virtualization +push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; + +my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '. + 'LEFT JOIN part_pkg USING ( pkgpart ) '. + 'LEFT JOIN pkg_class USING ( classnum ) '; + +my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql"; + +my $sql_query = { + 'table' => 'cust_pkg', + 'hashref' => {}, + 'select' => join(', ', + 'cust_pkg.*', + ( map "part_pkg.$_", qw( pkg freq ) ), + 'pkg_class.classname', + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields( + $cgi->param('cust_fields') + ), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = sub { + [ "${p}view/cust_main.cgi?".shift->custnum.'#cust_pkg', 'pkgnum' ]; +}; + +my $clink = sub { + my $cust_pkg = shift; + $cust_pkg->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +#if ( scalar(@cust_pkg) == 1 ) { +# print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum. +# "#cust_pkg". $cust_pkg[0]->pkgnum ); + +# my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); +# my $rowspan = scalar(@cust_svc) || 1; + +# my $n2 = ''; +# foreach my $cust_svc ( @cust_svc ) { +# my($label, $value, $svcdb) = $cust_svc->label; +# my $svcnum = $cust_svc->svcnum; +# my $sview = $p. "view"; +# print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, +# qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; +# $n2="</TR><TR>"; +# } + +sub time_or_blank { + my $column = shift; + return sub { + my $record = shift; + my $value = $record->get($column); #mmm closures + $value ? time2str('%b %d %Y', $value ) : ''; + }; +} + +</%init> diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi deleted file mode 100755 index 412c3f79d..000000000 --- a/httemplate/search/cust_pkg_report.cgi +++ /dev/null @@ -1,23 +0,0 @@ -<HTML> - <HEAD> - <TITLE>Packages</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Packages</H1> - <FORM ACTION="cust_pkg.cgi" METHOD="GET"> - <INPUT TYPE="hidden" NAME="magic" VALUE="bill"> - Return packages with next bill date:<BR><BR> - <TABLE> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - ) - %> - </TABLE> - <BR><INPUT TYPE="submit" VALUE="Get Report"> - - </FORM> - - </BODY> -</HTML> - diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html new file mode 100644 index 000000000..6369b202e --- /dev/null +++ b/httemplate/search/cust_svc.html @@ -0,0 +1,138 @@ +<% include( 'elements/search.html', + 'title' => 'Service search results', + 'name' => 'services', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + # package? + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + sub { + #$_[0]->svc. ': '. $_[0]->label; + my($label, $value, $svcdb) = $_[0]->label; + "$label: $value"; + }, + # package? + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + # package? + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rl'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +my @extra_sql = (); +my $orderby = 'ORDER BY svcnum'; #has to be ordered by something + #for pagination to work +if ( length( $cgi->param('search_svc') ) ) { + + my $string = $cgi->param('search_svc'); + $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace + + # implement fuzzy searching in subclasses too at some point? + # service searching maybe shouldn't be fuzzy... + + push @extra_sql, + ' ( '. join(' OR ', + map { my $table = $_; + my $search_sql = "FS::$table"->search_sql($string); + " ( svcdb = '$table' + AND 0 < ( SELECT COUNT(*) FROM $table + WHERE $table.svcnum = cust_svc.svcnum + AND $search_sql + ) + ) "; + } + FS::part_svc->svc_tables + ). ' ) '; + +} elsif ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb"; + push @extra_sql, "svcdb = '$1'"; + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + + push @extra_sql, "svcpart = $1"; + +} else { + eidiot("No search term specified"); +} + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = ' WHERE '. join(' AND ', @extra_sql ); + +my $sql_query = { + 'select' => join(', ', + 'cust_svc.*', + 'part_svc.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'table' => 'cust_svc', + 'addl_from' => $addl_from, + 'hashref' => {}, + 'extra_sql' => "$extra_sql $orderby", +}; + +my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql"; + +my $link = sub { + my $cust_svc = shift; + my $url = FS::UI::Web::svc_url( + 'm' => $m, + 'action' => 'view', + #'part_svc' => $cust_svc->part_svc, + 'svcdb' => $cust_svc->svcdb, #we have it from the joined search + #'svc' => $cust_svc, #redundant + 'query' => '', + ); + [ $url, 'svcnum' ]; +}; + +my $link_cust = sub { + my $cust_svc = shift; + if ( $cust_svc->custnum ) { + [ "${p}view/cust_main.cgi?", 'custnum' ]; + } else { + ''; + } +}; + +</%init> diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi new file mode 100644 index 000000000..604502d6f --- /dev/null +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -0,0 +1,182 @@ +<% include( 'elements/search.html', + 'title' => 'Tax exemptions', + 'name' => 'tax exemptions', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Month', + 'Amount', + 'Line item', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'exemptpkgnum', + sub { $_[0]->month. '/'. $_[0]->year; }, + sub { $money_char. $_[0]->amount; }, + + sub { + $_[0]->billpkgnum. ': '. + ( $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + ). + ' ('. + ( $_[0]->setup > 0 + ? $money_char. $_[0]->setup. ' setup' + : '' + ). + ( $_[0]->setup > 0 && $_[0]->recur > 0 + ? ' / ' + : '' + ). + ( $_[0]->recur > 0 + ? $money_char. $_[0]->recur. ' recur' + : '' + ). + ')'; + }, + + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + + '', + $ilink, + $ilink, + + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rrrlrc'.FS::UI::Web::cust_aligns(), # 'rlrrrc', + 'color' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%once> + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; + +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $join = " + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust + $join_pkg +"; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions'); + +my @where = (); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +if ( $beginning || $ending ) { + push @where, "_date >= $beginning", + "_date <= $ending"; + #"payby != 'COMP'; +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "agentnum = $1"; +} + +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.custnum = $1"; +} + +if ( $cgi->param('out') ) { + + push @where, " + 0 = ( + SELECT COUNT(*) FROM cust_main_county AS county_out + WHERE ( county_out.county = cust_main.county + OR ( county_out.county IS NULL AND cust_main.county = '' ) + OR ( county_out.county = '' AND cust_main.county IS NULL) + OR ( county_out.county IS NULL AND cust_main.county IS NULL) + ) + AND ( county_out.state = cust_main.state + OR ( county_out.state IS NULL AND cust_main.state = '' ) + OR ( county_out.state = '' AND cust_main.state IS NULL ) + OR ( county_out.state IS NULL AND cust_main.state IS NULL ) + ) + AND county_out.country = cust_main.country + AND county_out.tax > 0 + ) + "; + +} elsif ( $cgi->param('country' ) ) { + + my $county = dbh->quote( $cgi->param('county') ); + my $state = dbh->quote( $cgi->param('state') ); + my $country = dbh->quote( $cgi->param('country') ); + push @where, "( county = $county OR $county = '' )", + "( state = $state OR $state = '' )", + " country = $country"; + push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') ) + if $cgi->param('taxclass'); + +} + +my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; + +my $count_query = "SELECT COUNT(*), SUM(amount)". + " FROM cust_tax_exempt_pkg $join $where"; + +my $query = { + 'table' => 'cust_tax_exempt_pkg', + 'addl_from' => $join, + 'hashref' => {}, + 'select' => join(', ', + 'cust_tax_exempt_pkg.*', + 'cust_bill_pkg.*', + 'cust_bill.*', + 'part_pkg.pkg', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $where, +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +</%init> diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index d19fb4acd..7d5e58a3b 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -1,392 +1,658 @@ -<% - - my(%opt) = @_; - #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n"; - - my %align = ( - 'l' => 'left', - 'r' => 'right', - 'c' => 'center', - ' ' => '', - '.' => '', - ); - $opt{align} = [ map $align{$_}, split(//, $opt{align}) ], - unless !$opt{align} || ref($opt{align}); - - my $type = ''; - my $limit = ''; - my($maxrecords, $total, $offset, $count_arrayref); - - if ( $cgi->param('_type') =~ /^(csv|\w*\.xls)$/ ) { - - $type = $1; +% # options example... +% # (everything not commented required is optional) +% # +% # # basic options, required +% # 'title' => 'Page title', +% # +% # 'name_singular' => 'item', #singular name for the records returned +% # #OR# # (preferred, will be pluralized automatically) +% # 'name' => 'items', #plural name for the records returned +% # # (deprecated, will be singularlized +% # # simplisticly) +% # +% # # some HTML callbacks... +% # 'menubar' => '', #menubar arrayref +% # 'html_init' => '', #after the header/menubar and before the pager +% # 'html_form' => '', #after the pager, right before the results +% # # (only shown if there are results) +% # # (use this for any form-opening tag rather than +% # # html_init, to avoid a nested form) +% # 'html_foot' => '', #at the bottom +% # 'html_posttotal' => '', #at the bottom +% # # (these three can be strings or coderefs) +% # +% # +% # #literal SQL query string or qsearch hashref, required +% # 'query' => { +% # 'table' => 'tablename', +% # #everything else is optional... +% # 'hashref' => { 'field' => 'value', +% # 'field' => { 'op' => '<', +% # 'value' => '54', +% # }, +% # }, +% # 'select' => '*', +% # 'addl_from' => '', #'LEFT JOIN othertable USING ( key )', +% # 'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff', +% # +% # +% # }, +% # # "select * from tablename"; +% # +% # #required unless 'query' is an SQL query string (shouldn't be...) +% # 'count_query' => 'SELECT COUNT(*) FROM tablename', +% # +% # 'count_addl' => [], #additional count fields listref of sprintf strings +% # # [ $money_char.'%.2f total paid', ], +% # +% # #listref of column labels, <TH> +% # #required unless 'query' is an SQL query string +% # # (if not specified the database column names will be used) +% # 'header' => [ '#', 'Item' ], +% # +% # 'disable_download' => '', # set true to hide the CSV/Excel download links +% # 'disable_nonefound' => '', # set true to disable the "No matching Xs found" +% # # message +% # +% # #listref - each item is a literal column name (or method) or coderef +% # #if not specified all columns will be shown +% # 'fields' => [ +% # 'column', +% # sub { my $row = shift; $row->column; }, +% # ], +% # +% # #listref of column footers +% # 'footer' => [], +% # +% # #listref - each item is the empty string, or a listref of ... +% # 'links' => +% # +% # +% # 'align' => 'lrc.', #one letter for each column, left/right/center/none +% # # can also pass a listref with full values: +% # # [ 'left', 'right', 'center', '' ] +% # +% # #listrefs... +% # #currently only HTML, maybe eventually Excel too +% # 'color' => [], +% # 'size' => [], +% # 'style' => [], +% # +% # #redirect if there's only one item... +% # # listref of URL base and column name (or method) +% # # or a coderef that returns the same +% # 'redirect' => +% # +% # #set to 1 (or column position for "disabled" status col) to enable +% # #"show disabled/hide disabled" links +% # #(can't be used with a literal query) +% # 'disableable' => 1, +% +% my $DEBUG = 0; +% +% my(%opt) = @_; +% #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n"; +% +% my %align = ( +% 'l' => 'left', +% 'r' => 'right', +% 'c' => 'center', +% ' ' => '', +% '.' => '', +% ); +% $opt{align} = [ map $align{$_}, split(//, $opt{align}) ], +% unless !$opt{align} || ref($opt{align}); +% +% my $type = ''; +% my $limit = ''; +% my($confmax, $maxrecords, $total, $offset, $count_arrayref); +% +% if ( $cgi->param('_type') =~ /^(csv|\w*\.xls)$/ ) { +% +% $type = $1; +% +% } else { #setup some pagination things if we're in html mode +% +% unless (exists($opt{count_query}) && length($opt{count_query})) { +% ( $opt{count_query} = $opt{query} ) =~ +% s/^\s*SELECT\s*(.*?)\s+FROM\s/SELECT COUNT(*) FROM /i; #silly vim:/ +% } +% +% if ( $opt{disableable} && ! $cgi->param('showdisabled') ) { +% $opt{count_query} .= +% ( ( $opt{count_query} =~ /WHERE/i ) ? ' AND ' : ' WHERE ' ). +% "( disabled = '' OR disabled IS NULL )"; +% } +% +% my $conf = new FS::Conf; +% $confmax = $conf->config('maxsearchrecordsperpage'); +% if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) { +% $maxrecords = $1; +% } else { +% $maxrecords ||= $confmax; +% } +% +% $limit = $maxrecords ? "LIMIT $maxrecords" : ''; +% +% $offset = $cgi->param('offset') || 0; +% $limit .= " OFFSET $offset" if $offset; +% +% my $count_sth = dbh->prepare($opt{'count_query'}) +% or die "Error preparing $opt{'count_query'}: ". dbh->errstr; +% $count_sth->execute +% or die "Error executing $opt{'count_query'}: ". $count_sth->errstr; +% $count_arrayref = $count_sth->fetchrow_arrayref; +% $total = $count_arrayref->[0]; +% +% } +% +% #disableable handling +% my $posttotal = ''; +% if ( $opt{disableable} ) { +% +% my $name= $opt{'name_singular'} ? PL($opt{'name_singular'}) : $opt{'name'}; +% +% if ( $cgi->param('showdisabled') ) { +% $cgi->param('showdisabled', 0); +% $posttotal= '( <a href="'. $cgi->self_url. '">'. +% "hide disabled $name</a> )"; +% $cgi->param('showdisabled', 1); +% } else { +% $cgi->param('showdisabled', 1); +% $posttotal= '( <a href="'. $cgi->self_url. '">'. +% "show disabled $name</a> )"; +% $cgi->param('showdisabled', 0); +% } +% +% if ( $cgi->param('showdisabled') ) { +% +% my $offset = $opt{disableable}; +% +% splice @{ $opt{header} }, $offset, 0, 'Status'; +% +% splice @{ $opt{fields} }, $offset, 0, +% sub { shift->disabled ? 'DISABLED' : 'Active' }; +% +% if ( $opt{links} && scalar( @{ $opt{links} } ) ) { +% splice @{ $opt{links} }, $offset, 0, ''; +% } +% +% if ( $opt{align} && scalar( @{ $opt{align} } ) ) { +% splice @{ $opt{align} }, $offset, 0, 'center'; +% } +% +% unless ( $opt{color} && scalar( @{ $opt{color} } ) ) { +% #$opt{color} = [ map { '000000'; } @{$opt{header}} ]; +% $opt{color} = [ map { ''; } @{$opt{header}} ]; +% } +% splice @{ $opt{color} }, $offset, 0, +% sub { shift->disabled ? 'FF0000' : '00CC00'; }; +% +% if ( $opt{size} && scalar( @{ $opt{size} } ) ) { +% splice @{ $opt{size} }, $offset, 0, ''; +% } +% +% unless ( $opt{style} && scalar( @{ $opt{style} } ) ) { +% $opt{style} = [ map { ''; } @{$opt{header}} ]; +% } +% splice @{ $opt{style} }, $offset, 0, 'b'; +% +% } +% +% } +% +% # run the query +% +% my $header = $opt{header}; +% my $rows; +% if ( ref($opt{query}) ) { +% +% if ( $opt{disableable} && ! $cgi->param('showdisabled') ) { +% #%search = ( 'disabled' => '' ); +% $opt{'query'}->{'hashref'}->{'disabled'} = ''; +% $opt{'query'}->{'extra_sql'} =~ s/^\s*WHERE/ AND/i; +% } +% +% #eval "use FS::$opt{'query'};"; +% $rows = [ qsearch( +% $opt{'query'}->{'table'}, +% $opt{'query'}->{'hashref'} || {}, +% $opt{'query'}->{'select'}, +% $opt{'query'}->{'extra_sql'}. " $limit", +% '', +% (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : '') +% ) ]; +% +% } else { +% +% my $sth = dbh->prepare("$opt{'query'} $limit") +% or die "Error preparing $opt{'query'}: ". dbh->errstr; +% $sth->execute +% or die "Error executing $opt{'query'}: ". $sth->errstr; +% +% #can get # of rows without fetching them all? +% $rows = $sth->fetchall_arrayref; +% +% $header ||= $sth->{NAME}; +% +% } +% +% warn scalar(@$rows). ' rows returned from '. +% ( ref($opt{'query'}) ? 'qsearch query' : 'literal SQL query' ) +% if $DEBUG || $opt{'debug'}; +% +% # display the results - csv, xls or html +% +% if ( $type eq 'csv' ) { +% +% #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes +% http_header('Content-Type' => 'text/plain' ); +% +% my $csv = new Text::CSV_XS { 'always_quote' => 1, +% 'eol' => "\n", #"\015\012", #"\012" +% }; +% +% $csv->combine(@$header); #or die $csv->status; +% +<% $csv->string %> +% +% +% foreach my $row ( @$rows ) { +% +% if ( $opt{'fields'} ) { +% +% my @line = (); +% +% foreach my $field ( @{$opt{'fields'}} ) { +% if ( ref($field) eq 'CODE' ) { +% push @line, map { +% ref($_) eq 'ARRAY' +% ? '(N/A)' #unimplemented +% : $_; +% } +% &{$field}($row); +% } else { +% push @line, $row->$field(); +% } +% } +% +% $csv->combine(@line); #or die $csv->status; +% +% } else { +% $csv->combine(@$row); #or die $csv->status; +% } +% +% +<% $csv->string %> +% +% +% } +% +% #} elsif ( $type eq 'excel' ) { +% } elsif ( $type =~ /\.xls$/ ) { +% +% #http_header('Content-Type' => 'application/excel' ); #eww +% http_header('Content-Type' => 'application/vnd.ms-excel' ); +% #http_header('Content-Type' => 'application/msexcel' ); #alas +% +% my $data = ''; +% my $XLS = new IO::Scalar \$data; +% my $workbook = Spreadsheet::WriteExcel->new($XLS) +% or die "Error opening .xls file: $!"; +% +% my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); +% +% my($r,$c) = (0,0); +% +% $worksheet->write($r, $c++, $_) foreach @$header; +% +% foreach my $row ( @$rows ) { +% $r++; +% $c = 0; +% +% if ( $opt{'fields'} ) { +% +% #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% +% foreach my $field ( @{$opt{'fields'}} ) { +% #my $align = $aligns ? shift @$aligns : ''; +% #$align = " ALIGN=$align" if $align; +% #my $a = ''; +% #if ( $links ) { +% # my $link = shift @$links; +% # $link = &{$link}($row) if ref($link) eq 'CODE'; +% # if ( $link ) { +% # my( $url, $method ) = @{$link}; +% # if ( ref($method) eq 'CODE' ) { +% # $a = $url. &{$method}($row); +% # } else { +% # $a = $url. $row->$method(); +% # } +% # $a = qq(<A HREF="$a">); +% # } +% #} +% if ( ref($field) eq 'CODE' ) { +% foreach my $value ( &{$field}($row) ) { +% if ( ref($value) eq 'ARRAY' ) { +% $worksheet->write($r, $c++, '(N/A)' ); #unimplemented +% } else { +% $worksheet->write($r, $c++, $value ); +% } +% } +% } else { +% $worksheet->write($r, $c++, $row->$field() ); +% } +% } +% +% } else { +% $worksheet->write($r, $c++, $_) foreach @$row; +% } +% +% } +% +% $workbook->close();# or die "Error creating .xls file: $!"; +% +% http_header('Content-Length' => length($data) ); +% +<% $data %> +% +% +% } else { # regular HTML +% +% if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 ) { +% my $redirect = $opt{'redirect'}; +% $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE'; +% my( $url, $method ) = @$redirect; +% redirect( $url. $rows->[0]->$method() ); +% } else { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = PL($opt{'name_singular'}); +% } +% ( my $xlsname = $opt{'name'} ) =~ s/\W//g; +% if ( $total == 1 ) { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = $opt{'name_singular'} +% } else { +% #$opt{'name'} =~ s/s$// if $total == 1; +% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1; +% } +% } +% +% my @menubar = (); +% if ( $opt{'menubar'} ) { +% @menubar = @{ $opt{'menubar'} }; +% #} else { +% # @menubar = ( 'Main menu' => $p ); +% } + + <% include( '/elements/header.html', $opt{'title'}, + include( '/elements/menubar.html', @menubar ) + ) + %> + <% defined($opt{'html_init'}) + ? ( ref($opt{'html_init'}) + ? &{$opt{'html_init'}}() + : $opt{'html_init'} + ) + : '' + %> +% +% unless ( $total ) { +% unless ( $opt{'disable_nonefound'} ) { - } else { #setup some pagination things if we're in html mode + No matching <% $opt{'name'} %> found.<BR> +% } +% } else { - unless (exists($opt{'count_query'}) && length($opt{'count_query'})) { - ( $opt{'count_query'} = $opt{'query'} ) =~ - s/^\s*SELECT\s*(.*?)\s+FROM\s/SELECT COUNT(*) FROM /i; - } + <TABLE> + <TR> - my $conf = new FS::Conf; - $maxrecords = $conf->config('maxsearchrecordsperpage'); + <TD VALIGN="bottom"> - $limit = $maxrecords ? "LIMIT $maxrecords" : ''; + <FORM> - $offset = $cgi->param('offset') || 0; - $limit .= " OFFSET $offset" if $offset; + <% $total %> total <% $opt{'name'} %> - my $count_sth = dbh->prepare($opt{'count_query'}) - or die "Error preparing $opt{'count_query'}: ". dbh->errstr; - $count_sth->execute - or die "Error executing $opt{'count_query'}: ". $count_sth->errstr; - $count_arrayref = $count_sth->fetchrow_arrayref; - $total = $count_arrayref->[0]; +% if ( $confmax && $total > $confmax ) { +% $cgi->delete('maxrecords'); +% $cgi->param('_dummy', 1); - } +%# ( show <SELECT NAME="maxrecords" onChange="this.form.submit();"> + ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->self_url %>;maxrecords=' + this.options[this.selectedIndex].value;"> - # run the query +% foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) { + <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION> +% } + + </SELECT> per page ) + +% $cgi->param('maxrecords', $maxrecords); +% } + + <% $posttotal %> + + <% defined($opt{'html_posttotal'}) + ? ( ref($opt{'html_posttotal'}) + ? &{$opt{'html_posttotal'}}() + : $opt{'html_posttotal'} + ) + : '' + %> + <BR> + +% if ( $opt{'count_addl'} ) { +% my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { + + <% sprintf( $count, $count_arrayref->[++$n] ) %><BR> + +% } +% } + </FORM> - my $header = $opt{'header'}; - my $rows; - if ( ref($opt{'query'}) ) { - #eval "use FS::$opt{'query'};"; - $rows = [ qsearch( - $opt{'query'}->{'table'}, - $opt{'query'}->{'hashref'} || {}, - $opt{'query'}->{'select'}, - $opt{'query'}->{'extra_sql'}. " $limit", - '', - (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : '') - ) ]; - } else { - my $sth = dbh->prepare("$opt{'query'} $limit") - or die "Error preparing $opt{'query'}: ". dbh->errstr; - $sth->execute - or die "Error executing $opt{'query'}: ". $sth->errstr; - - #can get # of rows without fetching them all? - $rows = $sth->fetchall_arrayref; - - $header ||= $sth->{NAME}; - } - - if ( $type eq 'csv' ) { - - #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes - http_header('Content-Type' => 'text/plain' ); - - my $csv = new Text::CSV_XS { 'always_quote' => 1, - 'eol' => "\n", #"\015\012", #"\012" - }; - - $csv->combine(@$header); #or die $csv->status; - %><%= $csv->string %><% - - foreach my $row ( @$rows ) { - - if ( $opt{'fields'} ) { - - my @line = (); - - foreach my $field ( @{$opt{'fields'}} ) { - if ( ref($field) eq 'CODE' ) { - push @line, map { - ref($_) eq 'ARRAY' - ? '(N/A)' #unimplemented - : $_; - } - &{$field}($row); - } else { - push @line, $row->$field(); - } - } - - $csv->combine(@line); #or die $csv->status; - - } else { - $csv->combine(@$row); #or die $csv->status; - } - - %><%= $csv->string %><% - - } - - #} elsif ( $type eq 'excel' ) { - } elsif ( $type =~ /\.xls$/ ) { - - #http_header('Content-Type' => 'application/excel' ); #eww - http_header('Content-Type' => 'application/vnd.ms-excel' ); - #http_header('Content-Type' => 'application/msexcel' ); #alas - - my $data = ''; - my $XLS = new IO::Scalar \$data; - my $workbook = Spreadsheet::WriteExcel->new($XLS) - or die "Error opening .xls file: $!"; - - my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); - - my($r,$c) = (0,0); - - $worksheet->write($r, $c++, $_) foreach @$header; - - foreach my $row ( @$rows ) { - $r++; - $c = 0; - - if ( $opt{'fields'} ) { - - #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; - #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; - - foreach my $field ( @{$opt{'fields'}} ) { - #my $align = $aligns ? shift @$aligns : ''; - #$align = " ALIGN=$align" if $align; - #my $a = ''; - #if ( $links ) { - # my $link = shift @$links; - # $link = &{$link}($row) if ref($link) eq 'CODE'; - # if ( $link ) { - # my( $url, $method ) = @{$link}; - # if ( ref($method) eq 'CODE' ) { - # $a = $url. &{$method}($row); - # } else { - # $a = $url. $row->$method(); - # } - # $a = qq(<A HREF="$a">); - # } - #} - if ( ref($field) eq 'CODE' ) { - foreach my $value ( &{$field}($row) ) { - if ( ref($value) eq 'ARRAY' ) { - $worksheet->write($r, $c++, '(N/A)' ); #unimplemented - } else { - $worksheet->write($r, $c++, $value ); - } - } - } else { - $worksheet->write($r, $c++, $row->$field() ); - } - } - - } else { - $worksheet->write($r, $c++, $_) foreach @$row; - } - - } - - $workbook->close();# or die "Error creating .xls file: $!"; - - http_header('Content-Length' => length($data) ); - %><%= $data %><% - - } else { # regular HTML - - if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 ) { - my $redirect = $opt{'redirect'}; - $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE'; - my( $url, $method ) = @$redirect; - redirect( $url. $rows->[0]->$method() ); - } else { - ( my $xlsname = $opt{'name'} ) =~ s/\W//g; - $opt{'name'} =~ s/s$// if $total == 1; - - my @menubar = (); - if ( $opt{'menubar'} ) { - @menubar = @{ $opt{'menubar'} }; - } else { - @menubar = ( 'Main menu' => $p ); - } - %> - <%= include( '/elements/header.html', $opt{'title'}, - include( '/elements/menubar.html', @menubar ) - ) - %> - <%= defined($opt{'html_init'}) ? $opt{'html_init'} : '' %> - <% my $pager = include ( '/elements/pager.html', - 'offset' => $offset, - 'num_rows' => scalar(@$rows), - 'total' => $total, - 'maxrecords' => $maxrecords, - ); - %> - <% unless ( $total ) { %> - No matching <%= $opt{'name'} %> found.<BR> - <% } else { %> - - <TABLE> - <TR> - <TD VALIGN="bottom"> - <%= $total %> total <%= $opt{'name'} %><BR> - <% if ( $opt{'count_addl'} ) { %> - <% my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { %> - <%= sprintf( $count, $count_arrayref->[++$n] ) %><BR> - <% } %> - <% } %> - </TD> - <TD ALIGN="right"> - <% $cgi->param('_type', "$xlsname.xls" ); %> - Download full results<BR> - as <A HREF="<%= $cgi->self_url %>">Excel spreadsheet</A><BR> - <% $cgi->param('_type', 'csv'); %> - as <A HREF="<%= $cgi->self_url %>">CSV file</A> </TD> + +% unless ( $opt{'disable_download'} ) { + + <TD ALIGN="right"> +% $cgi->param('_type', "$xlsname.xls" ); + + Download full results<BR> + as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR> +% $cgi->param('_type', 'csv'); + + as <A HREF="<% $cgi->self_url %>">CSV file</A> + </TD> +% $cgi->param('_type', "html" ); +% } + </TR> <TR> <TD COLSPAN=2> - <%= $pager %> - <%= include('/elements/table-grid.html') %> + <% my $pager = include ( '/elements/pager.html', + 'offset' => $offset, + 'num_rows' => scalar(@$rows), + 'total' => $total, + 'maxrecords' => $maxrecords, + ) %> + + <% defined($opt{'html_form'}) + ? ( ref($opt{'html_form'}) + ? &{$opt{'html_form'}}() + : $opt{'html_form'} + ) + : '' + %> + + <% include('/elements/table-grid.html') %> <TR> - <% foreach my $header ( @$header ) { %> - <TH CLASS="grid" BGCOLOR="#cccccc"><%= $header %></TH> - <% } %> +% +% foreach my $header ( @$header ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"><% $header %></TH> +% } + </TR> - <% my $bgcolor1 = '#eeeeee'; - my $bgcolor2 = '#ffffff'; - my $bgcolor; - foreach my $row ( @$rows ) { - if ( $bgcolor eq $bgcolor1 ) { - $bgcolor = $bgcolor2; - } else { - $bgcolor = $bgcolor1; - } - %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% foreach my $row ( @$rows ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + <TR> - <% if ( $opt{'fields'} ) { - - my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; - my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; - my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : []; - my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : []; - my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : []; - - foreach my $field ( - - map { - if ( ref($_) eq 'ARRAY' ) { - - my $tableref = $_; - - '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0'. - ' STYLE="border:none">'. - - join('', map { - - my $rowref = $_; - - '<tr>'. - - join('', map { - - my $element = $_; - - '<TD STYLE="border:none"'. - ( $element->{'align'} - ? ' ALIGN="'. $element->{'align'}. '"' - : '' - ). '>'. - ( $element->{'link'} - ? '<A HREF="'. $element->{'link'}.'">' - : '' - ). - $element->{'data'}. - ( $element->{'link'} - ? '</A>' - : '' - ). - '</td>'; - - } @$rowref ). - - '</tr>'; - } @$tableref ). - - '</table>'; - - } else { - $_; - } - } - - map { - if ( ref($_) eq 'CODE' ) { - &{$_}($row); - } else { - $row->$_(); - } - } - @{$opt{'fields'}} - - ) { - - my $align = $aligns ? shift @$aligns : ''; - $align = " ALIGN=$align" if $align; - - my $a = ''; - if ( $links ) { - my $link = shift @$links; - $link = &{$link}($row) if ref($link) eq 'CODE'; - if ( $link ) { - my( $url, $method ) = @{$link}; - if ( ref($method) eq 'CODE' ) { - $a = $url. &{$method}($row); - } else { - $a = $url. $row->$method(); - } - $a = qq(<A HREF="$a">); - } - } - - my $font = ''; - my $color = shift @$colors; - $color = &{$color}($row) if ref($color) eq 'CODE'; - my $size = shift @$sizes; - $size = &{$size}($row) if ref($size) eq 'CODE'; - if ( $color || $size ) { - $font = '<FONT '. - ( $color ? "COLOR=#$color " : '' ). - ( $size ? qq(SIZE="$size" ) : '' ). - '>'; - } - - my($s, $es) = ( '', '' ); - my $style = shift @$styles; - $style = &{$style}($row) if ref($style) eq 'CODE'; - if ( $style ) { - $s = join( '', map "<$_>", split('', $style) ); - $es = join( '', map "</$_>", split('', $style) ); - } - - %> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"<%= $align %>><%= $font %><%= $a %><%= $s %><%= $field %><%= $es %><%= $a ? '</A>' : '' %><%= $font ? '</FONT>' : '' %></TD> - <% } %> - <% } else { %> - <% foreach ( @$row ) { %> - <TD CLASS="grid" BGCOLOR="$bgcolor"><%= $_ %></TD> - <% } %> - <% } %> +% if ( $opt{'fields'} ) { +% +% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : []; +% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : []; +% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : []; +% +% foreach my $field ( +% +% map { +% if ( ref($_) eq 'ARRAY' ) { +% +% my $tableref = $_; +% +% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>'. +% +% join('', map { +% +% my $rowref = $_; +% +% '<tr>'. +% +% join('', map { +% +% my $element = $_; +% +% '<TD'. +% ( $element->{'align'} +% ? ' ALIGN="'. $element->{'align'}. '"' +% : '' +% ). '>'. +% ( $element->{'link'} +% ? '<A HREF="'. $element->{'link'}.'">' +% : '' +% ). +% $element->{'data'}. +% ( $element->{'link'} +% ? '</A>' +% : '' +% ). +% '</td>'; +% +% } @$rowref ). +% +% '</tr>'; +% } @$tableref ). +% +% '</table>'; +% +% } else { +% $_; +% } +% } +% +% map { +% if ( ref($_) eq 'CODE' ) { +% &{$_}($row); +% } else { +% $row->$_(); +% } +% } +% @{$opt{'fields'}} +% +% ) { +% +% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid'; +% +% my $align = $aligns ? shift @$aligns : ''; +% $align = " ALIGN=$align" if $align; +% +% my $a = ''; +% if ( $links ) { +% my $link = shift @$links; +% $link = &{$link}($row) if ref($link) eq 'CODE'; +% if ( $link ) { +% my( $url, $method ) = @{$link}; +% if ( ref($method) eq 'CODE' ) { +% $a = $url. &{$method}($row); +% } else { +% $a = $url. $row->$method(); +% } +% $a = qq(<A HREF="$a">); +% } +% } +% +% my $font = ''; +% my $color = shift @$colors; +% $color = &{$color}($row) if ref($color) eq 'CODE'; +% my $size = shift @$sizes; +% $size = &{$size}($row) if ref($size) eq 'CODE'; +% if ( $color || $size ) { +% $font = '<FONT '. +% ( $color ? "COLOR=#$color " : '' ). +% ( $size ? qq(SIZE="$size" ) : '' ). +% '>'; +% } +% +% my($s, $es) = ( '', '' ); +% my $style = shift @$styles; +% $style = &{$style}($row) if ref($style) eq 'CODE'; +% if ( $style ) { +% $s = join( '', map "<$_>", split('', $style) ); +% $es = join( '', map "</$_>", split('', $style) ); +% } +% +% + + <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>"<% $align %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD> +% } +% } else { +% foreach ( @$row ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD> +% } +% } + </TR> - <% } %> +% } +% if ( $opt{'footer'} ) { - <% if ( $opt{'footer'} ) { %> <TR> - <% foreach my $footer ( @{ $opt{'footer'} } ) { %> - <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><%= $footer %></i></TH> - <% } %> +% foreach my $footer ( @{ $opt{'footer'} } ) { + + <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TH> +% } + </TR> - <% } %> +% } + </TABLE> - <%= $pager %> + <% $pager %> </TD> </TR> </TABLE> - - <% } %> - </BODY> - </HTML> - <% } %> -<% } %> +% } + + <% defined($opt{'html_foot'}) + ? ( ref($opt{'html_foot'}) + ? &{$opt{'html_foot'}}() + : $opt{'html_foot'} + ) + : '' + %> + <% include( '/elements/footer.html' ) %> +% } +% } diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html new file mode 100644 index 000000000..1e7bdd91c --- /dev/null +++ b/httemplate/search/inventory_item.html @@ -0,0 +1,125 @@ +<% include( 'elements/search.html', + 'title' => $title, + + #less lame to use Lingua:: something to pluralize + 'name' => $inventory_class->classname. 's', + + 'query' => { + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum }, + 'select' => join(', ', + 'inventory_item.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $extra_sql, + 'addl_from' => $addl_from, + }, + + 'count_query' => $count_query, + + 'header' => [ + '#', + $inventory_class->classname, + 'Service', + FS::UI::Web::cust_header(), + ], + + 'fields' => [ + 'itemnum', + 'item', + #'svcnum', #XXX proper full service customer link ala svc_acct + # "unallocated" ? "available" ? + sub { + #this could be way more efficient with a mixin + # like cust_main_Mixin that let us all all the methods + # on data we already have... + my $inventory_item = shift; + my $cust_svc = $inventory_item->cust_svc; + if ( $cust_svc ) { + my($label, $value) = $cust_svc->label; + "$label: $value"; + } else { + '(available)'; + } + }, + + \&FS::UI::Web::cust_fields, + + ], + 'align' => 'rll'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $classnum = $cgi->param('classnum'); +$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; +$classnum = $1; + +my $inventory_class = qsearchs( { + 'table' => 'inventory_class', + 'hashref' => { 'classnum' => $classnum }, +} ); + +my $title = $inventory_class->classname. ' Inventory'; + +#little false laziness with SQL fragments in inventory_class.pm +my $extra_sql = ''; +if ( $cgi->param('avail') ) { + $extra_sql = 'AND ( svcnum IS NULL OR svcnum = 0 )'; + $title .= ' - Available'; +} elsif ( $cgi->param('used') ) { + $extra_sql = 'AND svcnum IS NOT NULL AND svcnum > 0'; + $title .= ' - In use'; +} + +my $count_query = + "SELECT COUNT(*) FROM inventory_item WHERE classnum = $classnum $extra_sql"; + +my $link = sub { + my $inventory_item = shift; + if ( $inventory_item->svcnum ) { + [ "${p}view/svc_acct.cgi?", 'svcnum' ]; + } else { + ''; + } +}; +my $link_cust = sub { + my $inventory_item = shift; + if ( $inventory_item->custnum ) { + [ "${p}view/cust_main.cgi?", 'custnum' ]; + } else { + ''; + } +}; + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +</%init> diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi new file mode 100755 index 000000000..cb2171799 --- /dev/null +++ b/httemplate/search/pay_batch.cgi @@ -0,0 +1,130 @@ +<% include( 'elements/search.html', + 'title' => 'Payment Batches', + 'name_singular' => 'batch', + 'query' => { 'table' => 'pay_batch', + 'hashref' => $hashref, + 'extra_sql' => "$extra_sql ORDER BY batchnum DESC", + }, + 'count_query' => "$count_query $extra_sql", + 'header' => [ 'Batch', + 'Type', + 'First Download', + 'Last Upload', + 'Item Count', + 'Amount', + 'Status', + ], + 'align' => 'rcllrrc', + 'fields' => [ 'batchnum', + sub { + FS::payby->shortname(shift->payby); + }, + sub { + my $self = shift; + my $_date = $self->download; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'O' ) { + 'Download batch'; + } else { + ''; + } + }, + sub { + my $self = shift; + my $_date = $self->upload; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'I' ) { + 'Upload results'; + } else { + ''; + } + }, + sub { + my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + $statusmap{shift->status}; + }, + ], + 'links' => [ + $link, + '', + sub { shift->status eq 'O' ? $link : '' }, + sub { shift->status eq 'I' ? $link : '' }, + ], + 'size' => [ + '', + '', + sub { shift->status eq 'O' ? "+1" : '' }, + sub { shift->status eq 'I' ? "+1" : '' }, + ], + 'style' => [ + '', + '', + sub { shift->status eq 'O' ? "b" : '' }, + sub { shift->status eq 'I' ? "b" : '' }, + ], + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports') + || $FS::CurrentUser::CurrentUser->access_right('Process batches'); + +my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved'); +my $hashref = {}; +my $count_query = 'SELECT COUNT(*) FROM pay_batch'; + +my($begin, $end) = ( '', '' ); + +my @where; +if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $begin = str2time($1); + push @where, "download >= $begin"; +} +if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $end = str2time($1) + 86399; + push @where, "download < $end"; +} + +my @status; +if ( $cgi->param('open') ) { + push @status, "O"; +} + +if ( $cgi->param('intransit') ) { + push @status, "I"; +} + +if ( $cgi->param('resolved') ) { + push @status, "R"; +} + +push @where, + scalar(@status) ? q!(status='! . join(q!' OR status='!, @status) . q!')! + : q!status='X'!; # kludgy, X is unused at present + +my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; + +my $link = [ "${p}search/cust_pay_batch.cgi?batchnum=", 'batchnum' ]; + +</%init> diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html new file mode 100644 index 000000000..5907169d8 --- /dev/null +++ b/httemplate/search/pay_batch.html @@ -0,0 +1,33 @@ +<% include('/elements/header.html', 'Batch criteria' ) %> + +<FORM ACTION="pay_batch.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show open batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="intransit" VALUE="1" CHECKED></TD> + <TD>Show in-transit batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="resolved" VALUE="1" CHECKED></TD> + <TD>Show resolved batches</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Batches"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html index 8c8f57b5a..ab6490d33 100644 --- a/httemplate/search/prepay_credit.html +++ b/httemplate/search/prepay_credit.html @@ -1,15 +1,4 @@ -<% -my $agent = ''; -my $hashref = {}; -if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - $hashref->{agentnum} = $1; - $agent = qsearchs('agent', { 'agentnum' => $1 } ); -} - -my $count_query = 'SELECT COUNT(*) FROM prepay_credit'; -$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Unused Prepaid Cards'. ($agent ? ' for '. $agent->agent : ''), 'menubar' => [ @@ -22,11 +11,28 @@ $count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; }, 'count_query' => $count_query, #'redirect' => $link, - 'header' => [ '#', qw(Amount Time Agent) ], + 'header' => [ '#', qw(Amount Time Upload Download Total Agent) ], 'fields' => [ 'identifier', sub { sprintf('$%.2f', shift->amount ) }, - sub { my $c = shift; $c ? duration_exact($c->seconds) : '' }, + sub { my $c = shift; + $c->seconds ? duration_exact($c->seconds) : '' + }, + sub { my $c = shift; + $c->upbytes + ? FS::UI::Web::bytecount_unexact($c->upbytes) + : '' + }, + sub { my $c = shift; + $c->downbytes + ? FS::UI::Web::bytecount_unexact($c->downbytes) + : '' + }, + sub { my $c = shift; + $c->totalbytes + ? FS::UI::Web::bytecount_unexact($c->totalbytes) + : '' + }, sub { my $agent = shift->agent; $agent ? $agent->agent : ''; }, @@ -35,9 +41,28 @@ $count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; '', '', '', + '', + '', + '', sub { my $agent = shift->agent; $agent ? [ "${p}view/agent.cgi?", 'agentnum' ] : ''; }, ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $agent = ''; +my $hashref = {}; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +$hashref->{agentnum} = $1; +$agent = qsearchs('agent', { 'agentnum' => $1 } ); +} + +my $count_query = 'SELECT COUNT(*) FROM prepay_credit'; +$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; + +</%init> diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html new file mode 100644 index 000000000..c343014cc --- /dev/null +++ b/httemplate/search/queue.html @@ -0,0 +1,139 @@ +<% include( 'elements/search.html', + 'title' => 'Job Queue', + 'menubar' => [ 'Main menu' => $p, ], + 'name' => 'jobs', + 'html_form' => qq!<FORM NAME="jobForm" ACTION="$p/misc/queue.cgi" METHOD="POST">!, + 'query' => { 'table' => 'queue', + 'hashref' => $hashref, + 'extra_sql' => 'ORDER BY jobnum', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Job', + 'Args', + 'Date', + 'Status', + 'Account', # unless $hashref->{'svcnum'} + '', # checkbox column + ], + 'fields' => [ + 'jobnum', + 'job', + sub { + my $queue = shift; + if ( $dangerous + || $queue->job !~ /^FS::part_export::/ + || !$noactions + ) + { + encode_entities( join(' ', $queue->args) ); + } else { + ''; + } + }, + sub { + time2str( "%a %b %e %T %Y", shift->_date ); + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + $status .= ': '. $queue->statustext + if $queue->statustext; + my @queue_depend = $queue->queue_depend; + $status .= ' (waiting for '. + join(', ', map { $_->depend_jobnum } + @queue_depend + ). + ')' + if @queue_depend; + my $changable = $dangerous + || ( ! $noactions + && $status =~ /^failed/ + || $status =~ /^locked/ + ); + if ( $changable ) { + $status .= + qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!. + qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!; + } + $status; + }, + sub { + my $queue = shift; + # return '' if $hashref->{'svcnum'} + my $cust_svc = $queue->cust_svc; + my $account; + if ( $cust_svc ) { + my $table = $cust_svc->part_svc->svcdb; + my $label = ( $cust_svc->label )[1]; + qq!<A HREF="../view/$table.cgi?!. $queue->svcnum. + qq!">$label</A>!; + } else { + ''; + } + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + my $changable = $dangerous + || ( ! $noactions + && $status eq 'failed' + || $status eq 'locked' + ); + if ( $changable ) { + $areboxes = 1; + qq!<INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1">!; + } else { + ''; + } + }, + ], + #'links' => [ + # '', + # '', + # '', + # '', + # '', + # '', #$acct_link, + # '', + # ], + 'html_foot' => sub { + if ( $areboxes ) { + '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'. + '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'. + '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'. + '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>'. + '<SCRIPT TYPE="text/javascript">'. + ' function setAll(setTo) { '. + ' theForm = document.jobForm;'. + ' for (i=0,n=theForm.elements.length;i<n;i++)'. + ' if (theForm.elements[i].name.indexOf("jobnum") != -1)'. + ' theForm.elements[i].checked = setTo;'. + ' }'. + '</SCRIPT>'; + } else { + ''; + } + }, + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Job queue'); + +my $hashref = {}; + +my $conf = new FS::Conf; +my $dangerous = $conf->exists('queue_dangerous_controls'); + +my $noactions = 0; + +my $count_query = 'SELECT COUNT(*) FROM queue'; # + $hashref + +my $areboxes = 0; + +</%init> diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html index 52a99ff66..87e0fcdd5 100644 --- a/httemplate/search/reg_code.html +++ b/httemplate/search/reg_code.html @@ -1,13 +1,4 @@ -<% - -my $agentnum = $cgi->param('agentnum'); -$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; -$agentnum = $1; -my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); - -my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum"; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Unused Registration Codes for '. $agent->agent, 'name' => 'registration codes', @@ -34,3 +25,16 @@ my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum"; ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $agentnum = $cgi->param('agentnum'); +$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +$agentnum = $1; +my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + +my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum"; + +<%init> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html new file mode 100644 index 000000000..819ba2195 --- /dev/null +++ b/httemplate/search/report_cdr.html @@ -0,0 +1,17 @@ +<% include('/elements/header.html', 'Call Detail Record Search' ) %> + +<FORM ACTION="cdr.html" METHOD="GET"> +Status: <SELECT NAME="freesidestatus"> + <OPTION VALUE="">(all) + <OPTION VALUE="NULL">unprocessed + <OPTION VALUE="done">processed +</SELECT><BR> +<INPUT TYPE="submit" VALUE="Search Call Detail Records"> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +</%init> diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html index a7be76689..4fa09f96c 100644 --- a/httemplate/search/report_cust_bill.html +++ b/httemplate/search/report_cust_bill.html @@ -1,28 +1,34 @@ - <HEAD> - <TITLE>Invoice report criteria</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Invoice report criteria</H1> - <FORM ACTION="cust_bill.html" METHOD="GET"> - <INPUT TYPE="hidden" NAME="magic" VALUE="_date"> - <TABLE> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'Invoices for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - <TR> - <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> - <TD>Show only open invoices</TD> - </TR> - <TR> - <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="newest_percust" VALUE="1"></TD> - <TD>Show only the single most recent invoice per-customer</TD> - </TR> - </TABLE> - <BR><INPUT TYPE="submit" VALUE="Get Report"> - </FORM> - </BODY> -</HTML> +<% include('/elements/header.html', 'Invoice report criteria' ) %> +<FORM ACTION="cust_bill.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'Invoices for agent: ', + ) + %> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show only open invoices</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="newest_percust" VALUE="1"></TD> + <TD>Show only the single most recent invoice per-customer</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); + +</%init> diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html index 56bbd0ac0..993209763 100644 --- a/httemplate/search/report_cust_credit.html +++ b/httemplate/search/report_cust_credit.html @@ -1,36 +1,53 @@ -<HTML> - <HEAD> - <TITLE>Credit report criteria</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Credit report criteria</H1> - <FORM ACTION="cust_credit.html" METHOD="GET"> - <INPUT TYPE="hidden" NAME="magic" VALUE="_date"> - <TABLE> - <TR> - <TD ALIGN="right">Credits by employee: </TD> -<% - 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}; -%> - <TD><SELECT NAME="otaker"> - <OPTION VALUE="">all</OPTION> - <% foreach my $otaker ( @otakers ) { %> - <OPTION VALUE="<%= $otaker %>"><%= $otaker %></OPTION> - <% } %> - </SELECT> - </TD> - </TR> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - </TABLE> - <BR><INPUT TYPE="submit" VALUE="Get Report"> - </FORM> - </BODY> -</HTML> +<% include('/elements/header.html', 'Credit report' ) %> + +<FORM ACTION="cust_credit.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <TR> + <TD ALIGN="right">Credits by employee: </TD> + + <TD> + <SELECT NAME="otaker"> + <OPTION VALUE="">all</OPTION> +% foreach my $otaker ( @otakers ) { + <OPTION VALUE="<% $otaker %>"><% $otaker %></OPTION> +% } + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <% include( '/elements/tr-input-lessthan_greaterthan.html', + 'label' => 'Amount', + 'field' => 'amount', + ) + %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +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}; + +</%init> diff --git a/httemplate/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html new file mode 100644 index 000000000..1cd07ef76 --- /dev/null +++ b/httemplate/search/report_cust_main-zip.html @@ -0,0 +1,52 @@ +<% include('/elements/header.html', 'Zip code report') %> + + <FORM ACTION="cust_main-zip.html" METHOD="GET"> + + <TABLE> + + <TR> + <TD ALIGN="right">Billing or service zip</TD> + <TD> + <SELECT NAME="column"> + <OPTION VALUE="zip">Billing zip + <OPTION VALUE="ship_zip">Service zip + </SELECT> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Ignore +4 for US zip codes</TD> + <TD><INPUT TYPE="checkbox" NAME="ignore_plus4" VALUE="yes" CHECKED> </TD> + </TR> + + <TR> + <TD ALIGN="right">Show customers with status:</TD> + <TD> + <SELECT NAME="status"> + <OPTION VALUE="">all + <OPTION VALUE="prospect">prospect (no packages ever) + <OPTION SELECTED VALUE="uncancel">all except cancelled + <OPTION VALUE="active">active recurring packages + <OPTION VALUE="susp">suspended + <OPTION VALUE="cancel">cancelled + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List zip codes'); + +</%init> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html index 5d8b74e77..0327e042e 100644 --- a/httemplate/search/report_cust_pay.html +++ b/httemplate/search/report_cust_pay.html @@ -1,38 +1,78 @@ -<HTML> - <HEAD> - <TITLE>Payment report criteria</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Payment report criteria</H1> - <FORM ACTION="cust_pay.cgi" METHOD="GET"> - <INPUT TYPE="hidden" NAME="magic" VALUE="_date"> - <TABLE> - <TR> - <TD ALIGN="right">Payments of type: </TD> - <TD><SELECT NAME="payby"> - <OPTION VALUE="">all</OPTION> - <OPTION VALUE="CARD">credit card (all)</OPTION> - <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION> - <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION> - <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION> - <OPTION VALUE="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION> - <OPTION VALUE="CHEK">electronic check / ACH</OPTION> - <OPTION VALUE="BILL">check</OPTION> - <OPTION VALUE="PREP">prepaid card</OPTION> - <OPTION VALUE="CASH">cash</OPTION> - <OPTION VALUE="WEST">Western Union</OPTION> - <OPTION VALUE="MCRD">manual credit card</OPTION> - </SELECT> - </TD> - </TR> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - </TABLE> - <BR><INPUT TYPE="submit" VALUE="Get Report"> - </FORM> - </BODY> -</HTML> +<% include('/elements/header.html', 'Payment report' ) %> + +<FORM ACTION="cust_pay.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby" onChange="payby_changed(this)"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card (all)</OPTION> + <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION> + <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION> + <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION> + <OPTION VALUE="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + <OPTION VALUE="BILL">check</OPTION> + <OPTION VALUE="PREP">prepaid card</OPTION> + <OPTION VALUE="CASH">cash</OPTION> + <OPTION VALUE="WEST">Western Union</OPTION> + <OPTION VALUE="MCRD">manual credit card</OPTION> + </SELECT> + </TD> + </TR> + + <SCRIPT TYPE="text/javascript"> + + function payby_changed(what) { + if ( what.options[what.selectedIndex].value == 'BILL' ) { + document.getElementById('checkno_caption').style.color = '#000000'; + what.form.payinfo.disabled = false; + what.form.payinfo.style.backgroundColor = '#ffffff'; + } else { + document.getElementById('checkno_caption').style.color = '#bbbbbb'; + what.form.payinfo.disabled = true; + what.form.payinfo.style.backgroundColor = '#dddddd'; + } + } + + </SCRIPT> + + <TR> + <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD> + <TD> + <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd"> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <% include( '/elements/tr-input-lessthan_greaterthan.html', + 'label' => 'Amount', + 'field' => 'paid', + ) + %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_cust_pay_batch.html b/httemplate/search/report_cust_pay_batch.html new file mode 100644 index 000000000..f57a9557e --- /dev/null +++ b/httemplate/search/report_cust_pay_batch.html @@ -0,0 +1,43 @@ +<% include('/elements/header.html', 'Batch payment report' ) %> + +<FORM ACTION="cust_pay_batch.cgi" METHOD="GET"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="dcln" VALUE="1" CHECKED></TD> + <TD>Include approved items</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html new file mode 100755 index 000000000..5b2efa2ca --- /dev/null +++ b/httemplate/search/report_cust_pkg.html @@ -0,0 +1,144 @@ +<% include('/elements/header.html', 'Package Report' ) %> + +<FORM ACTION="cust_pkg.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="bill"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH> + </TR> + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + ) + %> + + <% include( '/elements/tr-select-cust_pkg-status.html', '', + 'onchange' => 'status_changed(this);', + ) + %> + + <SCRIPT TYPE="text/javascript"> + + function status_changed(what) { + +% foreach my $status ( '', FS::cust_pkg->statuses() ) { + + if ( what.options[what.selectedIndex].value == '<% $status %>' ) { + +% foreach my $field (qw( setup last_bill bill susp expire cancel )) { +% if ( $disable{$status}->{$field} ) { + + what.form.<% $field %>_beginning_text.disabled = true; + what.form.<% $field %>_ending_text.disabled = true; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#dddddd'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#dddddd'; + + what.form.<% $field %>_beginning_button.style.display = 'none'; + what.form.<% $field %>_ending_button.style.display = 'none'; + what.form.<% $field %>_beginning_disabled.style.display = ''; + what.form.<% $field %>_ending_disabled.style.display = ''; + +% } else { + + what.form.<% $field %>_beginning_text.disabled = false; + what.form.<% $field %>_ending_text.disabled = false; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff'; + + what.form.<% $field %>_beginning_button.style.display = ''; + what.form.<% $field %>_ending_button.style.display = ''; + what.form.<% $field %>_beginning_disabled.style.display = 'none'; + what.form.<% $field %>_ending_disabled.style.display = 'none'; + +% } +% } + + } + +% } + + } + + </SCRIPT> + + <% include( '/elements/tr-select-pkg_class.html', '', + 'pre_options' => [ '0' => 'all' ], + 'empty_label' => '(empty class)', + ) + %> + +% foreach my $field (qw( setup last_bill bill susp expire cancel )) { + + <TR> + <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD> + <TD> + <TABLE> + <% include( '/elements/tr-input-beginning_ending.html', + prefix => $field, + layout => 'horiz', + ) + %> + </TABLE> + </TD> + </TR> + +% } + + <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2> </TH> + </TR> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH> + </TR> + <% include( '/elements/tr-select-cust-fields.html' ) %> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); + +</%init> +<%once> + +my %label = ( + 'setup' => 'Setup', + 'last_bill' => 'Last bill', + 'bill' => 'Next bill', + 'susp' => 'Suspended', + 'expire' => 'Expires', + 'cancel' => 'Cancelled', +); + +#false laziness w/cust_pkg.cgi +my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, +); + +#hmm? +my %checkbox = ( + 'setup' => 0, + 'last_bill' => 0, + 'bill' => 0, + 'susp' => 1, + 'expire' => 1, + 'cancel' => 1, +); + +</%once> diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi index 1677591a3..fd9b01ec1 100644 --- a/httemplate/search/report_prepaid_income.cgi +++ b/httemplate/search/report_prepaid_income.cgi @@ -1,75 +1,14 @@ -<!-- mason kludge --> -<% - - #doesn't yet deal with daily/weekly packages - - #needs to be re-written in sql for efficiency - - my $time = time; - - my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time; - $now =~ /^(\d+)$/ or die "unparsable date?"; - $now = $1; - - my( $total, $total_legacy ) = ( 0, 0 ); - - my @cust_bill_pkg = - grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ } - qsearch( 'cust_bill_pkg', { - 'recur' => { op=>'!=', value=>0 }, - 'edate' => { op=>'>', value=>$now }, - }, ); - - my @cust_pkg = - grep { $_->part_pkg->recur != 0 - && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/ - } - qsearch ( 'cust_pkg', { - 'bill' => { op=>'>', value=>$now } - } ); - - foreach my $cust_bill_pkg ( @cust_bill_pkg) { - my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate; - - my $elapsed = $now - $cust_bill_pkg->sdate; - $elapsed = 0 if $elapsed < 0; - - my $remaining = 1 - $elapsed/$period; - - my $unearned = $remaining * $cust_bill_pkg->recur; - $total += $unearned; - - } - - foreach my $cust_pkg ( @cust_pkg ) { - my $period = $cust_pkg->bill - $cust_pkg->last_bill; - - my $elapsed = $now - $cust_pkg->last_bill; - $elapsed = 0 if $elapsed < 0; - - my $remaining = 1 - $elapsed/$period; - - my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy - $total_legacy += $unearned; - - } - - $total = sprintf('%.2f', $total); - $total_legacy = sprintf('%.2f', $total_legacy); - -%> - -<%= header( 'Prepaid Income (Unearned Revenue) Report', +<% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report', menubar( 'Main Menu'=>$p, ) ) %> -<%= table() %> +<% table() %> <TR> <TH>Actual Unearned Revenue</TH> <TH>Legacy Unearned Revenue</TH> </TR> <TR> - <TD ALIGN="right">$<%= $total %> + <TD ALIGN="right">$<% $total %> <TD ALIGN="right"> - <%= $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%> + <% $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%> </TD> </TR> @@ -84,3 +23,65 @@ revenue if you have imported longer-than monthly customer packages from a previous billing system. </BODY> </HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +#doesn't yet deal with daily/weekly packages + +#needs to be re-written in sql for efficiency + +my $time = time; + +my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time; +$now =~ /^(\d+)$/ or die "unparsable date?"; +$now = $1; + +my( $total, $total_legacy ) = ( 0, 0 ); + +my @cust_bill_pkg = + grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ } + qsearch( 'cust_bill_pkg', { + 'recur' => { op=>'!=', value=>0 }, + 'edate' => { op=>'>', value=>$now }, + }, ); + +my @cust_pkg = + grep { $_->part_pkg->recur != 0 + && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/ + } + qsearch ( 'cust_pkg', { + 'bill' => { op=>'>', value=>$now } + } ); + +foreach my $cust_bill_pkg ( @cust_bill_pkg) { + my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate; + + my $elapsed = $now - $cust_bill_pkg->sdate; + $elapsed = 0 if $elapsed < 0; + + my $remaining = 1 - $elapsed/$period; + + my $unearned = $remaining * $cust_bill_pkg->recur; + $total += $unearned; + +} + +foreach my $cust_pkg ( @cust_pkg ) { + my $period = $cust_pkg->bill - $cust_pkg->last_bill; + + my $elapsed = $now - $cust_pkg->last_bill; + $elapsed = 0 if $elapsed < 0; + + my $remaining = 1 - $elapsed/$period; + + my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy + $total_legacy += $unearned; + +} + +$total = sprintf('%.2f', $total); +$total_legacy = sprintf('%.2f', $total_legacy); + +</%init> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html index 57c318eba..81adb64ad 100644 --- a/httemplate/search/report_prepaid_income.html +++ b/httemplate/search/report_prepaid_income.html @@ -1,13 +1,13 @@ -<HTML> - <HEAD> - <TITLE>Prepaid Income (Unearned Revenue) Report</TITLE> - <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> - <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> - <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> - <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Prepaid Income (Unearned Revenue) Report</H1> +<% include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report', + '', + '', + '<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + ' +) %> + <FORM ACTION="report_prepaid_income.cgi" METHOD="GET"> <TABLE> <TR> @@ -32,8 +32,12 @@ }); </SCRIPT> - <INPUT TYPE="submit" VALUE="Generate report"> - </BODY> -</HTML> - <TABLE> +<INPUT TYPE="submit" VALUE="Generate report"> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); +</%init> diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index d675346f0..6e5893870 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -1,136 +1,11 @@ -<% - - my $charged = <<END; - sum( charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - - ) -END - - my $owed_cols = <<END; - coalesce( - ( select $charged from cust_bill - where cust_bill._date > extract(epoch from now())-2592000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_0_30, - - coalesce( - ( select $charged from cust_bill - where cust_bill._date > extract(epoch from now())-5184000 - and cust_bill._date <= extract(epoch from now())-2592000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_30_60, - - coalesce( - ( select $charged from cust_bill - where cust_bill._date > extract(epoch from now())-7776000 - and cust_bill._date <= extract(epoch from now())-5184000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_60_90, - - coalesce( - ( select $charged from cust_bill - where cust_bill._date <= extract(epoch from now())-7776000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_90_pl, - - coalesce( - ( select $charged from cust_bill - where cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_total -END - - my $recurring = <<END; - '0' != ( select freq from part_pkg - where cust_pkg.pkgpart = part_pkg.pkgpart ) -END - - my $packages_cols = <<END; - - ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and $recurring - and ( cancel = 0 or cancel is null ) - ) as uncancelled_pkgs, - - ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and $recurring - and ( cancel = 0 or cancel is null ) - and ( susp = 0 or susp is null ) - ) as active_pkgs - -END - - my $where = <<END; -where 0 < - coalesce( - ( select $charged from cust_bill - where cust_main.custnum = cust_bill.custnum - ) - ,0 - ) -END - - my $agentnum = ''; - if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - $agentnum = $1; - $where .= " AND agentnum = '$agentnum' "; - } - - my $count_sql = "select count(*) from cust_main $where"; - - my $sql_query = { - 'table' => 'cust_main', - 'hashref' => {}, - 'select' => "*, $owed_cols, $packages_cols", - 'extra_sql' => "$where order by coalesce(lower(company), ''), lower(last)", - }; - - if ( $agentnum ) { - $owed_cols =~ - s/cust_bill\.custnum/cust_bill.custnum AND cust_main.agentnum = '$agentnum'/g; - } - my $total_sql = "select $owed_cols"; - my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; - $total_sth->execute or die $total_sth->errstr; - my $row = $total_sth->fetchrow_hashref(); - - my $conf = new FS::Conf; - my $money_char = $conf->config('money_char') || '$'; - - my $align = join('', map { /#/ ? 'r' : 'l' } FS::UI::Web::cust_header() ). - 'crrrrr'; - - my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Accounts Receivable Aging Summary', 'name' => 'customers', 'query' => $sql_query, 'count_query' => $count_sql, 'header' => [ FS::UI::Web::cust_header(), - 'Status', # (me)', + #'Status', # (me)', #'Status', # (cust_main)', '0-30', '30-60', @@ -145,7 +20,7 @@ END scalar(FS::UI::Web::cust_header()-1) ) ), - '', + #'', #'', sprintf( $money_char.'%.2f', $row->{'owed_0_30'} ), @@ -154,26 +29,13 @@ END sprintf( $money_char.'%.2f', $row->{'owed_60_90'} ), sprintf( $money_char.'%.2f', - $row->{'owed_90_pl'} ), + $row->{'owed_90_0'} ), sprintf( '<b>'. $money_char.'%.2f'. '</b>', - $row->{'owed_total'} ), + $row->{'owed_0_0'} ), ], 'fields' => [ \&FS::UI::Web::cust_fields, - sub { - my $row = shift; - my $status = 'Cancelled'; - my $statuscol = 'FF0000'; - if ( $row->uncancelled_pkgs ) { - $status = 'Suspended'; - $statuscol = 'FF9900'; - if ( $row->active_pkgs ) { - $status = 'Active'; - $statuscol = '00CC00'; - } - } - $status; - }, + #sub { ( &{$status_statuscol}(shift) )[0] }, #sub { ucfirst(shift->status) }, sub { sprintf( $money_char.'%.2f', shift->get('owed_0_30') ) }, @@ -182,13 +44,15 @@ END sub { sprintf( $money_char.'%.2f', shift->get('owed_60_90') ) }, sub { sprintf( $money_char.'%.2f', - shift->get('owed_90_pl') ) }, + shift->get('owed_90_0') ) }, sub { sprintf( $money_char.'%.2f', - shift->get('owed_total') ) }, + shift->get('owed_0_0') ) }, ], 'links' => [ - ( map $clink, FS::UI::Web::cust_header() ), - '', + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + #'', #'', '', '', @@ -197,29 +61,18 @@ END '', ], #'align' => 'rlccrrrrr', - 'align' => $align, + 'align' => FS::UI::Web::cust_aligns(). 'rrrrr', #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ], #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ], 'size' => [ ( map '', FS::UI::Web::cust_header() ), - '-1', '', '', '', '', '', ], - 'style' => [ ( map '', FS::UI::Web::cust_header() ), - 'b', '', '', '', '', 'b', ], + #'-1', '', '', '', '', '', ], + '', '', '', '', '', ], + 'style' => [ FS::UI::Web::cust_styles(), + #'b', '', '', '', '', 'b', ], + '', '', '', '', 'b', ], 'color' => [ - ( map '', FS::UI::Web::cust_header() ), - sub { - my $row = shift; - my $status = 'Cancelled'; - my $statuscol = 'FF0000'; - if ( $row->uncancelled_pkgs ) { - $status = 'Suspended'; - $statuscol = 'FF9900'; - if ( $row->active_pkgs ) { - $status = 'Active'; - $statuscol = '00CC00'; - } - } - $statuscol; - }, + FS::UI::Web::cust_colors(), + #sub { ( &{$status_statuscol}(shift) )[1] }, #sub { shift->statuscolor; }, '', '', @@ -230,3 +83,147 @@ END ) %> +<%once> + +sub owed { + my($start, $end, %opt) = @_; + + my @where = (); + + #handle start and end ranges + + #24h * 60m * 60s + push @where, "cust_bill._date <= extract(epoch from now())-". + ($start * 86400) + if $start; + + push @where, "cust_bill._date > extract(epoch from now()) - ". + ($end * 86400) + if $end; + + #handle 'cust' option + + push @where, "cust_main.custnum = cust_bill.custnum" + if $opt{'cust'}; + + #handle 'agentnum' option + my $join = ''; + if ( $opt{'agentnum'} ) { + $join = 'LEFT JOIN cust_main USING ( custnum )'; + push @where, "agentnum = '$opt{'agentnum'}'"; + } + + my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; + + my $as = $opt{'noas'} ? '' : "as owed_${start}_$end"; + + my $charged = <<END; +sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) + ,0 + ) + + ) +END + + "coalesce( ( select $charged from cust_bill $join $where ) ,0 ) $as"; + +} + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my @ranges = ( + [ 0, 30 ], + [ 30, 60 ], + [ 60, 90 ], + [ 90, 0 ], + [ 0, 0 ], +); + +my $owed_cols = join(',', map owed( @$_, 'cust'=>1 ), @ranges ); + +my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql; + +my $active_sql = FS::cust_pkg->active_sql; +my $inactive_sql = FS::cust_pkg->inactive_sql; +my $suspended_sql = FS::cust_pkg->suspended_sql; +my $cancelled_sql = FS::cust_pkg->cancelled_sql; + +my $packages_cols = <<END; + ( $select_count_pkgs ) AS num_pkgs_sql, + ( $select_count_pkgs AND $active_sql ) AS active_pkgs, + ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs, + ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs, + ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs +END + +my $days = 0; +if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) { + $days = $1; +} + +#my $where = "where ". owed(0, 0, 'cust'=>1, 'noas'=>1). " > 0"; +my $where = "where ". owed($days, 0, 'cust'=>1, 'noas'=>1). " > 0"; + +my $agentnum = ''; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + $where .= " AND agentnum = '$agentnum' "; +} + +#here is the agent virtualization +$where .= ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $count_sql = "select count(*) from cust_main $where"; + +my $sql_query = { + 'table' => 'cust_main', + 'hashref' => {}, + 'select' => "*, $owed_cols, $packages_cols", + 'extra_sql' => "$where order by coalesce(lower(company), ''), lower(last)", +}; + +my $total_sql = "select ". + join(',', map owed( @$_, 'agentnum'=>$agentnum ), @ranges ); + +my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; +$total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr; +my $row = $total_sth->fetchrow_hashref(); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $status_statuscol = sub { + #conceptual false laziness with cust_main::status... + my $row = shift; + + my $status = 'unknown'; + if ( $row->num_pkgs_sql == 0 ) { + $status = 'prospect'; + } elsif ( $row->active_pkgs > 0 ) { + $status = 'active'; + } elsif ( $row->inactive_pkgs > 0 ) { + $status = 'inactive'; + } elsif ( $row->suspended_pkgs > 0 ) { + $status = 'suspended'; + } elsif ( $row->cancelled_pkgs > 0 ) { + $status = 'cancelled' + } + + ( ucfirst($status), $FS::cust_main::statuscolor{$status} ); +}; + +</%init> diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html new file mode 100755 index 000000000..bb23f1f87 --- /dev/null +++ b/httemplate/search/report_receivables.html @@ -0,0 +1,26 @@ +<% include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %> + + <FORM ACTION="report_receivables.cgi" METHOD="GET"> + + <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <TR> + <TD ALIGN="right">Over </TD> + <TD><INPUT NAME="days" TYPE="text" SIZE=4 MAXLENGTH=3> days</TD> + </TR> + + </TABLE> + + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 9062f0626..918383b67 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -1,4 +1,173 @@ -<% +<% include("/elements/header.html", "$agentname Sales Tax Report - ". + ( $beginning + ? time2str('%h %o %Y ', $beginning ) + : '' + ). + 'through '. + ( $ending == 4294967295 + ? 'now' + : time2str('%h %o %Y', $ending ) + ), + menubar( 'Main Menu'=>$p, ) + ) +%> + +<% include('/elements/table-grid.html') %> + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH> +% unless ( $cgi->param('show_taxclasses') ) { + + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH> +% } + + </TR> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH> + </TR> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% foreach my $region ( @regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% } +% +% +% +% +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1"><% $money_char %><% sprintf('%.2f', $region->{'total'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_cust'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_pkg'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $exemptlink. $link %>"><% $money_char %><% sprintf('%.2f', $region->{'exempt_monthly'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'taxable'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $region->{'rate'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'owed'} ) %> + </TD> +% unless ( $cgi->param('show_taxclasses') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> +% } + + </TR> +% } + + +</TABLE> +% if ( $cgi->param('show_taxclasses') ) { + + + <BR> + <% include('/elements/table-grid.html') %> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH> + </TR> +% #some false laziness w/above +% $bgcolor1 = '#eeeeee'; +% $bgcolor2 = '#ffffff'; +% foreach my $region ( @base_regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% #if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% #} +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> + </TR> +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">Total</TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax ) %></A> + </TD> + </TR> + + </TABLE> +% } + + +</BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; @@ -7,23 +176,50 @@ my $user = getotaker; my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); -my $from_join_cust = " - FROM cust_bill_pkg +my $join_cust = " JOIN cust_bill USING ( invnum ) - JOIN cust_main USING ( custnum ) + LEFT JOIN cust_main USING ( custnum ) "; +my $from_join_cust = " + FROM cust_bill_pkg + $join_cust +"; my $join_pkg = " - JOIN cust_pkg USING ( pkgnum ) - JOIN part_pkg USING ( pkgpart ) -"; -my $where = " - WHERE _date >= $beginning AND _date <= $ending - AND ( county = ? OR ? = '' ) - AND ( state = ? OR ? = '' ) - AND country = ? - AND payby != 'COMP' + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) "; + +my $where = "WHERE _date >= $beginning AND _date <= $ending "; my @base_param = qw( county county state state country ); +if ( $conf->exists('tax-ship_address') ) { + + $where .= " + AND ( ( ( ship_last IS NULL OR ship_last = '' ) + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + ) + OR ( ship_last IS NOT NULL AND ship_last != '' + AND ( ship_county = ? OR ? = '' ) + AND ( ship_state = ? OR ? = '' ) + AND ship_country = ? + ) + ) + "; + # AND payby != 'COMP' + + push @base_param, @base_param; + +} else { + + $where .= " + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + "; + # AND payby != 'COMP' + +} my $agentname = ''; if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { @@ -35,19 +231,64 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { my $gotcust = " WHERE 0 < ( SELECT COUNT(*) FROM cust_main - WHERE ( cust_main.county = cust_main_county.county - OR cust_main_county.county = '' - OR cust_main_county.county IS NULL ) - AND ( cust_main.state = cust_main_county.state - OR cust_main_county.state = '' - OR cust_main_county.state IS NULL ) - AND ( cust_main.country = cust_main_county.country ) - LIMIT 1 - ) "; +if ( $conf->exists('tax-ship_address') ) { + + $gotcust .= " + WHERE + + ( cust_main_county.country = cust_main.country + OR cust_main_county.country = cust_main.ship_country + ) + + AND + + ( + + ( ( ship_last IS NULL OR ship_last = '' ) + AND ( cust_main_county.country = cust_main.country ) + AND ( cust_main_county.state = cust_main.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + OR + + ( ship_last IS NOT NULL AND ship_last != '' + AND ( cust_main_county.country = cust_main.ship_country ) + AND ( cust_main_county.state = cust_main.ship_state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.ship_county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + ) + + LIMIT 1 + ) + "; + +} else { + + $gotcust .= " + WHERE ( cust_main.county = cust_main_county.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + AND ( cust_main.state = cust_main_county.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main.country = cust_main_county.country ) + LIMIT 1 + ) + "; + +} -my $monthly_exempt_warning = 0; -my $taxclass_flag = 0; my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 ); my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 ); my $out = 'Out of taxable region(s)'; @@ -59,17 +300,18 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { $regions{$label}->{'label'} = $label; $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) ); - my $fromwhere = $from_join_cust. $join_pkg. $where; my @param = @base_param; + my $mywhere = $where; if ( $r->taxclass ) { - $fromwhere .= " AND taxclass = ? "; + $mywhere .= " AND taxclass = ? "; push @param, 'taxclass'; $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass if $cgi->param('show_taxclasses'); - $taxclass_flag = 1; } + my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' "; + # my $label = getlabel($r); # $regions{$label}->{'label'} = $label; @@ -83,57 +325,80 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { $total += $t; $regions{$label}->{'total'} += $t; - ## calculate package-exemption for this region - - foreach my $e ( grep { $r->get($_.'tax') =~ /^Y/i } - qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { - my $x = scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax" - ); - $exempt_pkg += $x; - $regions{$label}->{'exempt_pkg'} += $x; - } - ## calculate customer-exemption for this region - my($taxable, $x_cust) = (0, 0); - foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } - qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { - $taxable += scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" - ); - - $x_cust += scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" - ); - } +## my $taxable = $t; + +# my($taxable, $x_cust) = (0, 0); +# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } +# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { +# $taxable += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" +# ); +# +# $x_cust += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" +# ); +# } + + my $x_cust = scalar_sql($r, \@param, + "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) + $fromwhere AND $nottax AND tax = 'Y' " + ); $exempt_cust += $x_cust; $regions{$label}->{'exempt_cust'} += $x_cust; + + ## calculate package-exemption for this region - ## calculate monthly exemption (texas tax) for this region + my $x_pkg = scalar_sql($r, \@param, + "SELECT SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + $fromwhere + AND $nottax + AND ( + ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) + OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) + ) + AND ( tax != 'Y' OR tax IS NULL ) + " + ); + $exempt_pkg += $x_pkg; + $regions{$label}->{'exempt_pkg'} += $x_pkg; - my($sday,$smon,$syear) = (localtime($beginning) )[ 3, 4, 5 ]; - $monthly_exempt_warning=1 if $sday != 1 && $beginning; - $smon++; $syear+=1900; + ## calculate monthly exemption (texas tax) for this region - my $eending = ( $ending == 4294967295 ) ? time : $ending; - my($eday,$emon,$eyear) = (localtime($eending) )[ 3, 4, 5 ]; - $emon++; $eyear+=1900; + # count up all the cust_tax_exempt_pkg records associated with + # the actual line items. - my $x_monthly = scalar_sql($r, [ 'taxnum' ], - "SELECT SUM(amount) FROM cust_tax_exempt where taxnum = ? ". - " AND ( year > $syear OR ( year = $syear and month >= $smon ) )". - " AND ( year < $eyear OR ( year = $eyear and month <= $emon ) )" + my $x_monthly = scalar_sql($r, \@param, + "SELECT SUM(amount) + FROM cust_tax_exempt_pkg + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust $join_pkg + $mywhere" ); - if ( $x_monthly ) { - warn $r->taxnum(). ": $x_monthly\n"; - $taxable -= $x_monthly; - } +# if ( $x_monthly ) { +# #warn $r->taxnum(). ": $x_monthly\n"; +# $taxable -= $x_monthly; +# } $exempt_monthly += $x_monthly; $regions{$label}->{'exempt_monthly'} += $x_monthly; + my $taxable = $t - $x_cust - $x_pkg - $x_monthly; + $tot_taxable += $taxable; $regions{$label}->{'taxable'} += $taxable; @@ -149,7 +414,7 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { } -my $taxwhere = "$from_join_cust $where"; +my $taxwhere = "$from_join_cust $where AND payby != 'COMP' "; my @taxparam = @base_param; my %base_regions = (); #foreach my $label ( keys %regions ) { @@ -165,8 +430,8 @@ foreach my $r ( my $label = getlabel($r); - my $fromwhere = $join_pkg. $where; - my @param = @base_param; + #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; + #my @param = @base_param; #match itemdesc if necessary! my $named_tax = @@ -246,7 +511,7 @@ sub getlabel { $label = "$label (". $r->taxclass. ")" if $r->taxclass && $cgi->param('show_taxclasses') - && ! $opt{'no_taxclasses'}; + && ! $opt{'no_taxclass'}; #$label = $r->taxname. " ($label)" if $r->taxname; } return $label; @@ -263,170 +528,12 @@ sub scalar_sql { $sth->fetchrow_arrayref->[0] || 0; } -%> - -<% - -my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; - -%> - - -<%= header( "$agentname Sales Tax Report - ". - time2str('%h %o %Y through ', $beginning ). - ( $ending == 4294967295 - ? 'now' - : time2str('%h %o %Y', $ending ) - ), - menubar( 'Main Menu'=>$p, ) - ) -%> - -<%= include('/elements/table-grid.html') %> - <TR> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> - <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH> - <% unless ( $cgi->param('show_taxclasses') ) { %> - <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH> - <% } %> - </TR> - <TR> - <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH> - <TH CLASS="grid" BGCOLOR="#cccccc"></TH> - <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"></TH> - <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"></TH> - <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"></TH> - <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH> - </TR> - -<% my $bgcolor1 = '#eeeeee'; - my $bgcolor2 = '#ffffff'; - my $bgcolor; -%> - - <% foreach my $region ( @regions ) { - - if ( $bgcolor eq $bgcolor1 ) { - $bgcolor = $bgcolor2; - } else { - $bgcolor = $bgcolor1; - } - - my $link = $baselink; - if ( $region->{'label'} ne 'Total' ) { - if ( $region->{'label'} eq $out ) { - $link .= ';out=1'; - } else { - $link .= ';'. $region->{'url_param'}; - } - } - %> - - <TR> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><%= $region->{'label'} %></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $link %>;nottax=1"><%= $money_char %><%= sprintf('%.2f', $region->{'total'} ) %></A> - </TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $link %>;nottax=1;cust_tax=Y"><%= $money_char %><%= sprintf('%.2f', $region->{'exempt_cust'} ) %></A> - </TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $link %>;nottax=1;pkg_tax=Y"><%= $money_char %><%= sprintf('%.2f', $region->{'exempt_pkg'} ) %></A> - </TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_monthly'} ) %></A> - </TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <%= $money_char %><%= sprintf('%.2f', $region->{'taxable'} ) %></A> - </TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><%= $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"><%= $region->{'rate'} %></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><%= $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <%= $money_char %><%= sprintf('%.2f', $region->{'owed'} ) %> - </TD> - <% unless ( $cgi->param('show_taxclasses') ) { %> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $link %>;istax=1"><%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %></A> - </TD> - <% } %> - </TR> - - <% } %> - -</TABLE> - - -<% if ( $cgi->param('show_taxclasses') ) { %> - - <BR> - <%= include('/elements/table-grid.html') %> - <TR> - <TH CLASS="grid" BGCOLOR="#cccccc"></TH> - <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH> - </TR> - - <% #some false laziness w/above - foreach my $region ( @base_regions ) { - - if ( $bgcolor eq $bgcolor1 ) { - $bgcolor = $bgcolor2; - } else { - $bgcolor = $bgcolor1; - } - - my $link = $baselink; - #if ( $region->{'label'} ne 'Total' ) { - if ( $region->{'label'} eq $out ) { - $link .= ';out=1'; - } else { - $link .= ';'. $region->{'url_param'}; - } - #} - %> - - <TR> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>"><%= $region->{'label'} %></TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $link %>;istax=1"><%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %></A> - </TD> - </TR> - - <% } %> - - <TR> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>">Total</TD> - <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>" ALIGN="right"> - <A HREF="<%= $baselink %>;istax=1"><%= $money_char %><%= sprintf('%.2f', $tax ) %></A> - </TD> - </TR> - - </TABLE> - -<% } %> - - -<% if ( $monthly_exempt_warning ) { %> - <BR> - Partial-month tax reports (except for current month) may not be correct due - to month-granularity tax exemption (usually "texas tax"). For an accurate - report, start on the first of a month and end on the last day of a month (or - leave blank for to now). -<% } %> - -</BODY> -</HTML> +my $dateagentlink = "begin=$beginning;end=$ending"; +$dateagentlink .= ';agentnum='. $cgi->param('agentnum') + if length($agentname); +my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink"; +my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; +</%init> diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index eeaccc1ab..35b290c19 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -1,22 +1,42 @@ -<HTML> - <HEAD> - <TITLE>Tax Report Criteria</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <H1>Tax Report Criteria</H1> - <FORM ACTION="report_tax.cgi" METHOD="GET"> - <TABLE> - <%= include( '/elements/tr-select-agent.html' ) %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - <TR> - <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD> - <TD>Show tax classes</TD> - </TR> - </TABLE> - - <BR><INPUT TYPE="submit" VALUE="Get Report"> - - </FORM> - </BODY> -</HTML> +<% include('/elements/header.html', 'Tax Report' ) %> +<FORM ACTION="report_tax.cgi" METHOD="GET"> + +<TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> +% my $conf = new FS::Conf; +% if ( $conf->exists('enable_taxclasses') ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD> + <TD>Show tax classes</TD> + </TR> +% } +% my @pkg_class = qsearch('pkg_class', {}); +% if ( @pkg_class ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD> + <TD>Show package classes</TD> + </TR> +% } + + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html index b28c045d1..5f64ebc28 100644 --- a/httemplate/search/sql.html +++ b/httemplate/search/sql.html @@ -1,7 +1,13 @@ -<%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Query Results', 'name' => 'rows', 'query' => 'SELECT '. ( $cgi->param('sql') || eidiot('Empty query') ), ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Raw SQL'); + +</%init> diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi index b84df1a03..324729b6a 100644 --- a/httemplate/search/sqlradius.cgi +++ b/httemplate/search/sqlradius.cgi @@ -1,4 +1,4 @@ -<%= include( '/elements/header.html', 'RADIUS Sessions', +<% include( '/elements/header.html', 'RADIUS Sessions', include('/elements/menubar.html', 'Main menu' => $p, # popurl(2), ), @@ -6,285 +6,306 @@ ) %> -<% - ### - # parse cgi params - ### - - #sort of false laziness w/cust_pay.cgi - my $beginning = ''; - my $ending = ''; - if ( $cgi->param('beginning') - && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $beginning = str2time($1); - } - if ( $cgi->param('ending') - && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $ending = str2time($1) + 86399; - } - if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) { - $beginning = $1; - } - if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) { - $ending = $1; +% ### +% # and finally, display the thing +% ### +% +% foreach my $part_export ( +% #grep $_->can('usage_sessions'), qsearch( 'part_export' ) +% qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ), +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ) +% ) { +% %user2svc_acct = (); +% +% my $efields = tie my %efields, 'Tie::IxHash', %fields; +% delete $efields{'framedipaddress'} if $part_export->option('hide_ip'); +% if ( $part_export->option('hide_data') ) { +% delete $efields{$_} foreach qw(acctinputoctets acctoutputoctets); +% } +% if ( $part_export->option('show_called_station') ) { +% $efields->Splice(1, 0, +% 'calledstationid' => { +% 'name' => 'Destination', +% 'attrib' => 'Called-Station-ID', +% 'fmt' => +% sub { length($_[0]) ? shift : ' '; }, +% 'align' => 'left', +% }, +% ); +% } +% +% + + <% $part_export->exporttype %> to <% $part_export->machine %><BR> + <% include( '/elements/table-grid.html' ) %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; + + <TR> +% foreach my $field ( keys %efields ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"> + <% $efields{$field}->{name} %><BR> + <FONT SIZE=-2><% $efields{$field}->{attrib} %></FONT> + </TH> + +% } + </TR> + +% foreach my $session ( +% @{ $part_export->usage_sessions( +% $beginning, $ending, $cgi_svc_acct, $ip, $prefix, ) } +% ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> +% foreach my $field ( keys %efields ) { +% my $html = &{ $efields{$field}->{fmt} }( $session->{$field}, +% $session, +% $part_export, +% ); +% my $class = ( $html =~ /<TABLE/ ? 'inv' : 'grid' ); + + <TD CLASS="<%$class%>" BGCOLOR="<% $bgcolor %>" ALIGN="<% $efields{$field}->{align} %>"> + <% $html %> + </TD> +% } + </TR> + +% } + +</TABLE> +<BR><BR> + +% } + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +### +# parse cgi params +### + +#sort of false laziness w/cust_pay.cgi +my $beginning = ''; +my $ending = ''; +if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $beginning = str2time($1); +} +if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $ending = str2time($1); # + 86399; +} +if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) { + $beginning = $1; +} +if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) { + $ending = $1; +} + +my $cgi_svc_acct = ''; +if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } ); +} elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) { + my %search = { 'username' => $1 }; + my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; } + $cgi_svc_acct = qsearchs( 'svc_acct', \%search ) + if keys %search; +} elsif ( $cgi->param('username') =~ /^(.+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } ); +} + +my $ip = ''; +if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) { + $ip = $1; +} + +my $prefix = $cgi->param('prefix'); +$prefix =~ s/\D//g; +if ( $prefix =~ /^(\d+)$/ ) { + $prefix = $1; + $prefix = "011$prefix" unless $prefix =~ /^1/; +} else { + $prefix = ''; +} - my $cgi_svc_acct = ''; - if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) { - $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } ); - } elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) { - my %search = { 'username' => $1 }; - my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } ); - if ( $svc_domain ) { - $search{'domsvc'} = $svc_domain->svcnum; +### +# field formatting subroutines +### + +my %user2svc_acct = (); +my $user_format = sub { + my ( $user, $session, $part_export ) = @_; + + my $svc_acct = ''; + if ( exists $user2svc_acct{$user} ) { + $svc_acct = $user2svc_acct{$user}; + } else { + my %search = (); + if ( $part_export->exporttype eq 'sqlradius_withdomain' ) { + my $domain; + if ( $user =~ /^([^@]+)\@([^@]+)$/ ) { + $search{'username'} = $1; + $domain = $2; + } else { + $search{'username'} = $user; + $domain = $session->{'realm'}; + } + my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; + } + } elsif ( $part_export->exporttype eq 'sqlradius' ) { + $search{'username'} = $user; } else { - delete $search{'username'}; + die 'unknown export type '. $part_export->exporttype. + " for $part_export\n"; } - $cgi_svc_acct = qsearchs( 'svc_acct', \%search ) - if keys %search; - } elsif ( $cgi->param('username') =~ /^(.+)$/ ) { - $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } ); - } - - my $ip = ''; - if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) { - $ip = $1; + if ( keys %search ) { + my @svc_acct = + grep { qsearchs( 'export_svc', { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $_->cust_svc->svcpart, + } ) + } qsearch( 'svc_acct', \%search ); + if ( @svc_acct ) { + warn 'multiple svc_acct records for user $user found; '. + 'using first arbitrarily' + if scalar(@svc_acct) > 1; + $user2svc_acct{$user} = $svc_acct = shift @svc_acct; + } + } } - my $prefix = $cgi->param('prefix'); - $prefix =~ s/\D//g; - if ( $prefix =~ /^(\d+)$/ ) { - $prefix = $1; - $prefix = "011$prefix" unless $prefix =~ /^1/; + if ( $svc_acct ) { + my $svcnum = $svc_acct->svcnum; + qq(<A HREF="${p}view/svc_acct.cgi?$svcnum"><B>$user</B></A>); } else { - $prefix = ''; + "<B>$user</B>"; } - ### - # field formatting subroutines - ### +}; - my %user2svc_acct = (); - my $user_format = sub { - my ( $user, $session, $part_export ) = @_; +my $customer_format = sub { + my( $unused, $session ) = @_; + return ' ' unless exists $user2svc_acct{$session->{'username'}}; + my $svc_acct = $user2svc_acct{$session->{'username'}}; + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + return ' ' unless $cust_pkg; + my $cust_main = $cust_pkg->cust_main; - my $svc_acct = ''; - if ( exists $user2svc_acct{$user} ) { - $svc_acct = $user2svc_acct{$user}; - } else { - my %search = (); - if ( $part_export->exporttype eq 'sqlradius_withdomain' ) { - my $domain; - if ( $user =~ /^([^@]+)\@([^@]+)$/ ) { - $search{'username'} = $1; - $domain = $2; - } else { - $search{'username'} = $user; - $domain = $session->{'realm'}; - } - my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ); - if ( $svc_domain ) { - $search{'domsvc'} = $svc_domain->svcnum; - } else { - delete $search{'username'}; - } - } elsif ( $part_export->exporttype eq 'sqlradius' ) { - $search{'username'} = $user; - } else { - die 'unknown export type '. $part_export->exporttype. - " for $part_export\n"; - } - if ( keys %search ) { - my @svc_acct = - grep { qsearchs( 'export_svc', { - 'exportnum' => $part_export->exportnum, - 'svcpart' => $_->cust_svc->svcpart, - } ) - } qsearch( 'svc_acct', \%search ); - if ( @svc_acct ) { - warn 'multiple svc_acct records for user $user found; '. - 'using first arbitrarily' - if scalar(@svc_acct) > 1; - $user2svc_acct{$user} = $svc_acct = shift @svc_acct; - } - } - } + qq!<A HREF="${p}view/cust_main.cgi?!. $cust_main->custnum. '">'. + $cust_pkg->cust_main->name. '</A>'; +}; - if ( $svc_acct ) { - my $svcnum = $svc_acct->svcnum; - qq(<A HREF="${p}view/svc_acct.cgi?$svcnum"><B>$user</B></A>); - } else { - "<B>$user</B>"; - } +my $time_format = sub { + my $time = shift; + return ' ' if $time == 0; + my $pretty = time2str('%T%P %a %b %o %Y', $time ); + $pretty =~ s/ (\d)(st|dn|rd|th)/$1$2/; + $pretty; +}; - }; - - my $customer_format = sub { - my( $unused, $session ) = @_; - return ' ' unless exists $user2svc_acct{$session->{'username'}}; - my $svc_acct = $user2svc_acct{$session->{'username'}}; - my $cust_pkg = $svc_acct->cust_svc->cust_pkg; - return ' ' unless $cust_pkg; - my $cust_main = $cust_pkg->cust_main; - - qq!<A HREF="${p}view/cust_main.cgi?!. $cust_main->custnum. '">'. - $cust_pkg->cust_main->name. '</A>'; - }; - - my $time_format = sub { - my $time = shift; - return ' ' if $time == 0; - my $pretty = time2str('%T%P %a %b %o %Y', $time ); - $pretty =~ s/ (\d)(st|dn|rd|th)/$1$2/; - $pretty; - }; - - my $duration_format = sub { - my $seconds = shift; - my $hour = int($seconds/3600); - my $min = int( ($seconds%3600) / 60 ); - my $sec = $seconds%60; - '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>'. - '<TR><TD ALIGN="right">'. - ( $hour ? "<B>$hour</B>h" : ' ' ). - '</TD><TD ALIGN="right">'. - ( ( $hour || $min ) ? "<B>$min</B>m" : ' ' ). - '</TD><TD ALIGN="right">'. - "<B>$sec</B>s". - '</TD></TR></TABLE>'; - }; - - my $octets_format = sub { - my $octets = shift; - my $megs = $octets / 1048576; - sprintf('<B>%.3f</B> megs', $megs); - #my $gigs = $octets / 1073741824 - #sprintf('<B>%.3f</B> gigabytes', $gigs); - }; - - ### - # the fields - ### - - tie my %fields, 'Tie::IxHash', - 'username' => { - name => 'User', - attrib => 'UserName', - fmt => $user_format, - align => 'left', - }, - 'realm' => { - name => 'Realm', - attrib => 'Realm', - align => 'left', - }, - 'dummy' => { - name => 'Customer', - attrib => '', - fmt => $customer_format, - align => 'left', - }, - 'framedipaddress' => { - name => 'IP Address', - attrib => 'Framed-IP-Address', - fmt => sub { my $ip = shift; - length($ip) ? $ip : ' '; - }, - align => 'right', - }, - 'acctstarttime' => { - name => 'Start time', - attrib => 'Acct-Start-Time', - fmt => $time_format, - align => 'left', - }, - 'acctstoptime' => { - name => 'End time', - attrib => 'Acct-Stop-Time', - fmt => $time_format, - align => 'left', - }, - 'acctsessiontime' => { - name => 'Duration', - attrib => 'Acct-Session-Time', - fmt => $duration_format, - align => 'right', - }, - 'acctinputoctets' => { - name => 'Upload', # (from user)', - attrib => 'Acct-Input-Octets', - fmt => $octets_format, - align => 'right', - }, - 'acctoutputoctets' => { - name => 'Download', # (to user)', - attrib => 'Acct-Output-Octets', - fmt => $octets_format, - align => 'right', - }, - ; - $fields{$_}->{fmt} ||= sub { length($_[0]) ? shift : ' '; } - foreach keys %fields; - - ### - # and finally, display the thing - ### - - foreach my $part_export ( - #grep $_->can('usage_sessions'), qsearch( 'part_export' ) - qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ), - qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ) - ) { - %user2svc_acct = (); - - my $efields = tie my %efields, 'Tie::IxHash', %fields; - delete $efields{'framedipaddress'} if $part_export->option('hide_ip'); - if ( $part_export->option('hide_data') ) { - delete $efields{$_} foreach qw(acctinputoctets acctoutputoctets); - } - if ( $part_export->option('show_called_station') ) { - $efields->Splice(1, 0, - 'calledstationid' => { - 'name' => 'Destination', - 'attrib' => 'Called-Station-ID', - 'fmt' => - sub { length($_[0]) ? shift : ' '; }, - 'align' => 'left', - }, - ); - } +my $duration_format = sub { + my $seconds = shift; + my $hour = int($seconds/3600); + my $min = int( ($seconds%3600) / 60 ); + my $sec = $seconds%60; + '<TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0>'. + '<TR><TD CLASS="inv" ALIGN="right">'. + ( $hour ? "<B>$hour</B>h" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + ( ( $hour || $min ) ? "<B>$min</B>m" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + "<B>$sec</B>s". + '</TD></TR></TABLE>'; +}; -%> +my $octets_format = sub { + my $octets = shift; + my $megs = $octets / 1048576; + sprintf('<B>%.3f</B> megs', $megs); + #my $gigs = $octets / 1073741824 + #sprintf('<B>%.3f</B> gigabytes', $gigs); +}; -<%= $part_export->exporttype %> to <%= $part_export->machine %><BR> -<%= include( '/elements/table.html' ) %> -<TR> - <% foreach my $field ( keys %efields ) { %> - <TH> - <%= $efields{$field}->{name} %><BR> - <FONT SIZE=-2><%= $efields{$field}->{attrib} %></FONT> - </TH> - <% } %> -</TR> -<% foreach my $session ( - @{ $part_export->usage_sessions( - $beginning, $ending, $cgi_svc_acct, $ip, $prefix, ) } - ) { -%> - <TR> - <% foreach my $field ( keys %efields ) { %> - <TD ALIGN="<%= $efields{$field}->{align} %>"> - <%= &{ $efields{$field}->{fmt} }( $session->{$field}, - $session, - $part_export, - ) - %> - </TD> - <% } %> - </TR> -<% } %> +### +# the fields +### -</TABLE> -<BR><BR> +tie my %fields, 'Tie::IxHash', + 'username' => { + name => 'User', + attrib => 'UserName', + fmt => $user_format, + align => 'left', + }, + 'realm' => { + name => 'Realm', + attrib => 'Realm', + align => 'left', + }, + 'dummy' => { + name => 'Customer', + attrib => '', + fmt => $customer_format, + align => 'left', + }, + 'framedipaddress' => { + name => 'IP Address', + attrib => 'Framed-IP-Address', + fmt => sub { my $ip = shift; + length($ip) ? $ip : ' '; + }, + align => 'right', + }, + 'acctstarttime' => { + name => 'Start time', + attrib => 'Acct-Start-Time', + fmt => $time_format, + align => 'left', + }, + 'acctstoptime' => { + name => 'End time', + attrib => 'Acct-Stop-Time', + fmt => $time_format, + align => 'left', + }, + 'acctsessiontime' => { + name => 'Duration', + attrib => 'Acct-Session-Time', + fmt => $duration_format, + align => 'right', + }, + 'acctinputoctets' => { + name => 'Upload', # (from user)', + attrib => 'Acct-Input-Octets', + fmt => $octets_format, + align => 'right', + }, + 'acctoutputoctets' => { + name => 'Download', # (to user)', + attrib => 'Acct-Output-Octets', + fmt => $octets_format, + align => 'right', + }, +; +$fields{$_}->{fmt} ||= sub { length($_[0]) ? shift : ' '; } + foreach keys %fields; -<% } %> +</%init> diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html index 8f4878dbc..660a54f3c 100644 --- a/httemplate/search/sqlradius.html +++ b/httemplate/search/sqlradius.html @@ -1,12 +1,9 @@ -<%= include( '/elements/header.html', 'Search RADIUS sessions', '', '', ' -<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> -') %> +<% include( '/elements/header.html', 'Search RADIUS sessions' ) %> + <FORM NAME="OneTrueForm" ACTION="sqlradius.cgi" METHOD="GET"> -<% #include( '/elements/table.html' ) %> -<%= ntable('#cccccc') %> +% #include( '/elements/table.html' ) + +<% ntable('#cccccc') %> <TR> <TD ALIGN="right">Username: </TD> <TD><INPUT TYPE="text" NAME="username"></TD> @@ -15,13 +12,12 @@ <TD></TD> <TD><FONT SIZE="-1"><I>(leave blank to show all users)</I></FONT></TD> </TR> +% my @part_export = qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ); +% push @part_export, +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ); +% +% if ( grep { ! $_->option('hide_ip') } @part_export ) { -<% my @part_export = qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ); - push @part_export, - qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ); -%> - -<% if ( grep { ! $_->option('hide_ip') } @part_export ) { %> <TR> <TD ALIGN="right">IP address: </TD> <TD><INPUT TYPE="text" NAME="ip"></TD> @@ -30,9 +26,9 @@ <TD></TD> <TD><FONT SIZE="-1"><I>(leave blank to show all IPs)</I></FONT></TD> </TR> -<% } %> +% } +% if ( grep { $_->option('show_called_station') } @part_export ) { -<% if ( grep { $_->option('show_called_station') } @part_export ) { %> <TR> <TD ALIGN="right">Destination prefix:</TD> <TD><INPUT TYPE="text" NAME="prefix"></TD> @@ -45,50 +41,19 @@ <TD></TD> <TD><FONT SIZE="-1"><I>(leave blank to show all destinations)</I></FONT></TD> </TR> -<% } %> +% } + + +<% include( '/elements/tr-input-beginning_ending.html', 'input_time'=>1 ) %> -<TR> - <TD ALIGN="right">From: </TD> - <TD> - <INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"> - </TD> - <SCRIPT TYPE="text/javascript"> - Calendar.setup({ - inputField: "beginning_text", - ifFormat: "%m/%d/%Y", - button: "beginning_button", - align: "BR" - }); - </SCRIPT> -</TR> -<TR> - <TD></TD> - <TD><i>m/d/y</i></TD> -</TR> -<TR> - <TD ALIGN="right">To: </TD> - <TD> - <INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor:pointer" TITLE="Select date"> - </TD> - <SCRIPT TYPE="text/javascript"> - Calendar.setup({ - inputField: "ending_text", - ifFormat: "%m/%d/%Y", - button: "ending_button", - align: "BR" - }); - </SCRIPT> -</TR> -<TR> - <TD></TD> - <TD><i>m/d/y</i> - <BR><FONT SIZE="-1">(leave one or both dates blank for an open-ended search)</FONT> - </TD> -</TR> </TABLE> <BR><INPUT TYPE="submit" VALUE="View sessions"> </FORM> -</BODY> -</HTML> +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); +</%init> diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index b14591958..a702d604b 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -1,30 +1,84 @@ -<% - -my $orderby = 'ORDER BY svcnum'; +<% include( 'elements/search.html', + 'title' => 'Account Search Results', + 'name' => 'accounts', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Account', + 'UID', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'email', + 'uid', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); -my $cjoin = ''; my @extra_sql = (); -if ( $query =~ /^UN_(.*)$/ ) { - $query = $1; - $cjoin = 'LEFT JOIN cust_svc USING ( svcnum )'; - push @extra_sql, 'pkgnum IS NULL'; -} -if ( $query eq 'svcnum' ) { - #$orderby = "ORDER BY svcnum"; -} elsif ( $query eq 'username' ) { - $orderby = "ORDER BY LOWER(username)"; -} elsif ( $query eq 'uid' ) { - $orderby = "ORDER BY uid"; - push @extra_sql, "uid IS NOT NULL"; + if ( $cgi->param('domain') ) { + my $svc_domain = + qsearchs('svc_domain', { 'domain' => $cgi->param('domain') } ); + unless ( $svc_domain ) { + #it would be nice if this looked more like the other "not found" + #errors, but this will do for now. + eidiot "Domain ". $cgi->param('domain'). " not found at all"; + } else { + push @extra_sql, 'domsvc = '. $svc_domain->svcnum; + } + } + +my $orderby = 'ORDER BY svcnum'; +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $sortby = "LOWER($sortby)" + if $sortby eq 'username'; + push @extra_sql, "$sortby IS NOT NULL" + if $sortby eq 'uid'; + $orderby = "ORDER BY $sortby"; + } + } elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) { push @extra_sql, "popnum = $1"; $orderby = "ORDER BY LOWER(username)"; } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { - $cjoin ||= 'LEFT JOIN cust_svc USING ( svcnum )'; push @extra_sql, "svcpart = $1"; $orderby = "ORDER BY uid"; #$orderby = "ORDER BY svcnum"; @@ -72,12 +126,20 @@ if ( $query eq 'svcnum' ) { } +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + my $extra_sql = scalar(@extra_sql) ? ' WHERE '. join(' AND ', @extra_sql ) : ''; -my $count_query = "SELECT COUNT(*) FROM svc_acct $cjoin $extra_sql"; +my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql"; #if ( keys %svc_acct ) { # $count_query .= ' WHERE '. # join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}), @@ -90,14 +152,12 @@ my $sql_query = { 'hashref' => {}, # \%svc_acct, 'select' => join(', ', 'svc_acct.*', + 'part_svc.svc', 'cust_main.custnum', FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ]; @@ -110,31 +170,5 @@ my $link_cust = sub { } }; -%><%= include( 'elements/search.html', - 'title' => 'Account Search Results', - 'name' => 'accounts', - 'query' => $sql_query, - 'count_query' => $count_query, - 'redirect' => $link, - 'header' => [ '#', - 'Account', - 'UID', - 'Service', - FS::UI::Web::cust_header(), - ], - 'fields' => [ 'svcnum', - 'email', - 'uid', - 'svc', - \&FS::UI::Web::cust_fields, - ], - 'links' => [ $link, - $link, - $link, - '', - ( map { $link_cust } - FS::UI::Web::cust_header() - ), - ], - ) -%> +</%init> + 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 @@ -<HTML> - <HEAD> - <TITLE>Account Search</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Account Search - </FONT> - <BR><BR> - <FORM ACTION="svc_acct.cgi" METHOD="GET"> - Search for <B>username</B>: - <INPUT TYPE="text" NAME="username"> - - <P><INPUT TYPE="submit" VALUE="Search"> - - </FORM> - </BODY> -</HTML> - diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi index efadce600..1bbdbfcdb 100755 --- a/httemplate/search/svc_broadband.cgi +++ b/httemplate/search/svc_broadband.cgi @@ -1,96 +1,121 @@ -<% +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List services'); +% +%my $conf = new FS::Conf; +% +%my @svc_broadband = (); +%my $sortby=\*svcnum_sort; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% @svc_broadband=qsearch('svc_broadband',{}); +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_broadband = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_broadband; +% } +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'blocknum' ) { +% $sortby = \*blocknum_sort; +% } +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% +% @svc_broadband = +% qsearch( 'svc_broadband', {}, '', +% " WHERE $1 = ( SELECT svcpart FROM cust_svc ". +% " WHERE cust_svc.svcnum = svc_external.svcnum ) " +% ); +% +%} elsif ( $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { +% my $ip_addr = $1; +% @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr}); +%} +% +%my %routerbyblock = (); +%foreach my $router (qsearch('router', {})) { +% foreach ($router->addr_block) { +% $routerbyblock{$_->blocknum} = $router; +% } +%} +% +%if ( scalar(@svc_broadband) == 1 ) { +% print $cgi->redirect(popurl(2). "view/svc_broadband.cgi?". $svc_broadband[0]->svcnum); +% #exit; +%} elsif ( scalar(@svc_broadband) == 0 ) { +% -my $conf = new FS::Conf; - -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors -my(@svc_broadband,$sortby); -if ( $query eq 'svcnum' ) { - $sortby=\*svcnum_sort; - @svc_broadband=qsearch('svc_broadband',{}); -} elsif ( $query eq 'blocknum' ) { - $sortby=\*blocknum_sort; - @svc_broadband=qsearch('svc_broadband',{}); -} else { - $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/; - my($ip_addr)=$1; - @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr}); -} - -my %routerbyblock = (); -foreach my $router (qsearch('router', {})) { - foreach ($router->addr_block) { - $routerbyblock{$_->blocknum} = $router; - } -} - -if ( scalar(@svc_broadband) == 1 ) { - print $cgi->redirect(popurl(2). "view/svc_broadband.cgi?". $svc_broadband[0]->svcnum); - #exit; -} elsif ( scalar(@svc_broadband) == 0 ) { -%> -<!-- mason kludge --> -<% - eidiot "No matching ip address found!\n"; -} else { -%> <!-- mason kludge --> -<% - my($total)=scalar(@svc_broadband); - print header("IP Address Search Results",''), <<END; - - $total matching broadband services found - <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> - <TR> - <TH>Service #</TH> - <TH>Router</TH> - <TH>IP Address</TH> - </TR> -END - - foreach my $svc_broadband ( - sort $sortby (@svc_broadband) - ) { - my($svcnum,$ip_addr,$routername,$routernum)=( - $svc_broadband->svcnum, - $svc_broadband->ip_addr, - $routerbyblock{$svc_broadband->blocknum}->routername, - $routerbyblock{$svc_broadband->blocknum}->routernum, - ); - - my $rowspan = 1; - - print <<END; - <TR> - <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$svcnum</A></TD> - <TD ROWSPAN=$rowspan><A HREF="${p}view/router.cgi?$routernum">$routername</A></TD> - <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$ip_addr</A></TD> -END - - #print @rows; - print "</TR>"; - - } - - print <<END; - </TABLE> - </BODY> -</HTML> -END - -} - -sub svcnum_sort { - $a->getfield('svcnum') <=> $b->getfield('svcnum'); -} - -sub blocknum_sort { - if ($a->getfield('blocknum') == $b->getfield('blocknum')) { - $a->getfield('ip_addr') cmp $b->getfield('ip_addr'); - } else { - $a->getfield('blocknum') cmp $b->getfield('blocknum'); - } -} +% +% eidiot "No matching ip address found!\n"; +%} else { +% +<!-- mason kludge --> +% +% my($total)=scalar(@svc_broadband); +% print header("IP Address Search Results",''), <<END; +% +% $total matching broadband services found +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Service #</TH> +% <TH>Router</TH> +% <TH>IP Address</TH> +% </TR> +%END +% +% foreach my $svc_broadband ( +% sort $sortby (@svc_broadband) +% ) { +% my($svcnum,$ip_addr,$routername,$routernum)=( +% $svc_broadband->svcnum, +% $svc_broadband->ip_addr, +% $routerbyblock{$svc_broadband->blocknum}->routername, +% $routerbyblock{$svc_broadband->blocknum}->routernum, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/router.cgi?$routernum">$routername</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$ip_addr</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub blocknum_sort { +% if ($a->getfield('blocknum') == $b->getfield('blocknum')) { +% $a->getfield('ip_addr') cmp $b->getfield('ip_addr'); +% } else { +% $a->getfield('blocknum') cmp $b->getfield('blocknum'); +% } +%} +% +% +% -%> diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index f261ea9f3..b14a1cc4f 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -1,56 +1,102 @@ -<% +<% include( 'elements/search.html', + 'title' => "Domain Search Results", + 'name' => 'domains', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Domain', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'domain', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> -my $conf = new FS::Conf; +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors +my $conf = new FS::Conf; my $orderby = 'ORDER BY svcnum'; -my $join = ''; my %svc_domain = (); -my $extra_sql = ''; -if ( $query eq 'svcnum' ) { - #$orderby = 'ORDER BY svcnum'; -} elsif ( $query eq 'domain' ) { - $orderby = 'ORDER BY domain'; -} elsif ( $query eq 'UN_svcnum' ) { - #$orderby = 'ORDER BY svcnum'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = ' WHERE pkgnum IS NULL'; -} elsif ( $query eq 'UN_domain' ) { - $orderby = 'ORDER BY domain'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = ' WHERE pkgnum IS NULL'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { - #$orderby = 'ORDER BY svcnum'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = " WHERE svcpart = $1"; + push @extra_sql, "svcpart = $1"; } else { $cgi->param('domain') =~ /^([\w\-\.]+)$/; - $join = ''; $svc_domain{'domain'} = $1; } -my $count_query = "SELECT COUNT(*) FROM svc_domain $join $extra_sql"; +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = ''; +if ( @extra_sql ) { + $extra_sql = ( keys(%svc_domain) ? ' AND ' : ' WHERE ' ). + join(' AND ', @extra_sql ); +} + +my $count_query = "SELECT COUNT(*) FROM svc_domain $addl_from "; if ( keys %svc_domain ) { $count_query .= ' WHERE '. join(' AND ', map "$_ = ". dbh->quote($svc_domain{$_}), keys %svc_domain ); } +$count_query .= $extra_sql; my $sql_query = { 'table' => 'svc_domain', 'hashref' => \%svc_domain, 'select' => join(', ', 'svc_domain.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '. - 'LEFT JOIN cust_pkg USING ( pkgnum ) '. - 'LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; my $link = [ "${p}view/svc_domain.cgi?", 'svcnum' ]; @@ -61,25 +107,4 @@ my $link_cust = sub { $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; }; -%><%= include ('elements/search.html', - 'title' => "Domain Search Results", - 'name' => 'domains', - 'query' => $sql_query, - 'count_query' => $count_query, - 'redirect' => $link, - 'header' => [ '#', - 'Domain', - FS::UI::Web::cust_header(), - ], - 'fields' => [ 'svcnum', - 'domain', - \&FS::UI::Web::cust_fields, - ], - 'links' => [ $link, - $link, - ( map { $link_cust } - FS::UI::Web::cust_header() - ), - ], - ) -%> +</%init> 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 @@ -<HTML> - <HEAD> - <TITLE>Domain Search</TITLE> - </HEAD> - <BODY BGCOLOR="#e8e8e8"> - <FONT SIZE=7> - Domain Search - </FONT> - <BR><BR> - <FORM ACTION="svc_domain.cgi" METHOD="GET"> - Search for <B>domain</B>: - <INPUT TYPE="text" NAME="domain"> - - <P><INPUT TYPE="submit" VALUE="Search"> - - </FORM> - </BODY> -</HTML> - diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index c5ac13498..2710d75bc 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -1,101 +1,153 @@ -<% +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List services'); +% +%my $conf = new FS::Conf; +% +%my @svc_external = (); +%my @h_svc_external = (); +%my $sortby=\*svcnum_sort; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% @svc_external=qsearch('svc_external',{}); +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_external = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_external; +% } +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'id' ) { +% $sortby = \*id_sort; +% } +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% +% @svc_external = +% qsearch( 'svc_external', {}, '', +% " WHERE $1 = ( SELECT svcpart FROM cust_svc ". +% " WHERE cust_svc.svcnum = svc_external.svcnum ) " +% ); +% +%} elsif ( $cgi->param('title') =~ /^(.*)$/ ) { +% $sortby=\*id_sort; +% @svc_external=qsearch('svc_external',{ title => $1 }); +% if( $cgi->param('history') == 1 ) { +% @h_svc_external=qsearch('h_svc_external',{ title => $1 }); +% } +%} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) { +% my $id = $1; +% @svc_external = qsearchs('svc_external',{'id'=>$id}); +%} +% +%if ( scalar(@svc_external) == 1 ) { +% +% +<% $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %> +% +% +%} elsif ( scalar(@svc_external) == 0 ) { +% +% +<% include('/elements/header.html', 'External Search Results' ) %> -my $conf = new FS::Conf; + No matching external services found +% } else { +% +% +<% include('/elements/header.html', 'External Search Results', '') %> -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors -my(@svc_external,$sortby); -if ( $query eq 'svcnum' ) { - $sortby=\*svcnum_sort; - @svc_external=qsearch('svc_external',{}); -} elsif ( $query eq 'id' ) { - $sortby=\*id_sort; - @svc_external=qsearch('svc_external',{}); -} elsif ( $query eq 'UN_svcnum' ) { - $sortby=\*svcnum_sort; - @svc_external = grep qsearchs('cust_svc',{ - 'svcnum' => $_->svcnum, - 'pkgnum' => '', - }), qsearch('svc_external',{}); -} elsif ( $query eq 'UN_id' ) { - $sortby=\*id_sort; - @svc_external = grep qsearchs('cust_svc',{ - 'svcnum' => $_->svcnum, - 'pkgnum' => '', - }), qsearch('svc_external',{}); -} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { - @svc_external = - qsearch( 'svc_external', {}, '', - " WHERE $1 = ( SELECT svcpart FROM cust_svc ". - " WHERE cust_svc.svcnum = svc_external.svcnum ) " - ); - $sortby=\*svcnum_sort; -} else { - $cgi->param('id') =~ /^([\w\-\.]+)$/; - my($id)=$1; - #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain}); - @svc_external = qsearchs('svc_external',{'id'=>$id}); -} - -if ( scalar(@svc_external) == 1 ) { - print $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum); - #exit; -} elsif ( scalar(@svc_external) == 0 ) { -%> -<!-- mason kludge --> -<% - eidiot "No matching external services found!\n"; -} else { -%> -<!-- mason kludge --> -<%= header("External Search Results",'') %> - - <%= scalar(@svc_external) %> matching external services found + <% scalar(@svc_external) %> matching external services found <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> <TR> <TH>Service #</TH> - <TH><%= FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TH> - <TH><%= FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH> + <TH><% FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TH> + <TH><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH> </TR> +% +% foreach my $svc_external ( +% sort $sortby (@svc_external) +% ) { +% my($svcnum, $id, $title)=( +% $svc_external->svcnum, +% $svc_external->id, +% $svc_external->title, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% if( scalar(@h_svc_external) > 0 ) { +% print <<HTML; +% </TABLE> +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Freeside ID</TH> +% <TH>Service #</TH> +% <TH>Title</TH> +% <TH>Date</TH> +% </TR> +%HTML +% +% foreach my $h_svc ( @h_svc_external ) { +% my($svcnum, $id, $title, $user, $date)=( +% $h_svc->svcnum, +% $h_svc->id, +% $h_svc->title, +% $h_svc->history_user, +% $h_svc->history_date, +% ); +% my $rowspan = 1; +% my ($h_cust_svc) = qsearchs( 'h_cust_svc', { +% svcnum => $svcnum, +% }); +% my $cust_pkg = qsearchs( 'cust_pkg', { +% pkgnum => $h_cust_svc->pkgnum, +% }); +% my $custnum = $cust_pkg->custnum; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$title</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$date</A></TD> +% </TR> +%END +% } +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub id_sort { +% $a->getfield('id') <=> $b->getfield('id'); +%} +% +% -<% - foreach my $svc_external ( - sort $sortby (@svc_external) - ) { - my($svcnum, $id, $title)=( - $svc_external->svcnum, - $svc_external->id, - $svc_external->title, - ); - - my $rowspan = 1; - - print <<END; - <TR> - <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD> - <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD> - <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD> -END - - #print @rows; - print "</TR>"; - - } - - print <<END; - </TABLE> - </BODY> -</HTML> -END - -} - -sub svcnum_sort { - $a->getfield('svcnum') <=> $b->getfield('svcnum'); -} - -sub id_sort { - $a->getfield('id') <=> $b->getfield('id'); -} - -%> diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index a204e345f..eeb4c1075 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -1,46 +1,95 @@ -<% +<% include( 'elements/search.html', + 'title' => "Mail forward Search Results", + 'name' => 'mail forwards', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Mail to', + 'Forwards to', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + $format_src, + $format_dst, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link_src, + $link_dst, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + my $conf = new FS::Conf; -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors +my $orderby = 'ORDER BY svcnum'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; -my $orderby; + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } -my $cjoin = ''; -my @extra_sql = (); -if ( $query =~ /^UN_(.*)$/ ) { - $query = $1; - $cjoin = 'LEFT JOIN cust_svc USING ( svcnum )'; - push @extra_sql, 'pkgnum IS NULL'; +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + push @extra_sql, "svcpart = $1"; } -if ( $query eq 'svcnum' ) { - $orderby = 'ORDER BY svcnum'; -} else { - eidiot('unimplemented'); -} +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; my $extra_sql = scalar(@extra_sql) ? ' WHERE '. join(' AND ', @extra_sql ) : ''; -my $count_query = "SELECT COUNT(*) FROM svc_forward $cjoin $extra_sql"; +my $count_query = "SELECT COUNT(*) FROM svc_forward $addl_from $extra_sql"; my $sql_query = { 'table' => 'svc_forward', 'hashref' => {}, 'select' => join(', ', 'svc_forward.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; # <TH>Service #<BR><FONT SIZE=-1>(click to view forward)</FONT></TH> @@ -93,28 +142,4 @@ my $link_cust = sub { $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; }; -%><%= include( 'elements/search.html', - 'title' => "Mail forward Search Results", - 'name' => 'mail forwards', - 'query' => $sql_query, - 'count_query' => $count_query, - 'redirect' => $link, - 'header' => [ '#', - 'Mail to', - 'Forwards to', - FS::UI::Web::cust_header(), - ], - 'fields' => [ 'svcnum', - $format_src, - $format_dst, - \&FS::UI::Web::cust_fields, - ], - 'links' => [ $link, - $link_src, - $link_dst, - ( map { $link_cust } - FS::UI::Web::cust_header() - ), - ], - ) -%> +</%init> diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi new file mode 100644 index 000000000..0c1d57887 --- /dev/null +++ b/httemplate/search/svc_phone.cgi @@ -0,0 +1,115 @@ +<% include( 'elements/search.html', + 'title' => "Phone number search results", + 'name' => 'phone numbers', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Country code', + 'Phone number', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'countrycode', + 'phonenum', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlrr'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my %svc_phone = (); +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + push @extra_sql, "svcpart = $1"; +} else { + $cgi->param('phonenum') =~ /^([\d\- ]+)$/; + ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = ''; +if ( @extra_sql ) { + $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ). + join(' AND ', @extra_sql ); +} + +my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from "; +if ( keys %svc_phone ) { + $count_query .= ' WHERE '. + join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}), + keys %svc_phone + ); +} +$count_query .= $extra_sql; + +my $sql_query = { + 'table' => 'svc_phone', + 'hashref' => \%svc_phone, + 'select' => join(', ', + 'svc_phone.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ]; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index ae51c61fc..d9332e95d 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -1,53 +1,17 @@ -<% - -#my $conf = new FS::Conf; - -my($query)=$cgi->keywords; -$query ||= ''; #to avoid use of unitialized value errors -my $orderby; -if ( $query eq 'svcnum' ) { - $orderby = 'ORDER BY svcnum'; -} else { - eidiot('unimplemented'); -} - -my $count_query = 'SELECT COUNT(*) FROM svc_www'; -my $sql_query = { - 'table' => 'svc_www', - 'hashref' => {}, - 'select' => join(', ', - 'svc_www.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'extra_sql' => $orderby, - 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )'. - 'LEFT JOIN cust_pkg USING ( pkgnum )'. - 'LEFT JOIN cust_main USING ( custnum )', -}; - -my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ]; -#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ]; -my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ]; - -#smaller false laziness w/svc_*.cgi here -my $link_cust = sub { - my $svc_x = shift; - $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; -}; - -%><%= include( 'elements/search.html', +<% include( 'elements/search.html', 'title' => 'Virtual Host Search Results', 'name' => 'virtual hosts', 'query' => $sql_query, 'count_query' => $count_query, 'redirect' => $link, 'header' => [ '#', + 'Service', 'Zone', 'User', FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', + 'svc', sub { $_[0]->domain_record->zone }, sub { my $svc_www = shift; @@ -59,11 +23,89 @@ my $link_cust = sub { \&FS::UI::Web::cust_fields, ], 'links' => [ $link, + $link, '', $ulink, - ( map { $link_cust } + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], + 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], ) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +#my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + push @extra_sql, "svcpart = $1"; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = + scalar(@extra_sql) + ? ' WHERE '. join(' AND ', @extra_sql ) + : ''; + + +my $count_query = "SELECT COUNT(*) FROM svc_www $addl_from $extra_sql"; +my $sql_query = { + 'table' => 'svc_www', + 'hashref' => {}, + 'select' => join(', ', + 'svc_www.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ]; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi index 235485f6b..e2f810c3f 100755 --- a/httemplate/view/cust_bill-logo.cgi +++ b/httemplate/view/cust_bill-logo.cgi @@ -1,4 +1,8 @@ -<% +<% $conf->config_binary("logo$templatename.png") %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View invoices'); my $conf = new FS::Conf; @@ -12,4 +16,5 @@ if ( $templatename && $conf->exists("logo_$templatename.png") ) { } http_header('Content-Type' => 'image/png' ); -%><%= $conf->config_binary("logo$templatename.png") %> + +</%init> diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi index ce7ab0c5c..f09e1b74d 100755 --- a/httemplate/view/cust_bill-pdf.cgi +++ b/httemplate/view/cust_bill-pdf.cgi @@ -1,4 +1,8 @@ -<% +<% $pdf %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View invoices'); #untaint invnum my($query) = $cgi->keywords; @@ -6,7 +10,13 @@ $query =~ /^((.+)-)?(\d+)(.pdf)?$/; my $templatename = $2; my $invnum = $3; -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +my $cust_bill = qsearchs({ + 'select' => 'cust_bill.*', + 'table' => 'cust_bill', + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'invnum' => $invnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); die "Invoice #$invnum not found!" unless $cust_bill; my $pdf = $cust_bill->print_pdf( '', $templatename); @@ -14,4 +24,5 @@ my $pdf = $cust_bill->print_pdf( '', $templatename); http_header('Content-Type' => 'application/pdf' ); http_header('Content-Length' => length($pdf) ); http_header('Cache-control' => 'max-age=60' ); -%><%= $pdf %> + +</%init> diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi index e730a822a..5313dbf02 100755 --- a/httemplate/view/cust_bill-ps.cgi +++ b/httemplate/view/cust_bill-ps.cgi @@ -1,4 +1,8 @@ -<% +<% $cust_bill->print_ps( '', $templatename) %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View invoices'); #untaint invnum my($query) = $cgi->keywords; @@ -6,8 +10,15 @@ $query =~ /^((.+)-)?(\d+)$/; my $templatename = $2; my $invnum = $3; -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +my $cust_bill = qsearchs({ + 'select' => 'cust_bill.*', + 'table' => 'cust_bill', + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'invnum' => $invnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); die "Invoice #$invnum not found!" unless $cust_bill; http_header('Content-Type' => 'application/postscript' ); -%><%= $cust_bill->print_ps( '', $templatename) %> + +</%init> diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index 56c0c1736..42e1e6177 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -1,151 +1,165 @@ -<% - -#untaint invnum -my($query) = $cgi->keywords; -$query =~ /^((.+)-)?(\d+)$/; -my $templatename = $2; -my $invnum = $3; - -my $conf = new FS::Conf; - -my @payby = grep /\w/, $conf->config('payby'); -#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) -@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) - unless @payby; -my %payby = map { $_=>1 } @payby; - -my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); -die "Invoice #$invnum not found!" unless $cust_bill; -my $custnum = $cust_bill->getfield('custnum'); - -#my $printed = $cust_bill->printed; - -my $link = $templatename ? "$templatename-$invnum" : $invnum; - -%> -<%= header('Invoice View', menubar( +<% include("/elements/header.html",'Invoice View', menubar( "Main Menu" => $p, "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", )) %> -<% if ( $cust_bill->owed > 0 - && ( $payby{'BILL'} || $payby{'CASH'} || $payby{'WEST'} || $payby{'MCRD'} ) - ) - { - my $s = 0; -%> + +% if ( $cust_bill->owed > 0 +% && ( $payby{'BILL'} || $payby{'CASH'} || $payby{'WEST'} || $payby{'MCRD'} ) +% ) +% { +% my $s = 0; Post +% if ( $payby{'BILL'} ) { - <% if ( $payby{'BILL'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=BILL;invnum=<%= $invnum %>">check</A> - - <% } %> - <% if ( $payby{'CASH'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=CASH;invnum=<%= $invnum %>">cash</A> - - <% } %> - - <% if ( $payby{'WEST'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=WEST;invnum=<%= $invnum %>">Western Union</A> + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=BILL;invnum=<% $invnum %>">check</A> +% } +% if ( $payby{'CASH'} ) { + - <% } %> + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=CASH;invnum=<% $invnum %>">cash</A> +% } +% if ( $payby{'WEST'} ) { - <% if ( $payby{'MCRD'} ) { %> - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=MCRD;invnum=<%= $invnum %>">manual credit card</A> + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;invnum=<% $invnum %>">Western Union</A> +% } +% if ( $payby{'MCRD'} ) { + - <% } %> + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;invnum=<% $invnum %>">manual credit card</A> +% } + payment against this invoice<BR> +% } -<% } %> -<A HREF="<%= $p %>misc/print-invoice.cgi?<%= $link %>">Re-print this invoice</A> +<A HREF="<% $p %>misc/print-invoice.cgi?<% $link %>">Re-print this invoice</A> +% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { -<% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { %> - | <A HREF="<%= $p %>misc/email-invoice.cgi?<%= $link %>">Re-email + | <A HREF="<% $p %>misc/email-invoice.cgi?<% $link %>">Re-email this invoice</A> -<% } %> +% } +% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { -<% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { %> - | <A HREF="<%= $p %>misc/fax-invoice.cgi?<%= $link %>">Re-fax + | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $link %>">Re-fax this invoice</A> -<% } %> +% } + <BR><BR> +% if ( $conf->exists('invoice_latex') ) { -<% if ( $conf->exists('invoice_latex') ) { %> - <A HREF="<%= $p %>view/cust_bill-pdf.cgi?<%= $link %>.pdf">View typeset invoice</A> + <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>.pdf">View typeset invoice</A> <BR><BR> -<% } %> +% } +% #false laziness with search/cust_bill_event.cgi +% unless ( $templatename ) { -<% #false laziness with search/cust_bill_event.cgi - unless ( $templatename ) { %> - <%= table() %> + <% table() %> <TR> <TH>Event</TH> <TH>Date</TH> <TH>Status</TH> </TR> +% foreach my $cust_bill_event ( +% sort { $a->_date <=> $b->_date } $cust_bill->cust_bill_event +% ) { +% +% my $status = $cust_bill_event->status; +% $status .= ': '. encode_entities($cust_bill_event->statustext) +% if $cust_bill_event->statustext; +% my $part_bill_event = $cust_bill_event->part_bill_event; +% - <% foreach my $cust_bill_event ( - sort { $a->_date <=> $b->_date } $cust_bill->cust_bill_event - ) { - - my $status = $cust_bill_event->status; - $status .= ': '. encode_entities($cust_bill_event->statustext) - if $cust_bill_event->statustext; - my $part_bill_event = $cust_bill_event->part_bill_event; - %> <TR> - <TD><%= $part_bill_event->event %> - - <% if ( $part_bill_event->templatename ) { - my $alt_templatename = $part_bill_event->templatename; - my $alt_link = "$alt_templatename-$invnum"; - %> - ( <A HREF="<%= $p %>view/cust_bill.cgi?<%= $alt_link %>">view</A> - | <A HREF="<%= $p %>view/cust_bill-pdf.cgi?<%= $alt_link %>.pdf">view + <TD><% $part_bill_event->event %> +% if ( $part_bill_event->templatename ) { +% my $alt_templatename = $part_bill_event->templatename; +% my $alt_link = "$alt_templatename-$invnum"; +% + + ( <A HREF="<% $p %>view/cust_bill.cgi?<% $alt_link %>">view</A> + | <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $alt_link %>.pdf">view typeset</A> - | <A HREF="<%= $p %>misc/print-invoice.cgi?<%= $alt_link %>">re-print</A> - <% if ( grep { $_ ne 'POST' } - $cust_bill->cust_main->invoicing_list ) { %> - | <A HREF="<%= $p %>misc/email-invoice.cgi?<%= $alt_link %>">re-email</A> - <% } %> - - <% if ( $conf->exists('hylafax') - && length($cust_bill->cust_main->fax) ) { %> - | <A HREF="<%= $p %>misc/fax-invoice.cgi?<%= $alt_link %>">re-fax</A> - <% } %> + | <A HREF="<% $p %>misc/print-invoice.cgi?<% $alt_link %>">re-print</A> +% if ( grep { $_ ne 'POST' } +% $cust_bill->cust_main->invoicing_list ) { + + | <A HREF="<% $p %>misc/email-invoice.cgi?<% $alt_link %>">re-email</A> +% } +% if ( $conf->exists('hylafax') +% && length($cust_bill->cust_main->fax) ) { + + | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $alt_link %>">re-fax</A> +% } + ) - <% } %> +% } + </TD> - <TD><%= time2str("%a %b %e %T %Y", $cust_bill_event->_date) %></TD> - <TD><%= $status %></TD> + <TD><% time2str("%a %b %e %T %Y", $cust_bill_event->_date) %></TD> + <TD><% $status %></TD> </TR> - <% } %> +% } + </TABLE> <BR> +% } +% if ( $conf->exists('invoice_html') ) { + + <% join('', $cust_bill->print_html('', $templatename) ) %> +% } else { + + <PRE><% join('', $cust_bill->print_text('', $templatename) ) %></PRE> +% } + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View invoices'); + +#untaint invnum +my($query) = $cgi->keywords; +$query =~ /^((.+)-)?(\d+)$/; +my $templatename = $2; +my $invnum = $3; + +my $conf = new FS::Conf; + +my @payby = grep /\w/, $conf->config('payby'); +#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) + unless @payby; +my %payby = map { $_=>1 } @payby; + +my $cust_bill = qsearchs({ + 'select' => 'cust_bill.*', + 'table' => 'cust_bill', + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'invnum' => $invnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); +die "Invoice #$invnum not found!" unless $cust_bill; + +my $custnum = $cust_bill->custnum; + +#my $printed = $cust_bill->printed; + +my $link = $templatename ? "$templatename-$invnum" : $invnum; -<% } %> +</%init> -<% if ( $conf->exists('invoice_html') ) { %> - <%= join('', $cust_bill->print_html('', $templatename) ) %> -<% } else { %> - <PRE><%= join('', $cust_bill->print_text('', $templatename) ) %></PRE> -<% } %> -</BODY></HTML> diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 59c1a4b73..850b48b27 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -1,37 +1,13 @@ -<!-- mason kludge --> -<% +<% include("/elements/header.html","Customer View: ". $cust_main->name ) %> -my $conf = new FS::Conf; - -my %uiview = (); -my %uiadd = (); -foreach my $part_svc ( qsearch('part_svc',{}) ) { - $uiview{$part_svc->svcpart} = $p. "view/". $part_svc->svcdb . ".cgi"; - $uiadd{$part_svc->svcpart}= $p. "edit/". $part_svc->svcdb . ".cgi"; -} - -%> - -<%= header("Customer View", menubar( - 'Main Menu' => $p, -)) %> - -<% - -die "No customer specified (bad URL)!" unless $cgi->keywords; -my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array -$query =~ /^(\d+)$/; -my $custnum = $1; -my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); -die "Customer not found!" unless $cust_main; - -print qq!<A HREF="${p}edit/cust_main.cgi?$custnum">Edit this customer</A>!; +% if ( $curuser->access_right('Edit customer') ) { + <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> | +% } -%> - -<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> -<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/iframecontentmws.js"></SCRIPT> <SCRIPT TYPE="text/javascript"> function areyousure(href, message) { @@ -41,98 +17,155 @@ function areyousure(href, message) { </SCRIPT> <SCRIPT TYPE="text/javascript"> - -<% -my $ban = ''; -if ( $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) { - $ban = '<BR><P ALIGN="center">'. - '<INPUT TYPE="checkbox" NAME="ban" VALUE="1"> Ban this customer\\\'s '; - if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { - $ban .= 'credit card'; - } elsif ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { - $ban .= 'ACH account'; - } -} -%> - -var confirm_cancel = '<FORM METHOD="POST" ACTION="<%= $p %>misc/cust_main-cancel.cgi"> <INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>"> <BR><P ALIGN="center"><B>Permanently delete all services and cancel this customer?</B> <%= $ban%><BR><P ALIGN="CENTER"> <INPUT TYPE="submit" VALUE="Cancel customer"> <INPUT TYPE="BUTTON" VALUE="Don\'t cancel" onClick="cClick()"> </FORM> '; +% +%my $ban = ''; +%if ( $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) { +% $ban = '<BR><P ALIGN="center">'. +% '<INPUT TYPE="checkbox" NAME="ban" VALUE="1"> Ban this customer\\\'s '; +% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { +% $ban .= 'credit card'; +% } elsif ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { +% $ban .= 'ACH account'; +% } +%} +% + + +var confirm_cancel = '<FORM METHOD="POST" ACTION="<% $p %>misc/cust_main-cancel.cgi"> <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> <BR><P ALIGN="center"><B>Permanently delete all services and cancel this customer?</B> <% $ban%><BR><P ALIGN="CENTER"> <INPUT TYPE="submit" VALUE="Cancel customer"> <INPUT TYPE="BUTTON" VALUE="Don\'t cancel" onClick="cClick()"> </FORM> '; </SCRIPT> +% if ( $curuser->access_right('Cancel customer') +% && $cust_main->ncancelled_pkgs +% ) { +% -<% if ( $cust_main->ncancelled_pkgs ) { %> + <A HREF="javascript:void(0);" onClick="overlib(confirm_cancel, CAPTION, 'Confirm cancellation', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' ); return false; ">Cancel this customer</A> | +% } +% if ( $conf->exists('deletecustomers') +% && $curuser->access_right('Delete customer') +% ) { +% - | <A HREF="javascript:void(0);" onClick="overlib(confirm_cancel, CAPTION, 'Confirm cancellation', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' ); return false; ">Cancel this customer</A> + <A HREF="<% $p %>misc/delete-customer.cgi?<% $custnum%>">Delete this customer</A> | +% } +% unless ( $conf->exists('disable_customer_referrals') ) { -<% } %> + <A HREF="<% popurl(2) %>edit/cust_main.cgi?referral_custnum=<% $custnum %>">Refer a new customer</A> | + <A HREF="<% popurl(2) %>search/cust_main.cgi?referral_custnum=<% $custnum %>">View this customer's referrals</A> +% } -<% -print qq! | <A HREF="${p}misc/delete-customer.cgi?$custnum">!. - 'Delete this customer</A>' - if $conf->exists('deletecustomers'); - -unless ( $conf->exists('disable_customer_referrals') ) { - print qq! | <A HREF="!, popurl(2), - qq!edit/cust_main.cgi?referral_custnum=$custnum">!, - qq!Refer a new customer</A>!; - - print qq! | <A HREF="!, popurl(2), - qq!search/cust_main.cgi?referral_custnum=$custnum">!, - qq!View this customer's referrals</A>!; -} -print '<BR><BR>'; +<BR><BR> +% +%my $signupurl = $conf->config('signupurl'); +%if ( $signupurl ) { +% -my $signupurl = $conf->config('signupurl'); -if ( $signupurl ) { -print "This customer's signup URL: ". - "<a href=\"$signupurl?ref=$custnum\">$signupurl?ref=$custnum</a><BR><BR>"; -} + This customer's signup URL: <A HREF="<% $signupurl %>?ref=<% $custnum %>"><% $signupurl %>?ref=<% $custnum %></A><BR><BR> +% } -%> <A NAME="cust_main"></A> -<%= &itable() %> +<TABLE BORDER=0> <TR> <TD VALIGN="top"> - <%= include('cust_main/contacts.html', $cust_main ) %> + <% include('cust_main/contacts.html', $cust_main ) %> </TD> - <TD VALIGN="top"> - <%= include('cust_main/misc.html', $cust_main ) %> - <% if ( $conf->config('payby-default') ne 'HIDE' ) { %> + <TD VALIGN="top" STYLE="padding-left: 54px"> + <% include('cust_main/misc.html', $cust_main ) %> +% if ( $conf->config('payby-default') ne 'HIDE' ) { + <BR> - <%= include('cust_main/billing.html', $cust_main ) %> - <% } %> + <% include('cust_main/billing.html', $cust_main ) %> +% } + </TD> </TR> </TABLE> +% +%if ( $cust_main->comments =~ /[^\s\n\r]/ ) { +% -<% -if ( defined $cust_main->dbdef_table->column('comments') - && $cust_main->comments =~ /[^\s\n\r]/ ) { -%> <BR> Comments -<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %> +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> <TR> <TD BGCOLOR="#ffffff"> - <PRE><%= encode_entities($cust_main->comments) %></PRE> + <PRE><% encode_entities($cust_main->comments) %></PRE> </TD> </TR> </TABLE></TABLE> -<% } %> +% } +<BR><BR> +% my $notecount = scalar($cust_main->notes()); +% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) { + +<A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR> +% if ( $curuser->access_right('Add customer note') && +% ! $conf->exists('cust_main-disable_notes') +% ) { + + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_main_note.cgi?custnum=<% $cust_main->custnum %>', 616, 386, 'cust_main_note_popup' ), CAPTION, 'Enter customer note', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;">Add customer note</A> + +% } + +<BR> + +% if ($notecount) { + +<iframe src="<% $p %>view/cust_main/notes.html?custnum=<% $cust_main->custnum %>" height="186" width="616" name="cust_main_notes" frameborder="0" marginborder="0" marginheight="0" scrolling="auto"> + <div><br>[iframe not supported]<br><br></div> +</iframe> + +% }else{ # make firefox happy wrt POSTDATA + +<iframe src="<% $p %>view/cust_main/notes.html?custnum=<% $cust_main->custnum %>" height="24" width="616" name="cust_main_notes" frameborder="0" marginborder="0" marginheight="0" scrolling="auto"> + <div><br>[iframe not supported]<br><br></div> +</iframe> + +% } + +% } + + +% if ( $conf->config('ticket_system') ) { + + <BR><BR> + <% include('cust_main/tickets.html', $cust_main ) %> +% } -<% if ( $conf->config('ticket_system') ) { %> - <BR> - <%= include('cust_main/tickets.html', $cust_main ) %> -<% } %> <BR><BR> -<%= include('cust_main/packages.html', $cust_main ) %> -<% if ( $conf->config('payby-default') ne 'HIDE' ) { %> - <%= include('cust_main/payment_history.html', $cust_main ) %> -<% } %> +% #XXX enable me# if ( $curuser->access_right('View customer packages') { +<% include('cust_main/packages.html', $cust_main ) %> +% #} + +% if ( $conf->config('payby-default') ne 'HIDE' ) { + <% include('cust_main/payment_history.html', $cust_main ) %> +% } + + +<% include('/elements/footer.html') %> +<%init> -</BODY></HTML> +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('View customer'); + +my $conf = new FS::Conf; + +die "No customer specified (bad URL)!" unless $cgi->keywords; +my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array +$query =~ /^(\d+)$/; +my $custnum = $1; +my $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => {'custnum'=>$custnum}, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +die "Customer not found!" unless $cust_main; +</%init> diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index 5786a0711..1f80dc5bc 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -1,164 +1,192 @@ -<% - my( $cust_main ) = @_; - my @invoicing_list = $cust_main->invoicing_list; -%> +% +% my( $cust_main ) = @_; +% my @invoicing_list = $cust_main->invoicing_list; +% my $conf = new FS::Conf; +% my $money_char = $conf->config('money_char') || '$'; +% + Billing information -(<A HREF="<%= $p %>misc/bill.cgi?<%= $cust_main->custnum %>">Bill now</A>) -<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %> +% # If we can't see the unencrypted card, then bill now is an exercise in frustration +%if ( ! $cust_main->is_encrypted($cust_main->payinfo) ) { + (<A HREF="<% $p %>misc/bill.cgi?<% $cust_main->custnum %>">Bill now</A>) +% } + +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +% +%( my $balance = $cust_main->balance ) +% =~ s/^(\-?)(.*)$/<FONT SIZE=+1>$1<\/FONT>$money_char$2/; +% + + +<TR> + <TD ALIGN="right">Balance due</TD> + <TD BGCOLOR="#ffffff"><B><% $balance %></B></TD> +</TR> <TR> <TD ALIGN="right">Billing type</TD> <TD BGCOLOR="#ffffff"> +% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) { -<% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) { %> - Credit card <%= $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' %> + Credit card <% $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' %> </TD> </TR> <TR> <TD ALIGN="right">Card number</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payinfo_masked %></TD> -</TR> - -<% -#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html -my( $mon, $year ); -my $date = $cust_main->paydate || '12-2037'; -if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format - ( $mon, $year ) = ( $2, $1 ); -} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { - ( $mon, $year ) = ( $1, $3 ); -} else { - warn "unrecognized expiration date format: $date"; - ( $mon, $year ) = ( '', '' ); -} -%> + <TD BGCOLOR="#ffffff"><% $cust_main->paymask %></TD> +</TR> +% +%#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html +%my( $mon, $year ); +%my $date = $cust_main->paydate || '12-2037'; +%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +%} else { +% warn "unrecognized expiration date format: $date"; +% ( $mon, $year ) = ( '', '' ); +%} +% + <TR> <TD ALIGN="right">Expiration</TD> - <TD BGCOLOR="#ffffff"><%= "$mon/$year" %></TD> + <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD> </TR> +% if ( $cust_main->paystart_month ) { -<% if ( $cust_main->paystart_month ) { %> <TR> <TD ALIGN="right">Start date</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->paystart_month. '/'. $cust_main->paystart_year %> + <TD BGCOLOR="#ffffff"><% $cust_main->paystart_month. '/'. $cust_main->paystart_year %> </TR> -<% } elsif ( $cust_main->payissue ) { %> +% } elsif ( $cust_main->payissue ) { + <TR> <TD ALIGN="right">Issue #</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payissue %> + <TD BGCOLOR="#ffffff"><% $cust_main->payissue %> </TR> -<% } %> +% } + <TR> <TD ALIGN="right">Name on card</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payname %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> </TR> +% } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') { +% my( $account, $aba ) = split('@', $cust_main->payinfo ); +% -<% } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') { - my( $account, $aba ) = split('@', $cust_main->payinfo ); -%> - Electronic check <%= $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' %> + Electronic check <% $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' %> </TD> </TR> <TR> <TD ALIGN="right">ABA/Routing code</TD> - <TD BGCOLOR="#ffffff"><%= $aba %></TD> + <TD BGCOLOR="#ffffff"><% $aba %></TD> </TR> <TR> <TD ALIGN="right">Account number</TD> - <TD BGCOLOR="#ffffff"><%= 'x'x(length($account)-2). substr($account,(length($account)-2)) %></TD> + <TD BGCOLOR="#ffffff"><% 'x'x(length($account)-2). substr($account,(length($account)-2)) %></TD> </TR> <TR> <TD ALIGN="right">Bank name</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payname %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> </TR> +% } elsif ( $cust_main->payby eq 'LECB' ) { +% $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/; +% my $payinfo = "$1-$2-$3"; +% -<% } elsif ( $cust_main->payby eq 'LECB' ) { - $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/; - my $payinfo = "$1-$2-$3"; -%> Phone bill billing </TD> </TR> <TR> <TD ALIGN="right">Phone number</TD> - <TD BGCOLOR="#ffffff"><%= $payinfo %></TD> + <TD BGCOLOR="#ffffff"><% $payinfo %></TD> </TR> +% } elsif ( $cust_main->payby eq 'BILL' ) { -<% } elsif ( $cust_main->payby eq 'BILL' ) { %> Billing </TD> </TR> +% if ( $cust_main->payinfo ) { - <% if ( $cust_main->payinfo ) { %> <TR> <TD ALIGN="right">P.O. </TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payinfo %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD> </TR> - <% } %> +% } + <TR> <TD ALIGN="right">Attention</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payname %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> </TR> +% } elsif ( $cust_main->payby eq 'COMP' ) { -<% } elsif ( $cust_main->payby eq 'COMP' ) { %> Complimentary </TD> </TR> <TR> <TD ALIGN="right">Authorized by</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->payinfo %></TD> -</TR> - -<% -#false laziness w/above etc. -my( $mon, $year ); -my $date = $cust_main->paydate || '12-2037'; -if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format - ( $mon, $year ) = ( $2, $1 ); -} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { - ( $mon, $year ) = ( $1, $3 ); -} else { - warn "unrecognized expiration date format: $date"; - ( $mon, $year ) = ( '', '' ); -} -%> + <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD> +</TR> +% +%#false laziness w/above etc. +%my( $mon, $year ); +%my $date = $cust_main->paydate || '12-2037'; +%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +%} else { +% warn "unrecognized expiration date format: $date"; +% ( $mon, $year ) = ( '', '' ); +%} +% + <TR> <TD ALIGN="right">Expiration</TD> - <TD BGCOLOR="#ffffff"><%= "$mon/$year" %></TD> + <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD> </TR> +% } -<% } %> <TR> <TD ALIGN="right">Tax exempt</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->tax ? 'yes' : 'no' %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->tax ? 'yes' : 'no' %></TD> </TR> <TR> <TD ALIGN="right">Postal invoices</TD> <TD BGCOLOR="#ffffff"> - <%= ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no' %> + <% ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no' %> </TD> </TR> <TR> <TD ALIGN="right">FAX invoices</TD> <TD BGCOLOR="#ffffff"> - <%= ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'yes' : 'no' %> + <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'yes' : 'no' %> </TD> </TR> <TR> <TD ALIGN="right">Email invoices</TD> <TD BGCOLOR="#ffffff"> - <%= join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %> + <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %> </TD> </TR> +% if ( $conf->exists('voip-cust_cdr_spools') ) { + + <TR> + <TD ALIGN="right">Spool CDRs</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->spool_cdr ? 'yes' : 'no' %></TD> + </TR> +% } + </TABLE></TD></TR></TABLE> diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index 456d117a6..d5788c9a4 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -1,131 +1,100 @@ -<% - my( $cust_main ) = @_; - my $conf = new FS::Conf; -%> +% my %which = ( +% '' => 'Billing', +% 'ship_' => 'Service', +% ); +% foreach my $which ( '', 'ship_' ) { +% my $pre = $cust_main->get("${which}last") ? $which : ''; -Billing address -<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %> -<TR> - <TD ALIGN="right">Contact name</TD> - <TD COLSPAN=3 BGCOLOR="#ffffff"> - <%= $cust_main->last. ', '. $cust_main->first %> - </TD> -<% if ( $conf->exists('show_ss') ) { %> - <TD ALIGN="right">SS#</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->ss || ' ' %></TD> -<% } %> -</TR> -<TR> - <TD ALIGN="right">Company</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->company %></TD> -</TR> -<TR> - <TD ALIGN="right">Address</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->address1 %></TD> -</TR> -<% if ( $cust_main->address2 ) { %> -<TR> - <TD ALIGN="right"> </TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->address2 %></TD> -</TR> -<% } %> -<TR> - <TD ALIGN="right">City</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->city %></TD> - <TD ALIGN="right">State</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->state %></TD> - <TD ALIGN="right">Zip</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->zip %></TD> -</TR> -<TR> - <TD ALIGN="right">Country</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->country %></TD> -</TR> -<% - my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/ - ? 'Day Phone' - : FS::Msgcat::_gettext('daytime'); - my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/ - ? 'Night Phone' - : FS::Msgcat::_gettext('night'); -%> -<TR> - <TD ALIGN="right"><%= $daytime_label %></TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->daytime || ' ' %> - </TD> -</TR> -<TR> - <TD ALIGN="right"><%= $night_label %></TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->night || ' ' %> - </TD> -</TR> -<TR> - <TD ALIGN="right">Fax</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->fax || ' ' %> - </TD> -</TR> -</TABLE></TD></TR></TABLE> - -<% if ( defined $cust_main->dbdef_table->column('ship_last') ) { - my $pre = $cust_main->ship_last ? 'ship_' : ''; -%> - -<BR> -Service address -<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %> +<% $which{$which} %> address +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Contact name</TD> <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") %> + <% $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") %> </TD> +% if ( $which eq '' && $conf->exists('show_ss') ) { + <TD ALIGN="right">SS#</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->ss || ' ' %></TD> +% } </TR> <TR> <TD ALIGN="right">Company</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->get("${pre}company") %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") %></TD> </TR> <TR> <TD ALIGN="right">Address</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->get("${pre}address1") %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") %></TD> </TR> -<% if ( $cust_main->get("${pre}address2") ) { %> +% if ( $cust_main->get("${pre}address2") ) { + <TR> <TD ALIGN="right"> </TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"><%= $cust_main->get("${pre}address2") %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") %></TD> </TR> -<% } %> +% } + <TR> <TD ALIGN="right">City</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->get("${pre}city") %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") %></TD> +% if ( $cust_main->get("${pre}county") ) { + <TD ALIGN="right">County</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}county") %></TD> +% } <TD ALIGN="right">State</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->get("${pre}state") %></TD> + <TD BGCOLOR="#ffffff"><% state_label( $cust_main->get("${pre}state"), $cust_main->get("${pre}country") ) %></TD> <TD ALIGN="right">Zip</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->get("${pre}zip") %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}zip") %></TD> </TR> <TR> <TD ALIGN="right">Country</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->get("${pre}country") %></TD> + <TD BGCOLOR="#ffffff"><% code2country( $cust_main->get("${pre}country") ) %></TD> </TR> <TR> - <TD ALIGN="right"><%= $daytime_label %></TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->get("${pre}daytime") || ' ' %> + <TD ALIGN="right"><% $daytime_label %></TD> + <TD COLSPAN=3 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->get("${pre}daytime"), + 'callable'=>1 + ) + %> </TD> </TR> <TR> - <TD ALIGN="right"><%= $night_label %></TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->get("${pre}night") || ' ' %> + <TD ALIGN="right"><% $night_label %></TD> + <TD COLSPAN=3 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->get("${pre}night"), + 'callable'=>1 + ) + %> </TD> </TR> <TR> <TD ALIGN="right">Fax</TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <%= $cust_main->get("${pre}fax") || ' ' %> + <TD COLSPAN=3 BGCOLOR="#ffffff"> + <% $cust_main->get("${pre}fax") || ' ' %> </TD> </TR> </TABLE></TD></TR></TABLE> +% if ( $which ne 'ship_' ) { +<BR> +% } +% } + +<%once> + +my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/ + ? 'Day Phone' + : FS::Msgcat::_gettext('daytime'); +my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/ + ? 'Night Phone' + : FS::Msgcat::_gettext('night'); + +</%once> +<%init> + +my( $cust_main ) = @_; +my $conf = new FS::Conf; + +</%init> -<% } %> diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 69e120573..9528db243 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -1,63 +1,78 @@ -<% - my( $cust_main ) = @_; -%> +% +% my( $cust_main ) = @_; +% my $conf = new FS::Conf; +% my $date_format = ($conf->config('date_format') || "%m/%d/%Y"); +% + + +<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> -<%= ntable("#cccccc") %><TR><TD><%= &ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Customer number</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->custnum %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->custnum %></TD> </TR> -<% - my @agents = qsearch( 'agent', {} ); - my $agent; - unless ( scalar(@agents) == 1 ) { - $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } ); -%> +<TR> + <TD ALIGN="right">Status</TD> + <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% ucfirst($cust_main->status) %></B></FONT></TD> +</TR> +% +% my @agents = qsearch( 'agent', {} ); +% my $agent; +% unless ( scalar(@agents) == 1 ) { +% $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } ); +% + <TR> <TD ALIGN="right">Agent</TD> - <TD BGCOLOR="#ffffff"><%= $agent->agentnum %>: <%= $agent->agent %></TD> + <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD> </TR> +% +% } else { +% $agent = $agents[0]; +% } +% +% if ( $cust_main->agent_custid ) { +% -<% - } else { - $agent = $agents[0]; - } - - my @referrals = qsearch( 'part_referral', {} ); - unless ( scalar(@referrals) == 1 ) { - my $referral = qsearchs('part_referral', { - 'refnum' => $cust_main->refnum - } ); -%> <TR> - <TD ALIGN="right">Advertising source</TD> - <TD BGCOLOR="#ffffff"><%= $referral->refnum %>: <%= $referral->referral%></TD> + <TD ALIGN="right">Agent customer ref#</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->agent_custid %></TD> </TR> +% +% } +% +% unless ( FS::part_referral->num_part_referral == 1 ) { +% my $referral = qsearchs('part_referral', { +% 'refnum' => $cust_main->refnum +% } ); +% -<% } %> <TR> - <TD ALIGN="right">Order taker</TD> - <TD BGCOLOR="#ffffff"><%= $cust_main->otaker %></TD> + <TD ALIGN="right">Advertising source</TD> + <TD BGCOLOR="#ffffff"><% $referral->refnum %>: <% $referral->referral%></TD> </TR> +% } + + <TR> <TD ALIGN="right">Referring Customer</TD> <TD BGCOLOR="#ffffff"> +% +% my $referring_cust_main = ''; +% if ( $cust_main->referral_custnum +% && ( $referring_cust_main = +% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) +% ) +% ) { +% + +<A HREF="<% popurl(1) %>cust_main.cgi?<% $cust_main->referral_custnum %>"><%$cust_main->referral_custnum %>: <% - my $referring_cust_main = ''; - if ( $cust_main->referral_custnum - && ( $referring_cust_main = - qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) - ) - ) { -%> - -<A HREF="<%= popurl(1) %>cust_main.cgi?<%= $cust_main->referral_custnum %>"><%=$cust_main->referral_custnum %>: -<%= ( $referring_cust_main->company ? $referring_cust_main->company. ' ('. $referring_cust_main->last. ', '. $referring_cust_main->first. @@ -65,11 +80,33 @@ : $referring_cust_main->last. ', '. $referring_cust_main->first ) %></A> +% } -<% } %> </TD> </TR> +<TR> + <TD ALIGN="right">Order taker</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->otaker %></TD> +</TR> + + <TR> + <TD ALIGN="right">Signup Date</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->signupdate ? time2str($date_format, $cust_main->signupdate) : '' %></TD> + </TR> + +% if ( $conf->exists('cust_main-enable_birthdate') ) { +% my $dt = DateTime->from_epoch(epoch => $cust_main->birthdate, +% time_zone=>'floating', +% ); + + <TR> + <TD ALIGN="right">Date of Birth</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->birthdate ne '' ? $dt->strftime($date_format) : '' %></TD> + </TR> + +% } + </TABLE></TD></TR></TABLE> diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html new file mode 100755 index 000000000..f2d116930 --- /dev/null +++ b/httemplate/view/cust_main/notes.html @@ -0,0 +1,93 @@ +% +% my $conf = new FS::Conf; +% my $curuser = $FS::CurrentUser::CurrentUser; +% +% $cgi->param('custnum') =~ /^(\d+)$/ +% or die "No customer specified (bad URL)!"; +% my $custnum = $1; +% +% my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} ); +% die "Custimer not found!" unless $cust_main; +% + +<STYLE TYPE="text/css"> + +body { background: #e8e8e8 } +.inv table { border: none } +.inv TH { border: none } +.inv TD { border: none } + +</STYLE> + +% my (@notes) = $cust_main->notes(); +% if ( scalar(@notes) ) { + +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_crossframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/iframecontentmws.js"></SCRIPT> + +<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 BORDER=0 > + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% +% foreach my $note (@notes) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $pop = popurl(3); +% my $notenum = $note->notenum; +% my $clickjs = qq!onclick="overlib( OLiframeContent('${pop}edit/! . +% qq!cust_main_note.cgi?custnum=$custnum&! . +% qq!notenum=$notenum', 616, ! . +% qq!386, 'cust_main_note_popup' ), CAPTION, 'Edit customer ! . +% qq!note', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, ! . +% qq!CLOSECLICK, FRAME, top); return false;"!; +% +% my ($el, $eel); +% if ($curuser->access_right('Edit customer note') ) { +% $el = qq!<A HREF="javascript:void(0);" $clickjs>!; +% $eel = qq!</A>!; +% }else{ +% $el = $eel = ''; +% } + +<TR> + <% note_datestr($note,$conf,$bgcolor, $el, $eel) %> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <% $el %> <%$note->otaker%> <% $eel %> + </TD> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <%$note->comments%> + </TD> +</TR> + +% } #end display notes + +</TABLE> + +% } +% +%#subroutines +% +%sub note_datestr { +% my($note, $conf, $bgcolor, $el, $eel) = @_ or return ''; +% my $format=qq{<TD class="inv" bgcolor="$bgcolor" align="left">$el<B>%b</B>$eel</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" align="right">$el<B> %o,</B>$eel</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" align="right">$el<B> %Y </B>$eel</TD>}; +% $format .= qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="right">$el<B> %l$eel</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="center">$el<B>:</B>$eel</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left">$el<B>%M</B>$eel</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left">$el<B> %P </B>$eel</TD>} +% if $conf->exists('cust_main_note-display_times'); +% ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g; +% $strip; +% } +% + diff --git a/httemplate/view/cust_main/order_pkg.html b/httemplate/view/cust_main/order_pkg.html index ac2d05df2..f48bf0929 100644 --- a/httemplate/view/cust_main/order_pkg.html +++ b/httemplate/view/cust_main/order_pkg.html @@ -1,6 +1,7 @@ -<% - my( $cust_main ) = @_; -%> +% +% my( $cust_main ) = @_; +% + <SCRIPT TYPE="text/javascript"> function enable_order_pkg () { @@ -12,25 +13,26 @@ function enable_order_pkg () { } </SCRIPT> -<FORM NAME="OrderPkgForm" ACTION="<%= $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST"> +<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST"> -<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $cust_main->custnum %>"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>"> <SELECT NAME="pkgpart" onChange="enable_order_pkg()"><OPTION>Order additional package +% +%foreach my $part_pkg ( +% qsearch( 'part_pkg', { 'disabled' => '' }, '', +% ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '. +% ' WHERE typenum = '. $cust_main->agent->typenum. +% ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'. +% ' ORDER BY pkg' # case ? +% ) +%) { +% -<% -foreach my $part_pkg ( - qsearch( 'part_pkg', { 'disabled' => '' }, '', - ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '. - ' WHERE typenum = '. $cust_main->agent->typenum. - ' AND type_pkgs.pkgpart = part_pkg.pkgpart )' - ) -) { -%> - <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg %> - <%= $part_pkg->comment %> + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg %> - <% $part_pkg->comment %> +% } -<% } %> </SELECT> diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index ece1b62bb..b54208b79 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -1,494 +1,545 @@ -<% - my( $cust_main ) = @_; - my $conf = new FS::Conf; +% my( $cust_main ) = @_; +% my $conf = new FS::Conf; +% +% my $curuser = $FS::CurrentUser::CurrentUser; +% +% my $packages = get_packages($cust_main, $conf); - my $packages = get_packages($cust_main, $conf); -%> - -<STYLE TYPE="text/css"> -.package .provision { font-weight: bold } -</STYLE> <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A> +% if ( $curuser->access_right('Order customer package') ) { + + <% include('order_pkg.html', $cust_main ) %> +% } +% if ( $curuser->access_right('One-time charge') +% && $conf->config('payby-default') ne 'HIDE' +% ) { +% + + <% popup_link('edit/quick-charge.html?custnum='. $cust_main->custnum, 'One-time charge', 'One-time charge', 545) %> + <BR> +% } +% if ( $curuser->access_right('Bulk change customer packages') ) { -<%= include('order_pkg.html', $cust_main ) %> + <A HREF="<% $p %>edit/cust_pkg.cgi?<% $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services) +% } -<% if ( $conf->config('payby-default') ne 'HIDE' ) { %> - <%= include('quick-charge.html', $cust_main ) %> -<% } %> -<A HREF="<%= $p %>edit/cust_pkg.cgi?<%= $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services) <BR><BR> +% if ( @$packages ) { -<% if ( @$packages ) { %> Current packages -<% } %> - -<% if ( $cust_main->num_cancelled_pkgs ) { - if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledpackages') - && ! $cgi->param('showcancelledpackages') - ) - ) - { - $cgi->param('showcancelledpackages', 1); -%> - ( <a href="<%= $cgi->self_url %>">show -<% } else { - $cgi->param('showcancelledpackages', 0); -%> - ( <a href="<%= $cgi->self_url %>">hide -<% } %> +% } +% if ( $cust_main->num_cancelled_pkgs ) { +% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledpackages') +% && ! $cgi->param('showcancelledpackages') +% ) +% ) +% { +% $cgi->param('showcancelledpackages', 1); +% + + ( <a href="<% $cgi->self_url %>">show +% } else { +% $cgi->param('showcancelledpackages', 0); +% + + ( <a href="<% $cgi->self_url %>">hide +% } + cancelled packages</a> ) -<% } %> +% } +% if ( @$packages ) { + -<% if ( @$packages ) { %> +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; -<TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> <TR> - <TH>Package</TH> - <TH>Status</TH> - <TH COLSPAN=2>Services</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Package</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH> </TR> -<% -foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { - my $rowspan = 0; - - if ($pkg->{cancel}) { - $rowspan = 0; - } else { - foreach my $svcpart (@{$pkg->{svcparts}}) { - $rowspan += $svcpart->{count}; - $rowspan++ if ($svcpart->{count} < $svcpart->{quantity}); - } - } -%> - -<!--pkgnum: <%=$pkg->{pkgnum}%>--> +%foreach my $cust_pkg (@$packages) { +% +% my $part_pkg = $cust_pkg->part_pkg; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + +<!--pkgnum: <% $cust_pkg->pkgnum %>--> <TR> - <TD ROWSPAN=<%=$rowspan%>> - <A NAME="cust_pkg<%=$pkg->{pkgnum}%>"><%=$pkg->{pkgnum}%></A>: - <%=$pkg->{pkg}%> - <%=$pkg->{comment}%><BR> -<% unless ($pkg->{cancel}) { %> - ( <%=pkg_change_link($pkg)%> ) - ( <%=pkg_dates_link($pkg)%> | <%=pkg_customize_link($pkg,$cust_main->custnum)%> ) -<% } %> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A NAME="cust_pkg<% $cust_pkg->pkgnum %>"><% $cust_pkg->pkgnum %></A>: + <% $part_pkg->pkg %> - <% $part_pkg->comment %><BR> + <FONT SIZE=-1> +% unless ( $cust_pkg->get('cancel') ) { +% if ( $curuser->access_right('Change customer package') ) { + + ( <%pkg_change_link($cust_pkg)%> ) +% } +% if ( $curuser->access_right('Edit customer package dates') ) { + + ( <%pkg_dates_link($cust_pkg)%> ) +% } +% if ( $curuser->access_right('Customize customer package') ) { + + ( <%pkg_customize_link($cust_pkg,$cust_main->custnum)%> ) +% } +% } + + </FONT> </TD> - <TD ROWSPAN=<%=$rowspan%>> - <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> - -<% - sub myfreq { - my $part_pkg = shift; - my $freq = $part_pkg->freq_pretty; - $freq =~ s/ / /g; - $freq; - } - - #this should use cust_pkg->status and cust_pkg->statuscolor eventually - - my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4; - my $width = $conf->exists('cust_pkg-display_times') ? '38%' : '56%'; - - #false laziness w/edit/REAL_cust_pkg.cgi - my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until ); - unless ( $pkg->{'part_pkg'}->is_prepaid ) { - $billed_or_prepaid = 'billed'; - $last_bill_or_renewed = 'Last bill'; - $next_bill_or_prepaid_until = 'Next bill'; - } else { - $billed_or_prepaid = 'prepaid'; - $last_bill_or_renewed = 'Renewed'; - $next_bill_or_prepaid_until = 'Prepaid until'; - } - -%> - -<% if ( $pkg->{cancel} ) { %> <!-- #status: cancelled --> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> +% +% sub myfreq { +% my $part_pkg = shift; +% my $freq = $part_pkg->freq_pretty; +% $freq =~ s/ / /g; +% $freq; +% } +% +% #this should use cust_pkg->status and cust_pkg->statuscolor eventually +% +% my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4; +% my $width = $conf->exists('cust_pkg-display_times') ? '38%' : '56%'; +% +% #false laziness w/edit/REAL_cust_pkg.cgi +% my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until ); +% unless ( $part_pkg->is_prepaid ) { +% $billed_or_prepaid = 'billed'; +% $last_bill_or_renewed = 'Last bill'; +% $next_bill_or_prepaid_until = 'Next bill'; +% } else { +% $billed_or_prepaid = 'prepaid'; +% $last_bill_or_renewed = 'Renewed'; +% $next_bill_or_prepaid_until = 'Prepaid until'; +% } +% +% +% if ( $cust_pkg->get('cancel') ) { + <!-- #status: cancelled --> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><FONT COLOR="#ff0000"><B>Cancelled </B></FONT></TD> - <%= pkg_datestr($pkg,'cancel',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#ff0000"><B>Cancelled </B></FONT></TD> + <% pkg_datestr($cust_pkg, 'cancel', $conf) %> + </TR> + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#ff0000" SIZE="-2"> + <% $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' %> + </FONT></TD> </TR> +% unless ( $cust_pkg->get('setup') ) { - <% unless ( $pkg->{setup} ) { %> <TR> - <TD COLSPAN=<%=$colspan%>>Never billed</TD> + <TD COLSPAN=<%$colspan%>>Never billed</TD> </TR> +% } else { - <% } else { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Setup </TD> - <%= pkg_datestr($pkg, 'setup',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> +% if ( $cust_pkg->get('last_bill') ) { - <% if ( $pkg->{'last_bill'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %> </TD> - <%= pkg_datestr($pkg, 'last_bill',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($cust_pkg, 'last_bill',$conf) %> </TR> - <% } %> +% } +% if ( $cust_pkg->get('susp') ) { - <% if ( $pkg->{'susp'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Suspended </TD> - <%= pkg_datestr($pkg, 'susp',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Suspended </TD> + <% pkg_datestr($cust_pkg, 'susp', $conf) %> </TR> - <% } %> - - <% } %> - -<% } else { %> - - <% if ( $pkg->{susp} ) { %> <!-- #status: suspended --> +% } +% } +% } else { +% if ( $cust_pkg->get('susp') ) { + <!-- #status: suspended --> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><FONT COLOR="#FF9900"><B>Suspended</B> </FONT></TD> - <%= pkg_datestr($pkg,'susp',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#FF9900"><B>Suspended</B> </FONT></TD> + <% pkg_datestr($cust_pkg, 'susp', $conf) %> </TR> + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#FF9900" SIZE="-2"> + <% $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' %> + </FONT></TD> + </TR> +% unless ( $cust_pkg->get('setup') ) { - <% unless ( $pkg->{setup} ) { %> <TR> - <TD COLSPAN=<%=$colspan%>>Never billed</TD> + <TD COLSPAN=<%$colspan%>>Never billed</TD> </TR> +% } else { - <% } else { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Setup </TD> - <%= pkg_datestr($pkg, 'setup',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> - - <% } %> +% } +% if ( $cust_pkg->get('last_bill') ) { - <% if ( $pkg->{'last_bill'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %> </TD> - <%= pkg_datestr($pkg, 'last_bill',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($cust_pkg, 'last_bill', $conf) %> </TR> - <% } %> +% } + <!-- # next bill ?? --> +% if ( $cust_pkg->get('expire') ) { - <% if ( $pkg->{'expire'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Expires </TD> - <%= pkg_datestr($pkg, 'expire',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> + <% pkg_datestr($cust_pkg, 'expire', $conf) %> </TR> - <% } %> +% } + <TR> - <TD COLSPAN=<%=$colspan%>>( <%= pkg_unsuspend_link($pkg) %> | <%= pkg_cancel_link($pkg) %> )</TD> - </TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Unsuspend customer package') ) { - <% } else { %> <!-- #status: active --> + ( <% pkg_unsuspend_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package immediately') ) { - <% unless ( $pkg->{setup} ) { %> <!-- #not setup --> + ( <% pkg_cancel_link($cust_pkg) %> ) +% } + + </FONT> + </TD> + </TR> +% } else { + <!-- #status: active --> +% unless ( $cust_pkg->get('setup') ) { + <!-- #not setup --> +% unless ( $part_pkg->freq ) { - <% unless ( $pkg->{'freq'} ) { %> <TR> - <TD COLSPAN=<%=$colspan%>>Not yet billed (one-time charge)</TD> + <TD COLSPAN=<%$colspan%>>Not yet billed (one-time charge)</TD> </TR> <TR> - <TD COLSPAN=<%=$colspan%>>( <%= pkg_cancel_link($pkg) %> )</TD> - </TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Cancel customer package immediately') ) { - <% } else { %> + ( <% pkg_cancel_link($cust_pkg) %> ) +% } - <TR> - <TD COLSPAN=<%=$colspan%>>Not yet billed (<%= $billed_or_prepaid %> <%= myfreq($pkg->{part_pkg}) %>)</TD> + </FONT> + </TD> </TR> +% } else { - <% } %> - <% } else { %> <!-- #setup --> + <TR> + <TD COLSPAN=<%$colspan%>>Not yet billed (<% $billed_or_prepaid %> <% myfreq($part_pkg) %>)</TD> + </TR> +% } +% } else { + <!-- #setup --> +% unless ( $part_pkg->freq ) { - <% unless ( $pkg->{freq} ) { %> <TR> - <TD COLSPAN=<%=$colspan%>>One-time charge</TD> + <TD COLSPAN=<%$colspan%>>One-time charge</TD> </TR> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Billed </TD> - <%= pkg_datestr($pkg,'setup',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Billed </TD> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> +% } else { - <% } else { %> <TR> - <TD COLSPAN=<%=$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>, <%= $billed_or_prepaid %> <%= myfreq($pkg->{part_pkg}) %></TD> + <TD COLSPAN=<%$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>, <% $billed_or_prepaid %> <% myfreq($part_pkg) %></TD> </TR> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Setup </TD> - <%= pkg_datestr($pkg, 'setup',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> +% } +% } +% if ( $cust_pkg->get('last_bill') ) { - <% } %> + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($cust_pkg, 'last_bill', $conf) %> + </TR> +% } +% if ( $cust_pkg->get('bill') ) { #next bill - <% } %> + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $next_bill_or_prepaid_until %> </TD> + <% pkg_datestr($cust_pkg, 'bill', $conf) %> + </TR> +% } +% if ( $cust_pkg->get('expire') ) { - <% if ( $pkg->{'last_bill'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %> </TD> - <%= pkg_datestr($pkg, 'last_bill',$conf) %> + <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> + <% pkg_datestr($cust_pkg, 'expire', $conf) %> </TR> - <% } %> +% } +% if ( $part_pkg->freq ) { - <% if ( $pkg->{'next_bill'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right"><%= $next_bill_or_prepaid_until %> </TD> - <%= pkg_datestr($pkg, 'next_bill',$conf) %> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Suspend customer package') ) { + + ( <% pkg_suspend_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package immediately') ) { + + ( <% pkg_cancel_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package later') ) { + + ( <% pkg_expire_link($cust_pkg) %> ) +% } + + <FONT> + </TD> </TR> - <% } %> +% } +% } +% } + + +</TABLE> +</TD> + +<TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> + +% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { +% foreach my $part_svc ( $cust_pkg->part_svc ) { + +% #foreach my $service (@{$svcpart->{services}}) { +% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) { - <% if ( $pkg->{'expire'} ) { %> <TR> - <TD WIDTH="<%=$width%>" ALIGN="right">Expires </TD> - <%= pkg_datestr($pkg, 'expire',$conf) %> + <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD> + <TD STYLE="padding-bottom:0px"><B><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD> </TR> - <% } %> - <% if ( $pkg->{freq} ) { %> <TR> - <TD COLSPAN=<%=$colspan%>>( <%= pkg_suspend_link($pkg) %> | <%= pkg_cancel_link($pkg) %> )</TD> + <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2"> + +% if ( $curuser->access_right('Recharge customer service') +% && $part_svc->svcdb eq 'svc_acct' +% && ( $cust_svc->svc_x->seconds ne '' +% || $cust_svc->svc_x->upbytes ne '' +% || $cust_svc->svc_x->downbytes ne '' +% || $cust_svc->svc_x->totalbytes ne '' +% ) +% ) { + ( <%svc_recharge_link($cust_svc)%> ) +% } + </FONT></TD> + + <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2"> + +% if ( $curuser->access_right('Unprovision customer service') ) { + ( <%svc_unprovision_link($cust_svc)%> ) +% } + </FONT></TD> + </TR> +% } + +% if ( ! $cust_pkg->get('cancel') +% && $curuser->access_right('Provision customer service') +% && $part_svc->num_avail +% ) { + + <TR> + <TD COLSPAN=2 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px"> + <B><% svc_provision_link($cust_pkg, $part_svc, $conf, $curuser) %></B> + </TD> </TR> - <% } %> - <% } %> +% } -<% } %> +% } </TABLE> </TD> +% } #end display packages +% -<% - if ($rowspan == 0) { print qq!</TR>\n!; next; } - - my $cnt = 0; - foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { - foreach my $service (@{$svcpart->{services}}) { - print '<TR>' if ($cnt > 0); -%> - <TD><%=svc_link($svcpart,$service)%></TD> - <TD><%=svc_label_link($svcpart,$service)%><BR>( <%=svc_unprovision_link($service)%> )</TD> -</TR> -<% - $cnt++; - } - if ($svcpart->{count} < $svcpart->{quantity}) { - print qq!<TR>\n! if ($cnt > 0); - print qq! <TD COLSPAN=2>!.svc_provision_link($pkg, $svcpart, $conf).qq!</TD>\n</TR>\n!; - } - } -} -#end display packages -%> </TABLE> -<% } else { %> +% } else { + <BR> -<% } %> - -<% -#subroutines - -sub get_packages { - my $cust_main = shift or return undef; - my $conf = shift; - - my @packages = (); - my $method; - if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledpackages') - && ! $cgi->param('showcancelledpackages') ) - ) - { - $method = 'ncancelled_pkgs'; - } else { - $method = 'all_pkgs'; - } - - foreach my $cust_pkg ( $cust_main->$method() ) { - - my $part_pkg = $cust_pkg->part_pkg; - - my %pkg = (); - - #to get back to the original object... should use it in the first place!! - $pkg{cust_pkg} = $cust_pkg; - $pkg{part_pkg} = $part_pkg; - - $pkg{pkgnum} = $cust_pkg->pkgnum; - $pkg{pkg} = $part_pkg->pkg; - $pkg{pkgpart} = $part_pkg->pkgpart; - $pkg{comment} = $part_pkg->getfield('comment'); - $pkg{freq} = $part_pkg->freq; - $pkg{setup} = $cust_pkg->getfield('setup'); - $pkg{last_bill} = $cust_pkg->getfield('last_bill'); - $pkg{next_bill} = $cust_pkg->getfield('bill'); - $pkg{susp} = $cust_pkg->getfield('susp'); - $pkg{expire} = $cust_pkg->getfield('expire'); - $pkg{cancel} = $cust_pkg->getfield('cancel'); - - - my %svcparts = map { - $_->svcpart => { - $_->part_svc->hash, - 'quantity' => $_->quantity, - 'count' => $cust_pkg->num_cust_svc($_->svcpart), - #'services' => [], - }; - } $part_pkg->pkg_svc; - - foreach my $cust_svc ( $cust_pkg->cust_svc ) { - #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n"; - my $svc = { - 'svcnum' => $cust_svc->svcnum, - 'label' => ($cust_svc->label)[1], - }; - - #false laziness with above, to catch extraneous services. whole - #damn thing should be OO... - my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= { - $cust_svc->part_svc->hash, - 'quantity' => 0, - 'count' => $cust_pkg->num_cust_svc($cust_svc->svcpart), - #'services' => [], - } ); - - push @{$svcpart->{services}}, $svc; - - } - - $pkg{svcparts} = [ values %svcparts ]; - - push @packages, \%pkg; - - } - - return \@packages; - -} - -sub svc_link { - - my ($svcpart, $svc) = (shift,shift) or return ''; - return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!; - -} - -sub svc_label_link { - - my ($svcpart, $svc) = (shift,shift) or return ''; - return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!; - -} - -sub svc_provision_link { - my ($pkg, $svcpart, $conf) = @_; - ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/ /g; - my $num_left = $svcpart->{quantity} - $svcpart->{count}; - my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}"; - - my $url; - if ( $svcpart->{svcdb} eq 'svc_external' - && $conf->exists('svc_external-skip_manual') - ) { - $url = "${p}edit/process/$svcpart->{svcdb}.cgi?". - "pkgnum=$pkg->{pkgnum}&". - "svcpart=$svcpart->{svcpart}"; - } else { - $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart"; - } - - my $link = qq!<A CLASS="provision" HREF="$url">!. - "Provision $svc_nbsp ($num_left)</A>"; - if ( $conf->exists('legacy_link') ) { - $link .= '<BR>'. - qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!. - qq!$pkgnum_svcpart">!. - "Link to legacy $svc_nbsp ($num_left)</A>"; - } - $link; -} - -sub svc_unprovision_link { - my $svc = shift or return ''; - qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!. - qq!'Permanently unprovision and delete this service?')">Unprovision</A>!; -} - -# This should be generalized to use config options to determine order. -sub pkgsort_pkgnum_cancel { - if ($a->{cancel} and $b->{cancel}) { - return ($a->{pkgnum} <=> $b->{pkgnum}); - } elsif ($a->{cancel} or $b->{cancel}) { - return (-1) if ($b->{cancel}); - return (1) if ($a->{cancel}); - return (0); - } else { - return($a->{pkgnum} <=> $b->{pkgnum}); - } -} - -sub pkg_datestr { - my($pkg, $field, $conf) = @_ or return ''; - return ' ' unless $pkg->{$field}; - my $format = '<TD align="left"><B>%b</B></TD>'. - '<TD align="right"><B> %o,</B></TD>'. - '<TD align="right"><B> %Y</B></TD>'; - #$format .= ' <FONT SIZE=-3>%l:%M:%S%P %z</FONT>' - $format .= '<TD ALIGN="right"><B> %l</TD>'. - '<TD ALIGN="center"><B>:</B></TD>'. - '<TD ALIGN="left"><B>%M</B></TD>'. - '<TD ALIGN="left"><B> %P</B></TD>' - if $conf->exists('cust_pkg-display_times'); - ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g; - $strip; -} - -sub pkg_change_link { - my $pkg = shift or return ''; - return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!. - qq!Change package</a>!; -} - -sub pkg_suspend_link { - my $pkg = shift or return ''; - return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!; -} - -sub pkg_unsuspend_link { - my $pkg = shift or return ''; - return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!; -} - -sub pkg_cancel_link { - my $pkg = shift or return ''; - qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !. - qq!'Permanently delete included services and cancel this package?')">!. - qq!Cancel now</A> | !. - qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!; -} - -sub pkg_dates_link { - my $pkg = shift or return ''; - qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit dates</A>!; -} - -sub pkg_customize_link { - my $pkg = shift or return ''; - my $custnum = shift; - qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!. - qq!pkgnum=$pkg->{pkgnum}">Customize</A>!; -} - -%> +% } +% +%#subroutines +% +%sub get_packages { +% my $cust_main = shift or return undef; +% my $conf = shift; +% +% my @packages = (); +% my $method; +% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledpackages') +% && ! $cgi->param('showcancelledpackages') ) +% ) +% { +% $method = 'ncancelled_pkgs'; +% } else { +% $method = 'all_pkgs'; +% } +% +% [ $cust_main->$method() ]; +%} +% +%sub svc_provision_link { +% my ($cust_pkg, $part_svc, $conf, $curuser) = @_; +% ( my $svc_nbsp = $part_svc->svc ) =~ s/\s+/ /g; +% my $num_avail = $part_svc->num_avail; +% my $pkgnum_svcpart = "pkgnum=". $cust_pkg->pkgnum. ';'. +% "svcpart=". $part_svc->svcpart; +% my $url; +% if ( $part_svc->svcdb eq 'svc_external' #could be generalized +% && $conf->exists('svc_external-skip_manual') +% ) { +% $url = "${p}edit/process/". $part_svc->svcdb. ".cgi?$pkgnum_svcpart"; +% } else { +% $url = FS::UI::Web::svc_url( +% 'm' => $m, +% 'action' => 'edit', +% 'part_svc' => $part_svc, +% 'query' => $pkgnum_svcpart, +% ); +% #$url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart"; +% } +% +% my $link = qq!<A CLASS="provision" HREF="$url">!. +% "Provision $svc_nbsp ($num_avail)</A>"; +% if ( $conf->exists('legacy_link') +% && $curuser->access_right('View/link unlinked services') +% ) +% { +% $link .= '<BR>'. +% qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!. +% qq!$pkgnum_svcpart">!. +% "Link to legacy $svc_nbsp ($num_avail)</A>"; +% } +% $link; +%} +% +%sub svc_unprovision_link { +% my $cust_svc = shift or return ''; +% qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?!. $cust_svc->svcnum. +% qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!; +%} +% +%sub pkg_datestr { +% my($cust_pkg, $field, $conf) = @_ or return ''; +% return ' ' unless $cust_pkg->get($field); +% my $format = '<TD align="left"><B>%b</B></TD>'. +% '<TD align="right"><B> %o,</B></TD>'. +% '<TD align="right"><B> %Y</B></TD>'; +% #$format .= ' <FONT SIZE=-3>%l:%M:%S%P %z</FONT>' +% $format .= '<TD ALIGN="right"><B> %l</TD>'. +% '<TD ALIGN="center"><B>:</B></TD>'. +% '<TD ALIGN="left"><B>%M</B></TD>'. +% '<TD ALIGN="left"><B> %P</B></TD>' +% if $conf->exists('cust_pkg-display_times'); +% my $strip = time2str($format, $cust_pkg->get($field) ); +% $strip =~ s/ (\d)/$1/g; +% $strip; +%} +% +%sub pkg_change_link { pkg_link('misc/change_pkg', 'Change package', @_ ); } +% +%sub pkg_suspend_link { pkg_popup_link( 'misc/cancel_pkg.html?method=suspend', +% 'Suspend', +% 'Suspend', +% @_ +% ); +% } +% +%sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', 'Unsuspend', @_ ); } +%sub pkg_expire_link { pkg_link('misc/expire_pkg', 'Cancel later', @_ ); } +%sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', 'Edit dates', @_ ); } +% +%sub pkg_cancel_link { pkg_popup_link( 'misc/cancel_pkg.html?method=cancel', +% 'Cancel now', +% 'Cancel', +% @_ +% ); +% } +%sub pkg_expire_link { pkg_popup_link( 'misc/cancel_pkg.html?method=expire', +% 'Cancel later', +% 'Expire', #"Cancel package $num later" +% @_ +% ); +% } +% +%sub svc_recharge_link { svc_popup_link( 'misc/recharge_svc.html', +% 'Recharge', +% 'Recharge', +% @_ +% ); +% } +% +%sub pkg_link { +% my($action, $label, $cust_pkg) = @_; +% return '' unless $cust_pkg; +% qq!<a href="${p}$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!; +%} +% +%sub pkg_popup_link { +% my($action, $label, $actionlabel, $cust_pkg) = @_; +% $action .= '&pkgnum='. $cust_pkg->pkgnum; +% $actionlabel .= ' package '. $cust_pkg->pkgnum; +% popup_link($action, $label, $actionlabel, 392); +%} +% +%sub svc_popup_link { +% my($action, $label, $actionlabel, $cust_svc) = @_; +% $action .= '?svcnum='. $cust_svc->svcnum; +% $actionlabel .= ' service '. $cust_svc->svcnum; +% popup_link($action, $label, $actionlabel, 392); +%} +% +%sub popup_link { +% my($action, $label, $actionlabel, $width) = @_; +% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('$p$action', $width, 336, 'pkg_or_svc_action_popup' ), CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">$label</A>!; +%} +% +%sub pkg_customize_link { +% my $cust_pkg = shift or return ''; +% my $custnum = $cust_pkg->custnum; +% qq!<A HREF="${p}edit/part_pkg.cgi?!. +% "keywords=$custnum;". +% "clone=". $cust_pkg->part_pkg->pkgpart. ';'. +% "pkgnum=". $cust_pkg->pkgnum. +% qq!">Customize</A>!; +%} diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index ec99b8c54..b2d98cc55 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -1,428 +1,605 @@ -<% - my( $cust_main ) = @_; - my $custnum = $cust_main->custnum; - - my $conf = new FS::Conf; - - my @payby = grep /\w/, $conf->config('payby'); - #@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) - @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) - unless @payby; - my %payby = map { $_=>1 } @payby; - - my $s = 0; - -%> - <BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR> -<% if ( $payby{'BILL'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=BILL;custnum=<%= $custnum %>">Post check payment</A> - -<% } %> - -<% if ( $payby{'CASH'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=CASH;custnum=<%= $custnum %>">Post cash payment</A> - -<% } %> - -<% if ( $payby{'WEST'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=WEST;custnum=<%= $custnum %>">Post Western Union payment</A> - -<% } %> - -<% if ( $payby{'CARD'} || $payby{'DCRD'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>misc/payment.cgi?payby=CARD;custnum=<%= $custnum %>">Process credit card payment</A> +% my $s = 0; +% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=BILL;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter check payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter check payment</A> +% } + +% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=CASH;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter cash payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter cash payment</A> +% } + +% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>">Enter Western Union payment</A> +% } + +% if ( ( $payby{'CARD'} || $payby{'DCRD'} ) +% && $curuser->access_right('Process payment') +% && ! $cust_main->is_encrypted($cust_main->payinfo) +% ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card payment</A> +% } + +% if ( ( $payby{'CHEK'} || $payby{'DCHK'} ) +% && $curuser->access_right('Process payment') +% && ! $cust_main->is_encrypted($cust_main->payinfo) +% ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) payment</A> +% } + +% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline) credit card payment</A> +% } -<% } %> - -<% if ( $payby{'CHEK'} || $payby{'DCHK'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>misc/payment.cgi?payby=CHEK;custnum=<%= $custnum %>">Process electronic check (ACH) payment</A> - -<% } %> - -<% if ( $payby{'MCRD'} ) { %> - - <%= $s++ ? ' | ' : '' %> - <A HREF="<%= $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<%= $custnum %>">Post manual credit card payment</A> - -<% } %> - -<BR><A HREF="<%= $p %>edit/cust_credit.cgi?<%= $custnum %>">Post credit</A> <BR> -<% -#get payment history -my @history = (); - -#invoices -foreach my $cust_bill ($cust_main->cust_bill) { - my $pre = ( $cust_bill->owed > 0 ) - ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open ' - : ''; - my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : ''; - my $invnum = $cust_bill->invnum; - push @history, { - 'date' => $cust_bill->_date, - 'desc' => qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!. $pre. - "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'. - $post. '</A>', - 'charge' => $cust_bill->charged, - }; -} +% if ( $curuser->access_right('Post credit') ) { + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_credit.cgi?<% $custnum %>', 392, 336, 'cust_credit_popup' ), CAPTION, 'Enter credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter credit</A> + <BR> +% } + +% if ( $curuser->access_right('View customer tax exemptions') ) { + <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A> + <BR> +% } + +% if ( $conf->exists('batch-enable') +% #&& $curuser->access_right('View customer tax exemptions') +% ) { + View batched payments: +% foreach my $status (qw( Queued In-transit Complete All )) { + <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% $status %></A> + <% $status ne 'All' ? '|' : '' %> +% } + <BR> +% } + +%#get payment history +%my @history = (); +% +%#invoices +%foreach my $cust_bill ($cust_main->cust_bill) { +% my $pre = ( $cust_bill->owed > 0 ) +% ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open ' +% : ''; +% my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : ''; +% my $invnum = $cust_bill->invnum; +% my $link = $curuser->access_right('View invoices') +% ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">! +% : ''; +% push @history, { +% 'date' => $cust_bill->_date, +% 'desc' => $link. $pre. +% "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'. +% $post. ( $link ? '</A>' : '' ), +% 'charge' => $cust_bill->charged, +% }; +%} +% +%#payments (some false laziness w/credits) +%foreach my $cust_pay ($cust_main->cust_pay) { +% +% my $payby = $cust_pay->payby; +% +% my $payinfo; +% if ( $payby eq 'CARD' ) { +% $payinfo = $cust_pay->paymask; +% } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) { +% $payinfo = "ABA $2, Acct# $1"; +% } else { +% $payinfo = $cust_pay->payinfo; +% } +% my @cust_bill_pay = $cust_pay->cust_bill_pay; +% my @cust_pay_refund = $cust_pay->cust_pay_refund; +% +% my $target = "$payby$payinfo"; +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^PREP$/Prepaid card /; +% $payby =~ s/^CARD$/Credit card #/; +% $payby =~ s/^COMP$/Complimentary by /; +% $payby =~ s/^CASH$/Cash/; +% $payby =~ s/^WEST$/Western Union/; +% $payby =~ s/^MCRD$/Manual credit card/; +% $payby =~ s/^BILL$//; +% my $info = $payby ? " ($payby$payinfo)" : ''; +% +% my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); +% if ( scalar(@cust_bill_pay) == 0 +% && scalar(@cust_pay_refund) == 0 ) { +% #completely unapplied +% $pre = '<B><FONT COLOR="#FF0000">Unapplied '; +% $post = '</FONT></B>'; +% $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!. +% $cust_pay->paynum. +% qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!; +% +% } elsif ( scalar(@cust_bill_pay) == 1 +% && scalar(@cust_pay_refund) == 0 +% && $cust_pay->unapplied == 0 ) { +% #applied to one invoice, the usual situation +% $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum; +% } elsif ( scalar(@cust_bill_pay) == 0 +% && scalar(@cust_pay_refund) == 1 +% && $cust_pay->unapplied == 0 ) { +% #applied to one refund +% $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date); +% } else { +% #complicated +% $desc = '<BR>'; +% foreach my $app ( sort { $a->_date <=> $b->_date } +% ( @cust_bill_pay, @cust_pay_refund ) ) { +% if ( $app->isa('FS::cust_bill_pay') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' applied to Invoice #'. $app->invnum. +% '<BR>'; +% #' on '. time2str("%D", $cust_bill_pay->_date). +% } elsif ( $app->isa('FS::cust_pay_refund') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' refunded on '. time2str("%D", $app->_date). +% '<BR>'; +% } else { +% die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund"; +% } +% } +% if ( $cust_pay->unapplied > 0 ) { +% $desc .= ' '. +% '<B><FONT COLOR="#FF0000">$'. +% $cust_pay->unapplied. ' unapplied</FONT></B>'. +% qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!. +% $cust_pay->paynum. +% qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!. +% '<BR>'; +% } +% } +% +% my $refund = ''; +% my $refund_days = $conf->config('card_refund-days') || 120; +% if ( $cust_pay->closed !~ /^Y/i +% && $cust_pay->payby =~ /^(CARD|CHEK)$/ +% && time-$cust_pay->_date < $refund_days*86400 +% && $cust_pay->unrefunded > 0 +% && $curuser->access_right('Refund payment') +% ) { +% $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!. +% qq!paynum=!. $cust_pay->paynum. '"'. +% qq! TITLE="Send a refund for this payment to the payment gateway"!. +% qq!>refund</A>)!; +% } +% +% my $void = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && ( ( $cust_pay->payby eq 'CARD' +% && $curuser->access_right('Credit card void') +% ) +% || ( $cust_pay->payby eq 'CHEK' +% && $curuser->access_right('Echeck void') +% ) +% || ( $cust_pay->payby !~ /^(CARD|CHEK)$/ +% && $curuser->access_right('Regular void') +% ) +% ) +% ) +% { +% $void = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to void this payment?')"!. +% qq! TITLE="Void this payment from the database!. +% ( $cust_pay->payby =~ /^(CARD|CHEK)$/ +% ? ' (do not send anything to the payment gateway)' +% : '' +% ). '"'. +% qq!>void</A>)!; +% } +% +% my $delete = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && $conf->exists('deletepayments') +% && $curuser->access_right('Delete payment') +% ) +% { +% $delete = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to delete this payment?')"!. +% qq! TITLE="Delete this payment from the database completely - not recommended"!. +% qq!>delete</A>)!; +% } +% +% my $unapply = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && scalar(@cust_bill_pay) +% && $curuser->access_right('Unapply payment') +% ) +% { +% $unapply = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to unapply this payment?')"!. +% qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!. +% qq!>unapply</A>)!; +% } +% +% push @history, { +% 'date' => $cust_pay->_date, +% 'desc' => $pre. "Payment$post$info$desc". +% "$apply$refund$void$delete$unapply", +% 'payment' => $cust_pay->paid, +% 'target' => $target, +% }; +%} +% +%#voided payments +%foreach my $cust_pay_void ($cust_main->cust_pay_void) { +% +% my $payby = $cust_pay_void->payby; +% my $payinfo = $payby eq 'CARD' +% ? $cust_pay_void->paymask +% : $cust_pay_void->payinfo; +% +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^BILL$//; +% $payby =~ s/^(CARD|COMP)$/$1 /; +% my $info = $payby ? " ($payby$payinfo)" : ''; +% +% my $unvoid = ''; +% if ( $cust_pay_void->closed !~ /^Y/i +% && $curuser->access_right('Unvoid') +% ) +% { +% $unvoid = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum. +% qq!', 'Are you sure you want to unvoid this payment?')"!. +% qq! TITLE="Unvoid this payment from the database!. +% ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/ +% ? ' (do not send anything to the payment gateway)' +% : '' +% ). '"'. +% qq!>unvoid</A>)!; +% } +% +% push @history, { +% 'date' => $cust_pay_void->_date, +% 'desc' => "<DEL>Payment $info</DEL> <I>voided ". +% time2str("%D", $cust_pay_void->void_date). +% " by ". $cust_pay_void->otaker. '</i>'. $unvoid, +% 'void_payment' => $cust_pay_void->paid, +% }; +% +%} +% +%#credits (some false laziness w/payments) +%foreach my $cust_credit ($cust_main->cust_credit) { +% +% my @cust_credit_bill = $cust_credit->cust_credit_bill; +% my @cust_credit_refund = $cust_credit->cust_credit_refund; +% +% my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); +% if ( scalar(@cust_credit_bill) == 0 +% && scalar(@cust_credit_refund) == 0 ) { +% #completely unapplied +% $pre = '<B><FONT COLOR="#FF0000">Unapplied '; +% $post = '</FONT></B>'; +% $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!. +% $cust_credit->crednum. +% qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!; +% } elsif ( scalar(@cust_credit_bill) == 1 +% && scalar(@cust_credit_refund) == 0 +% && $cust_credit->credited == 0 ) { +% #applied to one invoice, the usual situation +% $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum; +% } elsif ( scalar(@cust_credit_bill) == 0 +% && scalar(@cust_credit_refund) == 1 +% && $cust_credit->credited == 0 ) { +% #applied to one refund +% $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date); +% } else { +% #complicated +% $desc = '<BR>'; +% foreach my $app ( sort { $a->_date <=> $b->_date } +% ( @cust_credit_bill, @cust_credit_refund ) ) { +% if ( $app->isa('FS::cust_credit_bill') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' applied to Invoice #'. $app->invnum. +% '<BR>'; +% #' on '. time2str("%D", $app->_date). +% } elsif ( $app->isa('FS::cust_credit_refund') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' refunded on '. time2str("%D", $app->_date). +% '<BR>'; +% } else { +% die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund"; +% } +% } +% if ( $cust_credit->credited > 0 ) { +% $desc .= ' <B><FONT COLOR="#FF0000">$'. +% $cust_credit->credited. ' unapplied</FONT></B>'. +% qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!. +% $cust_credit->crednum. +% qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!. +% '<BR>'; +% } +% } +%# +% my $delete = ''; +% if ( $cust_credit->closed !~ /^Y/i +% +% #s'pose deleting a credit isn't bad like deleting a payment +% # and this needs to be generally available until we have credit voiding.. +% #&& $conf->exists('deletecredits') +% +% && $curuser->access_right('Delete credit') +% ) +% { +% $delete = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum. +% qq!', 'Are you sure you want to delete this credit?')">!. +% qq!delete</A>)!; +% } +% +% my $unapply = ''; +% if ( $cust_credit->closed !~ /^Y/i +% && scalar(@cust_credit_bill) +% && $curuser->access_right('Unapply credit') +% ) +% { +% $unapply = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum. +% qq!', 'Are you sure you want to unapply this credit?')">!. +% qq!unapply</A>)!; +% } +% +% push @history, { +% 'date' => $cust_credit->_date, +% 'desc' => $pre. "Credit$post by ". $cust_credit->otaker. +% ( $cust_credit->reason +% ? ' ('. $cust_credit->reason. ')' +% : '' +% ). +% "$desc$apply$delete$unapply", +% 'credit' => $cust_credit->amount, +% }; +% +%} +% +%#refunds +%foreach my $cust_refund ($cust_main->cust_refund) { +% +% my $payby = $cust_refund->payby; +% my $payinfo = $payby eq 'CARD' +% ? $cust_refund->paymask +% : $cust_refund->payinfo; +% +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^(CARD|COMP)$/$1 /; +% +% my $delete = ''; +% if ( $cust_refund->closed !~ /^Y/i +% && $conf->exists('deleterefunds') +% && $curuser->access_right('Delete refund') +% ) +% { +% $delete = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/delete-cust_refund.cgi?!. $cust_refund->refundnum. +% qq!', 'Are you sure you want to delete this refund?')"!. +% qq! TITLE="Delete this refund from the database completely - not recommended"!. +% qq!>delete</A>)!; +% } +% +% push @history, { +% 'date' => $cust_refund->_date, +% 'desc' => "Refund ($payby$payinfo) by ". $cust_refund->otaker. "<BR>". +% $delete, +% 'refund' => $cust_refund->refund, +% }; +% +%} +% +% + + +<% include("/elements/table-grid.html") %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% -#payments (some false laziness w/credits) -foreach my $cust_pay ($cust_main->cust_pay) { - my $payby = $cust_pay->payby; - - my $payinfo; - if ( $payby eq 'CARD' ) { - $payinfo = $cust_pay->payinfo_masked; - } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) { - $payinfo = "ABA $2, Acct# $1"; - } else { - $payinfo = $cust_pay->payinfo; - } - my @cust_bill_pay = $cust_pay->cust_bill_pay; - my @cust_pay_refund = $cust_pay->cust_pay_refund; - - my $target = "$payby$payinfo"; - $payby =~ s/^BILL$/Check #/ if $payinfo; - $payby =~ s/^CHEK$/Electronic check /; - $payby =~ s/^PREP$/Prepaid card /; - $payby =~ s/^CARD$/Credit card #/; - $payby =~ s/^COMP$/Complimentary by /; - $payby =~ s/^CASH$/Cash/; - $payby =~ s/^WEST$/Western Union/; - $payby =~ s/^MCRD$/Manual credit card/; - $payby =~ s/^BILL$//; - my $info = $payby ? " ($payby$payinfo)" : ''; - - my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); - if ( scalar(@cust_bill_pay) == 0 - && scalar(@cust_pay_refund) == 0 ) { - #completely unapplied - $pre = '<B><FONT COLOR="#FF0000">Unapplied '; - $post = '</FONT></B>'; - $apply = qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!. - $cust_pay->paynum. '">apply</A>)'; - } elsif ( scalar(@cust_bill_pay) == 1 - && scalar(@cust_pay_refund) == 0 - && $cust_pay->unapplied == 0 ) { - #applied to one invoice, the usual situation - $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum; - } elsif ( scalar(@cust_bill_pay) == 0 - && scalar(@cust_pay_refund) == 1 - && $cust_pay->unapplied == 0 ) { - #applied to one refund - $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date); - } else { - #complicated - $desc = '<BR>'; - foreach my $app ( sort { $a->_date <=> $b->_date } - ( @cust_bill_pay, @cust_pay_refund ) ) { - if ( $app->isa('FS::cust_bill_pay') ) { - $desc .= ' '. - '$'. $app->amount. - ' applied to Invoice #'. $app->invnum. - '<BR>'; - #' on '. time2str("%D", $cust_bill_pay->_date). - } elsif ( $app->isa('FS::cust_pay_refund') ) { - $desc .= ' '. - '$'. $app->amount. - ' refunded on'. time2str("%D", $app->_date). - '<BR>'; - } else { - die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund"; - } - } - if ( $cust_pay->unapplied > 0 ) { - $desc .= ' '. - '<B><FONT COLOR="#FF0000">$'. - $cust_pay->unapplied. ' unapplied</FONT></B>'. - qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!. - $cust_pay->paynum. '">apply</A>)'. - '<BR>'; - } - } - - my $refund = ''; - my $refund_days = $conf->config('card_refund-days') || 120; - if ( $cust_pay->closed !~ /^Y/i - && $cust_pay->payby =~ /^(CARD|CHEK)$/ - && time-$cust_pay->_date < $refund_days*86400 - && $cust_pay->unrefunded > 0 - ) { - $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!. - qq!paynum=!. $cust_pay->paynum. '"'. - qq! TITLE="Send a refund for this payment to the payment gateway"!. - qq!>refund</A>)!; - } +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Charge</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>In-house<BR>Credit</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Refund</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Balance</FONT></TH> +</TR> +% +%#display payment history +% +%sub balance_forward_row { +% my( $b, $date ) = @_; +% my $conf = new FS::Conf; +% my $money_char = $conf->config('money_char') || '$'; +% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/; + + <TR ID="balance_forward_row"> + <TD CLASS="grid" BGCOLOR="#dddddd"> + <% time2str("%D",$date) %> + </TD> + + <TD CLASS="grid" BGCOLOR="#dddddd"> + <I>Starting balance on <% time2str("%D",$date) %></I> + (<A HREF="javascript:void(0);" onClick="show_history();">show prior history</A>) + </TD> + + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"><I><% $balance_forward %></I></TD> + + </TR> +%} +% +%my $balance = 0; +%my %target = (); +%my $money_char = $conf->config('money_char') || '$'; +% +%my $years = $conf->config('payment_history-years') || 2; +%my $older_than = time - $years * 31556736; #60*60*24*365.24 +%my $hidden = 0; +%my $seen = 0; +%my $old_history = 0; +%my $lastdate = 0; +% +%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { +% +% $lastdate = $item->{'date'}; +% +% my $display; +% if ( $item->{'date'} < $older_than ) { +% $display = ' STYLE="display:none" '; +% $hidden = 1; +% } else { +% +% $display = ''; +% +% if ( $hidden && ! $seen++ ) { +% balance_forward_row($balance, $item->{'date'}); +% } +% +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $charge = exists($item->{'charge'}) +% ? sprintf("$money_char\%.2f", $item->{'charge'}) +% : ''; +% +% my $payment = exists($item->{'payment'}) +% ? sprintf("- $money_char\%.2f", $item->{'payment'}) +% : ''; +% +% $payment ||= sprintf( "<DEL>- $money_char\%.2f</DEL>", +% $item->{'void_payment'} +% ) +% if exists($item->{'void_payment'}); +% +% my $credit = exists($item->{'credit'}) +% ? sprintf("- $money_char\%.2f", $item->{'credit'}) +% : ''; +% +% my $refund = exists($item->{'refund'}) +% ? sprintf("$money_char\%.2f", $item->{'refund'}) +% : ''; +% +% my $target = exists($item->{'target'}) ? $item->{'target'} : ''; +% +% $balance += $item->{'charge'} if exists $item->{'charge'}; +% $balance -= $item->{'payment'} if exists $item->{'payment'}; +% $balance -= $item->{'credit'} if exists $item->{'credit'}; +% $balance += $item->{'refund'} if exists $item->{'refund'}; +% $balance = sprintf("%.2f", $balance); +% $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp +% ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/- \$/; +% +% + + + <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% unless ( !$target || $target{$target}++ ) { + + <A NAME="<% $target %>"> +% } + + <% time2str("%D",$item->{'date'}) %> +% if ( $target && $target{$target} == 1 ) { - my $void = ''; - if ( $cust_pay->closed !~ /^Y/i - && ( $cust_pay->payby ne 'CARD' || $conf->exists('cc-void') ) - && ( $cust_pay->payby ne 'CHEK' || $conf->exists('echeck-void') ) - ) { - $void = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum. - qq!', 'Are you sure you want to void this payment?')"!. - qq! TITLE="Void this payment from the database!. - ( $cust_pay->payby =~ /^(CARD|CHEK)$/ - ? ' (do not send anything to the payment gateway)' - : '' - ). '"'. - qq!>void</A>)!; - } + </A> +% } - my $delete = ''; - if ( $cust_pay->closed !~ /^Y/i && $conf->exists('deletepayments') ) { - $delete = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum. - qq!', 'Are you sure you want to delete this payment?')"!. - qq! TITLE="Delete this payment from the database completely - not recommended"!. - qq!>delete</A>)!; - } + </FONT> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $item->{'desc'} %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $charge %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $payment %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $credit %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $refund %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $showbalance %> + </TD> + </TR> +% } - my $unapply = ''; - if ( $cust_pay->closed !~ /^Y/i - && $conf->exists('unapplypayments') - && scalar(@cust_bill_pay) ) { - $unapply = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum. - qq!', 'Are you sure you want to unapply this payment?')"!. - qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!. - qq!>unapply</A>)!; - } +%if ( scalar(@history) && $hidden && ! $seen++ ) { +% balance_forward_row($balance, $lastdate); +%} - push @history, { - 'date' => $cust_pay->_date, - 'desc' => $pre. "Payment$post$info$desc". - "$apply$refund$void$delete$unapply", - 'payment' => $cust_pay->paid, - 'target' => $target, - }; -} +</TABLE> -#voided payments -foreach my $cust_pay_void ($cust_main->cust_pay_void) { - - my $payby = $cust_pay_void->payby; - my $payinfo = $payby eq 'CARD' - ? $cust_pay_void->payinfo_masked - : $cust_pay_void->payinfo; - - $payby =~ s/^BILL$/Check #/ if $payinfo; - $payby =~ s/^CHEK$/Electronic check /; - $payby =~ s/^BILL$//; - $payby =~ s/^(CARD|COMP)$/$1 /; - my $info = $payby ? " ($payby$payinfo)" : ''; - - my $unvoid = ''; - if ( $cust_pay_void->closed !~ /^Y/i && $conf->exists('unvoid') ) { - $unvoid = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum. - qq!', 'Are you sure you want to unvoid this payment?')"!. - qq! TITLE="Unvoid this payment from the database!. - ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/ - ? ' (do not send anything to the payment gateway)' - : '' - ). '"'. - qq!>unvoid</A>)!; - } +<SCRIPT TYPE="text/javascript"> - push @history, { - 'date' => $cust_pay_void->_date, - 'desc' => "<DEL>Payment $info</DEL> <I>voided ". - time2str("%D", $cust_pay_void->void_date). - " by ". $cust_pay_void->otaker. '</i>'. $unvoid, - 'void_payment' => $cust_pay_void->paid, - }; +function show_history () { + //alert('showing history!'); -} + var balance_forward_row = document.getElementById('balance_forward_row'); -#credits (some false laziness w/payments) -foreach my $cust_credit ($cust_main->cust_credit) { - - my @cust_credit_bill = $cust_credit->cust_credit_bill; - my @cust_credit_refund = $cust_credit->cust_credit_refund; - - my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); - if ( scalar(@cust_credit_bill) == 0 - && scalar(@cust_credit_refund) == 0 ) { - #completely unapplied - $pre = '<B><FONT COLOR="#FF0000">Unapplied '; - $post = '</FONT></B>'; - $apply = qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!. - $cust_credit->crednum. '">apply</A>)'; - } elsif ( scalar(@cust_credit_bill) == 1 - && scalar(@cust_credit_refund) == 0 - && $cust_credit->credited == 0 ) { - #applied to one invoice, the usual situation - $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum; - } elsif ( scalar(@cust_credit_bill) == 0 - && scalar(@cust_credit_refund) == 1 - && $cust_credit->credited == 0 ) { - #applied to one refund - $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date); - } else { - #complicated - $desc = '<BR>'; - foreach my $app ( sort { $a->_date <=> $b->_date } - ( @cust_credit_bill, @cust_credit_refund ) ) { - if ( $app->isa('FS::cust_credit_bill') ) { - $desc .= ' '. - '$'. $app->amount. - ' applied to Invoice #'. $app->invnum. - '<BR>'; - #' on '. time2str("%D", $app->_date). - } elsif ( $app->isa('FS::cust_credit_refund') ) { - $desc .= ' '. - '$'. $app->amount. - ' refunded on'. time2str("%D", $app->_date). - '<BR>'; - } else { - die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund"; - } - } - if ( $cust_credit->credited > 0 ) { - $desc .= ' <B><FONT COLOR="#FF0000">$'. - $cust_credit->credited. ' unapplied</FONT></B>'. - qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!. - $cust_credit->crednum. '">apply</A>)'. - '<BR>'; - } - } -# - my $delete = ''; - if ( $cust_credit->closed !~ /^Y/i && $conf->exists('deletecredits') ) { - $delete = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum. - qq!', 'Are you sure you want to delete this credit?')">!. - qq!delete</A>)!; - } - - my $unapply = ''; - if ( $cust_credit->closed !~ /^Y/i - && $conf->exists('unapplycredits') - && scalar(@cust_credit_bill) ) { - $unapply = qq! (<A HREF="javascript:areyousure('!. - qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum. - qq!', 'Are you sure you want to unapply this credit?')">!. - qq!unapply</A>)!; + balance_forward_row.style.display = 'none'; + for ( var i = 0; i < <% $old_history %>; i++ ) { + var oldRow = document.getElementById('old_history'+i); + oldRow.style.display = ''; } - - push @history, { - 'date' => $cust_credit->_date, - 'desc' => $pre. "Credit$post by ". $cust_credit->otaker. - ( $cust_credit->reason - ? ' ('. $cust_credit->reason. ')' - : '' - ). - "$desc$apply$delete$unapply", - 'credit' => $cust_credit->amount, - }; } -#refunds -foreach my $cust_refund ($cust_main->cust_refund) { +</SCRIPT> - my $payby = $cust_refund->payby; - my $payinfo = $payby eq 'CARD' - ? $cust_refund->payinfo_masked - : $cust_refund->payinfo; +<%init> - $payby =~ s/^BILL$/Check #/ if $payinfo; - $payby =~ s/^CHEK$/Electronic check /; - $payby =~ s/^(CARD|COMP)$/$1 /; +my( $cust_main ) = @_; +my $custnum = $cust_main->custnum; - push @history, { - 'date' => $cust_refund->_date, - 'desc' => "Refund ($payby$payinfo) by ". $cust_refund->otaker, - 'refund' => $cust_refund->refund, - }; +my $conf = new FS::Conf; -} - -%> +my $curuser = $FS::CurrentUser::CurrentUser; -<%= table() %> -<TR> - <TH>Date</TH> - <TH>Description</TH> - <TH><FONT SIZE=-1>Charge</FONT></TH> - <TH><FONT SIZE=-1>Payment</FONT></TH> - <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH> - <TH><FONT SIZE=-1>Refund</FONT></TH> - <TH><FONT SIZE=-1>Balance</FONT></TH> -</TR> +my @payby = grep /\w/, $conf->config('payby'); +#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) + unless @payby; +my %payby = map { $_=>1 } @payby; -<% -#display payment history - -my %target; -my $balance = 0; -foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { - - my $charge = exists($item->{'charge'}) - ? sprintf('$%.2f', $item->{'charge'}) - : ''; - my $payment = exists($item->{'payment'}) - ? sprintf('- $%.2f', $item->{'payment'}) - : ''; - $payment ||= sprintf('<DEL>- $%.2f</DEL>', $item->{'void_payment'}) - if exists($item->{'void_payment'}); - my $credit = exists($item->{'credit'}) - ? sprintf('- $%.2f', $item->{'credit'}) - : ''; - my $refund = exists($item->{'refund'}) - ? sprintf('$%.2f', $item->{'refund'}) - : ''; - - my $target = exists($item->{'target'}) ? $item->{'target'} : ''; - - $balance += $item->{'charge'} if exists $item->{'charge'}; - $balance -= $item->{'payment'} if exists $item->{'payment'}; - $balance -= $item->{'credit'} if exists $item->{'credit'}; - $balance += $item->{'refund'} if exists $item->{'refund'}; - $balance = sprintf("%.2f", $balance); - $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp - ( my $showbalance = '$'. $balance ) =~ s/^\$\-/- \$/; - -%> - - <TR> - <TD> - <% unless ( !$target || $target{$target}++ ) { %> - <A NAME="<%= $target %>"> - <% } %> - <%= time2str("%D",$item->{'date'}) %> - <% if ( $target && $target{$target} == 1 ) { %> - </A> - <% } %> - </FONT> - </TD> - <TD><%= $item->{'desc'} %></TD> - <TD ALIGN="right"><%= $charge %></TD> - <TD ALIGN="right"><%= $payment %></TD> - <TD ALIGN="right"><%= $credit %></TD> - <TD ALIGN="right"><%= $refund %></TD> - <TD ALIGN="right"><%= $showbalance %></TD> - </TR> - -<% } %> - -</TABLE> +my %status = ( + 'Queued' => 'O', #Open + 'In-transit' => 'I', + 'Complete' => 'R', #Resolved + 'All' => '', +); +</%init> diff --git a/httemplate/view/cust_main/quick-charge.html b/httemplate/view/cust_main/quick-charge.html index 2fe3d5f3d..06ffd75e6 100644 --- a/httemplate/view/cust_main/quick-charge.html +++ b/httemplate/view/cust_main/quick-charge.html @@ -1,18 +1,73 @@ -<% - my( $cust_main ) = @_; -%> +<SCRIPT TYPE="text/javascript"> -<FORM ACTION="<%=$p%>edit/process/quick-charge.cgi" METHOD="POST"> +function enable_quick_charge () { + //alert('enable_quick_charge ' + document.QuickChargeForm.amount.value + ' - ' + document.QuickChargeForm.pkg.value ); + if ( document.QuickChargeForm.amount.value + && document.QuickChargeForm.pkg.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} -<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $cust_main->custnum %>"> +function enable_quick_charge_desc () { + //alert('enable_quick_charge ' + document.QuickChargeForm.amount.value + ' - ' + document.QuickChargeForm.pkg.value ); + if ( document.QuickChargeForm.amount.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} -Description:<INPUT TYPE="text" NAME="pkg"> +function enable_quick_charge_amount () { + //alert('enable_quick_charge ' + document.QuickChargeForm.amount.value + ' - ' + document.QuickChargeForm.pkg.value ); + if ( document.QuickChargeForm.pkg.value ) { + document.QuickChargeForm.submit.disabled = false; + } else { + document.QuickChargeForm.submit.disabled = true; + } +} -Amount:<INPUT TYPE="text" NAME="amount" SIZE=6> +function validate_quick_charge () { + //alert('validate_quick_charge'); + var pkg = document.QuickChargeForm.pkg.value; + var pkg_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/ ; + var amount = document.QuickChargeForm.amount.value; + var amount_regex = /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ; -<%= include('/elements/select-taxclass.html') %> + if ( amount_regex.test(amount) && pkg_regex.test(pkg) ) { + return true; + } else if ( amount_regex.test(amount) ) { + if ( pkg ) { + alert('Illegal description - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' ); + } else { + alert('Enter a description for the one-time charge'); + } + return false; + } else { + alert('Illegal amount - enter an amount to charge, for example, "5" or "43" or "21.46".'); + return false; + } +} -<INPUT TYPE="submit" VALUE="One-time charge"> +</SCRIPT> + +<FORM NAME="QuickChargeForm" ACTION="<%$p%>edit/process/quick-charge.cgi" METHOD="POST" onSubmit="return validate_quick_charge()"> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>"> + +Description:<INPUT TYPE="text" NAME="pkg" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_desc()"> + +Amount:<INPUT TYPE="text" NAME="amount" SIZE=6 onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_amount()"> + +<% include('/elements/select-taxclass.html') %> + +<INPUT NAME="submit" TYPE="submit" VALUE="One-time charge" DISABLED> </FORM> +<%init> + +my( $cust_main ) = @_; + +</%init> diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html index 72d68152a..84cc90299 100644 --- a/httemplate/view/cust_main/tickets.html +++ b/httemplate/view/cust_main/tickets.html @@ -1,54 +1,78 @@ -<% - my( $cust_main ) = @_; +% +% my( $cust_main ) = @_; +% +% my $conf = new FS::Conf; +% my $num = $conf->config('cust_main-max_tickets') || 10; +% +% my @tickets = (); +% unless ( $conf->config('ticket_system-custom_priority_field') ) { +% +% @tickets = +% @{ FS::TicketSystem->customer_tickets($cust_main->custnum, $num) }; +% +% } else { +% +% foreach my $priority ( +% $conf->config('ticket_system-custom_priority_field-values'), '' +% ) { +% last if scalar(@tickets) >= $num; +% push @tickets, +% @{ FS::TicketSystem->customer_tickets( $cust_main->custnum, +% $num - scalar(@tickets), +% $priority, +% ) +% }; +% } +% +% } +% +% - my $conf = new FS::Conf; - my $num = 10; +<A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A> +<BR> - my @tickets = (); - unless ( $conf->config('ticket_system-custom_priority_field') ) { +(<A HREF="<% FS::TicketSystem->href_customer_tickets($cust_main->custnum) %>">View all tickets for this customer</A>) +(<A HREF="<% FS::TicketSystem->href_new_ticket($cust_main, join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) ) %>">New ticket for this customer</A>) - @tickets = - @{ FS::TicketSystem->customer_tickets($cust_main->custnum, $num) }; +<% include("/elements/table-grid.html") %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% - } else { - foreach my $priority ( - $conf->config('ticket_system-custom_priority_field-values'), '' - ) { - last if scalar(@tickets) >= $num; - push @tickets, - @{ FS::TicketSystem->customer_tickets( $cust_main->custnum, - $num - scalar(@tickets), - $priority, - ) - }; - } - - } - -%> - -Highest priority tickets -(<A HREF="<%= FS::TicketSystem->href_customer_tickets($cust_main->custnum) %>">View all tickets for this customer</A>) -(<A HREF="<%= FS::TicketSystem->href_new_ticket($cust_main, join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) ) %>">New ticket for this customer</A>) -<%= table() %> <TR> - <TH>#</TH> - <TH>Subject</TH> - <TH>Priority</TH> - <TH>Queue</TH> - <TH>Status</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">#</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Subject</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Priority</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Queue</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> </TR> -<% foreach my $ticket ( @tickets ) { - my $href = FS::TicketSystem->href_ticket($ticket->{id}); -%> +% foreach my $ticket ( @tickets ) { +% my $href = FS::TicketSystem->href_ticket($ticket->{id}); +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + <TR> - <TD><A HREF=<%=$href%>><%= $ticket->{id} %></A></TD> - <TD><A HREF=<%=$href%>><%= $ticket->{subject} %></A></TD> - <TD ALIGN="right"><%= $ticket->{content} || $ticket->{priority} %></TD> - <TD><%= $ticket->{name} %></TD> - <TD><%= $ticket->{status} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF=<%$href%>><% $ticket->{id} %></A></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF=<%$href%>><% $ticket->{subject} %></A></TD> + + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{content} || $ticket->{priority} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{name} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{status} %></TD> + </TR> -<% } %> +% } + + </TABLE> diff --git a/httemplate/view/cust_pkg.cgi b/httemplate/view/cust_pkg.cgi deleted file mode 100755 index a20149ae2..000000000 --- a/httemplate/view/cust_pkg.cgi +++ /dev/null @@ -1,165 +0,0 @@ -<!-- mason kludge --> -<% - -my $conf = new FS::Conf; - -my %uiview = (); -my %uiadd = (); -foreach my $part_svc ( qsearch('part_svc',{}) ) { - $uiview{$part_svc->svcpart} = popurl(2). "view/". $part_svc->svcdb . ".cgi"; - $uiadd{$part_svc->svcpart}= popurl(2). "edit/". $part_svc->svcdb . ".cgi"; -} - -my ($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $pkgnum = $1; - -#get package record -my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); -die "No package!" unless $cust_pkg; -my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); - -my $custnum = $cust_pkg->getfield('custnum'); -print header('Package View', menubar( - "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", - 'Main Menu' => popurl(2) -)); - -#print info -my ($susp,$cancel,$expire)=( - $cust_pkg->getfield('susp'), - $cust_pkg->getfield('cancel'), - $cust_pkg->getfield('expire'), -); -my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); -my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); -my $otaker = $cust_pkg->getfield('otaker'); - -print <<END; -<SCRIPT> -function areyousure(href) { - if (confirm("Permanently delete included services and cancel this package?") == true) - window.location.href = href; -} -</SCRIPT> -END - -print "Package information"; -print ' (<A HREF="'. popurl(2). 'misc/unsusp_pkg.cgi?'. $pkgnum. - '">unsuspend</A>)' - if ( $susp && ! $cancel ); - -print ' (<A HREF="'. popurl(2). 'misc/susp_pkg.cgi?'. $pkgnum. - '">suspend</A>)' - unless ( $susp || $cancel ); - -print ' (<A HREF="javascript:areyousure(\''. popurl(2). 'misc/cancel_pkg.cgi?'. - $pkgnum. '\')">cancel</A>)' - unless $cancel; - -print ' (<A HREF="'. popurl(2). 'edit/REAL_cust_pkg.cgi?'. $pkgnum. - '">edit dates</A>)'; - -print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2), - '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">', - $pkgnum, '</TD></TR>', - '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">', - $pkg, '</TD></TR>', - '<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">', - $comment, '</TD></TR>', - '<TR><TD ALIGN="right">Setup date</TD><TD BGCOLOR="#ffffff">', - ( $setup ? time2str("%D",$setup) : "(Not setup)" ), '</TD></TR>'; - -print '<TR><TD ALIGN="right">Last bill date</TD><TD BGCOLOR="#ffffff">', - ( $cust_pkg->get('last_bill') ? time2str("%D",$cust_pkg->get('last_bill')) : " " ), - '</TD></TR>' - if $cust_pkg->dbdef_table->column('last_bill'); - -print '<TR><TD ALIGN="right">Next bill date</TD><TD BGCOLOR="#ffffff">', - ( $bill ? time2str("%D",$bill) : " " ), '</TD></TR>'; - -print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">', - time2str("%D",$susp), '</TD></TR>' if $susp; -print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">', - time2str("%D",$expire), '</TD></TR>' if $expire; -print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">', - time2str("%D",$cancel), '</TD></TR>' if $cancel; -print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">', - $otaker, '</TD></TR>', - '</TABLE></TD></TR></TABLE>'; - -unless ($expire) { - print <<END; -<FORM ACTION="../misc/expire_pkg.cgi" METHOD="post"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> -Expire (date): <INPUT TYPE="text" NAME="date" VALUE="" > -<INPUT TYPE="submit" VALUE="Cancel later"> -END -} - -unless ($cancel) { - - #services - print '<BR>Service Information', &table(); - - #list of services this pkgpart includes - my $pkg_svc; - my %pkg_svc; - #foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $cust_pkg->pkgpart }) ) { - foreach $pkg_svc ( $cust_pkg->part_pkg->pkg_svc ) { - $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity; - } - - #list of records from cust_svc - my $svcpart; - foreach $svcpart (sort {$a <=> $b} keys %pkg_svc) { - - my($svc)=qsearchs('part_svc',{'svcpart'=>$svcpart})->getfield('svc'); - - my(@cust_svc)=qsearch('cust_svc',{'pkgnum'=>$pkgnum, - 'svcpart'=>$svcpart, - }); - - my($enum); - for $enum ( 1 .. $pkg_svc{$svcpart} ) { - - my($cust_svc); - if ( $cust_svc=shift @cust_svc ) { - my($svcnum)=$cust_svc->svcnum; - my($label, $value, $svcdb) = $cust_svc->label; - print <<END; -<TR><TD><A HREF="$uiview{$svcpart}?$svcnum">(View/Edit) $svc: $value<A></TD></TR> -END - } else { - print qq!<TR><TD>!. - qq!<A HREF="$uiadd{$svcpart}?pkgnum$pkgnum-svcpart$svcpart">!. - qq!(Provision) $svc</A>!; - - print qq! or <A HREF="../misc/link.cgi?pkgnum$pkgnum-svcpart$svcpart">!. - qq!(Link to legacy) $svc</A>! - if $conf->exists('legacy_link'); - - print '</TD></TR>'; - } - - } - warn "WARNING: Leftover services pkgnum $pkgnum!" if @cust_svc;; - } - - print "</TABLE><FONT SIZE=-1>", - "Choose (View/Edit) to view or edit an existing service<BR>", - "Choose (Provision) to setup a new service<BR>"; - - print "Choose (Link to legacy) to link to a legacy (pre-Freeside) service" - if $conf->exists('legacy_link'); - - print "</FONT>"; -} - -#formatting -print <<END; - </BODY> -</HTML> -END - -%> diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html new file mode 100644 index 000000000..f5b65ac49 --- /dev/null +++ b/httemplate/view/elements/svc_Common.html @@ -0,0 +1,137 @@ +% # options example... +% # +% # 'table' => 'svc_something' +% # +% # 'labels' => { +% # 'column' => 'Label', +% # }, +% # +% # listref - each item is a literal column name (or method) or (notyet) coderef +% # if not specified all columns (except for the primary key) will be viewable +% # 'fields' => [ +% # ] +% # +% # # defaults to "edit/$table.cgi?", will have svcnum appended +% # 'edit_url' => +% +% +% if ( $custnum ) { + + + <% include("/elements/header.html","View $label: $value") %> + + <% include( '/elements/small_custview.html', $custnum, '', 1, + "${p}view/cust_main.cgi") %> + <BR> +% } else { + + + <SCRIPT> + function areyousure(href) { + if (confirm("Permanently delete this <% $label %>?") == true) + window.location.href = href; + } + </SCRIPT> + + <% include("/elements/header.html","View $label: $value", menubar( + "Cancel this (unaudited) $label" => + "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')" + )) %> +% } + + +Service #<B><% $svcnum %></B> +% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; +| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A> +<BR> + +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +% foreach my $f ( @$fields ) { +% +% my( $field, $type); +% if ( ref($f) ) { +% $field = $f->{'field'}, +% $type = $f->{'type'} || 'text', +% } else { +% $field = $f; +% $type = 'text'; +% } +% + + + <TR> + <TD ALIGN="right"> + <% ( $opt{labels} && exists $opt{labels}->{$field} ) + ? $opt{labels}->{$field} + : $field + %> + </TD> +% +% #eventually more options for <SELECT>, etc. fields +% + + + <TD BGCOLOR="#ffffff"><% $svc_x->$field %><TD> + + </TR> +% } +% foreach (sort { $a cmp $b } $svc_x->virtual_fields) { + + <% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %> +% } + + +</TABLE></TD></TR></TABLE> + +<BR> +<% joblisting({'svcnum'=>$svcnum}, 1) %> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer services') + || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me + +my(%opt) = @_; + +my $table = $opt{'table'}; + +my $fields = $opt{'fields'} + #|| [ grep { $_ ne 'svcnum' } dbdef->table($table)->columns ]; + || [ grep { $_ ne 'svcnum' } fields($table) ]; + +my $svcnum; +if ( $cgi->param('svcnum') ) { + $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum"; + $svcnum = $1; +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/ or die "no svcnum"; + $svcnum = $1; +} +my $svc_x = qsearchs({ + 'select' => $opt{'table'}.'.*', + 'table' => $opt{'table'}, + 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => { 'svcnum' => $svcnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n"; + +my $cust_svc = $svc_x->cust_svc; +my($label, $value, $svcdb) = $cust_svc->label; + +my $pkgnum = $cust_svc->pkgnum; + +my($cust_pkg, $custnum); +if ($pkgnum) { + $cust_pkg = $cust_svc->cust_pkg; + $custnum = $cust_pkg->custnum; +} else { + $cust_pkg = ''; + $custnum = ''; +} + +</%init> diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html new file mode 100644 index 000000000..defbee974 --- /dev/null +++ b/httemplate/view/svc_Common.html @@ -0,0 +1,29 @@ +<% include('elements/svc_Common.html', + 'table' => $table, + 'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=", + %opt, + ) +%> +<%init> + +# false laziness w/edit/svc_Common.html + +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +my $table = $1; +require "FS/$table.pm"; + +my %opt; +if ( UNIVERSAL::can("FS::$table", 'table_info') ) { + $opt{'name'} = "FS::$table"->table_info->{'name'}; + + my $fields = "FS::$table"->table_info->{'fields'}; + my %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } + keys %$fields; + $opt{'labels'} = \%labels; +} + +</%init> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index b42362d91..86478681c 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -1,51 +1,11 @@ -<% +% if ( $custnum ) { -my $conf = new FS::Conf; - -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; -my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); -die "Unknown svcnum" unless $svc_acct; - -#false laziness w/all svc_*.cgi -my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } ); -my $pkgnum = $cust_svc->getfield('pkgnum'); -my($cust_pkg, $custnum); -if ($pkgnum) { - $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); - $custnum = $cust_pkg->custnum; -} else { - $cust_pkg = ''; - $custnum = ''; -} -#eofalse - -my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); -die "Unknown svcpart" unless $part_svc; -my $svc = $part_svc->svc; - -die 'Empty domsvc for svc_acct.svcnum '. $svc_acct->svcnum - unless $svc_acct->domsvc; -my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } ); -die 'Unknown domain (domsvc '. $svc_acct->domsvc. - ' for svc_acct.svcnum '. $svc_acct->svcnum. ')' - unless $svc_domain; -my $domain = $svc_domain->domain; - -%> - -<% if ( $custnum ) { %> - - <%= header("View $svc account", menubar( - "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - "Main menu" => $p, - )) %> - - <%= include( '/elements/small_custview.html', $custnum, '', 1 ) %> + <% include("/elements/header.html","View $svc account") %> + <% include( '/elements/small_custview.html', $custnum, '', 1, + "${p}view/cust_main.cgi") %> <BR> -<% } else { %> +% } else { <SCRIPT> function areyousure(href) { @@ -54,76 +14,81 @@ my $domain = $svc_domain->domain; } </SCRIPT> - <%= header('Account View', menubar( + <% include("/elements/header.html",'Account View', menubar( "Cancel this (unaudited) account" => "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')", - "Main menu" => $p, )) %> -<% } %> - -<% if ( $part_svc->part_export_usage ) { - - my $last_bill; - my %plandata; - if ( $cust_pkg ) { - #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really - #belong in plan data - %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } - split("\n", $cust_pkg->part_pkg->plandata ); - - $last_bill = $cust_pkg->last_bill; - } else { - $last_bill = 0; - %plandata = (); - } +% } + +% if ( $part_svc->part_export_usage ) { +% +% my $last_bill; +% my %plandata; +% if ( $cust_pkg ) { +% #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really +% #belong in plan data +% %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } +% split("\n", $cust_pkg->part_pkg->plandata ); +% +% $last_bill = $cust_pkg->last_bill; +% } else { +% $last_bill = 0; +% %plandata = (); +% } +% +% my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time ); +% my $hour = int($seconds/3600); +% my $min = int( ($seconds%3600) / 60 ); +% my $sec = $seconds%60; +% +% my $input = $svc_acct->attribute_since_sqlradacct( +% $last_bill, time, 'AcctInputOctets' +% ) / 1048576; +% my $output = $svc_acct->attribute_since_sqlradacct( +% $last_bill, time, 'AcctOutputOctets' +% ) / 1048576; +% +% - my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time ); - my $hour = int($seconds/3600); - my $min = int( ($seconds%3600) / 60 ); - my $sec = $seconds%60; - - my $input = $svc_acct->attribute_since_sqlradacct( - $last_bill, time, 'AcctInputOctets' - ) / 1048576; - my $output = $svc_acct->attribute_since_sqlradacct( - $last_bill, time, 'AcctOutputOctets' - ) / 1048576; - -%> RADIUS session information<BR> - <%= ntable('#cccccc',2) %> + <% ntable('#cccccc',2) %> <TR><TD BGCOLOR="#ffffff"> +% if ( $seconds ) { + + Online <B><% $hour %></B>h <B><% $min %></B>m <B><% $sec %></B>s +% } else { - <% if ( $seconds ) { %> - Online <B><%= $hour %></B>h <B><%= $min %></B>m <B><%= $sec %></B>s - <% } else { %> Has not logged on - <% } %> +% } +% if ( $cust_pkg ) { + + since last bill (<% time2str('%a %b %o %Y', $last_bill) %>) +% if ( length($plandata{recur_included_hours}) ) { + + - <% $plandata{recur_included_hours} %> total hours in plan +% } - <% if ( $cust_pkg ) { %> - since last bill (<%= time2str('%a %b %o %Y', $last_bill) %>) - <% if ( length($plandata{recur_included_hours}) ) { %> - - <%= $plandata{recur_included_hours} %> total hours in plan - <% } %> <BR> - <% } else { %> +% } else { + (no billing cycle available for unaudited account)<BR> - <% } %> +% } + - Upload: <B><%= sprintf("%.3f", $input) %></B> megabytes<BR> - Download: <B><%= sprintf("%.3f", $output) %></B> megabytes<BR> + Upload: <B><% sprintf("%.3f", $input) %></B> megabytes<BR> + Download: <B><% sprintf("%.3f", $output) %></B> megabytes<BR> +% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=$svcnum!; - <% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=$svcnum!; %> View session detail: - <%= $href %>;begin=<%= $last_bill %>">this billing cycle</A> - | <%= $href %>;begin=<%= time-15552000 %>">past six months</A> - | <%= $href %>">all sessions</A> + <% $href %>;begin=<% $last_bill %>">this billing cycle</A> + | <% $href %>;begin=<% time-15552000 %>">past six months</A> + | <% $href %>">all sessions</A> </TD></TR></TABLE><BR> +% } -<% } %> <SCRIPT TYPE="text/javascript"> function enable_change () { @@ -134,199 +99,266 @@ function enable_change () { } } </SCRIPT> -<FORM NAME="OneTrueForm" ACTION="<%=$p%>edit/process/cust_svc.cgi"> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> - -<% #print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!; %> - -<% - my @part_svc = (); - if ( $pkgnum ) { - @part_svc = grep { $_->svcdb eq 'svc_acct' - && $_->svcpart != $part_svc->svcpart } - $cust_pkg->available_part_svc; - } else { - @part_svc = qsearch('part_svc', { - svcdb => 'svc_acct', - disabled => '', - svcpart => { op=>'!=', value=>$part_svc->svcpart }, - } ); - } -%> - -Service #<B><%= $svcnum %></B> -| <A HREF="<%=$p%>edit/svc_acct.cgi?<%=$svcnum%>">Edit this service</A> +<FORM NAME="OneTrueForm" ACTION="<%$p%>edit/process/cust_svc.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +% #print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!; +% +% my @part_svc = (); +% if ( $pkgnum ) { +% @part_svc = grep { $_->svcdb eq 'svc_acct' +% && $_->svcpart != $part_svc->svcpart } +% $cust_pkg->available_part_svc; +% } else { +% @part_svc = qsearch('part_svc', { +% svcdb => 'svc_acct', +% disabled => '', +% svcpart => { op=>'!=', value=>$part_svc->svcpart }, +% } ); +% } +% + + +Service #<B><% $svcnum %></B> +| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A> +% if ( @part_svc ) { -<% if ( @part_svc ) { %> | <SELECT NAME="svcpart" onChange="enable_change()"> <OPTION VALUE="">Change service</OPTION> <OPTION VALUE="">--------------</OPTION> - <% foreach my $opt_part_svc ( @part_svc ) { %> - <OPTION VALUE="<%= $opt_part_svc->svcpart %>"><%= $opt_part_svc->svc %></OPTION> - <% } %> +% foreach my $opt_part_svc ( @part_svc ) { + + <OPTION VALUE="<% $opt_part_svc->svcpart %>"><% $opt_part_svc->svc %></OPTION> +% } + </SELECT> <INPUT NAME="submit" TYPE="submit" VALUE="Change" disabled> -<% } %> +% } + -<%= &ntable("#cccccc") %><TR><TD><%= &ntable("#cccccc",2) %> +<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> <TR> <TD ALIGN="right">Service</TD> - <TD BGCOLOR="#ffffff"><%= $part_svc->svc %></TD> + <TD BGCOLOR="#ffffff"><% $part_svc->svc %></TD> </TR> <TR> <TD ALIGN="right">Username</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->username %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->username %></TD> </TR> <TR> <TD ALIGN="right">Domain</TD> - <TD BGCOLOR="#ffffff"><%= $domain %></TD> + <TD BGCOLOR="#ffffff"><% $domain %></TD> </TR> <TR> <TD ALIGN="right">Password</TD> <TD BGCOLOR="#ffffff"> +% my $password = $svc_acct->_password; +% if ( $password =~ /^\*\w+\* (.*)$/ ) { +% $password = $1; +% - <% my $password = $svc_acct->_password; %> - <% if ( $password =~ /^\*\w+\* (.*)$/ ) { - $password = $1; - %> <I>(login disabled)</I> - <% } %> +% } +% if ( $conf->exists('showpasswords') ) { + + <PRE><% encode_entities($password) %></PRE> +% } else { - <% if ( $conf->exists('showpasswords') ) { %> - <PRE><%= encode_entities($password) %></PRE> - <% } else { %> <I>(hidden)</I> - <% } %> +% } + </TD> </TR> -<% $password = ''; %> +% $password = ''; +% if ( $conf->exists('security_phrase') ) { +% my $sec_phrase = $svc_acct->sec_phrase; +% -<% if ( $conf->exists('security_phrase') ) { - my $sec_phrase = $svc_acct->sec_phrase; -%> <TR> <TD ALIGN="right">Security phrase</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->sec_phrase %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->sec_phrase %></TD> </TR> -<% } %> +% } +% if ( $svc_acct->popnum ) { +% my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum}); +% -<% if ( $svc_acct->popnum ) { - my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum}); -%> <TR> <TD ALIGN="right">Access number</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct_pop->text %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct_pop->text %></TD> </TR> -<% } %> +% } +% if ($svc_acct->uid ne '') { -<% if ($svc_acct->uid ne '') { %> <TR> <TD ALIGN="right">UID</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->uid %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->uid %></TD> </TR> -<% } %> +% } +% if ($svc_acct->gid ne '') { -<% if ($svc_acct->gid ne '') { %> <TR> <TD ALIGN="right">GID</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->gid %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->gid %></TD> </TR> -<% } %> +% } +% if ($svc_acct->finger ne '') { -<% if ($svc_acct->finger ne '') { %> <TR> <TD ALIGN="right">GECOS</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->finger %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->finger %></TD> </TR> -<% } %> +% } +% if ($svc_acct->dir ne '') { -<% if ($svc_acct->dir ne '') { %> <TR> <TD ALIGN="right">Home directory</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->dir %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->dir %></TD> </TR> -<% } %> +% } +% if ($svc_acct->shell ne '') { -<% if ($svc_acct->shell ne '') { %> <TR> <TD ALIGN="right">Shell</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->shell %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->shell %></TD> </TR> -<% } %> +% } +% if ($svc_acct->quota ne '') { -<% if ($svc_acct->quota ne '') { %> <TR> <TD ALIGN="right">Quota</TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->quota %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->quota %></TD> </TR> -<% } %> +% } +% if ($svc_acct->slipip) { -<% if ($svc_acct->slipip) { %> <TR> <TD ALIGN="right">IP address</TD> <TD BGCOLOR="#ffffff"> - <%= ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' ) + <% ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' ) ? "<I>(Dynamic)</I>" : $svc_acct->slipip %> </TD> </TR> -<% } %> +% } +% my %ulabel = ( seconds => 'Seconds', +% upbytes => 'Upload bytes', +% downbytes => 'Download bytes', +% totalbytes => 'Total bytes', +% ); +% foreach my $uf ( keys %ulabel ) { +% my $tf = $uf . "_threshold"; +% if ( $svc_acct->$tf ne '' ) { + <TR> + <TD ALIGN="right"><% $ulabel{$uf} %> remaining</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->$uf %></TD> + </TR> + +% } +% } +% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) { +% $attribute =~ /^radius_(.*)$/; +% my $pattribute = $FS::raddb::attrib{$1}; +% -<% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) { - $attribute =~ /^radius_(.*)$/; - my $pattribute = $FS::raddb::attrib{$1}; -%> <TR> - <TD ALIGN="right">Radius (reply) <%= $pattribute %></TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->getfield($attribute) %></TD> + <TD ALIGN="right">Radius (reply) <% $pattribute %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD> </TR> -<% } %> +% } +% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) { +% $attribute =~ /^rc_(.*)$/; +% my $pattribute = $FS::raddb::attrib{$1}; +% -<% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) { - $attribute =~ /^rc_(.*)$/; - my $pattribute = $FS::raddb::attrib{$1}; -%> <TR> - <TD ALIGN="right">Radius (check) <%= $pattribute %></TD> - <TD BGCOLOR="#ffffff"><%= $svc_acct->getfield($attribute) %></TD> + <TD ALIGN="right">Radius (check) <% $pattribute %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD> </TR> -<% } %> +% } + <TR> <TD ALIGN="right">RADIUS groups</TD> - <TD BGCOLOR="#ffffff"><%= join('<BR>', $svc_acct->radius_groups) %></TD> + <TD BGCOLOR="#ffffff"><% join('<BR>', $svc_acct->radius_groups) %></TD> </TR> +% if ( $svc_acct->seconds =~ /^\d+$/ ) { -<% if ( $svc_acct->seconds =~ /^\d+$/ ) { %> <TR> <TD ALIGN="right">Prepaid time</TD> - <TD BGCOLOR="#ffffff"><%= duration_exact($svc_acct->seconds) %></TD> + <TD BGCOLOR="#ffffff"><% duration_exact($svc_acct->seconds) %></TD> </TR> -<% } %> +% } +% +%# Can this be abstracted further? Maybe a library function like +%# widget('HTML', 'view', $svc_acct) ? It would definitely make UI +%# style management easier. +% +% foreach (sort { $a cmp $b } $svc_acct->virtual_fields) { -<% -# Can this be abstracted further? Maybe a library function like -# widget('HTML', 'view', $svc_acct) ? It would definitely make UI -# style management easier. -%> + <% $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %> +% } -<% foreach (sort { $a cmp $b } $svc_acct->virtual_fields) { %> - <%= $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %> -<% } %> </TABLE></TD></TR></TABLE> </FORM> <BR><BR> -<%= join("<BR>", $conf->config('svc_acct-notes') ) %> +<% join("<BR>", $conf->config('svc_acct-notes') ) %> <BR><BR> -<%= joblisting({'svcnum'=>$svcnum}, 1) %> +<% joblisting({'svcnum'=>$svcnum}, 1) %> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer services') + || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me + +my $conf = new FS::Conf; + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_acct = qsearchs({ + 'select' => 'svc_acct.*', + 'table' => 'svc_acct', + 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => {'svcnum'=>$svcnum}, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); +die "Unknown svcnum" unless $svc_acct; + +#false laziness w/all svc_*.cgi +my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } ); +my $pkgnum = $cust_svc->getfield('pkgnum'); +my($cust_pkg, $custnum); +if ($pkgnum) { + $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); + $custnum = $cust_pkg->custnum; +} else { + $cust_pkg = ''; + $custnum = ''; +} +#eofalse + +my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); +die "Unknown svcpart" unless $part_svc; +my $svc = $part_svc->svc; + +die 'Empty domsvc for svc_acct.svcnum '. $svc_acct->svcnum + unless $svc_acct->domsvc; +my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } ); +die 'Unknown domain (domsvc '. $svc_acct->domsvc. + ' for svc_acct.svcnum '. $svc_acct->svcnum. ')' + unless $svc_domain; +my $domain = $svc_domain->domain; -</BODY> -</HTML> +</%init> diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index f381b5ad3..a76e5a3d1 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -1,50 +1,4 @@ -<!-- mason kludge --> -<% - -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; -my $svc_broadband = qsearchs( 'svc_broadband', { 'svcnum' => $svcnum } ) - or die "svc_broadband: Unknown svcnum $svcnum"; - -#false laziness w/all svc_*.cgi -my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); -my $pkgnum = $cust_svc->getfield('pkgnum'); -my($cust_pkg, $custnum); -if ($pkgnum) { - $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); - $custnum = $cust_pkg->custnum; -} else { - $cust_pkg = ''; - $custnum = ''; -} -#eofalse - -my $addr_block = $svc_broadband->addr_block; -my $router = $addr_block->router; - -if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" }; - -my ( - $routername, - $routernum, - $speed_down, - $speed_up, - $ip_addr, - $ip_gateway, - $ip_netmask, - ) = ( - $router->getfield('routername'), - $router->getfield('routernum'), - $svc_broadband->getfield('speed_down'), - $svc_broadband->getfield('speed_up'), - $svc_broadband->getfield('ip_addr'), - $addr_block->ip_gateway, - $addr_block->NetAddr->mask, - ); -%> - -<%=header('Broadband Service View', menubar( +<%include("/elements/header.html",'Broadband Service View', menubar( ( ( $custnum ) ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", ) @@ -55,101 +9,205 @@ my ( )) %> -<A HREF="<%=${p}%>edit/svc_broadband.cgi?<%=$svcnum%>">Edit this information</A> +<A HREF="<%${p}%>edit/svc_broadband.cgi?<%$svcnum%>">Edit this information</A> <BR> -<%=ntable("#cccccc")%> +<%ntable("#cccccc")%> <TR> <TD> - <%=ntable("#cccccc",2)%> + <%ntable("#cccccc",2)%> <TR> <TD ALIGN="right">Service number</TD> - <TD BGCOLOR="#ffffff"><%=$svcnum%></TD> + <TD BGCOLOR="#ffffff"><%$svcnum%></TD> + </TR> + <TR> + <TD ALIGN="right">Description</TD> + <TD BGCOLOR="#ffffff"><%$description%></TD> </TR> <TR> <TD ALIGN="right">Router</TD> - <TD BGCOLOR="#ffffff"><%=$routernum%>: <%=$routername%></TD> + <TD BGCOLOR="#ffffff"><%$routernum%>: <%$routername%></TD> </TR> <TR> <TD ALIGN="right">Download Speed</TD> - <TD BGCOLOR="#ffffff"><%=$speed_down%></TD> + <TD BGCOLOR="#ffffff"><%$speed_down%></TD> </TR> <TR> <TD ALIGN="right">Upload Speed</TD> - <TD BGCOLOR="#ffffff"><%=$speed_up%></TD> + <TD BGCOLOR="#ffffff"><%$speed_up%></TD> </TR> <TR> <TD ALIGN="right">IP Address</TD> - <TD BGCOLOR="#ffffff"><%=$ip_addr%></TD> + <TD BGCOLOR="#ffffff"><%$ip_addr%></TD> </TR> <TR> <TD ALIGN="right">IP Netmask</TD> - <TD BGCOLOR="#ffffff"><%=$ip_netmask%></TD> + <TD BGCOLOR="#ffffff"><%$ip_netmask%></TD> </TR> <TR> <TD ALIGN="right">IP Gateway</TD> - <TD BGCOLOR="#ffffff"><%=$ip_gateway%></TD> + <TD BGCOLOR="#ffffff"><%$ip_gateway%></TD> + </TR> + <TR> + <TD ALIGN="right">MAC Address</TD> + <TD BGCOLOR="#ffffff"><%$mac_addr%></TD> + </TR> + <TR> + <TD ALIGN="right">Latitude</TD> + <TD BGCOLOR="#ffffff"><%$latitude%></TD> + </TR> + <TR> + <TD ALIGN="right">Longitude</TD> + <TD BGCOLOR="#ffffff"><%$longitude%></TD> + </TR> + <TR> + <TD ALIGN="right">Altitude</TD> + <TD BGCOLOR="#ffffff"><%$altitude%></TD> + </TR> + <TR> + <TD ALIGN="right">VLAN Profile</TD> + <TD BGCOLOR="#ffffff"><%$vlan_profile%></TD> + </TR> + <TR> + <TD ALIGN="right">Authentication Key</TD> + <TD BGCOLOR="#ffffff"><%$auth_key%></TD> </TR> <TR COLSPAN="2"><TD></TD></TR> +% +%foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) { +% print $svc_broadband->pvf($_)->widget('HTML', 'view', +% $svc_broadband->getfield($_)), "\n"; +%} +% +% -<% -foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) { - print $svc_broadband->pvf($_)->widget('HTML', 'view', - $svc_broadband->getfield($_)), "\n"; -} - -%> </TABLE> </TD> </TR> </TABLE> <BR> -<%=ntable("#cccccc", 2)%> -<% - my $sb_router = qsearchs('router', { svcnum => $svcnum }); - if ($sb_router) { - %> - <B>Router associated: <%=$sb_router->routername%> </B> - <A HREF="<%=popurl(2)%>edit/router.cgi?<%=$sb_router->routernum%>"> +<%ntable("#cccccc", 2)%> +% +% my $sb_router = qsearchs('router', { svcnum => $svcnum }); +% if ($sb_router) { +% + + <B>Router associated: <%$sb_router->routername%> </B> + <A HREF="<%popurl(2)%>edit/router.cgi?<%$sb_router->routernum%>"> (details) </A> <BR> - <% my @sb_addr_block; - if (@sb_addr_block = $sb_router->addr_block) { - %> +% my @sb_addr_block; +% if (@sb_addr_block = $sb_router->addr_block) { +% + <B>Address space </B> - <A HREF="<%=popurl(2)%>browse/addr_block.cgi"> + <A HREF="<%popurl(2)%>browse/addr_block.cgi"> (edit) </A> <BR> - <% print ntable("#cccccc", 1); - foreach (@sb_addr_block) { %> +% print ntable("#cccccc", 1); +% foreach (@sb_addr_block) { + <TR> - <TD><%=$_->ip_gateway%>/<%=$_->ip_netmask%></TD> + <TD><%$_->ip_gateway%>/<%$_->ip_netmask%></TD> </TR> - <% } %> +% } + </TABLE> - <% } else { %> +% } else { + <B>No address space allocated.</B> - <% } %> +% } + <BR> - <% - } else { -%> +% +% } else { +% + -<FORM METHOD="GET" ACTION="<%=popurl(2)%>edit/router.cgi"> - <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>"> +<FORM METHOD="GET" ACTION="<%popurl(2)%>edit/router.cgi"> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> Add router named - <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%=$svcnum%>)"> + <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%$svcnum%>)"> <INPUT TYPE="submit" VALUE="Add router"> </FORM> +% +%} +% -<% -} -%> <BR> -<%=joblisting({'svcnum'=>$svcnum}, 1)%> - </BODY> -</HTML> +<%joblisting({'svcnum'=>$svcnum}, 1)%> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer services') + || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_broadband = qsearchs({ + 'select' => 'svc_broadband.*', + 'table' => 'svc_broadband', + 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => { 'svcnum' => $svcnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}) or die "svc_broadband: Unknown svcnum $svcnum"; + +#false laziness w/all svc_*.cgi +my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); +my $pkgnum = $cust_svc->getfield('pkgnum'); +my($cust_pkg, $custnum); +if ($pkgnum) { + $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); + $custnum = $cust_pkg->custnum; +} else { + $cust_pkg = ''; + $custnum = ''; +} +#eofalse + +my $addr_block = $svc_broadband->addr_block; +my $router = $addr_block->router; + +if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" }; + +my ( + $routername, + $routernum, + $speed_down, + $speed_up, + $ip_addr, + $ip_gateway, + $ip_netmask, + $mac_addr, + $latitude, + $longitude, + $altitude, + $vlan_profile, + $auth_key, + $description, + ) = ( + $router->getfield('routername'), + $router->getfield('routernum'), + $svc_broadband->getfield('speed_down'), + $svc_broadband->getfield('speed_up'), + $svc_broadband->getfield('ip_addr'), + $addr_block->ip_gateway, + $addr_block->NetAddr->mask, + $svc_broadband->mac_addr, + $svc_broadband->latitude, + $svc_broadband->longitude, + $svc_broadband->altitude, + $svc_broadband->vlan_profile, + $svc_broadband->auth_key, + $svc_broadband->description, + ); +</%init> diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi index 428f3e9bf..7fdce37df 100755 --- a/httemplate/view/svc_domain.cgi +++ b/httemplate/view/svc_domain.cgi @@ -1,38 +1,4 @@ -<!-- mason kludge --> -<% - -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; -my $svc_domain = qsearchs('svc_domain',{'svcnum'=>$svcnum}); -die "Unknown svcnum" unless $svc_domain; - -my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -my $pkgnum = $cust_svc->getfield('pkgnum'); -my($cust_pkg, $custnum); -if ($pkgnum) { - $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - $custnum=$cust_pkg->getfield('custnum'); -} else { - $cust_pkg = ''; - $custnum = ''; -} - -my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); -die "Unknown svcpart" unless $part_svc; - -my $email = ''; -if ($svc_domain->catchall) { - my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } ); - die "Unknown svcpart" unless $svc_acct; - $email = $svc_acct->email; -} - -my $domain = $svc_domain->domain; - -%> - -<%= header('Domain View', menubar( +<% include("/elements/header.html",'Domain View', menubar( ( ( $pkgnum || $custnum ) ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", ) @@ -42,12 +8,12 @@ my $domain = $svc_domain->domain; "Main menu" => $p, )) %> -Service #<%= $svcnum %> -<BR>Service: <B><%= $part_svc->svc %></B> -<BR>Domain name: <B><%= $domain %></B> -<BR>Catch all email <A HREF="<%= ${p} %>misc/catchall.cgi?<%= $svcnum %>">(change)</A>: -<%= $email ? "<B>$email</B>" : "<I>(none)<I>" %> -<BR><BR><A HREF="<%= ${p} %>misc/whois.cgi?custnum=<%=$custnum%>;svcnum=<%=$svcnum%>;domain=<%=$domain%>">View whois information.</A> +Service #<% $svcnum %> +<BR>Service: <B><% $part_svc->svc %></B> +<BR>Domain name: <B><% $domain %></B> +<BR>Catch all email <A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>: +<% $email ? "<B>$email</B>" : "<I>(none)<I>" %> +<BR><BR><A HREF="<% ${p} %>misc/whois.cgi?custnum=<%$custnum%>;svcnum=<%$svcnum%>;domain=<%$domain%>">View whois information.</A> <BR><BR> <SCRIPT> function areyousure(href, message) { @@ -59,50 +25,121 @@ Service #<%= $svcnum %> } </SCRIPT> -<% my @records; if ( @records = $svc_domain->domain_record ) { %> - <%= ntable("",2) %> - <tr><th>Zone</th><th>Type</th><th>Data</th></tr> - - <% foreach my $domain_record ( @records ) { - my $type = $domain_record->rectype eq '_mstr' - ? "(slave)" - : $domain_record->recaf. ' '. $domain_record->rectype; - %> - - <tr><td><%= $domain_record->reczone %></td> - <td><%= $type %></td> - <td><%= $domain_record->recdata %> - - <% unless ( $domain_record->rectype eq 'SOA' ) { %> - (<A HREF="javascript:areyousure('<%=$p%>misc/delete-domain_record.cgi?<%=$domain_record->recnum%>', 'Delete \'<%= $domain_record->reczone %> <%= $type %> <%= $domain_record->recdata %>\' ?' )">delete</A>) - <% } %> - </td></tr> - <% } %> +% my @records; if ( @records = $svc_domain->domain_record ) { + + <% include('/elements/table-grid.html') %> + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = $bgcolor2; + + <tr> + <th CLASS="grid" BGCOLOR="#cccccc">Zone</th> + <th CLASS="grid" BGCOLOR="#cccccc">Type</th> + <th CLASS="grid" BGCOLOR="#cccccc">Data</th> + </tr> + +% foreach my $domain_record ( @records ) { +% my $type = $domain_record->rectype eq '_mstr' +% ? "(slave)" +% : $domain_record->recaf. ' '. $domain_record->rectype; + + + <tr> + <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->reczone %></td> + <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $type %></td> + <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->recdata %> + +% unless ( $domain_record->rectype eq 'SOA' ) { + (<A HREF="javascript:areyousure('<%$p%>misc/delete-domain_record.cgi?<%$domain_record->recnum%>', 'Delete \'<% $domain_record->reczone %> <% $type %> <% $domain_record->recdata %>\' ?' )">delete</A>) +% } + </td> + </tr> + + +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + +% } + </table> -<% } %> +% } + <BR> -<FORM METHOD="POST" ACTION="<%=$p%>edit/process/domain_record.cgi"> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>"> +<FORM METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> <INPUT TYPE="text" NAME="reczone"> <INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN <SELECT NAME="rectype"> -<% foreach (qw( A NS CNAME MX PTR TXT) ) { %> - <OPTION VALUE="<%=$_%>"><%=$_%></OPTION> -<% } %> +% foreach (qw( A NS CNAME MX PTR TXT) ) { + + <OPTION VALUE="<%$_%>"><%$_%></OPTION> +% } + </SELECT> <INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Add record"> </FORM><BR><BR>or<BR><BR> -<FORM NAME="SlaveForm" METHOD="POST" ACTION="<%=$p%>edit/process/domain_record.cgi"> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>"> +<FORM NAME="SlaveForm" METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> +% if ( @records ) { + Delete all records and +% } -<% if ( @records ) { %> Delete all records and <% } %> Slave from nameserver IP -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> <INPUT TYPE="hidden" NAME="reczone" VALUE="@"> <INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> <INPUT TYPE="hidden" NAME="rectype" VALUE="_mstr"> <INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Slave domain" onClick="return slave_areyousure()"> </FORM> -<BR><BR><%= joblisting({'svcnum'=>$svcnum}, 1) %> -</BODY></HTML> +<BR><BR><% joblisting({'svcnum'=>$svcnum}, 1) %> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer services') + || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_domain = qsearchs({ + 'select' => 'svc_domain.*', + 'table' => 'svc_domain', + 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => {'svcnum'=>$svcnum}, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); +die "Unknown svcnum" unless $svc_domain; + +my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +my $pkgnum = $cust_svc->getfield('pkgnum'); +my($cust_pkg, $custnum); +if ($pkgnum) { + $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + $custnum=$cust_pkg->getfield('custnum'); +} else { + $cust_pkg = ''; + $custnum = ''; +} + +my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); +die "Unknown svcpart" unless $part_svc; + +my $email = ''; +if ($svc_domain->catchall) { + my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } ); + die "Unknown svcpart" unless $svc_acct; + $email = $svc_acct->email; +} + +my $domain = $svc_domain->domain; + +</%init> diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi index 49183cd95..b87166a17 100644 --- a/httemplate/view/svc_external.cgi +++ b/httemplate/view/svc_external.cgi @@ -1,11 +1,50 @@ -<!-- mason kludge --> -<% +<% include("/elements/header.html",'External Service View', menubar( + ( ( $custnum ) + ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + ) + : ( "Cancel this (unaudited) external service" => + "${p}misc/cancel-unaudited.cgi?$svcnum" ) + ), + "Main menu" => $p, +)) %> + +<A HREF="<%$p%>edit/svc_external.cgi?<%$svcnum%>">Edit this information</A><BR> +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> + +<TR><TD ALIGN="right">Service number</TD> + <TD BGCOLOR="#ffffff"><% $svcnum %></TD></TR> +<TR><TD ALIGN="right"><% FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TD> + <TD BGCOLOR="#ffffff"><% $conf->config('svc_external-display_type') eq 'artera_turbo' ? sprintf('%010d', $svc_external->id) : $svc_external->id %></TD></TR> +<TR><TD ALIGN="right"><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TD> + <TD BGCOLOR="#ffffff"><% $svc_external->title %></TD></TR> +% foreach (sort { $a cmp $b } $svc_external->virtual_fields) { + + <% $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %> +% } + + +</TABLE></TD></TR></TABLE> +<BR><% joblisting({'svcnum'=>$svcnum}, 1) %> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer services') + || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me my($query) = $cgi->keywords; $query =~ /^(\d+)$/; my $svcnum = $1; -my $svc_external = qsearchs( 'svc_external', { 'svcnum' => $svcnum } ) - or die "svc_external: Unknown svcnum $svcnum"; +my $svc_external = qsearchs({ + 'select' => 'svc_external.*', + 'table' => 'svc_external', + 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => { 'svcnum' => $svcnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}) or die "svc_external: Unknown svcnum $svcnum"; my $conf = new FS::Conf; @@ -22,33 +61,4 @@ if ($pkgnum) { } #eofalse - -%> - -<%= header('External Service View', menubar( - ( ( $custnum ) - ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - ) - : ( "Cancel this (unaudited) external service" => - "${p}misc/cancel-unaudited.cgi?$svcnum" ) - ), - "Main menu" => $p, -)) %> - -<A HREF="<%=$p%>edit/svc_external.cgi?<%=$svcnum%>">Edit this information</A><BR> -<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %> - -<TR><TD ALIGN="right">Service number</TD> - <TD BGCOLOR="#ffffff"><%= $svcnum %></TD></TR> -<TR><TD ALIGN="right"><%= FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TD> - <TD BGCOLOR="#ffffff"><%= $conf->config('svc_external-display_type') eq 'artera_turbo' ? sprintf('%010d', $svc_external->id) : $svc_external->id %></TD></TR> -<TR><TD ALIGN="right"><%= FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TD> - <TD BGCOLOR="#ffffff"><%= $svc_external->title %></TD></TR> - -<% foreach (sort { $a cmp $b } $svc_external->virtual_fields) { %> - <%= $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %> -<% } %> - -</TABLE></TD></TR></TABLE> -<BR><%= joblisting({'svcnum'=>$svcnum}, 1) %> -</BODY></HTML> +</%init> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi index 52360bcc2..487ebb220 100755 --- a/httemplate/view/svc_forward.cgi +++ b/httemplate/view/svc_forward.cgi @@ -1,84 +1,94 @@ -<!-- mason kludge --> -<% +% die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('View customer services') +% || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me +% +%my $conf = new FS::Conf; +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_forward = qsearchs({ +% 'select' => 'svc_forward.*', +% 'table' => 'svc_forward', +% 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) ', +% 'hashref' => {'svcnum'=>$svcnum}, +% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +%}); +%die "Unknown svcnum" unless $svc_forward; +% +%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% $custnum=$cust_pkg->getfield('custnum'); +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ) +% or die "Unkonwn svcpart"; +% +%print header('Mail Forward View', menubar( +% ( ( $pkgnum || $custnum ) +% ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +% ) +% : ( "Cancel this (unaudited) mail forward" => +% "${p}misc/cancel-unaudited.cgi?$svcnum" ) +% ), +% "Main menu" => $p, +%)); +% +%my($srcsvc,$dstsvc,$dst) = ( +% $svc_forward->srcsvc, +% $svc_forward->dstsvc, +% $svc_forward->dst, +%); +%my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; +% +%my $svc = $part_svc->svc; +% +%my $source; +%if ($srcsvc) { +% my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc}) +% or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc"; +% $source = $svc_acct->email; +%} else { +% $source = $src; +%} +% +%my $destination; +%if ($dstsvc) { +% my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc}) +% or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc"; +% $destination = $svc_acct->email; +%} else { +% $destination = $dst; +%} +% +%print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!. +% ntable("#cccccc",2). +% '<TR><TD ALIGN="right">Service number</TD>'. +% qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. +% '<TR><TD ALIGN="right">Service</TD>'. +% qq!<TD BGCOLOR="#ffffff">$svc</TD></TR>!. +% qq!<TR><TD ALIGN="right">Email to</TD>!. +% qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!. +% qq!<TR><TD ALIGN="right">Forwards to </TD>!. +% qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!; +% +%foreach (sort { $a cmp $b } $svc_forward->virtual_fields) { +% print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)), +% "\n"; +%} +% +%print qq! </TABLE>!. +% '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). +% '</BODY></HTML>' +%; +% +% -my $conf = new FS::Conf; - -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; -my $svc_forward = qsearchs('svc_forward',{'svcnum'=>$svcnum}); -die "Unknown svcnum" unless $svc_forward; - -my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); -my $pkgnum = $cust_svc->getfield('pkgnum'); -my($cust_pkg, $custnum); -if ($pkgnum) { - $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - $custnum=$cust_pkg->getfield('custnum'); -} else { - $cust_pkg = ''; - $custnum = ''; -} - -my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ) - or die "Unkonwn svcpart"; - -print header('Mail Forward View', menubar( - ( ( $pkgnum || $custnum ) - ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - ) - : ( "Cancel this (unaudited) mail forward" => - "${p}misc/cancel-unaudited.cgi?$svcnum" ) - ), - "Main menu" => $p, -)); - -my($srcsvc,$dstsvc,$dst) = ( - $svc_forward->srcsvc, - $svc_forward->dstsvc, - $svc_forward->dst, -); -my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; - -my $svc = $part_svc->svc; - -my $source; -if ($srcsvc) { - my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc}) - or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc"; - $source = $svc_acct->email; -} else { - $source = $src; -} - -my $destination; -if ($dstsvc) { - my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc}) - or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc"; - $destination = $svc_acct->email; -} else { - $destination = $dst; -} - -print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!. - ntable("#cccccc",2). - '<TR><TD ALIGN="right">Service number</TD>'. - qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. - '<TR><TD ALIGN="right">Service</TD>'. - qq!<TD BGCOLOR="#ffffff">$svc</TD></TR>!. - qq!<TR><TD ALIGN="right">Email to</TD>!. - qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!. - qq!<TR><TD ALIGN="right">Forwards to </TD>!. - qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!; - -foreach (sort { $a cmp $b } $svc_forward->virtual_fields) { - print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)), - "\n"; -} - -print qq! </TABLE>!. - '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). - '</BODY></HTML>' -; - -%> diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi new file mode 100644 index 000000000..732f3cd79 --- /dev/null +++ b/httemplate/view/svc_phone.cgi @@ -0,0 +1,10 @@ +<% include('elements/svc_Common.html', + 'table' => 'svc_phone', + 'fields' => [qw( countrycode phonenum )], #pin + 'labels' => { + 'countrycode' => 'Country code', + 'phonenum' => 'Phone number', + 'pin' => 'PIN', + }, + ) +%> diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi index 6c8cd6a0b..0579a55b4 100644 --- a/httemplate/view/svc_www.cgi +++ b/httemplate/view/svc_www.cgi @@ -1,73 +1,88 @@ -<!-- mason kludge --> -<% +% die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('View customer services') +% || $FS::CurrentUser::CurrentUser->access_right('View customer'); #XXX remove me +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_www = qsearchs({ +% 'select' => 'svc_www.*', +% 'table' => 'svc_www', +% 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) ', +% 'hashref' => { 'svcnum' => $svcnum }, +% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +%}) or die "svc_www: Unknown svcnum $svcnum"; +% +%#false laziness w/all svc_*.cgi +%my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); +% $custnum = $cust_pkg->custnum; +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +%#eofalse +% +%my $part_svc=qsearchs('part_svc',{'svcpart'=>$cust_svc->svcpart}) +% or die "svc_www: Unknown svcpart" . $cust_svc->svcpart; -my($query) = $cgi->keywords; -$query =~ /^(\d+)$/; -my $svcnum = $1; -my $svc_www = qsearchs( 'svc_www', { 'svcnum' => $svcnum } ) - or die "svc_www: Unknown svcnum $svcnum"; +%my $usersvc = $svc_www->usersvc; +%my $svc_acct = ''; +%my $email = ''; +%if ( $usersvc ) { +% $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } ) +% or die "svc_www: Unknown usersvc $usersvc"; +% $email = $svc_acct->email; +%} +% +%my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } ) +% or die "svc_www: Unknown recnum ". $svc_www->recnum; +% +%my $www = $domain_record->zone; +% +%print header('Website View', menubar( +% ( ( $custnum ) +% ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +% ) +% : ( "Cancel this (unaudited) website" => +% "${p}misc/cancel-unaudited.cgi?$svcnum" ) +% ), +% "Main menu" => $p, +%)). +% qq!<A HREF="${p}edit/svc_www.cgi?$svcnum">Edit this information</A><BR>!. +% ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2). +% qq!<TR><TD ALIGN="right">Service number</TD>!. +% qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. +% qq!<TR><TD ALIGN="right">Website name</TD>!. +% qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!; +%if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F' +% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) { +% print qq!<TR><TD ALIGN="right">Account</TD>!. +% qq!<TD BGCOLOR="#ffffff">!; +% +% if ( $usersvc ) { +% print qq!<A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A>!; +% } else { +% print '</i>(none)</i>'; +% } +% +% print '</TD></TR>'; +%} +% +%foreach (sort { $a cmp $b } $svc_www->virtual_fields) { +% print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)), +% "\n"; +%} +% +% +%print '</TABLE></TD></TR></TABLE>'. +% '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). +% '</BODY></HTML>' +%; +% -#false laziness w/all svc_*.cgi -my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); -my $pkgnum = $cust_svc->getfield('pkgnum'); -my($cust_pkg, $custnum); -if ($pkgnum) { - $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); - $custnum = $cust_pkg->custnum; -} else { - $cust_pkg = ''; - $custnum = ''; -} -#eofalse - -my $usersvc = $svc_www->usersvc; -my $svc_acct = ''; -my $email = ''; -if ( $usersvc ) { - $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } ) - or die "svc_www: Unknown usersvc $usersvc"; - $email = $svc_acct->email; -} - -my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } ) - or die "svc_www: Unknown recnum ". $svc_www->recnum; - -my $www = $domain_record->zone; - -print header('Website View', menubar( - ( ( $custnum ) - ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", - ) - : ( "Cancel this (unaudited) website" => - "${p}misc/cancel-unaudited.cgi?$svcnum" ) - ), - "Main menu" => $p, -)). - qq!<A HREF="${p}edit/svc_www.cgi?$svcnum">Edit this information</A><BR>!. - ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2). - qq!<TR><TD ALIGN="right">Service number</TD>!. - qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. - qq!<TR><TD ALIGN="right">Website name</TD>!. - qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!. - qq!<TR><TD ALIGN="right">Account</TD>!. - qq!<TD BGCOLOR="#ffffff">!; - -if ( $usersvc ) { - print qq!<A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A>!; -} else { - print '</i>(none)</i>'; -} - -print '</TD></TR>'; - -foreach (sort { $a cmp $b } $svc_www->virtual_fields) { - print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)), - "\n"; -} - - -print '</TABLE></TD></TR></TABLE>'. - '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). - '</BODY></HTML>' -; -%> diff --git a/init.d/freeside-init b/init.d/freeside-init index 56733bdc6..a62b187ab 100644 --- a/init.d/freeside-init +++ b/init.d/freeside-init @@ -40,6 +40,9 @@ case "$1" in kill `cat /var/run/freeside-queued.pid` echo "done." + #and + killall freeside-queued + echo -n "Stopping freeside-sqlradius-radacctd: " kill `cat /var/run/freeside-sqlradius-radacctd.pid` echo "done." diff --git a/install/debian/3.1/INSTALL b/install/debian/3.1/INSTALL index 550c71a57..c047660f0 100644 --- a/install/debian/3.1/INSTALL +++ b/install/debian/3.1/INSTALL @@ -28,7 +28,7 @@ apt-get install make screen zsh cvs fsh rsync \ libtext-autoformat-perl libtext-quoted-perl libregexp-common-perl \ libhtml-scrubber-perl libtree-simple-perl liblocale-subcountry-perl \ libtext-csv-perl libspreadsheet-writeexcel-perl libfrontier-rpc-perl \ - libjavascript-rpc-perl libipc-run3-perl + libjavascript-rpc-perl libipc-run3-perl libjson-perl useradd freeside groupadd freeside diff --git a/rt/Changelog b/rt/Changelog index 2da390e97..a01bc89c1 100644 --- a/rt/Changelog +++ b/rt/Changelog @@ -1,9 +1,789 @@ ------------------------------------------------------------------------ -r3729 | jesse | 2005-08-28 15:45:06 -0400 (Sun, 28 Aug 2005) | 1 line +r4386 | jesse | 2006-01-12 10:52:27 -0500 (Thu, 12 Jan 2006) | 1 line Changed paths: - A /rt/tags/3.4.4 (from /rt/branches/3.4-RELEASE:3728) + A /rt/tags/3.4.5 (from /rt/branches/3.4-RELEASE:4385) + +Tagged as 3.4.5 by svn RelEng 1.0 +------------------------------------------------------------------------ +r4385 | jesse | 2006-01-12 10:48:22 -0500 (Thu, 12 Jan 2006) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/releng.cnf + + r22371@truegrounds: jesse | 2006-01-12 16:25:39 +0100 + * This be 3.4.5 + +------------------------------------------------------------------------ +r4384 | jesse | 2006-01-12 10:48:13 -0500 (Thu, 12 Jan 2006) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/sbin/rt-setup-database.in + + r22370@truegrounds: jesse | 2006-01-12 16:25:19 +0100 + * Silence a warning introduced by a patch to fix oracle installs + +------------------------------------------------------------------------ +r4383 | jesse | 2006-01-12 10:48:03 -0500 (Thu, 12 Jan 2006) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/t/regression/22search_tix_by_txn.t + + r22369@truegrounds: jesse | 2006-01-12 16:23:48 +0100 + * Forced timezone for a date test to GMT, since it's searching on subjective dates + +------------------------------------------------------------------------ +r4372 | jesse | 2006-01-11 12:22:05 -0500 (Wed, 11 Jan 2006) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/de.po + + r22357@truegrounds: jesse | 2006-01-11 18:20:01 +0100 + RT-Ticket: 7222 + RT-Status: resolved + RT-Update: correspond + + * German translation update from Dirk Pape + +------------------------------------------------------------------------ +r4307 | jesse | 2005-12-13 16:54:03 -0500 (Tue, 13 Dec 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/releng.cnf + + r20436@truegrounds: jesse | 2005-12-13 16:51:41 -0500 + * 3.4.5rc3 + +------------------------------------------------------------------------ +r4306 | jesse | 2005-12-13 16:53:52 -0500 (Tue, 13 Dec 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N.pm + + r20435@truegrounds: jesse | 2005-12-13 16:51:06 -0500 + * warning silencing for a log message + +------------------------------------------------------------------------ +r4303 | alexmv | 2005-12-13 13:58:20 -0500 (Tue, 13 Dec 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + + r7707@zoq-fot-pik: chmrr | 2005-12-13 13:54:45 -0500 + * I don't think this join to Attachments is needed or useful -- it means you don't see changes with no attachments + +------------------------------------------------------------------------ +r4220 | jesse | 2005-12-02 17:59:40 -0500 (Fri, 02 Dec 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/releng.cnf + + r19695@truegrounds: jesse | 2005-12-02 17:58:50 -0500 + * RC2 + +------------------------------------------------------------------------ +r4216 | jesse | 2005-12-02 17:02:21 -0500 (Fri, 02 Dec 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Search/Build.html + + r19688@truegrounds: jesse | 2005-12-02 17:01:28 -0500 + RT-Ticket: 6962 + RT-Status: resolved + RT-Update: correspond + + * Patch from Rolf Grossmann to fix some bogosity in the query builder + + +------------------------------------------------------------------------ +r4212 | jesse | 2005-12-01 23:14:40 -0500 (Thu, 01 Dec 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/Header + A /rt/branches/3.4-RELEASE/html/NoAuth/printrt.css + + r19674@truegrounds: jesse | 2005-12-01 23:13:50 -0500 + * Added a print stylesheet from Koos van den Hout + +------------------------------------------------------------------------ +r4175 | jesse | 2005-11-30 16:03:40 -0500 (Wed, 30 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/releng.cnf + + r19588@truegrounds: jesse | 2005-11-30 16:00:10 -0500 + * Bump to 3.4.5rc1 + +------------------------------------------------------------------------ +r4154 | jesse | 2005-11-29 18:55:07 -0500 (Tue, 29 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/ShowCustomFields + M /rt/branches/3.4-RELEASE/html/Ticket/Elements/EditCustomFields + + r19545@truegrounds: jesse | 2005-11-29 18:51:07 -0500 + * A pair of new callbacks to make it easier to hide away a custom field on ticket display/edit + +------------------------------------------------------------------------ +r4120 | robert | 2005-11-19 22:52:28 -0500 (Sat, 19 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + + r4186@bear: rspier | 2005-11-19 19:51:38 -0800 + typo fix: s/load/Load/ + +------------------------------------------------------------------------ +r4096 | alexmv | 2005-11-14 18:34:44 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/CustomFieldValues.pm + + r7182@zoq-fot-pik: chmrr | 2005-11-14 18:34:13 -0500 + RT-Ticket: 6994 + RT-Status: resolved + RT-Update: correspond + * Sort custom vield values by SortOrder, then *Name*, then id; patch + from Troy Davis <troy@nack.net> + +------------------------------------------------------------------------ +r4092 | alexmv | 2005-11-14 17:35:40 -0500 (Mon, 14 Nov 2005) | 6 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/EmailParser.pm + M /rt/branches/3.4-RELEASE/lib/RT/Interface/Email.pm + + r7175@zoq-fot-pik: chmrr | 2005-11-14 17:35:03 -0500 + RT-Ticket: 7010 + RT-Status: resolved + RT-Update: correspond + * Treat our email addresses as case-insensitive + +------------------------------------------------------------------------ +r4090 | ruz | 2005-11-14 17:02:36 -0500 (Mon, 14 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE/html/Elements/QueryString + +* /Elements/QueryString now supports ARRAY refs, this allow us to handle + multiple arguments with the same name, this behaviour is consistent with + how HTML::Mason handle arguments +------------------------------------------------------------------------ +r4089 | ruz | 2005-11-14 16:57:36 -0500 (Mon, 14 Nov 2005) | 1 line +Changed paths: + M /rt/branches/3.4-RELEASE/etc/schema.mysql + +* revert back mysql.schema, commited by accident +------------------------------------------------------------------------ +r4087 | ruz | 2005-11-14 16:50:12 -0500 (Mon, 14 Nov 2005) | 2 lines +Changed paths: + M /rt/branches/3.4-RELEASE/etc/schema.mysql + M /rt/branches/3.4-RELEASE/html/Elements/Callback + +* fix: really hide hidden paths from callbacks +* fix: fetch data from the %cache by one key when store data with other +------------------------------------------------------------------------ +r4086 | alexmv | 2005-11-14 16:49:33 -0500 (Mon, 14 Nov 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/etc/RT_Config.pm.in + M /rt/branches/3.4-RELEASE/lib/RT/Interface/Email.pm + + r7165@zoq-fot-pik: chmrr | 2005-11-14 16:49:07 -0500 + RT-Ticket: 7131 + RT-Status: resolved + RT-Update: correspond + * The $RT::rtname regex should be case insensitive for matching + subjects; thanks to Phil Smith III <psmith@levanta.com> for the + catch + +------------------------------------------------------------------------ +r4085 | alexmv | 2005-11-14 16:30:12 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/NoAuth/webrt.css + + r7163@zoq-fot-pik: chmrr | 2005-11-14 16:29:36 -0500 + RT-Ticket: 6507 + RT-Status: resolved + RT-Update: correspond + * Standardize fonts to "Verdana, Arial, Helvetica, sans-serif"; + variant of patch from Maxime Henrion <mux@FreeBSD.org> + +------------------------------------------------------------------------ +r4084 | alexmv | 2005-11-14 15:51:27 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Record.pm + + r7161@zoq-fot-pik: chmrr | 2005-11-14 15:50:56 -0500 + RT-Ticket: 6458 + RT-Status: resolved + RT-Update: correspond + * Removed extra return argument from _AddLink, thanks to Todd Chapman + <todd@chaka.net> + +------------------------------------------------------------------------ +r4083 | alexmv | 2005-11-14 15:43:24 -0500 (Mon, 14 Nov 2005) | 6 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + + r7159@zoq-fot-pik: chmrr | 2005-11-14 15:42:48 -0500 + RT-Ticket: 6457 + RT-Status: resolved + RT-Update: correspond + * Typo in Ticket_Overlay.pm, found by Todd Chapman <todd@chaka.net> + +------------------------------------------------------------------------ +r4081 | alexmv | 2005-11-14 14:59:42 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/CollectionAsTable/Row + + r7155@zoq-fot-pik: chmrr | 2005-11-14 14:59:06 -0500 + RT-Ticket: 7020 + RT-Status: resolved + RT-Update: correspond + * Actually make use of 'style' if it is provided; thanks to Kelly + F. Hickel <kfh@mqsoftware.com> + +------------------------------------------------------------------------ +r4080 | alexmv | 2005-11-14 14:55:17 -0500 (Mon, 14 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/fr.po + + r7152@zoq-fot-pik: chmrr | 2005-11-14 14:54:43 -0500 + * Restore rightful .po headers on new french translation + +------------------------------------------------------------------------ +r4079 | alexmv | 2005-11-14 14:52:57 -0500 (Mon, 14 Nov 2005) | 6 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/t/regression/09record_cf_api.t + + r7148@zoq-fot-pik: chmrr | 2005-11-14 14:51:58 -0500 + RT-Ticket: 6559 + RT-Status: resolved + RT-Update: correspond + * Tests from Todd Chapman for loading CF from a wrong queue + +------------------------------------------------------------------------ +r4078 | jesse | 2005-11-14 14:50:54 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/fr.po + + r18904@truegrounds: jesse | 2005-11-14 14:49:25 -0500 + RT-Ticket: 7105 + RT-Status: resolved + RT-Update: correspond + + Updated French translation from Jerome Fenal + +------------------------------------------------------------------------ +r4077 | alexmv | 2005-11-14 14:20:49 -0500 (Mon, 14 Nov 2005) | 6 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/ja.po + + r7146@zoq-fot-pik: chmrr | 2005-11-14 14:20:03 -0500 + RT-Ticket: 7090 + RT-Status: resolved + RT-Update: correspond + * New Japanese .po, from Daisuke Maki <daisuke@wafu.ne.jp> + +------------------------------------------------------------------------ +r4076 | jesse | 2005-11-14 14:18:48 -0500 (Mon, 14 Nov 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/t/regression/06mailgateway.t + + r18900@truegrounds: jesse | 2005-11-14 13:57:34 -0500 + RT-Ticket: 7122 + RT-Status: resolved + RT-Update: correspond + + * Patch from Todd Chapman to honor changed a $rtname variable when running + the test suite + +------------------------------------------------------------------------ +r4075 | jesse | 2005-11-14 14:18:36 -0500 (Mon, 14 Nov 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/t/regression/07acl.t + + r18899@truegrounds: jesse | 2005-11-14 13:40:24 -0500 + RT-Ticket: 7121 + RT-Status: resolved + RT-Update: correspond + + * Patch from Todd Chapman to make the web based acl tests honor RT::WebPath + + +------------------------------------------------------------------------ +r4074 | alexmv | 2005-11-14 13:52:00 -0500 (Mon, 14 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/etc/RT_Config.pm.in + M /rt/branches/3.4-RELEASE/lib/RT/Interface/Email.pm + + r7140@zoq-fot-pik: chmrr | 2005-11-14 13:51:14 -0500 + * Better bounce handling, from Abhijit Menon-Sen <ams@oryx.com> + +------------------------------------------------------------------------ +r4073 | jesse | 2005-11-14 13:37:01 -0500 (Mon, 14 Nov 2005) | 16 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/sbin/rt-setup-database.in + + r18895@truegrounds: jesse | 2005-11-14 13:35:29 -0500 + RT-Ticket: 7136 + RT-Status: resolved + RT-Update: correspond + + Stuart Knight reports: + + As part of the "initdb" processing, the scripts went through and created a new database user, in my case called RT3. + + When it came time to create the tables, the script was still logged on as the dba user "system", so all of tables/sequences were created under "system"'s schema. + + I followed through the rt-setup-database script, and spotted that there was a database disconnect, followed by an immediate reconnect, as the same user. (in the case of Oracle this still being the "dba" account) + + Putting an extra validation check in here for Oracle, and then connecting as the intended database user fixed up the issue. + + +------------------------------------------------------------------------ +r4072 | alexmv | 2005-11-14 13:33:43 -0500 (Mon, 14 Nov 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Principal_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/t/regression/07rights.t + + r7135@zoq-fot-pik: chmrr | 2005-11-14 13:32:23 -0500 + RT-Ticket: 7101 + RT-Status: resolved + RT-Update: correspond + + * Don't modify EquivObjects arrayref, thanks to Todd Chapman + + +------------------------------------------------------------------------ +r4071 | jesse | 2005-11-14 13:21:29 -0500 (Mon, 14 Nov 2005) | 10 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/etc/RT_Config.pm.in + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + A /rt/branches/3.4-RELEASE/lib/t/regression/14linking.t + + r18893@truegrounds: jesse | 2005-11-14 13:19:52 -0500 + RT-Ticket: 7128 + RT-Status: resolved + RT-Update: correspond + + A big patch from Todd Chapman (with lots of juicy tests) to optionally + create two transactions when you create a link. (Also, this means that we'll + run scrips twice). This is off by default in RT 3.4 + + +------------------------------------------------------------------------ +r4069 | jesse | 2005-11-14 12:55:46 -0500 (Mon, 14 Nov 2005) | 7 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/ScrubHTML + + r18888@truegrounds: jesse | 2005-11-14 12:54:25 -0500 + RT-Ticket: 7048 + RT-Status: resolved + RT-Update: correspond + + * Akos Torok pointed out that our HTML scrubber removed "PRE" tags from HTML + +------------------------------------------------------------------------ +r4065 | jesse | 2005-11-14 12:44:41 -0500 (Mon, 14 Nov 2005) | 9 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/README + + r18880@truegrounds: jesse | 2005-11-14 12:42:48 -0500 + RT-Ticket: 7081 + RT-Status: resolved + RT-Update: correspond + + * Added a note to the readme warning users to clean out the + mason cache on upgrades - Ruslan + + +------------------------------------------------------------------------ +r4064 | alexmv | 2005-11-14 12:43:06 -0500 (Mon, 14 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/ru.po + + r7122@zoq-fot-pik: chmrr | 2005-11-14 12:42:37 -0500 + * Updated russian translation from Andrew Kornilov <andy@eva.dp.ua> + +------------------------------------------------------------------------ +r4063 | jesse | 2005-11-14 12:38:59 -0500 (Mon, 14 Nov 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/RT__Ticket/ColumnMap + + r18877@truegrounds: jesse | 2005-11-14 12:37:37 -0500 + RT-Ticket: 7087 + RT-Status: resolved + RT-Update: correspond + + Displayed linked tickets in search results were inverted + + +------------------------------------------------------------------------ +r4061 | robert | 2005-11-13 00:14:57 -0500 (Sun, 13 Nov 2005) | 9 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/Header + M /rt/branches/3.4-RELEASE/html/Ticket/Elements/ShowTransactionAttachments + + r4124@bear: rspier | 2005-11-12 21:08:45 -0800 + Undefined Warning Elimination: + - index.html passes in $session{'home_refresh_interval'} which can be null. + + r4125@bear: rspier | 2005-11-12 21:14:28 -0800 + Undefined Warning Elimination: + GetHeader will return undefined when the header doesn't exist. (This is _good_, as that is different than empty.) + But.. =~ warns. + +------------------------------------------------------------------------ +r4060 | jesse | 2005-11-11 15:27:56 -0500 (Fri, 11 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/sbin/rt-test-dependencies.in + + r18722@truegrounds: jesse | 2005-11-11 15:26:34 -0500 + * SB 1.35 dependency + +------------------------------------------------------------------------ +r4059 | jesse | 2005-11-11 00:12:49 -0500 (Fri, 11 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Groups_Overlay.pm + + r18716@truegrounds: jesse | 2005-11-11 00:10:08 -0500 + * fix from ruslan for fallout from his WhoHaveRight refactoring + +------------------------------------------------------------------------ +r4035 | jesse | 2005-11-06 17:15:18 -0500 (Sun, 06 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/releng.cnf + + r18412@truegrounds: jesse | 2005-11-06 17:13:58 -0500 + * Bumped to 3.4.5pre1 + +------------------------------------------------------------------------ +r4034 | jesse | 2005-11-06 17:15:06 -0500 (Sun, 06 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Groups_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Principal_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Users_Overlay.pm + + r18411@truegrounds: jesse | 2005-11-06 17:13:33 -0500 + * Patch to significantly improve performance on "WhoHaveRight" from Ruslan. + +------------------------------------------------------------------------ +r4033 | jesse | 2005-11-06 17:13:38 -0500 (Sun, 06 Nov 2005) | 4 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Attachments_Overlay.pm + + r18409@truegrounds: jesse | 2005-11-06 17:11:57 -0500 + * Fix to attachment ordering when you ask for a txn's attachments. + (Postgres doesn't default to ordering by id, so we were getting the wrong txn content) + +------------------------------------------------------------------------ +r4011 | pdh | 2005-11-01 00:43:02 -0500 (Tue, 01 Nov 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE/html/Ticket/Elements/ShowTransactionAttachments + +Add a missing space, before the Style Police come after me. + + +------------------------------------------------------------------------ +r4010 | pdh | 2005-10-31 19:21:57 -0500 (Mon, 31 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE/html/Ticket/Elements/ShowTransactionAttachments + +Make $RT::MaxInlineBody work properly. + + +------------------------------------------------------------------------ +r3989 | alexmv | 2005-10-24 17:26:18 -0400 (Mon, 24 Oct 2005) | 4 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/EditCustomField + M /rt/branches/3.4-RELEASE/html/Elements/EditCustomFieldSelect + + r6881@zoq-fot-pik: chmrr | 2005-10-24 17:25:14 -0400 + * Ensure custom fields keep correct fallback values; for instance, if + "add another attachment" is clicked + +------------------------------------------------------------------------ +r3967 | jesse | 2005-10-14 17:10:24 -0400 (Fri, 14 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + + r17368@hualien: jesse | 2005-10-14 17:08:10 -0400 + * When Robert made the change to how CustomFieldValues works, he broke the API. Fixed + +------------------------------------------------------------------------ +r3966 | jesse | 2005-10-14 17:10:11 -0400 (Fri, 14 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + + r17360@hualien: jesse | 2005-10-14 15:21:46 -0400 + * Perltidy + +------------------------------------------------------------------------ +r3958 | ruz | 2005-10-13 08:40:24 -0400 (Thu, 13 Oct 2005) | 1 line +Changed paths: + M /rt/branches/3.4-RELEASE/html/User/Elements/Tabs + +* new callback in html/User/Elements/Tabs +------------------------------------------------------------------------ +r3957 | ruz | 2005-10-13 08:37:47 -0400 (Thu, 13 Oct 2005) | 1 line +Changed paths: + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay_SQL.pm + +* code comments +------------------------------------------------------------------------ +r3948 | ruz | 2005-10-10 20:01:50 -0400 (Mon, 10 Oct 2005) | 1 line +Changed paths: + M /rt/branches/3.4-RELEASE/lib/RT/Action/SendEmail.pm + +* get rid of "not a number" warning +------------------------------------------------------------------------ +r3945 | ruz | 2005-10-10 15:47:29 -0400 (Mon, 10 Oct 2005) | 4 lines +Changed paths: + M /rt/branches/3.4-RELEASE/lib/RT/Action/SendEmail.pm + +backport of the 3.5-TESTING@3543 +Changes: +* fix attachments ordering + +------------------------------------------------------------------------ +r3944 | ruz | 2005-10-10 15:27:36 -0400 (Mon, 10 Oct 2005) | 15 lines +Changed paths: + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay_SQL.pm + A /rt/branches/3.4-RELEASE/lib/t/regression/22search_tix_by_watcher.t + +backport of the 3.5-TESTING@3943 +Changes +* fix for search by owner's fields, now owner is WATCHERFIELD instead of ENUM +* added backward compatible variant for Owner, next searches should work +** Owner = '<id>' +** Owner != '<id>' +** Owner = '<name>' +** Owner != '<name>' +** for other operators or if subfield(subkey) is specified search works + as for other watchers +* Fix for searches like "Cc.Name <> 'SomeBody'", was skipping tickets + with empty Cc list. +* get rid of some unint warnings +* test suite for all corner cases + +------------------------------------------------------------------------ +r3938 | robert | 2005-10-07 00:20:15 -0400 (Fri, 07 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + + r3995@bear: rspier | 2005-10-06 21:19:24 -0700 + [fsck.com #7067] - If we can't find a customfield that the user is allowed to see on a ticket, don't return any values, (when specifying a custom field) + +------------------------------------------------------------------------ +r3901 | alexmv | 2005-10-03 14:15:35 -0400 (Mon, 03 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/cs.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/da.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/de.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/en.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/es.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/fi.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/fr.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/he.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/hu.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/id.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/it.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/ja.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/nl.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/no.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/pl.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/pt_br.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/ru.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/zh_cn.po + M /rt/branches/3.4-RELEASE/lib/RT/I18N/zh_tw.po + + r6568@zoq-fot-pik: chmrr | 2005-10-03 14:14:49 -0400 + * Header fixes in PO files to include correct RT version + +------------------------------------------------------------------------ +r3900 | alexmv | 2005-10-03 13:32:45 -0400 (Mon, 03 Oct 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/I18N/es.po + + r6566@zoq-fot-pik: chmrr | 2005-10-03 13:28:24 -0400 + * Updated spanish translation, thanks to Carlos Velasco + +------------------------------------------------------------------------ +r3896 | alexmv | 2005-09-30 15:56:31 -0400 (Fri, 30 Sep 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Approvals/Elements/PendingMyApproval + + r6558@zoq-fot-pik: chmrr | 2005-09-30 15:56:06 -0400 + RT-Ticket: 7029 + RT-Status: resolved + RT-Update: correspond + + * Applied missing limit for AdminCcs, from Todd Chapman + + +------------------------------------------------------------------------ +r3895 | alexmv | 2005-09-30 15:19:57 -0400 (Fri, 30 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Elements/RT__Ticket/ColumnMap + + r6555@zoq-fot-pik: chmrr | 2005-09-30 15:18:22 -0400 + * Link to the *other* end of the link, not the one that is us + +------------------------------------------------------------------------ +r3894 | alexmv | 2005-09-30 15:19:46 -0400 (Fri, 30 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Search/Elements/BuildFormatString + + r6554@zoq-fot-pik: chmrr | 2005-09-30 15:16:47 -0400 + * Remove unused and deprecated code path (bugs 6605, 7008) + +------------------------------------------------------------------------ +r3893 | jesse | 2005-09-28 13:27:29 -0400 (Wed, 28 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE/html/Search/Results.tsv + +Switch from ->CustomFields to ->TicketCustomFields to stop using a deprecated API. + Thanks to T.J. Maciak + +------------------------------------------------------------------------ +r3892 | robert | 2005-09-28 12:16:03 -0400 (Wed, 28 Sep 2005) | 8 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Action/SendEmail.pm + + r3945@bear: rspier | 2005-09-28 09:15:08 -0700 + Performance Improvement when Sending Email using sendmailpipe - + + MIME::Entity would bog down in certain cases because of it's use of IO::Scalar during stringification. MIME::Entity will be switching to IO::ScalarArray, which will help... but RT was causing it to store into a temporary string anyway, which was silly. + + This change has MIME::Entity write directly to the pipe, which is a lot more efficient. Seems to cut out ~33% of user time. (Because we don't need to have a temporary IO::Scalar thingy around.) Also will reduce peak memory usage. + + +------------------------------------------------------------------------ +r3881 | jesse | 2005-09-23 15:39:36 -0400 (Fri, 23 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Transaction_Overlay.pm + + r15970@hualien: jesse | 2005-09-23 15:37:43 -0400 + * Our algorithm for finding a fallback for transaction content wasn't trying hard enough. reported by John Gedeon. + +------------------------------------------------------------------------ +r3877 | alexmv | 2005-09-22 15:09:22 -0400 (Thu, 22 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/CustomField_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/EmailParser.pm + M /rt/branches/3.4-RELEASE/lib/RT/Link_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/ObjectCustomFieldValues_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Queue_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Record.pm + M /rt/branches/3.4-RELEASE/lib/RT/Ticket_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + M /rt/branches/3.4-RELEASE/lib/RT/Transactions_Overlay.pm + + r6458@zoq-fot-pik: chmrr | 2005-09-22 15:08:37 -0400 + * Add where the faulty caller was in deprecated warnings + +------------------------------------------------------------------------ +r3855 | jesse | 2005-09-16 12:26:10 -0400 (Fri, 16 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Search/Results.rdf + + r15770@hualien: jesse | 2005-09-16 12:23:15 -0400 + * The RSS feeds should come with a default subject, as feeds really want to have article titles in some clients + +------------------------------------------------------------------------ +r3854 | jesse | 2005-09-16 12:25:42 -0400 (Fri, 16 Sep 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/ACE_Overlay.pm + + r15749@hualien: jesse | 2005-09-15 11:14:56 -0400 + * It was possible to get into an infinite loop when removing a member from a group + +------------------------------------------------------------------------ +r3849 | jesse | 2005-09-13 12:07:54 -0400 (Tue, 13 Sep 2005) | 5 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Record.pm + + r15723@hualien: jesse | 2005-09-13 12:05:40 -0400 + * When pulling data out of the database, we need to be more careful + about whether it's utf8 or not. Thanks to Ruslan Zakirov + + +------------------------------------------------------------------------ +r3847 | glasser | 2005-09-12 18:11:43 -0400 (Mon, 12 Sep 2005) | 4 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/html/Search/Build.html + + r41532@maclaurin-seven-twelve: glasser | 2005-09-12 18:04:55 -0400 + Defining subs in Mason components is dangerous, since they clash with subs defined + in every other component. + +------------------------------------------------------------------------ +r3754 | robert | 2005-09-01 17:47:36 -0400 (Thu, 01 Sep 2005) | 10 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/lib/RT/Tickets_Overlay.pm + + r3800@bear: rspier | 2005-09-01 14:46:59 -0700 + RT-Ticket: 6986 + RT-Status: resolved + RT-Update: correspond + + If we didn't generate any SQL, don't pass it to FromSQL which will reset the dirty flag and then SB won't actually run anything. + + Also, tests. + + +------------------------------------------------------------------------ +r3739 | robert | 2005-08-31 16:46:16 -0400 (Wed, 31 Aug 2005) | 3 lines +Changed paths: + M /rt/branches/3.4-RELEASE + M /rt/branches/3.4-RELEASE/configure.ac + + r3748@woof: rspier | 2005-08-31 13:41:53 -0700 + Check for invalid character (-) in mysql database names and prevent RT from allowing it to be configured. -Tagged as 3.4.4 by svn RelEng 1.0 ------------------------------------------------------------------------ r3728 | jesse | 2005-08-28 15:44:18 -0400 (Sun, 28 Aug 2005) | 3 lines Changed paths: diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED index a013b832c..7e6d8df47 100644 --- a/rt/FREESIDE_MODIFIED +++ b/rt/FREESIDE_MODIFIED @@ -3,6 +3,7 @@ config.layout config.layout.in etc/RT_SiteConfig.pm lib/RT/Interface/Web_Vendor.pm +lib/RT/SearchBuilder.pm #need DBIx::SearchBuilder >= 1.36 for Pg 8.1+ lib/RT/URI/freeside.pm lib/RT/URI/freeside/Internal.pm lib/RT/URI/freeside/XMLRPC.pm @@ -21,3 +22,11 @@ html/Ticket/Elements/ShowCustomers html/Ticket/ModifyCustomers.html html/NoAuth/images/small-logo.png html/NoAuth/webrt.css + +html/Elements/TitleBoxStart +html/Search/Bulk.html + +html/Elements/FreesideNewCust +html/Elements/FreesideSearch +html/Elements/FreesideSvcSearch + @@ -147,6 +147,13 @@ want to read a more comprehensive installation guide at: /opt/rt3/sbin/rt-setup-database --action insert \ --datadir etc/upgrade/<version> + Clear mason cache dir: + + rm -fr /opt/rt3/var/mason_data/obj + + Stop and start web-server. + + 8 If you're upgrading from RT 2.0: Please upgrade from RT 2.0 to RT 3.2 and then follow the instructions diff --git a/rt/bin/webmux.pl b/rt/bin/webmux.pl deleted file mode 100755 index 96e7ebf8d..000000000 --- a/rt/bin/webmux.pl +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/perl -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - -use strict; - -BEGIN { - $ENV{'PATH'} = '/bin:/usr/bin'; # or whatever you need - $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'}; - $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'}; - $ENV{'ENV'} = '' if defined $ENV{'ENV'}; - $ENV{'IFS'} = '' if defined $ENV{'IFS'}; - -} - -use lib ("/opt/rt3/local/lib", "/opt/rt3/lib"); -use RT; - -package RT::Mason; - -use CGI qw(-private_tempfiles); #bring this in before mason, to make sure we - #set private_tempfiles - -BEGIN { - if ($mod_perl::VERSION >= 1.9908) { - require Apache::RequestUtil; - no warnings 'redefine'; - my $sub = *Apache::request{CODE}; - *Apache::request = sub { - my $r; - eval { $r = $sub->('Apache'); }; - # warn $@ if $@; - return $r; - }; - } - if ($CGI::MOD_PERL) { - require HTML::Mason::ApacheHandler; - } - else { - require HTML::Mason::CGIHandler; - } -} - -use HTML::Mason; # brings in subpackages: Parser, Interp, etc. - -use vars qw($Nobody $SystemUser $r); - -#This drags in RT's config.pm -RT::LoadConfig(); - -use Carp; - -{ - package HTML::Mason::Commands; - use vars qw(%session); - - use RT::Tickets; - use RT::Transactions; - use RT::Users; - use RT::CurrentUser; - use RT::Templates; - use RT::Queues; - use RT::ScripActions; - use RT::ScripConditions; - use RT::Scrips; - use RT::Groups; - use RT::GroupMembers; - use RT::CustomFields; - use RT::CustomFieldValues; - use RT::TicketCustomFieldValues; - - use RT::Interface::Web; - use MIME::Entity; - use Text::Wrapper; - use CGI::Cookie; - use Time::ParseDate; - use HTML::Entities; -} - - -# Activate the following if running httpd as root (the normal case). -# Resets ownership of all files created by Mason at startup. -# Note that mysql uses DB for sessions, so there's no need to do this. -unless ($RT::DatabaseType =~ /(mysql|Pg)/) { - # Clean up our umask to protect session files - umask(0077); - -if ( $CGI::MOD_PERL) { - chown( Apache->server->uid, Apache->server->gid, [$RT::MasonSessionDir] ) - if Apache->server->can('uid'); - } - # Die if WebSessionDir doesn't exist or we can't write to it - stat($RT::MasonSessionDir); - die "Can't read and write $RT::MasonSessionDir" - unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) ); -} - -my $ah = &RT::Interface::Web::NewApacheHandler(@RT::MasonParameters) if $CGI::MOD_PERL; - -sub handler { - ($r) = @_; - - local $SIG{__WARN__}; - local $SIG{__DIE__}; - - RT::Init(); - - # We don't need to handle non-text items - return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io; - - my %session; - my $status; - eval { $status = $ah->handle_request($r) }; - if ($@) { - $RT::Logger->crit($@); - } - - undef (%session); - - if ($RT::Handle->TransactionDepth) { - $RT::Handle->ForceRollback; - $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ; - } - return $status; -} - -1; diff --git a/rt/config.pld b/rt/config.pld new file mode 100644 index 000000000..c71c7bbdd --- /dev/null +++ b/rt/config.pld @@ -0,0 +1,19 @@ +(test "x$prefix" = "xNONE" || test "x$prefix" = "x") && prefix=/opt/rt3 +(test "x$exec_prefix" = "xNONE" || test "x$exec_prefix" = "x") && exec_prefix=${prefix} +bindir=${exec_prefix}/bin +sbindir=${exec_prefix}/sbin +sysconfdir=${prefix}/etc +mandir=${prefix}/man +libdir=${prefix}/lib +datadir=${prefix}/share +(test "x$htmldir" = "xNONE" || test "x$htmldir" = "x") && htmldir=${datadir}/html +(test "x$manualdir" = "xNONE" || test "x$manualdir" = "x") && manualdir=${datadir}/doc +localstatedir=${prefix}/var +(test "x$logfiledir" = "xNONE" || test "x$logfiledir" = "x") && logfiledir=${localstatedir}/log +(test "x$masonstatedir" = "xNONE" || test "x$masonstatedir" = "x") && masonstatedir=${localstatedir}/mason_data +(test "x$sessionstatedir" = "xNONE" || test "x$sessionstatedir" = "x") && sessionstatedir=${localstatedir}/session_data +(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=${prefix}/local +(test "x$custometcdir" = "xNONE" || test "x$custometcdir" = "x") && custometcdir=${customdir}/etc +(test "x$customhtmldir" = "xNONE" || test "x$customhtmldir" = "x") && customhtmldir=${customdir}/html +(test "x$customlexdir" = "xNONE" || test "x$customlexdir" = "x") && customlexdir=${customdir}/po +(test "x$customlibdir" = "xNONE" || test "x$customlibdir" = "x") && customlibdir=${customdir}/lib diff --git a/rt/configure b/rt/configure index 395fc0b19..b1c5bba71 100755 --- a/rt/configure +++ b/rt/configure @@ -1,7 +1,7 @@ #! /bin/sh -# From configure.ac Revision: 3070 . +# From configure.ac Revision: 3739 . # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.59 for RT 3.4.4. +# Generated by GNU Autoconf 2.59 for RT 3.4.5. # # Report bugs to <rt-bugs@fsck.com>. # @@ -270,13 +270,13 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='RT' PACKAGE_TARNAME='rt' -PACKAGE_VERSION='3.4.4' -PACKAGE_STRING='RT 3.4.4' +PACKAGE_VERSION='3.4.5' +PACKAGE_STRING='RT 3.4.5' PACKAGE_BUGREPORT='rt-bugs@fsck.com' ac_unique_file="lib/RT.pm.in" ac_default_prefix=/opt/rt3 -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS rt_version_major rt_version_minor rt_version_patch INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA PERL SPEEDY_BIN exp_prefix exp_exec_prefix exp_bindir exp_sbindir exp_sysconfdir exp_mandir exp_libdir exp_datadir htmldir exp_htmldir manualdir exp_manualdir exp_localstatedir logfiledir exp_logfiledir masonstatedir exp_masonstatedir sessionstatedir exp_sessionstatedir customdir exp_customdir custometcdir exp_custometcdir customhtmldir exp_customhtmldir customlexdir exp_customlexdir customlibdir exp_customlibdir rt_layout_name BIN_OWNER LIBS_OWNER LIBS_GROUP DB_TYPE DATABASE_ENV_PREF DB_HOST DB_PORT DB_RT_HOST DB_DBA DB_DATABASE DB_RT_USER DB_RT_PASS WEB_USER WEB_GROUP RTGROUP APACHECTL RT_DEVEL_MODE RT_VERSION_MAJOR RT_VERSION_MINOR RT_VERSION_PATCH RT_PATH RT_DOC_PATH RT_LOCAL_PATH RT_LIB_PATH RT_ETC_PATH CONFIG_FILE_PATH RT_BIN_PATH RT_SBIN_PATH RT_VAR_PATH RT_MAN_PATH MASON_DATA_PATH MASON_SESSION_PATH MASON_HTML_PATH LOCAL_ETC_PATH MASON_LOCAL_HTML_PATH LOCAL_LEXICON_PATH LOCAL_LIB_PATH DESTDIR RT_LOG_PATH LIBOBJS LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS rt_version_major rt_version_minor rt_version_patch INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA AWK PERL SPEEDY_BIN exp_prefix exp_exec_prefix exp_bindir exp_sbindir exp_sysconfdir exp_mandir exp_libdir exp_datadir htmldir exp_htmldir manualdir exp_manualdir exp_localstatedir logfiledir exp_logfiledir masonstatedir exp_masonstatedir sessionstatedir exp_sessionstatedir customdir exp_customdir custometcdir exp_custometcdir customhtmldir exp_customhtmldir customlexdir exp_customlexdir customlibdir exp_customlibdir rt_layout_name BIN_OWNER LIBS_OWNER LIBS_GROUP DB_TYPE DATABASE_ENV_PREF DB_HOST DB_PORT DB_RT_HOST DB_DBA DB_DATABASE DB_RT_USER DB_RT_PASS WEB_USER WEB_GROUP RTGROUP APACHECTL RT_DEVEL_MODE RT_VERSION_MAJOR RT_VERSION_MINOR RT_VERSION_PATCH RT_PATH RT_DOC_PATH RT_LOCAL_PATH RT_LIB_PATH RT_ETC_PATH CONFIG_FILE_PATH RT_BIN_PATH RT_SBIN_PATH RT_VAR_PATH RT_MAN_PATH MASON_DATA_PATH MASON_SESSION_PATH MASON_HTML_PATH LOCAL_ETC_PATH MASON_LOCAL_HTML_PATH LOCAL_LEXICON_PATH LOCAL_LIB_PATH DESTDIR RT_LOG_PATH LIBOBJS LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. @@ -729,7 +729,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures RT 3.4.4 to adapt to many kinds of systems. +\`configure' configures RT 3.4.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -786,7 +786,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of RT 3.4.4:";; + short | recursive ) echo "Configuration of RT 3.4.5:";; esac cat <<\_ACEOF @@ -927,7 +927,7 @@ fi test -n "$ac_init_help" && exit 0 if $ac_init_version; then cat <<\_ACEOF -RT configure 3.4.4 +RT configure 3.4.5 generated by GNU Autoconf 2.59 Copyright (C) 2003 Free Software Foundation, Inc. @@ -941,7 +941,7 @@ cat >&5 <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by RT $as_me 3.4.4, which was +It was created by RT $as_me 3.4.5, which was generated by GNU Autoconf 2.59. Invocation command line was $ $0 $@ @@ -1283,7 +1283,7 @@ rt_version_major=3 rt_version_minor=4 -rt_version_patch=4 +rt_version_patch=5 test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 @@ -1393,6 +1393,46 @@ test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +echo "$as_me:$LINENO: checking for $ac_word" >&5 +echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6 +if test "${ac_cv_prog_AWK+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + echo "$as_me:$LINENO: result: $AWK" >&5 +echo "${ECHO_T}$AWK" >&6 +else + echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6 +fi + + test -n "$AWK" && break +done + # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 @@ -1998,6 +2038,24 @@ if test "${with_my_user_group+set}" = set; then WEB_GROUP=$my_group fi; +# Test for valid database names +if test "$DB_TYPE" == "mysql" ; then + echo "$as_me:$LINENO: checking if database name is valid" >&5 +echo $ECHO_N "checking if database name is valid... $ECHO_C" >&6 + if echo $DB_DATABASE | $AWK '/-/ { exit 1 }' ; then + echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6 +else + { { echo "$as_me:$LINENO: error: no. database name ($DB_DATABASE) contains '-' which is not valid for mysql" >&5 +echo "$as_me: error: no. database name ($DB_DATABASE) contains '-' which is not valid for mysql" >&2;} + { (exit 1); exit 1; }; } + +fi + + + +fi + @@ -2465,7 +2523,7 @@ _ASBOX } >&5 cat >&5 <<_CSEOF -This file was extended by RT $as_me 3.4.4, which was +This file was extended by RT $as_me 3.4.5, which was generated by GNU Autoconf 2.59. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -2520,7 +2578,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_cs_version="\\ -RT config.status 3.4.4 +RT config.status 3.4.5 configured by $0, generated by GNU Autoconf 2.59, with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\" @@ -2726,6 +2784,7 @@ s,@rt_version_patch@,$rt_version_patch,;t t s,@INSTALL_PROGRAM@,$INSTALL_PROGRAM,;t t s,@INSTALL_SCRIPT@,$INSTALL_SCRIPT,;t t s,@INSTALL_DATA@,$INSTALL_DATA,;t t +s,@AWK@,$AWK,;t t s,@PERL@,$PERL,;t t s,@SPEEDY_BIN@,$SPEEDY_BIN,;t t s,@exp_prefix@,$exp_prefix,;t t diff --git a/rt/configure.ac b/rt/configure.ac index 1d0b95f51..0d38ba482 100644 --- a/rt/configure.ac +++ b/rt/configure.ac @@ -3,11 +3,11 @@ dnl dnl Process this file with autoconf to produce a configure script dnl dnl Embed in generated ./configure script the following CVS info: -AC_REVISION($Revision: 1.1.1.5 $)dnl +AC_REVISION($Revision: 1.1.1.6 $)dnl dnl Setup autoconf AC_PREREQ(2.53) -AC_INIT(RT, [3.4.4], [rt-bugs@fsck.com]) +AC_INIT(RT, [3.4.5], [rt-bugs@fsck.com]) AC_CONFIG_SRCDIR([lib/RT.pm.in]) dnl Extract RT version number components @@ -23,6 +23,7 @@ test "x$rt_version_patch" = 'x' && rt_version_patch=0 dnl Check for programs AC_PROG_INSTALL +AC_PROG_AWK AC_ARG_VAR([PERL],[Perl interpreter command]) AC_PATH_PROG([PERL], [perl], [not found]) if test "$PERL" = 'not found'; then @@ -213,6 +214,15 @@ AC_ARG_WITH(my-user-group, WEB_USER=$my_user WEB_GROUP=$my_group) +# Test for valid database names +AS_IF([ test "$DB_TYPE" == "mysql" ], + [ AC_MSG_CHECKING([if database name is valid]) + AS_IF([ echo $DB_DATABASE | $AWK '/-/ { exit 1 }' ], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_ERROR([no. database name ($DB_DATABASE) contains '-' which is not valid for mysql]) ] + ) + ] + ) dnl Set the value of apachectl diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in index 773e3e2dc..10d46eb50 100644 --- a/rt/etc/RT_Config.pm.in +++ b/rt/etc/RT_Config.pm.in @@ -35,16 +35,16 @@ Set($rtname , "example.com"); # token matching and that you should use only "non-capturing" parenthesis # grouping. For example: # -# Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/ ); +# Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/i ); # # and NOT # -# Set($EmailSubjectTagRegex, qr/(example.com|example.org)/ ); +# Set($EmailSubjectTagRegex, qr/(example.com|example.org)/i ); # # This setting would make RT behave exactly as it does without the # setting enabled. # -# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/ ); +# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i ); @@ -210,6 +210,11 @@ Set($MailCommand , 'sendmailpipe'); # These options are good for most sendmail wrappers and workalikes Set($SendmailArguments , "-oi -t"); +# $SendmailBounceArguments defines what flags to pass to $Sendmail +# assuming RT needs to send an error (ie. bounce). + +Set($SendmailBounceArguments , '-f "<>"'); + # These arguments are good for sendmail brand sendmail 8 and newer #Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m"); @@ -468,6 +473,11 @@ Set($AmbiguousDayInPast , 1); @ActiveStatus = qw(new open stalled) unless @ActiveStatus; @InactiveStatus = qw(resolved rejected deleted) unless @InactiveStatus; +# Backward compatability setting. Add/Delete Link used to record one +# transaction and run one scrip. Set this value to 0 if you want +# both link transactions to have a scrip run. +Set($LinkTransactionsRun1Scrip , 1); + # }}} diff --git a/rt/html/Approvals/Elements/PendingMyApproval b/rt/html/Approvals/Elements/PendingMyApproval index f13ddf0f3..8d19399ab 100644 --- a/rt/html/Approvals/Elements/PendingMyApproval +++ b/rt/html/Approvals/Elements/PendingMyApproval @@ -77,6 +77,7 @@ $tickets->LimitOwner( VALUE => $session{'CurrentUser'}->Id ); # also consider AdminCcs as potential approvers. my $group_tickets = RT::Tickets->new( $session{'CurrentUser'} ); +$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->UserObj->EmailAddress, TYPE => 'AdminCc' ); my $created_before = RT::Date->new( $session{'CurrentUser'} ); my $created_after = RT::Date->new( $session{'CurrentUser'} ); diff --git a/rt/html/Elements/Callback b/rt/html/Elements/Callback index 937e923a1..c7aeb9f5d 100644 --- a/rt/html/Elements/Callback +++ b/rt/html/Elements/Callback @@ -68,14 +68,14 @@ if (!$callbacks) { push @$callbacks, # Skip backup files, files without a leading package name, # and files we've already seen - grep { !/^\.|~$/ + grep { !/\/\.|~$/ and $_ ne "/Callbacks/$Page/$_CallbackName" and not $seen{$_}++ } $m->interp->resolver->glob_path($path, $root); } $m->notes($CacheKey => $callbacks); - $cache{$Page,$_CallbackName} = $callbacks if !$RT::DevelMode; + $cache{$CacheKey} = $callbacks if !$RT::DevelMode; } my @rv; diff --git a/rt/html/Elements/CollectionAsTable/Row b/rt/html/Elements/CollectionAsTable/Row index 3316bc027..0de362ea8 100644 --- a/rt/html/Elements/CollectionAsTable/Row +++ b/rt/html/Elements/CollectionAsTable/Row @@ -71,6 +71,7 @@ foreach my $column (@Format) { $item++; $m->out('<td class="collection-as-table" '); $m->out( 'align="' . $column->{align} . '"' ) if ( $column->{align} ); + $m->out( 'style="' . $column->{style} . '"' ) if ( $column->{style} ); $m->out('>'); foreach my $subcol ( @{ $column->{output} } ) { if ( $subcol =~ /^__(.*?)__$/o ) { diff --git a/rt/html/Elements/EditCustomField b/rt/html/Elements/EditCustomField index d2398c9da..e443c764e 100644 --- a/rt/html/Elements/EditCustomField +++ b/rt/html/Elements/EditCustomField @@ -49,6 +49,10 @@ if ($Object) { $Values = $Object->CustomFieldValues($CustomField->id); $Values->Columns( qw( id CustomField ObjectType ObjectId Disabled Content ContentType ContentEncoding ) ); $NamePrefix ||= join('-', 'Object', ref($Object), $Object->Id, 'CustomField', ''); +} elsif (not $Default) { + my %TOP = $m->request_args; + $Default = $TOP{ $NamePrefix .$CustomField->Id . '-Values' } + || $TOP{ $NamePrefix .$CustomField->Id . '-Value' }; } my $Type = $CustomField->Type; diff --git a/rt/html/Elements/EditCustomFieldSelect b/rt/html/Elements/EditCustomFieldSelect index db33a6839..2a2a64a1d 100644 --- a/rt/html/Elements/EditCustomFieldSelect +++ b/rt/html/Elements/EditCustomFieldSelect @@ -53,7 +53,8 @@ % if ($Values) { <% $Values->HasEntry($value->Name) && ($selected = 1) && 'SELECTED' %> % } elsif ($Default) { - <% ($Default eq $value->Name) && ($selected = 1) && 'SELECTED' %> + <% (ref $Default ? (grep {$_ eq $value->Name} @{$Default}) : ($Default eq $value->Name)) + && ($selected = 1) && 'SELECTED' %> % } ><% $value->Name%></option> % } diff --git a/rt/html/Elements/FreesideInvoiceSearch b/rt/html/Elements/FreesideInvoiceSearch new file mode 100644 index 000000000..3842b2ff9 --- /dev/null +++ b/rt/html/Elements/FreesideInvoiceSearch @@ -0,0 +1,20 @@ +% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) { + + <form action="<% $RT::URI::freeside::URL %>/search/cust_bill.html" STYLE="margin:0"> + <SCRIPT TYPE="text/javascript"> + function clearhint_search_invoice (what) { + if ( what.value == '(inv #)' ) + what.value = ''; + } + </SCRIPT> + <input name="invnum" accesskey="0" VALUE="(inv #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="text-align:right; margin-bottom:1px; font-family: Arial, Verdana, Helvetica, sans-serif;"> + +% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { + <A HREF="<% $RT::URI::freeside::URL %>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%; font-weight:normal">Advanced</A> +% } + <BR> + + <input type="submit" value="<&|/l&>Search invoices</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </form> + +% } diff --git a/rt/html/Elements/FreesideNewCust b/rt/html/Elements/FreesideNewCust new file mode 100644 index 000000000..c752437da --- /dev/null +++ b/rt/html/Elements/FreesideNewCust @@ -0,0 +1,3 @@ +<form action="<% $RT::URI::freeside::URL %>/edit/cust_main.cgi" STYLE="margin:0"> +<INPUT TYPE="submit" VALUE="<&|/l&>New customer</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;"> +</FORM> diff --git a/rt/html/Elements/FreesideSearch b/rt/html/Elements/FreesideSearch new file mode 100644 index 000000000..2fed8fc73 --- /dev/null +++ b/rt/html/Elements/FreesideSearch @@ -0,0 +1,11 @@ +<form action="<% $RT::URI::freeside::URL %>/search/cust_main.cgi" STYLE="margin:0"> + <SCRIPT TYPE="text/javascript"> + function clearhint_search_cust (what) { + if ( what.value == '(cust #, name, company or phone)' ) + what.value = ''; + } + </SCRIPT> +<input name="search_cust" accesskey="0" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR> +<A NOTYET="<% $RT::URI::freeside::URL %>/search/cust_main.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> +<input type="submit" value="<&|/l&>Search customers</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> +</form> diff --git a/rt/html/Elements/FreesideSvcSearch b/rt/html/Elements/FreesideSvcSearch new file mode 100644 index 000000000..4a5942421 --- /dev/null +++ b/rt/html/Elements/FreesideSvcSearch @@ -0,0 +1,11 @@ +<form action="<% $RT::URI::freeside::URL %>/search/cust_svc.html" STYLE="margin:0"> + <SCRIPT TYPE="text/javascript"> + function clearhint_search_svc (what) { + if ( what.value == '(user, user@domain or domain)' ) + what.value = ''; + } + </SCRIPT> +<input name="search_svc" accesskey="0" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR> + <A NOTYET="<% $RT::URI::freeside::URL %>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> +<input type="submit" value="<&|/l&>Search services</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> +</form> diff --git a/rt/html/Elements/Header b/rt/html/Elements/Header index a2563fee3..b5512aae9 100644 --- a/rt/html/Elements/Header +++ b/rt/html/Elements/Header @@ -47,12 +47,14 @@ <HTML> <HEAD> <TITLE><%$Title%></TITLE> -% if ($Refresh > 0) { +% if ($Refresh && $Refresh > 0) { <META HTTP-EQUIV="REFRESH" CONTENT="<%$Refresh%>"> % } -<link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png"> -<link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css"> +<link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png" /> +<link media="all" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css" /> +<link media="print" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/printrt.css" type="text/css" /> + <script> function hideshow(num) { idstring = "element-" + num; @@ -67,7 +69,7 @@ function hideshow(num) { </script> <& /Elements/Callback, _CallbackName => 'Head', %ARGS &> </HEAD> -<BODY BGCOLOR="<%$BgColor%>" +<BODY BACKGROUND="<% $RT::URI::freeside::URL %>/images/background-cheat.png" STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0" % if ($Focus) { ONLOAD=" var tmp = (document.getElementsByName('<% $Focus %>')); @@ -75,13 +77,15 @@ ONLOAD=" " % } > -<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF"> +<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF" STYLE="padding-left:0; padding-right:4"> <tr> <td colspan=2 rowspan=2><img border=0 alt="freeside" src="<%$RT::WebImagesURL%>/small-logo.png" width="92" height="62"></td> - <td align="left" rowspan=2><font size=6><% &RT::URI::freeside::FreesideGetConfig('company_name') %> Ticketing</font></td> + <td align="left" rowspan=2><font size=6><% &RT::URI::freeside::FreesideGetConfig('company_name') || 'ExampleCo' %></font></td> <td align="right" valign="top"> % if ($session{'CurrentUser'} && $session{'CurrentUser'}->Id && $LoggedIn) { <SPAN STYLE="display: none"><A HREF="#skipnav"><&|/l&>Skip Menu</&></A> |</SPAN> +<&|/l, "<b>".$session{'CurrentUser'}->Name."</b>" &>Logged in as [_1]</&> +<BR> %if ($session{'CurrentUser'}->HasRight( Right => 'ModifySelf', Object => $RT::System )) { <A HREF="<%$RT::WebPath%><% $Prefs %>" ><&|/l&>Preferences</&></A> % } @@ -89,8 +93,6 @@ ONLOAD=" % unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) { | <A HREF="<%$RT::WebPath%>/NoAuth/Logout.html<%$URL ? "?URL=".$URL : ''%>"><&|/l&>Logout</&></a> % } -<BR> -<&|/l, "<b>".$session{'CurrentUser'}->Name."</b>" &>Logged in as [_1]</&> % } else { <&|/l&>Not logged in.</&> % } @@ -105,7 +107,7 @@ ONLOAD=" <td align=right> <FONT SIZE="-3"> <A HREF="http://www.sisd.com/freeside">Freeside</A> v<% &RT::URI::freeside::FreesideVersion() %><BR> - <A HREF="../docs/">Documentation</A><BR> + <A HREF="<% FS::Conf->new->config('support-key') ? "http://www.sisd.com/mediawiki/index.php/Supported:Documentation" : "http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation" %>">Documentation</A><BR> </FONT> </td> <td bgcolor=#000000></td> diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout index 94bdbe194..f13ee0dda 100644 --- a/rt/html/Elements/PageLayout +++ b/rt/html/Elements/PageLayout @@ -43,32 +43,43 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<table class="lightgray" border=0 cellspacing=0 cellpadding=0 width="100%"> - <th class="lightgray" align="left" width=42%><span class="rtname"><%$AppName%></span> - </th> +<table class="black" border=0 cellspacing=0 cellpadding=0 width="100%"> +<tr> + <TD colspan=5 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD> +</tr> +<tr> +%# <th class="black" align="left" width=15%><span class="rtname"><%$AppName%></span> +%# </th> <span class="topactions"> -% foreach my $action (sort keys %{$topactions}) { - <td class="lightgrayright"> +% my $notfirst = 0; foreach my $action (sort keys %{$topactions}) { + <td class="blackright" ALIGN="right" VALIGN="center"> <%$topactions->{"$action"}->{'html'} |n %> </td> % } </span> +</tr> </table> <table border=0 cellspacing=0 cellpadding=0 width="100%" height="100%"> +<TR> + <TD BGCOLOR="#000000" STYLE="padding:0" WIDTH="154"></TD> + <TD STYLE="padding:0" WIDTH="13"><IMG BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gray-corner.png"></TD> + <TD STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD> +</TR> %# Vertical menu <TR height="100%"> -<TD valign="top" width="140" class="lightgray"> +<TD valign="top" width="140" class="black"> <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &> </TD> +<TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gray-side.png"></TD> <td valign="top"> <table width="100%" height="100%" border="0" cellpadding="0" cellspacing="0"> <tr> - <td class="mediumgray" valign="top"> + <td class="<% $actions ? 'darkmediumgray' : 'bggray' %>" valign="top"> <span class="title"><%$title%></span> </td> </tr> <tr> -<td class="mediumgrayright" valign="top"> +<td class="<% $actions ? 'darkmediumgrayright' : 'bggrayright' %>" valign="top"> <span class="nav"> % if ($actions) { % my @actions; @@ -80,7 +91,7 @@ % } % } %#<% join(" | ", @actions) | n %> -<% '['. join("] [", @actions). ']' | n %> +<% '['. join("] [", @actions). '] ' | n %> % if ($subactions) { % my @actions; % foreach my $action (sort keys %{$subactions}) { diff --git a/rt/html/Elements/QueryString b/rt/html/Elements/QueryString index 7d41f15e0..1ddab85de 100644 --- a/rt/html/Elements/QueryString +++ b/rt/html/Elements/QueryString @@ -45,8 +45,12 @@ %# END BPS TAGGED BLOCK }}} <%init> my @params; -while ( (my $key, my $value) = each %ARGS ){ +while ( my ($key, $value) = each %ARGS ){ + if( UNIVERSAL::isa( $value, 'ARRAY' ) ) { + push @params, map $key."=".$m->interp->apply_escapes($_,'u'), @$value; + } else { push @params, $key."=".$m->interp->apply_escapes($value,'u'); + } } return(join('&',@params)); </%init> diff --git a/rt/html/Elements/RT__Ticket/ColumnMap b/rt/html/Elements/RT__Ticket/ColumnMap index dade91494..80e3c693c 100644 --- a/rt/html/Elements/RT__Ticket/ColumnMap +++ b/rt/html/Elements/RT__Ticket/ColumnMap @@ -95,6 +95,7 @@ sub LinkCallback { my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode}; my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type}; + my $other_mode = ($mode eq "Target" ? "Base" : "Target"); my $mode_uri = $mode.'URI'; my $local_type = 'Local'.$mode; @@ -105,7 +106,7 @@ sub LinkCallback { \'">', ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ), \'</A><BR>', - } @{ $_[0]->Links($mode,$type)->ItemsArrayRef } + } @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef } } } diff --git a/rt/html/Elements/ScrubHTML b/rt/html/Elements/ScrubHTML index 94a729907..443ded9c4 100644 --- a/rt/html/Elements/ScrubHTML +++ b/rt/html/Elements/ScrubHTML @@ -62,7 +62,7 @@ $scrubber->default( $scrubber->deny(qw[*]); $scrubber->allow( - qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD]); + qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD PRE]); $scrubber->comment(0); return ( $scrubber->scrub($Content) ); </%init> diff --git a/rt/html/Elements/ShowCustomFields b/rt/html/Elements/ShowCustomFields index 7591fa3aa..986184120 100644 --- a/rt/html/Elements/ShowCustomFields +++ b/rt/html/Elements/ShowCustomFields @@ -71,6 +71,9 @@ </table> <%INIT> my $CustomFields = $Object->CustomFields; + $m->comp('/Elements/Callback', _CallbackName => 'MassageCustomFields', + CustomFields => $CustomFields); + </%INIT> <%ARGS> $Object => undef diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch index e76f801df..e9fc5c6ed 100644 --- a/rt/html/Elements/SimpleSearch +++ b/rt/html/Elements/SimpleSearch @@ -43,7 +43,14 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<form action="<% $RT::WebPath %>/index.html"> -<input size="12" name="q" autocomplete="off" accesskey="0"> -<input type="submit" value="<&|/l&>Search tickets</&>"> +<form action="<% $RT::WebPath %>/index.html" STYLE="margin:0"> +<SCRIPT TYPE="text/javascript"> + function clearhint_search_ticket (what) { + if ( what.value == '(ticket # or subject string)' ) + what.value = ''; + } +</SCRIPT> +<input name="q" accesskey="0" VALUE="(ticket # or subject string)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR> +<A HREF="<% $RT::WebPath %>/Search/Build.html" STYLE="color: #ffffff; font-size: 70%; font-weight:normal">Advanced</A> +<input type="submit" value="<&|/l&>Search tickets</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px"> </form> diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs index f5839a9e5..721f920d5 100644 --- a/rt/html/Elements/Tabs +++ b/rt/html/Elements/Tabs @@ -57,9 +57,17 @@ <%INIT> my $action; my $basetopactions = { - A => { html => $m->scomp('/Elements/CreateTicket') +# A => { html => $m->scomp('/Elements/CreateTicket') +# }, + A => { html => $m->scomp('/Elements/FreesideNewCust') }, - B => { html => $m->scomp('/Elements/SimpleSearch') + B => { html => $m->scomp('/Elements/FreesideSearch') + }, + C => { html => $m->scomp('/Elements/FreesideInvoiceSearch') + }, + D => { html => $m->scomp('/Elements/FreesideSvcSearch') + }, + E => { html => $m->scomp('/Elements/SimpleSearch') } }; my $basetabs = { diff --git a/rt/html/Elements/TitleBoxStart b/rt/html/Elements/TitleBoxStart index 804e5cfaa..d98fe2744 100644 --- a/rt/html/Elements/TitleBoxStart +++ b/rt/html/Elements/TitleBoxStart @@ -78,7 +78,7 @@ $title_class => '' $titleright_href => undef $titleright => undef -$contentbg => "#dddddd" +$contentbg => "#d4d4d4" $color => "#336699" </%ARGS> <%init> diff --git a/rt/html/NoAuth/printrt.css b/rt/html/NoAuth/printrt.css new file mode 100644 index 000000000..72e7e8b7e --- /dev/null +++ b/rt/html/NoAuth/printrt.css @@ -0,0 +1,77 @@ +%# {{{ BEGIN BPS TAGGED BLOCK +%# +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# }}} END BPS TAGGED BLOCK +%# +%# Special stylesheet for printing tickets +%# Koos van den Hout koos@cs.uu.nl 2005-11-21 +%# + +SPAN.nav { display: none !important; } +.nav2 { display: none !important; } +.nav { display: none !important; } +.topnav { display: none !important; } +.blue { display: none !important; } +.darkblue { display: none !important; } +.blueright { display: none !important; } +.currentnav { display: none !important; } +th.titlebox { border-top: none; border-bottom: none; } +th.titleboxright { display:none !important; border-top: none; border-bottom: none; } +.titlebox { border-top: none; border-bottom: none; } + +div.downloadattachment, div.downloadcontenttype { + display: none !important; +} + + +a[href$="Respond"], a[href$="Comment"], a[href*="ShowEmailRecord"] { + display: none !important; +} + + +%# Provide a callback for adding/modifying the style sheet. +%# http://www.w3.org/TR/REC-CSS1 - section 3.2, says: +%# "latter specified rule wins" +<& /Elements/Callback &> +<%flags> +inherit => undef +</%flags> +<%init> +$r->content_type('text/css'); +$r->headers_out->{'Expires'} = '+30m'; +</%init> diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index bc688ac1d..5da0f8310 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -1,3 +1,4 @@ +/* %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: @@ -43,75 +44,80 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; +*/ + +/* * { + font-family: Arial, Verdana, Helvetica, sans-serif; + font-size: 1.2em; +} */ + +SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 12px; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} .nav2 { font-size: 10px; white-space: nowrap} -.nav { font-family: Verdana, Arial, Helvetica, sans-serif; +.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 13px; -%# font-weight: normal; font-weight: bold; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -.currentnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currentnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 13px; font-weight: bold; color: #FFFF66; text-decoration: none; white-space: nowrap} -.topnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.topnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: normal; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} +/* %# .topnav is the original RT class for the sidebar navigation tabs. %# Font-sizing by level depth was originally hard-coded into Elements/Menu. %# This modification sets a different class name for each level, allowing %# style sheet control over the formats. +*/ -a.topnav-0 { font-family: Verdana, sans-serif; +a.topnav-0 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -a.topnav-1 { font-family: Verdana, sans-serif; +a.topnav-1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -a.topnav-2 { font-family: Verdana, sans-serif; +a.topnav-2 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -a.topnav-3 { font-family: Verdana, sans-serif; +a.topnav-3 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -a.topnav-4 { font-family: Verdana, sans-serif; +a.topnav-4 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} -a.topnav-5 { font-family: Verdana, sans-serif; +a.topnav-5 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} li.topnav-0-minor { @@ -175,53 +181,54 @@ li.topnav-5-major { padding-bottom: .5em; } -.currenttopnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currenttopnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: bold; -%# color: #FFFF66; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} +/* %# .currenttopnav is the original RT class for the sidebar navigation tabs. %# Font-sizing by level depth was originally hard-coded into Elements/Menu. %# This modification sets a different class name for each level, allowing %# style sheet control over the formats +*/ -a.currenttopnav-0 { font-family: Verdana, sans-serif; +a.currenttopnav-0 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; font-weight: bold; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -a.currenttopnav-1 { font-family: Verdana, sans-serif; +a.currenttopnav-1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; font-weight: bold; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -a.currenttopnav-2 { font-family: Verdana, sans-serif; +a.currenttopnav-2 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -a.currenttopnav-3 { font-family: Verdana, sans-serif; +a.currenttopnav-3 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -a.currenttopnav-4 { font-family: Verdana, sans-serif; +a.currenttopnav-4 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -a.currenttopnav-5 { font-family: Verdana, sans-serif; +a.currenttopnav-5 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} li.currenttopnav-0-minor { @@ -285,18 +292,18 @@ li.currenttopnav-5-major { padding-bottom: .5em; } -.topactions { font-family: Verdana, Arial, Helvetica, sans-serif; +.topactions { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 10px; color: #FFFFFF; text-decoration: none; white-space: nowrap} -.subnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.subnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: normal; color: #FFFFFF; text-decoration: none; white-space: nowrap} -.currentsubnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currentsubnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: bold; color: #FFFF66; @@ -314,12 +321,12 @@ li.currenttopnav-5-major { } .blue { background-color: #4682B4; -%# background-color: #eeeeee; +/* %# background-color: #eeeeee; */ background-position: left top; vertical-align: top; text-align: left; } -%# Actually the "topactions" section +/* %# Actually the "topactions" section */ .blueright { background-color: #4682B4; background-position: left top; vertical-align: top; @@ -355,6 +362,55 @@ li.currenttopnav-5-major { vertical-align: top; text-align: right; } +.black { + background-color: #000000; + color: #ffffff; + background-position: left top; + vertical-align: top; + text-align: left; + } +.blackright { + background-color: #000000; + color: #ffffff; + background-position: left top; + vertical-align: center; + text-align: right; + font-size:16px; + padding-right:4px + } + +input.fsblackbutton { + background-color:#333333; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-family: Arial, Verdana, Helvetica, sans-serif; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666') +} + +input.fsblackbuttonselected { + background-color:#7e0079; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-family: Arial, Verdana, Helvetica, sans-serif; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079') +} + .mediumgray { background-color: #cccccc; background-position: left top; @@ -367,6 +423,30 @@ li.currenttopnav-5-major { vertical-align: top; text-align: right; } +.darkmediumgray { + background-color: #aaaaaa; + background-position: left top; + vertical-align: top; + text-align: left; + } +.darkmediumgrayright { + background-color: #aaaaaa; + background-position: left top; + vertical-align: top; + text-align: right; + } +.bggray { + background-color: #e8e8e8; + background-position: left top; + vertical-align: top; + text-align: left; + } +.bggrayright { + background-color: #e8e8e8; + background-position: left top; + vertical-align: top; + text-align: right; + } .white { background-color: #ffffff; background-position: left top; @@ -396,26 +476,26 @@ div.downloadattachment { } -td { font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 11px; +td { font-family: Arial, Verdana, Helvetica, sans-serif; + font-size: 12px; background-position: left top; } .black { background-color: #000000; background-position: left top; } -span.rtname { font-family: Verdana, Arial, Helvetica, sans-serif; +span.rtname { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 18px; font-weight: normal; color: #ffffff} -span.title { font-family: Verdana, Arial, Helvetica, sans-serif; +span.title { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 20px; font-weight: bold; color: #ffffff} -.header { font-family: Verdana, Arial, Helvetica, sans-serif; +.header { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 12px; font-weight: bold; color: #0066CC} -.subheader { font-family: Verdana, Arial, Helvetica, sans-serif; +.subheader { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: bold; color: #0066CC } @@ -426,9 +506,9 @@ span.title { font-family: Verdana, Arial, Helvetica, sans-serif; .labeltop { font-weight: normal; text-align: right; vertical-align: top } -.productnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.productnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; - color: #000000; + color: #FFFFFF; text-align: center; vertical-align: middle; text-decoration: none} @@ -450,6 +530,7 @@ TD.mainbody { padding-right: 1em; margin-left: 1em; margin-right: 1em; + background-color: #e8e8e8; } td.boxcontainer + td.boxcontainer { @@ -492,14 +573,14 @@ TD.titlebox { SPAN.message { font-size: 100%; - font-family: Verdana, Arial, Helvetica, sans-serif; + font-family: Arial, Verdana, Helvetica, sans-serif; } BODY { color: #000; background: #FFFFFF; - font-family: "Helvetica", sans-serif; + font-family: Verdana, Arial, Helvetica, sans-serif; margin-top: 0px; margin-bottom: 0px; margin-left: 0px; @@ -522,7 +603,7 @@ TR.evenline { H1, H2, H3 { margin-top: 0.2em; color: #336699; - font-family: "Helvetica", sans-serif; + font-family: Verdana, Arial, Helvetica, sans-serif; clear: both; } @@ -535,7 +616,7 @@ DIV.endmatter { margin-left: -7% } } -A { font-weight: bold; color: #000000; +A { font-weight: bold; color: #000000 } .currenttab { color: #ffffff;} @@ -549,8 +630,9 @@ A:link IMG, A:visited IMG { border-style: none } a:focus {text-decoration: underline } A IMG { color: white } /* The only way to hide the border in NS 4.x */ -a:link { text-decoration: none} -a:visited { text-decoration: none} +/* a:link { text-decoration: none} */ +/* a:visited { text-decoration: none} */ + a:hover { text-decoration: underline} /* a:focus { background-color: #ccccee } */ @@ -563,7 +645,7 @@ SPAN.date { font-size: 0.8em } span.title { font-size: 1.6em; vertical-align: middle; -%# color: #ffffff; +/* %# color: #ffffff; */ color: #000000; } span.productname { font-size: 2em; @@ -662,6 +744,7 @@ textarea.messagebox { width: 100%; } +/* %# Provide a callback for adding/modifying the style sheet. %# http://www.w3.org/TR/REC-CSS1 - section 3.2, says: %# "latter specified rule wins" @@ -673,3 +756,4 @@ inherit => undef $r->content_type('text/css'); #$r->headers_out->{'Expires'} = '+30m'; </%init> +*/ diff --git a/rt/html/Search/Build.html b/rt/html/Search/Build.html index cb6462651..5a66e02c9 100644 --- a/rt/html/Search/Build.html +++ b/rt/html/Search/Build.html @@ -256,237 +256,6 @@ if ( $ARGS{'LoadSavedSearch'} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) { # }}} # {{{ Parse the query -my $tree; -ParseQuery( $Query, \$tree, \@actions ); - -# if parsing went poorly, send them to the edit page to fix it -if ( $actions[0] ) { - $m->comp( "Edit.html", Query => $Query, actions => \@actions ); - $m->abort(); -} - -$Query = ""; - -my @options = $tree->GetDisplayedNodes; - -my @current_values = grep { defined } @options[@clauses]; - -# {{{ Try to find if we're adding a clause -foreach my $arg ( keys %ARGS ) { - if ( - $arg =~ m/^ValueOf(.+)/ - && ( ref $ARGS{$arg} eq "ARRAY" - ? grep { $_ ne "" } @{ $ARGS{$arg} } - : $ARGS{$arg} ne "" ) - ) - { - - # We're adding a $1 clause - my $field = $1; - my ( $keyword, $op, $value ); - - #figure out if it's a grouping - if ( $ARGS{ $field . "Field" } ) { - $keyword = $ARGS{ $field . "Field" }; - } - else { - $keyword = $field; - } - - my ( @ops, @values ); - if ( ref $ARGS{ 'ValueOf' . $field } eq "ARRAY" ) { - - # we have many keys/values to iterate over, because there is - # more than one CF with the same name. - @ops = @{ $ARGS{ $field . 'Op' } }; - @values = @{ $ARGS{ 'ValueOf' . $field } }; - } - else { - @ops = ( $ARGS{ $field . 'Op' } ); - @values = ( $ARGS{ 'ValueOf' . $field } ); - } - $RT::Logger->error("Bad Parameters passed into Query Builder") - unless @ops == @values; - - for my $i ( 0 .. @ops - 1 ) { - my ( $op, $value ) = ( $ops[$i], $values[$i] ); - next if $value eq ""; - - if ( $value eq 'NULL' && $op =~ /=/ ) { - if ( $op eq '=' ) { - $op = "IS"; - } - elsif ( $op eq '!=' ) { - $op = "IS NOT"; - } - - # This isn't "right", but... - # It has to be this way until #5182 is fixed - $value = "'NULL'"; - } - else { - $value = "'$value'"; - } - - my $clause = { - Key => $keyword, - Op => $op, - Value => $value - }; - - my $newnode = RT::Interface::Web::QueryBuilder::Tree->new($clause); - if (@current_values) { - foreach my $value (@current_values) { - my $newindex = $value->getIndex() + 1; - $value->insertSibling( $newindex, $newnode ); - $value = $newnode; - } - } - else { - $tree->getChild(0)->addChild($newnode); - @current_values = $newnode; - } - $newnode->getParent()->setNodeValue( $ARGS{'AndOr'} ); - } - } -} - -# }}} - -# {{{ Move things around -if ( $ARGS{"Up"} ) { - if (@current_values) { - foreach my $value (@current_values) { - my $index = $value->getIndex(); - if ( $value->getIndex() > 0 ) { - my $parent = $value->getParent(); - $parent->removeChild($index); - $parent->insertChild( $index - 1, $value ); - $value = $parent->getChild( $index - 1 ); - } - else { - push( @actions, [ loc("error: can't move up"), -1 ] ); - } - } - } - else { - push( @actions, [ loc("error: nothing to move"), -1 ] ); - } -} -elsif ( $ARGS{"Down"} ) { - if (@current_values) { - foreach my $value (@current_values) { - my $index = $value->getIndex(); - my $parent = $value->getParent(); - if ( $value->getIndex() < ( $parent->getChildCount - 1 ) ) { - $parent->removeChild($index); - $parent->insertChild( $index + 1, $value ); - $value = $parent->getChild( $index + 1 ); - } - else { - push( @actions, [ loc("error: can't move down"), -1 ] ); - } - } - } - else { - push( @actions, [ loc("error: nothing to move"), -1 ] ); - } -} -elsif ( $ARGS{"Left"} ) { - if (@current_values) { - foreach my $value (@current_values) { - my $parent = $value->getParent(); - my $grandparent = $parent->getParent(); - if ( !$grandparent->isRoot ) { - my $index = $parent->getIndex(); - $parent->removeChild($value); - $grandparent->insertChild( $index, $value ); - if ( $parent->isLeaf() ) { - $grandparent->removeChild($parent); - } - } - else { - push( @actions, [ loc("error: can't move left"), -1 ] ); - } - } - } - else { - push( @actions, [ loc("error: nothing to move"), -1 ] ); - } -} -elsif ( $ARGS{"Right"} ) { - if (@current_values) { - foreach my $value (@current_values) { - my $parent = $value->getParent(); - my $index = $value->getIndex(); - my $newparent; - if ( $index > 0 ) { - my $sibling = $parent->getChild( $index - 1 ); - if ( ref( $sibling->getNodeValue ) ) { - $parent->removeChild($value); - my $newtree = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent ); - $newtree->addChild($value); - } - else { - $parent->removeChild($index); - $sibling->addChild($value); - } - } - else { - $parent->removeChild($value); - $newparent = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent ); - $newparent->addChild($value); - } - } - } - else { - push( @actions, [ loc("error: nothing to move"), -1 ] ); - } -} -elsif ( $ARGS{"DeleteClause"} ) { - if (@current_values) { - $_->getParent()->removeChild($_) for @current_values; - } - else { - push( @actions, [ loc("error: nothing to delete"), -1 ] ); - } -} -elsif ( $ARGS{"Toggle"} ) { - my $ea; - if (@current_values) { - foreach my $value (@current_values) { - my $parent = $value->getParent(); - - if ( $parent->getNodeValue eq 'AND' ) { - $parent->setNodeValue('OR'); - } - else { - $parent->setNodeValue('AND'); - } - } - } - else { - push( @actions, [ loc("error: nothing to toggle"), -1 ] ); - } -} - -$tree->PruneChildlessAggregators; - -# }}} - -# {{{ Rebuild $Query based on the additions / movements -$Query = ""; -my $optionlist_arrayref; - -($Query, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values); - -my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>) - . (" " x (5 * $_->{DEPTH})) - . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref; - - - - use Regexp::Common qw /delimited/; # States @@ -496,7 +265,17 @@ use constant OP => 4; use constant PAREN => 8; use constant KEYWORD => 16; -sub ParseQuery { +my $_match = sub { + + # Case insensitive equality + my ( $y, $x ) = @_; + return 1 if $x =~ /^$y$/i; + + # return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv? + return 0; +}; + +my $ParseQuery = sub { my $string = shift; my $tree = shift; my @actions = shift; @@ -548,12 +327,12 @@ sub ParseQuery { my $current = 0; # Highest priority is last - $current = OP if _match( $re_op, $val ); - $current = VALUE if _match( $re_value, $val ); + $current = OP if $_match->( $re_op, $val ); + $current = VALUE if $_match->( $re_value, $val ); $current = KEYWORD - if _match( $re_keyword, $val ) && ( $want & KEYWORD ); - $current = AGGREG if _match( $re_aggreg, $val ); - $current = PAREN if _match( $re_paren, $val ); + if $_match->( $re_keyword, $val ) && ( $want & KEYWORD ); + $current = AGGREG if $_match->( $re_aggreg, $val ); + $current = PAREN if $_match->( $re_paren, $val ); unless ( $current && $want & $current ) { @@ -666,25 +445,242 @@ sub ParseQuery { # This will never happen, because the parser will complain push @actions, [ loc("Mismatched parentheses"), -1 ] unless $depth == 1; +}; + +my $tree; +$ParseQuery->( $Query, \$tree, \@actions ); + +# if parsing went poorly, send them to the edit page to fix it +if ( $actions[0] ) { + $m->comp( "Edit.html", Query => $Query, actions => \@actions ); + $m->abort(); } -sub _match { +$Query = ""; - # Case insensitive equality - my ( $y, $x ) = @_; - return 1 if $x =~ /^$y$/i; +my @options = $tree->GetDisplayedNodes; - # return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv? - return 0; +my @current_values = grep { defined } @options[@clauses]; + +# {{{ Move things around +if ( $ARGS{"Up"} ) { + if (@current_values) { + foreach my $value (@current_values) { + my $index = $value->getIndex(); + if ( $value->getIndex() > 0 ) { + my $parent = $value->getParent(); + $parent->removeChild($index); + $parent->insertChild( $index - 1, $value ); + $value = $parent->getChild( $index - 1 ); + } + else { + push( @actions, [ loc("error: can't move up"), -1 ] ); + } + } + } + else { + push( @actions, [ loc("error: nothing to move"), -1 ] ); + } +} +elsif ( $ARGS{"Down"} ) { + if (@current_values) { + foreach my $value (@current_values) { + my $index = $value->getIndex(); + my $parent = $value->getParent(); + if ( $value->getIndex() < ( $parent->getChildCount - 1 ) ) { + $parent->removeChild($index); + $parent->insertChild( $index + 1, $value ); + $value = $parent->getChild( $index + 1 ); + } + else { + push( @actions, [ loc("error: can't move down"), -1 ] ); + } + } + } + else { + push( @actions, [ loc("error: nothing to move"), -1 ] ); + } } +elsif ( $ARGS{"Left"} ) { + if (@current_values) { + foreach my $value (@current_values) { + my $parent = $value->getParent(); + my $grandparent = $parent->getParent(); + if ( !$grandparent->isRoot ) { + my $index = $parent->getIndex(); + $parent->removeChild($value); + $grandparent->insertChild( $index, $value ); + if ( $parent->isLeaf() ) { + $grandparent->removeChild($parent); + } + } + else { + push( @actions, [ loc("error: can't move left"), -1 ] ); + } + } + } + else { + push( @actions, [ loc("error: nothing to move"), -1 ] ); + } +} +elsif ( $ARGS{"Right"} ) { + if (@current_values) { + foreach my $value (@current_values) { + my $parent = $value->getParent(); + my $index = $value->getIndex(); + my $newparent; + if ( $index > 0 ) { + my $sibling = $parent->getChild( $index - 1 ); + if ( ref( $sibling->getNodeValue ) ) { + $parent->removeChild($value); + my $newtree = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent ); + $newtree->addChild($value); + } + else { + $parent->removeChild($index); + $sibling->addChild($value); + } + } + else { + $parent->removeChild($value); + $newparent = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent ); + $newparent->addChild($value); + } + } + } + else { + push( @actions, [ loc("error: nothing to move"), -1 ] ); + } +} +elsif ( $ARGS{"DeleteClause"} ) { + if (@current_values) { + $_->getParent()->removeChild($_) for @current_values; + @current_values = (); + } + else { + push( @actions, [ loc("error: nothing to delete"), -1 ] ); + } +} +elsif ( $ARGS{"Toggle"} ) { + my $ea; + if (@current_values) { + foreach my $value (@current_values) { + my $parent = $value->getParent(); + + if ( $parent->getNodeValue eq 'AND' ) { + $parent->setNodeValue('OR'); + } + else { + $parent->setNodeValue('AND'); + } + } + } + else { + push( @actions, [ loc("error: nothing to toggle"), -1 ] ); + } +} + +# {{{ Try to find if we're adding a clause +foreach my $arg ( keys %ARGS ) { + if ( + $arg =~ m/^ValueOf(.+)/ + && ( ref $ARGS{$arg} eq "ARRAY" + ? grep { $_ ne "" } @{ $ARGS{$arg} } + : $ARGS{$arg} ne "" ) + ) + { + + # We're adding a $1 clause + my $field = $1; + my ( $keyword, $op, $value ); + + #figure out if it's a grouping + if ( $ARGS{ $field . "Field" } ) { + $keyword = $ARGS{ $field . "Field" }; + } + else { + $keyword = $field; + } + + my ( @ops, @values ); + if ( ref $ARGS{ 'ValueOf' . $field } eq "ARRAY" ) { + + # we have many keys/values to iterate over, because there is + # more than one CF with the same name. + @ops = @{ $ARGS{ $field . 'Op' } }; + @values = @{ $ARGS{ 'ValueOf' . $field } }; + } + else { + @ops = ( $ARGS{ $field . 'Op' } ); + @values = ( $ARGS{ 'ValueOf' . $field } ); + } + $RT::Logger->error("Bad Parameters passed into Query Builder") + unless @ops == @values; + + for my $i ( 0 .. @ops - 1 ) { + my ( $op, $value ) = ( $ops[$i], $values[$i] ); + next if $value eq ""; -sub debug { - my $message = shift; - $m->print( $message . "<br>" ); + if ( $value eq 'NULL' && $op =~ /=/ ) { + if ( $op eq '=' ) { + $op = "IS"; + } + elsif ( $op eq '!=' ) { + $op = "IS NOT"; + } + + # This isn't "right", but... + # It has to be this way until #5182 is fixed + $value = "'NULL'"; + } + else { + $value = "'$value'"; + } + + my $clause = { + Key => $keyword, + Op => $op, + Value => $value + }; + + my $newnode = RT::Interface::Web::QueryBuilder::Tree->new($clause); + if (@current_values) { + foreach my $value (@current_values) { + my $newindex = $value->getIndex() + 1; + $value->insertSibling( $newindex, $newnode ); + $value = $newnode; + } + } + else { + $tree->getChild(0)->addChild($newnode); + @current_values = $newnode; + } + $newnode->getParent()->setNodeValue( $ARGS{'AndOr'} ); + } + } } # }}} +$tree->PruneChildlessAggregators; + +# }}} + +# {{{ Rebuild $Query based on the additions / movements +$Query = ""; +my $optionlist_arrayref; + +($Query, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values); + +my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>) + . (" " x (5 * $_->{DEPTH})) + . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref; + + + + +# }}} + # }}} my $queues = $tree->GetReferencedQueues; diff --git a/rt/html/Search/Bulk.html b/rt/html/Search/Bulk.html index f9eef26b6..b7c64e3f8 100644 --- a/rt/html/Search/Bulk.html +++ b/rt/html/Search/Bulk.html @@ -68,7 +68,7 @@ $Tickets->RedoSearch(); while (my $Ticket = $Tickets->Next) { $i++; if ($i % 2) { - $bgcolor = "#dddddd"; + $bgcolor = "#d4d4d4"; } else { $bgcolor = "#ffffff"; diff --git a/rt/html/Search/Elements/BuildFormatString b/rt/html/Search/Elements/BuildFormatString index 639e62c29..cffb81a48 100644 --- a/rt/html/Search/Elements/BuildFormatString +++ b/rt/html/Search/Elements/BuildFormatString @@ -121,11 +121,7 @@ foreach my $id (keys %cfqueues) { $CustomFields->LimitToGlobal; while ( my $CustomField = $CustomFields->Next ) { - my $queuestr; - if ($CustomField->QueueObj && $CustomField->QueueObj->Id != 0) { - $queuestr = $CustomField->QueueObj->Name . "."; - } - push @fields, "CustomField." . $queuestr . "{" . $CustomField->Name . "}"; + push @fields, "CustomField.{" . $CustomField->Name . "}"; } my ( @seen); diff --git a/rt/html/Search/Results.rdf b/rt/html/Search/Results.rdf index ee71fea94..7cc248306 100644 --- a/rt/html/Search/Results.rdf +++ b/rt/html/Search/Results.rdf @@ -75,7 +75,7 @@ $r->content_type('application/rdf+xml'); link => $RT::WebURL."/Ticket/Display.html?id=".$Ticket->id, description => $Ticket->Transactions->First->Content, dc => { - subject => $Ticket->Subject, + subject => ($Ticket->Subject || loc('No subject')), creator => $Ticket->CreatorObj->RealName . "<".$Ticket->CreatorObj->EmailAddress.">", }, ); diff --git a/rt/html/Search/Results.tsv b/rt/html/Search/Results.tsv index 17aa88ae1..e6b20481f 100644 --- a/rt/html/Search/Results.tsv +++ b/rt/html/Search/Results.tsv @@ -64,7 +64,7 @@ my @attrs = qw( id QueueObj->Name Subject Status TimeEstimated TimeWorked TimeLe if ($@) {die "Failed to find $attr - ". $@}; } - my $cfs = $Ticket->QueueObj->CustomFields(); + my $cfs = $Ticket->QueueObj->TicketCustomFields(); while (my $cf = $cfs->Next) { my @content; my $values = $Ticket->CustomFieldValues($cf->Id); diff --git a/rt/html/Ticket/Elements/EditCustomFields b/rt/html/Ticket/Elements/EditCustomFields index d566f4e71..6ae188fa7 100644 --- a/rt/html/Ticket/Elements/EditCustomFields +++ b/rt/html/Ticket/Elements/EditCustomFields @@ -91,7 +91,8 @@ if ($TicketObj) { $NamePrefix = "Object-RT::Ticket--CustomField-"; } - + $m->comp('/Elements/Callback', _CallbackName => 'MassageCustomFields', + CustomFields => $CustomFields); </%INIT> <%ARGS> diff --git a/rt/html/Ticket/Elements/EditCustomers b/rt/html/Ticket/Elements/EditCustomers index 47d1aa222..c5a6f708c 100644 --- a/rt/html/Ticket/Elements/EditCustomers +++ b/rt/html/Ticket/Elements/EditCustomers @@ -43,7 +43,7 @@ <&|/l&>Find customer</&><BR> <input name="CustomerString"> <input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>"> -<br><i>cust #, last name, or company</i> +<br><i>cust #, name, company or phone</i> <BR> %#<BR> %#<&|/l&>Find service</&><BR> diff --git a/rt/html/Ticket/Elements/ShowTransactionAttachments b/rt/html/Ticket/Elements/ShowTransactionAttachments index 8dabff421..d9e94ffa2 100644 --- a/rt/html/Ticket/Elements/ShowTransactionAttachments +++ b/rt/html/Ticket/Elements/ShowTransactionAttachments @@ -89,13 +89,13 @@ foreach my $message ( grep { $_->Parent == $Parent } @$Attachments ) { <div class="messagebody"> <%perl> # {{{ if it has a content-disposition: attachment, don't show inline -unless ( $message->GetHeader('Content-Disposition') =~ /attachment/i ) { +unless ( ($message->GetHeader('Content-Disposition')||"") =~ /attachment/i ) { my $content; # If it's text if ( $message->ContentType =~ m{^(text|message)}i - && $size <= $RT::MaxInlineBody ) + && $message->ContentLength <= $RT::MaxInlineBody ) { if ( @@ -158,6 +158,11 @@ unless ( $message->GetHeader('Content-Disposition') =~ /attachment/i ) { . $message->Id . '/">' ); } + elsif ( $message->ContentLength > 0 ) { + $m->out( + loc( 'Message body not shown because it is too large or is not plain text.' ) + ); + } } # }}} diff --git a/rt/html/User/Elements/Tabs b/rt/html/User/Elements/Tabs index 1d25fb926..625b30f93 100644 --- a/rt/html/User/Elements/Tabs +++ b/rt/html/User/Elements/Tabs @@ -61,6 +61,9 @@ }, }; + # Now let callbacks add their extra tabs + $m->comp('/Elements/Callback', tabs => $tabs, %ARGS); + foreach my $tab (sort keys %{$tabs}) { if ($tabs->{$tab}->{'path'} eq $current_tab) { $tabs->{$tab}->{"subtabs"} = $subtabs; diff --git a/rt/lib/RT/ACE_Overlay.pm b/rt/lib/RT/ACE_Overlay.pm index 75e39d03a..a045d57a9 100644 --- a/rt/lib/RT/ACE_Overlay.pm +++ b/rt/lib/RT/ACE_Overlay.pm @@ -814,7 +814,7 @@ sub Object { else { $RT::Logger->warning( "$self -> Object called for an object " . "of an unknown type:" - . $self->ObjectType ); + . $self->__Value('ObjectType') ); return (undef); } } diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index 3c70dc401..431b97c75 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -253,9 +253,9 @@ sub SendMessage { if ( $RT::MailCommand eq 'sendmailpipe' ) { eval { - open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!; - print MAIL $MIMEObj->as_string; - close(MAIL); + open( my $mail, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!; + $MIMEObj->print($mail); + close($mail); }; if ($@) { $RT::Logger->crit( $msgid . "Could not send mail. -" . $@ ); @@ -323,7 +323,7 @@ sub AddAttachments { FIELD => 'TransactionId', VALUE => $self->TransactionObj->Id ); - $attachments->OrderBy('id'); + $attachments->OrderBy( FIELD => 'id'); my $transaction_content_obj = $self->TransactionObj->ContentObj; @@ -445,7 +445,7 @@ sub SetRTSpecialHeaders { # If there is one, and we can parse it, then base our Message-ID on it if ($msgid - and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>$/ + and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\Q$RT::Organization\E>$/ "<$1." . $self->TicketObj->id . "-" . $self->ScripObj->id . "-" . $self->ScripActionObj->{_Message_ID} diff --git a/rt/lib/RT/Attachments_Overlay.pm b/rt/lib/RT/Attachments_Overlay.pm index 1ea56e47c..5afcd551e 100644 --- a/rt/lib/RT/Attachments_Overlay.pm +++ b/rt/lib/RT/Attachments_Overlay.pm @@ -81,6 +81,8 @@ sub _Init { $self->{'table'} = "Attachments"; $self->{'primary_key'} = "id"; + $self->OrderBy ( FIELD => 'id', + ORDER => 'ASC'); return ( $self->SUPER::_Init(@_)); } # }}} diff --git a/rt/lib/RT/CustomFieldValues.pm b/rt/lib/RT/CustomFieldValues.pm index 9a55af8bd..d4d2d1a23 100644 --- a/rt/lib/RT/CustomFieldValues.pm +++ b/rt/lib/RT/CustomFieldValues.pm @@ -81,17 +81,18 @@ sub _Init { $self->{'table'} = 'CustomFieldValues'; $self->{'primary_key'} = 'id'; - - - # By default, order by SortOrder - $self->OrderByCols( - { ALIAS => 'main', - FIELD => 'SortOrder', - ORDER => 'ASC' }, - { ALIAS => 'main', - FIELD => 'id', - ORDER => 'ASC' }, - ); + # By default, order by SortOrder + $self->OrderByCols( + { ALIAS => 'main', + FIELD => 'SortOrder', + ORDER => 'ASC' }, + { ALIAS => 'main', + FIELD => 'Name', + ORDER => 'ASC' }, + { ALIAS => 'main', + FIELD => 'id', + ORDER => 'ASC' }, + ); return ( $self->SUPER::_Init(@_) ); } diff --git a/rt/lib/RT/CustomField_Overlay.pm b/rt/lib/RT/CustomField_Overlay.pm index 9e0ce2460..743c85cd7 100644 --- a/rt/lib/RT/CustomField_Overlay.pm +++ b/rt/lib/RT/CustomField_Overlay.pm @@ -461,7 +461,7 @@ sub ValuesForTicket { my $self = shift; my $ticket_id = shift; - $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject"); + $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject at (". join(":",caller).")"); my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($ticket_id); @@ -485,7 +485,7 @@ sub AddValueForTicket { my %args = ( Ticket => undef, Content => undef, @_ ); - $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject"); + $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject at (". join(":",caller).")"); my $ticket = RT::Ticket->new($self->CurrentUser); @@ -513,7 +513,7 @@ sub DeleteValueForTicket { Content => undef, @_ ); - $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject"); + $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject at (". join(":",caller).")"); my $ticket = RT::Ticket->new($self->CurrentUser); @@ -615,7 +615,7 @@ sub ValidateType { my $type = shift; if ($type =~ s/(?:Single|Multiple)$//) { - $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead"); + $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")"); } if( $FieldTypes{$type}) { @@ -631,7 +631,7 @@ sub SetType { my $self = shift; my $type = shift; if ($type =~ s/(?:(Single)|Multiple)$//) { - warn "'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead"; + $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")"); $self->SetMaxValues($1 ? 1 : 0); } $self->SUPER::SetType($type); @@ -738,19 +738,19 @@ Takes a boolean. # }}} sub Queue { - $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated"); + $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated at (". join(":",caller).")"); return 0; } sub SetQueue { - $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated"); + $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated at (". join(":",caller).")"); return 0; } sub QueueObj { - $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated"); + $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated at (". join(":",caller).")"); return undef; } diff --git a/rt/lib/RT/EmailParser.pm b/rt/lib/RT/EmailParser.pm index 3a99e5a5e..eccc20a2f 100644 --- a/rt/lib/RT/EmailParser.pm +++ b/rt/lib/RT/EmailParser.pm @@ -277,7 +277,7 @@ sub _PostProcessNewEntity { sub ParseTicketId { my $self = shift; - $RT::Logger->warnings("RT::EmailParser->ParseTicketId deprecated. You should be using RT::Interface::Email"); + $RT::Logger->warnings("RT::EmailParser->ParseTicketId deprecated. You should be using RT::Interface::Email at (". join(":",caller).")"); require RT::Interface::Email; RT::Interface::Email::ParseTicketId(@_); @@ -435,7 +435,7 @@ sub IsRTAddress { # Example: the following rule would tell RT not to Cc # "tickets@noc.example.com" if ( defined($RT::RTAddressRegexp) && - $address =~ /$RT::RTAddressRegexp/ ) { + $address =~ /$RT::RTAddressRegexp/i ) { return(1); } else { return (undef); diff --git a/rt/lib/RT/Groups_Overlay.pm b/rt/lib/RT/Groups_Overlay.pm index cf29114dc..815fbd999 100644 --- a/rt/lib/RT/Groups_Overlay.pm +++ b/rt/lib/RT/Groups_Overlay.pm @@ -77,23 +77,79 @@ package RT::Groups; use strict; no warnings qw(redefine); +use RT::Users; + +# XXX: below some code is marked as subject to generalize in Groups, Users classes. +# RUZ suggest name Principals::Generic or Principals::Base as abstract class, but +# Jesse wants something that doesn't imply it's a Principals.pm subclass. +# See comments below for candidats. + # {{{ sub _Init +=begin testing + +# next had bugs +# Groups->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => xx ); +my $g = RT::Group->new($RT::SystemUser); +my ($id, $msg) = $g->CreateUserDefinedGroup(Name => 'GroupsNotEqualTest'); +ok ($id, "created group #". $g->id) or diag("error: $msg"); + +my $groups = RT::Groups->new($RT::SystemUser); +$groups->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $g->id ); +$groups->LimitToUserDefinedGroups(); +my $bug = grep $_->id == $g->id, @{$groups->ItemsArrayRef}; +ok (!$bug, "didn't find group"); + +=end testing + +=cut + sub _Init { my $self = shift; $self->{'table'} = "Groups"; $self->{'primary_key'} = "id"; + my @result = $self->SUPER::_Init(@_); + $self->OrderBy( ALIAS => 'main', FIELD => 'Name', ORDER => 'ASC'); - - return ( $self->SUPER::_Init(@_)); + # XXX: this code should be generalized + $self->{'princalias'} = $self->Join( + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'Principals', + FIELD2 => 'id' + ); + + # even if this condition is useless and ids in the Groups table + # only match principals with type 'Group' this could speed up + # searches in some DBs. + $self->Limit( ALIAS => $self->{'princalias'}, + FIELD => 'PrincipalType', + VALUE => 'Group', + ); + + return (@result); } # }}} +=head2 PrincipalsAlias + +Returns the string that represents this Users object's primary "Principals" alias. + +=cut + +# XXX: should be generalized, code duplication +sub PrincipalsAlias { + my $self = shift; + return($self->{'princalias'}); + +} + + # {{{ LimiToSystemInternalGroups =head2 LimitToSystemInternalGroups @@ -215,7 +271,7 @@ my $u = RT::User->new($RT::SystemUser); $u->Create(Name => 'Membertests'); my $g = RT::Group->new($RT::SystemUser); my ($id, $msg) = $g->CreateUserDefinedGroup(Name => 'Membertests'); -ok ($id,$msg); +ok ($id, $msg); my ($aid, $amsg) =$g->AddMember($u->id); ok ($aid, $amsg); @@ -227,9 +283,6 @@ $groups->WithMember(PrincipalId => $u->id); ok ($groups->Count == 1,"found the 1 group - " . $groups->Count); ok ($groups->First->Id == $g->Id, "it's the right one"); - - - =end testing @@ -285,7 +338,7 @@ ok ($testuser->HasRight(Object => $q, Right => 'OwnTicket') , "The test user doe $groups = RT::Groups->new($RT::SystemUser); $groups->WithRight(Right => 'OwnTicket', Object => $q); ok ($id,$msg); -is($groups->Count, 2); +is($groups->Count, 3); my $RTxGroup = RT::Group->new($RT::SystemUser); ($id, $msg) = $RTxGroup->CreateUserDefinedGroup( Name => 'RTxGroup', Description => "RTx extension group"); @@ -343,84 +396,66 @@ is($groups->Count, 1, "RTxGroupRight found for RTxObj2"); =cut - +#XXX: should be generilized sub WithRight { my $self = shift; my %args = ( Right => undef, Object => => undef, IncludeSystemRights => 1, IncludeSuperusers => undef, + IncludeSubgroupMembers => 0, EquivObjects => [ ], @_ ); - my $acl = $self->NewAlias('ACL'); - - # {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser - $self->Limit( ALIAS => $acl, - FIELD => 'RightName', - OPERATOR => ($args{Right} ? '=' : 'IS NOT'), - VALUE => $args{Right} || 'NULL', - ENTRYAGGREGATOR => 'OR' ); - - if ( $args{'IncludeSuperusers'} and $args{'Right'} ) { - $self->Limit( ALIAS => $acl, - FIELD => 'RightName', - OPERATOR => '=', - VALUE => 'SuperUser', - ENTRYAGGREGATOR => 'OR' ); - } - # }}} - - my ($or_check_ticket_roles, $or_check_roles); - my $which_object = "$acl.ObjectType = 'RT::System'"; - - if ( defined $args{'Object'} ) { - if ( ref($args{'Object'}) eq 'RT::Ticket' ) { - $or_check_ticket_roles = - " OR ( main.Domain = 'RT::Ticket-Role' AND main.Instance = " . $args{'Object'}->Id . ") "; - - # If we're looking at ticket rights, we also want to look at the associated queue rights. - # this is a little bit hacky, but basically, now that we've done the ticket roles magic, - # we load the queue object and ask all the rest of our questions about the queue. - $args{'Object'} = $args{'Object'}->QueueObj; - } - # TODO XXX This really wants some refactoring - if ( ref($args{'Object'}) eq 'RT::Queue' ) { - $or_check_roles = - " OR ( ( (main.Domain = 'RT::Queue-Role' AND main.Instance = " . - $args{'Object'}->Id . ") $or_check_ticket_roles ) " . - " AND main.Type = $acl.PrincipalType) "; - } - - if ( $args{'IncludeSystemRights'} ) { - $which_object .= ' OR '; - } - else { - $which_object = ''; - } - foreach my $obj ( @{ $args{'EquivObjects'} } ) { - next unless ( UNIVERSAL::can( $obj, 'id' ) ); - $which_object .= "($acl.ObjectType = '" . ref( $obj ) . "' AND $acl.ObjectId = " . $obj->id . ") OR "; - } - $which_object .= - " ($acl.ObjectType = '" . ref($args{'Object'}) . "'" . - " AND $acl.ObjectId = " . $args{'Object'}->Id . ") "; - } + my $from_role = $self->Clone; + $from_role->WithRoleRight( %args ); - $self->_AddSubClause( "WhichObject", "($which_object)" ); - - $self->_AddSubClause( "WhichGroup", - qq{ - ( ( $acl.PrincipalId = main.id - AND $acl.PrincipalType = 'Group' - AND ( main.Domain = 'SystemInternal' - OR main.Domain = 'UserDefined' - OR main.Domain = 'ACLEquivalence')) - $or_check_roles) - } - ); + my $from_group = $self->Clone; + $from_group->WithGroupRight( %args ); + + #XXX: DIRTY HACK + use DBIx::SearchBuilder::Union; + my $union = new DBIx::SearchBuilder::Union; + $union->add($from_role); + $union->add($from_group); + %$self = %$union; + bless $self, ref($union); + + return; } +#XXX: methods are active aliases to Users class to prevent code duplication +# should be generalized +sub _JoinGroups { + my $self = shift; + my %args = (@_); + return 'main' unless $args{'IncludeSubgroupMembers'}; + return $self->RT::Users::_JoinGroups( %args ); +} +sub _JoinGroupMembers { + my $self = shift; + my %args = (@_); + return 'main' unless $args{'IncludeSubgroupMembers'}; + return $self->RT::Users::_JoinGroupMembers( %args ); +} +sub _JoinGroupMembersForGroupRights { + my $self = shift; + my %args = (@_); + my $group_members = $self->_JoinGroupMembers( %args ); + unless( $group_members eq 'main' ) { + return $self->RT::Users::_JoinGroupMembersForGroupRights( %args ); + } + $self->Limit( ALIAS => $args{'ACLAlias'}, + FIELD => 'PrincipalId', + VALUE => "main.id", + QUOTEVALUE => 0, + ); +} +sub _JoinACL { return (shift)->RT::Users::_JoinACL( @_ ) } +sub _GetEquivObjects { return (shift)->RT::Users::_GetEquivObjects( @_ ) } +sub WithGroupRight { return (shift)->RT::Users::WhoHaveGroupRight( @_ ) } +sub WithRoleRight { return (shift)->RT::Users::WhoHaveRoleRight( @_ ) } + # {{{ sub LimitToEnabled =head2 LimitToEnabled @@ -432,18 +467,11 @@ Only find items that haven\'t been disabled sub LimitToEnabled { my $self = shift; - my $alias = $self->Join( - TYPE => 'left', - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'Principals', - FIELD2 => 'id' - ); - - $self->Limit( ALIAS => $alias, - FIELD => 'Disabled', - VALUE => '0', - OPERATOR => '=' ); + $self->Limit( ALIAS => $self->PrincipalsAlias, + FIELD => 'Disabled', + VALUE => '0', + OPERATOR => '=', + ); } # }}} @@ -458,20 +486,12 @@ Only find items that have been deleted. sub LimitToDeleted { my $self = shift; - my $alias = $self->Join( - TYPE => 'left', - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'Principals', - FIELD2 => 'id' - ); - $self->{'find_disabled_rows'} = 1; - $self->Limit( ALIAS => $alias, - FIELD => 'Disabled', - OPERATOR => '=', - VALUE => '1' - ); + $self->Limit( ALIAS => $self->PrincipalsAlias, + FIELD => 'Disabled', + OPERATOR => '=', + VALUE => 1, + ); } # }}} diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm index affff981b..e45faf3af 100644 --- a/rt/lib/RT/I18N.pm +++ b/rt/lib/RT/I18N.pm @@ -223,7 +223,7 @@ sub SetMIMEEntityToEncoding { # {{{ Convert the body eval { - $RT::Logger->debug("Converting '$charset' to '$enc' for ". $head->mime_type . " - ". $head->get('subject')); + $RT::Logger->debug("Converting '$charset' to '$enc' for ". $head->mime_type . " - ". ($head->get('subject') || 'Subjectless message')); # NOTE:: see the comments at the end of the sub. Encode::_utf8_off( $lines[$_] ) foreach ( 0 .. $#lines ); diff --git a/rt/lib/RT/I18N/cs.po b/rt/lib/RT/I18N/cs.po index 224a316eb..83fc3790d 100644 --- a/rt/lib/RT/I18N/cs.po +++ b/rt/lib/RT/I18N/cs.po @@ -1,9 +1,9 @@ # msgid "" msgstr "" -"Project-Id-Version: RT 3.2.2\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2005-01-21 00:00+0100\n" +"PO-Revision-Date: 2005-10-03 13:40-0400\n" "Last-Translator: Jan Okrouhly <okrouhly@civ.zcu.cz>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" diff --git a/rt/lib/RT/I18N/da.po b/rt/lib/RT/I18N/da.po index 76e97f5eb..dfb1666f6 100644 --- a/rt/lib/RT/I18N/da.po +++ b/rt/lib/RT/I18N/da.po @@ -1,5 +1,10 @@ +# msgid "" msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: 2005-10-03 13:42-0400\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/rt/lib/RT/I18N/de.po b/rt/lib/RT/I18N/de.po index 0b7f1eff7..6c4c38378 100644 --- a/rt/lib/RT/I18N/de.po +++ b/rt/lib/RT/I18N/de.po @@ -4,11 +4,11 @@ # msgid "" msgstr "" -"Project-Id-Version: RT 2.1.54\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-06-22 06:06+0200\n" -"PO-Revision-Date: 2003-02-20 04:47+0200\n" +"PO-Revision-Date: 2005-10-03 13:43-0400\n" "Last-Translator: Karsten Konrad <karsten.konrad@uni-graz.at>\n" -"Language-Team: RT German <rt@fxb.de>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -3785,15 +3785,15 @@ msgstr "Status von %1 auf %2 geändert" #: html/Ticket/Elements/Tabs:170 msgid "Steal" -msgstr "Übernehmen" +msgstr "Stehlen" #: lib/RT/Queue_Overlay.pm:118 msgid "Steal tickets" -msgstr "Anfragen übernehmen" +msgstr "Anfragen stehlen" #: lib/RT/Queue_Overlay.pm:118 msgid "StealTicket" -msgstr "AnfrageÜbernehmen" +msgstr "AnfrageStehlen" #: lib/RT/Transaction_Overlay.pm:667 #. ($Old->Name) diff --git a/rt/lib/RT/I18N/en.po b/rt/lib/RT/I18N/en.po index 28a840680..f7fa3c75c 100644 --- a/rt/lib/RT/I18N/en.po +++ b/rt/lib/RT/I18N/en.po @@ -1,3 +1,14 @@ +# +msgid "" +msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: 2005-10-03 13:44-0400\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + #: lib/RT/Date.pm:440 msgid "Apr." msgstr "Apr" diff --git a/rt/lib/RT/I18N/es.po b/rt/lib/RT/I18N/es.po index 87759ee1e..ebed9cc0d 100644 --- a/rt/lib/RT/I18N/es.po +++ b/rt/lib/RT/I18N/es.po @@ -1,9 +1,9 @@ # msgid "" msgstr "" -"Project-Id-Version: RT 2.1.x\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2003-03-23 12:38\n" +"PO-Revision-Date: 2005-10-03 13:44-0400\n" "Last-Translator: Tomà s Núñez Lirola <tomasnl@dsl.upc.es>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" @@ -95,7 +95,7 @@ msgstr "" #. ($TicketObj->OwnerObj->Name()) #. (loc($Ticket->Status())) msgid "%1 (Unchanged)" -msgstr "" +msgstr "%1 (Sin cambios)" #: NOT FOUND IN SOURCE msgid "%1 - %2 shown" @@ -106,7 +106,7 @@ msgstr "%1 - %2 mostrados" #. ("--condition-argument", "--condition") #. ("--action-argument", "--action") msgid "%1 - An argument to pass to %2" -msgstr "%1 - Un parametro para pasar a %2" +msgstr "%1 - Un parámetro para pasar a %2" #: bin/rt-crontool:210 #. ("--verbose") @@ -116,17 +116,17 @@ msgstr "%1 - El estado de la salida actualiza STDOUT" #: bin/rt-crontool:204 #. ("--action") msgid "%1 - Specify the action module you want to use" -msgstr "%1 - Especifica el modulo de accion que quieres usar" +msgstr "%1 - Especifica el módulo de accion que quieres usar" #: bin/rt-crontool:198 #. ("--condition") msgid "%1 - Specify the condition module you want to use" -msgstr "%1 - Especifica el modulo de condicion que quieres usar" +msgstr "%1 - Especifica el módulo de condición que quieres usar" #: bin/rt-crontool:191 #. ("--search") msgid "%1 - Specify the search module you want to use" -msgstr "%1 - Especifica el modulo de busqueda que quieres usar" +msgstr "%1 - Especifica el módulo de búsqueda que quieres usar" $RT::VERSION, '2004', @@ -201,7 +201,7 @@ msgstr "%1 ha cambiado de %2 a %3" #: html/Search/Build.html:212 #. ($Description) msgid "%1 copy" -msgstr "" +msgstr "%1 copiar" #: lib/RT/Record.pm:930 msgid "%1 could not be set to %2." @@ -219,21 +219,21 @@ msgstr "%1 no pudo fijar el estado a resuelto. La base de datos de RT podrÃa se #: lib/RT/Transaction_Overlay.pm:560 #. ($obj_type) msgid "%1 created" -msgstr "" +msgstr "%1 creado" #: lib/RT/Transaction_Overlay.pm:565 #. ($obj_type) msgid "%1 deleted" -msgstr "" +msgstr "%1 borrado" #: html/Elements/MyTickets:47 #. ($rows) msgid "%1 highest priority tickets I own" -msgstr "" +msgstr "Los %1 tickets de mayor prioridad que poseo..." #: NOT FOUND IN SOURCE msgid "%1 highest priority tickets I own..." -msgstr "Los %1 tickets de mayor prioridad que poseo... " +msgstr "Los %1 tickets de mayor prioridad que poseo..." #: NOT FOUND IN SOURCE msgid "%1 highest priority tickets I requested..." @@ -242,7 +242,7 @@ msgstr "Los %1 tickets de mayor prioridad que he pedido" #: bin/rt-crontool:186 #. ($0) msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron." -msgstr "$1 es una herramienta para actuar sobre los tickets con una herramienta de planificacion externa, como crom" +msgstr "$1 es una herramienta para actuar sobre los tickets con una herramienta de planificación externa, como crom" #: lib/RT/Queue_Overlay.pm:860 #. ($principal->Object->Name, $args{'Type'}) @@ -273,7 +273,7 @@ msgstr "%1 min" #: html/Elements/MyRequests:47 #. ($rows) msgid "%1 newest unowned tickets" -msgstr "" +msgstr "Los %1 tickets más recientes sin propietario" #: NOT FOUND IN SOURCE msgid "%1 not shown" @@ -281,7 +281,7 @@ msgstr "%1 no mostrado" #: lib/RT/CustomField_Overlay.pm:827 msgid "%1 objects" -msgstr "" +msgstr "%1 ibjetos" #: html/User/Elements/DelegateRights:97 #. (loc($ObjectType =~ /^RT::(.*)$/)) @@ -315,17 +315,17 @@ msgstr "%1 pondrá como pendiente una BASE [local] si es dependiente [o miembro] #: lib/RT/CustomField_Overlay.pm:828 msgid "%1's %2 objects" -msgstr "" +msgstr "%1's %2 objetos" #: lib/RT/CustomField_Overlay.pm:829 msgid "%1's %2's %3 objects" -msgstr "" +msgstr "%1's %2's %3 objetos" #: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:56 #. ($object->Name) #. ($Object->Name) msgid "%1's saved searches" -msgstr "" +msgstr "búsquedas guardadas de %1" #: lib/RT/Transaction_Overlay.pm:470 #. ($self) @@ -369,15 +369,15 @@ msgstr "(Marque las cajas para borrar)" #: html/Ticket/Elements/PreviewScrips:94 msgid "(Check boxes to disable notifications to the listed recipients)" -msgstr "" +msgstr "(Marcar las casillas para deshabilitar notificaciones a los receptores listados)" #: html/Ticket/Elements/PreviewScrips:116 msgid "(Check boxes to enable notifications to the listed recipients)" -msgstr "" +msgstr "(Marcar las casillas para habilitar notificaciones a los receptores listados)" #: html/Ticket/Create.html:209 msgid "(Enter ticket ids or URLs, separated with spaces)" -msgstr "" +msgstr "(Entrar ids de los tickets o URLs, separados con espacios)" #: html/Admin/Queues/Modify.html:75 html/Admin/Queues/Modify.html:81 #. ($RT::CorrespondAddress) @@ -407,11 +407,11 @@ msgstr "(Sin plantillas)" #: html/Admin/Elements/PickCustomFields:47 html/Admin/Elements/PickObjects:47 msgid "(None)" -msgstr "" +msgstr "(Ninguno)" #: html/Ticket/Update.html:88 msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)" -msgstr "(Envia una copia oculta de esta actualizacion a una lista delimitada por comas de direcciones de email. <b>NO</b> cambia quien recibirá futuras actualizaciones)" +msgstr "(Envia una copia oculta de esta actualización a una lista delimitada por comas de direcciones de email. <b>NO</b> cambia quien recibirá futuras actualizaciones)" #: NOT FOUND IN SOURCE msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)" @@ -435,7 +435,7 @@ msgstr "(Envia una copia de esta actualización a una lista de direcciones de co #: html/Admin/Elements/EditScrip:102 msgid "(Use these fields when you choose 'User Defined' for a condition or action)" -msgstr "" +msgstr "(Usar estos campos cuando se selecciona 'Definido por el usuario' para una condición o acción)" #: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54 msgid "(empty)" @@ -455,7 +455,7 @@ msgstr "(sin valor)" #: html/Admin/Elements/EditCustomFieldValues:47 msgid "(no values)" -msgstr "" +msgstr "(sin valores)" #: html/Elements/EditLinks:130 html/Ticket/Elements/BulkLinks:49 msgid "(only one ticket)" @@ -463,11 +463,11 @@ msgstr "(solo un ticket)" #: html/Elements/RT__Ticket/ColumnMap:146 msgid "(pending approval)" -msgstr "(pendiente de aprobacion)" +msgstr "(pendiente de aprobación)" #: html/Elements/RT__Ticket/ColumnMap:149 msgid "(pending other Collection)" -msgstr "" +msgstr "(pendiente de otra Recopilación)" #: NOT FOUND IN SOURCE msgid "(pending other tickets)" @@ -516,7 +516,7 @@ msgstr "Una plantilla en blanco" #: html/Admin/Users/Modify.html:363 msgid "A password was not set, so user won't be able to login." -msgstr "" +msgstr "Una contraseña no fue especificada, por lo que el usuario no podrá acceder al sistema" #: NOT FOUND IN SOURCE msgid "ACE Deleted" @@ -544,7 +544,7 @@ msgstr "ACEs solo pueden ser creadas o borradas." #: html/Search/Elements/SelectAndOr:46 msgid "AND" -msgstr "" +msgstr "Y" #: NOT FOUND IN SOURCE msgid "Aborting to avoid unintended ticket modifications.\\n" @@ -569,11 +569,11 @@ msgstr "Acción %1 no encontrada" #: NOT FOUND IN SOURCE msgid "Action committed." -msgstr "Action committed." +msgstr "Acción realizada." #: bin/rt-crontool:148 msgid "Action committed.\\n" -msgstr "" +msgstr "Acción realizada.\\n" #: bin/rt-crontool:144 msgid "Action prepared..." @@ -581,7 +581,7 @@ msgstr "Acción preparada..." #: html/Search/Build.html:85 msgid "Add" -msgstr "" +msgstr "Añadir" #: html/Search/Bulk.html:114 msgid "Add AdminCc" @@ -593,11 +593,11 @@ msgstr "Añadir Cc" #: html/Search/Elements/EditFormat:49 msgid "Add Columns" -msgstr "" +msgstr "Añadir Columnas" #: html/Search/Elements/PickCriteria:46 msgid "Add Criteria" -msgstr "" +msgstr "Añadir Criterio" #: html/Ticket/Create.html:144 html/Ticket/Update.html:114 msgid "Add More Files" @@ -609,7 +609,7 @@ msgstr "Añadir solicitante" #: html/Admin/Elements/AddCustomFieldValue:46 msgid "Add Value" -msgstr "" +msgstr "Añadir Valor" #: NOT FOUND IN SOURCE msgid "Add a keyword selection to this queue" @@ -629,7 +629,7 @@ msgstr "Añadir un scrip que se aplicará a todas las colas" #: html/Search/Build.html:85 msgid "Add additional criteria" -msgstr "" +msgstr "Añadir criterio adicional" #: html/Search/Bulk.html:146 msgid "Add comments or replies to selected tickets" @@ -645,7 +645,7 @@ msgstr "Añadir nuevos observadores" #: lib/RT/CustomField_Overlay.pm:103 msgid "Add, delete and modify custom field values for objects" -msgstr "" +msgstr "Añadir, borrar y modificar campo personalizo para objetos" #: NOT FOUND IN SOURCE msgid "AddNextState" @@ -751,7 +751,7 @@ msgstr "Cc Administrativa" #: html/Ticket/Elements/Tabs:197 msgid "Advanced" -msgstr "" +msgstr "Avanzado" #: NOT FOUND IN SOURCE msgid "Advanced Search" @@ -767,15 +767,15 @@ msgstr "Edad" #: html/Search/Elements/PickCriteria:52 msgid "Aggregator" -msgstr "" +msgstr "Agregador" #: etc/initialdata:363 msgid "All Approvals Passed" -msgstr "" +msgstr "Todas las Aprobaciones Superadas" #: NOT FOUND IN SOURCE msgid "All Custom Fields" -msgstr "Todos los campos custom" +msgstr "Todos los Campos Personalizados" #: html/Admin/Queues/index.html:75 msgid "All Queues" @@ -787,30 +787,30 @@ msgstr "Siempre envia un mensaje a los solicitantes independientemente del remit #: html/Search/Elements/EditQuery:56 msgid "And/Or" -msgstr "" +msgstr "Y/O" #: html/Admin/CustomFields/Modify.html:73 html/Admin/Elements/CustomFieldTabs:83 msgid "Applies to" -msgstr "" +msgstr "Aplica a" #: html/Search/Edit.html:64 msgid "Apply" -msgstr "" +msgstr "Aplicar" #: html/Search/Edit.html:64 msgid "Apply your changes" -msgstr "" +msgstr "Aplicar cambios" #: html/Elements/Tabs:74 msgid "Approval" -msgstr "Aprobacion" +msgstr "Aprobación" #: html/Approvals/Display.html:67 html/Approvals/Elements/ShowDependency:63 html/Approvals/index.html:86 #. ($Ticket->Id, $Ticket->Subject) #. ($ticket->id, $msg) #. ($link->BaseObj->Id, $link->BaseObj->Subject) msgid "Approval #%1: %2" -msgstr "Aprobacion #%1: %2" +msgstr "Aprobación #%1: %2" #: html/Approvals/index.html:75 #. ($ticket->Id) @@ -820,7 +820,7 @@ msgstr "Aprobación #%1: No se han guardado las notas debido a un error del sist #: html/Approvals/index.html:73 #. ($ticket->Id) msgid "Approval #%1: Notes recorded" -msgstr "Aprobacion #%1: Notas guardadas" +msgstr "Aprobación #%1: Notas guardadas" #: NOT FOUND IN SOURCE msgid "Approval Details" @@ -828,11 +828,11 @@ msgstr "Detalles de la aprobación" #: etc/initialdata:351 msgid "Approval Passed" -msgstr "" +msgstr "Aprobación superada" #: etc/initialdata:374 msgid "Approval Rejected" -msgstr "" +msgstr "Aprobación rechazada" #: NOT FOUND IN SOURCE msgid "Approval diagram" @@ -860,7 +860,7 @@ msgstr "Ascendente" #: lib/RT/Queue_Overlay.pm:97 msgid "Assign and remove custom fields" -msgstr "" +msgstr "Asignar y borrar campos personalizados" #: lib/RT/Queue_Overlay.pm:97 msgid "AssignCustomFields" @@ -897,7 +897,7 @@ msgstr "Archivos adjuntos" #: lib/RT/Attributes_Overlay.pm:172 msgid "Attribute Deleted" -msgstr "" +msgstr "Atributo borrado" #: lib/RT/Date.pm:444 msgid "Aug." @@ -1002,11 +1002,11 @@ msgstr "No se puede agregar un campo personalizable si no tiene un nombre" #: html/Admin/CustomFields/Objects.html:86 #. ($Class) msgid "Can't find a collection class for '%1'" -msgstr "" +msgstr "No se puede encontrar una clase de recopilación para '%1'" #: html/Search/Build.html:761 msgid "Can't find a saved search to work with" -msgstr "" +msgstr "No se puede encontrar una búsqueda guardada para trabajar" #: lib/RT/Link_Overlay.pm:160 msgid "Can't link a ticket to itself" @@ -1018,7 +1018,7 @@ msgstr "No se puede fusionar dentro de un caso ya fusionado. Nunca deberia recib #: html/Search/Build.html:766 msgid "Can't save this search" -msgstr "" +msgstr "No se puede guardar esta búsqueda" #: lib/RT/Record.pm:1266 lib/RT/Record.pm:1344 msgid "Can't specifiy both base and target" @@ -1039,11 +1039,11 @@ msgstr "Cambiar contraseña" #: html/Elements/Submit:100 msgid "Check All" -msgstr "" +msgstr "Seleccionar Todo" #: html/SelfService/Update.html:78 html/Ticket/Create.html:131 html/Ticket/Update.html:95 msgid "Check box to delete" -msgstr "Check box to delete" +msgstr "Selecciona la casilla para borrar" #: html/Admin/Elements/SelectRights:55 msgid "Check box to revoke right" @@ -1059,7 +1059,7 @@ msgstr "Ciudad" #: html/Elements/Submit:102 msgid "Clear All" -msgstr "" +msgstr "Borrar Todo" #: html/Ticket/Elements/ShowDates:68 msgid "Closed" @@ -1071,7 +1071,7 @@ msgstr "Solicitudes cerradas" #: html/SelfService/Closed.html:46 html/SelfService/Elements/Tabs:66 msgid "Closed tickets" -msgstr "" +msgstr "Tickets cerrados" #: NOT FOUND IN SOURCE msgid "Command not understood!\\n" @@ -1135,7 +1135,7 @@ msgstr "Condición" #: bin/rt-crontool:131 msgid "Condition matches..." -msgstr "La condicion coincide..." +msgstr "La condición coincide..." #: lib/RT/Scrip_Overlay.pm:189 msgid "Condition not found" @@ -1171,7 +1171,7 @@ msgstr "No se pudo crear grupo" #: html/Search/Elements/EditSearches:64 msgid "Copy" -msgstr "" +msgstr "Copiar" #: etc/initialdata:286 msgid "Correspondence" @@ -1199,12 +1199,12 @@ msgstr "No se pudo añadir un nuevo valor de campo personalizable para el ticket #: lib/RT/Record.pm:1693 msgid "Could not add new custom field value. " -msgstr "" +msgstr "No se pudo añadir nuevo valor de campo personalizado." #: lib/RT/Record.pm:1646 #. (, $value_msg) msgid "Could not add new custom field value. %1 " -msgstr "" +msgstr "No se pudo añadir nuevo valor de campo personalizado. %1 " #: lib/RT/Ticket_Overlay.pm:3004 lib/RT/Ticket_Overlay.pm:3012 lib/RT/Ticket_Overlay.pm:3029 msgid "Could not change owner. " @@ -1218,7 +1218,7 @@ msgstr "No se puede crear un CampoPersonalizable" #: html/Admin/Elements/EditCustomField:113 #. ($msg) msgid "Could not create CustomField: %1" -msgstr "" +msgstr "No se pudo crear CustomField: %1" #: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:502 lib/RT/Group_Overlay.pm:509 msgid "Could not create group" @@ -1263,7 +1263,7 @@ msgstr "No se pudo encontrar el usuario %1." #: html/Admin/CustomFields/Objects.html:69 msgid "Could not load CustomField %1" -msgstr "" +msgstr "No se pudo cargar CustomField %1" #: html/Admin/Groups/Members.html:109 html/User/Groups/Members.html:111 html/User/Groups/Modify.html:103 msgid "Could not load group" @@ -1272,11 +1272,11 @@ msgstr "No se puede cargar el grupo" #: lib/RT/SavedSearch.pm:120 #. ($privacy) msgid "Could not load object for %1" -msgstr "" +msgstr "No se pudo cargar objeto para %1" #: lib/RT/SavedSearch.pm:188 msgid "Could not load search attribute" -msgstr "" +msgstr "No se pudo cargar atributo de búsqueda" #: lib/RT/Queue_Overlay.pm:758 #. ($args{'Type'}) @@ -1299,7 +1299,7 @@ msgstr "No se pudo quitar ese principal como un %1 para este ticket" #: lib/RT/User_Overlay.pm:192 msgid "Could not set user info" -msgstr "" +msgstr "No se pudo establecer la información del usuario" #: lib/RT/Group_Overlay.pm:1011 msgid "Couldn't add member to group" @@ -1346,12 +1346,12 @@ msgstr "No se pudo cargar %1 desde la base de datos de usuarios.\\n" #: html/Admin/CustomFields/UserRights.html:149 #. ($id) msgid "Couldn't load Class %1" -msgstr "" +msgstr "No se pudo cargar Class %1" #: html/Admin/CustomFields/GroupRights.html:107 #. ($id) msgid "Couldn't load CustomField %1" -msgstr "" +msgstr "No se pudo cargar CustomField %1" #: NOT FOUND IN SOURCE msgid "Couldn't load KeywordSelects." @@ -1377,7 +1377,7 @@ msgstr "No se puedo cargar el enlace" #: html/Admin/Elements/ObjectCustomFields:83 html/Admin/Queues/CustomFields.html:59 html/Admin/Users/CustomFields.html:59 #. ($id) msgid "Couldn't load object %1" -msgstr "" +msgstr "No se pudo cargar objeto %1" #: html/Admin/Queues/People.html:142 #. ($id) @@ -1409,12 +1409,12 @@ msgstr "No se pudo cargar el ticket '%1'" #: lib/RT/Ticket_Overlay.pm:2612 #. ($args{'Base'}) msgid "Couldn't resolve base '%1' into a URI." -msgstr "" +msgstr "No se pudo resolver base '%1' en una URI." #: lib/RT/Ticket_Overlay.pm:2611 #. ($args{'Target'}) msgid "Couldn't resolve target '%1' into a URI." -msgstr "" +msgstr "No se pudo resolver objetivo '%1' en una URI." #: html/Admin/Users/Modify.html:173 html/User/Prefs.html:153 msgid "Country" @@ -1500,7 +1500,7 @@ msgstr "Crear una plantilla" #: html/SelfService/Create.html:46 html/SelfService/CreateTicketInQueue.html:46 msgid "Create a ticket" -msgstr "" +msgstr "Crear un ticket" #: NOT FOUND IN SOURCE msgid "Create failed: %1 / %2 / %3 " @@ -1565,7 +1565,7 @@ msgstr "Plantilla %1 creada" #: html/Search/Elements/PickBasics:102 msgid "Creator" -msgstr "" +msgstr "Creador" #: html/Elements/EditLinks:49 msgid "Current Links" @@ -1585,7 +1585,7 @@ msgstr "Permisos actuales" #: NOT FOUND IN SOURCE msgid "Current search criteria" -msgstr "Criterio de busqueda actual" +msgstr "Criterio de búsqueda actual" #: html/Admin/Queues/People.html:62 html/Ticket/Elements/EditPeople:66 msgid "Current watchers" @@ -1602,19 +1602,19 @@ msgstr "Campos personalizables" #: html/Admin/CustomFields/index.html:59 #. ($lookup) msgid "Custom Fields for %1" -msgstr "" +msgstr "Campos Personalizados para %1" #: html/Admin/Elements/EditScrip:123 msgid "Custom action cleanup code" -msgstr "Codigo de limpieza de accion personalizable" +msgstr "Codigo de limpieza de acción personalizable" #: html/Admin/Elements/EditScrip:115 msgid "Custom action preparation code" -msgstr "Codigo de preparacion de accion personalizable" +msgstr "Codigo de preparación de acción personalizable" #: html/Admin/Elements/EditScrip:107 msgid "Custom condition" -msgstr "Condicion personalizable" +msgstr "Condición personalizable" #: NOT FOUND IN SOURCE msgid "Custom field %1 %2 %3" @@ -1694,11 +1694,11 @@ msgstr "Plantilla de autorespuesta por defect" #: html/Tools/Offline.html:61 msgid "Default Queue" -msgstr "" +msgstr "Cola por Defecto" #: html/Tools/Offline.html:70 msgid "Default Requestor" -msgstr "" +msgstr "Solicitante por Defecto" #: etc/initialdata:296 msgid "Default admin comment template" @@ -1742,16 +1742,16 @@ msgstr "Borrar" #: html/Admin/Elements/EditTemplates:79 msgid "Delete Template" -msgstr "" +msgstr "Borrar Plantilla" #: lib/RT/SavedSearch.pm:211 #. ($msg) msgid "Delete failed: %1" -msgstr "" +msgstr "Borrado fallido: %1" #: html/Admin/Elements/EditScrips:74 msgid "Delete selected scrips" -msgstr "" +msgstr "Borrar scripts seleccionados" #: lib/RT/Queue_Overlay.pm:115 msgid "Delete tickets" @@ -1763,7 +1763,7 @@ msgstr "DeleteTicket" #: lib/RT/SavedSearch.pm:209 msgid "Deleted search" -msgstr "" +msgstr "Búsqueda borrada" #: NOT FOUND IN SOURCE msgid "Deleting this object could break referential integrity" @@ -1800,22 +1800,22 @@ msgstr "Dependencias: \\n" #: lib/RT/Transaction_Overlay.pm:707 #. ($value) msgid "Dependency by %1 added" -msgstr "" +msgstr "Dependencia para %1 añadida" #: lib/RT/Transaction_Overlay.pm:747 #. ($value) msgid "Dependency by %1 deleted" -msgstr "" +msgstr "Dependencia para %1 borrada" #: lib/RT/Transaction_Overlay.pm:704 #. ($value) msgid "Dependency on %1 added" -msgstr "" +msgstr "Dependencia en %1 añadida" #: lib/RT/Transaction_Overlay.pm:744 #. ($value) msgid "Dependency on %1 deleted" -msgstr "" +msgstr "Dependencia en %1 borrada" #: html/Elements/EditLinks:134 html/Elements/EditLinks:57 html/Elements/SelectLinkType:48 html/Elements/ShowLinks:48 html/Ticket/Create.html:211 html/Ticket/Elements/BulkLinks:52 html/Ticket/Elements/ShowDependencies:46 msgid "Depends on" @@ -1851,7 +1851,7 @@ msgstr "Mostrar Lista de Control de Acceso" #: html/Search/Elements/DisplayOptions:46 msgid "Display Columns" -msgstr "" +msgstr "Mostrar Columnas" #: lib/RT/Queue_Overlay.pm:100 msgid "Display Scrip templates for this queue" @@ -1867,7 +1867,7 @@ msgstr "Modo de despliegue" #: lib/RT/Group_Overlay.pm:176 msgid "Display saved searches for this group" -msgstr "" +msgstr "Mostrar búsquedas guardadas para este grupo" #: NOT FOUND IN SOURCE msgid "Display ticket #%1" @@ -1883,7 +1883,7 @@ msgstr "Hacer cualquier cosa y todo" #: html/Search/Build.html:112 msgid "Do the Search" -msgstr "" +msgstr "Realizar búsqueda" #: html/Elements/Refresh:51 msgid "Don't refresh this page." @@ -1891,7 +1891,7 @@ msgstr "No recargar esta página" #: NOT FOUND IN SOURCE msgid "Don't show search results" -msgstr "No mostrar los resultados de la busqueda" +msgstr "No mostrar los resultados de la búsqueda" #: html/Ticket/Elements/ShowTransactionAttachments:82 msgid "Download" @@ -1899,15 +1899,15 @@ msgstr "Descargar" #: html/Admin/Groups/index.html:61 html/Admin/Users/index.html:64 msgid "Download as a tab-delimited file" -msgstr "" +msgstr "Descargar como un fichero tabulado" #: html/Elements/SelectDateType:53 html/Ticket/Create.html:197 html/Ticket/Elements/EditDates:66 html/Ticket/Elements/ShowDates:64 lib/RT/Ticket_Overlay.pm:1149 msgid "Due" -msgstr "Retraso" +msgstr "Esperado" #: NOT FOUND IN SOURCE msgid "Due date '%1' could not be parsed" -msgstr "La fecha de retraso '%1' no pudo ser leida" +msgstr "La fecha esperada '%1' no pudo ser leÃda" #: NOT FOUND IN SOURCE msgid "ERROR: Couldn't load ticket '%1': %2.\\n" @@ -1924,15 +1924,15 @@ msgstr "Editar campos personalizados para %1" #: html/Admin/Global/CustomFields/Groups.html:9 msgid "Edit Custom Fields for all groups" -msgstr "" +msgstr "Editar Campos Personalizados para todos los grupos" #: html/Admin/Global/CustomFields/Users.html:9 msgid "Edit Custom Fields for all users" -msgstr "" +msgstr "Editar Campos Personalizados para todos los usuarios" #: html/Admin/Global/CustomFields/Queue-Tickets.html:9 html/Admin/Global/CustomFields/Queue-Transactions.html:9 msgid "Edit Custom Fields for tickets in all queues" -msgstr "" +msgstr "Editar Campos Personalizados para tickets en todas las colas" #: html/Search/Bulk.html:173 html/Ticket/ModifyLinks.html:57 msgid "Edit Links" @@ -1940,7 +1940,7 @@ msgstr "Editar relaciones" #: html/Search/Edit.html:68 msgid "Edit Query" -msgstr "" +msgstr "Editar Consulta" #: html/Admin/Queues/Templates.html:63 #. ($QueueObj->Name) @@ -1953,7 +1953,7 @@ msgstr "Editar palabras clave" #: lib/RT/Group_Overlay.pm:175 msgid "Edit saved searches for this group" -msgstr "" +msgstr "Editar búsquedas guardadas para este grupo" #: NOT FOUND IN SOURCE msgid "Edit scrips" @@ -2048,7 +2048,7 @@ msgstr "Estado %1 habilitado" #: html/Admin/CustomFields/Modify.html:143 html/Admin/Queues/Modify.html:162 #. (loc_fuzzy($msg)) msgid "Enabled status: %1" -msgstr "" +msgstr "Estado habilitado: %1" #: lib/RT/CustomField_Overlay.pm:64 msgid "Enter multiple values" @@ -2056,7 +2056,7 @@ msgstr "Introducir multiples valores" #: html/Elements/EditLinks:124 msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces." -msgstr "" +msgstr "Entrar objetos o URIs para linkar a los objetos. Separar múltiples entradas con espacios." #: lib/RT/CustomField_Overlay.pm:65 msgid "Enter one value" @@ -2064,7 +2064,7 @@ msgstr "Introducir un valor" #: html/Elements/EditLinks:121 msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces." -msgstr "" +msgstr "Entrar colas o URIs para linkar a las colas. Separar múltiples entradas con espacios." #: html/Elements/EditLinks:117 html/Search/Bulk.html:174 msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces." @@ -2072,7 +2072,7 @@ msgstr "Ingrese los números de ticket o las URL que llevan hacia el ticket. Sep #: lib/RT/CustomField_Overlay.pm:66 msgid "Enter up to %1 values" -msgstr "" +msgstr "Entrar hasta %1 valor/es" #: html/Elements/Login:61 html/SelfService/Error.html:46 html/SelfService/Error.html:47 msgid "Error" @@ -2092,7 +2092,7 @@ msgstr "Error en los parámetros para Queue->DelWatcher" #: lib/RT/Queue_Overlay.pm:830 msgid "Error in parameters to Queue->DeleteWatcher" -msgstr "" +msgstr "Error en los parámetros para Queue->DeleteWatcher" #: lib/RT/Ticket_Overlay.pm:1333 msgid "Error in parameters to Ticket->AddWatcher" @@ -2104,15 +2104,15 @@ msgstr "Error en los parámetros para Queue->DelWatcher" #: lib/RT/Ticket_Overlay.pm:1499 msgid "Error in parameters to Ticket->DeleteWatcher" -msgstr "" +msgstr "Error en los parámetros para Ticket->DeleteWatcher" #: bin/rt-crontool:233 msgid "Escalate tickets" -msgstr "" +msgstr "Escalar tickets" #: html/Ticket/Elements/ShowBasics:57 msgid "Estimated" -msgstr "" +msgstr "Estimado" #: etc/initialdata:20 msgid "Everyone" @@ -2136,7 +2136,7 @@ msgstr "Información extra" #: lib/RT/SavedSearch.pm:165 msgid "Failed to create search attribute" -msgstr "" +msgstr "Fallado en crear atributo de búsqueda" #: lib/RT/User_Overlay.pm:377 msgid "Failed to find 'Privileged' users pseudogroup." @@ -2154,7 +2154,7 @@ msgstr "Error al cargar el modulo %1. (%2)" #: lib/RT/SavedSearch.pm:168 #. ($privacy) msgid "Failed to load object for %1" -msgstr "" +msgstr "Error al cargar objeto para %1" #: lib/RT/Date.pm:438 msgid "Feb." @@ -2166,31 +2166,31 @@ msgstr "Febrero" #: html/Elements/SelectAttachmentField:50 msgid "Filename" -msgstr "" +msgstr "Nombre de fichero" #: lib/RT/CustomField_Overlay.pm:69 msgid "Fill in multiple text areas" -msgstr "" +msgstr "Rellenar en multiples areas de texto" #: lib/RT/CustomField_Overlay.pm:74 msgid "Fill in multiple wikitext areas" -msgstr "" +msgstr "Rellenar en multiples areas wikitext" #: lib/RT/CustomField_Overlay.pm:70 msgid "Fill in one text area" -msgstr "" +msgstr "Rellenar en un area de texto" #: lib/RT/CustomField_Overlay.pm:75 msgid "Fill in one wikitext area" -msgstr "" +msgstr "Rellenar en un area wikitext" #: lib/RT/CustomField_Overlay.pm:71 msgid "Fill in up to %1 text areas" -msgstr "" +msgstr "Rellenar en hasta %1 areas de texto" #: lib/RT/CustomField_Overlay.pm:76 msgid "Fill in up to %1 wikitext areas" -msgstr "" +msgstr "Rellenar en hasta %1 areas wikitext" #: NOT FOUND IN SOURCE msgid "Fin" @@ -2210,7 +2210,7 @@ msgstr "Encontrar grupo que" #: html/Admin/Groups/index.html:72 html/Admin/Queues/People.html:82 html/Ticket/Elements/EditPeople:55 msgid "Find groups whose" -msgstr "" +msgstr "Encontrar grupos cuyo" #: NOT FOUND IN SOURCE msgid "Find new/open tickets" @@ -2226,7 +2226,7 @@ msgstr "Encontrar tickets" #: NOT FOUND IN SOURCE msgid "Finish Approval" -msgstr "Aprobacion final" +msgstr "Aprobación final" #: html/Ticket/Elements/Tabs:81 msgid "First" @@ -2250,7 +2250,7 @@ msgstr "Forzar cambio" #: html/Search/Elements/EditFormat:52 msgid "Format" -msgstr "" +msgstr "Formato" #: html/Search/Results.html:107 #. ($ticketcount) @@ -2283,7 +2283,7 @@ msgstr "Encabezados completos" #: html/Tools/Offline.html:87 msgid "Get template from file" -msgstr "" +msgstr "Obtener plantilla desde fichero" #: NOT FOUND IN SOURCE msgid "Getting the current user from a pgp sig\\n" @@ -2300,11 +2300,11 @@ msgstr "Global" #: html/Admin/Elements/EditCustomFields:55 msgid "Global Custom Fields" -msgstr "" +msgstr "Campos Personalizados Globales" #: NOT FOUND IN SOURCE msgid "Global Keyword Selections" -msgstr "Seleccion de palabras clave globales" +msgstr "Selección de palabras clave globales" #: NOT FOUND IN SOURCE msgid "Global Scrips" @@ -2312,7 +2312,7 @@ msgstr "Acciones Globales" #: html/Admin/Global/CustomFields/index.html:59 msgid "Global custom field configuration" -msgstr "" +msgstr "Configuración de Campos Personalizados Globales" #: html/Admin/Elements/SelectTemplate:59 #. (loc($Template->Name)) @@ -2321,7 +2321,7 @@ msgstr "Plantilla global" #: html/Tools/Offline.html:91 msgid "Go" -msgstr "" +msgstr "Ir" #: html/Admin/Groups/index.html:67 html/Admin/Groups/index.html:73 html/Admin/Queues/People.html:80 html/Admin/Queues/People.html:84 html/Admin/Queues/index.html:66 html/Admin/Users/index.html:73 html/Search/Results.html:76 html/Ticket/Elements/EditPeople:53 html/Ticket/Elements/EditPeople:57 html/index.html:91 msgid "Go!" @@ -2394,11 +2394,11 @@ msgstr "Los grupos no pueden ser miembros de sus propios miembros" #: html/Admin/Groups/index.html:82 msgid "Groups matching search criteria" -msgstr "" +msgstr "Grupos coincidentes con el criterio de búsqueda" #: html/Ticket/Elements/ShowRequestor:77 msgid "Groups this user belongs to" -msgstr "" +msgstr "Grupos a los que este usuario pertenece" #: lib/RT/Interface/CLI.pm:94 lib/RT/Interface/CLI.pm:94 msgid "Hello!" @@ -2416,12 +2416,12 @@ msgstr "Historial" #: html/Admin/Groups/History.html:62 #. ($GroupObj->Name) msgid "History of the group %1" -msgstr "" +msgstr "Historico del grupo %1" #: html/Admin/Users/History.html:62 #. ($UserObj->Name) msgid "History of the user %1" -msgstr "" +msgstr "Historico del usuario %1" #: NOT FOUND IN SOURCE msgid "HomePhone" @@ -2442,7 +2442,7 @@ msgstr "Tengo [quant,_1,concrete mixer]." #: html/Search/Build.html:637 msgid "I'm lost" -msgstr "" +msgstr "Estoy perdido" #: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1494 msgid "Id" @@ -2454,15 +2454,15 @@ msgstr "Identidad" #: etc/initialdata:429 msgid "If an approval is rejected, reject the original and delete pending approvals" -msgstr "Si una aprobacion es rechazada, rechazar la original y borrar las aprobaciones pendientes" +msgstr "Si una aprobación es rechazada, rechazar la original y borrar las aprobaciones pendientes" #: html/Tools/Offline.html:74 msgid "If no Requestor is specified, create tickets with this requestor." -msgstr "" +msgstr "Si no es especificado un Solicitante, crear tickets con este solicitante." #: html/Tools/Offline.html:65 msgid "If no queue is specified, create tickets in this queue." -msgstr "" +msgstr "Si no es especificada una cola, crear tickets en esta cola." #: bin/rt-crontool:215 msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT." @@ -2486,7 +2486,7 @@ msgstr "Incluir campos personalizables deshabilitados en el listado." #: html/Admin/Groups/index.html:65 msgid "Include disabled groups in listing." -msgstr "" +msgstr "Incluir grupos deshabilitados en listado." #: html/Admin/Queues/index.html:65 msgid "Include disabled queues in listing." @@ -2498,11 +2498,11 @@ msgstr "Incluir usuarios deshabilitados en la búsqueda" #: html/Search/Build.html:663 msgid "Incomplete Query" -msgstr "" +msgstr "Consulta Incompleta" #: html/Search/Build.html:660 msgid "Incomplete query" -msgstr "" +msgstr "Consulta incompleta" #: html/Search/Elements/PickBasics:161 lib/RT/Tickets_Overlay.pm:1544 msgid "Initial Priority" @@ -2632,11 +2632,11 @@ msgstr "Leng" #: html/Admin/Users/Modify.html:94 html/User/Prefs.html:76 msgid "Language" -msgstr "" +msgstr "Lenguaje" #: html/Search/Elements/EditFormat:79 msgid "Large" -msgstr "" +msgstr "Grande" #: html/Ticket/Elements/Tabs:96 msgid "Last" @@ -2668,7 +2668,7 @@ msgstr "" #: html/Ticket/Elements/ShowBasics:68 msgid "Left" -msgstr "Izquierda" +msgstr "Queda" #: html/Admin/Users/Modify.html:109 msgid "Let this user access RT" @@ -2727,11 +2727,11 @@ msgstr "Enlaces" #: html/Search/Elements/EditSearches:76 msgid "Load" -msgstr "" +msgstr "Cargar" #: html/Search/Elements/EditSearches:74 msgid "Load saved search:" -msgstr "" +msgstr "Cargar búsqueda guardada:" #: lib/RT/System.pm:87 msgid "LoadSavedSearch" @@ -2739,16 +2739,16 @@ msgstr "" #: html/Admin/Tools/Configuration.html:64 msgid "Loaded perl modules" -msgstr "" +msgstr "Modulos perl cargados" #: lib/RT/SavedSearch.pm:112 #. ($self->Name) msgid "Loaded search %1" -msgstr "" +msgstr "Cargada búsqueda %1" #: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126 msgid "Location" -msgstr "Direccion" +msgstr "Dirección" #: lib/RT.pm:212 #. ($RT::LogDir) @@ -2814,11 +2814,11 @@ msgstr "Establecer tÃtulo" #: lib/RT/Group_Overlay.pm:177 msgid "Make this group visible to user" -msgstr "" +msgstr "Hacer este grupo visible al usuario" #: html/Admin/index.html:78 msgid "Manage custom fields and custom field values" -msgstr "" +msgstr "Gestionar campos personalizados y valores de campos personalizados" #: html/Admin/index.html:69 msgid "Manage groups and group membership" @@ -2826,7 +2826,7 @@ msgstr "Administrar grupos y miembros" #: html/Admin/index.html:85 msgid "Manage properties and configuration which apply to all queues" -msgstr "Administrar propiedades y configuracion que se aplique a todas las colas" +msgstr "Administrar propiedades y configuración que se aplique a todas las colas" #: html/Admin/index.html:74 msgid "Manage queues and queue-specific properties" @@ -2855,12 +2855,12 @@ msgstr "May." #: lib/RT/Transaction_Overlay.pm:720 #. ($value) msgid "Member %1 added" -msgstr "" +msgstr "Miembro %1 añadido" #: lib/RT/Transaction_Overlay.pm:760 #. ($value) msgid "Member %1 deleted" -msgstr "" +msgstr "Miembro %1 borrado" #: lib/RT/Group_Overlay.pm:1008 msgid "Member added" @@ -2889,21 +2889,21 @@ msgstr "Miembros" #: lib/RT/Transaction_Overlay.pm:717 #. ($value) msgid "Membership in %1 added" -msgstr "" +msgstr "Pertenencia a %1 añadida" #: lib/RT/Transaction_Overlay.pm:757 #. ($value) msgid "Membership in %1 deleted" -msgstr "" +msgstr "Pertenencia a %1 añadida" #: html/Admin/Elements/UserTabs:61 msgid "Memberships" -msgstr "" +msgstr "Pertenencias" #: html/Admin/Users/Memberships.html:60 #. ($UserObj->Name) msgid "Memberships of the user %1" -msgstr "" +msgstr "Pertenencias del usuario %1" #: lib/RT/Ticket_Overlay.pm:2849 msgid "Merge Successful" @@ -2915,7 +2915,7 @@ msgstr "Fusión fallida. No se pudo establecer el EffectiveId" #: lib/RT/Ticket_Overlay.pm:2744 msgid "Merge failed. Couldn't set Status" -msgstr "" +msgstr "Unión fallida. No se pudo establecer el Estado" #: html/Elements/EditLinks:129 html/Ticket/Elements/BulkLinks:48 msgid "Merge into" @@ -2924,7 +2924,7 @@ msgstr "Fusionar dentro de" #: lib/RT/Transaction_Overlay.pm:723 #. ($value) msgid "Merged into %1" -msgstr "" +msgstr "Unido en %1" #: html/Search/Bulk.html:165 html/Ticket/Update.html:116 msgid "Message" @@ -2932,19 +2932,19 @@ msgstr "Mensaje" #: lib/RT/Ticket_Overlay.pm:2406 msgid "Message could not be recorded" -msgstr "" +msgstr "Mensaje no pudo ser grabado" #: lib/RT/Ticket_Overlay.pm:2409 msgid "Message recorded" -msgstr "" +msgstr "Mensaje grabado" #: html/Ticket/Elements/PreviewScrips:115 msgid "Messages about this ticket will not be sent to..." -msgstr "" +msgstr "Mensajes acerca de este ticket no seran enviados..." #: html/Search/Build.html:667 msgid "Mismatched parentheses" -msgstr "" +msgstr "Parentesis no coincidentes" #: lib/RT/Record.pm:940 msgid "Missing a primary key?: %1" @@ -2969,12 +2969,12 @@ msgstr "Modificar el campo personalizable %1" #: html/Admin/Elements/ObjectCustomFields:96 #. (loc(lc($FriendlySubTypes)), loc(lc($Types))) msgid "Modify Custom Fields which apply to %1 for all %2" -msgstr "" +msgstr "Modificar Campos Personalizados que aplican a %1 para todo %2" #: html/Admin/Elements/ObjectCustomFields:98 #. (loc(lc($Types))) msgid "Modify Custom Fields which apply to all %1" -msgstr "" +msgstr "Modificar Campos Personalizados que aplican a todo %1" #: NOT FOUND IN SOURCE msgid "Modify Custom Fields which apply to all queues" @@ -2982,15 +2982,15 @@ msgstr "Modificar los campos personalizables que se apliquen a todas las colas" #: html/Admin/Global/GroupRights.html:106 html/Admin/Groups/GroupRights.html:94 html/Admin/Queues/GroupRights.html:107 msgid "Modify Group Rights" -msgstr "" +msgstr "Modificar Derechos de Grupo" #: html/Admin/Groups/Members.html:102 html/User/Groups/Members.html:101 msgid "Modify Members" -msgstr "" +msgstr "Modificar Miembros" #: html/User/Delegation.html:58 msgid "Modify Rights" -msgstr "" +msgstr "Modificar Derechos" #: lib/RT/Queue_Overlay.pm:98 msgid "Modify Scrip templates for this queue" @@ -3010,7 +3010,7 @@ msgstr "Modificar plantilla %1" #: html/Admin/Global/UserRights.html:75 html/Admin/Groups/UserRights.html:76 html/Admin/Queues/UserRights.html:75 msgid "Modify User Rights" -msgstr "" +msgstr "Modificar Derechos de Usuario" #: html/Admin/Queues/CustomField.html:66 #. ($QueueObj->Name()) @@ -3033,7 +3033,7 @@ msgstr "Modificar un scrip que se aplique a todas las colas" #: html/Admin/CustomFields/Objects.html:90 #. ($CF->Name) msgid "Modify associated objects for %1" -msgstr "" +msgstr "Modificar objetos asociados para %1" #: NOT FOUND IN SOURCE msgid "Modify dates for # %1" @@ -3051,7 +3051,7 @@ msgstr "Modificar fechas para ticket # %1" #: html/Admin/Elements/GlobalCustomFieldTabs:65 html/Admin/Global/index.html:72 msgid "Modify global custom fields" -msgstr "" +msgstr "Modificar campos personalizados globales" #: html/Admin/Elements/GlobalCustomFieldTabs:70 html/Admin/Global/GroupRights.html:46 html/Admin/Global/GroupRights.html:49 html/Admin/Global/index.html:77 msgid "Modify global group rights" @@ -3088,7 +3088,7 @@ msgstr "Modificar metadatos del grupo o borrar grupo" #: html/Admin/CustomFields/GroupRights.html:164 #. ($CustomFieldObj->Name) msgid "Modify group rights for custom field %1" -msgstr "" +msgstr "Modificar derechos de grupo para campos personalizados %1" #: html/Admin/Groups/GroupRights.html:46 html/Admin/Groups/GroupRights.html:50 html/Admin/Groups/GroupRights.html:56 #. ($GroupObj->Name) @@ -3111,13 +3111,13 @@ msgstr "Modificar la propia cuenta RT" #: html/Admin/Queues/People.html:46 html/Admin/Queues/People.html:50 #. ($QueueObj->Name) msgid "Modify people related to queue %1" -msgstr "Modificar personas relacionadas al cola %1" +msgstr "Modificar personas relacionadas con la cola %1" #: html/Ticket/ModifyPeople.html:46 html/Ticket/ModifyPeople.html:50 html/Ticket/ModifyPeople.html:56 #. ($Ticket->id) #. ($Ticket->Id) msgid "Modify people related to ticket #%1" -msgstr "Modificar personas relacionadas al ticket #%1" +msgstr "Modificar personas relacionadas con el ticket #%1" #: html/Admin/Queues/Scrips.html:67 #. ($QueueObj->Name) @@ -3169,7 +3169,7 @@ msgstr "Modificar tickets" #: html/Admin/CustomFields/UserRights.html:157 #. ($CustomFieldObj->Name) msgid "Modify user rights for custom field %1" -msgstr "" +msgstr "Modificar derechos de usuario para campos personalidos %1" #: html/Admin/Groups/UserRights.html:46 html/Admin/Groups/UserRights.html:50 html/Admin/Groups/UserRights.html:56 #. ($GroupObj->Name) @@ -3245,7 +3245,7 @@ msgstr "Se debe especificar un nombre" #: html/SelfService/Elements/MyRequests:70 #. ($friendly_status) msgid "My %1 tickets" -msgstr "" +msgstr "Mis %1 tickets" #: NOT FOUND IN SOURCE msgid "My Approvals" @@ -3257,7 +3257,7 @@ msgstr "Mis aprobaciones" #: html/Search/Elements/SearchPrivacy:50 html/Search/Elements/SelectSearchObject:53 html/Search/Elements/SelectSearchesForObjects:53 msgid "My saved searches" -msgstr "" +msgstr "Mis búsquedas guardadas" #: html/Admin/CustomFields/Modify.html:58 html/Admin/Elements/AddCustomFieldValue:53 html/Admin/Elements/EditCustomField:55 html/Admin/Elements/EditCustomFieldValues:55 html/Admin/Elements/ModifyTemplate:49 html/Admin/Groups/Modify.html:65 html/User/Groups/Modify.html:65 msgid "Name" @@ -3269,7 +3269,7 @@ msgstr "Nombre en uso" #: NOT FOUND IN SOURCE msgid "Need approval from system administrator" -msgstr "Se necesita aprobacion del administrador del sistema" +msgstr "Se necesita aprobación del administrador del sistema" #: html/Ticket/Elements/ShowDates:73 msgid "Never" @@ -3293,7 +3293,7 @@ msgstr "Nueva pendiente de aprobación" #: html/Ticket/Elements/Tabs:193 msgid "New Query" -msgstr "" +msgstr "Nueva Consulta" #: NOT FOUND IN SOURCE msgid "New Search" @@ -3341,7 +3341,7 @@ msgstr "Nueva plantilla" #: html/SelfService/Elements/Tabs:69 msgid "New ticket" -msgstr "" +msgstr "Nuevo ticket" #: lib/RT/Ticket_Overlay.pm:2713 msgid "New ticket doesn't exist" @@ -3369,7 +3369,7 @@ msgstr "Siguiente" #: html/Elements/TicketList:104 msgid "Next Page" -msgstr "" +msgstr "Pagina siguiente" #: NOT FOUND IN SOURCE msgid "Next page" @@ -3385,7 +3385,7 @@ msgstr "Alias" #: html/Admin/CustomFields/UserRights.html:145 msgid "No Class defined" -msgstr "" +msgstr "No existe Class definida" #: html/Admin/CustomFields/Modify.html:124 html/Admin/Elements/EditCustomField:119 msgid "No CustomField" @@ -3393,7 +3393,7 @@ msgstr "No hay campo personalizable" #: html/Admin/CustomFields/GroupRights.html:103 msgid "No CustomField defined" -msgstr "" +msgstr "No existe CustomField definido" #: html/Admin/Groups/GroupRights.html:105 html/Admin/Groups/UserRights.html:92 msgid "No Group defined" @@ -3401,7 +3401,7 @@ msgstr "No hay grupo definido" #: lib/RT/Tickets_Overlay_SQL.pm:477 msgid "No Query" -msgstr "" +msgstr "No existe Consulta" #: html/Admin/Queues/GroupRights.html:118 html/Admin/Queues/UserRights.html:89 msgid "No Queue defined" @@ -3425,7 +3425,7 @@ msgstr "No se especificó ticket. Abortando las modificaciones al ticket\\n\\n" #: html/Approvals/Elements/Approve:67 msgid "No action" -msgstr "No action" +msgstr "No acción" #: lib/RT/Record.pm:935 msgid "No column specified" @@ -3454,11 +3454,11 @@ msgstr "No hay grupo especificado" #: html/Admin/Groups/index.html:52 msgid "No groups matching search criteria found." -msgstr "" +msgstr "No existen grupos coincidentes con el criterio de búsqueda." #: lib/RT/Ticket_Overlay.pm:2349 msgid "No message attached" -msgstr "" +msgstr "Mensaje no adjuntado" #: lib/RT/User_Overlay.pm:1035 msgid "No password set" @@ -3507,7 +3507,7 @@ msgstr "Sin privilegios concedidos" #: lib/RT/SavedSearch.pm:187 msgid "No search loaded" -msgstr "" +msgstr "No hay búsqueda cargada" #: html/Search/Bulk.html:194 msgid "No search to operate on." @@ -3515,7 +3515,7 @@ msgstr "No hay búsqueda sobre la que operar" #: html/Elements/RT__Ticket/ColumnMap:134 msgid "No subject" -msgstr "" +msgstr "No hay asunto" #: NOT FOUND IN SOURCE msgid "No ticket id specified" @@ -3587,11 +3587,11 @@ msgstr "Notificar AdminCcs como comentario" #: etc/initialdata:93 etc/upgrade/3.1.17/content:6 msgid "Notify Ccs" -msgstr "" +msgstr "Notificar Ccs" #: etc/initialdata:89 etc/upgrade/3.1.17/content:2 msgid "Notify Ccs as Comment" -msgstr "" +msgstr "Notificar Ccs como Comentarios" #: etc/initialdata:128 msgid "Notify Other Recipients" @@ -3611,15 +3611,15 @@ msgstr "Notificar al propietario como comentario" #: etc/initialdata:376 msgid "Notify Owner of their rejected ticket" -msgstr "" +msgstr "Notificar al Propietario del ticket su rechazo" #: etc/initialdata:365 msgid "Notify Owner of their ticket has been approved by all approvers" -msgstr "" +msgstr "Notificar al Propietario del ticket de la aprobación por todos los aprobadores" #: etc/initialdata:353 msgid "Notify Owner of their ticket has been approved by some approver" -msgstr "" +msgstr "Notificar al Propietario del ticket de la aprobación por algun aprobador" #: etc/initialdata:334 msgid "Notify Owners and AdminCcs of new items pending their approval" @@ -3655,7 +3655,7 @@ msgstr "Noviembre" #: html/Search/Elements/SelectAndOr:47 msgid "OR" -msgstr "" +msgstr "O" #: lib/RT/Record.pm:319 msgid "Object could not be created" @@ -3663,7 +3663,7 @@ msgstr "No se pudo crear el objeto" #: lib/RT/Record.pm:124 msgid "Object could not be deleted" -msgstr "" +msgstr "Objeto no pudo ser borrado" #: lib/RT/Record.pm:338 msgid "Object created" @@ -3671,13 +3671,13 @@ msgstr "Objeto creado" #: lib/RT/Record.pm:121 msgid "Object deleted" -msgstr "" +msgstr "Objeto borrado" #: html/Admin/CustomFields/Objects.html:72 html/Admin/Elements/ObjectCustomFields:63 #. ($ObjectType) #. ($LookupType) msgid "Object of type %1 cannot take custom fields" -msgstr "" +msgstr "Objeto de tipo %1 no puede tener campos personalizados" #: lib/RT/CustomField_Overlay.pm:901 msgid "Object type mismatch" @@ -3725,7 +3725,7 @@ msgstr "Al cambiar de propietario" #: etc/initialdata:177 etc/upgrade/3.1.17/content:15 msgid "On Priority Change" -msgstr "" +msgstr "Al cambiar de prioridad" #: etc/initialdata:192 msgid "On Queue Change" @@ -3741,7 +3741,7 @@ msgstr "Al cambiar de status" #: etc/initialdata:150 msgid "On Transaction" -msgstr "Al hacer transaccion" +msgstr "Al hacer transacción" #: html/Approvals/Elements/PendingMyApproval:71 #. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>") @@ -3767,7 +3767,7 @@ msgstr "Solicitudes abiertas" #: html/SelfService/Elements/Tabs:63 msgid "Open tickets" -msgstr "" +msgstr "Tickets abiertos" #: NOT FOUND IN SOURCE msgid "Open tickets (from listing) in a new window" @@ -3783,7 +3783,7 @@ msgstr "Open tickets on correspondence" #: html/Search/Elements/DisplayOptions:56 msgid "Order by" -msgstr "" +msgstr "Ordenar por" #: NOT FOUND IN SOURCE msgid "Ordering and sorting" @@ -3800,11 +3800,11 @@ msgstr "Ticket originario: #%1" #: lib/RT/Transaction_Overlay.pm:611 msgid "Outgoing email about a comment recorded" -msgstr "" +msgstr "Email saliente acerca de un comentario grabado" #: lib/RT/Transaction_Overlay.pm:615 msgid "Outgoing email recorded" -msgstr "" +msgstr "Email saliente grabado" #: html/Admin/Queues/Modify.html:90 msgid "Over time, priority moves toward" @@ -3828,7 +3828,7 @@ msgstr "Propietario cambiado de %1 a %2" #: lib/RT/Ticket_Overlay.pm:495 msgid "Owner could not be set." -msgstr "" +msgstr "Propietario no pudo ser fijado." #: lib/RT/Transaction_Overlay.pm:661 #. ($Old->Name , $New->Name) @@ -3842,7 +3842,7 @@ msgstr "El propietario es" #: html/Elements/TicketList:78 #. ($Page, int($TotalFound/$Rows)+$oddRows) msgid "Page %1 of %2" -msgstr "" +msgstr "Pagina %1 de %2" #: html/Admin/Users/Modify.html:198 html/User/Prefs.html:97 msgid "Pager" @@ -3871,11 +3871,11 @@ msgstr "Contraseña cambiado" #: lib/RT/User_Overlay.pm:1038 lib/RT/User_Overlay.pm:215 #. ($RT::MinimumPasswordLength) msgid "Password needs to be at least %1 characters long" -msgstr "" +msgstr "La contraseña debe ser de al menos %1 caracteres de longitud" #: lib/RT/User_Overlay.pm:1045 msgid "Password set" -msgstr "" +msgstr "Contraseña fijada" #: NOT FOUND IN SOURCE msgid "Password too short" @@ -3888,15 +3888,15 @@ msgstr "Contraseña: %1" #: lib/RT/User_Overlay.pm:1031 msgid "Password: Permission Denied" -msgstr "" +msgstr "Contraseña: Permiso Denegado" #: html/Admin/Users/Modify.html:356 msgid "Passwords do not match." -msgstr "" +msgstr "Las Contraseñas no coinciden" #: html/User/Prefs.html:234 msgid "Passwords do not match. Your password has not been changed" -msgstr "" +msgstr "Las contraseñas no coinciden. Tu contraseña no ha sido cambiada" #: html/Ticket/Elements/ShowSummary:66 html/Ticket/Elements/Tabs:119 html/Ticket/ModifyAll.html:72 msgid "People" @@ -3904,7 +3904,7 @@ msgstr "Personas" #: etc/initialdata:133 msgid "Perform a user-defined action" -msgstr "Realizar una acion definida por el usuario" +msgstr "Realizar una acción definida por el usuario" #: html/Admin/Tools/Configuration.html:94 msgid "Perl configuration" @@ -3952,7 +3952,7 @@ msgstr "Prev" #: html/Elements/TicketList:101 msgid "Previous Page" -msgstr "" +msgstr "Página Anterior" #: NOT FOUND IN SOURCE msgid "Previous page" @@ -3998,11 +3998,11 @@ msgstr "Pseudogrupo para uso interno" #: html/Search/Elements/EditQuery:47 msgid "Query" -msgstr "" +msgstr "Consulta" #: html/Search/Build.html:124 html/Ticket/Elements/Tabs:195 msgid "Query Builder" -msgstr "" +msgstr "Constructor de Consulta" #: html/Elements/QuickCreate:55 html/Elements/Quicksearch:50 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:59 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1345 msgid "Queue" @@ -4060,11 +4060,11 @@ msgstr "Colas" #: html/Elements/Quicksearch:46 msgid "Quick search" -msgstr "" +msgstr "Búsqueda rapida" #: html/Elements/QuickCreate:46 msgid "Quick ticket creation" -msgstr "" +msgstr "Creación rápida de ticket" #: html/Search/Results.html:83 msgid "RSS" @@ -4138,7 +4138,7 @@ msgstr "RT no te pudo autenticar." #: NOT FOUND IN SOURCE msgid "RT couldn't find requestor via its external database lookup" -msgstr "RT no pudo encontrar el solicitante a través de una busqueda a la base de datos externa" +msgstr "RT no pudo encontrar el solicitante a través de una búsqueda a la base de datos externa" #: NOT FOUND IN SOURCE msgid "RT couldn't find the queue: %1" @@ -4146,7 +4146,7 @@ msgstr "RT no pudo encontrar la cola: %1" #: html/Elements/SetupSessionCookie:90 msgid "RT couldn't store your session." -msgstr "" +msgstr "RT no pudo almacenar tu sesión." #: NOT FOUND IN SOURCE msgid "RT couldn't validate this PGP signature. \\n" @@ -4192,22 +4192,22 @@ msgstr "Nombre real" #: lib/RT/Transaction_Overlay.pm:714 #. ($value) msgid "Reference by %1 added" -msgstr "" +msgstr "Referencia para %1 añadida" #: lib/RT/Transaction_Overlay.pm:754 #. ($value) msgid "Reference by %1 deleted" -msgstr "" +msgstr "Referencia para %1 borrada" #: lib/RT/Transaction_Overlay.pm:711 #. ($value) msgid "Reference to %1 added" -msgstr "" +msgstr "Referencia para %1 añadida" #: lib/RT/Transaction_Overlay.pm:751 #. ($value) msgid "Reference to %1 deleted" -msgstr "" +msgstr "Referencia para %1 borrada" #: html/Elements/EditLinks:103 html/Elements/EditLinks:154 html/Elements/ShowLinks:92 html/Ticket/Create.html:216 html/Ticket/Elements/BulkLinks:72 msgid "Referred to by" @@ -4252,11 +4252,11 @@ msgstr "Responder" #: html/Admin/Queues/Modify.html:72 msgid "Reply Address" -msgstr "" +msgstr "Dirección de Respuesta" #: html/Search/Bulk.html:151 html/Ticket/ModifyAll.html:94 html/Ticket/Update.html:76 msgid "Reply to requestors" -msgstr "" +msgstr "Responder a solicitantes" #: lib/RT/Queue_Overlay.pm:111 msgid "Reply to tickets" @@ -4293,7 +4293,7 @@ msgstr "Las solicitudes entran en vencimiento en" #: lib/RT/Attribute_Overlay.pm:146 #. ('Object') msgid "Required parameter '%1' not specified" -msgstr "" +msgstr "Parametro requerido '%1' no especificado" #: html/Elements/Submit:104 msgid "Reset" @@ -4334,7 +4334,7 @@ msgstr "Confirmar contraseña" #: html/Search/Elements/EditSearches:61 msgid "Revert" -msgstr "" +msgstr "Revertir" #: NOT FOUND IN SOURCE msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n" @@ -4392,7 +4392,7 @@ msgstr "RootApproval" #: html/Search/Elements/DisplayOptions:83 msgid "Rows per page" -msgstr "" +msgstr "Filas por pagina" #: lib/RT/Date.pm:418 msgid "Sat." @@ -4400,7 +4400,7 @@ msgstr "Sab." #: html/Search/Elements/EditSearches:70 msgid "Save" -msgstr "" +msgstr "Grabar" #: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:88 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html:238 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:60 html/Ticket/ModifyPeople.html:59 html/User/Groups/Modify.html:77 msgid "Save Changes" @@ -4408,7 +4408,7 @@ msgstr "Guardar Cambios" #: html/User/Prefs.html:179 msgid "Save Preferences" -msgstr "" +msgstr "Guardar Preferencias" #: html/Ticket/Elements/PreviewScrips:124 msgid "Save changes" @@ -4417,11 +4417,11 @@ msgstr "Guardar cambios" #: lib/RT/SavedSearch.pm:162 #. ($name) msgid "Saved search %1" -msgstr "" +msgstr "Búsqueda grabada %1" #: html/Search/Elements/EditSearches:46 msgid "Saved searches" -msgstr "" +msgstr "Búsquedas guardadas" #: html/Admin/Elements/ListGlobalScrips:61 html/Admin/Global/Scrip.html:70 html/Admin/Queues/Scrip.html:76 #. ($scrip->Id) @@ -4481,19 +4481,19 @@ msgstr "Seguridad:" #: lib/RT/CustomField_Overlay.pm:100 msgid "See custom fields" -msgstr "" +msgstr "Ver campos personalizados" #: lib/RT/Queue_Overlay.pm:107 msgid "See exact outgoing email messages and their recipeients" -msgstr "" +msgstr "Ver mensajes email salientes exactos y sus receptores" #: lib/RT/Queue_Overlay.pm:105 msgid "See ticket private commentary" -msgstr "" +msgstr "Ver comentario privado del ticket" #: lib/RT/Queue_Overlay.pm:104 msgid "See ticket summaries" -msgstr "" +msgstr "Ver sumarios del ticket" #: lib/RT/CustomField_Overlay.pm:100 msgid "SeeCustomField" @@ -4509,7 +4509,7 @@ msgstr "Ver cola" #: html/Admin/CustomFields/index.html:46 html/Admin/CustomFields/index.html:49 msgid "Select a Custom Field" -msgstr "" +msgstr "Seleccionar un Campo Personalizado" #: html/Admin/Groups/index.html:78 msgid "Select a group" @@ -4521,7 +4521,7 @@ msgstr "Seleccione una cola" #: html/SelfService/CreateTicketInQueue.html:48 msgid "Select a queue for your new ticket" -msgstr "" +msgstr "Seleccionar una cola para su nuevo ticket" #: html/Admin/Users/index.html:46 html/Admin/Users/index.html:49 html/Admin/Users/index.html:52 msgid "Select a user" @@ -4533,19 +4533,19 @@ msgstr "Seleccionar un campo personalizable" #: html/Admin/Global/CustomFields/index.html:70 msgid "Select custom fields for all user groups" -msgstr "" +msgstr "Seleccionar campos personalizados para todos los grupos de usuarios" #: html/Admin/Global/CustomFields/index.html:65 msgid "Select custom fields for all users" -msgstr "" +msgstr "Seleccionar campos personalizados para todos los usuarios" #: html/Admin/Global/CustomFields/index.html:76 msgid "Select custom fields for tickets in all queues" -msgstr "" +msgstr "Seleccionar campos personalizados para tickets en todas las colas" #: html/Admin/Global/CustomFields/index.html:83 msgid "Select custom fields for transactions on tickets in all queues" -msgstr "" +msgstr "Seleccionar campos personalizados para transacciones de tickets en todas las colas" #: html/Admin/Elements/GroupTabs:75 html/User/Elements/GroupTabs:71 msgid "Select group" @@ -4573,7 +4573,7 @@ msgstr "Selecionar plantilla" #: lib/RT/CustomField_Overlay.pm:61 msgid "Select up to %1 values" -msgstr "" +msgstr "Seleccionar hasta %1 valores" #: html/Admin/Elements/UserTabs:75 msgid "Select user" @@ -4589,11 +4589,11 @@ msgstr "SelectSingle" #: html/Admin/Elements/EditCustomFields:58 msgid "Selected Custom Fields" -msgstr "" +msgstr "Campos Personalizados Seleccionados" #: html/Admin/CustomFields/Objects.html:59 msgid "Selected objects" -msgstr "" +msgstr "Objetos seleccionados" #: NOT FOUND IN SOURCE msgid "Self Service" @@ -4625,11 +4625,11 @@ msgstr "Enviar mail a los Ccs y Bccs listados explicitamente" #: etc/initialdata:94 etc/upgrade/3.1.17/content:7 msgid "Sends mail to the Ccs" -msgstr "" +msgstr "Envia mail a los Ccs" #: etc/initialdata:90 etc/upgrade/3.1.17/content:3 msgid "Sends mail to the Ccs as a comment" -msgstr "" +msgstr "Envia mail a los Ccs como comentario" #: etc/initialdata:102 msgid "Sends mail to the administrative Ccs" @@ -4653,15 +4653,15 @@ msgstr "Septiembre" #: html/Ticket/Elements/ShowTransaction:150 msgid "Show" -msgstr "" +msgstr "Mostrar" #: html/Approvals/index.html:52 msgid "Show Approvals" -msgstr "" +msgstr "Mostrar Aprobaciones" #: html/Search/Elements/EditFormat:56 msgid "Show Columns" -msgstr "" +msgstr "Mostrar Columnas" #: html/Ticket/Elements/Tabs:201 msgid "Show Results" @@ -4753,7 +4753,7 @@ msgstr "Sencillo" #: html/Search/Elements/EditFormat:75 msgid "Size" -msgstr "" +msgstr "Tamaño" #: html/Elements/Header:85 msgid "Skip Menu" @@ -4765,7 +4765,7 @@ msgstr "" #: html/Admin/Elements/AddCustomFieldValue:49 html/Admin/Elements/EditCustomFieldValues:54 msgid "Sort" -msgstr "" +msgstr "Ordenar" #: NOT FOUND IN SOURCE msgid "Sort key" @@ -4793,7 +4793,7 @@ msgstr "Página de inicio" #: html/Elements/SelectDateType:48 html/Ticket/Elements/EditDates:53 html/Ticket/Elements/ShowDates:56 msgid "Started" -msgstr "Empezado" +msgstr "Comenzado" #: NOT FOUND IN SOURCE msgid "Started date '%1' could not be parsed" @@ -4801,11 +4801,11 @@ msgstr "La fecha de inicio '%1' no se pudo leer" #: html/Elements/SelectDateType:52 html/Ticket/Create.html:196 html/Ticket/Elements/EditDates:48 html/Ticket/Elements/ShowDates:52 msgid "Starts" -msgstr "Empieza" +msgstr "Comienzo" #: NOT FOUND IN SOURCE msgid "Starts By" -msgstr "Empezado por" +msgstr "Comenzado por" #: NOT FOUND IN SOURCE msgid "Starts date '%1' could not be parsed" @@ -4837,7 +4837,7 @@ msgstr "Robar" #: lib/RT/Queue_Overlay.pm:118 msgid "Steal tickets" -msgstr "" +msgstr "Robar tickets" #: lib/RT/Queue_Overlay.pm:118 msgid "StealTicket" @@ -4850,7 +4850,7 @@ msgstr "Robado de %1" #: html/Search/Elements/EditFormat:81 msgid "Style" -msgstr "" +msgstr "Estilo" #: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1460 msgid "Subject" @@ -4887,7 +4887,7 @@ msgstr "Sistema" #: html/Admin/Elements/ToolTabs:54 html/Admin/Tools/Configuration.html:48 msgid "System Configuration" -msgstr "" +msgstr "Configuración del Sistema" #: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:585 lib/RT/Interface/Web.pm:900 lib/RT/Interface/Web.pm:929 msgid "System Error" @@ -4908,7 +4908,7 @@ msgstr "" #: html/Admin/Tools/index.html:47 msgid "System Tools" -msgstr "" +msgstr "Herramientas del Sistema" #: lib/RT/ACE_Overlay.pm:634 msgid "System error. Right not delegated." @@ -4940,7 +4940,7 @@ msgstr "Coger" #: lib/RT/Queue_Overlay.pm:116 msgid "Take tickets" -msgstr "" +msgstr "Coger tickets" #: lib/RT/Queue_Overlay.pm:116 msgid "TakeTicket" @@ -4997,7 +4997,7 @@ msgstr "Este es el mismo valor" #: lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:615 msgid "That principal already has that right" -msgstr "" +msgstr "Ese principal ya tiene ese derecho" #: lib/RT/Queue_Overlay.pm:750 #. ($args{'Type'}) @@ -5108,15 +5108,15 @@ msgstr "Estos comentarios generalmente no están visibles para el usuario" #: lib/RT/CustomField_Overlay.pm:912 msgid "This custom field does not apply to that object" -msgstr "" +msgstr "Este campo personalizado no se aplica a este objeto" #: html/Admin/Tools/Configuration.html:50 msgid "This feature is only available to system administrators" -msgstr "" +msgstr "Esa caracteristica solo esta disponible a administradores del sistema" #: html/Ticket/Elements/PreviewScrips:93 msgid "This message will be sent to..." -msgstr "" +msgstr "Este mensaje sera enviado a..." #: NOT FOUND IN SOURCE msgid "This ticket %1 %2 (%3)\\n" @@ -5149,7 +5149,7 @@ msgstr "Ticket # %1 %2" #: NOT FOUND IN SOURCE msgid "Ticket # %1 Jumbo update: %2" -msgstr "Actualizacion Jumbo para el ticket # %1: %2" +msgstr "Actualización Jumbo para el ticket # %1: %2" #: html/Ticket/ModifyAll.html:46 html/Ticket/ModifyAll.html:50 #. ($Ticket->Id, $Ticket->Subject) @@ -5184,7 +5184,7 @@ msgstr "Ticket %1: %2" #: html/Admin/Elements/QueueTabs:74 msgid "Ticket Custom Fields" -msgstr "" +msgstr "Campos Personalizados del Ticket" #: html/Ticket/History.html:46 html/Ticket/History.html:49 #. ($Ticket->Id, $Ticket->Subject) @@ -5201,7 +5201,7 @@ msgstr "Ticket resuelto" #: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1085 msgid "Ticket Transactions" -msgstr "" +msgstr "Transacciones del Ticket" #: NOT FOUND IN SOURCE msgid "Ticket attachment" @@ -5258,7 +5258,7 @@ msgstr "Observadores del ticket" #: lib/RT/Search/FromSQL.pm:83 #. (ref $self) msgid "TicketSQL search module" -msgstr "" +msgstr "Modulo de búsqueda TicketSQL" #: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:68 lib/RT/CustomField_Overlay.pm:1084 msgid "Tickets" @@ -5282,7 +5282,7 @@ msgstr "Tickets que dependen de esta aprobación:" #: html/Search/Elements/PickBasics:148 html/Ticket/Elements/EditBasics:61 msgid "Time Estimated" -msgstr "" +msgstr "Tiempo Estimado" #: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:187 html/Ticket/Elements/EditBasics:69 msgid "Time Left" @@ -5314,7 +5314,7 @@ msgstr "TimeWorked" #: html/Search/Elements/EditFormat:74 msgid "Title" -msgstr "" +msgstr "Titulo" #: NOT FOUND IN SOURCE msgid "To generate a diff of this commit:" @@ -5335,7 +5335,7 @@ msgstr "Última actualización" #: html/Admin/Elements/Tabs:68 html/Admin/index.html:88 html/Elements/Tabs:71 msgid "Tools" -msgstr "" +msgstr "Herramientas" #: etc/initialdata:252 msgid "Transaction" @@ -5352,15 +5352,15 @@ msgstr "Transacción creada" #: html/Admin/Elements/QueueTabs:78 msgid "Transaction Custom Fields" -msgstr "" +msgstr "Campos Personalizados de Transacción" #: NOT FOUND IN SOURCE msgid "Transaction->Create couldn't, as you didn't specify a ticket id" -msgstr "Transaction->Create no pudo, ya no no especificó un ID de ticket" +msgstr "Transaction->Create no pudo, ya que no especificó un ID de ticket" #: lib/RT/Transaction_Overlay.pm:125 msgid "Transaction->Create couldn't, as you didn't specify an object type and id" -msgstr "" +msgstr "Transaction->Create no pudo, ya que no especificó un tipo de objeto e ID" #: lib/RT/Transaction_Overlay.pm:838 msgid "Transactions are immutable" @@ -5398,7 +5398,7 @@ msgstr "Codificación de contenido desconocida: %1" #: html/Search/Build.html:632 msgid "Unknown field: $key" -msgstr "" +msgstr "Campo desconocido: $key" #: html/Elements/SelectResultsPerPage:58 msgid "Unlimited" @@ -5406,7 +5406,7 @@ msgstr "Ilimitado" #: html/Search/Elements/SelectSearchesForObjects:63 msgid "Unnamed search" -msgstr "" +msgstr "Búsqueda sin nombre" #: etc/initialdata:32 msgid "Unprivileged" @@ -5414,11 +5414,11 @@ msgstr "No privilegiado" #: html/Admin/Elements/EditCustomFields:60 msgid "Unselected Custom Fields" -msgstr "" +msgstr "Campos Personalizados No Seleccionados" #: html/Admin/CustomFields/Objects.html:61 msgid "Unselected objects" -msgstr "" +msgstr "Objetos No Seleccionados" #: lib/RT/Transaction_Overlay.pm:648 msgid "Untaken" @@ -5430,7 +5430,7 @@ msgstr "Actualizar" #: html/Search/Bulk.html:178 msgid "Update All" -msgstr "" +msgstr "Actualizar Todo" #: NOT FOUND IN SOURCE msgid "Update ID" @@ -5438,7 +5438,7 @@ msgstr "Id de actualización" #: html/Ticket/Update.html:133 msgid "Update Ticket" -msgstr "" +msgstr "Actualizar Ticket" #: html/Search/Bulk.html:148 html/Ticket/ModifyAll.html:87 html/Ticket/Update.html:70 msgid "Update Type" @@ -5454,7 +5454,7 @@ msgstr "Actualizar correo" #: html/Search/Results.html:80 msgid "Update multiple tickets" -msgstr "" +msgstr "Actualizar multiples tickets" #: NOT FOUND IN SOURCE msgid "Update name" @@ -5500,39 +5500,39 @@ msgstr "Actualizado" #: html/Tools/Offline.html:95 msgid "Upload" -msgstr "" +msgstr "Subir" #: lib/RT/CustomField_Overlay.pm:84 msgid "Upload multiple files" -msgstr "" +msgstr "Subir multiples ficheros" #: lib/RT/CustomField_Overlay.pm:79 msgid "Upload multiple images" -msgstr "" +msgstr "Subir multiples imagenes" #: lib/RT/CustomField_Overlay.pm:85 msgid "Upload one file" -msgstr "" +msgstr "Subir un fichero" #: lib/RT/CustomField_Overlay.pm:80 msgid "Upload one image" -msgstr "" +msgstr "Subir una imagen" #: lib/RT/CustomField_Overlay.pm:86 msgid "Upload up to %1 files" -msgstr "" +msgstr "Subir hasta %1 ficheros" #: lib/RT/CustomField_Overlay.pm:81 msgid "Upload up to %1 images" -msgstr "" +msgstr "Subir hasta %1 imagenes" #: html/Tools/Offline.html:95 msgid "Upload your changes" -msgstr "" +msgstr "Subir cambios" #: html/Admin/index.html:90 msgid "Use other RT administrative tools" -msgstr "" +msgstr "Usar otra herramienta administrativa RT" #: NOT FOUND IN SOURCE msgid "User %1 %2: %3\\n" @@ -5545,7 +5545,7 @@ msgstr "Usuario %1 Contraseña: %2\\n" #: lib/RT/Ticket_Overlay.pm:496 #. ($args{'Owner'}) msgid "User '%1' could not be found." -msgstr "" +msgstr "No se encontro el Usuario '%1'" #: NOT FOUND IN SOURCE msgid "User '%1' not found" @@ -5561,7 +5561,7 @@ msgstr "Definido por el usuario" #: html/Admin/Elements/EditScrip:98 msgid "User Defined conditions and actions" -msgstr "" +msgstr "Condiciones y acciones Definidas por el Usuario" #: NOT FOUND IN SOURCE msgid "User ID" @@ -5595,7 +5595,7 @@ msgstr "Grupos definidos por el usuario" #: lib/RT/User_Overlay.pm:593 lib/RT/User_Overlay.pm:613 msgid "User loaded" -msgstr "" +msgstr "Usuario cargado" #: NOT FOUND IN SOURCE msgid "User notified" @@ -5607,7 +5607,7 @@ msgstr "Vista de usuario" #: html/Admin/Groups/index.html:99 msgid "User-defined groups" -msgstr "" +msgstr "Grupos definidos por el usuario" #: html/Admin/Users/Modify.html:69 html/Elements/Login:73 html/Ticket/Elements/AddWatchers:56 msgid "Username" @@ -5623,7 +5623,7 @@ msgstr "Usuarios que concuerdan con los criterios de búsqueda" #: lib/RT/Tickets_Overlay_SQL.pm:523 msgid "Valid Query" -msgstr "" +msgstr "Consulta Valida" #: NOT FOUND IN SOURCE msgid "ValueOfQueue" @@ -5635,7 +5635,7 @@ msgstr "Valores" #: lib/RT/Queue_Overlay.pm:108 msgid "Watch" -msgstr "Observar" +msgstr "" #: lib/RT/Queue_Overlay.pm:109 msgid "WatchAsAdminCc" @@ -5671,7 +5671,7 @@ msgstr "Cuando un ticket se crea" #: etc/initialdata:418 msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval" -msgstr "Cuando una aprobacion de ticket se crea, notifica al propietario y AdminCC del item que espera su aprobación" +msgstr "Cuando una aprobación de ticket se crea, notifica al propietario y AdminCC del item que espera su aprobación" #: etc/initialdata:151 msgid "When anything happens" @@ -5687,7 +5687,7 @@ msgstr "Siempre que el propietario de un ticket cambie" #: etc/initialdata:178 etc/upgrade/3.1.17/content:16 msgid "Whenever a ticket's priority changes" -msgstr "" +msgstr "Siempre que la prioridad de un ticket cambie" #: etc/initialdata:193 msgid "Whenever a ticket's queue changes" @@ -5699,7 +5699,7 @@ msgstr "Siempre que el estado de un ticket cambie" #: etc/initialdata:207 msgid "Whenever a user-defined condition occurs" -msgstr "Siempre que ocurra una condicion definida por el usuario" +msgstr "Siempre que ocurra una condición definida por el usuario" #: etc/initialdata:164 msgid "Whenever comments come in" @@ -5776,7 +5776,7 @@ msgstr "Su petición ha sido aprobada por %1. Otras aprobaciones pueden estar pe #: etc/initialdata:540 msgid "Your request has been approved." -msgstr "Su peticion ha sido aprobada." +msgstr "Su petición ha sido aprobada." #: NOT FOUND IN SOURCE msgid "Your request was rejected" @@ -5800,11 +5800,11 @@ msgstr "[sin asunto]" #: lib/RT/System.pm:88 msgid "allow creation of saved searches" -msgstr "" +msgstr "permitir creación de búsquedas guardadas" #: lib/RT/System.pm:87 msgid "allow loading of saved searches" -msgstr "" +msgstr "permitir carga de búsquedas guardadas" #: html/User/Elements/DelegateRights:80 #. ($right->PrincipalObj->Object->SelfDescription) @@ -5813,11 +5813,11 @@ msgstr "como priviligiado para %1" #: html/Search/Elements/PickBasics:127 msgid "belongs to" -msgstr "" +msgstr "pertenece a" #: html/SelfService/Closed.html:49 msgid "closed" -msgstr "" +msgstr "cerrado" #: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:55 msgid "contains" @@ -5857,7 +5857,7 @@ msgstr "borrado" #: html/Search/Elements/PickBasics:128 msgid "does not belong to" -msgstr "" +msgstr "no pertenece a" #: html/Search/Elements/PickBasics:61 msgid "does not match" @@ -5873,23 +5873,23 @@ msgstr "igual a" #: html/Search/Build.html:387 msgid "error: can't move down" -msgstr "" +msgstr "error: no se puede mover abajo" #: html/Search/Build.html:409 msgid "error: can't move left" -msgstr "" +msgstr "error: no se puede mover a la izquierda" #: html/Search/Build.html:368 msgid "error: can't move up" -msgstr "" +msgstr "error: no se puede mover arriba" #: html/Search/Build.html:451 msgid "error: nothing to delete" -msgstr "" +msgstr "error: nada a borrar" #: html/Search/Build.html:373 html/Search/Build.html:392 html/Search/Build.html:414 html/Search/Build.html:443 msgid "error: nothing to move" -msgstr "" +msgstr "error: nada a mover" #: html/Search/Build.html:469 msgid "error: nothing to toggle" @@ -5958,7 +5958,7 @@ msgstr "nuevo" #: html/Admin/Elements/PickCustomFields:64 html/Admin/Elements/PickObjects:63 msgid "no name" -msgstr "" +msgstr "sin nombre" #: html/Admin/Elements/EditScrips:64 msgid "no value" @@ -6004,7 +6004,7 @@ msgstr "sec" #: lib/RT/System.pm:86 msgid "show Configuration tab" -msgstr "" +msgstr "mostrar etiqueta de Configuración" #: html/Search/Results.html:82 msgid "spreadsheet" diff --git a/rt/lib/RT/I18N/fi.po b/rt/lib/RT/I18N/fi.po index eaac6bfb0..cfe974b6d 100644 --- a/rt/lib/RT/I18N/fi.po +++ b/rt/lib/RT/I18N/fi.po @@ -2,11 +2,11 @@ # First Author: Janne Pirkkanen <jp@oppipoika.net>, Jul 2002 msgid "" msgstr "" -"Project-Id-Version: RT 2.1.x\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-07-08 17:41+0200\n" -"PO-Revision-Date: 2004-01-13 15:21+0200\n" +"PO-Revision-Date: 2005-10-03 13:45-0400\n" "Last-Translator: Tuukka Vainio <tuukka.vainio@utu.fi>\n" -"Language-Team: Finnish\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/rt/lib/RT/I18N/fr.po b/rt/lib/RT/I18N/fr.po index 8d1f3c3b2..8bbbe1003 100644 --- a/rt/lib/RT/I18N/fr.po +++ b/rt/lib/RT/I18N/fr.po @@ -1,14 +1,16 @@ +# translation of fr.po to +# jfenal <jfenal@free.fr>, 2005. msgid "" msgstr "" -"Project-Id-Version: fr\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2005-08-06 22:14+0200\n" +"PO-Revision-Date: 2005-10-28 01:54+0200\n" "Last-Translator: jfenal <jfenal@free.fr>\n" -"Language-Team: <fr@li.org>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: KBabel 1.9.1\n" +"X-Generator: KBabel 1.10.2\n" #: html/Approvals/Elements/Approve:48 html/Approvals/Elements/ShowDependency:71 html/SelfService/Display.html:46 html/Ticket/Display.html:47 html/Ticket/Display.html:51 #. ($Ticket->id, $Ticket->Subject) @@ -16,9 +18,9 @@ msgstr "" #. ($link->BaseObj->Id, $link->BaseObj->Subject) #. ($TicketObj->Id, $TicketObj->Subject) msgid "#%1: %2" -msgstr "n°%1: %2" +msgstr "n°%1 : %2" -#: lib/RT/Record.pm:927 +#: lib/RT/Record.pm:934 #. ($label) msgid "$prefix %1" msgstr "$prefix %1" @@ -38,7 +40,7 @@ msgstr "%1 %2" msgid "%1 %2 %3 %4:%5:%6 %7" msgstr "%1 %2 %3 %4:%5:%6 %7" -#: lib/RT/Record.pm:1672 lib/RT/Transaction_Overlay.pm:636 lib/RT/Transaction_Overlay.pm:679 +#: lib/RT/Record.pm:1678 lib/RT/Transaction_Overlay.pm:636 lib/RT/Transaction_Overlay.pm:679 #. ($cf->Name, $new_value->Content) #. ($field, $self->NewValue) #. ($self->Field, $principal->Object->Name) @@ -50,13 +52,13 @@ msgstr "%1 %2 ajouté" msgid "%1 %2 ago" msgstr "il y a %1 %2" -#: lib/RT/Record.pm:1679 lib/RT/Transaction_Overlay.pm:643 +#: lib/RT/Record.pm:1685 lib/RT/Transaction_Overlay.pm:643 #. ($cf->Name, $old_content, $new_value->Content) #. ($field, $self->OldValue, $self->NewValue) msgid "%1 %2 changed to %3" msgstr "%1 %2 changé en %3" -#: lib/RT/Record.pm:1676 lib/RT/Transaction_Overlay.pm:639 lib/RT/Transaction_Overlay.pm:685 +#: lib/RT/Record.pm:1682 lib/RT/Transaction_Overlay.pm:639 lib/RT/Transaction_Overlay.pm:685 #. ($cf->Name, $old_value->Content) #. ($field, $self->OldValue) #. ($self->Field, $principal->Object->Name) @@ -118,7 +120,7 @@ msgstr "%1 RT %2 Copyright 1996-%3 %4." msgid "%1 ScripAction loaded" msgstr "ScripAction %1 chargée" -#: lib/RT/Record.pm:1709 +#: lib/RT/Record.pm:1715 #. ($args{'Value'}, $cf->Name) msgid "%1 added as a value for %2" msgstr "%1 ajouté(e) comme valeur de %2" @@ -147,9 +149,9 @@ msgstr "%1 changé(e) de %2 à %3" msgid "%1 copy" msgstr "%1 copie" -#: lib/RT/Record.pm:931 +#: lib/RT/Record.pm:938 msgid "%1 could not be set to %2." -msgstr "%1 n'a pas pu être positionné à %2" +msgstr "%1 n'a pas pu être positionné à %2." #: lib/RT/Ticket_Overlay.pm:2751 #. ($self) @@ -226,12 +228,12 @@ msgstr "Recherches sauvées de %1" #: lib/RT/Transaction_Overlay.pm:470 #. ($self) msgid "%1: no attachment specified" -msgstr "%1: pas d'attachement spécifié" +msgstr "%1 : pas d'attachement spécifié" #: html/Ticket/Elements/ShowTransactionAttachments:78 #. ($size) msgid "%1b" -msgstr "%1b" +msgstr "%1o" #: html/Ticket/Elements/ShowTransactionAttachments:75 #. (int( $size / 102.4 ) / 10) @@ -303,7 +305,7 @@ msgstr "(Transmettre une copie de cette mise à jour à une liste d'adresses ema #: html/Admin/Elements/EditScrip:102 msgid "(Use these fields when you choose 'User Defined' for a condition or action)" -msgstr "(Utilisez ces champs lorsque vous sélectionnez 'User Defined' pour une condition ou une action)" +msgstr "(Utilisez ces champs lorsque vous sélectionnez « Défini par l'utilisateur » pour une condition ou une action)" #: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54 msgid "(empty)" @@ -313,7 +315,7 @@ msgstr "(vide)" msgid "(no name listed)" msgstr "(aucun nom)" -#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:60 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:65 lib/RT/I18N/fr.po.orig:146 lib/RT/Transaction_Overlay.pm:580 +#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:61 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:65 lib/RT/I18N/fr.po.mine:143 lib/RT/I18N/fr.po.orig:146 lib/RT/I18N/fr.po.r3834:141 lib/RT/I18N/fr.po.r3942:142 lib/RT/I18N/ru.po.orig:236 lib/RT/Transaction_Overlay.pm:580 msgid "(no value)" msgstr "(sans information)" @@ -325,11 +327,11 @@ msgstr "(aucune valeur)" msgid "(only one ticket)" msgstr "(un seul ticket)" -#: html/Elements/RT__Ticket/ColumnMap:146 +#: html/Elements/RT__Ticket/ColumnMap:147 msgid "(pending approval)" msgstr "(en attente d'approbation)" -#: html/Elements/RT__Ticket/ColumnMap:149 +#: html/Elements/RT__Ticket/ColumnMap:150 msgid "(pending other Collection)" msgstr "(en attente d'autres Collection)" @@ -578,17 +580,17 @@ msgstr "Approbations" #. ($ticket->id, $msg) #. ($link->BaseObj->Id, $link->BaseObj->Subject) msgid "Approval #%1: %2" -msgstr "Approbation n°%1: %2" +msgstr "Approbation n°%1 : %2" #: html/Approvals/index.html:75 #. ($ticket->Id) msgid "Approval #%1: Notes not recorded due to a system error" -msgstr "Approbation n°%1: Notes non enregistrées en raison d'une erreur système" +msgstr "Approbation n°%1 : Notes non enregistrées en raison d'une erreur système" #: html/Approvals/index.html:73 #. ($ticket->Id) msgid "Approval #%1: Notes recorded" -msgstr "Approbation n°%1: Notes non enregistrées" +msgstr "Approbation n°%1 : Notes non enregistrées" #: etc/initialdata:351 msgid "Approval Passed" @@ -604,7 +606,7 @@ msgstr "Approuver" #: etc/initialdata:504 msgid "Approver's notes: %1" -msgstr "Notes de l'approbateur : %1" +msgstr "Notes de l'approbateur : %1" #: lib/RT/Date.pm:440 msgid "Apr." @@ -643,7 +645,7 @@ msgstr "Attachement '%1' ne peut pas être chargé" msgid "Attachment created" msgstr "Attachement créé" -#: lib/RT/Tickets_Overlay.pm:1702 +#: lib/RT/Tickets_Overlay.pm:1773 msgid "Attachment filename" msgstr "Nom de fichier de l'attachement" @@ -724,7 +726,7 @@ msgstr "Impossible d'ajouter une valeur de champ personnalisé sans un nom" msgid "Can't find a collection class for '%1'" msgstr "Impossible de trouver une collection pour la classe '%1'" -#: html/Search/Build.html:761 +#: html/Search/Build.html:756 msgid "Can't find a saved search to work with" msgstr "Impossible de trouver une recherche sauvée et de l'utiliser" @@ -732,18 +734,18 @@ msgstr "Impossible de trouver une recherche sauvée et de l'utiliser" msgid "Can't link a ticket to itself" msgstr "Un ticket ne peut être lié à lui même" -#: html/Search/Build.html:766 +#: html/Search/Build.html:761 msgid "Can't save this search" msgstr "Impossible de sauver cette recherche" -#: lib/RT/Record.pm:1267 lib/RT/Record.pm:1345 +#: lib/RT/Record.pm:1274 lib/RT/Record.pm:1352 msgid "Can't specifiy both base and target" msgstr "Impossible de spécifier à la fois la base et la cible" #: html/autohandler:148 #. ($msg) msgid "Cannot create user: %1" -msgstr "Impossible de créer l'utilisateur: %1" +msgstr "Impossible de créer l'utilisateur : %1" #: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:85 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:81 lib/RT/ACE_Overlay.pm:113 msgid "Cc" @@ -861,11 +863,11 @@ msgstr "Courrier" msgid "Correspondence added" msgstr "Courrier ajouté" -#: lib/RT/Record.pm:1694 +#: lib/RT/Record.pm:1700 msgid "Could not add new custom field value. " msgstr "Impossible d'ajouter une nouvelle valeur de champ personnalisé. " -#: lib/RT/Record.pm:1647 +#: lib/RT/Record.pm:1653 #. (, $value_msg) msgid "Could not add new custom field value. %1 " msgstr "Impossible d'ajouter une nouvelle valeur de champ personnalisé. %1" @@ -882,7 +884,7 @@ msgstr "Impossible de créer le champ personnalisé" #: html/Admin/Elements/EditCustomField:113 #. ($msg) msgid "Could not create CustomField: %1" -msgstr "Impossible de créer le champ personnalisé : %1" +msgstr "Impossible de créer le champ personnalisé : %1" #: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:494 lib/RT/Group_Overlay.pm:501 msgid "Could not create group" @@ -891,7 +893,7 @@ msgstr "Impossible de créer un groupe" #: html/Admin/Global/Template.html:96 html/Admin/Queues/Template.html:93 #. ($msg) msgid "Could not create template: %1" -msgstr "Impossible de créer le modèle : %1" +msgstr "Impossible de créer le modèle : %1" #: lib/RT/Ticket_Overlay.pm:1051 lib/RT/Ticket_Overlay.pm:396 msgid "Could not create ticket. Queue not set" @@ -949,12 +951,12 @@ msgstr "Impossible de sauver les informations utilisateur" msgid "Couldn't add member to group" msgstr "Impossible d'ajouter un membre à ce groupe" -#: lib/RT/Record.pm:1706 lib/RT/Record.pm:1758 +#: lib/RT/Record.pm:1712 lib/RT/Record.pm:1764 #. ($Msg) msgid "Couldn't create a transaction: %1" -msgstr "Impossible de créer une transaction : %1" +msgstr "Impossible de créer une transaction : %1" -#: lib/RT/Record.pm:940 +#: lib/RT/Record.pm:947 msgid "Couldn't find row" msgstr "Colonne introuvable" @@ -1164,17 +1166,17 @@ msgstr "Programme de préparation d'action personnalisé " msgid "Custom condition" msgstr "Condition personnalisée" -#: lib/RT/Tickets_Overlay.pm:2176 +#: lib/RT/Tickets_Overlay.pm:2244 #. ($CF->Name) msgid "Custom field %1 has a value." msgstr "Le champ personnalisé %1 a une valeur." -#: lib/RT/Tickets_Overlay.pm:2172 +#: lib/RT/Tickets_Overlay.pm:2240 #. ($CF->Name) msgid "Custom field %1 has no value." msgstr "Le champ personnalisé %1 n'a pas de valeur." -#: lib/RT/Record.pm:1580 lib/RT/Record.pm:1741 +#: lib/RT/Record.pm:1586 lib/RT/Record.pm:1747 #. ($args{'Field'}) msgid "Custom field %1 not found" msgstr "Le champ personnalisé %1 est introuvable" @@ -1263,7 +1265,7 @@ msgstr "Détruire le modèle" #: lib/RT/SavedSearch.pm:211 #. ($msg) msgid "Delete failed: %1" -msgstr "Échec de la destruction : %1" +msgstr "Échec de la destruction : %1" #: html/Admin/Elements/EditScrips:74 msgid "Delete selected scrips" @@ -1451,7 +1453,7 @@ msgstr "Modifier les membres du groupe %1" msgid "Editing membership for personal group %1" msgstr "Modifier les membres du groupe personnel %1" -#: lib/RT/Record.pm:1282 lib/RT/Record.pm:1359 +#: lib/RT/Record.pm:1289 lib/RT/Record.pm:1366 msgid "Either base or target must be specified" msgstr "La base ou la cible doivent être spécifiées" @@ -1487,7 +1489,7 @@ msgstr "État %1 activé" #: html/Admin/CustomFields/Modify.html:143 html/Admin/Queues/Modify.html:162 #. (loc_fuzzy($msg)) msgid "Enabled status: %1" -msgstr "État actif : %1" +msgstr "État actif : %1" #: lib/RT/CustomField_Overlay.pm:64 msgid "Enter multiple values" @@ -1547,7 +1549,7 @@ msgstr "Tout le monde" #: bin/rt-crontool:219 msgid "Example:" -msgstr "Exemple :" +msgstr "Exemple : " #: html/Admin/Users/Modify.html:99 msgid "Extra info" @@ -1607,7 +1609,7 @@ msgstr "Saisir au plus %1 champs de type texte" msgid "Fill in up to %1 wikitext areas" msgstr "Saisir au plus %1 champs de type wiki" -#: html/Search/Elements/PickBasics:162 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:79 lib/RT/Tickets_Overlay.pm:1598 +#: html/Search/Elements/PickBasics:162 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:79 lib/RT/Tickets_Overlay.pm:1669 msgid "Final Priority" msgstr "Priorité finale" @@ -1637,7 +1639,7 @@ msgstr "Foo Bar Baz" #: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:757 msgid "Foo!" -msgstr "Foo !" +msgstr "Foo ! " #: html/Search/Bulk.html:105 msgid "Force change" @@ -1652,7 +1654,7 @@ msgstr "Format" msgid "Found %quant(%1,ticket)" msgstr "%quant(%1,ticket) trouvés" -#: lib/RT/Record.pm:943 +#: lib/RT/Record.pm:950 msgid "Found Object" msgstr "Objet trouvé" @@ -1666,7 +1668,7 @@ msgstr "En-têtes complets" #: html/Tools/Offline.html:87 msgid "Get template from file" -msgstr "Obtenir le modèle à partir d'un fichier" +msgstr "Obtenir le modèle à partir d'un fichier" #: lib/RT/Transaction_Overlay.pm:673 #. ($New->Name) @@ -1688,7 +1690,7 @@ msgstr "Configuration globale des champs personnalisés" #: html/Admin/Elements/SelectTemplate:59 #. (loc($Template->Name)) msgid "Global template: %1" -msgstr "Modèle global : %1" +msgstr "Modèle global : %1" #: html/Tools/Offline.html:91 msgid "Go" @@ -1696,7 +1698,7 @@ msgstr "Lancer" #: html/Admin/Groups/index.html:67 html/Admin/Groups/index.html:73 html/Admin/Queues/People.html:80 html/Admin/Queues/People.html:84 html/Admin/Queues/index.html:66 html/Admin/Users/index.html:73 html/Search/Results.html:76 html/Ticket/Elements/EditPeople:53 html/Ticket/Elements/EditPeople:57 html/index.html:91 msgid "Go!" -msgstr "Lancer !" +msgstr "Lancer !" #: html/Elements/GotoTicket:46 html/SelfService/Elements/GotoTicket:46 msgid "Goto ticket" @@ -1749,7 +1751,7 @@ msgstr "Cette utilisateur appartient aux groupes" #: lib/RT/Interface/CLI.pm:94 lib/RT/Interface/CLI.pm:94 msgid "Hello!" -msgstr "Bonjour !" +msgstr "Bonjour !" #: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:773 #. ($name) @@ -1779,11 +1781,11 @@ msgstr "Page d'accueil" msgid "I have %quant(%1,concrete mixer)." msgstr "J'ai %quant(%1, toupie à béton)." -#: html/Search/Build.html:637 +#: html/Search/Build.html:416 msgid "I'm lost" msgstr "Je suis perdu" -#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1523 +#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1594 msgid "Id" msgstr "Identifiant" @@ -1811,11 +1813,11 @@ msgstr "Si cet outil était setgid, un utilisateur local mal intentionné pourra msgid "If you've updated anything above, be sure to" msgstr "Si vous avez fait une modification, assurez vous de" -#: lib/RT/Record.pm:934 +#: lib/RT/Record.pm:941 msgid "Illegal value for %1" msgstr "Valeur incorrecte pour %1" -#: lib/RT/Record.pm:937 +#: lib/RT/Record.pm:944 msgid "Immutable field" msgstr "Champ non modifiable" @@ -1831,15 +1833,15 @@ msgstr "Afficher les files inactives." msgid "Include disabled users in search." msgstr "Inclure les utilisateurs désactivés dans le résultat." -#: html/Search/Build.html:663 +#: html/Search/Build.html:442 msgid "Incomplete Query" msgstr "Requête incomplète" -#: html/Search/Build.html:660 +#: html/Search/Build.html:439 msgid "Incomplete query" msgstr "Requête incomplète" -#: html/Search/Elements/PickBasics:161 lib/RT/Tickets_Overlay.pm:1573 +#: html/Search/Elements/PickBasics:161 lib/RT/Tickets_Overlay.pm:1644 msgid "Initial Priority" msgstr "Priorité initiale" @@ -1858,7 +1860,7 @@ msgstr "Erreur interne" #: lib/RT/Record.pm:305 #. ($id->{error_message}) msgid "Internal Error: %1" -msgstr "Erreur interne : %1" +msgstr "Erreur interne : %1" #: lib/RT/Group_Overlay.pm:668 msgid "Invalid Group Type" @@ -1868,7 +1870,7 @@ msgstr "Type de groupe invalide" msgid "Invalid Right" msgstr "Droit invalide" -#: lib/RT/Record.pm:939 +#: lib/RT/Record.pm:946 msgid "Invalid data" msgstr "Données invalides" @@ -1885,7 +1887,7 @@ msgstr "Droit invalide" msgid "Invalid value for %1" msgstr "File invalide pour %1" -#: lib/RT/Record.pm:1598 +#: lib/RT/Record.pm:1604 msgid "Invalid value for custom field" msgstr "Valeur incorrecte pour le champ personnalisé" @@ -1903,7 +1905,7 @@ msgstr "Il est suggéré de créer un utilisateur Unix non privilégié apparten #: bin/rt-crontool:188 msgid "It takes several arguments:" -msgstr "Il faut plusieurs paramètres :" +msgstr "Il faut plusieurs paramètres : " #: html/Search/Elements/EditFormat:85 msgid "Italic" @@ -1973,25 +1975,25 @@ msgstr "Autoriser cet utilisateur à recevoir des droits" msgid "Link" msgstr "Relation" -#: lib/RT/Record.pm:1293 +#: lib/RT/Record.pm:1300 msgid "Link already exists" msgstr "Le lien existe déja" -#: lib/RT/Record.pm:1307 +#: lib/RT/Record.pm:1314 msgid "Link could not be created" msgstr "Le lien ne peut être ajouté" -#: lib/RT/Record.pm:1313 +#: lib/RT/Record.pm:1320 #. ($TransString) msgid "Link created (%1)" msgstr "Le lien est ajouté (%1)" -#: lib/RT/Record.pm:1374 +#: lib/RT/Record.pm:1381 #. ($TransString) msgid "Link deleted (%1)" msgstr "Le lien est effacé (%1)" -#: lib/RT/Record.pm:1380 +#: lib/RT/Record.pm:1387 msgid "Link not found" msgstr "Lien introuvable" @@ -2010,7 +2012,7 @@ msgstr "Charger" #: html/Search/Elements/EditSearches:74 msgid "Load saved search:" -msgstr "Charger les recherches sauvées :" +msgstr "Charger les recherches sauvées :" #: lib/RT/System.pm:87 msgid "LoadSavedSearch" @@ -2209,13 +2211,13 @@ msgstr "Message sauvegardé" msgid "Messages about this ticket will not be sent to..." msgstr "Les messages relatifs à ce ticket ne seront pas envoyés à ..." -#: html/Search/Build.html:667 +#: html/Search/Build.html:446 msgid "Mismatched parentheses" msgstr "Parenthèses non correspondantes" -#: lib/RT/Record.pm:941 +#: lib/RT/Record.pm:948 msgid "Missing a primary key?: %1" -msgstr "Clé primaire manquante ? : %1" +msgstr "Clé primaire manquante ? : %1" #: html/Admin/Users/Modify.html:193 html/User/Prefs.html:93 msgid "Mobile" @@ -2590,7 +2592,7 @@ msgstr "Aucun champ personnalisé défini" msgid "No Group defined" msgstr "Aucun groupe défini" -#: lib/RT/Tickets_Overlay_SQL.pm:477 +#: lib/RT/Tickets_Overlay_SQL.pm:479 msgid "No Query" msgstr "Aucune requête" @@ -2610,7 +2612,7 @@ msgstr "Pas de modèle" msgid "No action" msgstr "Pas d'action" -#: lib/RT/Record.pm:936 +#: lib/RT/Record.pm:943 msgid "No column specified" msgstr "Aucune colonne spécifiée" @@ -2688,7 +2690,7 @@ msgstr "Pas de recherche chargée" msgid "No search to operate on." msgstr "Pas de critère de recherche." -#: html/Elements/RT__Ticket/ColumnMap:134 +#: html/Elements/RT__Ticket/ColumnMap:135 html/Search/Results.rdf:78 msgid "No subject" msgstr "Pas de sujet" @@ -2700,17 +2702,17 @@ msgstr "Aucun type de transaction spécifié" msgid "No users matching search criteria found." msgstr "Aucun utilisateur ne correspond aux critères de recherche." -#: lib/RT/Record.pm:933 +#: lib/RT/Record.pm:940 msgid "No value sent to _Set!\\n" -msgstr "Pas de valeur à positionner !\\n" +msgstr "Pas de valeur à positionner  \\n" #: html/Elements/QuickCreate:61 msgid "Nobody" msgstr "Personne" -#: lib/RT/Record.pm:938 +#: lib/RT/Record.pm:945 msgid "Nonexistant field?" -msgstr "Champ inexistant ?" +msgstr "Champ inexistant ? " #: html/Elements/Header:96 msgid "Not logged in." @@ -2927,7 +2929,7 @@ msgstr "Organisation" #: html/Approvals/Elements/Approve:54 #. ($approving->Id, $approving->Subject) msgid "Originating ticket: #%1" -msgstr "Ticket source: n°%1" +msgstr "Ticket source : n°%1" #: lib/RT/Transaction_Overlay.pm:611 msgid "Outgoing email about a comment recorded" @@ -2949,7 +2951,7 @@ msgstr "Tickets propres" msgid "OwnTicket" msgstr "PrendreTicket" -#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:101 html/SelfService/Elements/MyRequests:51 html/Ticket/Create.html:69 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:111 lib/RT/Tickets_Overlay.pm:1763 +#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:101 html/SelfService/Elements/MyRequests:51 html/Ticket/Create.html:69 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:111 lib/RT/Tickets_Overlay.pm:1834 msgid "Owner" msgstr "Intervenant" @@ -2999,11 +3001,11 @@ msgstr "Mot de passe défini" #: html/User/Prefs.html:232 #. (loc_fuzzy($msg)) msgid "Password: %1" -msgstr "Mot de passe : %1" +msgstr "Mot de passe : %1" #: lib/RT/User_Overlay.pm:1031 msgid "Password: Permission Denied" -msgstr "Mot de passe : permission refusée" +msgstr "Mot de passe : permission refusée" #: html/Admin/Users/Modify.html:356 msgid "Passwords do not match." @@ -3039,7 +3041,7 @@ msgstr "Groupes personnels" #: html/User/Elements/DelegateRights:58 msgid "Personal groups:" -msgstr "Groupes personnels:" +msgstr "Groupes personnels :" #: html/Admin/Users/Modify.html:180 html/User/Prefs.html:82 msgid "Phone numbers" @@ -3066,7 +3068,7 @@ msgstr "Page précédente" msgid "Principal %1 not found." msgstr "Personne/groupe %1 non trouvé." -#: html/Search/Elements/PickBasics:160 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:74 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1547 +#: html/Search/Elements/PickBasics:160 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:74 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1618 msgid "Priority" msgstr "Priorité" @@ -3076,7 +3078,7 @@ msgstr "La priorité débute à " #: html/Search/Elements/EditSearches:50 msgid "Privacy:" -msgstr "Privé:" +msgstr "Confidentialité :" #: etc/initialdata:25 msgid "Privileged" @@ -3085,7 +3087,7 @@ msgstr "Privilégié" #: html/Admin/Users/Modify.html:334 html/User/Prefs.html:223 #. (loc_fuzzy($msg)) msgid "Privileged status: %1" -msgstr "Statut privilégiés : %1" +msgstr "Statut privilégiés : %1" #: html/Admin/Users/index.html:102 msgid "Privileged users" @@ -3103,7 +3105,7 @@ msgstr "Requête" msgid "Query Builder" msgstr "Constructeur de requête" -#: html/Elements/QuickCreate:55 html/Elements/Quicksearch:50 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:59 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1374 +#: html/Elements/QuickCreate:55 html/Elements/Quicksearch:50 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:59 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1445 msgid "Queue" msgstr "File" @@ -3432,7 +3434,7 @@ msgstr "Mise à jour de la recherche %1" #: bin/rt-crontool:213 msgid "Security:" -msgstr "Sécurité :" +msgstr "Sécurité : " #: lib/RT/CustomField_Overlay.pm:100 msgid "See custom fields" @@ -3710,7 +3712,7 @@ msgstr "Débute" msgid "State" msgstr "Etat" -#: html/Search/Elements/PickBasics:87 html/SelfService/Elements/MyRequests:50 html/SelfService/Update.html:57 html/Ticket/Create.html:63 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1142 lib/RT/Tickets_Overlay.pm:1407 +#: html/Search/Elements/PickBasics:87 html/SelfService/Elements/MyRequests:50 html/SelfService/Update.html:57 html/Ticket/Create.html:63 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1142 lib/RT/Tickets_Overlay.pm:1479 msgid "Status" msgstr "Statut" @@ -3739,7 +3741,7 @@ msgstr "Volé à %1" msgid "Style" msgstr "Style" -#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1489 +#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1561 msgid "Subject" msgstr "Sujet" @@ -3779,7 +3781,7 @@ msgstr "Erreur système" #: lib/RT/Transaction_Overlay.pm:215 lib/RT/Transaction_Overlay.pm:221 #. ($msg) msgid "System Error: %1" -msgstr "Erreur système : %1" +msgstr "Erreur système : %1" #: html/Admin/Tools/index.html:47 msgid "System Tools" @@ -3846,7 +3848,7 @@ msgstr "Modèle analysé" msgid "Templates" msgstr "Modèles" -#: lib/RT/CustomField_Overlay.pm:877 lib/RT/Record.pm:932 +#: lib/RT/CustomField_Overlay.pm:877 lib/RT/Record.pm:939 msgid "That is already the current value" msgstr "Ceci est déjà la valeur actuelle" @@ -3931,9 +3933,9 @@ msgstr "L'AdminCC d'un ticket" #: bin/rt-crontool:223 msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:" -msgstr "Cette commande trouve tous les tickets actifs de la file 'general' et positionne leur priorité à 99 s'ils n'ont pas été touchés depuis quatre heures:" +msgstr "Cette commande trouve tous les tickets actifs de la file 'general' et positionne leur priorité à 99 s'ils n'ont pas été touchés depuis quatre heures :" -#: lib/RT/Record.pm:935 +#: lib/RT/Record.pm:942 msgid "The new value has been set." msgstr "La nouvelle valeur est enregistrée." @@ -3981,12 +3983,12 @@ msgstr "Jeu." #: html/Ticket/ModifyAll.html:46 html/Ticket/ModifyAll.html:50 #. ($Ticket->Id, $Ticket->Subject) msgid "Ticket #%1 Jumbo update: %2" -msgstr "Ticket n°%1 mise à jour globale : %2" +msgstr "Ticket n°%1 mise à jour globale : %2" #: html/Approvals/Elements/ShowDependency:67 #. ($link->BaseObj->Id, $link->BaseObj->Subject) msgid "Ticket #%1: %2" -msgstr "Ticket n°%1: %2" +msgstr "Ticket n°%1 : %2" #: lib/RT/Action/CreateTickets.pm:1258 lib/RT/Action/CreateTickets.pm:1267 lib/RT/Action/CreateTickets.pm:595 lib/RT/Action/CreateTickets.pm:716 lib/RT/Action/CreateTickets.pm:729 #. ($T::Tickets{$template_id}->Id) @@ -4003,7 +4005,7 @@ msgstr "Ticket %1 créé dans la file '%2'" #: html/Search/Bulk.html:269 #. ($Ticket->Id,$_) msgid "Ticket %1: %2" -msgstr "Ticket %1 : %2" +msgstr "Ticket %1 : %2" #: html/Admin/Elements/QueueTabs:74 msgid "Ticket Custom Fields" @@ -4022,11 +4024,11 @@ msgstr "Ticket résolu" msgid "Ticket Transactions" msgstr "Transactions du ticket" -#: lib/RT/Tickets_Overlay.pm:1677 +#: lib/RT/Tickets_Overlay.pm:1748 msgid "Ticket content" msgstr "Contenu du ticket" -#: lib/RT/Tickets_Overlay.pm:1726 +#: lib/RT/Tickets_Overlay.pm:1797 msgid "Ticket content type" msgstr "Type du contenu du ticket" @@ -4053,7 +4055,7 @@ msgstr "Tickets" #: html/Approvals/Elements/ShowDependency:48 msgid "Tickets which depend on this approval:" -msgstr "Tickets dépendant de cette approbation:" +msgstr "Tickets dépendant de cette approbation :" #: html/Search/Elements/PickBasics:148 html/Ticket/Elements/EditBasics:61 msgid "Time Estimated" @@ -4067,7 +4069,7 @@ msgstr "Temps restant" msgid "Time Worked" msgstr "Temps passé" -#: lib/RT/Tickets_Overlay.pm:1648 +#: lib/RT/Tickets_Overlay.pm:1719 msgid "Time left" msgstr "Temps restant" @@ -4075,7 +4077,7 @@ msgstr "Temps restant" msgid "Time to display" msgstr "Temps de calcul" -#: lib/RT/Tickets_Overlay.pm:1623 +#: lib/RT/Tickets_Overlay.pm:1694 msgid "Time worked" msgstr "Temps passé" @@ -4129,7 +4131,7 @@ msgstr "Les transactions ne peuvent être transférées" msgid "Tue." msgstr "Mar." -#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1144 lib/RT/Tickets_Overlay.pm:1461 +#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1144 lib/RT/Tickets_Overlay.pm:1533 msgid "Type" msgstr "Type" @@ -4141,15 +4143,15 @@ msgstr "Fonction non disponible" msgid "Unix login" msgstr "Identifiant Unix" -#: lib/RT/Attachment_Overlay.pm:290 lib/RT/Record.pm:848 +#: lib/RT/Attachment_Overlay.pm:290 lib/RT/Record.pm:855 #. ($self->ContentEncoding) #. ($ContentEncoding) msgid "Unknown ContentEncoding %1" -msgstr "Type d'encodage de courrier inconnu: %1" +msgstr "Type d'encodage de courrier inconnu : %1" -#: html/Search/Build.html:632 +#: html/Search/Build.html:411 msgid "Unknown field: $key" -msgstr "Champ inconnu : $key" +msgstr "Champ inconnu : $key" #: html/Elements/SelectResultsPerPage:58 msgid "Unlimited" @@ -4197,7 +4199,7 @@ msgstr "Mettre à jour plusieurs tickets" #: lib/RT/Action/CreateTickets.pm:737 lib/RT/Interface/Web.pm:524 msgid "Update not recorded." -msgstr "Mise à jour non enregistrée" +msgstr "Mise à jour non enregistrée." #: html/Search/Bulk.html:99 msgid "Update selected tickets" @@ -4264,15 +4266,15 @@ msgstr "Utiliser les autres outils d'administration de RT" #: lib/RT/Ticket_Overlay.pm:496 #. ($args{'Owner'}) msgid "User '%1' could not be found." -msgstr "L'utilisateur %1 ne peut être trouvé" +msgstr "L'utilisateur %1 est introuvable." #: etc/initialdata:132 etc/initialdata:206 msgid "User Defined" -msgstr "Utilisateur défini" +msgstr "Défini par l'utilisateur" #: html/Admin/Elements/EditScrip:98 msgid "User Defined conditions and actions" -msgstr "Conditions et actions définies par l'usager" +msgstr "Conditions et actions définies par l'utilisateur" #: html/Admin/Elements/CustomFieldTabs:72 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:85 html/Admin/Elements/SystemTabs:68 html/Admin/Global/index.html:80 msgid "User Rights" @@ -4286,7 +4288,7 @@ msgstr "L'utilisateur a demandé un type de mise à jour non connu pour le champ #: html/Admin/Users/Modify.html:293 #. ($msg) msgid "User could not be created: %1" -msgstr "Utilisateur ne peut pas être créé : %1" +msgstr "L'utilisateur ne peut pas être créé : %1" #: lib/RT/User_Overlay.pm:331 msgid "User created" @@ -4294,7 +4296,7 @@ msgstr "Utilisateur créé" #: html/Admin/CustomFields/GroupRights.html:74 html/Admin/Global/GroupRights.html:88 html/Admin/Groups/GroupRights.html:75 html/Admin/Queues/GroupRights.html:90 msgid "User defined groups" -msgstr "Groupes utilisateur" +msgstr "Groupes définis par l'utilisateur" #: lib/RT/User_Overlay.pm:593 lib/RT/User_Overlay.pm:613 msgid "User loaded" @@ -4316,7 +4318,7 @@ msgstr "Utilisateurs" msgid "Users matching search criteria" msgstr "Utilisateurs correspondants aux critères de recherche" -#: lib/RT/Tickets_Overlay_SQL.pm:523 +#: lib/RT/Tickets_Overlay_SQL.pm:525 msgid "Valid Query" msgstr "Valider la requête" @@ -4506,29 +4508,29 @@ msgstr "ne contient pas" msgid "equal to" msgstr "égal à " -#: html/Search/Build.html:387 +#: html/Search/Build.html:578 msgid "error: can't move down" -msgstr "erreur : ne peut aller plus bas" +msgstr "erreur : ne peut aller plus bas" -#: html/Search/Build.html:409 +#: html/Search/Build.html:600 msgid "error: can't move left" -msgstr "erreur : ne peut aller à gauche" +msgstr "erreur : ne peut aller à gauche" -#: html/Search/Build.html:368 +#: html/Search/Build.html:559 msgid "error: can't move up" -msgstr "erreur : ne peut aller plus haut" +msgstr "erreur : ne peut aller plus haut" -#: html/Search/Build.html:451 +#: html/Search/Build.html:642 msgid "error: nothing to delete" -msgstr "erreur : rien à effacer" +msgstr "erreur : rien à effacer" -#: html/Search/Build.html:373 html/Search/Build.html:392 html/Search/Build.html:414 html/Search/Build.html:443 +#: html/Search/Build.html:564 html/Search/Build.html:583 html/Search/Build.html:605 html/Search/Build.html:634 msgid "error: nothing to move" -msgstr "erreur : rien à déplacer" +msgstr "erreur : rien à déplacer" -#: html/Search/Build.html:469 +#: html/Search/Build.html:660 msgid "error: nothing to toggle" -msgstr "erreur : rien à commuter" +msgstr "erreur : rien à commuter" #: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59 msgid "greater than" @@ -4585,7 +4587,7 @@ msgstr "sans nom" #: html/Admin/Elements/EditScrips:64 msgid "no value" -msgstr "Non renseigné" +msgstr "non renseigné" #: html/Admin/Elements/EditQueueWatchers:48 html/Ticket/Elements/EditWatchers:49 msgid "none" @@ -4631,7 +4633,7 @@ msgstr "feuille de calcul" #: lib/RT/Queue_Overlay.pm:85 msgid "stalled" -msgstr "bloqué" +msgstr "stagnant" #: lib/RT/Group_Overlay.pm:222 #. ($self->Type) @@ -4655,7 +4657,7 @@ msgstr "ticket n°%1 %2" #: lib/RT/Group_Overlay.pm:236 #. ($self->Id) msgid "undescribed group %1" -msgstr "Groupe %1 non décrit" +msgstr "groupe %1 non décrit" #: lib/RT/Group_Overlay.pm:211 #. ($user->Object->Name) diff --git a/rt/lib/RT/I18N/he.po b/rt/lib/RT/I18N/he.po index b3c533868..f895f3549 100644 --- a/rt/lib/RT/I18N/he.po +++ b/rt/lib/RT/I18N/he.po @@ -1,7 +1,9 @@ -# Hebrew Translation of the RT interface by Shimi. -# Comments: shimi@shimi.net +# msgid "" msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: 2005-10-03 13:47-0400\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" diff --git a/rt/lib/RT/I18N/hu.po b/rt/lib/RT/I18N/hu.po index 857350841..3c199e082 100644 --- a/rt/lib/RT/I18N/hu.po +++ b/rt/lib/RT/I18N/hu.po @@ -1,11 +1,11 @@ +# msgid "" msgstr "" -"Project-Id-Version: RT 3.0.7\n" -"Report-Msgid-Bugs-To: \n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-06-22 06:06+0200\n" -"PO-Revision-Date: 2003-12-01 11:26+0100\n" +"PO-Revision-Date: 2005-10-03 13:48-0400\n" "Last-Translator: Attila K. Mergl <mergl@astron.hu>\n" -"Language-Team: Hungarian <hu@gnome.hu>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit" diff --git a/rt/lib/RT/I18N/id.po b/rt/lib/RT/I18N/id.po index f53284800..b02e15bbc 100644 --- a/rt/lib/RT/I18N/id.po +++ b/rt/lib/RT/I18N/id.po @@ -2,9 +2,9 @@ # msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2005-06-03\n" +"PO-Revision-Date: 2005-10-03 13:48-0400\n" "Last-Translator: James <james@actionmessage.com>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" diff --git a/rt/lib/RT/I18N/it.po b/rt/lib/RT/I18N/it.po index 95d8c18c1..1f23e188e 100644 --- a/rt/lib/RT/I18N/it.po +++ b/rt/lib/RT/I18N/it.po @@ -1,8 +1,9 @@ +# msgid "" msgstr "" -"Project-Id-Version: RT 3.4.1\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2005-02-12 02:00+0800\n" +"PO-Revision-Date: 2005-10-03 13:48-0400\n" "Last-Translator: Angelo Turetta <aturetta@bestunion.it>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" diff --git a/rt/lib/RT/I18N/ja.po b/rt/lib/RT/I18N/ja.po index 259f857c0..880346bb9 100644 --- a/rt/lib/RT/I18N/ja.po +++ b/rt/lib/RT/I18N/ja.po @@ -1,11 +1,9 @@ -# Japanese translation by Interactive Artists LLC -# 0.1 2002 08 15 msgid "" msgstr "" -"Project-Id-Version: RT 2.1.x\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2002-05-13 02:00+0800\n" -"Last-Translator: Jesse Vincent <jesse@bestpractical.com>\n" +"PO-Revision-Date: 2005-10-20 02:00+0800\n" +"Last-Translator: Daisuke Maki <daisuke@wafu.ne.jp>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -178,7 +176,7 @@ msgstr "" #. ($self->BriefDescription , $self->CreatorObj->Name) #. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) msgid "%1 by %2" -msgstr "%2ã«ã‚ˆã‚‹%1" +msgstr "%1 (%2)" #: lib/RT/Transaction_Overlay.pm:777 lib/RT/Transaction_Overlay.pm:786 lib/RT/Transaction_Overlay.pm:789 #. ($self->Field , $q1->Name , $q2->Name) @@ -198,12 +196,12 @@ msgstr "" #: NOT FOUND IN SOURCE msgid "%1 couldn't init a transaction (%2)\\n" -msgstr "%1ã¯ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ã‚’ã¯ã˜ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ(%2)\\n" +msgstr "%1ã¯ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ã‚’é–‹å§‹ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ(%2)\\n" #: lib/RT/Ticket_Overlay.pm:2743 #. ($self) msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent." -msgstr "%1ã¯åˆ†è§£ã™ã‚‹ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’è¨å®šã§ãã¾ã›ã‚“ã§ã—ãŸã€‚RTã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã«ä¸€è²«æ€§ãŒãªã„å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚" +msgstr "%1を解決状態ã«è¨å®šã§ãã¾ã›ã‚“ã§ã—ãŸã€‚RTã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã«ä¸€è²«æ€§ãŒãªã„å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚" #: lib/RT/Transaction_Overlay.pm:560 #. ($obj_type) @@ -218,7 +216,7 @@ msgstr "" #: html/Elements/MyTickets:47 #. ($rows) msgid "%1 highest priority tickets I own" -msgstr "" +msgstr "優先度ã®é«˜ã„ãƒã‚±ãƒƒãƒˆ%1ä»¶" #: bin/rt-crontool:186 #. ($0) @@ -254,7 +252,7 @@ msgstr "%1分" #: html/Elements/MyRequests:47 #. ($rows) msgid "%1 newest unowned tickets" -msgstr "" +msgstr "担当ã•れã¦ã„ãªã„ãƒã‚±ãƒƒãƒˆ%1ä»¶" #: NOT FOUND IN SOURCE msgid "%1 not shown" @@ -284,7 +282,7 @@ msgstr "%1タイプã¯%2ã§ã¯ä¸æ˜Žã§ã™" #: lib/RT/Action/ResolveMembers.pm:63 #. (ref $self) msgid "%1 will resolve all members of a resolved group ticket." -msgstr "%1ã¯åˆ†è§£ã•れãŸã‚°ãƒ«ãƒ¼ãƒ—ãƒã‚±ãƒƒãƒˆã®ã™ã¹ã¦ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’分解ã—ã¾ã™ã€‚" +msgstr "%1ã¯ã‚°ãƒ«ãƒ¼ãƒ—ãƒã‚±ãƒƒãƒˆã®ã™ã¹ã¦ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’解決状態ã«ã—ã¾ã™ã€‚" #: NOT FOUND IN SOURCE msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request." @@ -307,7 +305,7 @@ msgstr "" #: lib/RT/Transaction_Overlay.pm:470 #. ($self) msgid "%1: no attachment specified" -msgstr "%1:アタッãƒãƒ¡ãƒ³ãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“" +msgstr "%1:アタッãƒãƒ¡ãƒ³ãƒˆãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“" #: html/Ticket/Elements/ShowTransactionAttachments:78 #. ($size) @@ -436,20 +434,12 @@ msgstr "" #: html/Admin/Users/Modify.html:71 msgid "(required)" -msgstr "(必è¦ã§ã™ï¼‰" +msgstr "ï¼ˆå¿…é ˆé …ç›®ã§ã™ï¼‰" #: html/Ticket/Elements/ShowTransactionAttachments:82 msgid "(untitled)" msgstr "" -#: NOT FOUND IN SOURCE -msgid "25 highest priority tickets I own..." -msgstr "ç§ãŒæ‰€æœ‰ã—ã¦ã„ã‚‹25ã®æœ€ã‚‚é‡è¦ãªå„ªå…ˆæ¨©" - -#: NOT FOUND IN SOURCE -msgid "25 highest priority tickets I requested..." -msgstr "ç§ãŒãƒªã‚¯ã‚¨ã‚¹ãƒˆã—ãŸ25ã®æœ€ã‚‚é‡è¦ãªå„ªå…ˆæ¨©" - #: html/Ticket/Elements/ShowBasics:53 msgid "<% $Ticket->Status%>" msgstr "" @@ -469,7 +459,7 @@ msgstr "" #: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:47 lib/RT/StyleGuide.pod:787 #. ($m->scomp('/Elements/SelectNewTicketQueue')) msgid "<input type=\"submit\" value=\"New ticket in\"> %1" -msgstr "<input type=\"submit\" value=\"æ–°ã—ã„ãƒã‚±ãƒƒãƒˆ\"> %1" +msgstr "<input type=\"submit\" value=\"æ–°è¦ä½œæˆ\"> %1" #: etc/initialdata:218 msgid "A blank template" @@ -546,7 +536,7 @@ msgstr "" #: html/Search/Bulk.html:106 msgid "Add Requestor" -msgstr "リクエストã™ã‚‹äººã‚’ã‚’è¿½åŠ ã™ã‚‹" +msgstr "作æˆè€…ã‚’ã‚’è¿½åŠ ã™ã‚‹" #: html/Admin/Elements/AddCustomFieldValue:46 msgid "Add Value" @@ -676,7 +666,7 @@ msgstr "çµžè¾¼ã¿æ¤œç´¢" #: html/Elements/SelectDateRelation:57 msgid "After" -msgstr "後" +msgstr "ãŒä»¥ä¸‹ã‚ˆã‚Šå¾Œã§ã‚ã‚‹" #: html/Search/Elements/PickCriteria:52 msgid "Aggregator" @@ -745,7 +735,7 @@ msgstr "" #: lib/RT/Date.pm:440 msgid "Apr." -msgstr "四月" +msgstr "4月" #: html/Elements/SelectSortOrder:56 html/Search/Elements/DisplayOptions:73 msgid "Ascending" @@ -794,7 +784,7 @@ msgstr "" #: lib/RT/Date.pm:444 msgid "Aug." -msgstr "八月" +msgstr "8月" #: NOT FOUND IN SOURCE msgid "AuthSystem" @@ -838,7 +828,7 @@ msgstr "本当ã«å¤‰æ›´ã‚’ä¿å˜ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹" #: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:360 msgid "Before" -msgstr "å‰" +msgstr "ãŒä»¥ä¸‹ã‚ˆã‚Šå‰ã§ã‚ã‚‹" #: html/Elements/Header:80 msgid "Best Practical Solutions, LLC corporate logo" @@ -903,7 +893,7 @@ msgstr "" #: lib/RT/Record.pm:1266 lib/RT/Record.pm:1344 msgid "Can't specifiy both base and target" -msgstr "ベースã¨ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’特定ã§ãã¾ã›ã‚“" +msgstr "ベースã¨ã‚¿ãƒ¼ã‚²ãƒƒãƒˆä¸¡æ–¹ã‚’指定ã™ã‚‹äº‹ã¯ã§ãã¾ã›ã‚“" #: html/autohandler:148 #. ($msg) @@ -931,8 +921,9 @@ msgid "Check box to revoke right" msgstr "権利を無効ã«ã™ã‚‹ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹" #: html/Elements/EditLinks:146 html/Elements/EditLinks:85 html/Elements/ShowLinks:78 html/Ticket/Create.html:214 html/Ticket/Elements/BulkLinks:64 +# XXX - I can't come up with a good translation yet. leave as is in English msgid "Children" -msgstr "åä¾›" +msgstr "" #: html/Admin/Users/Modify.html:156 html/User/Prefs.html:141 msgid "City" @@ -944,7 +935,7 @@ msgstr "" #: html/Ticket/Elements/ShowDates:68 msgid "Closed" -msgstr "" +msgstr "解決日時" #: NOT FOUND IN SOURCE msgid "Closed requests" @@ -1081,7 +1072,7 @@ msgstr "" #: lib/RT/Ticket_Overlay.pm:3004 lib/RT/Ticket_Overlay.pm:3012 lib/RT/Ticket_Overlay.pm:3029 msgid "Could not change owner. " -msgstr "オーナー変更ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ" +msgstr "担当者変更ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ" #: html/Admin/CustomFields/Modify.html:119 #. ($msg) @@ -1393,7 +1384,7 @@ msgstr "" #: html/Elements/SelectDateType:47 html/Ticket/Elements/ShowDates:48 lib/RT/Ticket_Overlay.pm:1145 msgid "Created" -msgstr "作æˆã—ã¾ã—ãŸ" +msgstr "ä½œæˆæ—¥æ™‚" #: html/Admin/CustomFields/Modify.html:121 html/Admin/Elements/EditCustomField:117 #. ($CustomFieldObj->Name()) @@ -1503,7 +1494,7 @@ msgstr "日付" #: lib/RT/Date.pm:448 msgid "Dec." -msgstr "å二月" +msgstr "12月" #: etc/initialdata:222 msgid "Default Autoresponse template" @@ -1596,7 +1587,7 @@ msgstr "" #: html/Elements/EditLinks:138 html/Elements/EditLinks:66 html/Elements/ShowLinks:58 html/Ticket/Create.html:212 html/Ticket/Elements/BulkLinks:56 html/Ticket/Elements/ShowDependencies:53 msgid "Depended on by" -msgstr "次ã®ã‚‚ã®æ¬¡ç¬¬ã§ã‚ã‚‹" +msgstr "ä¾å˜ã•れã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ" #: NOT FOUND IN SOURCE msgid "Dependencies: \\n" @@ -1624,15 +1615,15 @@ msgstr "" #: html/Elements/EditLinks:134 html/Elements/EditLinks:57 html/Elements/SelectLinkType:48 html/Elements/ShowLinks:48 html/Ticket/Create.html:211 html/Ticket/Elements/BulkLinks:52 html/Ticket/Elements/ShowDependencies:46 msgid "Depends on" -msgstr "ã«ã‚ˆã‚‹" +msgstr "ä¾å˜ã—ã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ" #: html/Elements/SelectSortOrder:56 html/Search/Elements/DisplayOptions:78 msgid "Descending" -msgstr "é™é †ã™ã‚‹" +msgstr "é™é †" #: html/SelfService/Create.html:100 html/Ticket/Create.html:149 msgid "Describe the issue below" -msgstr "下ã®å•題点を表ã™" +msgstr "ãƒã‚±ãƒƒãƒˆã®æœ¬æ–‡ã‚’ä»¥ä¸‹ã«æ›¸ã込んã§ãã ã•ã„" #: html/Admin/CustomFields/Modify.html:61 html/Admin/Elements/AddCustomFieldValue:57 html/Admin/Elements/EditCustomField:60 html/Admin/Elements/EditCustomFieldValues:56 html/Admin/Elements/EditScrip:56 html/Admin/Elements/ModifyTemplate:57 html/Admin/Groups/Modify.html:71 html/Admin/Queues/Modify.html:69 html/Search/Elements/EditSearches:56 html/User/Groups/Modify.html:70 msgid "Description" @@ -1688,7 +1679,7 @@ msgstr "" #: html/Elements/Refresh:51 msgid "Don't refresh this page." -msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã‚’æ›´æ–°ã—ãªã„ã§ãã ã•ã„" +msgstr "ãƒšãƒ¼ã‚¸ã‚’å®šæœŸçš„ã«æ›´æ–°ã—ãªã„" #: html/Ticket/Elements/ShowTransactionAttachments:82 msgid "Download" @@ -1700,7 +1691,7 @@ msgstr "" #: html/Elements/SelectDateType:53 html/Ticket/Create.html:197 html/Ticket/Elements/EditDates:66 html/Ticket/Elements/ShowDates:64 lib/RT/Ticket_Overlay.pm:1149 msgid "Due" -msgstr "期é™åˆ‡ã‚Œ" +msgstr "終了予定日時" #: NOT FOUND IN SOURCE msgid "Due date '%1' could not be parsed" @@ -1794,7 +1785,7 @@ msgstr "テンプレート%1を編集ã™ã‚‹" #: lib/RT/Record.pm:1281 lib/RT/Record.pm:1358 msgid "Either base or target must be specified" -msgstr "ベースもã—ãã¯ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’特定ã—ãªã‘れã°ãªã‚Šã¾ã›ã‚“" +msgstr "ベースもã—ãã¯ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’指定ã—ãªã‘れã°ãªã‚Šã¾ã›ã‚“" #: html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65 msgid "Email" @@ -1942,7 +1933,7 @@ msgstr "" #: lib/RT/Date.pm:438 msgid "Feb." -msgstr "二月" +msgstr "2月" #: html/Elements/SelectAttachmentField:50 msgid "Filename" @@ -2138,7 +2129,7 @@ msgstr "グループãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。\\n" #: NOT FOUND IN SOURCE msgid "Group not specified.\\n" -msgstr "グループãŒç‰¹å®šã§ãã¾ã›ã‚“。\\n" +msgstr "ã‚°ãƒ«ãƒ¼ãƒ—ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。\\n" #: html/Admin/Elements/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:56 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:85 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1088 msgid "Groups" @@ -2163,11 +2154,11 @@ msgstr "ã“ã‚“ã«ã¡ã¯ï¼" #: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:773 #. ($name) msgid "Hello, %1" -msgstr "ã“ã‚“ã«ã¡ã¯ã€%1" +msgstr "ã“ã‚“ã«ã¡ã¯ã€%1ã•ã‚“" #: html/Admin/Elements/GroupTabs:70 html/Admin/Elements/UserTabs:64 html/Ticket/Elements/ShowHistory:51 html/Ticket/Elements/Tabs:111 msgid "History" -msgstr "ヒストリー" +msgstr "æ›´æ–°å±¥æ´" #: html/Admin/Groups/History.html:62 #. ($GroupObj->Name) @@ -2181,11 +2172,11 @@ msgstr "" #: NOT FOUND IN SOURCE msgid "HomePhone" -msgstr "自宅ã®é›»è©±" +msgstr "電話(自宅)" #: html/Elements/Tabs:65 msgid "Homepage" -msgstr "ホームページ" +msgstr "ホーム" #: lib/RT/Base.pm:110 #. (6) @@ -2206,7 +2197,7 @@ msgstr "ID" #: html/Admin/Users/Modify.html:65 html/User/Prefs.html:60 msgid "Identity" -msgstr "身分証明書" +msgstr "åŸºæœ¬æƒ…å ±" #: etc/initialdata:429 msgid "If an approval is rejected, reject the original and delete pending approvals" @@ -2226,7 +2217,7 @@ msgstr "" #: html/Admin/Queues/People.html:126 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:128 html/Ticket/ModifyPeople.html:59 msgid "If you've updated anything above, be sure to" -msgstr "上ã®ä½•ã‹ã‚’アップデートã—ãŸãªã‚‰ã€æ¬¡ã®ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„" +msgstr "å¤‰æ›´ã‚’åæ˜ ã™ã‚‹ã«ã¯ãƒœã‚¿ãƒ³ã‚’推ã—ã¦ãã ã•ã„" #: lib/RT/Record.pm:933 msgid "Illegal value for %1" @@ -2291,7 +2282,7 @@ msgstr "" #: NOT FOUND IN SOURCE msgid "Invalid owner. Defaulting to 'nobody'." -msgstr "無効ãªã‚ªãƒ¼ãƒŠãƒ¼ã§ã™ã€‚ '誰ã§ã‚‚ãªã„'ã«åˆæœŸè¨å®šã—ã¾ã™." +msgstr "ç„¡åŠ¹ãªæ‹…当者ã§ã™ã€‚ '誰ã§ã‚‚ãªã„'ã«åˆæœŸè¨å®šã—ã¾ã™." #: lib/RT/Scrip_Overlay.pm:158 lib/RT/Template_Overlay.pm:276 msgid "Invalid queue" @@ -2332,7 +2323,7 @@ msgstr "" #: lib/RT/Date.pm:437 msgid "Jan." -msgstr "一月" +msgstr "1月" #: lib/RT/Group_Overlay.pm:174 msgid "Join or leave this group" @@ -2340,15 +2331,15 @@ msgstr "" #: lib/RT/Date.pm:443 msgid "Jul." -msgstr "七月" +msgstr "7月" #: html/Ticket/Elements/Tabs:122 msgid "Jumbo" -msgstr "大ãã„" +msgstr "ç·åˆ" #: lib/RT/Date.pm:442 msgid "Jun." -msgstr "å…æœˆ" +msgstr "6月" #: NOT FOUND IN SOURCE msgid "Keyword" @@ -2372,7 +2363,7 @@ msgstr "最後ã®" #: html/Ticket/Elements/EditDates:59 html/Ticket/Elements/ShowDates:60 msgid "Last Contact" -msgstr "最後ã®ã‚³ãƒ³ã‚¿ã‚¯ãƒˆ" +msgstr "最終更新日時" #: html/Elements/SelectDateType:50 msgid "Last Contacted" @@ -2380,7 +2371,7 @@ msgstr "最後ã«ã‚³ãƒ³ã‚¿ã‚¯ãƒˆã—ãŸ" #: html/Elements/SelectDateType:51 msgid "Last Updated" -msgstr "最後ã«ã‚¢ãƒƒãƒ—デートã—ãŸ" +msgstr "最終更新日時" #: html/Search/Elements/PickBasics:103 msgid "LastUpdatedBy" @@ -2388,7 +2379,7 @@ msgstr "" #: html/Ticket/Elements/ShowBasics:68 msgid "Left" -msgstr "残ã£ãŸ" +msgstr "残り時間" #: html/Admin/Users/Modify.html:109 msgid "Let this user access RT" @@ -2400,7 +2391,7 @@ msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æ¨©åˆ©ã‚’èªã‚ã¾ã™" #: NOT FOUND IN SOURCE msgid "Limiting owner to %1 %2" -msgstr "オーナーを%1 %2ã«åˆ¶é™ã—ã¾ã™" +msgstr "担当者を%1 %2ã«åˆ¶é™ã—ã¾ã™" #: NOT FOUND IN SOURCE msgid "Limiting queue to %1 %2" @@ -2464,7 +2455,7 @@ msgstr "" #: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126 msgid "Location" -msgstr "å ´æ‰€" +msgstr "使‰€" #: lib/RT.pm:212 #. ($RT::LogDir) @@ -2474,7 +2465,7 @@ msgstr "ãƒã‚°ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãƒ¼%1ãŒè¦‹ã¤ã‹ã‚‰ãªã„ã€ã¾ãŸã¯æ›¸ã出ã #: html/Elements/Header:94 #. ("<b>".$session{'CurrentUser'}->Name."</b>") msgid "Logged in as %1" -msgstr "%1ã¨ã—ã¦ã‚µã‚¤ãƒ³ã™ã‚‹" +msgstr "\"%1\"ã§ãƒã‚°ã‚¤ãƒ³ã—ã¦ã„ã¾ã™" #: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:57 html/Elements/Login:66 html/Elements/Login:76 lib/RT/StyleGuide.pod:797 msgid "Login" @@ -2490,7 +2481,7 @@ msgstr "" #: html/Search/Bulk.html:104 msgid "Make Owner" -msgstr "オーナーを決ã‚ã‚‹" +msgstr "担当者を決ã‚ã‚‹" #: html/Search/Bulk.html:128 msgid "Make Status" @@ -2554,11 +2545,11 @@ msgstr "" #: lib/RT/Date.pm:439 msgid "Mar." -msgstr "三月" +msgstr "3月" #: lib/RT/Date.pm:441 msgid "May." -msgstr "五月" +msgstr "5月" #: lib/RT/Transaction_Overlay.pm:720 #. ($value) @@ -2916,7 +2907,7 @@ msgstr "多ãã®" #: lib/RT/User_Overlay.pm:227 msgid "Must specify 'Name' attribute" -msgstr "'åå‰'ã®å±žæ€§ã‚’特定ã—ã¦ãã ã•ã„" +msgstr "'åå‰'ã®å±žæ€§ã‚’指定ã—ã¦ãã ã•ã„" #: html/SelfService/Elements/MyRequests:70 #. ($friendly_status) @@ -3077,11 +3068,11 @@ msgstr "テンプレートãŒã‚りã¾ã›ã‚“" #: NOT FOUND IN SOURCE msgid "No Ticket specified. Aborting ticket " -msgstr "ãƒã‚±ãƒƒãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“。ãƒã‚±ãƒƒãƒˆã‚’終了ã—ã¾ã™" +msgstr "ãƒã‚±ãƒƒãƒˆãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。" #: NOT FOUND IN SOURCE msgid "No Ticket specified. Aborting ticket modifications\\n\\n" -msgstr "ãƒã‚±ãƒƒãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“。ãƒã‚±ãƒƒãƒˆã®ä¿®æ£ã‚’終了ã—ã¾ã™\\n\\n" +msgstr "ãƒã‚±ãƒƒãƒˆãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。ãƒã‚±ãƒƒãƒˆã®ä¿®æ£ã‚’終了ã—ã¾ã™\\n\\n" #: html/Approvals/Elements/Approve:67 msgid "No action" @@ -3110,7 +3101,7 @@ msgstr "%1記述ã¯ã‚りã¾ã›ã‚“" #: lib/RT/Users_Overlay.pm:185 msgid "No group specified" -msgstr "グループãŒç‰¹å®šã§ãã¾ã›ã‚“" +msgstr "ã‚°ãƒ«ãƒ¼ãƒ—ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“" #: html/Admin/Groups/index.html:52 msgid "No groups matching search criteria found." @@ -3147,7 +3138,7 @@ msgstr "アップデートãƒã‚±ãƒƒãƒˆã‚’見る許å¯ãŒã•ã‚りã¾ã›ã‚“" #: lib/RT/Queue_Overlay.pm:792 lib/RT/Ticket_Overlay.pm:1450 msgid "No principal specified" -msgstr "責任者ãŒç‰¹å®šã§ãã¾ã›ã‚“" +msgstr "è²¬ä»»è€…ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“" #: html/Admin/Queues/People.html:175 html/Admin/Queues/People.html:185 msgid "No principals selected." @@ -3179,11 +3170,11 @@ msgstr "" #: NOT FOUND IN SOURCE msgid "No ticket id specified" -msgstr "ãƒã‚±ãƒƒãƒˆIDãŒç‰¹å®šã§ãã¾ã›ã‚“" +msgstr "ãƒã‚±ãƒƒãƒˆIDãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“" #: lib/RT/Transaction_Overlay.pm:517 lib/RT/Transaction_Overlay.pm:554 msgid "No transaction type specified" -msgstr "トランザクションタイプãŒç‰¹å®šã§ãã¾ã›ã‚“" +msgstr "ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ã‚¿ã‚¤ãƒ—ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“" #: html/Admin/Users/index.html:55 msgid "No users matching search criteria found." @@ -3207,19 +3198,19 @@ msgstr "" #: html/Elements/Header:96 msgid "Not logged in." -msgstr "ãƒã‚°ã‚¤ãƒ³ã§ãã¾ã›ã‚“" +msgstr "ãƒã‚°ã‚¤ãƒ³ã—ã¦ã„ã¾ã›ã‚“" #: lib/RT/Date.pm:393 msgid "Not set" -msgstr "セットã§ãã¾ã›ã‚“" +msgstr "未指定" #: html/NoAuth/Reminder.html:48 msgid "Not yet implemented." -msgstr "ã¾ã 実行ã§ãã¾ã›ã‚“" +msgstr "未実装" #: NOT FOUND IN SOURCE msgid "Not yet implemented...." -msgstr "ã¾ã 実行ã§ãã¾ã›ã‚“。。。" +msgstr "未実装..." #: html/Approvals/Elements/Approve:70 msgid "Notes" @@ -3333,7 +3324,7 @@ msgstr "" #: lib/RT/Date.pm:446 msgid "Oct." -msgstr "åæœˆ" +msgstr "10月" #: html/Tools/Elements/Tabs:53 msgid "Offline" @@ -3349,7 +3340,7 @@ msgstr "" #: html/Elements/SelectDateRelation:56 msgid "On" -msgstr "ã«" +msgstr "ãŒä»¥ä¸‹ã§ã‚ã‚‹" #: etc/initialdata:163 msgid "On Comment" @@ -3399,7 +3390,7 @@ msgstr "" #: html/Elements/Quicksearch:52 msgid "Open" -msgstr "é–‹ã" +msgstr "ç€æ‰‹æ¸ˆã¿" #: html/Ticket/Elements/Tabs:159 msgid "Open it" @@ -3464,7 +3455,7 @@ msgstr "" #: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:101 html/SelfService/Elements/MyRequests:51 html/Ticket/Create.html:69 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:111 lib/RT/Tickets_Overlay.pm:1734 msgid "Owner" -msgstr "オーナー" +msgstr "担当者" #: lib/RT/Ticket_Overlay.pm:495 msgid "Owner could not be set." @@ -3473,11 +3464,11 @@ msgstr "" #: lib/RT/Transaction_Overlay.pm:661 #. ($Old->Name , $New->Name) msgid "Owner forcibly changed from %1 to %2" -msgstr "オーナーã¯å¼·åˆ¶çš„ã«%1ã‹ã‚‰%2を変更ã—ã¾ã—ãŸ" +msgstr "担当者ã¯å¼·åˆ¶çš„ã«%1ã‹ã‚‰%2を変更ã—ã¾ã—ãŸ" #: NOT FOUND IN SOURCE msgid "Owner is" -msgstr "オーナーã¯" +msgstr "担当者ã¯" #: html/Elements/TicketList:78 #. ($Page, int($TotalFound/$Rows)+$oddRows) @@ -3493,8 +3484,9 @@ msgid "PagerPhone" msgstr "ãƒã‚±ãƒƒãƒˆãƒ™ãƒ«é›»è©±" #: html/Elements/EditLinks:142 html/Elements/EditLinks:76 html/Elements/ShowLinks:68 html/Ticket/Create.html:213 html/Ticket/Elements/BulkLinks:60 +# XXX - I can't come up with a good translation yet. leave as is in English msgid "Parents" -msgstr "両親" +msgstr "" #: html/Elements/Login:74 html/User/Prefs.html:105 msgid "Password" @@ -3539,8 +3531,10 @@ msgid "Passwords do not match. Your password has not been changed" msgstr "" #: html/Ticket/Elements/ShowSummary:66 html/Ticket/Elements/Tabs:119 html/Ticket/ModifyAll.html:72 +# XXX - this is not really a good translation... but much better than +# "人々" (daisuke) msgid "People" -msgstr "人々" +msgstr "担当者ç‰" #: etc/initialdata:133 msgid "Perform a user-defined action" @@ -3600,7 +3594,7 @@ msgstr "å‰ã®ãƒšãƒ¼ã‚¸" #: NOT FOUND IN SOURCE msgid "Pri" -msgstr "優先権" +msgstr "優先度" #: lib/RT/ACE_Overlay.pm:158 lib/RT/ACE_Overlay.pm:240 lib/RT/ACE_Overlay.pm:570 #. ($args{'PrincipalId'}) @@ -3609,7 +3603,7 @@ msgstr "" #: html/Search/Elements/PickBasics:160 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:74 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1518 msgid "Priority" -msgstr "優先権" +msgstr "優先度" #: html/Admin/Queues/Modify.html:86 msgid "Priority starts at" @@ -3692,11 +3686,11 @@ msgstr "ã‚ュー" #: html/Elements/Quicksearch:46 msgid "Quick search" -msgstr "" +msgstr "簡易検索" #: html/Elements/QuickCreate:46 msgid "Quick ticket creation" -msgstr "" +msgstr "ãƒã‚±ãƒƒãƒˆæ–°è¦ä½œæˆ" #: html/Search/Results.html:83 msgid "RSS" @@ -3766,7 +3760,7 @@ msgstr "RTã§ã¯ã€ãŸã ã„ã¾ãŠä½¿ã„ã®æ–¹ã®èªè¨¼ãŒã§ãã¾ã›ã‚“ã§ã—ã #: NOT FOUND IN SOURCE msgid "RT couldn't find requestor via its external database lookup" -msgstr "RTã¯å¤–部ã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãƒ«ãƒƒã‚¯ã‚¢ãƒƒãƒ—を使ã£ã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã™ã‚‹äººã‚’見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ" +msgstr "RTã¯å¤–部ã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãƒ«ãƒƒã‚¯ã‚¢ãƒƒãƒ—を使ã£ã¦ä½œæˆè€…を見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ" #: NOT FOUND IN SOURCE msgid "RT couldn't find the queue: %1" @@ -3835,11 +3829,11 @@ msgstr "" #: html/Elements/EditLinks:103 html/Elements/EditLinks:154 html/Elements/ShowLinks:92 html/Ticket/Create.html:216 html/Ticket/Elements/BulkLinks:72 msgid "Referred to by" -msgstr "次ã®ã‚‚ã®ã«ã‚ˆã£ã¦å‚ç…§ã—ãŸ" +msgstr "å‚ç…§ã•れã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ" #: html/Elements/EditLinks:150 html/Elements/EditLinks:94 html/Elements/SelectLinkType:49 html/Elements/ShowLinks:82 html/Ticket/Create.html:215 html/Ticket/Elements/BulkLinks:68 msgid "Refers to" -msgstr "å‚ç…§ã™ã‚‹" +msgstr "å‚ç…§ã—ã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆ" #: NOT FOUND IN SOURCE msgid "Refine" @@ -3852,7 +3846,7 @@ msgstr "çµžè¾¼ã¿æ¤œç´¢" #: html/Elements/Refresh:57 #. ($value/60) msgid "Refresh this page every %1 minutes." -msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã‚’%1分ãŠãã«æ›´æ–°ã—ã¦ãã ã•ã„" +msgstr "ページを%1分ãŠãã«æ›´æ–°ã™ã‚‹" #: html/Search/Bulk.html:116 msgid "Remove AdminCc" @@ -3864,7 +3858,7 @@ msgstr "Ccを削除ã™ã‚‹" #: html/Search/Bulk.html:108 msgid "Remove Requestor" -msgstr "リクエストã™ã‚‹äººã‚’削除ã™ã‚‹" +msgstr "作æˆè€…を削除ã™ã‚‹" #: html/Ticket/Elements/ShowTransaction:171 html/Ticket/Elements/Tabs:145 msgid "Reply" @@ -3872,7 +3866,7 @@ msgstr "返信" #: html/Admin/Queues/Modify.html:72 msgid "Reply Address" -msgstr "" +msgstr "返信アドレス" #: html/Search/Bulk.html:151 html/Ticket/ModifyAll.html:94 html/Ticket/Update.html:76 msgid "Reply to requestors" @@ -3888,15 +3882,15 @@ msgstr "" #: etc/initialdata:44 lib/RT/ACE_Overlay.pm:112 msgid "Requestor" -msgstr "リクエストã™ã‚‹äºº" +msgstr "作æˆè€…" #: NOT FOUND IN SOURCE msgid "Requestor email address" -msgstr "リクエストã™ã‚‹äººã®Eメールアドレス" +msgstr "作æˆè€…ã®Eメールアドレス" #: html/SelfService/Create.html:63 html/Ticket/Create.html:77 html/Ticket/Elements/EditPeople:69 html/Ticket/Elements/ShowPeople:52 msgid "Requestors" -msgstr "リクエストã™ã‚‹äºº" +msgstr "作æˆè€…" #: html/Admin/Queues/Modify.html:96 msgid "Requests should be due in" @@ -3917,7 +3911,7 @@ msgstr "使‰€" #: html/Ticket/Elements/Tabs:155 msgid "Resolve" -msgstr "分解ã™ã‚‹" +msgstr "解決済ã¿ã«ã™ã‚‹" #: html/Ticket/Update.html:154 #. ($TicketObj->id, $TicketObj->Subject) @@ -3926,11 +3920,11 @@ msgstr "" #: etc/initialdata:323 html/Elements/SelectDateType:49 lib/RT/Ticket_Overlay.pm:1148 msgid "Resolved" -msgstr "分解ã—ãŸ" +msgstr "解決済ã¿" #: NOT FOUND IN SOURCE msgid "Response to requestors" -msgstr "リクエストã™ã‚‹äººã«è¿”ç”ã™ã‚‹" +msgstr "作æˆè€…ã«è¿”ç”ã™ã‚‹" #: html/Elements/ListActions:47 html/Search/Elements/NewListActions:47 msgid "Results" @@ -4016,7 +4010,7 @@ msgstr "変更をä¿å˜ã™ã‚‹" #: html/User/Prefs.html:179 msgid "Save Preferences" -msgstr "" +msgstr "変更をä¿å˜ã™ã‚‹" #: html/Ticket/Elements/PreviewScrips:124 msgid "Save changes" @@ -4245,7 +4239,7 @@ msgstr "" #: lib/RT/Date.pm:445 msgid "Sep." -msgstr "乿œˆ" +msgstr "9月" #: html/Ticket/Elements/ShowTransaction:150 msgid "Show" @@ -4385,7 +4379,7 @@ msgstr "é–‹å§‹æ—¥'%1'ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ" #: html/Elements/SelectDateType:52 html/Ticket/Create.html:196 html/Ticket/Elements/EditDates:48 html/Ticket/Elements/ShowDates:52 msgid "Starts" -msgstr "é–‹å§‹ã™ã‚‹" +msgstr "開始予定日時" #: NOT FOUND IN SOURCE msgid "Starts By" @@ -4397,11 +4391,11 @@ msgstr "é–‹å§‹æ—¥'%1'ã‚’è§£æžã§ãã¾ã›ã‚“ã§ã—ãŸ" #: html/Admin/Users/Modify.html:162 html/User/Prefs.html:145 msgid "State" -msgstr "状態" +msgstr "都é“府県" #: html/Search/Elements/PickBasics:87 html/SelfService/Elements/MyRequests:50 html/SelfService/Update.html:57 html/Ticket/Create.html:63 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1142 lib/RT/Tickets_Overlay.pm:1378 msgid "Status" -msgstr "ステータス" +msgstr "状態" #: etc/initialdata:309 msgid "Status Change" @@ -4413,7 +4407,7 @@ msgstr "ステータスãŒ%1ã‹ã‚‰%2ã«å¤‰æ›´ã•れã¾ã—ãŸ" #: html/Ticket/Elements/Tabs:170 msgid "Steal" -msgstr "盗用ã™ã‚‹" +msgstr "担当者変更" #: lib/RT/Queue_Overlay.pm:118 msgid "Steal tickets" @@ -4426,7 +4420,7 @@ msgstr "" #: lib/RT/Transaction_Overlay.pm:667 #. ($Old->Name) msgid "Stolen from %1" -msgstr "%1ã‹ã‚‰ç›—用ã—ãŸ" +msgstr "%1ã‹ã‚‰æ‹…当者を変更ã—ã¾ã—ãŸ" #: NOT FOUND IN SOURCE msgid "Stolen from %1 " @@ -4438,7 +4432,7 @@ msgstr "" #: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1460 msgid "Subject" -msgstr "サブジェクト" +msgstr "ä»¶å" #: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm:689 #. ($self->Data) @@ -4503,8 +4497,9 @@ msgid "TEST_STRING" msgstr "テスト_ストリング" #: html/Elements/MyRequests:50 html/Search/Elements/EditFormat:72 html/Ticket/Elements/Tabs:166 +# XXX - what the... msgid "Take" -msgstr "ã¨ã‚‹" +msgstr "担当ã™ã‚‹" #: lib/RT/Queue_Overlay.pm:116 msgid "Take tickets" @@ -4592,7 +4587,7 @@ msgstr "ãã®ã‚ューã¯ã‚りã¾ã›ã‚“" #: lib/RT/Ticket_Overlay.pm:3189 msgid "That ticket has unresolved dependencies" -msgstr "ãã®ãƒã‚±ãƒƒãƒˆã¯å¾“属物をã™ã§ã«åˆ†è§£ã—ã¾ã—ãŸ" +msgstr "未解決ã®ä¾å˜é–¢ä¿‚ãŒå˜åœ¨ã—ã¾ã™" #: NOT FOUND IN SOURCE msgid "That user already has that right" @@ -4714,7 +4709,7 @@ msgstr "ãƒã‚±ãƒƒãƒˆ# %1 %2" #: html/Ticket/ModifyAll.html:46 html/Ticket/ModifyAll.html:50 #. ($Ticket->Id, $Ticket->Subject) msgid "Ticket #%1 Jumbo update: %2" -msgstr "ãƒã‚±ãƒƒãƒˆã€€#%1 大ãã„アップデート: %2" +msgstr "ãƒã‚±ãƒƒãƒˆã€€#%1 更新(ç·åˆãƒ“ュー): %2" #: html/Approvals/Elements/ShowDependency:67 #. ($link->BaseObj->Id, $link->BaseObj->Subject) @@ -4793,7 +4788,7 @@ msgstr "ãƒã‚±ãƒƒãƒˆãŒå‰Šé™¤ã•れã¾ã—ãŸ" #: html/Ticket/Display.html:55 msgid "Ticket metadata" -msgstr "" +msgstr "ãƒã‚±ãƒƒãƒˆæƒ…å ±" #: etc/initialdata:310 msgid "Ticket status changed" @@ -4826,15 +4821,15 @@ msgstr "" #: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:187 html/Ticket/Elements/EditBasics:69 msgid "Time Left" -msgstr "æ™‚é–“ãŒæ®‹ã£ã¦ã„ã¾ã™" +msgstr "残り時間" #: html/Search/Elements/PickBasics:147 html/Ticket/Create.html:186 html/Ticket/Elements/EditBasics:65 msgid "Time Worked" -msgstr "使ã£ãŸæ™‚é–“" +msgstr "ä½œæ¥æ™‚é–“" #: lib/RT/Tickets_Overlay.pm:1619 msgid "Time left" -msgstr "残ã£ã¦ã„る時間" +msgstr "残り時間" #: html/Elements/Footer:71 msgid "Time to display" @@ -5028,7 +5023,7 @@ msgstr "アップデートタイプã¯é€šçŸ¥ã§ã‚‚コメントã§ã‚‚ã‚りã¾ã› #: html/Elements/SelectDateType:54 html/Ticket/Elements/ShowDates:72 lib/RT/Ticket_Overlay.pm:1147 msgid "Updated" -msgstr "アップデートã—ã¾ã—ãŸ" +msgstr "最終更新日時" #: html/Tools/Offline.html:95 msgid "Upload" @@ -5247,23 +5242,23 @@ msgstr "" #: NOT FOUND IN SOURCE msgid "WorkPhone" -msgstr "仕事先ã®é›»è©±" +msgstr "電話(仕事)" #: html/Ticket/Elements/ShowBasics:63 html/Ticket/Update.html:64 msgid "Worked" -msgstr "Worked" +msgstr "ç´¯ç©ä½œæ¥æ™‚é–“" #: lib/RT/Ticket_Overlay.pm:3096 msgid "You already own this ticket" -msgstr "ã‚ãªãŸã¯ã™ã§ã«ã“ã®ãƒã‚±ãƒƒãƒˆã‚’所有ã—ã¦ã„ã¾ã™" +msgstr "ã™ã§ã«ã“ã®ãƒã‚±ãƒƒãƒˆã‚’担当ã—ã¦ã„ã¾ã™" #: html/autohandler:158 html/autohandler:166 msgid "You are not an authorized user" -msgstr "ã‚ãªãŸã¯èªè¨¼ã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚りã¾ã›ã‚“" +msgstr "èªè¨¼ã•れã¦ã„ã¾ã›ã‚“。" #: lib/RT/Ticket_Overlay.pm:2978 msgid "You can only reassign tickets that you own or that are unowned" -msgstr "ã‚ãªãŸã¯æ‰€æœ‰ã€ã¾ãŸã¯æ‰€æœ‰ã•れã¦ã„ãªã„ãƒã‚±ãƒƒãƒˆã®ã¿ã‚’æ¢ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™" +msgstr "è‡ªåˆ†ãŒæ‹…当ã—ã¦ã„ã‚‹ãƒã‚±ãƒƒãƒˆã‹ã€æ‹…当者ã®ã„ãªã„ãƒã‚±ãƒƒãƒˆã—ã‹å¤‰æ›´ã™ã‚‹äº‹ãŒã§ãã¾ã›ã‚“。" #: NOT FOUND IN SOURCE msgid "You don't have permission to view that ticket.\\n" @@ -5272,23 +5267,23 @@ msgstr "ã‚ãªãŸã¯ãã®ãƒã‚±ãƒƒãƒˆã‚’見る許å¯ãŒã‚りã¾ã›ã‚“。\\n" #: docs/design_docs/string-extraction-guide.txt:47 lib/RT/StyleGuide.pod:780 #. ($num, $queue) msgid "You found %1 tickets in queue %2" -msgstr "ã‚ãªãŸã¯%2ã§ãƒã‚±ãƒƒãƒˆ%1を見ã¤ã‘ã¾ã—ãŸ" +msgstr "ã‚ュー%2ã§%1ä»¶ã®ãƒã‚±ãƒƒãƒˆãŒãƒ’ットã—ã¾ã—ãŸ" #: html/NoAuth/Logout.html:52 msgid "You have been logged out of RT." -msgstr "ã‚ãªãŸã¯RTã‹ã‚‰ãƒã‚°ã‚¢ã‚¦ãƒˆã—ãŸã¾ã¾ã§ã™" +msgstr "RTã‹ã‚‰ãƒã‚°ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸ" #: html/SelfService/Display.html:109 msgid "You have no permission to create tickets in that queue." -msgstr "ã‚ãªãŸã¯ã“ã®ã‚ューã§ãƒã‚±ãƒƒãƒˆä½œæˆã®è¨±å¯ãŒã‚りã¾ã›ã‚“" +msgstr "指定ã®ã®ã‚ューã§ãƒã‚±ãƒƒãƒˆä½œæˆã®è¨±å¯ãŒã‚りã¾ã›ã‚“" #: lib/RT/Ticket_Overlay.pm:1964 msgid "You may not create requests in that queue." -msgstr "ã‚ãªãŸã¯ã“ã®ã‚ューã§ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ä½œæˆãŒã§ãã‚‹ã§ã—ょã†" +msgstr "指定ã®ã‚ューã§ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ä½œæˆãŒã§ãã¾ã›ã‚“" #: html/NoAuth/Logout.html:56 msgid "You're welcome to login again" -msgstr "ãœã²ã¾ãŸãƒã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„" +msgstr "トップページ" #: NOT FOUND IN SOURCE msgid "Your %1 requests" @@ -5341,7 +5336,7 @@ msgstr "" #: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:55 msgid "contains" -msgstr "å«ã‚€" +msgstr "ãŒä»¥ä¸‹ã‚’å«ã‚€" #: NOT FOUND IN SOURCE msgid "correspondence (probably) not sent" @@ -5361,7 +5356,7 @@ msgstr "削除" #: lib/RT/Queue_Overlay.pm:88 msgid "deleted" -msgstr "削除ã•れãŸ" +msgstr "削除" #: html/Search/Elements/PickBasics:128 msgid "does not belong to" @@ -5373,7 +5368,7 @@ msgstr "ã‚ã„ã¾ã›ã‚“" #: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:56 msgid "doesn't contain" -msgstr "å«ã¿ã¾ã›ã‚“" +msgstr "ãŒä»¥ä¸‹ã‚’å«ã¾ãªã„" #: html/Elements/SelectEqualityOperator:59 msgid "equal to" @@ -5422,15 +5417,15 @@ msgstr "ID" #: html/Elements/SelectBoolean:53 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:57 html/Search/Elements/PickBasics:175 html/Search/Elements/PickBasics:74 html/Search/Elements/PickBasics:90 html/Search/Elements/PickCFs:53 msgid "is" -msgstr "ã§ã™" +msgstr "ãŒä»¥ä¸‹ã§ã‚ã‚‹" #: html/Elements/SelectBoolean:57 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:58 html/Search/Elements/PickBasics:176 html/Search/Elements/PickBasics:75 html/Search/Elements/PickBasics:91 html/Search/Elements/PickCFs:54 msgid "isn't" -msgstr "ã§ãªã„" +msgstr "ãŒä»¥ä¸‹ã§ã¯ãªã„" #: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59 msgid "less than" -msgstr "より少ãªã„" +msgstr "よりå°ã•ã„" #: html/Search/Elements/PickBasics:60 msgid "matches" @@ -5438,7 +5433,7 @@ msgstr "åˆã†" #: lib/RT/Date.pm:334 msgid "min" -msgstr "最低" +msgstr "分" #: html/Ticket/Update.html:64 msgid "minutes" @@ -5454,7 +5449,7 @@ msgstr "月" #: lib/RT/Queue_Overlay.pm:83 msgid "new" -msgstr "æ–°ã—ã„" +msgstr "æ–°è¦" #: html/Admin/Elements/PickCustomFields:64 html/Admin/Elements/PickObjects:63 msgid "no name" @@ -5474,7 +5469,7 @@ msgstr "ç‰ã—ããªã„" #: html/SelfService/Elements/MyRequests:83 lib/RT/Queue_Overlay.pm:84 msgid "open" -msgstr "é–‹ã" +msgstr "ç€æ‰‹" #: lib/RT/Group_Overlay.pm:227 #. ($self->Name, $user->Name) @@ -5488,11 +5483,11 @@ msgstr "ã‚ュー %1 %2" #: lib/RT/Queue_Overlay.pm:87 msgid "rejected" -msgstr "æ‹’å¦ã•れã¾ã—ãŸ" +msgstr "æ‹’å¦" #: lib/RT/Queue_Overlay.pm:86 msgid "resolved" -msgstr "分解ã•れã¾ã—ãŸ" +msgstr "解決済ã¿" #: lib/RT/Date.pm:330 msgid "sec" @@ -5508,7 +5503,7 @@ msgstr "" #: lib/RT/Queue_Overlay.pm:85 msgid "stalled" -msgstr "æ¢ã¾ã‚Šã¾ã—ãŸ" +msgstr "ä¿ç•™" #: lib/RT/Group_Overlay.pm:230 #. ($self->Type) @@ -5555,3 +5550,7 @@ msgstr "テンプレート %1ã¨" msgid "years" msgstr "å¹´" +#: share/html/Elements/Quicksearch, et al +msgid "New" +msgstr "æ–°è¦" + diff --git a/rt/lib/RT/I18N/nl.po b/rt/lib/RT/I18N/nl.po index 91547c870..51892b002 100644 --- a/rt/lib/RT/I18N/nl.po +++ b/rt/lib/RT/I18N/nl.po @@ -1,5 +1,9 @@ +# msgid "" msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: 2005-10-03 13:50-0400\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" diff --git a/rt/lib/RT/I18N/no.po b/rt/lib/RT/I18N/no.po index 995ab7852..92b2ed840 100644 --- a/rt/lib/RT/I18N/no.po +++ b/rt/lib/RT/I18N/no.po @@ -1,10 +1,11 @@ +# msgid "" msgstr "" -"Project-Id-Version: RT 3.0.11\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2003-04-01 06:06+0200\n" -"PO-Revision-Date: 2003-05-01 04:47+0200\n" +"PO-Revision-Date: 2005-10-03 13:50-0400\n" "Last-Translator: Ronny Pettersen <ronny.pettersen@edb.com>\n" -"Language-Team: RT Norwegian <rt@thefeed.no>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/rt/lib/RT/I18N/pl.po b/rt/lib/RT/I18N/pl.po index 3faa4eccb..e7a334b96 100644 --- a/rt/lib/RT/I18N/pl.po +++ b/rt/lib/RT/I18N/pl.po @@ -2,9 +2,9 @@ # msgid "" msgstr "" -"Project-Id-Version: RT 3.2.2\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2007-01-18 11:36+0800\n" -"PO-Revision-Date: 2005-01-18 02:00+0800\n" +"PO-Revision-Date: 2005-10-03 13:51-0400\n" "Last-Translator: Piotr Åšliwa <piotr.sliwa@comarch.pl>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" diff --git a/rt/lib/RT/I18N/pt_br.po b/rt/lib/RT/I18N/pt_br.po index cbf92c594..cf172b1be 100644 --- a/rt/lib/RT/I18N/pt_br.po +++ b/rt/lib/RT/I18N/pt_br.po @@ -1,9 +1,9 @@ -# $Id: pt_br.po,v 1.1.1.4 2005-10-15 09:10:38 ivan Exp $ +# $Id: pt_br.po,v 1.1.1.5 2006-10-17 08:50:11 ivan Exp $ msgid "" msgstr "" -"Project-Id-Version: RT 2.1.x\n" +"Project-Id-Version: RT 3.4.x\n" "POT-Creation-Date: 2002-05-02 11:36+0800\n" -"PO-Revision-Date: 2002-12-07 23:20-02:00\n" +"PO-Revision-Date: 2005-10-03 13:51-0400\n" "Last-Translator: Gustavo Chaves <gustavo@cpqd.com.br>\n" "Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" diff --git a/rt/lib/RT/I18N/ru.po b/rt/lib/RT/I18N/ru.po index cd93e0412..8688cde3e 100644 --- a/rt/lib/RT/I18N/ru.po +++ b/rt/lib/RT/I18N/ru.po @@ -1,15 +1,15 @@ -# translation of ru-nortfm.po to Russian -# translation of ru.po to Russian -# Andrew Kornilov <andy@eva.dp.ua>, 2004, 2005. +# translation of Request Tracker en.po to Russian +# Andrew Kornilov <hiddenman@tpway.com>, 2004, 2005. +# +# msgid "" msgstr "" -"Last-Translator: Andrew Kornilov <andy@eva.dp.ua>\n" -"PO-Revision-Date: 2005-03-11 15:38+0200\n" -"Language-Team: Russian <ru@li.org>\n" +"Last-Translator: Andrew Kornilov <hiddenman@tpway.com>\n" +"PO-Revision-Date: 2005-09-25 22:16+0300\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: KBabel 1.9.1\n" -"Project-Id-Version: ru\n" +"Project-Id-Version: RT 3.4.x\n" "MIME-Version: 1.0\n" #: NOT FOUND IN SOURCE @@ -24,7 +24,7 @@ msgstr "№" msgid "#%1: %2" msgstr "" -#: lib/RT/Record.pm:926 +#: lib/RT/Record.pm:925 #. ($label) msgid "$prefix %1" msgstr "" @@ -34,35 +34,35 @@ msgstr "" msgid "%1 #%2" msgstr "" -#: lib/RT/Date.pm:361 +#: lib/RT/Date.pm:360 #. ($s, $time_unit) msgid "%1 %2" msgstr "" -#: lib/RT/Date.pm:397 +#: lib/RT/Date.pm:396 #. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900)) msgid "%1 %2 %3 %4:%5:%6 %7" msgstr "%1 %2 %3 %4:%5:%6 %7" -#: lib/RT/Record.pm:1671 lib/RT/Transaction_Overlay.pm:636 lib/RT/Transaction_Overlay.pm:679 +#: lib/RT/Record.pm:1674 lib/RT/Transaction_Overlay.pm.orig:634 lib/RT/Transaction_Overlay.pm.orig:677 lib/RT/Transaction_Overlay.pm:634 lib/RT/Transaction_Overlay.pm:677 #. ($cf->Name, $new_value->Content) #. ($field, $self->NewValue) #. ($self->Field, $principal->Object->Name) msgid "%1 %2 added" msgstr "%1 %2 добавлен" -#: lib/RT/Date.pm:358 +#: lib/RT/Date.pm:357 #. ($s, $time_unit) msgid "%1 %2 ago" msgstr "%1 %2 назад" -#: lib/RT/Record.pm:1678 lib/RT/Transaction_Overlay.pm:643 +#: lib/RT/Record.pm:1681 lib/RT/Transaction_Overlay.pm.orig:641 lib/RT/Transaction_Overlay.pm:641 #. ($cf->Name, $old_content, $new_value->Content) #. ($field, $self->OldValue, $self->NewValue) msgid "%1 %2 changed to %3" msgstr "%1 %2 изменено на %3" -#: lib/RT/Record.pm:1675 lib/RT/Transaction_Overlay.pm:639 lib/RT/Transaction_Overlay.pm:685 +#: lib/RT/Record.pm:1678 lib/RT/Transaction_Overlay.pm.orig:637 lib/RT/Transaction_Overlay.pm.orig:683 lib/RT/Transaction_Overlay.pm:637 lib/RT/Transaction_Overlay.pm:683 #. ($cf->Name, $old_value->Content) #. ($field, $self->OldValue) #. ($self->Field, $principal->Object->Name) @@ -73,8 +73,9 @@ msgstr "%1 %2 удален" msgid "%1 %2 of group %3" msgstr "%1 %2 of group %3" -#: html/Admin/Elements/EditScrips:65 html/Admin/Elements/ListGlobalScrips:64 html/Ticket/Elements/PreviewScrips:98 +#: html/Admin/Elements/EditScrips:65 html/Admin/Elements/ListGlobalScrips:49 html/Ticket/Elements/PreviewScrips:98 #. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name)) +#. ($scrip->ConditionObj->Name, $scrip->ActionObj->Name, $scrip->TemplateObj->Name) msgid "%1 %2 with template %3" msgstr "%1 %2 Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¾Ð¼ %3" @@ -126,26 +127,16 @@ msgstr "" msgid "%1 - Specify the search module you want to use" msgstr "" - $RT::VERSION, - '2005', - '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',) - $RT::VERSION, - '2005', - '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',) #: html/Elements/Footer:58 -#. ('»|«', - $RT::VERSION, - '2005', - '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',) msgid "%1 RT %2 Copyright 1996-%3 %4." msgstr "" -#: lib/RT/ScripAction_Overlay.pm:151 +#: lib/RT/ScripAction_Overlay.pm:139 #. ($self->Id) msgid "%1 ScripAction loaded" msgstr "%1 СкриплетÐаДейÑтвие загружен" -#: lib/RT/Record.pm:1708 +#: lib/RT/Record.pm:1711 #. ($args{'Value'}, $cf->Name) msgid "%1 added as a value for %2" msgstr "%1 добавлено как значение Ð´Ð»Ñ %2" @@ -162,31 +153,31 @@ msgstr "%1 пÑевдонимы требуют идентификатор Ð·Ð°Ñ msgid "%1 aliases require a TicketId to work on (from %2) %3" msgstr "%1 пÑевдонимы требуют идентификатор заÑвки Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ над (от %2) %3" -#: lib/RT/Link_Overlay.pm:145 lib/RT/Link_Overlay.pm:152 +#: lib/RT/Link_Overlay.pm:144 lib/RT/Link_Overlay.pm:151 #. ($args{'Base'}) #. ($args{'Target'}) msgid "%1 appears to be a local object, but can't be found in the database" msgstr "%1 ÑвлÑетÑÑ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ñ‹Ð¼ объектом, но не найден в базе данных" -#: html/Ticket/Elements/ShowDates:73 lib/RT/Transaction_Overlay.pm:520 +#: html/Ticket/Elements/ShowDates:73 lib/RT/Transaction_Overlay.pm.orig:518 lib/RT/Transaction_Overlay.pm:518 #. ($self->BriefDescription , $self->CreatorObj->Name) #. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) msgid "%1 by %2" msgstr "%1 пользователем %2" -#: lib/RT/Transaction_Overlay.pm:777 lib/RT/Transaction_Overlay.pm:786 lib/RT/Transaction_Overlay.pm:789 +#: lib/RT/Transaction_Overlay.pm.orig:775 lib/RT/Transaction_Overlay.pm.orig:784 lib/RT/Transaction_Overlay.pm.orig:787 lib/RT/Transaction_Overlay.pm:775 lib/RT/Transaction_Overlay.pm:784 lib/RT/Transaction_Overlay.pm:787 #. ($self->Field , $q1->Name , $q2->Name) #. ($self->Field, $t2->AsString, $t1->AsString) #. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'") msgid "%1 changed from %2 to %3" msgstr "%1 изменена Ñ %2 на %3" -#: html/Search/Build.html:212 +#: html/Search/Build.html:192 #. ($Description) msgid "%1 copy" msgstr "%1 копиÑ" -#: lib/RT/Record.pm:930 +#: lib/RT/Record.pm:928 msgid "%1 could not be set to %2." msgstr "%1 невозможно уÑтановить в %2." @@ -194,17 +185,17 @@ msgstr "%1 невозможно уÑтановить в %2." msgid "%1 couldn't init a transaction (%2)\\n" msgstr "%1 не может инициировать транзакцию (%2)\\n" -#: lib/RT/Ticket_Overlay.pm:2743 +#: lib/RT/Ticket_Overlay.pm:2710 #. ($self) msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent." msgstr "%1 не может изменить ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° Решено. Возможно, база данных RT иÑпорчена." -#: lib/RT/Transaction_Overlay.pm:560 +#: lib/RT/Transaction_Overlay.pm.orig:558 lib/RT/Transaction_Overlay.pm:558 #. ($obj_type) msgid "%1 created" msgstr "%1 Ñоздана" -#: lib/RT/Transaction_Overlay.pm:565 +#: lib/RT/Transaction_Overlay.pm.orig:563 lib/RT/Transaction_Overlay.pm:563 #. ($obj_type) msgid "%1 deleted" msgstr "%1 удалена" @@ -227,7 +218,7 @@ msgstr "%1 заÑвок Ñ Ð½Ð°Ð¸Ð²Ñ‹Ñшими приоритетами, Ñоз msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron." msgstr "" -#: lib/RT/Queue_Overlay.pm:860 +#: lib/RT/Queue_Overlay.pm:847 #. ($principal->Object->Name, $args{'Type'}) msgid "%1 is no longer a %2 for this queue." msgstr "%1 больше не ÑвлÑетÑÑ %2 Ð´Ð»Ñ Ñтой очереди." @@ -266,7 +257,7 @@ msgstr "%1 поÑледних неназначенных заÑвок" msgid "%1 not shown" msgstr "%1 не отображаетÑÑ" -#: lib/RT/CustomField_Overlay.pm:827 +#: lib/RT/CustomField_Overlay.pm:813 msgid "%1 objects" msgstr "%1 объектов" @@ -300,11 +291,11 @@ msgstr "%1 решит вÑе заÑвки, входÑщие в групповоРmsgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request." msgstr "%1 приоÑтановит заÑвки, которые завиÑÑÑ‚ от запроÑа или включены в него" -#: lib/RT/CustomField_Overlay.pm:828 +#: lib/RT/CustomField_Overlay.pm:814 msgid "%1's %2 objects" msgstr "%1 %2 объектов" -#: lib/RT/CustomField_Overlay.pm:829 +#: lib/RT/CustomField_Overlay.pm:815 msgid "%1's %2's %3 objects" msgstr "" @@ -314,7 +305,7 @@ msgstr "" msgid "%1's saved searches" msgstr "%1 Ñохраненных запроÑов" -#: lib/RT/Transaction_Overlay.pm:470 +#: lib/RT/Transaction_Overlay.pm.orig:468 lib/RT/Transaction_Overlay.pm:468 #. ($self) msgid "%1: no attachment specified" msgstr "%1: без вложений" @@ -373,8 +364,8 @@ msgstr "(Введите идентификаторы или ÑÑылки на з #: html/Admin/Queues/Modify.html:75 html/Admin/Queues/Modify.html:81 #. ($RT::CorrespondAddress) #. ($RT::CommentAddress) -msgid "(If left blank, will default to %1)" -msgstr "(ЕÑли пуÑтое, то по-умолчанию равно %1)" +msgid "(If left blank, will default to %1" +msgstr "(ЕÑли пуÑтое, то по-умолчанию равно %1" #: NOT FOUND IN SOURCE msgid "(No Value)" @@ -388,7 +379,7 @@ msgstr "(Ðет дополнительных полей)" msgid "(No members)" msgstr "(Ðет пользователей)" -#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ListGlobalScrips:49 +#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ListGlobalScrips:53 msgid "(No scrips)" msgstr "(Ðет Ñкриплетов)" @@ -426,7 +417,7 @@ msgstr "(Отправить копию ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± Ñтом обно #: html/Admin/Elements/EditScrip:102 msgid "(Use these fields when you choose 'User Defined' for a condition or action)" -msgstr "(ИÑпользуйте Ñти Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¸ выборе 'Задано пользователем' Ð´Ð»Ñ ÑƒÐ»Ð¾Ð²Ð¸Ð¹ или дейÑтвий)" +msgstr "(ИÑпользуйте Ñти Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¸ выборе 'Задано пользователем' Ð´Ð»Ñ ÑƒÑловий или дейÑтвий)" #: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54 msgid "(empty)" @@ -448,7 +439,7 @@ msgstr "(нет имени)" msgid "(no subject)" msgstr "(нет темы)" -#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:60 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:65 lib/RT/Transaction_Overlay.pm:580 +#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:60 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:65 lib/RT/Transaction_Overlay.pm.orig:578 lib/RT/Transaction_Overlay.pm:578 msgid "(no value)" msgstr "(нет значениÑ)" @@ -476,7 +467,7 @@ msgstr "(в ожидании других заÑвок)" msgid "(requestor's group)" msgstr "(группа автора заÑвки)" -#: html/Admin/Users/Modify.html:71 +#: html/Admin/Users/Modify.html.orig:71 html/Admin/Users/Modify.html:71 msgid "(required)" msgstr "(требуетÑÑ)" @@ -504,10 +495,6 @@ msgstr "" msgid "<%$_%>" msgstr "" -#: html/Search/Elements/DisplayOptions:65 -msgid "<%$field%>" -msgstr "" - #: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:47 lib/RT/StyleGuide.pod:787 #. ($m->scomp('/Elements/SelectNewTicketQueue')) msgid "<input type=\"submit\" value=\"New ticket in\"> %1" @@ -517,10 +504,6 @@ msgstr "<input type=\"submit\" value=\"Создать заÑвку в очере msgid "A blank template" msgstr "ПуÑтой шаблон" -#: html/Admin/Users/Modify.html:363 -msgid "A password was not set, so user won't be able to login." -msgstr "" - #: NOT FOUND IN SOURCE msgid "ACE could not be deleted" msgstr "Ðевозможно удалить ACE" @@ -529,17 +512,17 @@ msgstr "Ðевозможно удалить ACE" msgid "ACE could not be found" msgstr "Ðевозможно найти ACE" -#: lib/RT/ACE_Overlay.pm:175 lib/RT/Principal_Overlay.pm:219 +#: lib/RT/ACE_Overlay.pm:181 lib/RT/Principal_Overlay.pm:224 msgid "ACE not found" msgstr "ACE не найден" -#: lib/RT/ACE_Overlay.pm:854 +#: lib/RT/ACE_Overlay.pm:866 msgid "ACEs can only be created and deleted." msgstr "ACEÑ‹ можно только Ñоздавать и удалÑть" #: html/Search/Elements/SelectAndOr:46 msgid "AND" -msgstr "" +msgstr "И" #: NOT FOUND IN SOURCE msgid "Aborting to avoid unintended ticket modifications.\\n" @@ -549,7 +532,7 @@ msgstr "Прекращаем работу во избежание нежелат msgid "About me" msgstr "Обо мне" -#: html/Admin/Users/Modify.html:106 +#: html/Admin/Users/Modify.html.orig:106 html/Admin/Users/Modify.html:106 msgid "Access control" msgstr "Права доÑтупа" @@ -557,7 +540,7 @@ msgstr "Права доÑтупа" msgid "Action" msgstr "ДейÑтвие" -#: lib/RT/Scrip_Overlay.pm:173 +#: lib/RT/Scrip_Overlay.pm:172 #. ($args{'ScripAction'}) msgid "Action %1 not found" msgstr "ДейÑтвие %1 не найдено" @@ -568,13 +551,13 @@ msgstr "ДейÑтвие принÑто." #: bin/rt-crontool:148 msgid "Action committed.\\n" -msgstr "" +msgstr "ДейÑтвие зафикÑировано.\\n" #: bin/rt-crontool:144 msgid "Action prepared..." msgstr "ДейÑтвие подготовлено..." -#: html/Search/Build.html:85 +#: html/Search/Build.html:65 msgid "Add" msgstr "Добавить" @@ -588,7 +571,7 @@ msgstr "Добавить Копию" #: html/Search/Elements/EditFormat:49 msgid "Add Columns" -msgstr "" +msgstr "Добавить колонки" #: html/Search/Elements/PickCriteria:46 msgid "Add Criteria" @@ -630,7 +613,7 @@ msgstr "Добавить Ñкриплет Ð´Ð»Ñ Ñтой очереди" msgid "Add a scrip which will apply to all queues" msgstr "Добавить Ñкриплет, который будет дейÑтвовать на вÑе очереди" -#: html/Search/Build.html:85 +#: html/Search/Build.html:65 msgid "Add additional criteria" msgstr "Добавить дополнительный критерий" @@ -655,7 +638,7 @@ msgstr "Добавить, удалить или изменить Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ msgid "AddNextState" msgstr "ДобавлÑтьСледующееСоÑтоÑние" -#: lib/RT/Queue_Overlay.pm:760 +#: lib/RT/Queue_Overlay.pm:747 #. ($args{'Type'}) msgid "Added principal as a %1 for this queue" msgstr "Пользователь добавлен как %1 Ð´Ð»Ñ Ñтой очереди" @@ -665,11 +648,11 @@ msgstr "Пользователь добавлен как %1 Ð´Ð»Ñ Ñтой оч msgid "Added principal as a %1 for this ticket" msgstr "Пользователь добавлен как %1 Ð´Ð»Ñ Ñтой заÑвки" -#: html/Admin/Users/Modify.html:146 html/User/Prefs.html:133 +#: html/Admin/Users/Modify.html.orig:146 html/Admin/Users/Modify.html:146 html/User/Prefs.html:133 msgid "Address1" msgstr "ÐдреÑ1" -#: html/Admin/Users/Modify.html:151 html/User/Prefs.html:137 +#: html/Admin/Users/Modify.html.orig:151 html/Admin/Users/Modify.html:151 html/User/Prefs.html:137 msgid "Address2" msgstr "ÐдреÑ2" @@ -709,7 +692,7 @@ msgstr "ОÑновные параметры очереди" msgid "AdminAllPersonalGroups" msgstr "ÐдминиÑтрироватьВÑеПерÑональныеГруппы" -#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:60 lib/RT/ACE_Overlay.pm:114 +#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:60 lib/RT/ACE_Overlay.pm:113 msgid "AdminCc" msgstr "ÐдминиÑтративнаÑКопиÑ" @@ -737,15 +720,15 @@ msgstr "ÐдминиÑтрироватьГруппу" msgid "AdminGroupMembership" msgstr "ÐдминиÑтрироватьЧленÑтвоВГруппах" -#: lib/RT/System.pm:81 +#: lib/RT/System.pm:80 msgid "AdminOwnPersonalGroups" msgstr "ÐдминиÑтрироватьСобÑтвенныеГруппы" -#: lib/RT/Queue_Overlay.pm:93 +#: lib/RT/Queue_Overlay.pm:92 msgid "AdminQueue" msgstr "ÐдминиÑтрироватьОчередь" -#: lib/RT/System.pm:82 +#: lib/RT/System.pm:81 msgid "AdminUsers" msgstr "ÐдминиÑтрироватьПользователей" @@ -807,7 +790,7 @@ msgstr "Ð’Ñегода отправлÑть ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð°Ð¼ з #: html/Search/Elements/EditQuery:56 msgid "And/Or" -msgstr "" +msgstr "И/Или" #: html/Admin/CustomFields/Modify.html:73 html/Admin/Elements/CustomFieldTabs:83 msgid "Applies to" @@ -866,7 +849,7 @@ msgstr "Подтвердить" msgid "Approver's notes: %1" msgstr "ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ð²ÑˆÐµÐ³Ð¾: %1" -#: lib/RT/Date.pm:440 +#: lib/RT/Date.pm:437 msgid "Apr." msgstr "Ðпр." @@ -878,11 +861,11 @@ msgstr "Ðпрель" msgid "Ascending" msgstr "Ð’ порÑдке возраÑтаниÑ" -#: lib/RT/Queue_Overlay.pm:97 +#: lib/RT/Queue_Overlay.pm:96 msgid "Assign and remove custom fields" msgstr "Ðазначение и удаление дополнительных полей" -#: lib/RT/Queue_Overlay.pm:97 +#: lib/RT/Queue_Overlay.pm:96 msgid "AssignCustomFields" msgstr "ÐазначатьДополнительныеПолÑ" @@ -903,11 +886,11 @@ msgstr "Вложенный файл" msgid "Attachment '%1' could not be loaded" msgstr "Вложение '%1' не может быть загружено" -#: lib/RT/Transaction_Overlay.pm:478 +#: lib/RT/Transaction_Overlay.pm.orig:476 lib/RT/Transaction_Overlay.pm:476 msgid "Attachment created" msgstr "Вложение Ñоздано" -#: lib/RT/Tickets_Overlay.pm:1673 +#: lib/RT/Tickets_Overlay.pm:1672 msgid "Attachment filename" msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ð´Ð»Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ" @@ -915,11 +898,11 @@ msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ð´Ð»Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ" msgid "Attachments" msgstr "ВложениÑ" -#: lib/RT/Attributes_Overlay.pm:172 +#: lib/RT/Attributes_Overlay.pm:170 msgid "Attribute Deleted" msgstr "Ðтрибут удален" -#: lib/RT/Date.pm:444 +#: lib/RT/Date.pm:441 msgid "Aug." msgstr "Ðвг." @@ -975,7 +958,7 @@ msgstr "Ð¡ÐºÑ€Ñ‹Ñ‚Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ" msgid "Be sure to save your changes" msgstr "Ðе забудьте Ñохранить наÑтройки" -#: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:360 +#: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:358 msgid "Before" msgstr "До" @@ -997,7 +980,7 @@ msgstr "ПуÑтой" #: html/Search/Elements/EditFormat:84 msgid "Bold" -msgstr "" +msgstr "Жирный" #: NOT FOUND IN SOURCE msgid "Bookmarkable URL for this search" @@ -1015,28 +998,28 @@ msgstr "Сокращенные заголовки" msgid "Bulk ticket update" msgstr "МножеÑтвенное обновление заÑвки" -#: lib/RT/User_Overlay.pm:1722 +#: lib/RT/User_Overlay.pm:1717 msgid "Can not modify system users" msgstr "Ðевомзожно изменить ÑиÑтемных пользователей" -#: lib/RT/Queue_Overlay.pm:92 +#: lib/RT/Queue_Overlay.pm:91 msgid "Can this principal see this queue" msgstr "Может ли данный пользователь проÑматривать Ñту очередь" -#: lib/RT/CustomField_Overlay.pm:370 +#: lib/RT/CustomField_Overlay.pm:364 msgid "Can't add a custom field value without a name" msgstr "Ðевозможно добавление Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»ÑŒÐ½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð±ÐµÐ· наименованиÑ" #: html/Admin/CustomFields/Objects.html:86 #. ($Class) msgid "Can't find a collection class for '%1'" -msgstr "" +msgstr "Ðевозможно найти клаÑÑ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ð¸ Ð´Ð»Ñ '%1'" -#: html/Search/Build.html:761 +#: html/Search/Build.html:819 msgid "Can't find a saved search to work with" msgstr "Ðевозможно найти Ñохраненный Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñ Ð½Ð¸Ð¼" -#: lib/RT/Link_Overlay.pm:160 +#: lib/RT/Link_Overlay.pm:159 msgid "Can't link a ticket to itself" msgstr "Ðевозможно ÑвÑзать заÑвку Ñаму Ñ Ñобой" @@ -1044,11 +1027,11 @@ msgstr "Ðевозможно ÑвÑзать заÑвку Ñаму Ñ Ñобой" msgid "Can't merge into a merged ticket. You should never get this error" msgstr "Ðевозможно объединить Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð½Ð¾Ð¹ заÑвкой (Ñта ошибка никогда не должна проиÑходить)." -#: html/Search/Build.html:766 +#: html/Search/Build.html:824 msgid "Can't save this search" msgstr "Ðевозможно Ñохранить Ñтот запроÑ" -#: lib/RT/Record.pm:1266 lib/RT/Record.pm:1344 +#: lib/RT/Record.pm:1257 lib/RT/Record.pm:1335 msgid "Can't specifiy both base and target" msgstr "Ðевозможно указывать одновременно и иÑточник и Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ" @@ -1057,7 +1040,7 @@ msgstr "Ðевозможно указывать одновременно и Ð¸Ñ msgid "Cannot create user: %1" msgstr "Ðевозможно Ñоздать пользователÑ: %1" -#: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:85 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:81 lib/RT/ACE_Overlay.pm:113 +#: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:85 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:81 lib/RT/ACE_Overlay.pm:112 msgid "Cc" msgstr "КопиÑ" @@ -1081,7 +1064,7 @@ msgstr "Выделите права, которые хотите Ð¾Ñ‚Ð¾Ð·Ð²Ð°Ñ‚Ñ msgid "Children" msgstr "Потомки" -#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:141 +#: html/Admin/Users/Modify.html.orig:156 html/Admin/Users/Modify.html:156 html/User/Prefs.html:141 msgid "City" msgstr "Город" @@ -1113,7 +1096,7 @@ msgstr "Код" msgid "Command not understood!\\n" msgstr "Команда не раÑпознана!\\n" -#: html/Ticket/Elements/ShowTransaction:182 html/Ticket/Elements/Tabs:176 +#: html/Ticket/Elements/ShowTransaction:181 html/Ticket/Elements/Tabs:176 msgid "Comment" msgstr "Комментировать" @@ -1125,11 +1108,11 @@ msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸ÐµÐ²" msgid "Comment not recorded" msgstr "Комментарий не запиÑан" -#: lib/RT/Queue_Overlay.pm:112 +#: lib/RT/Queue_Overlay.pm:111 msgid "Comment on tickets" msgstr "Комментарии заÑвки" -#: lib/RT/Queue_Overlay.pm:112 +#: lib/RT/Queue_Overlay.pm:111 msgid "CommentOnTicket" msgstr "КомментироватьЗаÑвку" @@ -1149,15 +1132,15 @@ msgstr "Комментарии (Ðе отправлÑÑŽÑ‚ÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð°Ð¼ зРmsgid "Comments about %1" msgstr "Комментарии о %1" -#: html/Admin/Users/Modify.html:224 html/Ticket/Elements/ShowRequestor:67 +#: html/Admin/Users/Modify.html.orig:224 html/Admin/Users/Modify.html:224 html/Ticket/Elements/ShowRequestor:67 msgid "Comments about this user" msgstr "Комментарии об Ñтом пользователе" -#: lib/RT/Transaction_Overlay.pm:623 +#: lib/RT/Transaction_Overlay.pm.orig:621 lib/RT/Transaction_Overlay.pm:621 msgid "Comments added" msgstr "Комментарии добавлены" -#: lib/RT/Action/Generic.pm:176 +#: lib/RT/Action/Generic.pm:168 msgid "Commit Stubbed" msgstr "" @@ -1173,7 +1156,7 @@ msgstr "УÑловие" msgid "Condition matches..." msgstr "ПодходÑщее уÑловие..." -#: lib/RT/Scrip_Overlay.pm:189 +#: lib/RT/Scrip_Overlay.pm:188 msgid "Condition not found" msgstr "УÑловие не найдено" @@ -1217,7 +1200,7 @@ msgstr "КорреÑпонденциÑ" msgid "Correspondence Address" msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÑпонденции" -#: lib/RT/Transaction_Overlay.pm:619 +#: lib/RT/Transaction_Overlay.pm.orig:617 lib/RT/Transaction_Overlay.pm:617 msgid "Correspondence added" msgstr "КорреÑÐ¿Ð¾Ð½Ð´ÐµÐ½Ñ†Ð¸Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð°" @@ -1233,16 +1216,16 @@ msgstr "Ðевозможно добавить новое значение доп msgid "Could not add new custom field value for ticket. %1 " msgstr "Ðевозможно добавить новое значение дополнительного Ð¿Ð¾Ð»Ñ Ð·Ð°Ñвки. %1" -#: lib/RT/Record.pm:1693 +#: lib/RT/Record.pm:1696 msgid "Could not add new custom field value. " -msgstr "" +msgstr "Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ полÑ" -#: lib/RT/Record.pm:1646 +#: lib/RT/Record.pm:1649 #. (, $value_msg) msgid "Could not add new custom field value. %1 " msgstr "Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ полÑ. %1" -#: lib/RT/Ticket_Overlay.pm:3004 lib/RT/Ticket_Overlay.pm:3012 lib/RT/Ticket_Overlay.pm:3029 +#: lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2979 lib/RT/Ticket_Overlay.pm:2996 msgid "Could not change owner. " msgstr "Ðевозможно изменить ответÑтвенного." @@ -1254,9 +1237,9 @@ msgstr "Ðевозможно Ñоздать дополнительное полР#: html/Admin/Elements/EditCustomField:113 #. ($msg) msgid "Could not create CustomField: %1" -msgstr "" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ полÑ: %1" -#: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:502 lib/RT/Group_Overlay.pm:509 +#: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:501 lib/RT/Group_Overlay.pm:508 msgid "Could not create group" msgstr "Ðевозможно Ñоздать группу" @@ -1269,7 +1252,7 @@ msgstr "Ðевозможно Ñоздать шаблон: %1" msgid "Could not create ticket. Queue not set" msgstr "Ðевозможно Ñоздать заÑвку. Ðе задана очередь." -#: lib/RT/User_Overlay.pm:256 lib/RT/User_Overlay.pm:270 lib/RT/User_Overlay.pm:279 lib/RT/User_Overlay.pm:288 lib/RT/User_Overlay.pm:297 lib/RT/User_Overlay.pm:311 lib/RT/User_Overlay.pm:321 lib/RT/User_Overlay.pm:497 +#: lib/RT/User_Overlay.pm:255 lib/RT/User_Overlay.pm:269 lib/RT/User_Overlay.pm:278 lib/RT/User_Overlay.pm:287 lib/RT/User_Overlay.pm:296 lib/RT/User_Overlay.pm:310 lib/RT/User_Overlay.pm:320 lib/RT/User_Overlay.pm:496 msgid "Could not create user" msgstr "Ðевозможно Ñоздать пользователÑ" @@ -1285,11 +1268,11 @@ msgstr "Ðевозможно найти заÑвку Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾ msgid "Could not find group %1." msgstr "Ðевозможно найти группу %1." -#: lib/RT/Queue_Overlay.pm:738 lib/RT/Ticket_Overlay.pm:1384 +#: lib/RT/Queue_Overlay.pm:725 lib/RT/Ticket_Overlay.pm:1384 msgid "Could not find or create that user" msgstr "Ðевозможно найти или Ñоздать Ñтого пользователÑ" -#: lib/RT/Queue_Overlay.pm:799 lib/RT/Ticket_Overlay.pm:1465 +#: lib/RT/Queue_Overlay.pm:786 lib/RT/Ticket_Overlay.pm:1465 msgid "Could not find that principal" msgstr "Ðевозможно найти Ñтого пользователÑ" @@ -1305,16 +1288,16 @@ msgstr "Ðевозможно загрузить дополнительное пРmsgid "Could not load group" msgstr "Ðевозможно загрузить группу" -#: lib/RT/SavedSearch.pm:120 +#: lib/RT/SavedSearch.pm:119 #. ($privacy) msgid "Could not load object for %1" -msgstr "" +msgstr "Ошибка загрузки объекта Ð´Ð»Ñ %1" -#: lib/RT/SavedSearch.pm:188 +#: lib/RT/SavedSearch.pm:187 msgid "Could not load search attribute" -msgstr "" +msgstr "Ошибка загрузки атрибута запроÑа" -#: lib/RT/Queue_Overlay.pm:758 +#: lib/RT/Queue_Overlay.pm:745 #. ($args{'Type'}) msgid "Could not make that principal a %1 for this queue" msgstr "Ðевозможно назначить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1 Ð´Ð»Ñ Ñтой очереди" @@ -1324,7 +1307,7 @@ msgstr "Ðевозможно назначить Ñтого пользоватеРmsgid "Could not make that principal a %1 for this ticket" msgstr "Ðевозможно назначить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1 Ð´Ð»Ñ Ñтой заÑвки" -#: lib/RT/Queue_Overlay.pm:857 +#: lib/RT/Queue_Overlay.pm:844 #. ($args{'Type'}) msgid "Could not remove that principal as a %1 for this queue" msgstr "Ðевозможно отозвать функции у Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 Ð´Ð»Ñ Ñтой очереди" @@ -1333,15 +1316,15 @@ msgstr "Ðевозможно отозвать функции у пользова msgid "Could not remove that principal as a %1 for this ticket" msgstr "Ðевозможно отозвать функции у Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 Ð´Ð»Ñ Ñтой заÑвки" -#: lib/RT/User_Overlay.pm:192 +#: lib/RT/User_Overlay.pm:191 msgid "Could not set user info" msgstr "Ðевозможно уÑтановить информацию о пользователе" -#: lib/RT/Group_Overlay.pm:1011 +#: lib/RT/Group_Overlay.pm:1009 msgid "Couldn't add member to group" msgstr "Ðевозможно добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² группу" -#: lib/RT/Record.pm:1705 lib/RT/Record.pm:1757 +#: lib/RT/Record.pm:1708 lib/RT/Record.pm:1767 #. ($Msg) msgid "Couldn't create a transaction: %1" msgstr "Ðевозможно Ñоздать транзакцию: %1" @@ -1354,15 +1337,15 @@ msgstr "Ðевозможно определить дальнейшие дейÑÑ msgid "Couldn't find group\\n" msgstr "Ðевезможно найти группу\\n" -#: lib/RT/Record.pm:939 +#: lib/RT/Record.pm:937 msgid "Couldn't find row" msgstr "Ðевозможно найти Ñтроку" -#: lib/RT/Group_Overlay.pm:985 +#: lib/RT/Group_Overlay.pm:983 msgid "Couldn't find that principal" msgstr "Ðевозможно найти Ñтого пользователÑ" -#: lib/RT/CustomField_Overlay.pm:404 +#: lib/RT/CustomField_Overlay.pm:398 msgid "Couldn't find that value" msgstr "Ðевозможно найти Ñто значение" @@ -1374,7 +1357,7 @@ msgstr "Ðевозможно найти Ñтого наблюдателÑ" msgid "Couldn't find user\\n" msgstr "Ðевозможно найти пользователÑ\\n" -#: lib/RT/CurrentUser.pm:146 +#: lib/RT/CurrentUser.pm:145 #. ($self->Id) msgid "Couldn't load %1 from the users database.\\n" msgstr "Ðевозможно загрузить %1 из базы пользователей.\\n" @@ -1382,7 +1365,7 @@ msgstr "Ðевозможно загрузить %1 из базы пользовР#: html/Admin/CustomFields/UserRights.html:149 #. ($id) msgid "Couldn't load Class %1" -msgstr "" +msgstr "Ошибка загрузки клаÑÑа %1" #: html/Admin/CustomFields/GroupRights.html:107 #. ($id) @@ -1402,7 +1385,7 @@ msgstr "Ðевозможно загрузить Ñкриплеты." msgid "Couldn't load group %1" msgstr "Ðевозможно загрузить группу %1" -#: lib/RT/Link_Overlay.pm:203 lib/RT/Link_Overlay.pm:212 lib/RT/Link_Overlay.pm:239 +#: lib/RT/Link_Overlay.pm:202 lib/RT/Link_Overlay.pm:211 lib/RT/Link_Overlay.pm:238 msgid "Couldn't load link" msgstr "Ðевозможно загрузить ÑÑылку" @@ -1433,22 +1416,12 @@ msgstr "Ðевозможно загрузить шаблон" msgid "Couldn't load that user (%1)" msgstr "Ðевозможно загрузить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ (%1)" -#: html/SelfService/Display.html:156 +#: html/SelfService/Display.html:149 #. ($id) msgid "Couldn't load ticket '%1'" msgstr "Ðевозможно загрузить заÑвку '%1'" -#: lib/RT/Ticket_Overlay.pm:2612 -#. ($args{'Base'}) -msgid "Couldn't resolve base '%1' into a URI." -msgstr "" - -#: lib/RT/Ticket_Overlay.pm:2611 -#. ($args{'Target'}) -msgid "Couldn't resolve target '%1' into a URI." -msgstr "" - -#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:153 +#: html/Admin/Users/Modify.html.orig:173 html/Admin/Users/Modify.html:173 html/User/Prefs.html:153 msgid "Country" msgstr "Страна" @@ -1485,7 +1458,7 @@ msgstr "Создать новый глобальный Ñкриплет" msgid "Create a new global scrip" msgstr "Создать новый глобальный Ñкриплет" -#: html/Admin/Groups/Modify.html:125 html/Admin/Groups/Modify.html:99 +#: html/Admin/Groups/Modify.html:124 html/Admin/Groups/Modify.html:98 msgid "Create a new group" msgstr "Создать новую группу" @@ -1509,7 +1482,7 @@ msgstr "Создать новый шаблон" msgid "Create a new ticket" msgstr "Создать новую заÑвку" -#: html/Admin/Users/Modify.html:251 html/Admin/Users/Modify.html:306 +#: html/Admin/Users/Modify.html.orig:251 html/Admin/Users/Modify.html.orig:308 html/Admin/Users/Modify.html:251 html/Admin/Users/Modify.html:306 msgid "Create a new user" msgstr "Создать нового пользователÑ" @@ -1554,7 +1527,7 @@ msgstr "Создать новые заÑвки на оÑнове Ñтого ша msgid "Create ticket" msgstr "Создать заÑвку" -#: lib/RT/Queue_Overlay.pm:110 +#: lib/RT/Queue_Overlay.pm:109 msgid "Create tickets in this queue" msgstr "Создать заÑвки в Ñтой очереди" @@ -1562,7 +1535,7 @@ msgstr "Создать заÑвки в Ñтой очереди" msgid "Create, delete and modify custom fields" msgstr "Создать, удалить или изменить дополнительные полÑ" -#: lib/RT/Queue_Overlay.pm:93 +#: lib/RT/Queue_Overlay.pm:92 msgid "Create, delete and modify queues" msgstr "Создать, удалить или изменить очереди" @@ -1570,19 +1543,19 @@ msgstr "Создать, удалить или изменить очереди" msgid "Create, delete and modify the members of any user's personal groups" msgstr "Создать, удалить или изменить членов любой пользовательÑкой перÑональной группы" -#: lib/RT/System.pm:81 +#: lib/RT/System.pm:80 msgid "Create, delete and modify the members of personal groups" msgstr "Создать, удалить или изменить членов перÑональных групп" -#: lib/RT/System.pm:82 +#: lib/RT/System.pm:81 msgid "Create, delete and modify users" msgstr "Создать, удалить или изменить пользователей" -#: lib/RT/System.pm:88 +#: lib/RT/System.pm:87 msgid "CreateSavedSearch" -msgstr "" +msgstr "СоздаватьСохраненныйЗапроÑ" -#: lib/RT/Queue_Overlay.pm:110 +#: lib/RT/Queue_Overlay.pm:109 msgid "CreateTicket" msgstr "Создать ЗаÑвку" @@ -1643,7 +1616,7 @@ msgstr "Текущие наблюдатели" msgid "Custom Field #%1" msgstr "Дополнительное поле #%1" -#: html/Admin/Elements/SystemTabs:61 html/Admin/Elements/Tabs:62 html/Admin/Global/index.html:71 html/Admin/Users/Modify.html:208 html/Admin/index.html:77 html/Ticket/Elements/ShowSummary:57 +#: html/Admin/Elements/SystemTabs:61 html/Admin/Elements/Tabs:62 html/Admin/Global/index.html:71 html/Admin/Users/Modify.html.orig:208 html/Admin/Users/Modify.html:208 html/Admin/index.html:77 html/Ticket/Elements/ShowSummary:57 msgid "Custom Fields" msgstr "Дополнительные полÑ" @@ -1682,7 +1655,7 @@ msgstr "Дополнительное поле %1 имеет значение." msgid "Custom field %1 has no value." msgstr "Дополнительное поле %1 не имеет значениÑ." -#: lib/RT/Record.pm:1579 lib/RT/Record.pm:1740 +#: lib/RT/Record.pm:1581 #. ($args{'Field'}) msgid "Custom field %1 not found" msgstr "Ðевозможно найти дополнительное поле %1" @@ -1691,11 +1664,11 @@ msgstr "Ðевозможно найти дополнительное поле %1 msgid "Custom field deleted" msgstr "Дополнительное поле удалено" -#: NOT FOUND IN SOURCE +#: lib/RT/CustomField_Overlay.pm:289 lib/RT/Record.pm:1749 msgid "Custom field not found" msgstr "Ðевозможно найти дополнительное поле" -#: lib/RT/CustomField_Overlay.pm:1041 +#: lib/RT/CustomField_Overlay.pm:1027 #. ($args{'Content'}, $self->Name) msgid "Custom field value %1 could not be found for custom field %2" msgstr "Ðевозможно найти значение %1 дополнительного Ð¿Ð¾Ð»Ñ %2" @@ -1704,19 +1677,19 @@ msgstr "Ðевозможно найти значение %1 дополнител msgid "Custom field value changed from %1 to %2" msgstr "Значение дополнительного Ð¿Ð¾Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¾ Ñ %1 на %2" -#: lib/RT/CustomField_Overlay.pm:414 +#: lib/RT/CustomField_Overlay.pm:408 msgid "Custom field value could not be deleted" msgstr "Ðевозможно удалить значение дополнительного полÑ" -#: lib/RT/CustomField_Overlay.pm:1047 +#: lib/RT/CustomField_Overlay.pm:1033 msgid "Custom field value could not be found" msgstr "Ðевозможно найти значение дополнительного Ð¿Ð¾Ð»Ñ " -#: lib/RT/CustomField_Overlay.pm:1049 lib/RT/CustomField_Overlay.pm:412 +#: lib/RT/CustomField_Overlay.pm:1035 lib/RT/CustomField_Overlay.pm:406 msgid "Custom field value deleted" msgstr "Значение дополнительного Ð¿Ð¾Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¾" -#: html/Elements/SelectGroups:51 html/Elements/SelectUsers:51 lib/RT/Transaction_Overlay.pm:627 +#: html/Elements/SelectGroups:51 html/Elements/SelectUsers:51 lib/RT/Transaction_Overlay.pm.orig:625 lib/RT/Transaction_Overlay.pm:625 msgid "CustomField" msgstr "ДополнительноеПоле" @@ -1728,7 +1701,7 @@ msgstr "Ошибка данных" msgid "Dates" msgstr "Даты" -#: lib/RT/Date.pm:448 +#: lib/RT/Date.pm:445 msgid "Dec." msgstr "Дек." @@ -1776,11 +1749,11 @@ msgstr "Умолчание: %1/%2 изменено Ñ %3 на %4" msgid "Delegate rights" msgstr "Делегирование прав" -#: lib/RT/System.pm:85 +#: lib/RT/System.pm:84 msgid "Delegate specific rights which have been granted to you." msgstr "Делегирование отдельных прав, которые выданы вам." -#: lib/RT/System.pm:85 +#: lib/RT/System.pm:84 msgid "DelegateRights" msgstr "ДелегироватьПрава" @@ -1796,36 +1769,36 @@ msgstr "Удалить" msgid "Delete Template" msgstr "Удалить шаблон" -#: lib/RT/SavedSearch.pm:211 +#: lib/RT/SavedSearch.pm:210 #. ($msg) msgid "Delete failed: %1" -msgstr "" +msgstr "Ошибка удалениÑ: %1" #: html/Admin/Elements/EditScrips:74 msgid "Delete selected scrips" msgstr "Удалить выбранные Ñкриплеты" -#: lib/RT/Queue_Overlay.pm:115 +#: lib/RT/Queue_Overlay.pm:114 msgid "Delete tickets" msgstr "Удалить заÑвки" -#: lib/RT/Queue_Overlay.pm:115 +#: lib/RT/Queue_Overlay.pm:114 msgid "DeleteTicket" msgstr "УдалÑтьЗаÑвку" -#: lib/RT/SavedSearch.pm:209 +#: lib/RT/SavedSearch.pm:208 msgid "Deleted search" -msgstr "" +msgstr "Удаленный запроÑ" #: NOT FOUND IN SOURCE msgid "Deleting this object could break referential integrity" msgstr "Удаление Ñтого объекта может нарушить ÑÑылочную целоÑтноÑть" -#: lib/RT/Queue_Overlay.pm:391 +#: lib/RT/Queue_Overlay.pm:378 msgid "Deleting this object would break referential integrity" msgstr "Удаление Ñтого объекта нарушит ÑÑылочную целоÑтноÑть" -#: lib/RT/User_Overlay.pm:513 +#: lib/RT/User_Overlay.pm:512 msgid "Deleting this object would violate referential integrity" msgstr "Удаление Ñтого объекта нарушит ÑÑылочную целоÑтноÑть" @@ -1849,22 +1822,22 @@ msgstr "От неё завиÑÑÑ‚" msgid "Dependencies: \\n" msgstr "ЗавиÑимоÑти: \\n" -#: lib/RT/Transaction_Overlay.pm:707 +#: lib/RT/Transaction_Overlay.pm.orig:705 lib/RT/Transaction_Overlay.pm:705 #. ($value) msgid "Dependency by %1 added" msgstr "Добавлено требование заÑвкой %1" -#: lib/RT/Transaction_Overlay.pm:747 +#: lib/RT/Transaction_Overlay.pm.orig:745 lib/RT/Transaction_Overlay.pm:745 #. ($value) msgid "Dependency by %1 deleted" msgstr "Удалено требование заÑвкой %1" -#: lib/RT/Transaction_Overlay.pm:704 +#: lib/RT/Transaction_Overlay.pm.orig:702 lib/RT/Transaction_Overlay.pm:702 #. ($value) msgid "Dependency on %1 added" msgstr "Добавлена завиÑимоÑть от %1" -#: lib/RT/Transaction_Overlay.pm:744 +#: lib/RT/Transaction_Overlay.pm.orig:742 lib/RT/Transaction_Overlay.pm:742 #. ($value) msgid "Dependency on %1 deleted" msgstr "Удалена завиÑимоÑть от %1" @@ -1897,7 +1870,7 @@ msgstr "ПодробноÑти" msgid "Display" msgstr "Показать" -#: lib/RT/Queue_Overlay.pm:94 +#: lib/RT/Queue_Overlay.pm:93 msgid "Display Access Control List" msgstr "Показать ÑпиÑок прав доÑтупа" @@ -1905,11 +1878,11 @@ msgstr "Показать ÑпиÑок прав доÑтупа" msgid "Display Columns" msgstr "Показать колонки" -#: lib/RT/Queue_Overlay.pm:100 +#: lib/RT/Queue_Overlay.pm:99 msgid "Display Scrip templates for this queue" msgstr "Показать шаблоны Ñкриплетов Ð´Ð»Ñ Ñтой очереди" -#: lib/RT/Queue_Overlay.pm:103 +#: lib/RT/Queue_Overlay.pm:102 msgid "Display Scrips for this queue" msgstr "Показать Ñкриплеты Ð´Ð»Ñ Ñтой очереди" @@ -1929,13 +1902,13 @@ msgstr "Показать заÑвку #%1" msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>" msgstr "РаÑпроÑтранÑетÑÑ Ð¿Ð¾ верÑии 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> GNU GPL.</a>" -#: lib/RT/System.pm:76 +#: lib/RT/System.pm:75 msgid "Do anything and everything" msgstr "Делать вÑе и везде" -#: html/Search/Build.html:112 +#: html/Search/Build.html:92 msgid "Do the Search" -msgstr "" +msgstr "ИÑкать" #: html/Elements/Refresh:51 msgid "Don't refresh this page." @@ -1984,15 +1957,19 @@ msgstr "Редактирование дополнительных полей дР#: html/Admin/Global/CustomFields/Groups.html:9 msgid "Edit Custom Fields for all groups" -msgstr "" +msgstr "Редактирование дополнительных полей вÑех групп" #: html/Admin/Global/CustomFields/Users.html:9 msgid "Edit Custom Fields for all users" -msgstr "" +msgstr "Редактирование дополнительных полей вÑех пользователей" #: html/Admin/Global/CustomFields/Queue-Tickets.html:9 html/Admin/Global/CustomFields/Queue-Transactions.html:9 msgid "Edit Custom Fields for tickets in all queues" -msgstr "" +msgstr "Редактирование дополнительных полей заÑвок во вÑех очередÑÑ…" + +#: html/Admin/Global/CustomFields/Queue-T2.html:9 +msgid "Edit Custom Fields for transactions on tickets in all queues" +msgstr "Редактирование дополнительных полей транзакций заÑвок во вÑех очередÑÑ…" #: html/Search/Bulk.html:173 html/Ticket/ModifyLinks.html:57 msgid "Edit Links" @@ -2063,15 +2040,15 @@ msgstr "Редактирование ÑпиÑка пользователей пРmsgid "Editing template %1" msgstr "Редактирование шаблона %1" -#: lib/RT/Record.pm:1281 lib/RT/Record.pm:1358 +#: lib/RT/Record.pm:1272 lib/RT/Record.pm:1349 msgid "Either base or target must be specified" msgstr "Ðужно указать либо иÑточник, либо Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ" -#: html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65 +#: html/Admin/Users/Modify.html.orig:74 html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65 msgid "Email" msgstr "Email" -#: lib/RT/User_Overlay.pm:236 +#: lib/RT/User_Overlay.pm:235 msgid "Email address in use" msgstr "Email Ð°Ð´Ñ€ÐµÑ ÑƒÐ¶Ðµ занÑÑ‚" @@ -2103,7 +2080,7 @@ msgstr "ИÑпользуемые дополнительные полÑ" msgid "Enabled Queues" msgstr "ИÑпользуемые очереди" -#: html/Admin/Elements/EditCustomField:136 html/Admin/Groups/Modify.html:150 html/Admin/Users/Modify.html:342 html/User/Groups/Modify.html:138 +#: html/Admin/Elements/EditCustomField:136 html/Admin/Groups/Modify.html:149 html/Admin/Users/Modify.html.orig:349 html/Admin/Users/Modify.html:347 html/User/Groups/Modify.html:138 #. (loc_fuzzy($msg)) msgid "Enabled status %1" msgstr "Ðктивирован ÑÑ‚Ð°Ñ‚ÑƒÑ %1" @@ -2149,7 +2126,7 @@ msgstr "Ошибка" msgid "Error adding watcher" msgstr "Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ" -#: lib/RT/Queue_Overlay.pm:669 +#: lib/RT/Queue_Overlay.pm:656 msgid "Error in parameters to Queue->AddWatcher" msgstr "Ошибка в параметрах Queue->AddWatcher" @@ -2157,7 +2134,7 @@ msgstr "Ошибка в параметрах Queue->AddWatcher" msgid "Error in parameters to Queue->DelWatcher" msgstr "Ошибка в параметрах Queue->DelWatcher" -#: lib/RT/Queue_Overlay.pm:830 +#: lib/RT/Queue_Overlay.pm:817 msgid "Error in parameters to Queue->DeleteWatcher" msgstr "Ошибка в параметрах Queue->DeleteWatcher" @@ -2201,19 +2178,19 @@ msgstr "ExternalAuthId" msgid "ExternalContactInfoId" msgstr "ExternalContactInfoId" -#: html/Admin/Users/Modify.html:99 +#: html/Admin/Users/Modify.html.orig:99 html/Admin/Users/Modify.html:99 msgid "Extra info" msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" -#: lib/RT/SavedSearch.pm:165 +#: lib/RT/SavedSearch.pm:164 msgid "Failed to create search attribute" -msgstr "" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð° запроÑа" -#: lib/RT/User_Overlay.pm:377 +#: lib/RT/User_Overlay.pm:376 msgid "Failed to find 'Privileged' users pseudogroup." msgstr "Ðевозможно найти пÑевдо-группу 'Привилегированные'" -#: lib/RT/User_Overlay.pm:384 +#: lib/RT/User_Overlay.pm:383 msgid "Failed to find 'Unprivileged' users pseudogroup" msgstr "Ðевозможно найти пÑевдо-группу 'Ðепривилегированные'" @@ -2222,12 +2199,12 @@ msgstr "Ðевозможно найти пÑевдо-группу 'ÐепривРmsgid "Failed to load module %1. (%2)" msgstr "Ошибка загрузки Ð¼Ð¾Ð´ÑƒÐ»Ñ %1. (%2)" -#: lib/RT/SavedSearch.pm:168 +#: lib/RT/SavedSearch.pm:167 #. ($privacy) msgid "Failed to load object for %1" -msgstr "" +msgstr "Ошибка загрузки объекта Ð´Ð»Ñ %1" -#: lib/RT/Date.pm:438 +#: lib/RT/Date.pm:435 msgid "Feb." msgstr "Фев." @@ -2245,7 +2222,7 @@ msgstr "Заполнить неÑколько текÑтовых полей" #: lib/RT/CustomField_Overlay.pm:74 msgid "Fill in multiple wikitext areas" -msgstr "" +msgstr "Заполнить неÑколько полей wikitext" #: lib/RT/CustomField_Overlay.pm:70 msgid "Fill in one text area" @@ -2253,7 +2230,7 @@ msgstr "Заполнить одно текÑтоввое поле" #: lib/RT/CustomField_Overlay.pm:75 msgid "Fill in one wikitext area" -msgstr "" +msgstr "Заполнить одно поле wikitext" #: lib/RT/CustomField_Overlay.pm:71 msgid "Fill in up to %1 text areas" @@ -2261,13 +2238,13 @@ msgstr "Заполнить до %1 текÑтовых полей" #: lib/RT/CustomField_Overlay.pm:76 msgid "Fill in up to %1 wikitext areas" -msgstr "" +msgstr "Заполнить до %1 полей wikitext" #: NOT FOUND IN SOURCE msgid "Fin" msgstr "Конец" -#: html/Search/Elements/PickBasics:162 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:79 lib/RT/Tickets_Overlay.pm:1569 +#: html/Search/Elements/PickBasics:162 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:79 lib/RT/Tickets_Overlay.pm:1568 msgid "Final Priority" msgstr "Конечный приоритет" @@ -2321,14 +2298,14 @@ msgstr "Изменить принудительно" #: html/Search/Elements/EditFormat:52 msgid "Format" -msgstr "" +msgstr "Форматировать" #: html/Search/Results.html:107 #. ($ticketcount) msgid "Found %quant(%1,ticket)" msgstr "Ðайдено %quant(%1,ticket)" -#: lib/RT/Record.pm:942 +#: lib/RT/Record.pm:939 msgid "Found Object" msgstr "Ðайден объект" @@ -2336,7 +2313,7 @@ msgstr "Ðайден объект" msgid "FreeformContactInfo" msgstr "FreeformContactInfo" -#: lib/RT/Date.pm:417 +#: lib/RT/Date.pm:415 msgid "Fri." msgstr "Птн." @@ -2352,7 +2329,7 @@ msgstr "ВзÑть шаблон из файла" msgid "Getting the current user from a pgp sig\\n" msgstr "Берем текущего Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· pgp подпиÑи\\n" -#: lib/RT/Transaction_Overlay.pm:673 +#: lib/RT/Transaction_Overlay.pm.orig:671 lib/RT/Transaction_Overlay.pm:671 #. ($New->Name) msgid "Given to %1" msgstr "Ðазначено %1" @@ -2371,7 +2348,7 @@ msgstr "Общие Ñкриплеты" #: html/Admin/Global/CustomFields/index.html:59 msgid "Global custom field configuration" -msgstr "" +msgstr "Конфигурирование общих дополнительных полей" #: html/Admin/Elements/SelectTemplate:59 #. (loc($Template->Name)) @@ -2410,7 +2387,7 @@ msgstr "Группа %1 %2: %3" msgid "Group Rights" msgstr "Права группы" -#: lib/RT/Group_Overlay.pm:991 +#: lib/RT/Group_Overlay.pm:989 msgid "Group already has member" msgstr "Пользователь уже входит в группу" @@ -2418,20 +2395,20 @@ msgstr "Пользователь уже входит в группу" msgid "Group could not be created." msgstr "Ðевозможно Ñоздать группу." -#: html/Admin/Groups/Modify.html:109 +#: html/Admin/Groups/Modify.html:108 #. ($create_msg) msgid "Group could not be created: %1" msgstr "Ðевозможно Ñоздать группу: %1" -#: lib/RT/Group_Overlay.pm:529 +#: lib/RT/Group_Overlay.pm:528 msgid "Group created" msgstr "Группа Ñоздана" -#: lib/RT/Group_Overlay.pm:1163 +#: lib/RT/Group_Overlay.pm:1161 msgid "Group has no such member" msgstr "Ð’ группе нет такого пользователÑ" -#: lib/RT/Group_Overlay.pm:971 lib/RT/Queue_Overlay.pm:745 lib/RT/Queue_Overlay.pm:805 lib/RT/Ticket_Overlay.pm:1391 lib/RT/Ticket_Overlay.pm:1471 +#: lib/RT/Group_Overlay.pm:969 lib/RT/Queue_Overlay.pm:732 lib/RT/Queue_Overlay.pm:792 lib/RT/Ticket_Overlay.pm:1391 lib/RT/Ticket_Overlay.pm:1471 msgid "Group not found" msgstr "Группа не найдена" @@ -2443,11 +2420,11 @@ msgstr "Группа не найдена.\\n" msgid "Group not specified.\\n" msgstr "Ðе задана группа.\\n" -#: html/Admin/Elements/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:56 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:85 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1088 +#: html/Admin/Elements/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:56 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:85 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1074 msgid "Groups" msgstr "Группы" -#: lib/RT/Group_Overlay.pm:997 +#: lib/RT/Group_Overlay.pm:995 msgid "Groups can't be members of their members" msgstr "Группы не могут быть членами входÑщих в них пользователей" @@ -2499,15 +2476,15 @@ msgstr "ИмеетÑÑ %quant(%1,concrete mixer)." msgid "I have [quant,_1,concrete mixer]." msgstr "ИмеетÑÑ [quant,_1,concrete mixer]." -#: html/Search/Build.html:637 +#: html/Search/Build.html:697 msgid "I'm lost" msgstr "" -#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1494 +#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1493 msgid "Id" msgstr "ЗаÑвка" -#: html/Admin/Users/Modify.html:65 html/User/Prefs.html:60 +#: html/Admin/Users/Modify.html.orig:65 html/Admin/Users/Modify.html:65 html/User/Prefs.html:60 msgid "Identity" msgstr "ЛичноÑть" @@ -2531,7 +2508,7 @@ msgstr "ЕÑли бы Ñта программа имела уÑтановленРmsgid "If you've updated anything above, be sure to" msgstr "ПоÑле любых изменений необходимо" -#: lib/RT/Record.pm:933 +#: lib/RT/Record.pm:931 msgid "Illegal value for %1" msgstr "ÐедопуÑтимое значение Ð´Ð»Ñ %1" @@ -2539,7 +2516,7 @@ msgstr "ÐедопуÑтимое значение Ð´Ð»Ñ %1" msgid "Image" msgstr "Изображение" -#: lib/RT/Record.pm:936 +#: lib/RT/Record.pm:934 msgid "Immutable field" msgstr "ÐеизменÑемое поле" @@ -2559,15 +2536,15 @@ msgstr "Показывать неиÑпользуемые очереди." msgid "Include disabled users in search." msgstr "Показывать отключенных пользователей." -#: html/Search/Build.html:663 +#: html/Search/Build.html:723 msgid "Incomplete Query" -msgstr "" +msgstr "Ðезавершенный запроÑ" -#: html/Search/Build.html:660 +#: html/Search/Build.html:720 msgid "Incomplete query" -msgstr "" +msgstr "Ðезавершенный запроÑ" -#: html/Search/Elements/PickBasics:161 lib/RT/Tickets_Overlay.pm:1544 +#: html/Search/Elements/PickBasics:161 lib/RT/Tickets_Overlay.pm:1543 msgid "Initial Priority" msgstr "Ðачальный приоритет" @@ -2575,24 +2552,24 @@ msgstr "Ðачальный приоритет" msgid "InitialPriority" msgstr "Ðачальный приоритет" -#: lib/RT/ScripAction_Overlay.pm:134 +#: lib/RT/ScripAction_Overlay.pm:122 msgid "Input error" msgstr "Ошибка ввода" -#: lib/RT/Ticket_Overlay.pm:3454 +#: lib/RT/Ticket_Overlay.pm:3421 msgid "Internal Error" msgstr "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°" -#: lib/RT/Record.pm:305 +#: lib/RT/Record.pm:304 #. ($id->{error_message}) msgid "Internal Error: %1" msgstr "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %1" -#: lib/RT/Group_Overlay.pm:676 +#: lib/RT/Group_Overlay.pm:675 msgid "Invalid Group Type" msgstr "ÐедопуÑтимый тип группы" -#: lib/RT/Principal_Overlay.pm:161 +#: lib/RT/Principal_Overlay.pm:166 msgid "Invalid Right" msgstr "ÐедопуÑтимое право" @@ -2600,7 +2577,7 @@ msgstr "ÐедопуÑтимое право" msgid "Invalid Type" msgstr "ÐедопуÑтимый тип" -#: lib/RT/Record.pm:938 +#: lib/RT/Record.pm:936 msgid "Invalid data" msgstr "ÐедопуÑтимые данные" @@ -2608,20 +2585,20 @@ msgstr "ÐедопуÑтимые данные" msgid "Invalid owner. Defaulting to 'nobody'." msgstr "ÐеÑущеÑтвующий ответÑтвенный. ИÑпользуем 'nobody'." -#: lib/RT/Scrip_Overlay.pm:158 lib/RT/Template_Overlay.pm:276 +#: lib/RT/Scrip_Overlay.pm:157 lib/RT/Template_Overlay.pm:276 msgid "Invalid queue" msgstr "ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´ÑŒ" -#: lib/RT/ACE_Overlay.pm:265 lib/RT/ACE_Overlay.pm:274 lib/RT/ACE_Overlay.pm:280 lib/RT/ACE_Overlay.pm:291 +#: lib/RT/ACE_Overlay.pm:274 lib/RT/ACE_Overlay.pm:283 lib/RT/ACE_Overlay.pm:289 lib/RT/ACE_Overlay.pm:300 lib/RT/ACE_Overlay.pm:305 msgid "Invalid right" msgstr "ÐедопуÑтимое право" -#: lib/RT/Record.pm:280 +#: lib/RT/Record.pm:279 #. ($key) msgid "Invalid value for %1" msgstr "ÐедопуÑтимое значение Ð´Ð»Ñ %1" -#: lib/RT/Record.pm:1597 +#: lib/RT/Record.pm:1600 msgid "Invalid value for custom field" msgstr "ÐедопуÑтимое значение дополнительного полÑ" @@ -2643,13 +2620,13 @@ msgstr "Ðто требует неÑколько параметров:" #: html/Search/Elements/EditFormat:85 msgid "Italic" -msgstr "" +msgstr "Ðаклонный" #: NOT FOUND IN SOURCE msgid "Items pending my approval" msgstr "ЗаÑвки, ожидающие вашего подтверждениÑ" -#: lib/RT/Date.pm:437 +#: lib/RT/Date.pm:434 msgid "Jan." msgstr "Янв." @@ -2661,7 +2638,7 @@ msgstr "Январь" msgid "Join or leave this group" msgstr "ПриÑоединитьÑÑ Ð¸Ð»Ð¸ покинуть Ñту группу" -#: lib/RT/Date.pm:443 +#: lib/RT/Date.pm:440 msgid "Jul." msgstr "Июл." @@ -2673,7 +2650,7 @@ msgstr "Июль" msgid "Jumbo" msgstr "Ð’Ñе данные" -#: lib/RT/Date.pm:442 +#: lib/RT/Date.pm:439 msgid "Jun." msgstr "Июн." @@ -2689,13 +2666,13 @@ msgstr "Ключевое Ñлово" msgid "Lang" msgstr "Язык" -#: html/Admin/Users/Modify.html:94 html/User/Prefs.html:76 +#: html/Admin/Users/Modify.html.orig:94 html/Admin/Users/Modify.html:94 html/User/Prefs.html:76 msgid "Language" msgstr "Язык" #: html/Search/Elements/EditFormat:79 msgid "Large" -msgstr "" +msgstr "Большой" #: html/Ticket/Elements/Tabs:96 msgid "Last" @@ -2733,11 +2710,11 @@ msgstr "ПоÑледний раз обновлено" msgid "Left" msgstr "ОÑталоÑÑŒ" -#: html/Admin/Users/Modify.html:109 +#: html/Admin/Users/Modify.html.orig:109 html/Admin/Users/Modify.html:109 msgid "Let this user access RT" msgstr "Разрешить доÑтуп к RT" -#: html/Admin/Users/Modify.html:113 +#: html/Admin/Users/Modify.html.orig:113 html/Admin/Users/Modify.html:113 msgid "Let this user be granted rights" msgstr "ПредоÑтавить пользователю права" @@ -2751,27 +2728,27 @@ msgstr "Ограничиваем очередь Ð´Ð»Ñ %1 %2" #: html/Search/Elements/EditFormat:68 msgid "Link" -msgstr "" +msgstr "СÑылка" -#: lib/RT/Record.pm:1292 +#: lib/RT/Record.pm:1283 msgid "Link already exists" msgstr "СвÑзь уже ÑущеÑтвует" -#: lib/RT/Record.pm:1306 +#: lib/RT/Record.pm:1297 msgid "Link could not be created" msgstr "Ðевозможно Ñоздать ÑвÑзь" -#: lib/RT/Record.pm:1312 +#: lib/RT/Record.pm:1303 #. ($TransString) msgid "Link created (%1)" msgstr "СвÑзь Ñоздана (%1)" -#: lib/RT/Record.pm:1373 +#: lib/RT/Record.pm:1364 #. ($TransString) msgid "Link deleted (%1)" msgstr "СвÑзь удалена (%1)" -#: lib/RT/Record.pm:1379 +#: lib/RT/Record.pm:1370 msgid "Link not found" msgstr "СвÑзь не найдена" @@ -2796,24 +2773,24 @@ msgstr "Загрузить" msgid "Load saved search:" msgstr "Загрузить Ñохраненный запроÑ:" -#: lib/RT/System.pm:87 +#: lib/RT/System.pm:86 msgid "LoadSavedSearch" -msgstr "" +msgstr "ЗагружатьСохраненныеЗапроÑÑ‹" #: html/Admin/Tools/Configuration.html:64 msgid "Loaded perl modules" msgstr "Загруженные модули perl" -#: lib/RT/SavedSearch.pm:112 +#: lib/RT/SavedSearch.pm:111 #. ($self->Name) msgid "Loaded search %1" -msgstr "" +msgstr "Загружен Ð·Ð°Ð¿Ñ€Ð¾Ñ %1" -#: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126 +#: html/Admin/Users/Modify.html.orig:138 html/Admin/Users/Modify.html:138 html/User/Prefs.html:126 msgid "Location" msgstr "МеÑтонахождение" -#: lib/RT.pm:212 +#: lib/RT.pm:204 #. ($RT::LogDir) msgid "Log directory %1 not found or couldn't be written.\\n RT can't run." msgstr "Ðе найден каталог Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ %1 или не доÑтупен на запиÑÑŒ.\\n RT не может продолжить работу." @@ -2831,7 +2808,7 @@ msgstr "Войти" msgid "Logout" msgstr "Выйти" -#: lib/RT/CustomField_Overlay.pm:866 +#: lib/RT/CustomField_Overlay.pm:852 msgid "Lookup type mismatch" msgstr "" @@ -2877,7 +2854,7 @@ msgstr "УÑтановить тему" #: lib/RT/Group_Overlay.pm:177 msgid "Make this group visible to user" -msgstr "" +msgstr "Сделать Ñту группу видимой Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" #: html/Admin/index.html:78 msgid "Manage custom fields and custom field values" @@ -2899,7 +2876,7 @@ msgstr "Управление очередÑми и их параметрами" msgid "Manage users and passwords" msgstr "Управление пользователÑми и паролÑми" -#: lib/RT/Date.pm:439 +#: lib/RT/Date.pm:436 msgid "Mar." msgstr "Мар." @@ -2911,29 +2888,29 @@ msgstr "Март" msgid "May" msgstr "Май" -#: lib/RT/Date.pm:441 +#: lib/RT/Date.pm:438 msgid "May." msgstr "Май" -#: lib/RT/Transaction_Overlay.pm:720 +#: lib/RT/Transaction_Overlay.pm.orig:718 lib/RT/Transaction_Overlay.pm:718 #. ($value) msgid "Member %1 added" msgstr "УчаÑтник %1 добавлен" -#: lib/RT/Transaction_Overlay.pm:760 +#: lib/RT/Transaction_Overlay.pm.orig:758 lib/RT/Transaction_Overlay.pm:758 #. ($value) msgid "Member %1 deleted" msgstr "УчаÑтник %1 удален" -#: lib/RT/Group_Overlay.pm:1008 +#: lib/RT/Group_Overlay.pm:1006 msgid "Member added" msgstr "Пользователь добавлен в группу" -#: lib/RT/Group_Overlay.pm:1170 +#: lib/RT/Group_Overlay.pm:1168 msgid "Member deleted" msgstr "Пользователь удален из группы" -#: lib/RT/Group_Overlay.pm:1174 +#: lib/RT/Group_Overlay.pm:1172 msgid "Member not deleted" msgstr "Пользователь не удален из группы" @@ -2949,12 +2926,12 @@ msgstr "СоÑтоит в" msgid "Members" msgstr "УчаÑтники" -#: lib/RT/Transaction_Overlay.pm:717 +#: lib/RT/Transaction_Overlay.pm.orig:715 lib/RT/Transaction_Overlay.pm:715 #. ($value) msgid "Membership in %1 added" msgstr "УчаÑтие в %1 добавлено" -#: lib/RT/Transaction_Overlay.pm:757 +#: lib/RT/Transaction_Overlay.pm.orig:755 lib/RT/Transaction_Overlay.pm:755 #. ($value) msgid "Membership in %1 deleted" msgstr "УчаÑтие в %1 удалено" @@ -2968,23 +2945,23 @@ msgstr "УчаÑтие в группах" msgid "Memberships of the user %1" msgstr "УчаÑтие в группах данного Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1" -#: lib/RT/Ticket_Overlay.pm:2849 +#: lib/RT/Ticket_Overlay.pm:2816 msgid "Merge Successful" msgstr "ЗаÑвки уÑпешно объединены" -#: lib/RT/Ticket_Overlay.pm:2736 +#: lib/RT/Ticket_Overlay.pm:2703 msgid "Merge failed. Couldn't set EffectiveId" msgstr "Ошибка объединениÑ. Ðевозможно уÑтановить идентификатор заÑвки." -#: lib/RT/Ticket_Overlay.pm:2744 +#: lib/RT/Ticket_Overlay.pm:2711 msgid "Merge failed. Couldn't set Status" -msgstr "" +msgstr "Ошибка объединениÑ. Ðевозможно уÑтановить ÑтатуÑ" #: html/Elements/EditLinks:129 html/Ticket/Elements/BulkLinks:48 msgid "Merge into" msgstr "Объединить Ñ Ð·Ð°Ñвкой" -#: lib/RT/Transaction_Overlay.pm:723 +#: lib/RT/Transaction_Overlay.pm.orig:721 lib/RT/Transaction_Overlay.pm:721 #. ($value) msgid "Merged into %1" msgstr "Объединено в %1" @@ -2997,7 +2974,7 @@ msgstr "Сообщение" msgid "Message body not shown because it is too large or is not plain text." msgstr "Тело ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð½Ðµ показано потому, что оно или Ñлишком большое или не ÑвлÑетÑÑ Ñ‚ÐµÐºÑтом." -#: lib/RT/Ticket_Overlay.pm:2406 +#: lib/RT/Ticket_Overlay.pm:2383 msgid "Message could not be recorded" msgstr "Ðевозможно запиÑать Ñообщение" @@ -3005,7 +2982,7 @@ msgstr "Ðевозможно запиÑать Ñообщение" msgid "Message recipients" msgstr "Получатели ÑообщениÑ" -#: lib/RT/Ticket_Overlay.pm:2409 +#: lib/RT/Ticket_Overlay.pm:2386 msgid "Message recorded" msgstr "Сообщение запиÑано" @@ -3013,15 +2990,15 @@ msgstr "Сообщение запиÑано" msgid "Messages about this ticket will not be sent to..." msgstr "Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± Ñтой заÑвке не будет отправлены..." -#: html/Search/Build.html:667 +#: html/Search/Build.html:727 msgid "Mismatched parentheses" -msgstr "" +msgstr "ÐеÑовпадающие Ñкобки" -#: lib/RT/Record.pm:940 +#: lib/RT/Record.pm:938 msgid "Missing a primary key?: %1" msgstr "Пропущен первичный ключ?: %1" -#: html/Admin/Users/Modify.html:193 html/User/Prefs.html:93 +#: html/Admin/Users/Modify.html.orig:193 html/Admin/Users/Modify.html:193 html/User/Prefs.html:93 msgid "Mobile" msgstr "Мобильный" @@ -3037,7 +3014,7 @@ msgstr "Изменено" msgid "Modify" msgstr "Изменить" -#: lib/RT/Queue_Overlay.pm:95 +#: lib/RT/Queue_Overlay.pm:94 msgid "Modify Access Control List" msgstr "Изменить ÑпиÑок ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð´Ð¾Ñтупа" @@ -3048,7 +3025,7 @@ msgstr "Изменить дополнительное поле %1" #: html/Admin/Elements/ObjectCustomFields:96 #. (loc(lc($FriendlySubTypes)), loc(lc($Types))) msgid "Modify Custom Fields which apply to %1 for all %2" -msgstr "" +msgstr "Изменение дополнительных полей, которые применÑÑŽÑ‚ÑÑ Ðº %1 Ð´Ð»Ñ Ð²Ñех %2" #: html/Admin/Elements/ObjectCustomFields:98 #. (loc(lc($Types))) @@ -3071,11 +3048,11 @@ msgstr "Изменить учаÑтников" msgid "Modify Rights" msgstr "Изменить права" -#: lib/RT/Queue_Overlay.pm:98 +#: lib/RT/Queue_Overlay.pm:97 msgid "Modify Scrip templates for this queue" msgstr "Изменить шаблоны Ñкриплетов Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ очереди" -#: lib/RT/Queue_Overlay.pm:101 +#: lib/RT/Queue_Overlay.pm:100 msgid "Modify Scrips for this queue" msgstr "Изменить Ñкриплеты Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ очереди" @@ -3130,7 +3107,7 @@ msgstr "Изменить даты заÑвки #%1" #: html/Admin/Elements/GlobalCustomFieldTabs:65 html/Admin/Global/index.html:72 msgid "Modify global custom fields" -msgstr "" +msgstr "Изменить общие дополнительные полÑ" #: html/Admin/Elements/GlobalCustomFieldTabs:70 html/Admin/Global/GroupRights.html:46 html/Admin/Global/GroupRights.html:49 html/Admin/Global/index.html:77 msgid "Modify global group rights" @@ -3183,7 +3160,7 @@ msgstr "Изменить права группы на очередь %1" msgid "Modify membership roster for this group" msgstr "Изменить ÑпиÑок учаÑтников Ñтой группы" -#: lib/RT/System.pm:83 +#: lib/RT/System.pm:82 msgid "Modify one's own RT account" msgstr "Изменить ÑобÑтвенную учетную запиÑÑŒ RT" @@ -3217,16 +3194,16 @@ msgstr "Изменить шаблон %1" msgid "Modify templates which apply to all queues" msgstr "Изменить шаблоны, которые применÑÑŽÑ‚ÑÑ ÐºÐ¾ вÑем очередÑм" -#: html/Admin/Groups/Modify.html:119 html/User/Groups/Modify.html:107 +#: html/Admin/Groups/Modify.html:118 html/User/Groups/Modify.html:107 #. ($Group->Name) msgid "Modify the group %1" msgstr "Изменить группу %1" -#: lib/RT/Queue_Overlay.pm:96 +#: lib/RT/Queue_Overlay.pm:95 msgid "Modify the queue watchers" msgstr "Изменить очередь наблюдателей" -#: html/Admin/Users/Modify.html:301 +#: html/Admin/Users/Modify.html.orig:303 html/Admin/Users/Modify.html:301 #. ($UserObj->Name) msgid "Modify the user %1" msgstr "Изменить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1" @@ -3241,7 +3218,7 @@ msgstr "Изменить заÑвку # %1" msgid "Modify ticket #%1" msgstr "Изменить заÑвку # %1" -#: lib/RT/Queue_Overlay.pm:114 +#: lib/RT/Queue_Overlay.pm:113 msgid "Modify tickets" msgstr "Изменить заÑвки" @@ -3264,7 +3241,7 @@ msgstr "Изменить права Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° очередь msgid "Modify watchers for queue '%1'" msgstr "Изменить наблюдателей очереди '%1'" -#: lib/RT/Queue_Overlay.pm:95 +#: lib/RT/Queue_Overlay.pm:94 msgid "ModifyACL" msgstr "ИзменÑтьПраваДоÑтупа" @@ -3276,27 +3253,27 @@ msgstr "ИзменÑтьДополнительноеПоле" msgid "ModifyOwnMembership" msgstr "ИзменÑтьСобÑтвенноеУчаÑтиеВГруппах" -#: lib/RT/Queue_Overlay.pm:96 +#: lib/RT/Queue_Overlay.pm:95 msgid "ModifyQueueWatchers" msgstr "ИзменÑтьÐаблюдателейОчереди" -#: lib/RT/Queue_Overlay.pm:101 +#: lib/RT/Queue_Overlay.pm:100 msgid "ModifyScrips" msgstr "ИзменÑтьСкриплеты" -#: lib/RT/System.pm:83 +#: lib/RT/System.pm:82 msgid "ModifySelf" msgstr "ИзменÑтьСебÑ" -#: lib/RT/Queue_Overlay.pm:98 +#: lib/RT/Queue_Overlay.pm:97 msgid "ModifyTemplate" msgstr "ИзменÑтьШÐ°Ð±Ð»Ð¾Ð½" -#: lib/RT/Queue_Overlay.pm:114 +#: lib/RT/Queue_Overlay.pm:113 msgid "ModifyTicket" msgstr "ИзменÑтьЗаÑвку" -#: lib/RT/Date.pm:413 +#: lib/RT/Date.pm:411 msgid "Mon." msgstr "Пнд." @@ -3321,7 +3298,7 @@ msgstr "ПеремеÑтить вверх" msgid "Multiple" msgstr "ÐеÑколько значений" -#: lib/RT/User_Overlay.pm:227 +#: lib/RT/User_Overlay.pm:226 msgid "Must specify 'Name' attribute" msgstr "Ð’Ñ‹ должны указать ИмÑ" @@ -3346,7 +3323,7 @@ msgstr "Ваши Ñохраненные запроÑÑ‹" msgid "Name" msgstr "ИмÑ" -#: lib/RT/User_Overlay.pm:234 +#: lib/RT/User_Overlay.pm:233 msgid "Name in use" msgstr "Ð˜Ð¼Ñ ÑƒÐ¶Ðµ иÑпользуетÑÑ" @@ -3370,7 +3347,7 @@ msgstr "Ðовых" msgid "New Links" msgstr "Ðовые ÑвÑзи" -#: html/Admin/Users/Modify.html:119 html/User/Prefs.html:109 +#: html/Admin/Users/Modify.html.orig:119 html/Admin/Users/Modify.html:119 html/User/Prefs.html:109 msgid "New Password" msgstr "Ðовый пароль" @@ -3378,7 +3355,7 @@ msgstr "Ðовый пароль" msgid "New Pending Approval" msgstr "Ðовое ожидающее подтверждение" -#: html/Ticket/Elements/Tabs:193 +#: html/Ticket/Elements/Tabs.rej:16 html/Ticket/Elements/Tabs.rej:8 html/Ticket/Elements/Tabs:193 msgid "New Query" msgstr "Ðовый запроÑ" @@ -3402,7 +3379,7 @@ msgstr "ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°" msgid "New password" msgstr "Ðовый пароль" -#: lib/RT/User_Overlay.pm:817 +#: lib/RT/User_Overlay.pm:814 msgid "New password notification sent" msgstr "Отправлено Ñообщение Ñ Ð½Ð¾Ð²Ñ‹Ð¼ паролем" @@ -3434,7 +3411,7 @@ msgstr "Ðовый шаблон" msgid "New ticket" msgstr "ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ñвка" -#: lib/RT/Ticket_Overlay.pm:2713 +#: lib/RT/Ticket_Overlay.pm:2680 msgid "New ticket doesn't exist" msgstr "ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ñвка не ÑущеÑтвует" @@ -3470,7 +3447,7 @@ msgstr "Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтраница" msgid "NickName" msgstr "ПÑевдоним" -#: html/Admin/Users/Modify.html:84 html/User/Prefs.html:72 +#: html/Admin/Users/Modify.html.orig:84 html/Admin/Users/Modify.html:84 html/User/Prefs.html:72 msgid "Nickname" msgstr "ПÑевдоним" @@ -3480,7 +3457,7 @@ msgstr "Ðет" #: html/Admin/CustomFields/UserRights.html:145 msgid "No Class defined" -msgstr "" +msgstr "КлаÑÑ Ð½Ðµ указан" #: html/Admin/CustomFields/Modify.html:124 html/Admin/Elements/EditCustomField:119 msgid "No CustomField" @@ -3522,7 +3499,7 @@ msgstr "ЗаÑвка не указана. ОтменÑем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð· msgid "No action" msgstr "Ðет дейÑтвиÑ" -#: lib/RT/Record.pm:935 +#: lib/RT/Record.pm:933 msgid "No column specified" msgstr "Колонка не указана" @@ -3538,12 +3515,12 @@ msgstr "Ðет комментариев о пользователе" msgid "No correspondence attached" msgstr "ПуÑтое Ñообщение" -#: lib/RT/Action/Generic.pm:186 lib/RT/Condition/Generic.pm:198 lib/RT/Search/ActiveTicketsInQueue.pm:78 lib/RT/Search/Generic.pm:135 +#: lib/RT/Action/Generic.pm:178 lib/RT/Condition/Generic.pm:197 lib/RT/Search/ActiveTicketsInQueue.pm:77 lib/RT/Search/Generic.pm:134 #. (ref $self) msgid "No description for %1" msgstr "Ðет опиÑÐ°Ð½Ð¸Ñ Ð´Ð»Ñ %1" -#: lib/RT/Users_Overlay.pm:185 +#: lib/RT/Users_Overlay.pm:184 msgid "No group specified" msgstr "Ðе указана группа" @@ -3551,15 +3528,15 @@ msgstr "Ðе указана группа" msgid "No groups matching search criteria found." msgstr "Группы, удовелÑтворÑющие уÑловию поиÑка, не найдены." -#: lib/RT/Ticket_Overlay.pm:2349 +#: lib/RT/Ticket_Overlay.pm:2344 msgid "No message attached" msgstr "Ðет приÑоединенных Ñообщений" -#: lib/RT/User_Overlay.pm:1035 +#: lib/RT/User_Overlay.pm:1032 msgid "No password set" msgstr "Пароль не уÑтановлен" -#: lib/RT/Queue_Overlay.pm:358 +#: lib/RT/Queue_Overlay.pm:345 msgid "No permission to create queues" msgstr "Ðет прав Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´ÐµÐ¹" @@ -3568,11 +3545,11 @@ msgstr "Ðет прав Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´ÐµÐ¹" msgid "No permission to create tickets in the queue '%1'" msgstr "Ðет прав Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð·Ð°Ñвок в очереди '%1'" -#: lib/RT/User_Overlay.pm:187 +#: lib/RT/User_Overlay.pm:186 msgid "No permission to create users" msgstr "Ðет прав Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¹" -#: html/SelfService/Display.html:165 +#: html/SelfService/Display.html:158 msgid "No permission to display that ticket" msgstr "Ðет прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñтой заÑвки" @@ -3580,7 +3557,7 @@ msgstr "Ðет прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñтой заÑвки" msgid "No permission to view update ticket" msgstr "Ðет прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра обновлений Ñтой заÑвки" -#: lib/RT/Queue_Overlay.pm:792 lib/RT/Ticket_Overlay.pm:1450 +#: lib/RT/Queue_Overlay.pm:779 lib/RT/Ticket_Overlay.pm:1450 msgid "No principal specified" msgstr "Пользователь не указан" @@ -3600,9 +3577,9 @@ msgstr "Права не найдены" msgid "No rights granted." msgstr "Права не выданы." -#: lib/RT/SavedSearch.pm:187 +#: lib/RT/SavedSearch.pm:186 msgid "No search loaded" -msgstr "" +msgstr "Ðи один Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ðµ загружен" #: html/Search/Bulk.html:194 msgid "No search to operate on." @@ -3616,7 +3593,7 @@ msgstr "Ðет темы" msgid "No ticket id specified" msgstr "Идентификатор заÑвки не указан" -#: lib/RT/Transaction_Overlay.pm:517 lib/RT/Transaction_Overlay.pm:554 +#: lib/RT/Transaction_Overlay.pm.orig:515 lib/RT/Transaction_Overlay.pm.orig:552 lib/RT/Transaction_Overlay.pm:515 lib/RT/Transaction_Overlay.pm:552 msgid "No transaction type specified" msgstr "Тип транзакции не указан" @@ -3632,7 +3609,7 @@ msgstr "Пользователи, удовелÑтворÑющие уÑÐ»Ð¾Ð²Ð¸Ñ msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n" msgstr "Ðе найден пользователь RT. Обработчик CVS отключен. ОбратитеÑÑŒ к админиÑтратору RT.\\n" -#: lib/RT/Record.pm:932 +#: lib/RT/Record.pm:930 msgid "No value sent to _Set!\\n" msgstr "Ðикакое значение не отправлено _Set!\\n" @@ -3640,7 +3617,7 @@ msgstr "Ðикакое значение не отправлено _Set!\\n" msgid "Nobody" msgstr "Ðикто" -#: lib/RT/Record.pm:937 +#: lib/RT/Record.pm:935 msgid "Nonexistant field?" msgstr "ÐеÑущеÑтвующее поле?" @@ -3652,7 +3629,7 @@ msgstr "Ðе зарегиÑтрирован" msgid "Not logged in." msgstr "Ðе зарегиÑтрирован." -#: lib/RT/Date.pm:393 +#: lib/RT/Date.pm:392 msgid "Not set" msgstr "Ðе уÑтановлено" @@ -3668,7 +3645,7 @@ msgstr "Еще не реализовано..." msgid "Notes" msgstr "ПримечаниÑ" -#: lib/RT/User_Overlay.pm:820 +#: lib/RT/User_Overlay.pm:817 msgid "Notification could not be sent" msgstr "Ðевозможно отправить уведомление" @@ -3740,7 +3717,7 @@ msgstr "УведомлÑть Ðвторов заÑвки, Копии и Ðдми msgid "Notify Requestors, Ccs and AdminCcs as Comment" msgstr "УведомлÑть Ðвторов заÑвки, Копии и ÐдминиÑтративныеКопии как Комментарии" -#: lib/RT/Date.pm:447 +#: lib/RT/Date.pm:444 msgid "Nov." msgstr "ÐоÑ." @@ -3750,23 +3727,23 @@ msgstr "ÐоÑбрь" #: html/Search/Elements/SelectAndOr:47 msgid "OR" -msgstr "" +msgstr "ИЛИ" -#: lib/RT/Record.pm:319 +#: lib/RT/Record.pm:318 msgid "Object could not be created" msgstr "Ðевозможно Ñоздать объект" -#: lib/RT/Record.pm:124 +#: lib/RT/Record.pm:123 msgid "Object could not be deleted" -msgstr "" +msgstr "Объект не может быть удален" -#: lib/RT/Record.pm:338 +#: lib/RT/Record.pm:337 msgid "Object created" msgstr "Объект Ñоздан" -#: lib/RT/Record.pm:121 +#: lib/RT/Record.pm:120 msgid "Object deleted" -msgstr "" +msgstr "Объект удален" #: html/Admin/CustomFields/Objects.html:72 html/Admin/Elements/ObjectCustomFields:63 #. ($ObjectType) @@ -3774,11 +3751,11 @@ msgstr "" msgid "Object of type %1 cannot take custom fields" msgstr "Тип объекта %1 не может Ñодержать дополнительные полÑ" -#: lib/RT/CustomField_Overlay.pm:901 +#: lib/RT/CustomField_Overlay.pm:887 msgid "Object type mismatch" msgstr "ÐеÑовпадение типа объекта" -#: lib/RT/Date.pm:446 +#: lib/RT/Date.pm:443 msgid "Oct." msgstr "Окт." @@ -3884,7 +3861,7 @@ msgstr "Сортировать по" msgid "Ordering and sorting" msgstr "ПорÑдок и Ñортировка" -#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:129 +#: html/Admin/Users/Modify.html.orig:141 html/Admin/Users/Modify.html:141 html/User/Prefs.html:129 msgid "Organization" msgstr "ОрганизациÑ" @@ -3893,11 +3870,11 @@ msgstr "ОрганизациÑ" msgid "Originating ticket: #%1" msgstr "ЗаÑвка-первоиÑточник: #%1" -#: lib/RT/Transaction_Overlay.pm:611 +#: lib/RT/Transaction_Overlay.pm.orig:609 lib/RT/Transaction_Overlay.pm:609 msgid "Outgoing email about a comment recorded" msgstr "ИÑходÑÑ‰Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° о комментарии запиÑана" -#: lib/RT/Transaction_Overlay.pm:615 +#: lib/RT/Transaction_Overlay.pm.orig:613 lib/RT/Transaction_Overlay.pm:613 msgid "Outgoing email recorded" msgstr "ИÑходÑÑ‰Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° запиÑана" @@ -3909,15 +3886,15 @@ msgstr "Со временем поднÑть приоритет до" msgid "Overview" msgstr "Обзор" -#: lib/RT/Queue_Overlay.pm:113 +#: lib/RT/Queue_Overlay.pm:112 msgid "Own tickets" msgstr "Ðазначить ÑебÑ" -#: lib/RT/Queue_Overlay.pm:113 +#: lib/RT/Queue_Overlay.pm:112 msgid "OwnTicket" msgstr "БытьОтветÑтвеннымЗаЗаÑвку" -#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:101 html/SelfService/Elements/MyRequests:51 html/Ticket/Create.html:69 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:111 lib/RT/Tickets_Overlay.pm:1734 +#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:101 html/SelfService/Elements/MyRequests:51 html/Ticket/Create.html:69 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:110 lib/RT/Tickets_Overlay.pm:1733 msgid "Owner" msgstr "ОтветÑтвенный" @@ -3929,7 +3906,7 @@ msgstr "ОтветÑтвенный изменен Ñ %1 на %2" msgid "Owner could not be set." msgstr "ОтветÑтвенный не может быть назначен" -#: lib/RT/Transaction_Overlay.pm:661 +#: lib/RT/Transaction_Overlay.pm.orig:659 lib/RT/Transaction_Overlay.pm:659 #. ($Old->Name , $New->Name) msgid "Owner forcibly changed from %1 to %2" msgstr "ОтветÑтвенный принудительно изменен Ñ %1 на %2" @@ -3943,7 +3920,7 @@ msgstr "ОтветÑтвенный" msgid "Page %1 of %2" msgstr "Страница %1 из %2" -#: html/Admin/Users/Modify.html:198 html/User/Prefs.html:97 +#: html/Admin/Users/Modify.html.orig:198 html/Admin/Users/Modify.html:198 html/User/Prefs.html:97 msgid "Pager" msgstr "Пейджер" @@ -3967,33 +3944,25 @@ msgstr "Пароль" msgid "Password Reminder" msgstr "ПодÑказка к паролю" -#: lib/RT/Transaction_Overlay.pm:770 lib/RT/User_Overlay.pm:1046 +#: lib/RT/Transaction_Overlay.pm.orig:768 lib/RT/Transaction_Overlay.pm:768 lib/RT/User_Overlay.pm:1041 msgid "Password changed" -msgstr "" +msgstr "Пароль изменен" -#: lib/RT/User_Overlay.pm:1038 lib/RT/User_Overlay.pm:215 +#: lib/RT/User_Overlay.pm:1035 lib/RT/User_Overlay.pm:214 #. ($RT::MinimumPasswordLength) msgid "Password needs to be at least %1 characters long" -msgstr "" - -#: lib/RT/User_Overlay.pm:1045 -msgid "Password set" -msgstr "" +msgstr "Пароль должен ÑоÑтоÑÑ‚ не менее чем из %1 Ñимволов" #: NOT FOUND IN SOURCE msgid "Password too short" msgstr "Пароль Ñлишком короткий" -#: html/User/Prefs.html:232 +#: html/Admin/Users/Modify.html.orig:357 html/Admin/Users/Modify.html:355 html/User/Prefs.html:232 #. (loc_fuzzy($msg)) msgid "Password: %1" msgstr "Пароль: %1" -#: lib/RT/User_Overlay.pm:1031 -msgid "Password: Permission Denied" -msgstr "" - -#: html/Admin/Users/Modify.html:356 +#: html/Admin/Users/Modify.html.orig:359 html/Admin/Users/Modify.html:357 msgid "Passwords do not match." msgstr "Пароли не Ñовпадают" @@ -4013,7 +3982,7 @@ msgstr "Выволнить дейÑтвие, указанное пользова msgid "Perl configuration" msgstr "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Perl" -#: lib/RT/ACE_Overlay.pm:252 lib/RT/ACE_Overlay.pm:258 lib/RT/ACE_Overlay.pm:581 lib/RT/ACE_Overlay.pm:591 lib/RT/ACE_Overlay.pm:601 lib/RT/ACE_Overlay.pm:666 lib/RT/Attribute_Overlay.pm:158 lib/RT/Attribute_Overlay.pm:164 lib/RT/Attribute_Overlay.pm:402 lib/RT/Attribute_Overlay.pm:411 lib/RT/Attribute_Overlay.pm:424 lib/RT/CurrentUser.pm:117 lib/RT/CurrentUser.pm:126 lib/RT/CustomField_Overlay.pm:1022 lib/RT/CustomField_Overlay.pm:167 lib/RT/CustomField_Overlay.pm:184 lib/RT/CustomField_Overlay.pm:195 lib/RT/CustomField_Overlay.pm:366 lib/RT/CustomField_Overlay.pm:398 lib/RT/CustomField_Overlay.pm:697 lib/RT/CustomField_Overlay.pm:870 lib/RT/CustomField_Overlay.pm:905 lib/RT/CustomField_Overlay.pm:950 lib/RT/Group_Overlay.pm:1125 lib/RT/Group_Overlay.pm:1129 lib/RT/Group_Overlay.pm:1138 lib/RT/Group_Overlay.pm:1248 lib/RT/Group_Overlay.pm:1252 lib/RT/Group_Overlay.pm:1258 lib/RT/Group_Overlay.pm:453 lib/RT/Group_Overlay.pm:550 lib/RT/Group_Overlay.pm:628 lib/RT/Group_Overlay.pm:636 lib/RT/Group_Overlay.pm:734 lib/RT/Group_Overlay.pm:738 lib/RT/Group_Overlay.pm:744 lib/RT/Group_Overlay.pm:930 lib/RT/Group_Overlay.pm:934 lib/RT/Group_Overlay.pm:947 lib/RT/Queue_Overlay.pm:1051 lib/RT/Queue_Overlay.pm:141 lib/RT/Queue_Overlay.pm:159 lib/RT/Queue_Overlay.pm:654 lib/RT/Queue_Overlay.pm:664 lib/RT/Queue_Overlay.pm:678 lib/RT/Queue_Overlay.pm:816 lib/RT/Queue_Overlay.pm:825 lib/RT/Queue_Overlay.pm:838 lib/RT/Scrip_Overlay.pm:150 lib/RT/Scrip_Overlay.pm:161 lib/RT/Scrip_Overlay.pm:226 lib/RT/Scrip_Overlay.pm:540 lib/RT/Template_Overlay.pm:112 lib/RT/Template_Overlay.pm:118 lib/RT/Template_Overlay.pm:309 lib/RT/Ticket_Overlay.pm:1318 lib/RT/Ticket_Overlay.pm:1328 lib/RT/Ticket_Overlay.pm:1342 lib/RT/Ticket_Overlay.pm:1483 lib/RT/Ticket_Overlay.pm:1493 lib/RT/Ticket_Overlay.pm:1507 lib/RT/Ticket_Overlay.pm:1624 lib/RT/Ticket_Overlay.pm:1944 lib/RT/Ticket_Overlay.pm:2082 lib/RT/Ticket_Overlay.pm:2252 lib/RT/Ticket_Overlay.pm:2302 lib/RT/Ticket_Overlay.pm:2474 lib/RT/Ticket_Overlay.pm:2577 lib/RT/Ticket_Overlay.pm:2625 lib/RT/Ticket_Overlay.pm:2704 lib/RT/Ticket_Overlay.pm:2718 lib/RT/Ticket_Overlay.pm:2942 lib/RT/Ticket_Overlay.pm:2952 lib/RT/Ticket_Overlay.pm:2957 lib/RT/Ticket_Overlay.pm:3180 lib/RT/Ticket_Overlay.pm:3184 lib/RT/Ticket_Overlay.pm:3327 lib/RT/Ticket_Overlay.pm:3448 lib/RT/Transaction_Overlay.pm:505 lib/RT/Transaction_Overlay.pm:512 lib/RT/Transaction_Overlay.pm:540 lib/RT/Transaction_Overlay.pm:547 lib/RT/User_Overlay.pm:1184 lib/RT/User_Overlay.pm:1725 lib/RT/User_Overlay.pm:370 lib/RT/User_Overlay.pm:736 lib/RT/User_Overlay.pm:775 +#: lib/RT/ACE_Overlay.pm:261 lib/RT/ACE_Overlay.pm:267 lib/RT/ACE_Overlay.pm:593 lib/RT/ACE_Overlay.pm:603 lib/RT/ACE_Overlay.pm:613 lib/RT/ACE_Overlay.pm:678 lib/RT/Attribute_Overlay.pm:158 lib/RT/Attribute_Overlay.pm:164 lib/RT/Attribute_Overlay.pm:402 lib/RT/Attribute_Overlay.pm:411 lib/RT/Attribute_Overlay.pm:424 lib/RT/CurrentUser.pm:116 lib/RT/CurrentUser.pm:125 lib/RT/CustomField_Overlay.pm:1008 lib/RT/CustomField_Overlay.pm:162 lib/RT/CustomField_Overlay.pm:179 lib/RT/CustomField_Overlay.pm:190 lib/RT/CustomField_Overlay.pm:360 lib/RT/CustomField_Overlay.pm:392 lib/RT/CustomField_Overlay.pm:684 lib/RT/CustomField_Overlay.pm:856 lib/RT/CustomField_Overlay.pm:891 lib/RT/CustomField_Overlay.pm:936 lib/RT/Group_Overlay.pm:1123 lib/RT/Group_Overlay.pm:1127 lib/RT/Group_Overlay.pm:1136 lib/RT/Group_Overlay.pm:1246 lib/RT/Group_Overlay.pm:1250 lib/RT/Group_Overlay.pm:1256 lib/RT/Group_Overlay.pm:452 lib/RT/Group_Overlay.pm:549 lib/RT/Group_Overlay.pm:627 lib/RT/Group_Overlay.pm:635 lib/RT/Group_Overlay.pm:732 lib/RT/Group_Overlay.pm:736 lib/RT/Group_Overlay.pm:742 lib/RT/Group_Overlay.pm:928 lib/RT/Group_Overlay.pm:932 lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:1038 lib/RT/Queue_Overlay.pm:140 lib/RT/Queue_Overlay.pm:158 lib/RT/Queue_Overlay.pm:641 lib/RT/Queue_Overlay.pm:651 lib/RT/Queue_Overlay.pm:665 lib/RT/Queue_Overlay.pm:803 lib/RT/Queue_Overlay.pm:812 lib/RT/Queue_Overlay.pm:825 lib/RT/Scrip_Overlay.pm:149 lib/RT/Scrip_Overlay.pm:160 lib/RT/Scrip_Overlay.pm:225 lib/RT/Scrip_Overlay.pm:539 lib/RT/Template_Overlay.pm:112 lib/RT/Template_Overlay.pm:118 lib/RT/Template_Overlay.pm:309 lib/RT/Ticket_Overlay.pm:1318 lib/RT/Ticket_Overlay.pm:1328 lib/RT/Ticket_Overlay.pm:1342 lib/RT/Ticket_Overlay.pm:1483 lib/RT/Ticket_Overlay.pm:1493 lib/RT/Ticket_Overlay.pm:1507 lib/RT/Ticket_Overlay.pm:1624 lib/RT/Ticket_Overlay.pm:1944 lib/RT/Ticket_Overlay.pm:2082 lib/RT/Ticket_Overlay.pm:2250 lib/RT/Ticket_Overlay.pm:2297 lib/RT/Ticket_Overlay.pm:2451 lib/RT/Ticket_Overlay.pm:2552 lib/RT/Ticket_Overlay.pm:2592 lib/RT/Ticket_Overlay.pm:2671 lib/RT/Ticket_Overlay.pm:2685 lib/RT/Ticket_Overlay.pm:2909 lib/RT/Ticket_Overlay.pm:2919 lib/RT/Ticket_Overlay.pm:2924 lib/RT/Ticket_Overlay.pm:3147 lib/RT/Ticket_Overlay.pm:3151 lib/RT/Ticket_Overlay.pm:3294 lib/RT/Ticket_Overlay.pm:3415 lib/RT/Transaction_Overlay.pm.orig:503 lib/RT/Transaction_Overlay.pm.orig:510 lib/RT/Transaction_Overlay.pm.orig:538 lib/RT/Transaction_Overlay.pm.orig:545 lib/RT/Transaction_Overlay.pm:503 lib/RT/Transaction_Overlay.pm:510 lib/RT/Transaction_Overlay.pm:538 lib/RT/Transaction_Overlay.pm:545 lib/RT/User_Overlay.pm:1028 lib/RT/User_Overlay.pm:1179 lib/RT/User_Overlay.pm:1720 lib/RT/User_Overlay.pm:369 lib/RT/User_Overlay.pm:733 lib/RT/User_Overlay.pm:772 msgid "Permission Denied" msgstr "Ðет доÑтупа" @@ -4029,7 +3998,7 @@ msgstr "Личные группы" msgid "Personal groups:" msgstr "Личные группы:" -#: html/Admin/Users/Modify.html:180 html/User/Prefs.html:82 +#: html/Admin/Users/Modify.html.orig:180 html/Admin/Users/Modify.html:180 html/User/Prefs.html:82 msgid "Phone numbers" msgstr "Ðомера телефонов" @@ -4037,7 +4006,7 @@ msgstr "Ðомера телефонов" msgid "Placeholder" msgstr "Заполнитель" -#: html/Elements/Header:87 html/Elements/Tabs:88 html/SelfService/Elements/Tabs:75 html/SelfService/Prefs.html:46 html/User/Prefs.html:46 html/User/Prefs.html:49 +#: html/Elements/Header:87 html/Elements/Tabs:88 html/SelfService/Elements/Tabs:72 html/SelfService/Prefs.html:46 html/User/Prefs.html:46 html/User/Prefs.html:49 msgid "Preferences" msgstr "ÐаÑтройки" @@ -4045,7 +4014,7 @@ msgstr "ÐаÑтройки" msgid "Prefs" msgstr "ÐаÑтройки" -#: lib/RT/Action/Generic.pm:196 +#: lib/RT/Action/Generic.pm:188 msgid "Prepare Stubbed" msgstr "Подготовка не реализована" @@ -4065,12 +4034,12 @@ msgstr "ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ñтраница" msgid "Pri" msgstr "Приоритет" -#: lib/RT/ACE_Overlay.pm:158 lib/RT/ACE_Overlay.pm:240 lib/RT/ACE_Overlay.pm:570 +#: lib/RT/ACE_Overlay.pm:157 lib/RT/ACE_Overlay.pm:238 lib/RT/ACE_Overlay.pm:582 #. ($args{'PrincipalId'}) msgid "Principal %1 not found." msgstr "Пользователь %1 не найден." -#: html/Search/Elements/PickBasics:160 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:74 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1518 +#: html/Search/Elements/PickBasics:160 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:74 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1517 msgid "Priority" msgstr "Приоритет" @@ -4086,7 +4055,7 @@ msgstr "КонфиденциальноÑть:" msgid "Privileged" msgstr "Привилегированные" -#: html/Admin/Users/Modify.html:334 html/User/Prefs.html:223 +#: html/Admin/Users/Modify.html.orig:337 html/Admin/Users/Modify.html:335 html/User/Prefs.html:223 #. (loc_fuzzy($msg)) msgid "Privileged status: %1" msgstr "СоÑтоÑние привилегий: %1" @@ -4107,11 +4076,11 @@ msgstr "ПÑевдогруппы Ð´Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ³Ð¾ иÑпользоРmsgid "Query" msgstr "ЗапроÑ" -#: html/Search/Build.html:124 html/Ticket/Elements/Tabs:195 +#: html/Search/Build.html:103 html/Ticket/Elements/Tabs:195 msgid "Query Builder" msgstr "ПоÑтроитель запроÑа" -#: html/Elements/QuickCreate:55 html/Elements/Quicksearch:50 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:59 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1345 +#: html/Elements/QuickCreate:55 html/Elements/Quicksearch:50 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:59 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1344 msgid "Queue" msgstr "Очередь" @@ -4133,11 +4102,11 @@ msgstr "Ðаименование очереди" msgid "Queue Scrips" msgstr "Скриплеты очереди" -#: lib/RT/Queue_Overlay.pm:362 +#: lib/RT/Queue_Overlay.pm:349 msgid "Queue already exists" msgstr "Очередь уже ÑущеÑтвует" -#: lib/RT/Queue_Overlay.pm:371 lib/RT/Queue_Overlay.pm:377 +#: lib/RT/Queue_Overlay.pm:358 lib/RT/Queue_Overlay.pm:364 msgid "Queue could not be created" msgstr "Ðевозможно Ñоздать очередь" @@ -4145,7 +4114,7 @@ msgstr "Ðевозможно Ñоздать очередь" msgid "Queue could not be loaded." msgstr "Ðевозможно загрузить очередь" -#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:381 lib/RT/StyleGuide.pod:809 +#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:368 lib/RT/StyleGuide.pod:809 msgid "Queue created" msgstr "Очередь Ñоздана" @@ -4153,7 +4122,7 @@ msgstr "Очередь Ñоздана" msgid "Queue is not specified." msgstr "Очередь не указана." -#: html/SelfService/Display.html:102 lib/RT/CustomField_Overlay.pm:192 +#: html/SelfService/Display.html:95 lib/RT/CustomField_Overlay.pm:187 msgid "Queue not found" msgstr "Очередь не найдена" @@ -4211,7 +4180,7 @@ msgstr "Ошибка конфигурации RT" msgid "RT Critical error. Message not recorded!" msgstr "КритичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° RT: Сообщение не было Ñохранено!" -#: html/Elements/Error:65 html/SelfService/Error.html:62 +#: html/Elements/Error:63 html/SelfService/Error.html:62 msgid "RT Error" msgstr "Ошибка RT" @@ -4253,13 +4222,13 @@ msgstr "RT не может найти очередь: %1" #: html/Elements/SetupSessionCookie:90 msgid "RT couldn't store your session." -msgstr "" +msgstr "Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð²Ð°ÑˆÐµÐ¹ ÑеÑÑии" #: NOT FOUND IN SOURCE msgid "RT couldn't validate this PGP signature. \\n" msgstr "RT не может проверить Ñту подпиÑÑŒ PGP. \\n" -#: html/Elements/PageLayout:108 +#: html/Elements/PageLayout:107 #. ($RT::rtname) msgid "RT for %1" msgstr "RT Ð´Ð»Ñ %1" @@ -4296,7 +4265,7 @@ msgstr "RT будет обрабатывать Ñто Ñообщение как msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified." msgstr "Командный режим RT email требует иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи .PGP. Ð’Ñ‹ либо не подпиÑали Ñообщение, либо ваша подпиÑÑŒ не может быть проверена." -#: html/Admin/Users/Modify.html:79 html/User/Prefs.html:69 +#: html/Admin/Users/Modify.html.orig:79 html/Admin/Users/Modify.html:79 html/User/Prefs.html:69 msgid "Real Name" msgstr "Полное имÑ" @@ -4304,22 +4273,22 @@ msgstr "Полное имÑ" msgid "RealName" msgstr "Полное имÑ" -#: lib/RT/Transaction_Overlay.pm:714 +#: lib/RT/Transaction_Overlay.pm.orig:712 lib/RT/Transaction_Overlay.pm:712 #. ($value) msgid "Reference by %1 added" msgstr "СÑылка на заÑвку заÑвки %1 добавлена" -#: lib/RT/Transaction_Overlay.pm:754 +#: lib/RT/Transaction_Overlay.pm.orig:752 lib/RT/Transaction_Overlay.pm:752 #. ($value) msgid "Reference by %1 deleted" msgstr "СÑылка на заÑвку заÑвки %1 удалена" -#: lib/RT/Transaction_Overlay.pm:711 +#: lib/RT/Transaction_Overlay.pm.orig:709 lib/RT/Transaction_Overlay.pm:709 #. ($value) msgid "Reference to %1 added" msgstr "СÑылка на %1 добавлена" -#: lib/RT/Transaction_Overlay.pm:751 +#: lib/RT/Transaction_Overlay.pm.orig:749 lib/RT/Transaction_Overlay.pm:749 #. ($value) msgid "Reference to %1 deleted" msgstr "СÑылка на %1 удалена" @@ -4365,7 +4334,7 @@ msgstr "Удалить копию" msgid "Remove Requestor" msgstr "Удалить Ðвтора ЗаÑвки" -#: html/Ticket/Elements/ShowTransaction:171 html/Ticket/Elements/Tabs:145 +#: html/Ticket/Elements/ShowTransaction:170 html/Ticket/Elements/Tabs:145 msgid "Reply" msgstr "Ответить" @@ -4377,15 +4346,15 @@ msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð°" msgid "Reply to requestors" msgstr "Отвечать авторам заÑвки" -#: lib/RT/Queue_Overlay.pm:111 +#: lib/RT/Queue_Overlay.pm:110 msgid "Reply to tickets" msgstr "Отвечать на заÑвки" -#: lib/RT/Queue_Overlay.pm:111 +#: lib/RT/Queue_Overlay.pm:110 msgid "ReplyToTicket" msgstr "ОтвечатьÐаЗаÑвку" -#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:112 +#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:111 msgid "Requestor" msgstr "Ðвтор заÑвки" @@ -4418,7 +4387,7 @@ msgstr "Ðеобходимый параметр '%1' не указан" msgid "Reset" msgstr "ОчиÑтить" -#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:85 +#: html/Admin/Users/Modify.html.orig:183 html/Admin/Users/Modify.html:183 html/User/Prefs.html:85 msgid "Residence" msgstr "Домашний" @@ -4426,7 +4395,7 @@ msgstr "Домашний" msgid "Resolve" msgstr "Решить" -#: html/Ticket/Update.html:154 +#: html/Ticket/Update.html:151 #. ($TicketObj->id, $TicketObj->Subject) msgid "Resolve ticket #%1 (%2)" msgstr "Решить заÑвка #%1 (%2)" @@ -4447,7 +4416,7 @@ msgstr "Результаты" msgid "Results per page" msgstr "Результатов на Ñтранице" -#: html/Admin/Users/Modify.html:126 html/User/Prefs.html:116 +#: html/Admin/Users/Modify.html.orig:126 html/Admin/Users/Modify.html:126 html/User/Prefs.html:116 msgid "Retype Password" msgstr "Повторите пароль" @@ -4459,19 +4428,19 @@ msgstr "Обратить" msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n" msgstr "Право %1 не найдено Ð´Ð»Ñ %2 %3 в рамках %4 (%5)\\n" -#: lib/RT/ACE_Overlay.pm:631 +#: lib/RT/ACE_Overlay.pm:643 msgid "Right Delegated" msgstr "Право делегировано" -#: lib/RT/ACE_Overlay.pm:321 +#: lib/RT/ACE_Overlay.pm:333 msgid "Right Granted" msgstr "Право выдано" -#: lib/RT/ACE_Overlay.pm:179 +#: lib/RT/ACE_Overlay.pm:185 msgid "Right Loaded" msgstr "Право загружено" -#: lib/RT/ACE_Overlay.pm:696 lib/RT/ACE_Overlay.pm:717 +#: lib/RT/ACE_Overlay.pm:708 lib/RT/ACE_Overlay.pm:729 msgid "Right could not be revoked" msgstr "Право не может быть отозвано" @@ -4479,11 +4448,11 @@ msgstr "Право не может быть отозвано" msgid "Right not found" msgstr "Право не найдено" -#: lib/RT/ACE_Overlay.pm:561 lib/RT/ACE_Overlay.pm:656 +#: lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:668 msgid "Right not loaded." msgstr "Право не загружено" -#: lib/RT/ACE_Overlay.pm:713 +#: lib/RT/ACE_Overlay.pm:725 msgid "Right revoked" msgstr "Право отозвано" @@ -4491,12 +4460,12 @@ msgstr "Право отозвано" msgid "Rights" msgstr "Права" -#: html/Admin/CustomFields/GroupRights.html:129 lib/RT/Interface/Web.pm:901 +#: html/Admin/CustomFields/GroupRights.html:129 lib/RT/Interface/Web.pm:869 #. ($object_type) msgid "Rights could not be granted for %1" msgstr "Ðевозможно выдать права Ð´Ð»Ñ %1" -#: html/Admin/CustomFields/GroupRights.html:156 lib/RT/Interface/Web.pm:930 +#: html/Admin/CustomFields/GroupRights.html:156 lib/RT/Interface/Web.pm:898 #. ($object_type) msgid "Rights could not be revoked for %1" msgstr "Ðевозможно отозвать права %1" @@ -4509,7 +4478,7 @@ msgstr "ПÑевдо-группы" msgid "Rows per page" msgstr "Строк на Ñтранице" -#: lib/RT/Date.pm:418 +#: lib/RT/Date.pm:416 msgid "Sat." msgstr "Суб." @@ -4517,7 +4486,7 @@ msgstr "Суб." msgid "Save" msgstr "Сохранить" -#: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:88 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html:238 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:60 html/Ticket/ModifyPeople.html:59 html/User/Groups/Modify.html:77 +#: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:87 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html.orig:238 html/Admin/Users/Modify.html:238 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:60 html/Ticket/ModifyPeople.html:59 html/User/Groups/Modify.html:77 msgid "Save Changes" msgstr "Сохранить изменениÑ" @@ -4529,23 +4498,22 @@ msgstr "Сохранить наÑтройки" msgid "Save changes" msgstr "Сохранить изменениÑ" -#: lib/RT/SavedSearch.pm:162 +#: lib/RT/SavedSearch.pm:161 #. ($name) msgid "Saved search %1" -msgstr "" +msgstr "Сохраненный Ð·Ð°Ð¿Ñ€Ð¾Ñ %1" #: html/Search/Elements/EditSearches:46 msgid "Saved searches" msgstr "Сохраненные запроÑÑ‹" -#: html/Admin/Elements/ListGlobalScrips:61 html/Admin/Global/Scrip.html:70 html/Admin/Queues/Scrip.html:76 -#. ($scrip->Id) +#: html/Admin/Global/Scrip.html:70 html/Admin/Queues/Scrip.html:76 #. ($id) #. ($ARGS{'id'}) msgid "Scrip #%1" msgstr "Скриплет #%1" -#: lib/RT/Scrip_Overlay.pm:205 +#: lib/RT/Scrip_Overlay.pm:204 msgid "Scrip Created" msgstr "Скриплет Ñоздан" @@ -4569,7 +4537,7 @@ msgstr "Скриплеты Ð´Ð»Ñ %1\\n" msgid "Scrips which apply to all queues" msgstr "Скриплеты, которые дейÑтвуют Ð´Ð»Ñ Ð²Ñех очередей" -#: html/Elements/SimpleSearch:48 html/Search/Build.html:112 +#: html/Elements/SimpleSearch:48 html/Search/Build.html:92 msgid "Search" msgstr "ПоиÑк" @@ -4577,18 +4545,18 @@ msgstr "ПоиÑк" msgid "Search Criteria" msgstr "Критерии поиÑка" -#: lib/RT/SavedSearch.pm:116 +#: lib/RT/SavedSearch.pm:115 msgid "Search attribute load failure" -msgstr "" +msgstr "Ошибка загрузки атрибута запроÑа" #: html/Approvals/Elements/PendingMyApproval:60 msgid "Search for approvals" msgstr "ПоиÑк подтверждений" -#: lib/RT/SavedSearch.pm:194 +#: lib/RT/SavedSearch.pm:193 #. ($msg) msgid "Search update: %1" -msgstr "" +msgstr "Обновление запроÑа: %1" #: NOT FOUND IN SOURCE msgid "Searches can't be associated with that kind of object" @@ -4602,15 +4570,15 @@ msgstr "БезопаÑноÑть:" msgid "See custom fields" msgstr "ПроÑмотреть дополнительные полÑ" -#: lib/RT/Queue_Overlay.pm:107 +#: lib/RT/Queue_Overlay.pm:106 msgid "See exact outgoing email messages and their recipeients" msgstr "ПроÑмотреть полноÑтью иÑходÑщую почту и ее получателей" -#: lib/RT/Queue_Overlay.pm:105 +#: lib/RT/Queue_Overlay.pm:104 msgid "See ticket private commentary" msgstr "ПроÑмотреть конфиденциальный комментарий заÑвки" -#: lib/RT/Queue_Overlay.pm:104 +#: lib/RT/Queue_Overlay.pm:103 msgid "See ticket summaries" msgstr "ПроÑмотреть Ñводную информацию заÑвки" @@ -4620,9 +4588,9 @@ msgstr "ПроÑматриватьДополнительныеПолÑ" #: lib/RT/Group_Overlay.pm:177 msgid "SeeGroup" -msgstr "" +msgstr "ПроÑматриватьГруппу" -#: lib/RT/Queue_Overlay.pm:92 +#: lib/RT/Queue_Overlay.pm:91 msgid "SeeQueue" msgstr "ПроÑмативатьОчередь" @@ -4660,19 +4628,19 @@ msgstr "Выберите дополнительное поле" #: html/Admin/Global/CustomFields/index.html:70 msgid "Select custom fields for all user groups" -msgstr "" +msgstr "Выберите дополнительные Ð¿Ð¾Ð»Ñ Ð²Ñех пользовательÑких групп" #: html/Admin/Global/CustomFields/index.html:65 msgid "Select custom fields for all users" -msgstr "" +msgstr "Выберите дополнительные Ð¿Ð¾Ð»Ñ Ð²Ñех пользователей" #: html/Admin/Global/CustomFields/index.html:76 msgid "Select custom fields for tickets in all queues" -msgstr "" +msgstr "Выберите дополнительные Ð¿Ð¾Ð»Ñ Ð·Ð°Ñвок во вÑех очередÑÑ…" #: html/Admin/Global/CustomFields/index.html:83 msgid "Select custom fields for transactions on tickets in all queues" -msgstr "" +msgstr "Выберите дополнительные Ð¿Ð¾Ð»Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ð¹ заÑвок во вÑех очередÑÑ…" #: html/Admin/Elements/GroupTabs:75 html/User/Elements/GroupTabs:71 msgid "Select group" @@ -4708,11 +4676,11 @@ msgstr "Выберите пользователÑ" #: NOT FOUND IN SOURCE msgid "SelectMultiple" -msgstr "Выбрать неÑколько" +msgstr "ВыбиратьÐеÑколько" #: NOT FOUND IN SOURCE msgid "SelectSingle" -msgstr "Выбрать одно" +msgstr "ВыбиратьОдно" #: html/Admin/Elements/EditCustomFields:58 msgid "Selected Custom Fields" @@ -4770,7 +4738,7 @@ msgstr "ОтправлÑть почту ÐдминиÑтративнымКопи msgid "Sends mail to the owner" msgstr "ОтправлÑть почту ОтветÑтвенному" -#: lib/RT/Date.pm:445 +#: lib/RT/Date.pm:442 msgid "Sep." msgstr "Сен." @@ -4782,7 +4750,7 @@ msgstr "РазделÑйте неÑколько ÑÑылок пробелами" msgid "September" msgstr "СентÑбрь" -#: html/Ticket/Elements/ShowTransaction:150 +#: html/Ticket/Elements/ShowTransaction:149 msgid "Show" msgstr "Показать" @@ -4834,15 +4802,15 @@ msgstr "Показать конфиденциальный комментарий msgid "Show ticket summaries" msgstr "Показать Ñводную информацию заÑвки" -#: lib/RT/Queue_Overlay.pm:94 +#: lib/RT/Queue_Overlay.pm:93 msgid "ShowACL" msgstr "ПоказыватьПраваДоÑтупа" -#: lib/RT/System.pm:86 +#: lib/RT/System.pm:85 msgid "ShowConfigTab" -msgstr "" +msgstr "ПоказыватьЗакладкуКонфигурации" -#: lib/RT/Queue_Overlay.pm:107 +#: lib/RT/Queue_Overlay.pm:106 msgid "ShowOutgoingEmail" msgstr "ПоказыватьИÑходÑщуюПочту" @@ -4850,31 +4818,31 @@ msgstr "ПоказыватьИÑходÑщуюПочту" msgid "ShowSavedSearches" msgstr "ПоказыватьСохраненныеЗапроÑÑ‹" -#: lib/RT/Queue_Overlay.pm:103 +#: lib/RT/Queue_Overlay.pm:102 msgid "ShowScrips" msgstr "ПоказыватьСкриплеты" -#: lib/RT/Queue_Overlay.pm:100 +#: lib/RT/Queue_Overlay.pm:99 msgid "ShowTemplate" msgstr "ПоказыватьШаблон" -#: lib/RT/Queue_Overlay.pm:104 +#: lib/RT/Queue_Overlay.pm:103 msgid "ShowTicket" msgstr "ПоказыватьЗаÑвку" -#: lib/RT/Queue_Overlay.pm:105 +#: lib/RT/Queue_Overlay.pm:104 msgid "ShowTicketComments" msgstr "ПоказыватьКомментарииЗаÑвки" -#: lib/RT/Queue_Overlay.pm:108 +#: lib/RT/Queue_Overlay.pm:107 msgid "Sign up as a ticket Requestor or ticket or queue Cc" msgstr "ПодпиÑатьÑÑ ÐºÐ°Ðº Ðвтор заÑвки или ÐšÐ¾Ð¿Ð¸Ñ Ð·Ð°Ñвки или очереди" -#: lib/RT/Queue_Overlay.pm:109 +#: lib/RT/Queue_Overlay.pm:108 msgid "Sign up as a ticket or queue AdminCc" msgstr "ПодпиÑатьÑÑ ÐºÐ°Ðº ÐдминиÑтративнаÑÐšÐ¾Ð¿Ð¸Ñ Ð·Ð°Ñвки или очереди" -#: html/Admin/Users/Modify.html:229 html/User/Prefs.html:167 +#: html/Admin/Users/Modify.html.orig:229 html/Admin/Users/Modify.html:229 html/User/Prefs.html:167 msgid "Signature" msgstr "ПодпиÑÑŒ" @@ -4888,7 +4856,7 @@ msgstr "Одно значение" #: html/Search/Elements/EditFormat:75 msgid "Size" -msgstr "" +msgstr "Размер" #: html/Elements/Header:85 msgid "Skip Menu" @@ -4896,7 +4864,7 @@ msgstr "ПропуÑтить меню" #: html/Search/Elements/EditFormat:78 msgid "Small" -msgstr "" +msgstr "Маленький" #: html/Admin/Elements/AddCustomFieldValue:49 html/Admin/Elements/EditCustomFieldValues:54 msgid "Sort" @@ -4954,11 +4922,11 @@ msgstr "Ðачато" msgid "Starts date '%1' could not be parsed" msgstr "Ðе могу разобрать дату 'Ðачато': '%1'" -#: html/Admin/Users/Modify.html:162 html/User/Prefs.html:145 +#: html/Admin/Users/Modify.html.orig:162 html/Admin/Users/Modify.html:162 html/User/Prefs.html:145 msgid "State" msgstr "СоÑтоÑние" -#: html/Search/Elements/PickBasics:87 html/SelfService/Elements/MyRequests:50 html/SelfService/Update.html:57 html/Ticket/Create.html:63 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1142 lib/RT/Tickets_Overlay.pm:1378 +#: html/Search/Elements/PickBasics:87 html/SelfService/Elements/MyRequests:50 html/SelfService/Update.html:57 html/Ticket/Create.html:63 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1142 lib/RT/Tickets_Overlay.pm:1377 msgid "Status" msgstr "СтатуÑ" @@ -4978,37 +4946,33 @@ msgstr "ИзменÑтьСтатуÑ" msgid "Steal" msgstr "Переназначать Ñебе" -#: lib/RT/Queue_Overlay.pm:118 +#: lib/RT/Queue_Overlay.pm:117 msgid "Steal tickets" msgstr "Переназначить заÑвки Ñебе" -#: lib/RT/Queue_Overlay.pm:118 +#: lib/RT/Queue_Overlay.pm:117 msgid "StealTicket" msgstr "ПереназначатьЗаÑвкуСебе" -#: lib/RT/Transaction_Overlay.pm:667 +#: lib/RT/Transaction_Overlay.pm.orig:665 lib/RT/Transaction_Overlay.pm:665 #. ($Old->Name) -msgid "Stolen from %1" -msgstr "ОтветÑтвенный переназначен Ñ %1" - -#: NOT FOUND IN SOURCE msgid "Stolen from %1 " -msgstr "ОтветÑтвенный переназначен Ñ %1 " +msgstr "ОтветÑтвенный переназначен Ñ %1" #: html/Search/Elements/EditFormat:81 msgid "Style" -msgstr "" +msgstr "Стиль" -#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1460 +#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:154 html/SelfService/Create.html:79 html/SelfService/Elements/MyRequests:49 html/SelfService/Update.html:65 html/Ticket/Create.html:105 html/Ticket/Elements/EditBasics:48 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:80 lib/RT/Ticket_Overlay.pm:1138 lib/RT/Tickets_Overlay.pm:1459 msgid "Subject" msgstr "Тема" -#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm:689 +#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm.orig:687 lib/RT/Transaction_Overlay.pm:687 #. ($self->Data) msgid "Subject changed to %1" msgstr "Тема изменена на %1" -#: html/Elements/Submit:97 +#: html/Elements/Submit.rej:17 html/Elements/Submit.rej:9 html/Elements/Submit:97 msgid "Submit" msgstr "Отправить" @@ -5020,7 +4984,7 @@ msgstr "Отправить запроÑ" msgid "Submit Workflow" msgstr "Отправить поÑледовательноÑть дейÑтвий" -#: lib/RT/Group_Overlay.pm:782 +#: lib/RT/Group_Overlay.pm:780 msgid "Succeeded" msgstr "УÑпешно" @@ -5032,11 +4996,11 @@ msgstr "Ð¡Ð²Ð¾Ð´Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" msgid "Summary matches" msgstr "Ð’Ñего Ñовпадений" -#: lib/RT/Date.pm:419 +#: lib/RT/Date.pm:417 msgid "Sun." msgstr "Ð’Ñк." -#: lib/RT/System.pm:76 +#: lib/RT/System.pm:75 msgid "SuperUser" msgstr "ÐдминиÑтратор" @@ -5048,7 +5012,7 @@ msgstr "СиÑтемные" msgid "System Configuration" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ" -#: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:585 lib/RT/Interface/Web.pm:900 lib/RT/Interface/Web.pm:929 +#: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:597 lib/RT/Interface/Web.pm:868 lib/RT/Interface/Web.pm:897 msgid "System Error" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°" @@ -5060,20 +5024,20 @@ msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°. Право не выдано" msgid "System Error. right not granted" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°. Право не выдано" -#: lib/RT/Transaction_Overlay.pm:215 lib/RT/Transaction_Overlay.pm:221 +#: lib/RT/Transaction_Overlay.pm.orig:213 lib/RT/Transaction_Overlay.pm.orig:219 lib/RT/Transaction_Overlay.pm:213 lib/RT/Transaction_Overlay.pm:219 #. ($msg) msgid "System Error: %1" -msgstr "" +msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %1" #: html/Admin/Tools/index.html:47 msgid "System Tools" msgstr "СиÑтемные утилиты" -#: lib/RT/ACE_Overlay.pm:634 +#: lib/RT/ACE_Overlay.pm:646 msgid "System error. Right not delegated." msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°. Право не делегировано." -#: lib/RT/ACE_Overlay.pm:164 lib/RT/ACE_Overlay.pm:229 lib/RT/ACE_Overlay.pm:324 lib/RT/ACE_Overlay.pm:921 +#: lib/RT/ACE_Overlay.pm:170 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:336 lib/RT/ACE_Overlay.pm:933 msgid "System error. Right not granted." msgstr "CиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°. Право не выдано." @@ -5089,25 +5053,25 @@ msgstr "СиÑтемные группы" msgid "SystemRolegroup for internal use" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¿Ñевдо-группа Ð´Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ³Ð¾ иÑпользованиÑ" -#: lib/RT/CurrentUser.pm:358 +#: lib/RT/CurrentUser.pm:356 msgid "TEST_STRING" msgstr "TEST_STRING" #: html/Elements/MyRequests:50 html/Search/Elements/EditFormat:72 html/Ticket/Elements/Tabs:166 msgid "Take" -msgstr "ВзÑть Ñебе" +msgstr "Ðазначить ÑÐµÐ±Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ñтвенным" -#: lib/RT/Queue_Overlay.pm:116 +#: lib/RT/Queue_Overlay.pm:115 msgid "Take tickets" -msgstr "ВзÑть заÑвки ÑебÑ" +msgstr "Ðазначить ÑÐµÐ±Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ñтвенным за заÑвки" -#: lib/RT/Queue_Overlay.pm:116 +#: lib/RT/Queue_Overlay.pm:115 msgid "TakeTicket" -msgstr "БратьЗаÑвкуСебе" +msgstr "ÐазначатьСебÑОтветÑтвеннымЗаЗаÑвку" -#: lib/RT/Transaction_Overlay.pm:652 +#: lib/RT/Transaction_Overlay.pm.orig:650 lib/RT/Transaction_Overlay.pm:650 msgid "Taken" -msgstr "ВзÑта" +msgstr "Ðазначен ответÑтвенным" #: NOT FOUND IN SOURCE msgid "Task" @@ -5126,7 +5090,7 @@ msgstr "Шаблон #%1" msgid "Template deleted" msgstr "Шаблон удален" -#: lib/RT/Scrip_Overlay.pm:181 +#: lib/RT/Scrip_Overlay.pm:180 msgid "Template not found" msgstr "Шаблон не найден" @@ -5134,7 +5098,7 @@ msgstr "Шаблон не найден" msgid "Template not found\\n" msgstr "Шаблон не найден\\n" -#: lib/RT/Template_Overlay.pm:376 +#: lib/RT/Template_Overlay.pm:373 msgid "Template parsed" msgstr "Шаблон обработан" @@ -5150,11 +5114,11 @@ msgstr "Шаблоны Ð´Ð»Ñ %1\\n" msgid "Text" msgstr "ТекÑÑ‚" -#: lib/RT/CustomField_Overlay.pm:877 lib/RT/Record.pm:931 +#: lib/RT/CustomField_Overlay.pm:863 lib/RT/Record.pm:929 msgid "That is already the current value" msgstr "Ðто уже текущее значение" -#: lib/RT/CustomField_Overlay.pm:407 +#: lib/RT/CustomField_Overlay.pm:401 msgid "That is not a value for this custom field" msgstr "Ðто поле не может иметь такого значениÑ" @@ -5162,11 +5126,11 @@ msgstr "Ðто поле не может иметь такого Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ msgid "That is the same value" msgstr "Значение не изменилоÑÑŒ" -#: lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:615 +#: lib/RT/ACE_Overlay.pm:318 lib/RT/ACE_Overlay.pm:627 msgid "That principal already has that right" msgstr "Ðтот пользователь уже имеет Ñто право." -#: lib/RT/Queue_Overlay.pm:750 +#: lib/RT/Queue_Overlay.pm:737 #. ($args{'Type'}) msgid "That principal is already a %1 for this queue" msgstr "Ðтот пользователь уже %1 Ð´Ð»Ñ Ñтой очереди" @@ -5176,7 +5140,7 @@ msgstr "Ðтот пользователь уже %1 Ð´Ð»Ñ Ñтой очеред msgid "That principal is already a %1 for this ticket" msgstr "Ðтот пользователь уже %1 Ð´Ð»Ñ Ñтой заÑвки " -#: lib/RT/Queue_Overlay.pm:849 +#: lib/RT/Queue_Overlay.pm:836 #. ($args{'Type'}) msgid "That principal is not a %1 for this queue" msgstr "Ðтот пользователь не %1 Ð´Ð»Ñ Ñтой очереди" @@ -5189,7 +5153,7 @@ msgstr "Ðтот пользователь не %1 Ð´Ð»Ñ Ñтой заÑвки" msgid "That queue does not exist" msgstr "Ðта очередь не ÑущеÑтвует" -#: lib/RT/Ticket_Overlay.pm:3189 +#: lib/RT/Ticket_Overlay.pm:3156 msgid "That ticket has unresolved dependencies" msgstr "Ðта заÑвка имеет неразрешенные завиÑимоÑти" @@ -5197,27 +5161,27 @@ msgstr "Ðта заÑвка имеет неразрешенные завиÑим msgid "That user already has that right" msgstr "Пользователь уже имеет Ñто право" -#: lib/RT/Ticket_Overlay.pm:2993 +#: lib/RT/Ticket_Overlay.pm:2960 msgid "That user already owns that ticket" msgstr "Пользователь уже ответÑтвенен за Ñту заÑвку" -#: lib/RT/Ticket_Overlay.pm:2965 +#: lib/RT/Ticket_Overlay.pm:2932 msgid "That user does not exist" msgstr "Пользователь не ÑущеÑтвует" -#: lib/RT/User_Overlay.pm:390 +#: lib/RT/User_Overlay.pm:389 msgid "That user is already privileged" msgstr "Ðтот пользователь уже привилегированный" -#: lib/RT/User_Overlay.pm:411 +#: lib/RT/User_Overlay.pm:410 msgid "That user is already unprivileged" msgstr "Ðтот пользователь уже непривилегированный" -#: lib/RT/User_Overlay.pm:403 +#: lib/RT/User_Overlay.pm:402 msgid "That user is now privileged" msgstr "Ðтот пользователь теперь привилегированный" -#: lib/RT/User_Overlay.pm:424 +#: lib/RT/User_Overlay.pm:423 msgid "That user is now unprivileged" msgstr "Ðтот пользователь теперь непривилегированный" @@ -5225,11 +5189,11 @@ msgstr "Ðтот пользователь теперь непривилегирРmsgid "That user is now unprivilegedileged" msgstr "Пользователь теперь непривилегированный" -#: lib/RT/Ticket_Overlay.pm:2986 +#: lib/RT/Ticket_Overlay.pm:2953 msgid "That user may not own tickets in that queue" msgstr "Ðтот пользователь не может быть ответÑтвенным за заÑвки в Ñтой очереди" -#: lib/RT/Link_Overlay.pm:234 +#: lib/RT/Link_Overlay.pm:233 msgid "That's not a numerical id" msgstr "Ðто не чиÑловой идентификатор" @@ -5237,11 +5201,11 @@ msgstr "Ðто не чиÑловой идентификатор" msgid "The Basics" msgstr "ОÑновное" -#: lib/RT/ACE_Overlay.pm:113 +#: lib/RT/ACE_Overlay.pm:112 msgid "The CC of a ticket" msgstr "ÐšÐ¾Ð¿Ð¸Ñ Ð·Ð°Ñвки" -#: lib/RT/ACE_Overlay.pm:114 +#: lib/RT/ACE_Overlay.pm:113 msgid "The administrative CC of a ticket" msgstr "ÐдминиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ Ð·Ð°Ñвки" @@ -5257,15 +5221,15 @@ msgstr "ÐижеÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° найдет вÑе актив msgid "The following commands were not proccessed:\\n\\n" msgstr "Ðти команды не были выполнены:\\n\\n" -#: lib/RT/Record.pm:934 +#: lib/RT/Record.pm:932 msgid "The new value has been set." msgstr "Ðовое значение уÑтановлено" -#: lib/RT/ACE_Overlay.pm:111 +#: lib/RT/ACE_Overlay.pm:110 msgid "The owner of a ticket" msgstr "ОтветÑтвенный за заÑвку" -#: lib/RT/ACE_Overlay.pm:112 +#: lib/RT/ACE_Overlay.pm:111 msgid "The requestor of a ticket" msgstr "Ðвтор заÑвки" @@ -5273,7 +5237,7 @@ msgstr "Ðвтор заÑвки" msgid "These comments aren't generally visible to the user" msgstr "Ðти комментарии не показываютÑÑ Ð¾Ð±Ñ‹ÐºÐ½Ð¾Ð²ÐµÐ½Ð½Ð¾Ð¼Ñƒ пользователю" -#: lib/RT/CustomField_Overlay.pm:912 +#: lib/RT/CustomField_Overlay.pm:898 msgid "This custom field does not apply to that object" msgstr "Ðто дополнительное поле не отноÑитÑÑ Ðº Ñтому объекту" @@ -5293,7 +5257,7 @@ msgstr "Ðта заÑвка %1 %2 (%3)\\n" msgid "This tool allows the user to run arbitrary perl modules from within RT." msgstr "Ðтот инÑтрумент позволÑет пользователю запуÑкать некоторые модули Perl из RT." -#: lib/RT/Transaction_Overlay.pm:288 +#: lib/RT/Transaction_Overlay.pm.orig:286 lib/RT/Transaction_Overlay.pm:286 msgid "This transaction appears to have no content" msgstr "Похоже, что Ñта Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ð½Ðµ имеет Ñодержимого" @@ -5306,7 +5270,7 @@ msgstr "%1 заÑвок макÑимального приоритета Ñтог msgid "This user's 25 highest priority tickets" msgstr "25 важнейших заÑвок пользователÑ..." -#: lib/RT/Date.pm:416 +#: lib/RT/Date.pm:414 msgid "Thu." msgstr "Чтв." @@ -5332,7 +5296,7 @@ msgstr "ЗаÑвка #%1 Изменение вÑех данных: %2" msgid "Ticket #%1: %2" msgstr "ЗаÑвка #%1: %2" -#: lib/RT/Action/CreateTickets.pm:1258 lib/RT/Action/CreateTickets.pm:1267 lib/RT/Action/CreateTickets.pm:595 lib/RT/Action/CreateTickets.pm:716 lib/RT/Action/CreateTickets.pm:729 +#: lib/RT/Action/CreateTickets.pm:1255 lib/RT/Action/CreateTickets.pm:1264 lib/RT/Action/CreateTickets.pm:593 lib/RT/Action/CreateTickets.pm:713 lib/RT/Action/CreateTickets.pm:726 #. ($T::Tickets{$template_id}->Id) #. ($T::Tickets{$template_id}->id) #. ($ticket->Id) @@ -5370,7 +5334,7 @@ msgstr "ЗаÑвка #" msgid "Ticket Resolved" msgstr "ЗаÑвка решена" -#: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1085 +#: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1071 msgid "Ticket Transactions" msgstr "Транзакции заÑвки" @@ -5378,11 +5342,11 @@ msgstr "Транзакции заÑвки" msgid "Ticket attachment" msgstr "Ð’Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð°Ñвки" -#: lib/RT/Tickets_Overlay.pm:1648 +#: lib/RT/Tickets_Overlay.pm:1647 msgid "Ticket content" msgstr "Содержимое заÑвки" -#: lib/RT/Tickets_Overlay.pm:1697 +#: lib/RT/Tickets_Overlay.pm:1696 msgid "Ticket content type" msgstr "Тип данных Ñодержимого заÑвки" @@ -5426,12 +5390,12 @@ msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ñвки изменен" msgid "Ticket watchers" msgstr "Ðаблюдатели за заÑвкой" -#: lib/RT/Search/FromSQL.pm:83 +#: lib/RT/Search/FromSQL.pm:82 #. (ref $self) msgid "TicketSQL search module" msgstr "" -#: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:68 lib/RT/CustomField_Overlay.pm:1084 +#: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:68 lib/RT/CustomField_Overlay.pm:1070 msgid "Tickets" msgstr "ПоиÑк заÑвки" @@ -5463,7 +5427,7 @@ msgstr "ОÑталоÑÑŒ времени" msgid "Time Worked" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð² работе" -#: lib/RT/Tickets_Overlay.pm:1619 +#: lib/RT/Tickets_Overlay.pm:1618 msgid "Time left" msgstr "ОÑталоÑÑŒ времени" @@ -5471,7 +5435,7 @@ msgstr "ОÑталоÑÑŒ времени" msgid "Time to display" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°" -#: lib/RT/Tickets_Overlay.pm:1594 +#: lib/RT/Tickets_Overlay.pm:1593 msgid "Time worked" msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð² работе" @@ -5485,7 +5449,7 @@ msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð² работе" #: html/Search/Elements/EditFormat:74 msgid "Title" -msgstr "" +msgstr "Заголовок" #: NOT FOUND IN SOURCE msgid "To generate a diff of this commit:" @@ -5512,12 +5476,12 @@ msgstr "Утилиты" msgid "Transaction" msgstr "ТранзакциÑ" -#: lib/RT/Transaction_Overlay.pm:794 +#: lib/RT/Transaction_Overlay.pm.orig:792 lib/RT/Transaction_Overlay.pm:792 #. ($self->Data) msgid "Transaction %1 purged" msgstr "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ %1 удалена" -#: lib/RT/Transaction_Overlay.pm:174 +#: lib/RT/Transaction_Overlay.pm.orig:172 lib/RT/Transaction_Overlay.pm:172 msgid "Transaction Created" msgstr "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ñоздана" @@ -5529,11 +5493,11 @@ msgstr "Дополнительные Ð¿Ð¾Ð»Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ð¸" msgid "Transaction->Create couldn't, as you didn't specify a ticket id" msgstr "ТранзакциÑ->Создать невозможно, так как вы не указали идентификатор заÑвки" -#: lib/RT/Transaction_Overlay.pm:125 +#: lib/RT/Transaction_Overlay.pm.orig:123 lib/RT/Transaction_Overlay.pm:123 msgid "Transaction->Create couldn't, as you didn't specify an object type and id" msgstr "ТранзакциÑ->Создать невозможно, так как вы не указали тип объекта и идентификатор" -#: lib/RT/Transaction_Overlay.pm:838 +#: lib/RT/Transaction_Overlay.pm.orig:836 lib/RT/Transaction_Overlay.pm:836 msgid "Transactions are immutable" msgstr "ÐеизменÑемые транзакции" @@ -5541,19 +5505,19 @@ msgstr "ÐеизменÑемые транзакции" msgid "Trying to delete a right: %1" msgstr "ПытаемÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ право: %1" -#: lib/RT/Date.pm:414 +#: lib/RT/Date.pm:412 msgid "Tue." msgstr "Втр." -#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1144 lib/RT/Tickets_Overlay.pm:1432 +#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1144 lib/RT/Tickets_Overlay.pm:1431 msgid "Type" msgstr "Тип" -#: lib/RT/ScripCondition_Overlay.pm:129 +#: lib/RT/ScripCondition_Overlay.pm:128 msgid "Unimplemented" msgstr "Ðе реализовано" -#: html/Admin/Users/Modify.html:89 +#: html/Admin/Users/Modify.html.orig:89 html/Admin/Users/Modify.html:89 msgid "Unix login" msgstr "Логин UNIX" @@ -5561,15 +5525,15 @@ msgstr "Логин UNIX" msgid "UnixUsername" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ UNIX" -#: lib/RT/Attachment_Overlay.pm:290 lib/RT/Record.pm:847 +#: lib/RT/Attachment_Overlay.pm:289 lib/RT/Record.pm:846 #. ($self->ContentEncoding) #. ($ContentEncoding) msgid "Unknown ContentEncoding %1" msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° %1" -#: html/Search/Build.html:632 +#: html/Search/Build.html:692 msgid "Unknown field: $key" -msgstr "" +msgstr "ÐеизвеÑтное поле: $key" #: html/Elements/SelectResultsPerPage:58 msgid "Unlimited" @@ -5591,9 +5555,9 @@ msgstr "Ðевыбранные дополнительные полÑ" msgid "Unselected objects" msgstr "Ðевыбранные объекты" -#: lib/RT/Transaction_Overlay.pm:648 +#: lib/RT/Transaction_Overlay.pm.orig:646 lib/RT/Transaction_Overlay.pm:646 msgid "Untaken" -msgstr "Ðе взÑта" +msgstr "Ðет ответÑтвенного" #: NOT FOUND IN SOURCE msgid "Untitled search" @@ -5611,7 +5575,7 @@ msgstr "Обновить вÑе" msgid "Update ID" msgstr "Обновить идентификатор" -#: html/Ticket/Update.html:133 +#: html/Ticket/Update.html:130 msgid "Update Ticket" msgstr "Обновить заÑвку" @@ -5635,7 +5599,7 @@ msgstr "Обновить неÑколько заÑвок" msgid "Update name" msgstr "Обновить имÑ" -#: lib/RT/Action/CreateTickets.pm:737 lib/RT/Interface/Web.pm:524 +#: lib/RT/Action/CreateTickets.pm:734 lib/RT/Interface/Web.pm:492 msgid "Update not recorded." msgstr "Обновление не запиÑано." @@ -5660,12 +5624,12 @@ msgstr "Обновление заÑвки # %1" msgid "Update ticket #%1" msgstr "Обновление заÑвки #%1" -#: html/Ticket/Update.html:156 +#: html/Ticket/Update.html:153 #. ($TicketObj->id, $TicketObj->Subject) msgid "Update ticket #%1 (%2)" msgstr "Обновление заÑвки #%1 (%2)" -#: lib/RT/Action/CreateTickets.pm:735 lib/RT/Interface/Web.pm:523 +#: lib/RT/Action/CreateTickets.pm:732 lib/RT/Interface/Web.pm:490 msgid "Update type was neither correspondence nor comment." msgstr "Обновление не было ни Ñообщением, ни комментарием." @@ -5707,7 +5671,7 @@ msgstr "Загрузить ваши изменениÑ." #: html/Admin/index.html:90 msgid "Use other RT administrative tools" -msgstr "" +msgstr "Другие админиÑтративные утилиты RT" #: NOT FOUND IN SOURCE msgid "User %1 %2: %3\\n" @@ -5750,17 +5714,17 @@ msgstr "Логин" msgid "User Rights" msgstr "Права пользователÑ" -#: lib/RT/Interface/Web.pm:1283 +#: lib/RT/Interface/Web.pm:1251 #. ($cf->Name, $class, $Object->id) msgid "User asked for an unknown update type for custom field %1 for %2 object #%3" msgstr "Пользователь запроÑил обновление неизвеÑтного типа Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ %1 Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° %2 #%3" -#: html/Admin/Users/Modify.html:293 +#: html/Admin/Users/Modify.html.orig:292 html/Admin/Users/Modify.html.rej:14 html/Admin/Users/Modify.html.rej:4 html/Admin/Users/Modify.html:292 #. ($msg) msgid "User could not be created: %1" msgstr "Ðевозможно Ñоздать пользователÑ: %1" -#: lib/RT/User_Overlay.pm:331 +#: lib/RT/User_Overlay.pm:330 msgid "User created" msgstr "Пользователь Ñоздан" @@ -5768,7 +5732,7 @@ msgstr "Пользователь Ñоздан" msgid "User defined groups" msgstr "Группы, определенные пользователем" -#: lib/RT/User_Overlay.pm:593 lib/RT/User_Overlay.pm:613 +#: lib/RT/User_Overlay.pm:592 lib/RT/User_Overlay.pm:612 msgid "User loaded" msgstr "Пользовать загружен" @@ -5784,11 +5748,11 @@ msgstr "ПользовательÑкие наÑтройки" msgid "User-defined groups" msgstr "Группы, заданные пользователем" -#: html/Admin/Users/Modify.html:69 html/Elements/Login:73 html/Ticket/Elements/AddWatchers:56 +#: html/Admin/Users/Modify.html.orig:69 html/Admin/Users/Modify.html:69 html/Elements/Login:73 html/Ticket/Elements/AddWatchers:56 msgid "Username" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" -#: html/Admin/Elements/GlobalCustomFieldTabs:55 html/Admin/Elements/SelectNewGroupMembers:47 html/Admin/Elements/Tabs:53 html/Admin/Global/CustomFields/index.html:64 html/Admin/Groups/Members.html:76 html/Admin/Queues/People.html:89 html/Admin/index.html:62 html/User/Groups/Members.html:79 lib/RT/CustomField_Overlay.pm:1086 +#: html/Admin/Elements/GlobalCustomFieldTabs:55 html/Admin/Elements/SelectNewGroupMembers:47 html/Admin/Elements/Tabs:53 html/Admin/Global/CustomFields/index.html:64 html/Admin/Groups/Members.html:76 html/Admin/Queues/People.html:89 html/Admin/index.html:62 html/User/Groups/Members.html:79 lib/RT/CustomField_Overlay.pm:1072 msgid "Users" msgstr "Пользователи" @@ -5808,11 +5772,11 @@ msgstr "Значение запроÑа" msgid "Values" msgstr "ЗначениÑ" -#: lib/RT/Queue_Overlay.pm:108 +#: lib/RT/Queue_Overlay.pm:107 msgid "Watch" msgstr "Ðаблюдать" -#: lib/RT/Queue_Overlay.pm:109 +#: lib/RT/Queue_Overlay.pm:108 msgid "WatchAsAdminCc" msgstr "ÐаблюдатьÐдминиÑтративнойКопией" @@ -5828,7 +5792,7 @@ msgstr "Ðаблюдатели" msgid "WebEncoding" msgstr "WebEncoding" -#: lib/RT/Date.pm:415 +#: lib/RT/Date.pm:413 msgid "Wed." msgstr "Срд." @@ -5892,7 +5856,7 @@ msgstr "ÐšÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑвÑзана пользователем" msgid "Which refer to" msgstr "ÐšÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑÑылаетÑÑ Ð½Ð°" -#: html/Admin/Users/Modify.html:188 html/User/Prefs.html:89 +#: html/Admin/Users/Modify.html.orig:188 html/Admin/Users/Modify.html:188 html/User/Prefs.html:89 msgid "Work" msgstr "Рабочий" @@ -5902,7 +5866,7 @@ msgstr "Работать автономно" #: NOT FOUND IN SOURCE msgid "WorkPhone" -msgstr "Рабочий" +msgstr "Рабочий телефон" #: html/Ticket/Elements/ShowBasics:63 html/Ticket/Update.html:64 msgid "Worked" @@ -5912,7 +5876,7 @@ msgstr "Ð’ работе" msgid "Yes" msgstr "Да" -#: lib/RT/Ticket_Overlay.pm:3096 +#: lib/RT/Ticket_Overlay.pm:3063 msgid "You already own this ticket" msgstr "Ð’Ñ‹ уже ответÑтвенный за Ñту заÑвку" @@ -5924,7 +5888,7 @@ msgstr "Ð’Ñ‹ незарегиÑтрированный пользователь" msgid "You can access it with the Download button on the right." msgstr "Ð’Ñ‹ можете получить Ñто, нажав Ñправа кнопку Загрузить" -#: lib/RT/Ticket_Overlay.pm:2978 +#: lib/RT/Ticket_Overlay.pm:2945 msgid "You can only reassign tickets that you own or that are unowned" msgstr "Ð’Ñ‹ можете назначать ответÑтвенного только Ð´Ð»Ñ Ñвоих или ничьих заÑвок." @@ -5941,7 +5905,7 @@ msgstr "Ðайдено %1 заÑвок в очереди %2" msgid "You have been logged out of RT." msgstr "Ð’Ñ‹ вышли из RT." -#: html/SelfService/Display.html:109 +#: html/SelfService/Display.html:102 msgid "You have no permission to create tickets in that queue." msgstr "У Ð²Ð°Ñ Ð½ÐµÑ‚ права Ñоздавать заÑвки в Ñтой очереди." @@ -5981,7 +5945,7 @@ msgstr "Ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¾Ñ‚ÐºÐ»Ð¾Ð½ÐµÐ½." msgid "Your username or password is incorrect" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль неверные" -#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:149 +#: html/Admin/Users/Modify.html.orig:168 html/Admin/Users/Modify.html:168 html/User/Prefs.html:149 msgid "Zip" msgstr "ИндекÑ" @@ -5989,13 +5953,13 @@ msgstr "ИндекÑ" msgid "[no subject]" msgstr "[нет темы]" -#: lib/RT/System.pm:88 +#: lib/RT/System.pm:87 msgid "allow creation of saved searches" -msgstr "" +msgstr "разрешить Ñоздание Ñохраненных запроÑов" -#: lib/RT/System.pm:87 +#: lib/RT/System.pm:86 msgid "allow loading of saved searches" -msgstr "" +msgstr "разрешить загрузку Ñохраненных запроÑов" #: NOT FOUND IN SOURCE msgid "and is not" @@ -6012,7 +5976,7 @@ msgstr "Ñ Ð¿Ñ€Ð°Ð²Ð°Ð¼Ð¸ %1" #: html/Search/Elements/PickBasics:127 msgid "belongs to" -msgstr "" +msgstr "отноÑитÑÑ Ðº" #: html/SelfService/Closed.html:49 msgid "closed" @@ -6038,7 +6002,7 @@ msgstr "корреÑÐ¿Ð¾Ð½Ð´ÐµÐ½Ñ†Ð¸Ñ (возможно) не отправлеРmsgid "correspondence sent" msgstr "корреÑÐ¿Ð¾Ð½Ð´ÐµÐ½Ñ†Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð°" -#: html/Admin/Queues/Modify.html:98 lib/RT/Date.pm:342 +#: html/Admin/Queues/Modify.html:98 lib/RT/Date.pm:341 msgid "days" msgstr "дней" @@ -6046,13 +6010,13 @@ msgstr "дней" msgid "delete" msgstr "удалить" -#: lib/RT/Queue_Overlay.pm:88 +#: lib/RT/Queue_Overlay.pm:87 msgid "deleted" msgstr "удалена" #: html/Search/Elements/PickBasics:128 msgid "does not belong to" -msgstr "" +msgstr "не отноÑитÑÑ Ðº" #: html/Search/Elements/PickBasics:61 msgid "does not match" @@ -6070,29 +6034,29 @@ msgstr "Ð°Ð´Ñ€ÐµÑ email" msgid "equal to" msgstr "равнÑетÑÑ" -#: html/Search/Build.html:387 +#: html/Search/Build.html:371 msgid "error: can't move down" -msgstr "" +msgstr "ошибка: невозможно перемеÑтить вниз" -#: html/Search/Build.html:409 +#: html/Search/Build.html:393 msgid "error: can't move left" -msgstr "" +msgstr "ошибка: невозможно перемеÑтить влево" -#: html/Search/Build.html:368 +#: html/Search/Build.html:352 msgid "error: can't move up" -msgstr "" +msgstr "ошибка: невозможно перемеÑтить вверх" -#: html/Search/Build.html:451 +#: html/Search/Build.html:435 msgid "error: nothing to delete" -msgstr "" +msgstr "ошибка: нет данных Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ" -#: html/Search/Build.html:373 html/Search/Build.html:392 html/Search/Build.html:414 html/Search/Build.html:443 +#: html/Search/Build.html:357 html/Search/Build.html:376 html/Search/Build.html:398 html/Search/Build.html:427 msgid "error: nothing to move" -msgstr "" +msgstr "ошибка: нет данных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ" -#: html/Search/Build.html:469 +#: html/Search/Build.html:453 msgid "error: nothing to toggle" -msgstr "" +msgstr "ошибка: нет данных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ" #: NOT FOUND IN SOURCE msgid "false" @@ -6111,7 +6075,7 @@ msgstr "больше чем" msgid "group '%1'" msgstr "группа '%1'" -#: lib/RT/Date.pm:338 +#: lib/RT/Date.pm:337 msgid "hours" msgstr "чаÑов" @@ -6135,7 +6099,7 @@ msgstr "меньше чем" msgid "matches" msgstr "Ñовпадает" -#: lib/RT/Date.pm:334 +#: lib/RT/Date.pm:333 msgid "min" msgstr "мин" @@ -6147,11 +6111,11 @@ msgstr "минут" msgid "modifications\\n\\n" msgstr "изменениÑ\\n\\n" -#: lib/RT/Date.pm:350 +#: lib/RT/Date.pm:349 msgid "months" msgstr "меÑÑцев" -#: lib/RT/Queue_Overlay.pm:83 +#: lib/RT/Queue_Overlay.pm:82 msgid "new" msgstr "новаÑ" @@ -6175,7 +6139,7 @@ msgstr "не равно" msgid "notlike" msgstr "неравно" -#: html/SelfService/Elements/MyRequests:83 lib/RT/Queue_Overlay.pm:84 +#: html/SelfService/Elements/MyRequests:83 lib/RT/Queue_Overlay.pm:83 msgid "open" msgstr "открыта" @@ -6189,27 +6153,27 @@ msgstr "Ð»Ð¸Ñ‡Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° '%1' Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%2'" msgid "queue %1 %2" msgstr "очередь %1 %2" -#: lib/RT/Queue_Overlay.pm:87 +#: lib/RT/Queue_Overlay.pm:86 msgid "rejected" msgstr "отклонена" -#: lib/RT/Queue_Overlay.pm:86 +#: lib/RT/Queue_Overlay.pm:85 msgid "resolved" msgstr "решена" -#: lib/RT/Date.pm:330 +#: lib/RT/Date.pm:329 msgid "sec" msgstr "Ñек" -#: lib/RT/System.pm:86 +#: lib/RT/System.pm:85 msgid "show Configuration tab" -msgstr "" +msgstr "показывать закладку КонфигурациÑ" #: html/Search/Results.html:82 msgid "spreadsheet" -msgstr "ÑÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° (.xls)" +msgstr "ÑÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð°" -#: lib/RT/Queue_Overlay.pm:85 +#: lib/RT/Queue_Overlay.pm:84 msgid "stalled" msgstr "приоÑтановлена" @@ -6223,7 +6187,7 @@ msgstr "ÑиÑтема %1" msgid "system group '%1'" msgstr "ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° '%1'" -#: html/Elements/Error:66 html/SelfService/Error.html:63 +#: html/Elements/Error:64 html/SelfService/Error.html:63 msgid "the calling component did not specify why" msgstr "вызывающий компонент не указал причину" @@ -6258,7 +6222,7 @@ msgstr "группа без опиÑаниÑ: %1" msgid "user %1" msgstr "пользователь %1" -#: lib/RT/Date.pm:346 +#: lib/RT/Date.pm:345 msgid "weeks" msgstr "недель" @@ -6266,7 +6230,7 @@ msgstr "недель" msgid "with template %1" msgstr "Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¾Ð¼ %1" -#: lib/RT/Date.pm:354 +#: lib/RT/Date.pm:353 msgid "years" msgstr "лет" diff --git a/rt/lib/RT/I18N/zh_cn.po b/rt/lib/RT/I18N/zh_cn.po index 1f8799825..ed6aff80d 100644 --- a/rt/lib/RT/I18N/zh_cn.po +++ b/rt/lib/RT/I18N/zh_cn.po @@ -1,8 +1,10 @@ -# Chinese localization catalog for Request Tracker (RT) +# msgid "" msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: Autrijus Tang <autrijus@autrijus.org>\n" -"Language-Team: Chinese <members@ourinet.com>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/rt/lib/RT/I18N/zh_tw.po b/rt/lib/RT/I18N/zh_tw.po index 7fbda2187..d9c64e8ce 100644 --- a/rt/lib/RT/I18N/zh_tw.po +++ b/rt/lib/RT/I18N/zh_tw.po @@ -1,8 +1,10 @@ -# Chinese localization catalog for Request Tracker (RT) +# msgid "" msgstr "" +"Project-Id-Version: RT 3.4.x\n" +"PO-Revision-Date: 2005-10-03 13:54-0400\n" "Last-Translator: Autrijus Tang <autrijus@autrijus.org>\n" -"Language-Team: Chinese <members@ourinet.com>\n" +"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 5db7c8aa7..efc4c268e 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -56,7 +56,7 @@ BEGIN { use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); # set the version for version checking - $VERSION = do { my @r = (q$Revision: 1.1.1.5 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker + $VERSION = do { my @r = (q$Revision: 1.1.1.6 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker @ISA = qw(Exporter); @@ -68,6 +68,7 @@ BEGIN { &CheckForLoops &CheckForSuspiciousSender &CheckForAutoGenerated + &CheckForBounce &MailError &ParseCcAddressesFromHead &ParseSenderAddressFromHead @@ -171,6 +172,16 @@ sub CheckForAutoGenerated { # }}} +# {{{ sub CheckForBounce +sub CheckForBounce { + my $head = shift; + + my $ReturnPath = $head->get("Return-path") || "" ; + return ($ReturnPath =~ /<>/); +} + +# }}} + # {{{ IsRTAddress =head2 IsRTAddress ADDRESS @@ -187,7 +198,7 @@ sub IsRTAddress { # Example: the following rule would tell RT not to Cc # "tickets@noc.example.com" if ( defined($RT::RTAddressRegexp) && - $address =~ /$RT::RTAddressRegexp/ ) { + $address =~ /$RT::RTAddressRegexp/i ) { return(1); } else { return (undef); @@ -250,7 +261,7 @@ sub MailError { } if ($RT::MailCommand eq 'sendmailpipe') { - open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0); + open (MAIL, "|$RT::SendmailPath $RT::SendmailBounceArguments $RT::SendmailArguments") || return(0); print MAIL $entity->as_string; close(MAIL); } @@ -378,7 +389,8 @@ sub ParseSenderAddressFromHead { =head2 ParseErrorsToAddressFromHead Takes a MIME::Header object. Return a single value : user@host -of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender) +of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:, +From:, Sender) =cut @@ -386,7 +398,7 @@ sub ParseErrorsToAddressFromHead { my $head = shift; #Figure out who's sending this message. - foreach my $header ('Errors-To' , 'Reply-To', 'From', 'Sender' ) { + foreach my $header ('Return-path', 'Errors-To' , 'Reply-To', 'From', 'Sender' ) { # If there's a header of that name my $headerobj = $head->get($header); if ($headerobj) { @@ -436,7 +448,7 @@ sub ParseTicketId { my $Subject = shift; my $id; - my $test_name = $RT::EmailSubjectTagRegex || qr/\Q$RT::rtname\E/; + my $test_name = $RT::EmailSubjectTagRegex || qr/\Q$RT::rtname\E/i; if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) { my $id = $1; @@ -655,6 +667,8 @@ EOT # }}} # {{{ Lets check for mail loops of various sorts. + my $IsBounce = CheckForBounce($head); + my $IsAutoGenerated = CheckForAutoGenerated($head); my $IsSuspiciousSender = CheckForSuspiciousSender($head); @@ -665,7 +679,7 @@ EOT #If the message is autogenerated, we need to know, so we can not # send mail to the sender - if ( $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) { + if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) { $SquelchReplies = 1; $ErrorsTo = $RT::OwnerEmail; } diff --git a/rt/lib/RT/Link_Overlay.pm b/rt/lib/RT/Link_Overlay.pm index 28143cfeb..c870ee4c4 100644 --- a/rt/lib/RT/Link_Overlay.pm +++ b/rt/lib/RT/Link_Overlay.pm @@ -315,7 +315,7 @@ Returns true if the base of this link is a local ticket sub BaseIsLocal { my $self = shift; - $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal"); + $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal at (". join(":",caller).")"); return $self->BaseURI->IsLocal; } @@ -331,7 +331,7 @@ Returns true if the target of this link is a local ticket sub TargetIsLocal { my $self = shift; - $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal"); + $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal at (". join(":",caller).")"); return $self->TargetURI->IsLocal; } @@ -348,7 +348,7 @@ Returns an HTTP url to access the base of this link sub BaseAsHREF { my $self = shift; - $RT::Logger->crit("Link::BaseAsHREF deprecated in favor of ->BaseURI->AsHREF"); + $RT::Logger->crit("Link::BaseAsHREF deprecated in favor of ->BaseURI->AsHREF at (". join(":",caller).")"); return $self->BaseURI->HREF; } # }}} @@ -363,7 +363,7 @@ return an HTTP url to access the target of this link sub TargetAsHREF { my $self = shift; - $RT::Logger->crit("Link::TargetAsHREF deprecated in favor of ->TargetURI->AsHREF"); + $RT::Logger->crit("Link::TargetAsHREF deprecated in favor of ->TargetURI->AsHREF at (". join(":",caller).")"); return $self->TargetURI->HREF; } # }}} diff --git a/rt/lib/RT/ObjectCustomFieldValues_Overlay.pm b/rt/lib/RT/ObjectCustomFieldValues_Overlay.pm index 315c16f30..3e6e62a30 100644 --- a/rt/lib/RT/ObjectCustomFieldValues_Overlay.pm +++ b/rt/lib/RT/ObjectCustomFieldValues_Overlay.pm @@ -80,7 +80,7 @@ sub LimitToTicket { my $ticket = shift; - $RT::Logger->warning(ref($self) . " -> LimitToTicket deprecated in favor of LimitToObject"); + $RT::Logger->warning(ref($self) . " -> LimitToTicket deprecated in favor of LimitToObject at (". join(":",caller).")"); $self->Limit( FIELD => 'ObjectType', VALUE => 'RT::Ticket', diff --git a/rt/lib/RT/Principal_Overlay.pm b/rt/lib/RT/Principal_Overlay.pm index 4783c5ca6..1986470ee 100644 --- a/rt/lib/RT/Principal_Overlay.pm +++ b/rt/lib/RT/Principal_Overlay.pm @@ -280,8 +280,6 @@ This takes the params: Object => an RT style object (->id will get its id) - - Returns 1 if a matching ACE was found. Returns undef if no ACE was found. @@ -295,38 +293,41 @@ sub HasRight { Right => undef, Object => undef, EquivObjects => undef, - @_ + @_, ); + unless ( $args{'Right'} ) { + $RT::Logger->crit("HasRight called without a right"); + return (undef); + } + + $args{EquivObjects} = [ @{ $args{EquivObjects} } ] if $args{EquivObjects}; + if ( $self->Disabled ) { - $RT::Logger->err( "Disabled User: " + $RT::Logger->error( "Disabled User: " . $self->id . " failed access check for " . $args{'Right'} ); return (undef); } - if ( !defined $args{'Right'} ) { - $RT::Logger->crit("HasRight called without a right"); - return (undef); - } - if ( defined( $args{'Object'} ) && UNIVERSAL::can( $args{'Object'}, 'id' ) - && $args{'Object'}->id ) - { + && $args{'Object'}->id ) { + push( @{ $args{'EquivObjects'} }, $args{Object} ); } else { - $RT::Logger->crit("$self HasRight called with no valid object"); + $RT::Logger->crit("HasRight called with no valid object"); return (undef); } # If this object is a ticket, we care about ticket roles and queue roles - if ( ( ref( $args{'Object'} ) eq 'RT::Ticket' ) && $args{'Object'}->Id ) { + if ( UNIVERSAL::isa( $args{'Object'} => 'RT::Ticket' ) ) { -# this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object -# and ask all the rest of our questions about the queue. + # this is a little bit hacky, but basically, now that we've done + # the ticket roles magic, we load the queue object + # and ask all the rest of our questions about the queue. push( @{ $args{'EquivObjects'} }, $args{'Object'}->QueueObj ); } @@ -354,161 +355,119 @@ sub HasRight { # }}} - # {{{ if we've cached a positive result for this query, return 1 - - my $cached_answer = $_ACL_CACHE->fetch($hashkey); - # Returns undef on cache miss + my $cached_answer = $_ACL_CACHE->fetch($hashkey); if ( defined $cached_answer ) { if ( $cached_answer == 1 ) { return (1); } elsif ( $cached_answer == -1 ) { - return (0); + return (undef); } } - my ( $or_look_at_object_rights, $or_check_roles ); - my $right = $args{'Right'}; + my $hitcount = $self->_HasRight( %args ); + + $_ACL_CACHE->set( $hashkey => $hitcount? 1:-1 ); + return ($hitcount); +} + +=head2 _HasRight - # {{{ Construct Right Match +Low level HasRight implementation, use HasRight method instead. + +=cut + +sub _HasRight +{ + my $self = shift; + my %args = ( + Right => undef, + Object => undef, + EquivObjects => [], + @_ + ); + + my $right = $args{'Right'}; + my @objects = @{ $args{'EquivObjects'} }; # If an object is defined, we want to look at rights for that object - my @look_at_objects; - push( @look_at_objects, "ACL.ObjectType = 'RT::System'" ) + push( @objects, 'RT::System' ) unless $self->can('_IsOverrideGlobalACL') - and $self->_IsOverrideGlobalACL( $args{Object} ); - - foreach my $obj ( @{ $args{'EquivObjects'} } ) { - next unless ( UNIVERSAL::can( $obj, 'id' ) ); - my $type = ref($obj); - my $id = $obj->id; - - unless ($id) { - use Carp; - Carp::cluck( - "Trying to check $type rights for an unspecified $type"); - $RT::Logger->crit( - "Trying to check $type rights for an unspecified $type"); + && $self->_IsOverrideGlobalACL( $args{Object} ); + + my ($check_roles, $check_objects) = ('',''); + if( @objects ) { + my @role_clauses; + my @object_clauses; + foreach my $obj ( @objects ) { + my $type = ref($obj)? ref($obj): $obj; + my $id; + $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id; + + my $role_clause = "Groups.Domain = '$type-Role'"; + # XXX: Groups.Instance is VARCHAR in DB, we should quote value + # if we want mysql 4.0 use indexes here. we MUST convert that + # field to integer and drop this quotes. + $role_clause .= " AND Groups.Instance = '$id'" if $id; + push @role_clauses, "($role_clause)"; + + my $object_clause = "ACL.ObjectType = '$type'"; + $object_clause .= " AND ACL.ObjectId = $id" if $id; + push @object_clauses, "($object_clause)"; } - push @look_at_objects, - "(ACL.ObjectType = '$type' AND ACL.ObjectId = '$id')"; - } - # }}} - - # {{{ Build that honkin-big SQL query + $check_roles .= join ' OR ', @role_clauses; + $check_objects = join ' OR ', @object_clauses; + } my $query_base = "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE " . # Only find superuser or rights with the name $right - "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') " . + "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') " # Never find disabled groups. - "AND Principals.Disabled = 0 " - . "AND CachedGroupMembers.Disabled = 0 " - . "AND Principals.id = Groups.id " - . # We always grant rights to Groups - -# See if the principal is a member of the group recursively or _is the rightholder_ -# never find recursively disabled group members -# also, check to see if the right is being granted _directly_ to this principal, -# as is the case when we want to look up group rights -"AND Principals.id = CachedGroupMembers.GroupId AND CachedGroupMembers.MemberId = '" - . $self->Id . "' " - . + . "AND Principals.Disabled = 0 " + . "AND CachedGroupMembers.Disabled = 0 " - # Make sure the rights apply to the entire system or to the object in question - "AND ( " . join( ' OR ', @look_at_objects ) . ") "; - -# The groups query does the query based on group membership and individual user rights - - my $groups_query = $query_base . - -# limit the result set to groups of types ACLEquivalence (user) UserDefined, SystemInternal and Personal -"AND ( ( ACL.PrincipalId = Principals.id AND ACL.PrincipalType = 'Group' AND " - . "(Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence' OR Groups.Domain = 'Personal'))" - . - - " ) "; - $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result - - my @roles; - foreach my $object ( @{ $args{'EquivObjects'} } ) { - push( @roles, $self->_RolesForObject( ref($object), $object->id ) ); - } - - # The roles query does the query based on roles - my $roles_query; - if (@roles) { - $roles_query = - $query_base . "AND " . " ( (" - . join( ' OR ', @roles ) . " ) " - . " AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.id AND Principals.PrincipalType = 'Group') "; - $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result - - } - - # }}} - - # {{{ Actually check the ACL by performing an SQL query - # $RT::Logger->debug("Now Trying $groups_query"); + # We always grant rights to Groups + . "AND Principals.id = Groups.id " + . "AND Principals.PrincipalType = 'Group' " + + # See if the principal is a member of the group recursively or _is the rightholder_ + # never find recursively disabled group members + # also, check to see if the right is being granted _directly_ to this principal, + # as is the case when we want to look up group rights + . "AND Principals.id = CachedGroupMembers.GroupId " + . "AND CachedGroupMembers.MemberId = ". $self->Id ." " + + # Make sure the rights apply to the entire system or to the object in question + . "AND ($check_objects) "; + + # The groups query does the query based on group membership and individual user rights + my $groups_query = $query_base + # limit the result set to groups of types ACLEquivalence (user), + # UserDefined, SystemInternal and Personal. All this we do + # via (ACL.PrincipalType = 'Group') condition + . "AND ACL.PrincipalId = Principals.id " + . "AND ACL.PrincipalType = 'Group' "; + + $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result my $hitcount = $self->_Handle->FetchResult($groups_query); + return 1 if $hitcount; # get out of here if success - # }}} - - # {{{ if there's a match, the right is granted - if ($hitcount) { - $_ACL_CACHE->set( $hashkey => 1 ); - return (1); - } + # The roles query does the query based on roles + my $roles_query = $query_base + . "AND ACL.PrincipalType = Groups.Type " + . "AND ($check_roles) "; + $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result - # Now check the roles query $hitcount = $self->_Handle->FetchResult($roles_query); + return 1 if $hitcount; # get out of here if success - if ($hitcount) { - $_ACL_CACHE->set( $hashkey => 1 ); - return (1); - } - - # We failed to find an acl hit - $_ACL_CACHE->set( $hashkey => -1 ); - return (undef); -} - -# }}} - -# {{{ _RolesForObject - - - -=head2 _RolesForObject( $object_type, $object_id) - -Returns an SQL clause finding role groups for Objects - -=cut - - -sub _RolesForObject { - my $self = shift; - my $type = shift; - my $id = shift; - - unless ($id) { - $id = '0'; - } - - # This should never be true. - unless ($id =~ /^\d+$/) { - $RT::Logger->crit("RT::Prinicipal::_RolesForObject called with type $type and a non-integer id: '$id'"); - $id = "'$id'"; - } - - my $clause = "(Groups.Domain = '".$type."-Role' AND Groups.Instance = $id) "; - - return($clause); + return 0; } # }}} @@ -578,6 +537,8 @@ sub _ReferenceId { # just return the value for non-objects return $scalar unless UNIVERSAL::can($scalar, 'id'); + return ref($scalar) unless $scalar->id; + # an object -- return the class and id return(ref($scalar)."-". $scalar->id); } diff --git a/rt/lib/RT/Queue_Overlay.pm b/rt/lib/RT/Queue_Overlay.pm index 2f180fc56..299072eea 100644 --- a/rt/lib/RT/Queue_Overlay.pm +++ b/rt/lib/RT/Queue_Overlay.pm @@ -521,7 +521,7 @@ Returns an RT::CustomFields object containing all global custom fields, as well sub CustomFields { my $self = shift; - warn "Queue->CustomFields is deprecated, use Queue->TicketCustomFields instead"; + warn "Queue->CustomFields is deprecated, use Queue->TicketCustomFields instead at (". join(":",caller).")"; return $self->TicketCustomFields(@_); } diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index d3a826808..341d88bfc 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -670,7 +670,14 @@ sub __Value { return('') if ( !defined($value) || $value eq ''); - return Encode::decode_utf8($value) || $value if $args{'decode_utf8'}; + if( $args{'decode_utf8'} ) { + # XXX: is_utf8 check should be here unless Encode bug would be fixed + # see http://rt.cpan.org/NoAuth/Bug.html?id=14559 + return Encode::decode_utf8($value) unless Encode::is_utf8($value); + } else { + # check is_utf8 here just to be shure + return Encode::encode_utf8($value) if Encode::is_utf8($value); + } return $value; } @@ -1290,7 +1297,7 @@ sub _AddLink { Target => $args{'Target'} ); if ( $old_link->Id ) { $RT::Logger->debug("$self Somebody tried to duplicate a link"); - return ( $old_link->id, $self->loc("Link already exists"), 0 ); + return ( $old_link->id, $self->loc("Link already exists") ); } # }}} @@ -1538,8 +1545,7 @@ sub CustomFieldLookupType { #TODO Deprecated API. Destroy in 3.6 sub _LookupTypes { my $self = shift; - $RT::Logger->warning("_LookupTypes call is deprecated. Replace with CustomFieldLookupType"); - $RT::Logger->warning("Besides, it was a private API. Were you doing using it?"); + $RT::Logger->warning("_LookupTypes call is deprecated at (". join(":",caller)."). Replace with CustomFieldLookupType"); return($self->CustomFieldLookupType); diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm index 3de9fc265..b6d980d4a 100644 --- a/rt/lib/RT/SearchBuilder.pm +++ b/rt/lib/RT/SearchBuilder.pm @@ -68,7 +68,7 @@ ok (require RT::SearchBuilder); package RT::SearchBuilder; use RT::Base; -use DBIx::SearchBuilder; +use DBIx::SearchBuilder 1.36; use strict; use vars qw(@ISA); diff --git a/rt/lib/RT/Ticket_Overlay.pm b/rt/lib/RT/Ticket_Overlay.pm index d04ececd8..1ab91e28e 100644 --- a/rt/lib/RT/Ticket_Overlay.pm +++ b/rt/lib/RT/Ticket_Overlay.pm @@ -750,7 +750,7 @@ sub Create { $RT::Handle->Commit(); $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name ); $ErrStr = join( "\n", $ErrStr, @non_fatal_errors ); - return ( $self->Id, $0, $ErrStr ); + return ( $self->Id, 0, $ErrStr ); } } @@ -2501,8 +2501,11 @@ sub DeleteLink { $direction='Base'; } - if ( $val ) { - my $remote_uri = RT::URI->new( $RT::SystemUser ); + if ( $args{'Silent'} ) { + return ( $val, $Msg ); + } + else { + my $remote_uri = RT::URI->new( $self->CurrentUser ); $remote_uri->FromURI( $remote_link ); my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( @@ -2512,6 +2515,17 @@ sub DeleteLink { TimeTaken => 0 ); + if ( $remote_uri->IsLocal ) { + + my $OtherObj = $remote_uri->Object; + my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink', + Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base} + : $LINKDIRMAP{$args{'Type'}}->{Target}, + OldValue => $self->URI, + ActivateScrips => ! $RT::LinkTransactionsRun1Scrip, + TimeTaken => 0 ); + } + return ( $Trans, $Msg ); } } @@ -2524,52 +2538,6 @@ sub DeleteLink { Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket. -=begin testing - -my $q1 = RT::Queue->new($RT::SystemUser); -my ($id,$msg) = $q1->Create(Name => 'LinkTest1'); -ok ($id,$msg); -my $q2 = RT::Queue->new($RT::SystemUser); -($id,$msg) = $q2->Create(Name => 'LinkTest2'); -ok ($id,$msg); - -my $u1 = RT::User->new($RT::SystemUser); -($id,$msg) =$u1->Create(Name => 'LinkTestUser'); - -ok ($id,$msg); - -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'CreateTicket'); -ok ($id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ModifyTicket'); -ok ($id,$msg); - -my $tid; - -my $creator = RT::CurrentUser->new($u1->id); - -my $ticket = RT::Ticket->new( $creator); -ok($ticket->isa('RT::Ticket')); -($id,$tid, $msg) = $ticket->Create(Subject => 'Link test 1', Queue => $q1->id); -ok ($id,$msg); - - -my $ticket2 = RT::Ticket->new($RT::SystemUser); -($id, $tid, $msg) = $ticket2->Create(Subject => 'Link test 2', Queue => $q2->id); -ok ($id, $msg); - -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); -ok(!$id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'CreateTicket'); -ok ($id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ModifyTicket'); -ok ($id,$msg); -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); -ok($id,$msg); -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => -1); -ok(!$id,$msg); - -=end testing - =cut sub AddLink { @@ -2657,7 +2625,7 @@ sub _AddLink { return ( $val, $Msg ); } else { - my $remote_uri = RT::URI->new( $RT::SystemUser ); + my $remote_uri = RT::URI->new( $self->CurrentUser ); $remote_uri->FromURI( $remote_link ); #Write the transaction @@ -2666,6 +2634,17 @@ sub _AddLink { Field => $LINKDIRMAP{$args{'Type'}}->{$direction}, NewValue => $remote_uri->URI || $remote_link, TimeTaken => 0 ); + + if ( $remote_uri->IsLocal ) { + + my $OtherObj = $remote_uri->Object; + my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink', + Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base} + : $LINKDIRMAP{$args{'Type'}}->{Target}, + NewValue => $self->URI, + ActivateScrips => ! $RT::LinkTransactionsRun1Scrip, + TimeTaken => 0 ); + } return ( $val, $Msg ); } @@ -3239,7 +3218,7 @@ Takes no arguments. Marks this ticket for garbage collection sub Kill { my $self = shift; - $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead."); + $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead at (". join(":",caller).")."); return $self->Delete; } @@ -3482,7 +3461,7 @@ sub _Set { #If we can't actually set the field to the value, don't record # a transaction. instead, get out of here. - if ( $ret == 0 ) { return ( 0, $msg ); } + return ( 0, $msg ) unless $ret; } if ( $args{'RecordTransaction'} == 1 ) { @@ -3698,13 +3677,17 @@ See L<RT::Record> sub CustomFieldValues { my $self = shift; my $field = shift; - unless ( $field =~ /^\d+$/ ) { + if ( $field and $field !~ /^\d+$/ ) { my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->LoadByNameAndQueue( Name => $field, Queue => $self->QueueObj->Id ); unless ( $cf->id ) { $cf->LoadByNameAndQueue( Name => $field, Queue => '0' ); } $field = $cf->id; + unless ( $field =~ /^\d+$/ ) { + # If we didn't find a valid cfid, give up. + return RT::CustomFieldValues->new($self->CurrentUser); + } } return $self->SUPER::CustomFieldValues($field); } diff --git a/rt/lib/RT/Tickets_Overlay.pm b/rt/lib/RT/Tickets_Overlay.pm index 0e6585c07..1c31f3ffd 100644 --- a/rt/lib/RT/Tickets_Overlay.pm +++ b/rt/lib/RT/Tickets_Overlay.pm @@ -1,38 +1,38 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # <jesse@bestpractical.com> -# +# # (Except where explicitly superseded by other copyright notices) -# -# +# +# # LICENSE: -# +# # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -# -# +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) -# +# # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that @@ -41,7 +41,7 @@ # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. -# +# # END BPS TAGGED BLOCK }}} # Major Changes: @@ -103,7 +103,7 @@ my %FIELDS = ( Type => [ 'ENUM', ], Creator => [ 'ENUM' => 'User', ], LastUpdatedBy => [ 'ENUM' => 'User', ], - Owner => [ 'ENUM' => 'User', ], + Owner => [ 'WATCHERFIELD' => 'Owner', ], EffectiveId => [ 'INT', ], id => [ 'INT', ], InitialPriority => [ 'INT', ], @@ -118,26 +118,26 @@ my %FIELDS = ( DependentOn => [ 'LINK' => From => 'DependsOn', ], DependedOnBy => [ 'LINK' => From => 'DependsOn', ], ReferredToBy => [ 'LINK' => From => 'RefersTo', ], - Told => ['DATE' => 'Told',], - Starts => ['DATE' => 'Starts',], - Started => ['DATE' => 'Started',], - Due => ['DATE' => 'Due',], - Resolved => ['DATE' => 'Resolved',], - LastUpdated => ['DATE' => 'LastUpdated',], - Created => ['DATE' => 'Created',], - Subject => ['STRING',], - Content => ['TRANSFIELD',], - ContentType => ['TRANSFIELD',], - Filename => ['TRANSFIELD',], - TransactionDate => ['TRANSDATE',], - Requestor => ['WATCHERFIELD' => 'Requestor',], - Requestors => ['WATCHERFIELD' => 'Requestor',], - Cc => ['WATCHERFIELD' => 'Cc',], - AdminCc => ['WATCHERFIELD' => 'AdminCc',], - Watcher => ['WATCHERFIELD'], - LinkedTo => ['LINKFIELD',], - CustomFieldValue =>['CUSTOMFIELD',], - CF => ['CUSTOMFIELD',], + Told => [ 'DATE' => 'Told', ], + Starts => [ 'DATE' => 'Starts', ], + Started => [ 'DATE' => 'Started', ], + Due => [ 'DATE' => 'Due', ], + Resolved => [ 'DATE' => 'Resolved', ], + LastUpdated => [ 'DATE' => 'LastUpdated', ], + Created => [ 'DATE' => 'Created', ], + Subject => [ 'STRING', ], + Content => [ 'TRANSFIELD', ], + ContentType => [ 'TRANSFIELD', ], + Filename => [ 'TRANSFIELD', ], + TransactionDate => [ 'TRANSDATE', ], + Requestor => [ 'WATCHERFIELD' => 'Requestor', ], + Requestors => [ 'WATCHERFIELD' => 'Requestor', ], + Cc => [ 'WATCHERFIELD' => 'Cc', ], + AdminCc => [ 'WATCHERFIELD' => 'AdminCc', ], + Watcher => ['WATCHERFIELD'], + LinkedTo => [ 'LINKFIELD', ], + CustomFieldValue => [ 'CUSTOMFIELD', ], + CF => [ 'CUSTOMFIELD', ], Updated => [ 'TRANSDATE', ], RequestorGroup => [ 'MEMBERSHIPFIELD' => 'Requestor', ], CCGroup => [ 'MEMBERSHIPFIELD' => 'Cc', ], @@ -159,7 +159,7 @@ my %dispatch = ( LINKFIELD => \&_LinkFieldLimit, CUSTOMFIELD => \&_CustomFieldLimit, ); -my %can_bundle = ( WATCHERFIELD => "yeps", ); +my %can_bundle = ( WATCHERFIELD => "yes", ); # Default EntryAggregator per type # if you specify OP, you must specify all valid OPs @@ -210,10 +210,10 @@ require RT::Tickets_Overlay_SQL; # {{{ sub SortFields @SORTFIELDS = qw(id Status - Queue Subject - Owner Created Due Starts Started - Told - Resolved LastUpdated Priority TimeWorked TimeLeft); + Queue Subject + Owner Created Due Starts Started + Told + Resolved LastUpdated Priority TimeWorked TimeLeft); =head2 SortFields @@ -268,7 +268,8 @@ sub _EnumLimit { $op = "!=" if $op eq "<>"; die "Invalid Operation: $op for $field" - unless $op eq "=" or $op eq "!="; + unless $op eq "=" + or $op eq "!="; my $meta = $FIELDS{$field}; if ( defined $meta->[1] ) { @@ -299,7 +300,7 @@ sub _IntLimit { my ( $sb, $field, $op, $value, @rest ) = @_; die "Invalid Operator $op for $field" - unless $op =~ /^(=|!=|>|<|>=|<=)$/; + unless $op =~ /^(=|!=|>|<|>=|<=)$/; $sb->_SQLLimit( FIELD => $field, @@ -326,7 +327,7 @@ sub _LinkLimit { die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS)/io; die "Incorrect Metadata for $field" - unless ( defined $meta->[1] and defined $meta->[2] ); + unless ( defined $meta->[1] and defined $meta->[2] ); my $direction = $meta->[1]; @@ -388,17 +389,17 @@ sub _LinkLimit { $sb->_SQLLimit( ALIAS => $linkalias, ENTRYAGGREGATOR => 'AND', - FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), - OPERATOR => 'IS', - VALUE => 'NULL', - QUOTEVALUE => '0', + FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), + OPERATOR => 'IS', + VALUE => 'NULL', + QUOTEVALUE => '0', ); } else { $sb->{_sql_linkalias} = $sb->NewAlias('Links') - unless defined $sb->{_sql_linkalias}; + unless defined $sb->{_sql_linkalias}; $sb->_OpenParen(); @@ -413,9 +414,9 @@ sub _LinkLimit { $sb->_SQLLimit( ALIAS => $sb->{_sql_linkalias}, ENTRYAGGREGATOR => 'AND', - FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), - OPERATOR => '=', - VALUE => $value, + FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), + OPERATOR => '=', + VALUE => $value, ); #If we're searching on target, join the base to ticket.id @@ -443,16 +444,16 @@ sub _DateLimit { my ( $sb, $field, $op, $value, @rest ) = @_; die "Invalid Date Op: $op" - unless $op =~ /^(=|>|<|>=|<=)$/; + unless $op =~ /^(=|>|<|>=|<=)$/; my $meta = $FIELDS{$field}; die "Incorrect Meta Data for $field" - unless ( defined $meta->[1] ); + unless ( defined $meta->[1] ); use POSIX 'strftime'; - - my $date = RT::Date->new($sb->CurrentUser); - $date->Set(Format => 'unknown', Value => $value); + + my $date = RT::Date->new( $sb->CurrentUser ); + $date->Set( Format => 'unknown', Value => $value ); my $time = $date->Unix; if ( $op eq "=" ) { @@ -461,8 +462,8 @@ sub _DateLimit { # particular single day. in the database, we need to check for > # and < the edges of that day. - my $daystart = - strftime( "%Y-%m-%d %H:%M", gmtime( $time - ( $time % 86400 ) ) ); + my $daystart = strftime( "%Y-%m-%d %H:%M", + gmtime( $time - ( $time % 86400 ) ) ); my $dayend = strftime( "%Y-%m-%d %H:%M", gmtime( $time + ( 86399 - $time % 86400 ) ) ); @@ -541,8 +542,6 @@ sub _TransDateLimit { $sb->{_sql_transalias} = $sb->NewAlias('Transactions') unless defined $sb->{_sql_transalias}; - $sb->{_sql_trattachalias} = $sb->NewAlias('Attachments') - unless defined $sb->{_sql_trattachalias}; my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); @@ -569,11 +568,11 @@ sub _TransDateLimit { @rest ); $sb->_SQLLimit( - ALIAS => $sb->{_sql_transalias}, - FIELD => 'Created', - OPERATOR => "<=", - VALUE => $dayend, - CASESENSITIVE => 0, + ALIAS => $sb->{_sql_transalias}, + FIELD => 'Created', + OPERATOR => "<=", + VALUE => $dayend, + CASESENSITIVE => 0, @rest, ENTRYAGGREGATOR => 'AND', ); @@ -594,15 +593,6 @@ sub _TransDateLimit { ); } - # Join Transactions To Attachments - - $sb->_SQLJoin( - ALIAS1 => $sb->{_sql_trattachalias}, - FIELD1 => 'TransactionId', - ALIAS2 => $sb->{_sql_transalias}, - FIELD2 => 'id', - ); - # Join Transactions to Tickets $sb->_SQLJoin( ALIAS1 => 'main', @@ -666,9 +656,9 @@ sub _TransLimit { my ( $self, $field, $op, $value, @rest ) = @_; $self->{_sql_transalias} = $self->NewAlias('Transactions') - unless defined $self->{_sql_transalias}; + unless defined $self->{_sql_transalias}; $self->{_sql_trattachalias} = $self->NewAlias('Attachments') - unless defined $self->{_sql_trattachalias}; + unless defined $self->{_sql_trattachalias}; $self->_OpenParen; @@ -791,8 +781,6 @@ sub _WatcherLimit { my $value = shift; my %rest = (@_); - $self->_OpenParen; - # Find out what sort of watcher we're looking for my $fieldname; if ( ref $field ) { @@ -800,84 +788,151 @@ sub _WatcherLimit { } else { $fieldname = $field; + $field = [ [ $field, $op, $value, %rest ] ]; # gross hack } my $meta = $FIELDS{$fieldname}; my $type = ( defined $meta->[1] ? $meta->[1] : undef ); -# We only want _one_ clause for all of requestors, cc, admincc -# It's less flexible than what we used to do, but now it sort of actually works. (no huge cartesian products that hose the db) - my $groups = $self->{ 'watcherlimit_' . ('global') . "_groups" } ||= - $self->NewAlias('Groups'); - my $groupmembers = - $self->{ 'watcherlimit_' . ('global') . "_groupmembers" } ||= - $self->NewAlias('CachedGroupMembers'); - my $users = $self->{ 'watcherlimit_' . ('global') . "_users" } ||= - $self->NewAlias('Users'); - -# Use regular joins instead of SQL joins since we don't want the joins inside ticketsql or we get a huge cartesian product - $self->SUPER::Limit( - ALIAS => $groups, - FIELD => 'Domain', - VALUE => 'RT::Ticket-Role', - ENTRYAGGREGATOR => 'AND' - ); - $self->Join( - ALIAS1 => $groups, - FIELD1 => 'Instance', - ALIAS2 => 'main', - FIELD2 => 'id' - ); - $self->Join( - ALIAS1 => $groups, - FIELD1 => 'id', - ALIAS2 => $groupmembers, - FIELD2 => 'GroupId' - ); - $self->Join( - ALIAS1 => $groupmembers, - FIELD1 => 'MemberId', - ALIAS2 => $users, - FIELD2 => 'id' - ); + # Owner was ENUM field, so "Owner = 'xxx'" allowed user to + # search by id and Name at the same time, this is workaround + # to preserve backward compatibility + if ( $fieldname eq 'Owner' ) { + my $flag = 0; + for my $chunk ( splice @$field ) { + my ( $f, $op, $value, %rest ) = @$chunk; + if ( !$rest{SUBKEY} && $op =~ /^!?=$/ ) { + $self->_OpenParen unless $flag++; + my $o = RT::User->new( $self->CurrentUser ); + $o->Load($value); + $value = $o->Id; + $self->_SQLLimit( + FIELD => 'Owner', + OPERATOR => $op, + VALUE => $value, + %rest, + ); + } + else { + push @$field, $chunk; + } + } + $self->_CloseParen if $flag; + return unless @$field; + } + + my $users = $self->_WatcherJoin($type); # If we're looking for multiple watchers of a given type, # TicketSQL will be handing it to us as an array of clauses in # $field - if ( ref $field ) { # gross hack - $self->_OpenParen; - for my $chunk (@$field) { - ( $field, $op, $value, %rest ) = @$chunk; - $self->_SQLLimit( - ALIAS => $users, - FIELD => $rest{SUBKEY} || 'EmailAddress', - VALUE => $value, - OPERATOR => $op, - CASESENSITIVE => 0, - %rest - ); - } - $self->_CloseParen; - } - else { + $self->_OpenParen; + for my $chunk (@$field) { + ( $field, $op, $value, %rest ) = @$chunk; + $rest{SUBKEY} ||= 'EmailAddress'; + + my $re_negative_op = qr[!=|NOT LIKE]; + $self->_OpenParen if $op =~ /$re_negative_op/; + $self->_SQLLimit( ALIAS => $users, - FIELD => $rest{SUBKEY} || 'EmailAddress', + FIELD => $rest{SUBKEY}, VALUE => $value, OPERATOR => $op, CASESENSITIVE => 0, %rest ); + + if ( $op =~ /$re_negative_op/ ) { + $self->_SQLLimit( + ALIAS => $users, + FIELD => $rest{SUBKEY}, + OPERATOR => 'IS', + VALUE => 'NULL', + ENTRYAGGREGATOR => 'OR', + ); + $self->_CloseParen; + } } + $self->_CloseParen; +} - $self->_SQLLimit( +=head2 _WatcherJoin + +Helper function which provides joins to a watchers table both for limits +and for ordering. + +=cut + +sub _WatcherJoin { + my $self = shift; + my $type = shift; + + # we cache joins chain per watcher type + # if we limit by requestor then we shouldn't join requestors again + # for sort or limit on other requestors + if ( $self->{'_watcher_join_users_alias'}{ $type || 'any' } ) { + return $self->{'_watcher_join_users_alias'}{ $type || 'any' }; + } + +# we always have watcher groups for ticket +# this join should be NORMAL +# XXX: if we change this from Join to NewAlias+Limit +# then Pg will complain because SB build wrong query. +# Query looks like "FROM (Tickets LEFT JOIN CGM ON(Groups.id = CGM.GroupId)), Groups" +# Pg doesn't like that fact that it doesn't know about Groups table yet when +# join CGM table into Tickets. Problem is in Join method which doesn't use +# ALIAS1 argument when build braces. + my $groups = $self->Join( + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'Groups', + FIELD2 => 'Instance', + ENTRYAGGREGATOR => 'AND' + ); + $self->SUPER::Limit( + ALIAS => $groups, + FIELD => 'Domain', + VALUE => 'RT::Ticket-Role', + ENTRYAGGREGATOR => 'AND' + ); + $self->SUPER::Limit( ALIAS => $groups, FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'AND' - ) - if ($type); + ) + if ($type); - $self->_CloseParen; + my $groupmembers = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $groups, + FIELD1 => 'id', + TABLE2 => 'CachedGroupMembers', + FIELD2 => 'GroupId' + ); + + # XXX: work around, we must hide groups that + # are members of the role group we search in, + # otherwise them result in wrong NULLs in Users + # table and break ordering. Now, we know that + # RT doesn't allow to add groups as members of the + # ticket roles, so we just hide entries in CGM table + # with MemberId == GroupId from results + my $groupmembers = $self->SUPER::Limit( + LEFTJOIN => $groupmembers, + FIELD => 'GroupId', + OPERATOR => '!=', + VALUE => "$groupmembers.MemberId", + QUOTEVALUE => 0, + ); + my $users = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $groupmembers, + FIELD1 => 'MemberId', + TABLE2 => 'Users', + FIELD2 => 'id' + ); + return $self->{'_watcher_join_users_alias'}{ $type || 'any' } = $users; } =head2 _WatcherMembershipLimit @@ -1110,11 +1165,10 @@ sub _CustomFieldLimit { } $field = $1 if $field =~ /^{(.+)}$/; # trim { } - -# If we're trying to find custom fields that don't match something, we -# want tickets where the custom field has no value at all. Note that -# we explicitly don't include the "IS NULL" case, since we would -# otherwise end up with a redundant clause. + # If we're trying to find custom fields that don't match something, we + # want tickets where the custom field has no value at all. Note that + # we explicitly don't include the "IS NULL" case, since we would + # otherwise end up with a redundant clause. my $null_columns_ok; if ( ( $op =~ /^NOT LIKE$/i ) or ( $op eq '!=' ) ) { @@ -1162,12 +1216,13 @@ sub _CustomFieldLimit { VALUE => $cfid, ENTRYAGGREGATOR => 'AND' ); - } else { + } + else { my $cfalias = $self->Join( - TYPE => 'left', - EXPRESSION => "'$field'", - TABLE2 => 'CustomFields', - FIELD2 => 'Name', + TYPE => 'left', + EXPRESSION => "'$field'", + TABLE2 => 'CustomFields', + FIELD2 => 'Name', ); $TicketCFs = $self->{_sql_object_cf_alias}{$cfkey} = $self->Join( @@ -1178,10 +1233,10 @@ sub _CustomFieldLimit { FIELD2 => 'CustomField', ); $self->SUPER::Limit( - LEFTJOIN => $TicketCFs, - FIELD => 'ObjectId', - VALUE => 'main.id', - QUOTEVALUE => 0, + LEFTJOIN => $TicketCFs, + FIELD => 'ObjectId', + VALUE => 'main.id', + QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND', ); } @@ -1193,11 +1248,12 @@ sub _CustomFieldLimit { ENTRYAGGREGATOR => 'AND' ); $self->SUPER::Limit( - LEFTJOIN => $TicketCFs, - FIELD => 'Disabled', - OPERATOR => '=', - VALUE => '0', - ENTRYAGGREGATOR => 'AND'); + LEFTJOIN => $TicketCFs, + FIELD => 'Disabled', + OPERATOR => '=', + VALUE => '0', + ENTRYAGGREGATOR => 'AND' + ); } $self->_OpenParen if ($null_columns_ok); @@ -1223,8 +1279,6 @@ sub _CustomFieldLimit { } $self->_CloseParen if ($null_columns_ok); - - } # End Helper Functions @@ -1251,13 +1305,15 @@ sub Limit { DESCRIPTION => undef, @_ ); - $args{'DESCRIPTION'} = $self->loc( "[_1] [_2] [_3]", - $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'} ) - if ( !defined $args{'DESCRIPTION'} ); + $args{'DESCRIPTION'} = $self->loc( + "[_1] [_2] [_3]", $args{'FIELD'}, + $args{'OPERATOR'}, $args{'VALUE'} + ) + if ( !defined $args{'DESCRIPTION'} ); my $index = $self->_NextIndex; - #make the TicketRestrictions hash the equivalent of whatever we just passed in; +#make the TicketRestrictions hash the equivalent of whatever we just passed in; %{ $self->{'TicketRestrictions'}{$index} } = %args; @@ -1265,11 +1321,15 @@ sub Limit { # If we're looking at the effective id, we don't want to append the other clause # which limits us to tickets where id = effective id - if ( $args{'FIELD'} eq 'EffectiveId' ) { + if ( $args{'FIELD'} eq 'EffectiveId' + && ( !$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) ) + { $self->{'looking_at_effective_id'} = 1; } - if ( $args{'FIELD'} eq 'Type' ) { + if ( $args{'FIELD'} eq 'Type' + && ( !$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) ) + { $self->{'looking_at_type'} = 1; } @@ -1286,7 +1346,7 @@ Returns a frozen string suitable for handing back to ThawLimits. sub _FreezeThawKeys { 'TicketRestrictions', 'restriction_index', 'looking_at_effective_id', - 'looking_at_type'; + 'looking_at_type'; } # {{{ sub FreezeLimits @@ -1323,8 +1383,8 @@ sub ThawLimits { require MIME::Base64; #We don't need to die if the thaw fails. - @{$self}{ $self->_FreezeThawKeys } = - eval { @{ Storable::thaw( MIME::Base64::base64_decode($in) ) }; }; + @{$self}{ $self->_FreezeThawKeys } + = eval { @{ Storable::thaw( MIME::Base64::base64_decode($in) ) }; }; $RT::Logger->error($@) if $@; @@ -1370,8 +1430,9 @@ sub LimitQueue { FIELD => 'Queue', VALUE => $args{VALUE}, OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => - join( ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE}, ), + DESCRIPTION => join( + ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE}, + ), ); } @@ -1457,8 +1518,8 @@ sub LimitType { FIELD => 'Type', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => - join( ' ', $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'}, ), + DESCRIPTION => join( ' ', + $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'}, ), ); } @@ -1485,9 +1546,8 @@ sub LimitSubject { FIELD => 'Subject', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'}, - ), + DESCRIPTION => join( ' ', + $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } @@ -1520,7 +1580,7 @@ sub LimitId { VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => - join( ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'}, ), + join( ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'}, ), ); } @@ -1595,8 +1655,8 @@ sub LimitFinalPriority { VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', - $self->loc('Final Priority'), - $args{'OPERATOR'}, $args{'VALUE'}, ), + $self->loc('Final Priority'), $args{'OPERATOR'}, + $args{'VALUE'}, ), ); } @@ -1674,8 +1734,8 @@ sub LimitContent { VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, DESCRIPTION => join( ' ', - $self->loc('Ticket content'), - $args{'OPERATOR'}, $args{'VALUE'}, ), + $self->loc('Ticket content'), $args{'OPERATOR'}, + $args{'VALUE'}, ), ); } @@ -1759,8 +1819,8 @@ sub LimitOwner { FIELD => 'Owner', VALUE => $args{'VALUE'}, OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => - join( ' ', $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(), ), + DESCRIPTION => join( ' ', + $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(), ), ); } @@ -1819,10 +1879,9 @@ sub LimitWatcher { sub LimitRequestor { my $self = shift; my %args = (@_); - my ( $package, $filename, $line ) = caller; - $RT::Logger->error( -"Tickets->LimitRequestor is deprecated. please rewrite call at $package - $filename: $line" - ); + $RT::Logger->error( "Tickets->LimitRequestor is deprecated at (" + . join( ":", caller ) + . ")" ); $self->LimitWatcher( TYPE => 'Requestor', @_ ); } @@ -1898,8 +1957,8 @@ sub LimitLinkedFrom { # translate RT2 From/To naming to RT3 TicketSQL naming my %fromToMap = qw(DependsOn DependentOn - MemberOf HasMember - RefersTo ReferredToBy); + MemberOf HasMember + RefersTo ReferredToBy); my $type = $args{'TYPE'}; $type = $fromToMap{$type} if exists( $fromToMap{$type} ); @@ -2032,10 +2091,9 @@ sub LimitDate { #Set the description if we didn't get handed it above unless ( $args{'DESCRIPTION'} ) { - $args{'DESCRIPTION'} = - $args{'FIELD'} . " " - . $args{'OPERATOR'} . " " - . $args{'VALUE'} . " GMT"; + $args{'DESCRIPTION'} = $args{'FIELD'} . " " + . $args{'OPERATOR'} . " " + . $args{'VALUE'} . " GMT"; } $self->Limit(%args); @@ -2109,10 +2167,9 @@ sub LimitTransactionDate { #Set the description if we didn't get handed it above unless ( $args{'DESCRIPTION'} ) { - $args{'DESCRIPTION'} = - $args{'FIELD'} . " " - . $args{'OPERATOR'} . " " - . $args{'VALUE'} . " GMT"; + $args{'DESCRIPTION'} = $args{'FIELD'} . " " + . $args{'OPERATOR'} . " " + . $args{'VALUE'} . " GMT"; } $self->Limit(%args); @@ -2168,12 +2225,12 @@ sub LimitCustomField { #If we are looking to compare with a null value. if ( $args{'OPERATOR'} =~ /^is$/i ) { - $args{'DESCRIPTION'} ||= - $self->loc( "Custom field [_1] has no value.", $CF->Name ); + $args{'DESCRIPTION'} + ||= $self->loc( "Custom field [_1] has no value.", $CF->Name ); } elsif ( $args{'OPERATOR'} =~ /^is not$/i ) { - $args{'DESCRIPTION'} ||= - $self->loc( "Custom field [_1] has a value.", $CF->Name ); + $args{'DESCRIPTION'} + ||= $self->loc( "Custom field [_1] has a value.", $CF->Name ); } # if we're not looking to compare with a null value @@ -2185,22 +2242,22 @@ sub LimitCustomField { my $q = ""; if ( $CF->Queue ) { my $qo = new RT::Queue( $self->CurrentUser ); - $qo->load( $CF->Queue ); + $qo->Load( $CF->Queue ); $q = $qo->Name; } my @rest; @rest = ( ENTRYAGGREGATOR => 'AND' ) - if ( $CF->Type eq 'SelectMultiple' ); + if ( $CF->Type eq 'SelectMultiple' ); $self->Limit( VALUE => $args{VALUE}, FIELD => "CF." - . ( + . ( $q ? $q . ".{" . $CF->Name . "}" : $CF->Name - ), + ), OPERATOR => $args{OPERATOR}, CUSTOMFIELD => 1, @rest, @@ -2289,7 +2346,8 @@ sub ItemsArrayRef { push( @{ $self->{'items_array'} }, $item ); } $self->GotoItem($placeholder); - $self->{'items_array'} = $self->ItemsOrderBy( $self->{'items_array'} ); + $self->{'items_array'} + = $self->ItemsOrderBy( $self->{'items_array'} ); } return ( $self->{'items_array'} ); } @@ -2305,18 +2363,21 @@ sub Next { my $Ticket = $self->SUPER::Next(); if ( ( defined($Ticket) ) and ( ref($Ticket) ) ) { - if ( $Ticket->__Value('Status') eq 'deleted' && - !$self->{'allow_deleted_search'} ) { - return($self->Next()); - } - # Since Ticket could be granted with more rights instead - # of being revoked, it's ok if queue rights allow - # ShowTicket. It seems need another query, but we have - # rights cache in Principal::HasRight. - elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket') || - $Ticket->CurrentUserHasRight('ShowTicket')) { - return($Ticket); - } + if ( $Ticket->__Value('Status') eq 'deleted' + && !$self->{'allow_deleted_search'} ) + { + return ( $self->Next() ); + } + + # Since Ticket could be granted with more rights instead + # of being revoked, it's ok if queue rights allow + # ShowTicket. It seems need another query, but we have + # rights cache in Principal::HasRight. + elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket') + || $Ticket->CurrentUserHasRight('ShowTicket') ) + { + return ($Ticket); + } if ( $Ticket->__Value('Status') eq 'deleted' ) { return ( $self->Next() ); @@ -2399,10 +2460,10 @@ sub RestrictionValues { my $self = shift; my $field = shift; map $self->{'TicketRestrictions'}{$_}{'VALUE'}, grep { - $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field - && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "=" - } - keys %{ $self->{'TicketRestrictions'} }; + $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field + && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "=" + } + keys %{ $self->{'TicketRestrictions'} }; } # }}} @@ -2461,9 +2522,9 @@ sub _RestrictionsToClauses { #use Data::Dumper; #print Dumper($restriction),"\n"; - # We need to reimplement the subclause aggregation that SearchBuilder does. - # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main', - # Then SB AND's the different Subclauses together. + # We need to reimplement the subclause aggregation that SearchBuilder does. + # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main', + # Then SB AND's the different Subclauses together. # So, we want to group things into Subclauses, convert them to # SQL, and then join them with the appropriate DefaultEA. @@ -2486,14 +2547,15 @@ sub _RestrictionsToClauses { } die "I don't know about $field yet" - unless ( exists $FIELDS{$realfield} or $restriction->{CUSTOMFIELD} ); + unless ( exists $FIELDS{$realfield} + or $restriction->{CUSTOMFIELD} ); my $type = $FIELDS{$realfield}->[0]; my $op = $restriction->{'OPERATOR'}; my $value = ( - grep { defined } - map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET) + grep {defined} + map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET) )[0]; # this performs the moral equivalent of defined or/dor/C<//>, @@ -2511,10 +2573,12 @@ sub _RestrictionsToClauses { # defined $restriction->{'TARGET'} ? # $restriction->{TARGET} ) - my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND"; + my $ea = $restriction->{ENTRYAGGREGATOR} + || $DefaultEA{$type} + || "AND"; if ( ref $ea ) { die "Invalid operator $op for $field ($type)" - unless exists $ea->{$op}; + unless exists $ea->{$op}; $ea = $ea->{$op}; } @@ -2574,7 +2638,7 @@ sub _ProcessRestrictions { } else { $sql = $self->ClausesToSQL($clauseRef); - $self->FromSQL($sql); + $self->FromSQL($sql) if $sql; } } @@ -2602,7 +2666,7 @@ sub _BuildItemMap { $self->{'item_map'}->{$id}->{'defined'} = 1; $self->{'item_map'}->{$id}->{prev} = $prev; $self->{'item_map'}->{$id}->{next} = $items->[0]->EffectiveId - if ( $items->[0] ); + if ( $items->[0] ); $prev = $id; } $self->{'item_map'}->{'last'} = $prev; @@ -2623,13 +2687,14 @@ $ItemMap->{$id}->{next} = the ticket id found after $id sub ItemMap { my $self = shift; $self->_BuildItemMap() - unless ( $self->{'items_array'} and $self->{'item_map'} ); + unless ( $self->{'items_array'} and $self->{'item_map'} ); return ( $self->{'item_map'} ); } =cut + } @@ -2650,7 +2715,6 @@ sub PrepForSerialization { $self->RedoSearch(); } - =head1 FLAGS RT::Tickets supports several flags which alter search behavior: @@ -2667,6 +2731,15 @@ BUG: There should be an API for this =cut +=begin testing + +# We assume that we've got some tickets hanging around from before. +ok( my $unlimittickets = RT::Tickets->new( $RT::SystemUser ) ); +ok( $unlimittickets->UnLimit ); +ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets" ); + +=end testing + 1; diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm index 677391702..525f252af 100644 --- a/rt/lib/RT/Tickets_Overlay_SQL.pm +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -85,11 +85,13 @@ sub _InitSQL { sub _SQLLimit { my $self = shift; my %args = (@_); - if ($args{'FIELD'} eq 'EffectiveId') { + if ($args{'FIELD'} eq 'EffectiveId' && + (!$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) ) { $self->{'looking_at_effective_id'} = 1; } - if ($args{'FIELD'} eq 'Type') { + if ($args{'FIELD'} eq 'Type' && + (!$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) ) { $self->{'looking_at_type'} = 1; } @@ -288,7 +290,7 @@ sub _parser { # print "$ea Key=[$key] op=[$op] val=[$val]\n"; - my $subkey; + my $subkey = ''; if ($key =~ /^(.+?)\.(.+)$/) { $key = $1; $subkey = $2; @@ -377,11 +379,11 @@ sub ClausesToSQL { my $first = 1; # Build SQL from the data hash - for my $data ( @{ $clauses->{$f} } ) { - $sql .= $data->[0] unless $first; $first=0; - $sql .= " '". $data->[2] . "' "; - $sql .= $data->[3] . " "; - $sql .= "'". $data->[4] . "' "; + for my $data ( @{ $clauses->{$f} } ) { + $sql .= $data->[0] unless $first; $first=0; # ENTRYAGGREGATOR + $sql .= " '". $data->[2] . "' "; # FIELD + $sql .= $data->[3] . " "; # OPERATOR + $sql .= "'". $data->[4] . "' "; # VALUE } push @sql, " ( " . $sql . " ) "; diff --git a/rt/lib/RT/Transaction_Overlay.pm b/rt/lib/RT/Transaction_Overlay.pm index b179084c8..f6647c647 100644 --- a/rt/lib/RT/Transaction_Overlay.pm +++ b/rt/lib/RT/Transaction_Overlay.pm @@ -365,7 +365,7 @@ sub ContentObj { # which has some content. else { - my $all_parts = $Attachment->Children(); + my $all_parts = $self->Attachments; while ( my $part = $all_parts->Next ) { if (( $part->ContentType() =~ '^(text/plain$|message/)' ) && $part->Content() ) { return ($part); diff --git a/rt/lib/RT/Transactions_Overlay.pm b/rt/lib/RT/Transactions_Overlay.pm index fe5157556..3ea8f12a0 100644 --- a/rt/lib/RT/Transactions_Overlay.pm +++ b/rt/lib/RT/Transactions_Overlay.pm @@ -100,7 +100,7 @@ sub Limit { my %args = (@_); if ($args{'FIELD'} eq 'Ticket') { - Carp::cluck("Historical code calling RT::Transactions::Limit with a 'Ticket'. This deprecated API will be deleted in 3.6"); + Carp::cluck("Historical code calling RT::Transactions::Limit with a 'Ticket' at (". join(":",caller)."). This deprecated API will be deleted in 3.6"); $self->SUPER::Limit(FIELD => 'ObjectType', OPERATOR => '=', VALUE =>'RT::Ticket'); $args{'FIELD'} = 'ObjectId'; $self->SUPER::Limit(%args); diff --git a/rt/lib/RT/Users_Overlay.pm b/rt/lib/RT/Users_Overlay.pm index 4bb9f8f91..19f1beadf 100644 --- a/rt/lib/RT/Users_Overlay.pm +++ b/rt/lib/RT/Users_Overlay.pm @@ -88,10 +88,15 @@ sub _Init { $self->{'princalias'} = $self->NewAlias('Principals'); + # XXX: should be generalized $self->Join( ALIAS1 => 'main', FIELD1 => 'id', ALIAS2 => $self->{'princalias'}, FIELD2 => 'id' ); + $self->Limit( ALIAS => $self->{'princalias'}, + FIELD => 'PrincipalType', + VALUE => 'User', + ); return (@result); } @@ -102,9 +107,9 @@ sub _Init { Returns the string that represents this Users object's primary "Principals" alias. - =cut +# XXX: should be generalized sub PrincipalsAlias { my $self = shift; return($self->{'princalias'}); @@ -141,10 +146,11 @@ Only find items that haven\'t been disabled =cut +# XXX: should be generalized sub LimitToEnabled { my $self = shift; - $self->Limit( ALIAS => $self->{'princalias'}, + $self->Limit( ALIAS => $self->PrincipalsAlias, FIELD => 'Disabled', VALUE => '0', OPERATOR => '=' ); @@ -187,7 +193,7 @@ sub MemberOfGroup { my $groupalias = $self->NewAlias('CachedGroupMembers'); # Join the principal to the groups table - $self->Join( ALIAS1 => $self->{'princalias'}, + $self->Join( ALIAS1 => $self->PrincipalsAlias, FIELD1 => 'id', ALIAS2 => $groupalias, FIELD2 => 'MemberId' ); @@ -288,69 +294,75 @@ is($users->Count, 1, "RTxUserRight found for RTxObj2"); =end testing - find all users who the right Right for this group, either individually or as members of groups - If passed a queue object, with no id, it will find users who have that right for _any_ queue - - =cut -sub WhoHaveRight { +# XXX: should be generalized +sub _JoinGroupMembers +{ my $self = shift; my %args = ( - Right => undef, - Object => undef, - IncludeSystemRights => undef, - IncludeSuperusers => undef, IncludeSubgroupMembers => 1, - EquivObjects => [ ], @_ ); - if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) { - $RT::Logger->crit( "$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API"); - return (undef); - } - - - # Find only members of groups that have the right. - - my $acl = $self->NewAlias('ACL'); - my $groups = $self->NewAlias('Groups'); - my $userprinc = $self->{'princalias'}; - -# The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups -# if we bind to CachedGroupMembers, we'll find all members of groups recursively. -# if we don't we'll find only 'direct' members of the group in question - my $cgm; + my $principals = $self->PrincipalsAlias; + # The cachedgroupmembers table is used for unrolling group memberships + # to allow fast lookups. if we bind to CachedGroupMembers, we'll find + # all members of groups recursively. if we don't we'll find only 'direct' + # members of the group in question + my $group_members; if ( $args{'IncludeSubgroupMembers'} ) { - $cgm = $self->NewAlias('CachedGroupMembers'); + $group_members = $self->NewAlias('CachedGroupMembers'); } else { - $cgm = $self->NewAlias('GroupMembers'); + $group_members = $self->NewAlias('GroupMembers'); } -#Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc) $self->Join( - ALIAS1 => $cgm, + ALIAS1 => $group_members, FIELD1 => 'MemberId', - ALIAS2 => $userprinc, + ALIAS2 => $principals, FIELD2 => 'id' ); + return $group_members; +} + +# XXX: should be generalized +sub _JoinGroups +{ + my $self = shift; + my %args = (@_); + + my $group_members = $self->_JoinGroupMembers( %args ); + my $groups = $self->NewAlias('Groups'); $self->Join( ALIAS1 => $groups, FIELD1 => 'id', - ALIAS2 => $cgm, + ALIAS2 => $group_members, FIELD2 => 'GroupId' ); -# {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser + return $groups; +} + +# XXX: should be generalized +sub _JoinACL +{ + my $self = shift; + my %args = ( + Right => undef, + IncludeSuperusers => undef, + @_, + ); + + my $acl = $self->NewAlias('ACL'); $self->Limit( ALIAS => $acl, FIELD => 'RightName', @@ -358,7 +370,6 @@ sub WhoHaveRight { VALUE => $args{Right} || 'NULL', ENTRYAGGREGATOR => 'OR' ); - if ( $args{'IncludeSuperusers'} and $args{'Right'} ) { $self->Limit( ALIAS => $acl, @@ -368,97 +379,240 @@ sub WhoHaveRight { ENTRYAGGREGATOR => 'OR' ); } + return $acl; +} - # }}} +# XXX: should be generalized +sub _GetEquivObjects +{ + my $self = shift; + my %args = ( + Object => undef, + IncludeSystemRights => undef, + EquivObjects => [ ], + @_ + ); + return () unless $args{'Object'}; + + my @objects = ($args{'Object'}); + if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) { + # If we're looking at ticket rights, we also want to look at the associated queue rights. + # this is a little bit hacky, but basically, now that we've done the ticket roles magic, + # we load the queue object and ask all the rest of our questions about the queue. + + # XXX: This should be abstracted into object itself + if( $args{'Object'}->id ) { + push @objects, $args{'Object'}->QueueObj; + } else { + push @objects, 'RT::Queue'; + } + } - my ( $or_check_ticket_roles, $or_check_roles ); - my $which_object = "$acl.ObjectType = 'RT::System'"; + if( $args{'IncludeSystemRights'} ) { + push @objects, 'RT::System'; + } + push @objects, @{ $args{'EquivObjects'} }; + return grep $_, @objects; +} - if ( defined $args{'Object'} ) { - if ( ref( $args{'Object'} ) eq 'RT::Ticket' ) { - $or_check_ticket_roles = " OR ( $groups.Domain = 'RT::Ticket-Role' AND $groups.Instance = " . $args{'Object'}->Id . ") "; +# XXX: should be generalized +sub WhoHaveRight { + my $self = shift; + my %args = ( + Right => undef, + Object => undef, + IncludeSystemRights => undef, + IncludeSuperusers => undef, + IncludeSubgroupMembers => 1, + EquivObjects => [ ], + @_ + ); -# If we're looking at ticket rights, we also want to look at the associated queue rights. -# this is a little bit hacky, but basically, now that we've done the ticket roles magic, -# we load the queue object and ask all the rest of our questions about the queue. - $args{'Object'} = $args{'Object'}->QueueObj; - } + if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) { + $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API"); + return (undef); + } - # TODO XXX This really wants some refactoring - if ( ref( $args{'Object'} ) eq 'RT::Queue' ) { - $or_check_roles = " OR ( ( ($groups.Domain = 'RT::Queue-Role' "; - $or_check_roles .= "AND $groups.Instance = " . $args{'Object'}->id if ( $args{'Object'}->id ); - $or_check_roles .= ") $or_check_ticket_roles ) " . " AND $groups.Type = $acl.PrincipalType) "; - } - if ( $args{'IncludeSystemRights'} ) { - $which_object .= ' OR '; - } - else { - $which_object = ''; - } - foreach my $obj ( @{ $args{'EquivObjects'} } ) { - $which_object .= "($acl.ObjectType = '" . ref( $obj ) . "' AND $acl.ObjectId = " . $obj->id . ") OR "; - } - $which_object .= " ($acl.ObjectType = '" . ref( $args{'Object'} ) . "'"; - if ( $args{'Object'}->id ) { - $which_object .= " AND $acl.ObjectId = " . $args{'Object'}->id; + my $from_role = $self->Clone; + $from_role->WhoHaveRoleRight( %args ); + + my $from_group = $self->Clone; + $from_group->WhoHaveGroupRight( %args ); + + #XXX: DIRTY HACK + use DBIx::SearchBuilder::Union; + my $union = new DBIx::SearchBuilder::Union; + $union->add($from_role); + $union->add($from_group); + %$self = %$union; + bless $self, ref($union); + + return; +} +# }}} + +# XXX: should be generalized +sub WhoHaveRoleRight +{ + my $self = shift; + my %args = ( + Right => undef, + Object => undef, + IncludeSystemRights => undef, + IncludeSuperusers => undef, + IncludeSubgroupMembers => 1, + EquivObjects => [ ], + @_ + ); + + my $groups = $self->_JoinGroups( %args ); + my $acl = $self->_JoinACL( %args ); + + my ($check_roles, $check_objects) = ('',''); + + my @objects = $self->_GetEquivObjects( %args ); + if ( @objects ) { + my @role_clauses; + my @object_clauses; + foreach my $obj ( @objects ) { + my $type = ref($obj)? ref($obj): $obj; + my $id; + $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id; + + my $role_clause = "$groups.Domain = '$type-Role'"; + # XXX: Groups.Instance is VARCHAR in DB, we should quote value + # if we want mysql 4.0 use indexes here. we MUST convert that + # field to integer and drop this quotes. + $role_clause .= " AND $groups.Instance = '$id'" if $id; + push @role_clauses, "($role_clause)"; + + my $object_clause = "$acl.ObjectType = '$type'"; + $object_clause .= " AND $acl.ObjectId = $id" if $id; + push @object_clauses, "($object_clause)"; } - $which_object .= ") "; + $check_roles .= join ' OR ', @role_clauses; + $check_objects = join ' OR ', @object_clauses; + } else { + if( !$args{'IncludeSystemRights'} ) { + $check_objects = "($acl.ObjectType != 'RT::System')"; + } } - $self->_AddSubClause( "WhichObject", "($which_object)" ); - $self->_AddSubClause( - "WhichGroup", - qq{ ( ( $acl.PrincipalId = $groups.id AND $acl.PrincipalType = 'Group' - AND ( $groups.Domain = 'SystemInternal' OR $groups.Domain = 'UserDefined' OR $groups.Domain = 'ACLEquivalence')) - $or_check_roles) } - ); - # only include regular RT users - $self->LimitToEnabled; + + $self->_AddSubClause( "WhichObject", "($check_objects)" ); + $self->_AddSubClause( "WhichRole", "($check_roles)" ); + + $self->Limit( ALIAS => $acl, + FIELD => 'PrincipalType', + VALUE => "$groups.Type", + QUOTEVALUE => 0, + ); # no system user - $self->Limit( ALIAS => $userprinc, FIELD => 'id', OPERATOR => '!=', VALUE => $RT::SystemUser->id); + $self->Limit( ALIAS => $self->PrincipalsAlias, + FIELD => 'id', + OPERATOR => '!=', + VALUE => $RT::SystemUser->id + ); + return; +} +# XXX: should be generalized +sub _JoinGroupMembersForGroupRights +{ + my $self = shift; + my %args = (@_); + my $group_members = $self->_JoinGroupMembers( %args ); + $self->Limit( ALIAS => $args{'ACLAlias'}, + FIELD => 'PrincipalId', + VALUE => "$group_members.GroupId", + QUOTEVALUE => 0, + ); +} + +# XXX: should be generalized +sub WhoHaveGroupRight +{ + my $self = shift; + my %args = ( + Right => undef, + Object => undef, + IncludeSystemRights => undef, + IncludeSuperusers => undef, + IncludeSubgroupMembers => 1, + EquivObjects => [ ], + @_ + ); + + # Find only rows where the right granted is + # the one we're looking up or _possibly_ superuser + my $acl = $self->_JoinACL( %args ); + + my ($check_objects) = (''); + my @objects = $self->_GetEquivObjects( %args ); + + if ( @objects ) { + my @object_clauses; + foreach my $obj ( @objects ) { + my $type = ref($obj)? ref($obj): $obj; + my $id; + $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id; + + my $object_clause = "$acl.ObjectType = '$type'"; + $object_clause .= " AND $acl.ObjectId = $id" if $id; + push @object_clauses, "($object_clause)"; + } + + $check_objects = join ' OR ', @object_clauses; + } else { + if( !$args{'IncludeSystemRights'} ) { + $check_objects = "($acl.ObjectType != 'RT::System')"; + } + } + $self->_AddSubClause( "WhichObject", "($check_objects)" ); + + $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl ); + # Find only members of groups that have the right. + $self->Limit( ALIAS => $acl, + FIELD => 'PrincipalType', + VALUE => 'Group', + ); + + # no system user + $self->Limit( ALIAS => $self->PrincipalsAlias, + FIELD => 'id', + OPERATOR => '!=', + VALUE => $RT::SystemUser->id + ); + return; } -# }}} -# {{{ WhoBelongToGroups +# {{{ WhoBelongToGroups =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 } =cut +# XXX: should be generalized sub WhoBelongToGroups { my $self = shift; my %args = ( Groups => undef, IncludeSubgroupMembers => 1, @_ ); - # Unprivileged users can't be granted real system rights. + # Unprivileged users can't be granted real system rights. # is this really the right thing to be saying? $self->LimitToPrivileged(); - my $userprinc = $self->{'princalias'}; - my $cgm; - - # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups - # if we bind to CachedGroupMembers, we'll find all members of groups recursively. - # if we don't we'll find only 'direct' members of the group in question - - if ( $args{'IncludeSubgroupMembers'} ) { - $cgm = $self->NewAlias('CachedGroupMembers'); - } - else { - $cgm = $self->NewAlias('GroupMembers'); - } - - #Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc) - $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId', - ALIAS2 => $userprinc, FIELD2 => 'id' ); + my $group_members = $self->_JoinGroupMembers( %args ); foreach my $groupid (@{$args{'Groups'}}) { - $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR') - + $self->Limit( ALIAS => $group_members, + FIELD => 'GroupId', + VALUE => $groupid, + QUOTEVALUE => 0, + ENTRYAGGREGATOR => 'OR', + ); } } # }}} diff --git a/rt/lib/t/regression/06mailgateway.t b/rt/lib/t/regression/06mailgateway.t index 8486aea9e..1bdc38a69 100644 --- a/rt/lib/t/regression/06mailgateway.t +++ b/rt/lib/t/regression/06mailgateway.t @@ -62,7 +62,7 @@ use RT::I18N; ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url http://this.test.for.non-connection.is.expected.to.generate.an.error"), "Opened the mailgate - The error below is expected - $@"); print MAIL <<EOF; From: root\@localhost -To: rt\@example.com +To: rt\@$RT::rtname Subject: This is a test of new ticket creation Foob! @@ -78,7 +78,7 @@ is ( $? >> 8, 75, "The error message above is expected The mail gateway exited w ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost -To: rt\@example.com +To: rt\@$RT::rtname Subject: This is a test of new ticket creation Blah! @@ -105,8 +105,8 @@ ok ($tick->Subject eq 'This is a test of new ticket creation', "Created the tick ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist\@example.com -To: rt\@example.com +From: doesnotexist\@$RT::rtname +To: rt\@$RT::rtname Subject: This is a test of new ticket creation as an unknown user Blah! @@ -123,7 +123,7 @@ $tick = $tickets->First(); ok ($tick->Id, "found ticket ".$tick->Id); ok ($tick->Subject ne 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account"); my $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist@example.com'); +$u->Load("doesnotexist\@$RT::rtname"); ok( $u->Id == 0, " user does not exist and was not created by failed ticket submission"); @@ -141,8 +141,8 @@ ok ($val, "Granted everybody the right to create tickets - $msg"); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist\@example.com -To: rt\@example.com +From: doesnotexist\@$RT::rtname +To: rt\@$RT::rtname Subject: This is a test of new ticket creation as an unknown user Blah! @@ -160,7 +160,7 @@ $tick = $tickets->First(); ok ($tick->Id, "found ticket ".$tick->Id); ok ($tick->Subject eq 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account"); $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist@example.com'); +$u->Load("doesnotexist\@$RT::rtname"); ok( $u->Id != 0, " user does not exist and was created by ticket submission"); # }}} @@ -174,9 +174,9 @@ ok( $u->Id != 0, " user does not exist and was created by ticket submission"); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist-2\@example.com -To: rt\@example.com -Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user +From: doesnotexist-2\@$RT::rtname +To: rt\@$RT::rtname +Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a reply as an unknown user Blah! (Should not work.) Foob! @@ -186,7 +186,7 @@ close (MAIL); is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist-2@example.com'); +$u->Load('doesnotexist-2@$RT::rtname'); ok( $u->Id == 0, " user does not exist and was not created by ticket correspondence submission"); # }}} @@ -199,9 +199,9 @@ ok ($val, "Granted everybody the right to reply to tickets - $msg"); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist-2\@example.com -To: rt\@example.com -Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user +From: doesnotexist-2\@$RT::rtname +To: rt\@$RT::rtname +Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a reply as an unknown user Blah! Foob! @@ -212,7 +212,7 @@ is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist-2@example.com'); +$u->Load("doesnotexist-2\@$RT::rtname"); ok( $u->Id != 0, " user exists and was created by ticket correspondence submission"); # }}} @@ -225,9 +225,9 @@ ok( $u->Id != 0, " user exists and was created by ticket correspondence submissi ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action comment"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist-3\@example.com -To: rt\@example.com -Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user +From: doesnotexist-3\@$RT::rtname +To: rt\@$RT::rtname +Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a comment as an unknown user Blah! (Should not work.) Foob! @@ -238,7 +238,7 @@ close (MAIL); is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist-3@example.com'); +$u->Load("doesnotexist-3\@$RT::rtname"); ok( $u->Id == 0, " user does not exist and was not created by ticket comment submission"); # }}} @@ -250,9 +250,9 @@ ok ($val, "Granted everybody the right to reply to tickets - $msg"); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action comment"), "Opened the mailgate - $@"); print MAIL <<EOF; -From: doesnotexist-3\@example.com -To: rt\@example.com -Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user +From: doesnotexist-3\@$RT::rtname +To: rt\@$RT::rtname +Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a comment as an unknown user Blah! Foob! @@ -263,7 +263,7 @@ close (MAIL); is ($? >> 8, 0, "The mail gateway exited normally. yay"); $u = RT::User->new($RT::SystemUser); -$u->Load('doesnotexist-3@example.com'); +$u->Load("doesnotexist-3\@$RT::rtname"); ok( $u->Id != 0, " user exists and was created by ticket comment submission"); # }}} @@ -351,7 +351,7 @@ ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --act print MAIL <<EOF; From: root\@localhost -To: rtemail\@example.com +To: rtemail\@$RT::rtname Subject: This is a test of I18N ticket creation Content-Type: text/plain; charset="utf-8" @@ -385,7 +385,7 @@ ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --act print MAIL <<EOF; From: root\@localhost -To: rtemail\@example.com +To: rtemail\@$RT::rtname Subject: This is a test of I18N ticket creation Content-Type: text/plain; charset="utf-8" @@ -436,7 +436,7 @@ is( $tick->Owner, $RT::Nobody->Id, 'owner of the new ticket is nobody' ); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action take"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost -Subject: [example.com \#$id] test +Subject: [$RT::rtname \#$id] test EOF close (MAIL); @@ -461,7 +461,7 @@ is( $tick->Owner, $RT::Nobody->Id, 'set owner back to nobody'); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action take-correspond"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost -Subject: [example.com \#$id] correspondence +Subject: [$RT::rtname \#$id] correspondence test EOF @@ -474,14 +474,14 @@ is( $tick->Id, $id, 'load correct ticket'); is( $tick->OwnerObj->EmailAddress, 'root@localhost', 'successfuly take ticket via email'); my $txns = $tick->Transactions; $txns->Limit( FIELD => 'Type', VALUE => 'Correspond'); -is( $txns->Last->Subject, "[example.com \#$id] correspondence", 'successfuly add correspond within take via email' ); +is( $txns->Last->Subject, "[$RT::rtname \#$id] correspondence", 'successfuly add correspond within take via email' ); # +1 because of auto open is( $tick->Transactions->Count, 6, 'no superfluous transactions'); ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue general --action resolve"), "Opened the mailgate - $@"); print MAIL <<EOF; From: root\@localhost -Subject: [example.com \#$id] test +Subject: [$RT::rtname \#$id] test EOF close (MAIL); diff --git a/rt/lib/t/regression/07acl.t b/rt/lib/t/regression/07acl.t index bb1ccdc0b..e30a59bef 100644 --- a/rt/lib/t/regression/07acl.t +++ b/rt/lib/t/regression/07acl.t @@ -49,9 +49,9 @@ is($agent->{'status'}, 200, "Fetched the page ok"); ok($agent->{'content'} =~ /Logout/i, "Found a logout link"); # Test for absence of Configure and Preferences tabs. -ok(!$agent->find_link( url => '/Admin/', +ok(!$agent->find_link( url => "$RT::WebPath/Admin/", text => 'Configuration'), "No config tab" ); -ok(!$agent->find_link( url => '/User/Prefs.html', +ok(!$agent->find_link( url => "$RT::WebPath/User/Prefs.html", text => 'Preferences'), "No prefs pane" ); # Now test for their presence, one at a time. Sleep for a bit after @@ -59,18 +59,18 @@ ok(!$agent->find_link( url => '/User/Prefs.html', $user_obj->PrincipalObj->GrantRight(Right => 'ShowConfigTab'); $agent->reload(); ok($agent->{'content'} =~ /Logout/i, "Reloaded page successfully"); -ok($agent->find_link( url => '/Admin/', +ok($agent->find_link( url => "$RT::WebPath/Admin/", text => 'Configuration'), "Found config tab" ); $user_obj->PrincipalObj->RevokeRight(Right => 'ShowConfigTab'); $user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf'); $agent->reload(); ok($agent->{'content'} =~ /Logout/i, "Reloaded page successfully"); -ok($agent->find_link( url => '/User/Prefs.html', +ok($agent->find_link( url => "$RT::WebPath/User/Prefs.html", text => 'Preferences'), "Found prefs pane" ); $user_obj->PrincipalObj->RevokeRight(Right => 'ModifySelf'); # Good. Now load the search page and test Load/Save Search. -$agent->follow_link( url => '/Search/Build.html', +$agent->follow_link( url => "$RT::WebPath/Search/Build.html", text => 'Tickets'); is($agent->{'status'}, 200, "Fetched search builder page"); ok($agent->{'content'} !~ /Load saved search/i, "No search loading box"); diff --git a/rt/lib/t/regression/07rights.t b/rt/lib/t/regression/07rights.t index 4764b49d4..d34627705 100644 --- a/rt/lib/t/regression/07rights.t +++ b/rt/lib/t/regression/07rights.t @@ -45,7 +45,7 @@ # # END BPS TAGGED BLOCK }}} -use Test::More tests => 14; +use Test::More tests => 26; use RT; RT::LoadConfig(); RT::Init(); @@ -108,3 +108,33 @@ is( $ticket->Owner, $user_id, "set correct owner" ); ok( $user->HasRight( Right => 'ReplyToTicket', Object => $ticket ), "user is owner and can reply to ticket" ); +# Testing of EquivObjects +$group = RT::Group->new( $RT::SystemUser ); +ok( $group->LoadQueueRoleGroup( Queue => $queue_id, Type=> 'AdminCc' ), "load queue AdminCc role group" ); +$ace = RT::ACE->new( $RT::SystemUser ); +my ($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ModifyTicket', Object => $queue ); +ok( $ace_id, "Granted queue AdminCc role group with ModifyTicket right: $msg" ); +ok( $group->PrincipalObj->HasRight( Right => 'ModifyTicket', Object => $queue ), "role group can modify ticket" ); +ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is not AdminCc and can't modify ticket" ); +($status, $msg) = $ticket->AddWatcher(Type => 'AdminCc', PrincipalId => $user->PrincipalId); +ok( $status, "successfuly added user as AdminCc"); +ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is AdminCc and can modify ticket" ); + +my $ticket2 = RT::Ticket->new($RT::SystemUser); +my ($ticket2_id) = $ticket2->Create( Queue => $queue_id, Subject => 'test2'); +ok( $ticket2_id, 'new ticket created' ); +ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2 ), "user is not AdminCc and can't modify ticket2" ); + +# now we can finally test EquivObjects +my $equiv = [ $ticket ]; +ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv ), + "user is not AdminCc but can modify ticket2 because of EquivObjects" ); + +# the first a third test below are the same, so they should both pass +my $equiv2 = []; +ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv2 ), + "user is not AdminCc and can't modify ticket2" ); +ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket, EquivObjects => $equiv2 ), + "user is AdminCc and can modify ticket" ); +ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv2 ), + "user is not AdminCc and can't modify ticket2 (same question different answer)" ); diff --git a/rt/lib/t/regression/09record_cf_api.t b/rt/lib/t/regression/09record_cf_api.t index 1428a28db..78f111bd8 100644 --- a/rt/lib/t/regression/09record_cf_api.t +++ b/rt/lib/t/regression/09record_cf_api.t @@ -2,7 +2,7 @@ use strict; use warnings FATAL => 'all'; -use Test::More tests => 131; +use Test::More tests => 133; use RT; RT::LoadConfig(); @@ -21,6 +21,9 @@ my $queue = RT::Queue->new( $RT::SystemUser ); $queue->Create( Name => 'RecordCustomFields-'.$$ ); ok ($queue->id, "Created the queue"); +my $queue2 = RT::Queue->new( $RT::SystemUser ); +$queue2->Create( Name => 'RecordCustomFields2' ); + my $ticket = RT::Ticket->new( $RT::SystemUser ); $ticket->Create( Queue => $queue->Id, @@ -51,6 +54,11 @@ $global_cf3->Create( Name => 'RecordCustomFields3-'.$$, Type => 'SelectSingle', $global_cf3->AddValue( Name => 'RecordCustomFieldValues31' ); $global_cf3->AddValue( Name => 'RecordCustomFieldValues32' ); +my $local_cf4 = RT::CustomField->new( $RT::SystemUser ); +$local_cf4->Create( Name => 'RecordCustomFields4', Type => 'SelectSingle', Queue => $queue2->id ); +$local_cf4->AddValue( Name => 'RecordCustomFieldValues41' ); +$local_cf4->AddValue( Name => 'RecordCustomFieldValues42' ); + my @custom_fields = ($local_cf1, $local_cf2, $global_cf3); @@ -178,6 +186,14 @@ $test_add_delete_cycle->( sub { return $_[0]->id } ); # lets test cycle via CF object reference $test_add_delete_cycle->( sub { return $_[0] } ); +$ticket->AddCustomFieldValue( Field => $local_cf2->id , Value => 'Baz' ); +$ticket->AddCustomFieldValue( Field => $global_cf3->id , Value => 'Baz' ); +# now if we ask for cf values on RecordCustomFields4 we should not get any +$cfvs = $ticket->CustomFieldValues( 'RecordCustomFields4' ); +is( $cfvs->Count, 0, "No custom field values for non-Queue cf" ); +is( $ticket->FirstCustomFieldValue( 'RecordCustomFields4' ), undef, "No first custom field value for non-Queue cf" ); + + #SKIP: { # skip "TODO: should we add CF values to objects via CF Name?", 48; # names are not unique diff --git a/rt/lib/t/regression/14linking.t b/rt/lib/t/regression/14linking.t new file mode 100644 index 000000000..6fdf61405 --- /dev/null +++ b/rt/lib/t/regression/14linking.t @@ -0,0 +1,143 @@ +use Test::More tests => '39'; +use_ok('RT'); +use_ok('RT::Ticket'); +use_ok('RT::ScripConditions'); +use_ok('RT::ScripActions'); +use_ok('RT::Template'); +use_ok('RT::Scrips'); +use_ok('RT::Scrip'); +RT::LoadConfig(); +RT::Init(); + +use File::Temp qw/tempfile/; +my ($fh, $filename) = tempfile( UNLINK => 1, SUFFIX => '.rt'); +my $link_scrips_orig = $RT::LinkTransactionsRun1Scrip; +$RT::LinkTransactionsRun1Scrip = 1; + +my $condition = RT::ScripCondition->new( $RT::SystemUser ); +$condition->Load('User Defined'); +ok($condition->id); +my $action = RT::ScripAction->new( $RT::SystemUser ); +$action->Load('User Defined'); +ok($action->id); +my $template = RT::Template->new( $RT::SystemUser ); +$template->Load('Blank'); +ok($template->id); + +my $q1 = RT::Queue->new($RT::SystemUser); +my ($id,$msg) = $q1->Create(Name => "LinkTest1.$$"); +ok ($id,$msg); +my $q2 = RT::Queue->new($RT::SystemUser); +($id,$msg) = $q2->Create(Name => "LinkTest2.$$"); +ok ($id,$msg); + +my $commit_code = <<END; +open(FILE, "<$filename"); +my \$data = <FILE>; +chomp \$data; +close FILE; +open(FILE, ">$filename"); +if (\$self->TransactionObj->Type eq 'AddLink') { + print FILE \$data+1, "\n"; +} +else { + print FILE \$data-1, "\n"; +} +close FILE; +1; +END + +my $Scrips = RT::Scrips->new( $RT::SystemUser ); +$Scrips->UnLimit; +while ( my $Scrip = $Scrips->Next ) { + $Scrip->Delete if $Scrip->Description =~ /Add or Delete Link \d+/; +} + + +my $scrip = RT::Scrip->new($RT::SystemUser); +($id,$msg) = $scrip->Create( Description => "Add or Delete Link $$", + ScripCondition => $condition->id, + ScripAction => $action->id, + Template => $template->id, + Stage => 'TransactionCreate', + Queue => 0, + CustomIsApplicableCode => '$self->TransactionObj->Type =~ /(Add|Delete)Link/;', + CustomPrepareCode => '1;', + CustomCommitCode => $commit_code, + ); +ok($id, "Scrip created"); + +my $u1 = RT::User->new($RT::SystemUser); +($id,$msg) =$u1->Create(Name => "LinkTestUser.$$"); + +ok ($id,$msg); + +($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'CreateTicket'); +ok ($id,$msg); +($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ModifyTicket'); +ok ($id,$msg); + +my $tid; + +my $creator = RT::CurrentUser->new($u1->id); + +my $ticket = RT::Ticket->new( $creator); +ok($ticket->isa('RT::Ticket')); +($id,$tid, $msg) = $ticket->Create(Subject => 'Link test 1', Queue => $q1->id); +ok ($id,$msg); + + +my $ticket2 = RT::Ticket->new($RT::SystemUser); +($id, $tid, $msg) = $ticket2->Create(Subject => 'Link test 2', Queue => $q2->id); +ok ($id, $msg); + +($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); +ok(!$id,$msg); +ok(link_count($filename) == 0, "scrips ok"); +($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'CreateTicket'); +ok ($id,$msg); +($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ModifyTicket'); +ok ($id,$msg); +($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); +ok($id,$msg); +ok(link_count($filename) == 1, "scrips ok"); +($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => -1); +ok(!$id,$msg); +ok(link_count($filename) == 1, "scrips ok"); + +my $transactions = $ticket2->Transactions; +$transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' ); +ok( $transactions->Count == 1, "Transaction found in other ticket" ); +ok( $transactions->First->Field eq 'ReferredToBy'); +ok( $transactions->First->NewValue eq $ticket->URI ); + +($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id); +ok($id,$msg); +ok(link_count($filename) == 0, "scrips ok"); +$transactions = $ticket2->Transactions; +$transactions->Limit( FIELD => 'Type', VALUE => 'DeleteLink' ); +ok( $transactions->Count == 1, "Transaction found in other ticket" ); +ok( $transactions->First->Field eq 'ReferredToBy'); +ok( $transactions->First->OldValue eq $ticket->URI ); + +$RT::LinkTransactionsRun1Scrip = 0; +($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); +ok($id,$msg); +ok(link_count($filename) == 2, "scrips ok"); +($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id); +ok($id,$msg); +ok(link_count($filename) == 0, "scrips ok"); + +# restore +$RT::LinkTransactionsRun1Scrip = $link_scrips_orig; + +sub link_count { + + my $file = shift; + open(FILE, "<$file"); + my $data = <FILE>; + chomp $data; + return $data + 0; + close FILE; + +} diff --git a/rt/lib/t/regression/22search_tix_by_txn.t b/rt/lib/t/regression/22search_tix_by_txn.t index 54dad9860..958273c5c 100644 --- a/rt/lib/t/regression/22search_tix_by_txn.t +++ b/rt/lib/t/regression/22search_tix_by_txn.t @@ -1,6 +1,6 @@ #use Test::More tests => 26; use Test::More qw/no_plan/; - +$ENV{'TZ'} = 'GMT'; use RT; RT::LoadConfig(); RT::Init(); diff --git a/rt/lib/t/regression/22search_tix_by_watcher.t b/rt/lib/t/regression/22search_tix_by_watcher.t new file mode 100644 index 000000000..dd87de989 --- /dev/null +++ b/rt/lib/t/regression/22search_tix_by_watcher.t @@ -0,0 +1,215 @@ +#!/usr/bin/perl -w +use strict; +use warnings; + +use Test::More qw/no_plan/; +use_ok('RT'); +RT::LoadConfig(); +RT::Init(); +use RT::Ticket; + +my $q = RT::Queue->new($RT::SystemUser); +my $queue = 'SearchTests-'.rand(200); +$q->Create(Name => $queue); + +my @data = ( + { Subject => '1', Requestor => 'bravo@example.com' }, + { Subject => '2', Cc => 'alpha@example.com' }, +); + +my $total = 0; + +sub add_tix_from_data { + my @res = (); + while (@data) { + my $t = RT::Ticket->new($RT::SystemUser); + my ( $id, undef $msg ) = $t->Create( + Queue => $q->id, + %{ shift(@data) }, + ); + ok( $id, "ticket created" ) or diag("error: $msg"); + push @res, $t; + $total++; + } + return @res; +} +add_tix_from_data(); + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue'"); + is($tix->Count, $total, "found $total tickets"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Requestor = 'bravo\@example.com'"); + is($tix->Count, 1, "found ticket(s)"); + is($tix->First->RequestorAddresses, 'bravo@example.com',"correct requestor"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Cc = 'alpha\@example.com'"); + is($tix->Count, 1, "found ticket(s)"); + is($tix->First->CcAddresses, 'alpha@example.com', "correct Cc"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND (Cc = 'alpha\@example.com' OR Requestor = 'bravo\@example.com')"); + is($tix->Count, 2, "found ticket(s)"); + my @mails; + while (my $t = $tix->Next) { + push @mails, $t->RequestorAddresses; + push @mails, $t->CcAddresses; + } + @mails = sort grep $_, @mails; + is_deeply(\@mails, ['alpha@example.com', 'bravo@example.com'], "correct addresses"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND (Cc = 'alpha\@example.com' AND Requestor = 'bravo\@example.com')"); + is($tix->Count, 0, "found ticket(s)"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Cc != 'alpha\@example.com'"); + is($tix->Count, 1, "found ticket(s)"); + is($tix->First->RequestorAddresses, 'bravo@example.com',"correct requestor"); +} + +@data = ( { Subject => '3' } ); +add_tix_from_data(); + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Cc != 'alpha\@example.com'"); + is($tix->Count, 2, "found ticket(s)"); + my @mails; + while (my $t = $tix->Next) { push @mails, ($t->CcAddresses||'') } + is( scalar(grep 'alpha@example.com' eq $_, @mails), 0, "no tickets with non required data"); +} + +{ + # has no requestor search + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Requestor IS NULL"); + is($tix->Count, 2, "found ticket(s)"); + my @mails; + while (my $t = $tix->Next) { push @mails, ($t->RequestorAddresses||'') } + is( scalar(grep $_, @mails), 0, "no tickets with non required data"); +} + +{ + # has at least one requestor search + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Requestor IS NOT NULL"); + is($tix->Count, 1, "found ticket(s)"); + my @mails; + while (my $t = $tix->Next) { push @mails, ($t->RequestorAddresses||'') } + is( scalar(grep !$_, @mails), 0, "no tickets with non required data"); +} + +@data = ( { Subject => '3', Requestor => 'charly@example.com' } ); +add_tix_from_data(); + +{ + # has no requestor search + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND + (Requestor = 'bravo\@example.com' OR Requestor = 'charly\@example.com')"); + is($tix->Count, 2, "found ticket(s)"); + my @mails; + while (my $t = $tix->Next) { push @mails, ($t->RequestorAddresses||'') } + is_deeply( [sort @mails], + ['bravo@example.com', 'charly@example.com'], + "requestor addresses are correct" + ); +} + +# owner is special watcher because reference is duplicated in two places, +# owner was an ENUM field now it's WATCHERFIELD, but should support old +# style ENUM searches for backward compatibility +my $nobody = RT::Nobody(); +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->id ."'"); + is($tix->Count, 4, "found ticket(s)"); +} +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->Name ."'"); + is($tix->Count, 4, "found ticket(s)"); +} +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->id ."'"); + is($tix->Count, 0, "found ticket(s)"); +} +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->Name ."'"); + is($tix->Count, 0, "found ticket(s)"); +} + +{ + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner.Name LIKE 'nob'"); + is($tix->Count, 4, "found ticket(s)"); +} + +{ + # create ticket and force type to not a 'ticket' value + # bug #6898@rt3.fsck.com + # and http://marc.theaimsgroup.com/?l=rt-devel&m=112662934627236&w=2 + @data = ( { Subject => 'not a ticket' } ); + my($t) = add_tix_from_data(); + $t->_Set( Field => 'Type', + Value => 'not a ticket', + CheckACL => 0, + RecordTransaction => 0, + ); + $total--; + + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND Owner = 'Nobody'"); + is($tix->Count, 4, "found ticket(s)"); +} + +{ + my $everyone = RT::Group->new( $RT::SystemUser ); + $everyone->LoadSystemInternalGroup('Everyone'); + ok($everyone->id, "loaded 'everyone' group"); + my($id, $msg) = $everyone->PrincipalObj->GrantRight( Right => 'OwnTicket', + Object => $q + ); + ok($id, "granted OwnTicket right to Everyone on '$queue'") or diag("error: $msg"); + + my $u = RT::User->new( $RT::SystemUser ); + $u->LoadByCols( EmailAddress => 'alpha@example.com' ); + ok($u->id, "loaded user"); + @data = ( { Subject => '4', Owner => $u->id } ); + my($t) = add_tix_from_data(); + is( $t->Owner, $u->id, "created ticket with custom owner" ); + my $u_alpha_id = $u->id; + + $u = RT::User->new( $RT::SystemUser ); + $u->LoadByCols( EmailAddress => 'bravo@example.com' ); + ok($u->id, "loaded user"); + @data = ( { Subject => '5', Owner => $u->id } ); + ($t) = add_tix_from_data(); + is( $t->Owner, $u->id, "created ticket with custom owner" ); + my $u_bravo_id = $u->id; + + my $tix = RT::Tickets->new($RT::SystemUser); + $tix->FromSQL("Queue = '$queue' AND + ( Owner = '$u_alpha_id' OR + Owner = '$u_bravo_id' )" + ); + is($tix->Count, 2, "found ticket(s)"); +} + +exit(0) diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index 49feba845..06c04dcf1 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -130,7 +130,14 @@ if ( $args{'action'} eq 'init' ) { unless ($RT::DatabaseType eq 'SQLite') { $dbh->disconnect; + + if ($RT::DatabaseType eq "Oracle") { + $RT::DatabasePassword = $RT::DatabasePassword; #Warning avidance + $dbh = DBI->connect( $Handle->DSN, ${RT::DatabaseUser}, ${RT::DatabasePassword} ) || die $DBI::errstr; + } else { + $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr; + } } print "Now populating database schema.\n"; insert_schema(); diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in index f79e4e5c2..ce1f44150 100644 --- a/rt/sbin/rt-test-dependencies.in +++ b/rt/sbin/rt-test-dependencies.in @@ -153,7 +153,7 @@ Digest::MD5 2.27 DBI 1.37 Test::Inline Class::ReturnValue 0.40 -DBIx::SearchBuilder 1.26 +DBIx::SearchBuilder 1.35 Text::Template File::Spec 0.8 HTML::Entities |