From: Ivan Kohler Date: Mon, 27 Apr 2015 09:59:21 +0000 (-0700) Subject: Merge branch 'master' of git.freeside.biz:/home/git/freeside X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=ad3bcb39580173f0ac1b6357cb49515d48af7ddf;hp=6be98dd167d1a77ff9f71c97c385bff65769f30d Merge branch 'master' of git.freeside.biz:/home/git/freeside --- diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm index 695b4cab3..b6f8aa4c6 100644 --- a/FS/FS/ClientAPI/MasonComponent.pm +++ b/FS/FS/ClientAPI/MasonComponent.pm @@ -27,6 +27,7 @@ my %allowed_comps = map { $_=>1 } qw( my %session_comps = map { $_=>1 } qw( /elements/location.html /elements/tr-amount_fee.html + /elements/select-part_pkg.html /edit/cust_main/first_pkg/select-part_pkg.html ); @@ -106,6 +107,26 @@ my %session_callbacks = ( }, + '/elements/select-part_pkg.html' => sub { + my( $custnum, $argsref ) = @_; + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return "unknown custnum $custnum"; + + my $pkgpart = $cust_main->agent->pkgpart_hashref; + + #false laziness w/ edit/cust_main/first_pkg.html + my @first_svc = ( 'svc_acct', 'svc_phone' ); + + my @part_pkg = + grep { $pkgpart->{ $_->pkgpart } + || ( $_->agentnum && $_->agentnum == $cust_main->agentnum ) + } + qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case? + + push @$argsref, 'part_pkg' => \@part_pkg; + ''; + }, + ); my $outbuf; diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 93f817de6..e2f859527 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -49,6 +49,8 @@ use FS::contact; use FS::cust_contact; use FS::cust_location; +use FS::ClientAPI::MyAccount::quotation; # just for code organization + $DEBUG = 0; $me = '[FS::ClientAPI::MyAccount]'; diff --git a/FS/FS/ClientAPI/MyAccount/quotation.pm b/FS/FS/ClientAPI/MyAccount/quotation.pm new file mode 100644 index 000000000..787a0997c --- /dev/null +++ b/FS/FS/ClientAPI/MyAccount/quotation.pm @@ -0,0 +1,220 @@ +package FS::ClientAPI::MyAccount::quotation; + +use strict; +use FS::Record qw(qsearch qsearchs); +use FS::quotation; +use FS::quotation_pkg; + +our $DEBUG = 1; + +sub _custoragent_session_custnum { + FS::ClientAPI::MyAccount::_custoragent_session_custnum(@_); +} + +sub _quotation { + # the currently active quotation + my $session = shift; + my $quotation; + if ( my $quotationnum = $session->{'quotationnum'} ) { + $quotation = FS::quotation->by_key($quotationnum); + } + if ( !$quotation ) { + # find the last quotation created through selfservice + $quotation = qsearchs( 'quotation', { + 'custnum' => $session->{'custnum'}, + 'usernum' => $FS::CurrentUser::CurrentUser->usernum, + 'disabled' => '', + }); + warn "found selfservice quotation #". $quotation->quotationnum."\n" + if $quotation and $DEBUG; + } + if ( !$quotation ) { + $quotation = FS::quotation->new({ + 'custnum' => $session->{'custnum'}, + 'usernum' => $FS::CurrentUser::CurrentUser->usernum, + '_date' => time, + }); + $quotation->insert; # what to do on error? call the police? + warn "started new selfservice quotation #". $quotation->quotationnum."\n" + if $quotation and $DEBUG; + } + $session->{'quotationnum'} = $quotation->quotationnum; + return $quotation; +} + +=item quotation_info { session } + +Returns a hashref describing the current quotation, containing: + +- "sections", an arrayref containing one section for each billing frequency. + Each one will have: + - "description" + - "subtotal" + - "detail_items", an arrayref of detail items, each with: + - "pkgnum", the reference number (actually the quotationpkgnum field) + - "description", the package name (or tax name) + - "quantity" + - "amount" + +=cut + +sub quotation_info { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session); + return { 'error' => "No current quotation for this customer" } if !$quotation; + warn "quotation_info #".$quotation->quotationnum + if $DEBUG; + + # code reuse ftw + my $null_escape = sub { @_ }; + my ($sections) = $quotation->_items_sections(escape => $null_escape); + foreach my $section (@$sections) { + $section->{'detail_items'} = + [ $quotation->_items_pkg('section' => $section, escape_function => $null_escape) ]; + } + return { 'error' => '', 'sections' => $sections } +} + +=item quotation_print { session, 'format' } + +Renders the quotation. 'format' can be either 'html' or 'pdf'; the resulting +hashref will contain 'document' => the HTML or PDF contents. + +=cut + +sub quotation_print { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session); + return { 'error' => "No current quotation for this customer" } if !$quotation; + warn "quotation_print #".$quotation->quotationnum + if $DEBUG; + + my $format = $p->{'format'} + or return { 'error' => "No rendering format specified" }; + + my $document; + if ($format eq 'html') { + $document = $quotation->print_html; + } elsif ($format eq 'pdf') { + $document = $quotation->print_pdf; + } + warn "$format, ".length($document)." bytes\n" + if $DEBUG; + return { 'error' => '', 'document' => $document }; +} + +=item quotation_add_pkg { session, 'pkgpart', 'quantity', [ location opts ] } + +Adds a package to the user's current quotation. Session info and 'pkgpart' are +required. 'quantity' defaults to 1. + +Location can be specified as 'locationnum' to use an existing location, or +'address1', 'address2', 'city', 'state', 'zip', 'country' to create a new one, +or it will default to the customer's service location. + +=cut + +sub quotation_add_pkg { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session); + my $cust_main = $quotation->cust_main; + + my $pkgpart = $p->{'pkgpart'}; + my $allowed_pkgpart = $cust_main->agent->pkgpart_hashref; + + my $part_pkg = FS::part_pkg->by_key($pkgpart); + + if (!$part_pkg or !$allowed_pkgpart->{$pkgpart}) { + warn "disallowed quotation_pkg pkgpart $pkgpart\n" + if $DEBUG; + return { 'error' => "unknown package $pkgpart" }; + } + + warn "creating quotation_pkg with pkgpart $pkgpart\n" + if $DEBUG; + my $quotation_pkg = FS::quotation_pkg->new({ + 'quotationnum' => $quotation->quotationnum, + 'pkgpart' => $p->{'pkgpart'}, + 'quantity' => $p->{'quantity'} || 1, + }); + if ( $p->{locationnum} > 0 ) { + $quotation_pkg->set('locationnum', $p->{locationnum}); + } elsif ( $p->{address1} ) { + my $location = FS::cust_location->find_or_insert( + 'custnum' => $cust_main->custnum, + map { $_ => $p->{$_} } + qw( address1 address2 city county state zip country ) + ); + $quotation_pkg->set('locationnum', $location->locationnum); + } + + my $error = $quotation_pkg->insert + || $quotation->estimate; + + { 'error' => $error, + 'quotationnum' => $quotation->quotationnum }; +} + +=item quotation_remove_pkg { session, 'pkgnum' } + +Removes the package from the user's current quotation. 'pkgnum' is required. + +=cut + +sub quotation_remove_pkg { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session); + my $quotationpkgnum = $p->{pkgnum}; + my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum); + if (!$quotation_pkg + or $quotation_pkg->quotationnum != $quotation->quotationnum) { + return { 'error' => "unknown quotation item $quotationpkgnum" }; + } + warn "removing quotation_pkg with pkgpart ".$quotation_pkg->pkgpart."\n" + if $DEBUG; + + my $error = $quotation_pkg->delete + || $quotation->estimate; + + { 'error' => $error, + 'quotationnum' => $quotation->quotationnum }; +} + +=item quotation_order + +Convert the current quotation to a package order. + +=cut + +sub quotation_order { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session); + + my $error = $quotation->order; + $quotation->set('disabled' => 'Y'); + $error ||= $quotation->replace; + + return { 'error' => $error }; +} + +1; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 952b19940..5f1b38c0f 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -52,6 +52,7 @@ our %typefix = ( 'login_info' => \%typefix_skin_info, 'invoice_logo' => { 'logo' => 'base64', }, 'login_banner_image' => { 'image' => 'base64', }, + 'quotation_print' => { 'document' => 'base64' }, ); sub AUTOLOAD { @@ -186,6 +187,12 @@ sub ss2clientapi { 'call_time' => 'PrepaidPhone/call_time', 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa', 'phonenum_balance' => 'PrepaidPhone/phonenum_balance', + + 'quotation_info' => 'MyAccount/quotation/quotation_info', + 'quotation_print' => 'MyAccount/quotation/quotation_print', + 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg', + 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg', + 'quotation_order' => 'MyAccount/quotation/quotation_order', }; } diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index a37e5a6ef..c5c03ff08 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2854,6 +2854,13 @@ and customer address. Include units.', }, { + 'key' => 'manual_process-single_invoice_amount', + 'section' => 'billing', + 'description' => 'When entering manual credit card and ACH payments, amount will not autofill if the customer has more than one open invoice', + 'type' => 'checkbox', + }, + + { 'key' => 'manual_process-pkgpart', 'section' => 'billing', 'description' => 'Package to add to each manual credit card and ACH payment entered by employees from the backend. Enabling this option may be in violation of your merchant agreement(s), so please check it(/them) carefully before enabling this option.', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 7f28e11f7..42122f700 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3595,6 +3595,7 @@ sub tables_hashref { 'columnlabel', 'varchar', 'NULL', $char_d, '', '', 'columnvalue', 'varchar', 'NULL', 512, '', '', 'columnflag', 'char', 'NULL', 1, '', '', + 'required', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'columnnum', 'unique' => [ [ 'svcpart', 'columnname' ] ], diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index d9d458809..930966a94 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -179,6 +179,12 @@ sub cutoff_day { if ( $self->option('sync_bill_date',1) ) { my $next_bill = $cust_pkg->cust_main->next_bill_date; if ( defined($next_bill) ) { + # careful here. if the prorate calculation is going to round to + # the nearest day, this needs to always return the same result + if ( $self->option('prorate_round_day', 1) ) { + my $hour = (localtime($next_bill))[2]; + $next_bill += 64800 if $hour >= 12; + } return (localtime($next_bill))[3]; } } diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index f56878acf..1da30cbb4 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -95,8 +95,12 @@ the part_svc_column table appropriately (see L). =item I__I - Default or fixed value for I in I. +=item I__I_label + =item I__I_flag - defines I__I action: 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. +=item I__I_required - I should always have a true value + =back If you want to add part_svc_column records for fields that do not exist as @@ -145,6 +149,7 @@ sub insert { foreach my $field ( grep { $_ ne 'svcnum' && ( defined( $self->getfield($svcdb.'__'.$_.'_flag') ) + || defined($self->getfield($svcdb.'__'.$_.'_required')) || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) } (fields($svcdb), @fields) ) { @@ -156,6 +161,7 @@ sub insert { my $flag = $self->getfield($svcdb.'__'.$field.'_flag'); my $label = $self->getfield($svcdb.'__'.$field.'_label'); + my $required = $self->getfield($svcdb.'__'.$field.'_required') ? 'Y' : ''; if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { if ( uc($flag) =~ /^([A-Z])$/ ) { @@ -170,6 +176,8 @@ sub insert { $part_svc_column->setfield('columnlabel', $label) if $label !~ /^\s*$/; + $part_svc_column->setfield('required', $required); + if ( $previous ) { $error = $part_svc_column->replace($previous); } else { @@ -279,6 +287,7 @@ sub replace { foreach my $field ( grep { $_ ne 'svcnum' && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') ) + || defined($new->getfield($svcdb.'__'.$_.'_required')) || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) } (fields($svcdb),@fields) ) { @@ -291,6 +300,7 @@ sub replace { my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); my $label = $new->getfield($svcdb.'__'.$field.'_label'); + my $required = $new->getfield($svcdb.'__'.$field.'_required') ? 'Y' : ''; if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { @@ -309,6 +319,8 @@ sub replace { $part_svc_column->setfield('columnlabel', $label) if $label !~ /^\s*$/; + $part_svc_column->setfield('required', $required); + if ( $previous ) { $error = $part_svc_column->replace($previous); } else { @@ -699,6 +711,8 @@ some components specified by "select-.*.html", and a bunch more... =item select_allow_empty - Used with select_table, adds an empty option +=item required - This field should always have a true value (do not use with type checkbox or disabled) + =back =cut @@ -773,7 +787,7 @@ sub process { and ref($param->{ $f }) ) { $param->{ $f } = join(',', @{ $param->{ $f } }); } - ( $f, $f.'_flag', $f.'_label' ); + ( $f, $f.'_flag', $f.'_label', $f.'_required' ); } @fields; diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index 38ce1fa80..75a2dfb1a 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -45,6 +45,8 @@ fields are currently supported: =item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class. For virtual fields, can also be 'X' for excluded. +=item required - column value expected to be true + =back =head1 METHODS @@ -91,6 +93,7 @@ sub check { || $self->ut_alpha('columnname') || $self->ut_textn('columnlabel') || $self->ut_anything('columnvalue') + || $self->ut_flag('required') ; return $error if $error; diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm index f2a96208f..45f35229f 100644 --- a/FS/FS/quotation.pm +++ b/FS/FS/quotation.pm @@ -695,22 +695,24 @@ sub estimate { # discounts if ( $cust_bill_pkg->get('discounts') ) { my $discount = $cust_bill_pkg->get('discounts')->[0]; - # discount records are generated as (setup, recur). - # well, not always, sometimes it's just (recur), but fixing this - # is horribly invasive. - my $qpd = $quotation_pkg_discount{$quotationpkgnum} - ||= qsearchs('quotation_pkg_discount', { - 'quotationpkgnum' => $quotationpkgnum - }); - - if (!$qpd) { #can't happen - warn "$me simulated bill returned a discount but no discount is in effect.\n"; - } - if ($discount and $qpd) { - if ( $i == 0 ) { - $qpd->set('setup_amount', $discount->amount); - } else { - $qpd->set('recur_amount', $discount->amount); + if ( $discount ) { + # discount records are generated as (setup, recur). + # well, not always, sometimes it's just (recur), but fixing this + # is horribly invasive. + my $qpd = $quotation_pkg_discount{$quotationpkgnum} + ||= qsearchs('quotation_pkg_discount', { + 'quotationpkgnum' => $quotationpkgnum + }); + + if (!$qpd) { #can't happen + warn "$me simulated bill returned a discount but no discount is in effect.\n"; + } + if ($discount and $qpd) { + if ( $i == 0 ) { + $qpd->set('setup_amount', $discount->amount); + } else { + $qpd->set('recur_amount', $discount->amount); + } } } } # end of discount stuff diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 8199ba183..b1f9d146f 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -152,13 +152,46 @@ sub cust_linked { Checks the validity of fields in this record. -At present, this does nothing but call FS::Record::check (which, in turn, -does nothing but run virtual field checks). +Only checks fields marked as required in table_info or +part_svc_column definition. Should be invoked by service-specific +check using SUPER. Invokes FS::Record::check using SUPER. =cut sub check { my $self = shift; + + ## Checking required fields + + # get fields marked as required in table_info + my $required = {}; + my $labels = {}; + my $tinfo = $self->can('table_info') ? $self->table_info : {}; + my $fields = $tinfo->{'fields'} || {}; + foreach my $field (keys %$fields) { + if (ref($fields->{$field}) && $fields->{$field}->{'required'}) { + $required->{$field} = 1; + $labels->{$field} = $fields->{$field}->{'label'}; + } + } + # add fields marked as required in database + foreach my $column ( + qsearch('part_svc_column',{ + 'svcpart' => $self->svcpart, + 'required' => 'Y' + }) + ) { + $required->{$column->columnname} = 1; + $labels->{$column->columnname} = $column->columnlabel; + } + # do the actual checking + foreach my $field (keys %$required) { + unless ($self->$field) { + my $name = $labels->{$field} || $field; + return "Field $name is required\n" + } + } + $self->SUPER::check; } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 452f250d8..790ac3468 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -283,6 +283,7 @@ sub table_info { disable_default => 1, disable_fixed => 1, disable_select => 1, + required => 1, }, 'password_selfchange' => { label => 'Password modification', type => 'checkbox', @@ -310,7 +311,9 @@ sub table_info { type => 'text', disable_inventory => 1, }, - '_password' => 'Password', + '_password' => { label => 'Password', + required => 1 + }, 'gid' => { label => 'GID', def_info => 'when blank, defaults to UID', @@ -333,6 +336,7 @@ sub table_info { select_key => 'svcnum', select_label => 'domain', disable_inventory => 1, + required => 1, }, 'pbxsvc' => { label => 'PBX', type => 'select-svc_pbx.html', diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index b01d67310..78556cf8b 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -134,7 +134,10 @@ sub table_info { 'display_weight' => 20, 'cancel_weight' => 60, 'fields' => { - 'domain' => 'Domain', + 'domain' => { + label => 'Domain', + required => 1, + }, 'parent_svcnum' => { label => 'Parent domain / Communigate administrator domain', type => 'select', diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 12d56bb6f..765d6111b 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -115,6 +115,13 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'start_thirdparty' => 'MyAccount/start_thirdparty', 'finish_thirdparty' => 'MyAccount/finish_thirdparty', + + 'quotation_info' => 'MyAccount/quotation/quotation_info', + 'quotation_print' => 'MyAccount/quotation/quotation_print', + 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg', + 'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg', + 'quotation_order' => 'MyAccount/quotation/quotation_order', + ); @EXPORT_OK = ( keys(%autoload), diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 0d3685355..ec5f321dd 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -61,6 +61,8 @@ function part_export_areyousure(href) { Modifier + Required + % my $conf = FS::Conf->new; % foreach my $part_svc ( @part_svc ) { @@ -78,6 +80,9 @@ function part_export_areyousure(href) { % $col->columnflag || ( $col->columnlabel !~ /^\S*$/ % && $col->columnlabel ne $def->{'label'} % ) +% || ( $col->required +% && !$def->{'required'} +% ) % ) % } % @dfields ; @@ -150,7 +155,7 @@ function part_export_areyousure(href) { % unless ( @fields ) { -% for ( 1..4 ) { +% for ( 1..5 ) { % } % } @@ -170,7 +175,6 @@ function part_export_areyousure(href) { <% $field %> <% $label %> <% $flag{$flag} %> - % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue); % if ( $flag =~ /^[MAH]$/ ) { @@ -189,6 +193,11 @@ function part_export_areyousure(href) { % } + +% if ($part_svc_column->required) { + Yes +% } + % $n1=""; % } #foreach $field % if ( $part_svc->restrict_edit_password ) { diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 2bb4f5e41..a6ccaf867 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -77,6 +77,7 @@ that field. Field Label Modifier + Required? % $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now % my $i = 0; @@ -210,9 +211,17 @@ that field. &> % } + +% if (!$def->{'type'} || !(grep {$_ eq $def->{'type'}} ('checkbox','disabled'))) { + required || $def->{'required'}) ? 'CHECKED' : '' %> + <% $def->{'required'} ? 'DISABLED' : '' %> + > +% } + - + % if ( $def->{def_info} ) { (<% $def->{def_info} %>) @@ -228,7 +237,7 @@ that field. <% emt('Require "Provision" access right to edit password') %> - + restrict_edit_password ? 'CHECKED' : '' %>> @@ -244,7 +253,7 @@ that field. <% emt('This service has an attached router') %> - + has_router ? 'CHECKED' : '' %>> diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 47b020c5a..7a47f1550 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -101,6 +101,15 @@ function flag_changed(obj) { } } } + var required = document.getElementById(layer + '__' + field + '_required'); + if (required && !required.disabledinit) { + if (newflag == "F") { + required.checked = false; + required.disabled = true; + } else { + required.disabled = false; + } + } } window.onload = function() { @@ -111,6 +120,17 @@ window.onload = function() { obj.setAttribute('should_be_multiple', true); } } + var inputs = document.getElementsByTagName('INPUT'); + for(i = 0; i < inputs.length; i++) { + var obj = inputs[i]; + if (obj.type == 'checkbox') { + if ( obj.name.match(/_required$/) ) { + if ( obj.disabled ) { + obj.disabledinit = 1; + } + } + } + } for(i = 0; i < selects.length; i++) { var obj = selects[i]; if ( obj.name.match(/_flag$/) ) { diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 90b03c7e8..b83ad7166 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -273,7 +273,9 @@ my @states = sort { $a cmp $b } keys %states; my $amount = ''; if ( $balance > 0 ) { - $amount = $balance; + $amount = $balance + unless $conf->exists('manual_process-single_invoice_amount') + && ($cust_main->open_cust_bill != 1); } my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32; diff --git a/ng_selfservice/images/cross.png b/ng_selfservice/images/cross.png new file mode 100644 index 000000000..1514d51a3 Binary files /dev/null and b/ng_selfservice/images/cross.png differ diff --git a/ng_selfservice/quotation.php b/ng_selfservice/quotation.php new file mode 100644 index 000000000..cf455431b --- /dev/null +++ b/ng_selfservice/quotation.php @@ -0,0 +1,130 @@ + + + + +quotation_info(array( + 'session_id' => $_COOKIE['session_id'], +)); + +$can_order = 0; + +if ( isset($quotation['sections']) and count($quotation['sections']) > 0 ) { + $can_order = 1; + # there are other ways this could be formatted, yes. + # if you want the HTML-formatted quotation, use quotation_print(). + print( + ''. + '

Order summary

'. + "\n" + ); + foreach ( $quotation['sections'] as $section ) { + print( + ''. + ''. + ''. + ''. + "\n" + ); + $row = 0; + foreach ( $section['detail_items'] as $detail ) { + print( + ''. + ''. + ''. + ''. + ''. "\n" + ); + $row = 1 - $row; + } + print( + ''. + ''. + ''. + ''. + ''. + '
'. htmlspecialchars($section['description']).'
' + ); + if ( $detail['pkgnum'] ) { + print( + ''. + '' + ); + } + print( + ''. htmlspecialchars($detail['description']). ''. $detail['amount']. '
Total'. $section['subtotal']. '
'. + "\n" + ); + } # foreach $section +} + +$pkgselect = $freeside->mason_comp( array( + 'session_id' => $_COOKIE['session_id'], + 'comp' => '/elements/select-part_pkg.html', + 'args' => array( 'onchange' , 'enable_order_pkg()', + 'empty_label' , 'Select package', + 'form_name' , 'AddPkgForm', + ), +)); +if ( isset($pkgselect['error']) && $pkgselect['error'] ) { + $error = $pkgselect['error']; + header('Location:index.php?error='. urlencode($pkgselect)); + die(); +} + +?> + + +
+ + +
+ +> +
+ + +
+> + + +
+ + + diff --git a/ng_selfservice/quotation_add_pkg.php b/ng_selfservice/quotation_add_pkg.php new file mode 100644 index 000000000..1e7e71fa9 --- /dev/null +++ b/ng_selfservice/quotation_add_pkg.php @@ -0,0 +1,31 @@ + $_COOKIE['session_id'], + 'pkgpart' => $_REQUEST['pkgpart'], + ); + + $results = $freeside->quotation_add_pkg($args); + + } + + if ( isset($results['error']) && $results['error'] ) { + $dest .= '?error=' . $results['error'] . ';pkgpart=' . $_REQUEST['pkgpart']; + } +} + +header("Location:$dest"); + +?> + diff --git a/ng_selfservice/quotation_order.php b/ng_selfservice/quotation_order.php new file mode 100644 index 000000000..d35eacbb2 --- /dev/null +++ b/ng_selfservice/quotation_order.php @@ -0,0 +1,15 @@ + $_COOKIE['session_id'] ); + +$results = $freeside->quotation_order($args); + +if ( isset($results['error']) && $results['error'] ) { + $dest = 'quotation.php?error=' . $results['error']; +} + +header("Location:$dest"); + +?> diff --git a/ng_selfservice/quotation_print.php b/ng_selfservice/quotation_print.php new file mode 100644 index 000000000..9676405d1 --- /dev/null +++ b/ng_selfservice/quotation_print.php @@ -0,0 +1,17 @@ + $_COOKIE['session_id'], + 'format' => 'pdf' +); + +$results = $freeside->quotation_print($args); +if ( isset($results['document']) ) { + header('Content-Type: application/pdf'); + header('Content-Disposition: filename=quotation.pdf'); + print($results['document']->scalar); +} else { + header("Location: quotation.php?error=" . $results['error']); +} + +?> diff --git a/ng_selfservice/quotation_remove_pkg.php b/ng_selfservice/quotation_remove_pkg.php new file mode 100644 index 000000000..07548c7f9 --- /dev/null +++ b/ng_selfservice/quotation_remove_pkg.php @@ -0,0 +1,31 @@ + $_COOKIE['session_id'], + 'pkgnum' => $_REQUEST['pkgnum'], + ); + + $results = $freeside->quotation_remove_pkg($args); + + } + + if ( isset($results['error']) && $results['error'] ) { + $dest .= '?error=' . $results['error']; + } + +} + +header("Location:$dest"); + +?>