summaryrefslogtreecommitdiff
path: root/FS/FS
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2015-04-25 15:02:15 -0700
committerMark Wells <mark@freeside.biz>2015-04-25 15:02:23 -0700
commitc110da0da864245e47cae019b8a347367cc6430c (patch)
treee406be890446095b8cd59a5b5c827c62dd063fff /FS/FS
parent4fda726fa9f8e709c68ec823edc5ae702723281c (diff)
selfservice quotations, #33852
Diffstat (limited to 'FS/FS')
-rw-r--r--FS/FS/ClientAPI/MasonComponent.pm21
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm2
-rw-r--r--FS/FS/ClientAPI/MyAccount/quotation.pm218
-rw-r--r--FS/FS/ClientAPI_XMLRPC.pm7
-rw-r--r--FS/FS/quotation.pm34
5 files changed, 266 insertions, 16 deletions
diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm
index 695b4ca..b6f8aa4 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 93f817d..e2f8595 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 0000000..ce2debd
--- /dev/null
+++ b/FS/FS/ClientAPI/MyAccount/quotation.pm
@@ -0,0 +1,218 @@
+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;
+
+ return { 'error' => $error };
+}
+
+1;
diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm
index 952b199..5f1b38c 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/quotation.pm b/FS/FS/quotation.pm
index f2a9620..45f3522 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