[freeside-commits] branch master updated. 911b5f2429377b0b989e8a10e9971b2463e554a7

Mark Wells mark at 420.am
Mon Apr 27 00:06:55 PDT 2015


The branch, master has been updated
       via  911b5f2429377b0b989e8a10e9971b2463e554a7 (commit)
       via  c110da0da864245e47cae019b8a347367cc6430c (commit)
      from  4fda726fa9f8e709c68ec823edc5ae702723281c (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 911b5f2429377b0b989e8a10e9971b2463e554a7
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Apr 27 00:05:46 2015 -0700

    disable quotation after ordering, #33852

diff --git a/FS/FS/ClientAPI/MyAccount/quotation.pm b/FS/FS/ClientAPI/MyAccount/quotation.pm
index ce2debd..787a099 100644
--- a/FS/FS/ClientAPI/MyAccount/quotation.pm
+++ b/FS/FS/ClientAPI/MyAccount/quotation.pm
@@ -211,6 +211,8 @@ sub quotation_order {
   my $quotation = _quotation($session);
 
   my $error = $quotation->order;
+  $quotation->set('disabled' => 'Y');
+  $error ||= $quotation->replace;
 
   return { 'error' => $error };
 }

commit c110da0da864245e47cae019b8a347367cc6430c
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Apr 25 15:02:15 2015 -0700

    selfservice quotations, #33852

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
diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm
index 9d7e7ed..a9da564 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/ng_selfservice/images/cross.png b/ng_selfservice/images/cross.png
new file mode 100644
index 0000000..1514d51
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 0000000..cf45543
--- /dev/null
+++ b/ng_selfservice/quotation.php
@@ -0,0 +1,130 @@
+<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'); ?>
diff --git a/ng_selfservice/quotation_add_pkg.php b/ng_selfservice/quotation_add_pkg.php
new file mode 100644
index 0000000..1e7e71f
--- /dev/null
+++ b/ng_selfservice/quotation_add_pkg.php
@@ -0,0 +1,31 @@
+<? 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");
+
+?>
+
diff --git a/ng_selfservice/quotation_order.php b/ng_selfservice/quotation_order.php
new file mode 100644
index 0000000..d35eacb
--- /dev/null
+++ b/ng_selfservice/quotation_order.php
@@ -0,0 +1,15 @@
+<? 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");
+
+?>
diff --git a/ng_selfservice/quotation_print.php b/ng_selfservice/quotation_print.php
new file mode 100644
index 0000000..9676405
--- /dev/null
+++ b/ng_selfservice/quotation_print.php
@@ -0,0 +1,17 @@
+<? 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']);
+}
+
+?>
diff --git a/ng_selfservice/quotation_remove_pkg.php b/ng_selfservice/quotation_remove_pkg.php
new file mode 100644
index 0000000..07548c7
--- /dev/null
+++ b/ng_selfservice/quotation_remove_pkg.php
@@ -0,0 +1,31 @@
+<? 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");
+
+?>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/ClientAPI/MasonComponent.pm                  |   21 ++
 FS/FS/ClientAPI/MyAccount.pm                       |    2 +
 FS/FS/ClientAPI/MyAccount/quotation.pm             |  220 ++++++++++++++++++++
 FS/FS/ClientAPI_XMLRPC.pm                          |    7 +
 FS/FS/quotation.pm                                 |   34 +--
 fs_selfservice/FS-SelfService/SelfService.pm       |    7 +
 .../cgi => ng_selfservice}/images/cross.png        |  Bin 655 -> 655 bytes
 ng_selfservice/quotation.php                       |  130 ++++++++++++
 ng_selfservice/quotation_add_pkg.php               |   31 +++
 ng_selfservice/quotation_order.php                 |   15 ++
 ng_selfservice/quotation_print.php                 |   17 ++
 ng_selfservice/quotation_remove_pkg.php            |   31 +++
 12 files changed, 499 insertions(+), 16 deletions(-)
 create mode 100644 FS/FS/ClientAPI/MyAccount/quotation.pm
 copy {fs_selfservice/FS-SelfService/cgi => ng_selfservice}/images/cross.png (100%)
 create mode 100644 ng_selfservice/quotation.php
 create mode 100644 ng_selfservice/quotation_add_pkg.php
 create mode 100644 ng_selfservice/quotation_order.php
 create mode 100644 ng_selfservice/quotation_print.php
 create mode 100644 ng_selfservice/quotation_remove_pkg.php




More information about the freeside-commits mailing list