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
);
},
+ '/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;
use FS::cust_contact;
use FS::cust_location;
+use FS::ClientAPI::MyAccount::quotation; # just for code organization
+
$DEBUG = 0;
$me = '[FS::ClientAPI::MyAccount]';
--- /dev/null
+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;
'login_info' => \%typefix_skin_info,
'invoice_logo' => { 'logo' => 'base64', },
'login_banner_image' => { 'image' => 'base64', },
+ 'quotation_print' => { 'document' => 'base64' },
);
sub AUTOLOAD {
'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',
};
}
},
{
+ '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.',
'columnlabel', 'varchar', 'NULL', $char_d, '', '',
'columnvalue', 'varchar', 'NULL', 512, '', '',
'columnflag', 'char', 'NULL', 1, '', '',
+ 'required', 'char', 'NULL', 1, '', '',
],
'primary_key' => 'columnnum',
'unique' => [ [ 'svcpart', 'columnname' ] ],
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];
}
}
=item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
+=item I<svcdb>__I<field>_label
+
=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> 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<svcdb>__I<field>_required - I<field> should always have a true value
+
=back
If you want to add part_svc_column records for fields that do not exist as
foreach my $field (
grep { $_ ne 'svcnum'
&& ( defined( $self->getfield($svcdb.'__'.$_.'_flag') )
+ || defined($self->getfield($svcdb.'__'.$_.'_required'))
|| $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
} (fields($svcdb), @fields)
) {
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])$/ ) {
$part_svc_column->setfield('columnlabel', $label)
if $label !~ /^\s*$/;
+ $part_svc_column->setfield('required', $required);
+
if ( $previous ) {
$error = $part_svc_column->replace($previous);
} else {
foreach my $field (
grep { $_ ne 'svcnum'
&& ( defined( $new->getfield($svcdb.'__'.$_.'_flag') )
+ || defined($new->getfield($svcdb.'__'.$_.'_required'))
|| $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
} (fields($svcdb),@fields)
) {
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*$/ ) {
$part_svc_column->setfield('columnlabel', $label)
if $label !~ /^\s*$/;
+ $part_svc_column->setfield('required', $required);
+
if ( $previous ) {
$error = $part_svc_column->replace($previous);
} else {
=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
and ref($param->{ $f }) ) {
$param->{ $f } = join(',', @{ $param->{ $f } });
}
- ( $f, $f.'_flag', $f.'_label' );
+ ( $f, $f.'_flag', $f.'_label', $f.'_required' );
}
@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, `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
|| $self->ut_alpha('columnname')
|| $self->ut_textn('columnlabel')
|| $self->ut_anything('columnvalue')
+ || $self->ut_flag('required')
;
return $error if $error;
# 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
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;
}
disable_default => 1,
disable_fixed => 1,
disable_select => 1,
+ required => 1,
},
'password_selfchange' => { label => 'Password modification',
type => 'checkbox',
type => 'text',
disable_inventory => 1,
},
- '_password' => 'Password',
+ '_password' => { label => 'Password',
+ required => 1
+ },
'gid' => {
label => 'GID',
def_info => 'when blank, defaults to UID',
select_key => 'svcnum',
select_label => 'domain',
disable_inventory => 1,
+ required => 1,
},
'pbxsvc' => { label => 'PBX',
type => 'select-svc_pbx.html',
'display_weight' => 20,
'cancel_weight' => 60,
'fields' => {
- 'domain' => 'Domain',
+ 'domain' => {
+ label => 'Domain',
+ required => 1,
+ },
'parent_svcnum' => {
label => 'Parent domain / Communigate administrator domain',
type => 'select',
'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),
<TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" STYLE="font-size: smaller;">Required</TH>
+
</TR>
% my $conf = FS::Conf->new;
% foreach my $part_svc ( @part_svc ) {
% $col->columnflag || ( $col->columnlabel !~ /^\S*$/
% && $col->columnlabel ne $def->{'label'}
% )
+% || ( $col->required
+% && !$def->{'required'}
+% )
% )
% }
% @dfields ;
</TD>
% unless ( @fields ) {
-% for ( 1..4 ) {
+% for ( 1..5 ) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
% }
% }
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD>
-
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
% my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
% if ( $flag =~ /^[MAH]$/ ) {
% }
</TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ($part_svc_column->required) {
+ Yes
+% }
+ </TD>
% $n1="</TR><TR>";
% } #foreach $field
% if ( $part_svc->restrict_edit_password ) {
<TH BGCOLOR="#cccccc">Field</TH>
<TH BGCOLOR="#cccccc">Label</TH>
<TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>
+ <TH BGCOLOR="#cccccc">Required?</TH>
</TR>
% $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now
% my $i = 0;
&>
% }
</TD>
+ <TD>
+% if (!$def->{'type'} || !(grep {$_ eq $def->{'type'}} ('checkbox','disabled'))) {
+ <INPUT ID="<% $name.'_required' %>" TYPE="checkbox" NAME="<% $svcdb %>__<% $field %>_required" VALUE="Y"
+ <% ($part_svc_column->required || $def->{'required'}) ? 'CHECKED' : '' %>
+ <% $def->{'required'} ? 'DISABLED' : '' %>
+ >
+% }
+ </TD>
</TR>
<TR CLASS="row<%$i%>">
- <TD COLSPAN=2 CLASS="def_info">
+ <TD COLSPAN=3 CLASS="def_info">
% if ( $def->{def_info} ) {
(<% $def->{def_info} %>)
</TD>
<TD COLSPAN=3 ALIGN="right">
<% emt('Require "Provision" access right to edit password') %>
</TD>
- <TD>
+ <TD COLSPAN=2>
<INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \
<% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>>
</TD>
<TD COLSPAN=3 ALIGN="right">
<% emt('This service has an attached router') %>
</TD>
- <TD>
+ <TD COLSPAN=2>
<INPUT TYPE="checkbox" NAME="has_router" VALUE="Y" \
<% $part_svc->has_router ? 'CHECKED' : '' %>>
</TD>
}
}
}
+ 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() {
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$/) ) {
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;
--- /dev/null
+<STYLE>
+td.amount {
+ text-align: right;
+}
+td.amount:before {
+ content: "$";
+}
+tr.total * {
+ background-color: #ddf;
+ font-weight: bold;
+}
+table.section {
+ width: 100%;
+ border-collapse: collapse;
+}
+table.section td {
+ font-size: small;
+ padding: 1ex 1ex;
+}
+table.section th {
+ text-align: left;
+ padding: 1ex;
+}
+.row0 td {
+ background-color: #eee;
+}
+.row1 td {
+ background-color: #fff;
+}
+</STYLE>
+
+<? $title ='Plan a new service order'; include('elements/header.php'); ?>
+<? $current_menu = 'services_new.php'; include('elements/menu.php'); ?>
+<?
+
+$quotation = $freeside->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(
+ '<INPUT STYLE="float: right" TYPE="button" onclick="window.location.href=\'quotation_print.php\'" value="Download a quotation" />'.
+ '<H3>Order summary</H3>'.
+ "\n"
+ );
+ foreach ( $quotation['sections'] as $section ) {
+ print(
+ '<TABLE CLASS="section">'.
+ '<TR>'.
+ '<TH COLSPAN=4>'. htmlspecialchars($section['description']).'</TH>'.
+ '</TR>'.
+ "\n"
+ );
+ $row = 0;
+ foreach ( $section['detail_items'] as $detail ) {
+ print(
+ '<TR CLASS="row' . $row . '">'.
+ '<TD>'
+ );
+ if ( $detail['pkgnum'] ) {
+ print(
+ '<A HREF="quotation_remove_pkg.php?pkgnum=' .
+ $detail['pkgnum'] . '">'.
+ '<IMG SRC="images/cross.png" /></A>'
+ );
+ }
+ print(
+ '</TD>'.
+ '<TD>'. htmlspecialchars($detail['description']). '</TD>'.
+ '<TD CLASS="amount">'. $detail['amount']. '</TD>'.
+ '</TR>'. "\n"
+ );
+ $row = 1 - $row;
+ }
+ print(
+ '<TR CLASS="total">'.
+ '<TD></TD>'.
+ '<TD>Total</TD>'.
+ '<TD CLASS="amount">'. $section['subtotal']. '</TD>'.
+ '</TR>'.
+ '</TABLE>'.
+ "\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();
+}
+
+?>
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+ document.AddPkgForm.submit.disabled =
+ (document.AddPkgForm.pkgpart.value == '');
+}
+</SCRIPT>
+
+<DIV STYLE="border-top: 1px solid; padding: 1ex">
+<? $error = $_REQUEST['error']; include('elements/error.php'); ?>
+
+<FORM NAME="AddPkgForm" ACTION="quotation_add_pkg.php" METHOD=POST>
+<? echo $pkgselect['output']; ?>
+<INPUT NAME="submit" TYPE="submit" VALUE="Add package" <? if ( ! isset($_REQUEST['pkgpart']) ) { echo 'DISABLED'; } ?>>
+</FORM>
+
+<? if ( $can_order ) { ?>
+<FORM NAME="OrderQuoteForm" ACTION="quotation_order.php" METHOD=POST>
+<INPUT TYPE="submit" VALUE="Confirm this order" <? if ( !$can_order ) { echo 'DISABLED'; } ?>>
+<? } ?>
+
+</DIV>
+
+<? include('elements/menu_footer.php'); ?>
+<? include('elements/footer.php'); ?>
--- /dev/null
+<? require('elements/session.php');
+
+$dest = 'quotation.php';
+
+if ( isset($_REQUEST['pkgpart']) ) {
+
+ $results = array();
+
+ $params = array( 'custnum', 'pkgpart' );
+
+ $matches = array();
+ if ( preg_match( '/^(\d+)$/', $_REQUEST['pkgpart'] ) ) {
+
+ $args = array(
+ 'session_id' => $_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");
+
+?>
+
--- /dev/null
+<? require('elements/session.php');
+
+$dest = 'services.php';
+
+$args = array( 'session_id' => $_COOKIE['session_id'] );
+
+$results = $freeside->quotation_order($args);
+
+if ( isset($results['error']) && $results['error'] ) {
+ $dest = 'quotation.php?error=' . $results['error'];
+}
+
+header("Location:$dest");
+
+?>
--- /dev/null
+<? require('elements/session.php');
+
+$args = array(
+ 'session_id' => $_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']);
+}
+
+?>
--- /dev/null
+<? require('elements/session.php');
+
+$dest = 'quotation.php';
+
+if ( isset($_REQUEST['pkgnum']) ) {
+
+ $results = array();
+
+ $params = array( 'custnum', 'pkgnum' );
+
+ $matches = array();
+ if ( preg_match( '/^(\d+)$/', $_REQUEST['pkgnum'] ) ) {
+
+ $args = array(
+ 'session_id' => $_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");
+
+?>