From c8cfa4829c828793e26082d0ce1dfb9733bbcef0 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Tue, 28 Apr 2015 11:31:34 -0700 Subject: [PATCH] explicitly pass quotationnum as an API param and other fixes, #33852 --- FS/FS/ClientAPI/MyAccount/quotation.pm | 159 ++++++++++++++++++++----- FS/FS/ClientAPI_XMLRPC.pm | 3 + fs_selfservice/FS-SelfService/SelfService.pm | 169 +++++++++++++++++++++++++++ 3 files changed, 299 insertions(+), 32 deletions(-) diff --git a/FS/FS/ClientAPI/MyAccount/quotation.pm b/FS/FS/ClientAPI/MyAccount/quotation.pm index 787a0997c..df2b37ed6 100644 --- a/FS/FS/ClientAPI/MyAccount/quotation.pm +++ b/FS/FS/ClientAPI/MyAccount/quotation.pm @@ -11,40 +11,123 @@ sub _custoragent_session_custnum { FS::ClientAPI::MyAccount::_custoragent_session_custnum(@_); } +# _quotation(session, quotationnum) +# returns that quotation, or '' if it doesn't exist and belong to this +# customer + sub _quotation { - # the currently active quotation my $session = shift; + my $quotationnum = shift; my $quotation; - if ( my $quotationnum = $session->{'quotationnum'} ) { - $quotation = FS::quotation->by_key($quotationnum); - } - if ( !$quotation ) { - # find the last quotation created through selfservice + + if ( $quotationnum =~ /^(\d+)$/ ) { $quotation = qsearchs( 'quotation', { - 'custnum' => $session->{'custnum'}, - 'usernum' => $FS::CurrentUser::CurrentUser->usernum, - 'disabled' => '', + 'custnum' => $session->{'custnum'}, + 'usernum' => $FS::CurrentUser::CurrentUser->usernum, + 'disabled' => '', + 'quotationnum' => $1, }); 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" + + return $quotation; + } + ''; +} + +=item list_quotations { session } + +Returns a hashref listing this customer's active self-service quotations. +Contents are: + +- 'quotations', an arrayref containing an element for each quotation. + - quotationnum, the primary key + - _date, the date it was started + - num_pkgs, the number of packages + - total_setup, the sum of setup fees + - total_recur, the sum of recurring charges + +=cut + +sub list_quotations { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my @quotations = qsearch('quotation', { + 'custnum' => $session->{'custnum'}, + 'usernum' => $FS::CurrentUser::CurrentUser->usernum, + 'disabled' => '', + }); + my @q; + foreach my $quotation (@quotations) { + warn "found selfservice quotation #". $quotation->quotationnum."\n" if $quotation and $DEBUG; - } - $session->{'quotationnum'} = $quotation->quotationnum; - return $quotation; + push @q, { 'quotationnum' => $quotation->quotationnum, + '_date' => $quotation->_date, + 'num_pkgs' => scalar($quotation->quotation_pkg), + 'total_setup' => $quotation->total_setup, + 'total_recur' => $quotation->total_recur, + }; + } + return { 'quotations' => \@q, 'error' => '' }; } -=item quotation_info { session } +=item quotation_new { session } + +Creates a quotation and returns its quotationnum. + +=cut -Returns a hashref describing the current quotation, containing: +sub quotation_new { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = FS::quotation->new({ + 'custnum' => $session->{'custnum'}, + 'usernum' => $FS::CurrentUser::CurrentUser->usernum, + '_date' => time, + }); + my $error = $quotation->insert; + if ( $error ) { + warn "failed to create selfservice quotation for custnum #" . + $session->{custnum} . "\n"; + return { 'error' => $error }; + } else { + warn "started new selfservice quotation #". $quotation->quotationnum."\n" + if $DEBUG; + return { 'error' => $error, 'quotationnum' => $quotation->quotationnum }; + } +} + +=item quotation_delete { session, quotationnum } + +Disables (doesn't actually delete) the specified quotationnum. + +=cut + +sub quotation_delete { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; + warn "quotation_delete #".$quotation->quotationnum + if $DEBUG; + + $quotation->set('disabled' => 'Y'); + my $error = $quotation->replace; + return { 'error' => $error }; +} + +=item quotation_info { session, quotationnum } + +Returns a hashref describing the specified quotation, containing: - "sections", an arrayref containing one section for each billing frequency. Each one will have: @@ -55,6 +138,9 @@ Returns a hashref describing the current quotation, containing: - "description", the package name (or tax name) - "quantity" - "amount" +- "num_pkgs", the number of packages in the quotation +- "total_setup", the sum of setup/one-time charges and their taxes +- "total_recur", the sum of all recurring charges and their taxes =cut @@ -64,8 +150,8 @@ sub quotation_info { 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; + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; warn "quotation_info #".$quotation->quotationnum if $DEBUG; @@ -76,7 +162,12 @@ sub quotation_info { $section->{'detail_items'} = [ $quotation->_items_pkg('section' => $section, escape_function => $null_escape) ]; } - return { 'error' => '', 'sections' => $sections } + return { 'error' => '', + 'sections' => $sections, + 'num_pkgs' => scalar($quotation->quotation_pkg), + 'total_setup' => $quotation->total_setup, + 'total_recur' => $quotation->total_recur, + }; } =item quotation_print { session, 'format' } @@ -92,8 +183,8 @@ sub quotation_print { 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; + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; warn "quotation_print #".$quotation->quotationnum if $DEBUG; @@ -128,7 +219,8 @@ sub quotation_add_pkg { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; - my $quotation = _quotation($session); + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; my $cust_main = $quotation->cust_main; my $pkgpart = $p->{'pkgpart'}; @@ -161,7 +253,8 @@ sub quotation_add_pkg { } my $error = $quotation_pkg->insert - || $quotation->estimate; + || $quotation->estimate + || ''; { 'error' => $error, 'quotationnum' => $quotation->quotationnum }; @@ -179,7 +272,8 @@ sub quotation_remove_pkg { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; - my $quotation = _quotation($session); + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; my $quotationpkgnum = $p->{pkgnum}; my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum); if (!$quotation_pkg @@ -208,7 +302,8 @@ sub quotation_order { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; - my $quotation = _quotation($session); + my $quotation = _quotation($session, $p->{quotationnum}) + or return { 'error' => "Quotation not found" }; my $error = $quotation->order; $quotation->set('disabled' => 'Y'); diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 5f1b38c0f..757f76490 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -188,6 +188,9 @@ sub ss2clientapi { 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa', 'phonenum_balance' => 'PrepaidPhone/phonenum_balance', + 'list_quotations' => 'MyAccount/quotation/list_quotations', + 'quotation_new' => 'MyAccount/quotation/quotation_new', + 'quotation_delete' => 'MyAccount/quotation/quotation_delete', 'quotation_info' => 'MyAccount/quotation/quotation_info', 'quotation_print' => 'MyAccount/quotation/quotation_print', 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg', diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 765d6111b..939596ecb 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -116,6 +116,9 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'start_thirdparty' => 'MyAccount/start_thirdparty', 'finish_thirdparty' => 'MyAccount/finish_thirdparty', + 'list_quotations' => 'MyAccount/quotation/list_quotations', + 'quotation_new' => 'MyAccount/quotation/quotation_new', + 'quotation_delete' => 'MyAccount/quotation/quotation_delete', 'quotation_info' => 'MyAccount/quotation/quotation_info', 'quotation_print' => 'MyAccount/quotation/quotation_print', 'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg', @@ -283,6 +286,31 @@ FS::SelfService - Freeside self-service API } ); + #quoting a package, then ordering after confirmation + + my $rv = quotation_new({ 'session_id' => $session_id }); + my $qnum = $rv->{quotationnum}; + # add packages to the quotation + $rv = quotation_add_pkg({ 'session_id' => $session_id, + 'quotationnum' => $qnum, + 'pkgpart' => $pkgpart, + 'quantity' => $quantity, # defaults to 1 + }); + # repeat until all packages are added + # view the pricing information + $rv = quotation_info({ 'session_id' => $session_id, + 'quotationnum' => $qnum, + }); + print "Total setup charges: ".$rv->{total_setup}."\n". + "Total recurring charges: ".$rv->{total_recur}."\n"; + # quotation_info also provides a detailed breakdown of charges, in + # $rv->{sections}. + + # ask customer for confirmation, then: + $rv = quotation_order({ 'session_id' => $session_id, + 'quotationnum' => $qnum, + }); + #!!! cancel_pkg example # signup functionality @@ -1283,6 +1311,147 @@ svcpart or service definition to provision =back +=head2 "MY ACCOUNT" QUOTATION FUNCTIONS + +All of these functions require the user to be logged in, and the 'session_id' +key to be included in the argument hashref.` + +=over 4 + +=item list_quotations HASHREF + +Returns a hashref listing this customer's active self-service quotations. +Contents are: + +- 'quotations', an arrayref containing an element for each quotation. + - quotationnum, the primary key + - _date, the date it was started + - num_pkgs, the number of packages + - total_setup, the sum of setup fees + - total_recur, the sum of recurring charges + +=item quotation_new HASHREF + +Creates an empty quotation and returns a hashref containing 'quotationnum', +the primary key of the new quotation. + +=item quotation_delete HASHREF + +Disables (does not really delete) a quotation. Takes the following arguments: + +=over 4 + +=item session_id + +=item quotationnum - the quotation to delete + +=back + +Returns 'error' => a string, which will be empty on success. + +=item quotation_info HASHREF + +Returns total and detailed pricing information on a quotation. + +Takes the following arguments: + +=over 4 + +=item session_id + +=item quotationnum - the quotation to return + +=back + +Returns a hashref containing: + +- total_setup, the total of setup fees (and their taxes) +- total_recur, the total of all recurring charges (and their taxes) +- sections, an arrayref containing an element for each quotation section. + - description, a line of text describing the group of charges + - subtotal, the total of charges in this group (if appropriate) + - detail_items, an arrayref of line items + - pkgnum, the reference number of the package + - description, the package name (or tax name) + - quantity + - amount, the amount charged + If the detail item represents a subtotal, it will instead contain: + - total_item: description of the subtotal + - total_amount: the subtotal amount + + +=item quotation_print HASHREF + +Renders the quotation as HTML or PDF. Takes the following arguments: + +=over 4 + +=item session_id + +=item quotationnum - the quotation to return + +=item format - 'html' or 'pdf' + +=back + +Returns a hashref containing 'document', the contents of the file. + +=item quotation_add_pkg HASHREF + +Adds a package to a quotation. Takes the following arguments: + +=over 4 + +=item session_id + +=item pkgpart - the package to add + +=item quotationnum - the quotation to add it to + +=item quantity - the package quantity (defaults to 1) + +=item address1, address2, city, state, zip, country - address fields to set +the service location + +=back + +Returns 'error' => a string, which will be empty on success. + +=item quotation_remove_pkg HASHREF + +Removes a package from a quotation. Takes the following arguments: + +=over 4 + +=item session_id + +=item pkgnum - the primary key (quotationpkgnum) of the package to remove + +=item quotationnum - the quotation to remove it from + +=back + +Returns 'error' => a string, which will be empty on success. + +=back + +=item quotation_order HASHREF + +Converts the packages in a quotation into real packages. Takes the following +arguments: + +Takes the following arguments: + +=over 4 + +=item session_id + +=item quotationnum - the quotation to order + +=back + +=back + =head1 SIGNUP FUNCTIONS =over 4 -- 2.11.0