order stages
authorTony Cook <tony@develop-help.com>
Thu, 2 Jun 2011 04:50:26 +0000 (14:50 +1000)
committerTony Cook <tony@develop-help.com>
Wed, 8 Jun 2011 02:36:47 +0000 (12:36 +1000)
12 files changed:
MANIFEST
schema/bse.sql
site/cgi-bin/modules/BSE/DB/Mysql.pm
site/cgi-bin/modules/BSE/TB/Order.pm
site/cgi-bin/modules/BSE/TB/Orders.pm
site/cgi-bin/modules/BSE/UI/AdminShop.pm
site/cgi-bin/modules/BSE/UI/Shop.pm
site/data/db/bse_msg_base.data
site/data/db/bse_msg_defaults.data
site/templates/admin/order_detail.tmpl
site/templates/email/ordershipped.tmpl [new file with mode: 0644]
site/util/mysql.str

index 5b4ee29ba70317612d3a8fa1eae406d813967473..2b945e0751727bf68f6f72c2df96fa136531bdc8 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -636,6 +636,7 @@ site/templates/custom/payment_type.include
 site/templates/custom/payment_type_email.include
 site/templates/email/base.tmpl
 site/templates/email/email.css
+site/templates/email/ordershipped.tmpl
 site/templates/error.tmpl
 site/templates/error_base.tmpl
 site/templates/extras.txt
index 977d07a76d409619706ab818d789e965658f974a..5dcdebf005305bda70af2e3b1c5223b3b8f2ae99 100644 (file)
@@ -332,6 +332,8 @@ create table orders (
 
   freight_tracking varchar(255) not null default '',
 
+  stage varchar(20) not null default '',
+
   primary key (id),
   index order_cchash(ccNumberHash),
   index order_userId(userId, orderDate)
index 10250dea0818e9f66b739502c94ce3f464125424..6a4db65f22c5260f2d84d241cdb408cc760ec08a 100644 (file)
@@ -5,7 +5,7 @@ use vars qw/@ISA/;
 use Carp 'confess';
 @ISA = qw(BSE::DB);
 
-our $VERSION = "1.001";
+our $VERSION = "1.002";
 
 use vars qw($VERSION $MAX_CONNECTION_AGE);
 
@@ -151,13 +151,13 @@ delete from bse_wishlist where user_id = ? and product_id = ?
 SQL
 
    Orders => 'select * from orders',
-   getOrderByPkey => 'select * from orders where id = ?',
+   #getOrderByPkey => 'select * from orders where id = ?',
    getOrderItemByOrderId => 'select * from order_item where orderId = ?',
-   addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
-   replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   #addOrder => 'insert orders values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   #replaceOrder => 'replace orders values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    addOrderItem => 'insert order_item values(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    replaceOrderItem => 'replace order_item values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
-   getOrderByUserId => 'select * from orders where userId = ?',
+   #getOrderByUserId => 'select * from orders where userId = ?',
    deleteOrdersItems => 'delete from order_item where orderId = ?',
 
    getOrderItemByProductId => 'select * from order_item where productId = ?',
index bbd5ef5cae26a6033e648820b2402b37eb901a10..daa011bbf48a341755e52ae40a1888d1cab8ec13 100644 (file)
@@ -6,7 +6,7 @@ use vars qw/@ISA/;
 @ISA = qw/Squirrel::Row/;
 use Carp 'confess';
 
-our $VERSION = "1.002";
+our $VERSION = "1.004";
 
 sub columns {
   return qw/id
@@ -28,7 +28,11 @@ sub columns {
            ccStatus2 ccTranId complete delivOrganization billOrganization
            delivStreet2 billStreet2 purchase_order shipping_method
            shipping_name shipping_trace
-          paypal_token paypal_tran_id freight_tracking/;
+          paypal_token paypal_tran_id freight_tracking stage/;
+}
+
+sub table {
+  return "orders";
 }
 
 sub defaults {
@@ -36,6 +40,13 @@ sub defaults {
   require Digest::MD5;
   return
     (
+     billFirstName => "",
+     billLastName => "",
+     billStreet => "",
+     billSuburb => "",
+     billState => "",
+     billPostCode => "",
+     billCountry => "",
      total => 0,
      wholesaleTotal => 0,
      gst => 0,
@@ -80,7 +91,10 @@ sub defaults {
      shipping_method => '',
      shipping_name => '',
      shipping_trace => undef,
+     paypal_token => "",
+     paypal_tran_id => "",
      freight_tracking => "",
+     stage => "incomplete",
     );
 }
 
@@ -119,6 +133,12 @@ returns the SiteUser object of the user who made this order.
 sub siteuser {
   my ($self) = @_;
 
+  if ($self->siteuser_id) {
+    require SiteUsers;
+    my $user = SiteUsers->getByPkey($self->siteuser_id);
+    $user and return $user;
+  }
+
   $self->{userId} or return;
 
   require SiteUsers;
@@ -330,4 +350,122 @@ sub deliv_country_code {
   }
 }
 
+=item stage
+
+Return the order stage.
+
+If the stage is empty, guess from the order flags.
+
+=cut
+
+sub stage {
+  my ($self) = @_;
+
+  if ($self->{stage} ne "") {
+    return $self->{stage};
+  }
+
+  if (!$self->complete) {
+    return "incomplete";
+  }
+  elsif ($self->filled) {
+    return "shipped";
+  }
+  else {
+    return "unprocessed";
+  }
+}
+
+sub stage_description {
+  my ($self, $lang) = @_;
+
+  return BSE::TB::Orders->stage_label($self->stage, $lang);
+}
+
+=item mail_recipient
+
+Return a value suitable for BSE::ComposeMail's to parameter.
+
+=cut
+
+sub mail_recipient {
+  my ($self) = @_;
+
+  my $user = $self->siteuser;
+
+  if ($user && $user->email eq $self->emailAddress) {
+    return $user;
+  }
+
+  return $self->emailAddress;
+}
+
+sub send_shipped_email {
+  my ($self) = @_;
+
+  my $to = $self->mail_recipient;
+  require BSE::ComposeMail;
+  my $mailer = BSE::ComposeMail->new(cfg => BSE::Cfg->single);
+  require BSE::Util::Tags;
+  require BSE::Util::Iterate;
+  my $it = BSE::Util::Iterate::Objects->new;
+  my %acts =
+    (
+     BSE::Util::Tags->mail_tags(),
+     order => [ \&BSE::Util::Tags::tag_object_plain, $self ],
+     $it->make
+     (
+      single => "orderitem",
+      plural => "orderitems",
+      code => [ items => $self ],
+     ),
+    );
+
+  $mailer->send
+    (
+     to => $to,
+     subject => "Your order has shipped",
+     template => "email/ordershipped",
+     acts => \%acts,
+     log_msg => "Notify customer order has shipped",
+     log_object => $self,
+     log_component => "shopadmin:orders:saveorder",
+    );
+}
+
+sub new_stage {
+  my ($self, $who, $stage, $stage_note) = @_;
+
+  unless ($stage ne $self->stage
+         || defined $stage_note && $stage_note =~ /\S/) {
+    return;
+  }
+
+  my $old_stage = $self->stage;
+  my $msg = "Set to stage '$stage'";
+  if (defined $stage_note && $stage_note =~ /\S/) {
+    $msg .= ": $stage_note";
+  }
+  require BSE::TB::AuditLog;
+  BSE::TB::AuditLog->log
+    (
+     component => "shopadmin:orders:saveorder",
+     object => $self,
+     msg => $msg,
+     level => "info",
+     actor => $who || "U"
+    );
+
+  if ($stage ne $old_stage) {
+    $self->set_stage($stage);
+    if ($stage eq "shipped") {
+      $self->send_shipped_email();
+      $self->set_filled(1);
+    }
+    else {
+      $self->set_filled(0);
+    }
+  }
+}
+
 1;
index 43cef183567576c7ce8de99d2b142487a46d961e..2a832cec06d8bc462d217bf30b168c3e0b25f9c0 100644 (file)
@@ -5,7 +5,7 @@ use vars qw(@ISA $VERSION);
 @ISA = qw(Squirrel::Table);
 use BSE::TB::Order;
 
-our $VERSION = "1.002";
+our $VERSION = "1.003";
 
 sub rowClass {
   return 'BSE::TB::Order';
@@ -33,4 +33,53 @@ sub dummy_shipping_methods {
   return @ship;
 }
 
+=item stages()
+
+Return the possible values for the order stage field.
+
+=cut
+
+sub stages {
+  return qw(incomplete unprocessed backorder picked shipped cancelled stalled returned);
+}
+
+=item settable_stages()
+
+Return the stages that the admin can set the order stage to.
+
+=cut
+
+sub settable_stages {
+  my ($class) = @_;
+
+  return grep $_ ne "incomplete", $class->stages;
+}
+
+=item stage_labels($lang)
+
+Return descriptive labels for the stage values.
+
+=cut
+
+sub stage_labels {
+  my ($self, $lang) = @_;
+
+  return map { $_ => $self->stage_label($_, $lang) } $self->stages;
+}
+
+=item stage_label($stage, $lang)
+
+Given a stage value, return the label.
+
+=cut
+
+sub stage_label {
+  my ($self, $stage, $lang) = @_;
+
+  require BSE::Message;
+  my $msgs = BSE::Message->new;
+
+  return $msgs->text($lang, "msg:bse/shop/orderstages/$stage", [], $stage);
+}
+
 1;
index 632ae72bc1e87b27b3e90902c57eb01c19d44ce6..93456b996d869f64574087cbb0db8f3be5b5d7cf 100644 (file)
@@ -12,14 +12,14 @@ use Constants qw(:shop $SHOPID $PRODUCTPARENT
 use BSE::TB::Images;
 use Articles;
 use BSE::Sort;
-use BSE::Util::Tags qw(tag_hash tag_error_img);
+use BSE::Util::Tags qw(tag_hash tag_error_img tag_object_plain tag_object);
 use BSE::Util::Iterate;
 use BSE::WebUtil 'refresh_to_admin';
 use BSE::Util::HTML qw(:default popup_menu);
 use BSE::Arrows;
 use BSE::Shop::Util qw(:payment order_item_opts nice_options);
 
-our $VERSION = "1.004";
+our $VERSION = "1.005";
 
 my %actions =
   (
@@ -586,6 +586,21 @@ sub tag_shipping_method_select {
     );
 }
 
+sub tag_stage_select {
+  my ($self, $req, $order) = @_;
+
+  my @stages = BSE::TB::Orders->settable_stages;
+  
+  my %stage_labels = BSE::TB::Orders->stage_labels;
+  return popup_menu
+    (
+     -name => "stage",
+     -values => \@stages,
+     -default => $order->stage,
+     -labels => \%stage_labels,
+    );
+}
+
 sub req_order_detail {
   my ($class, $req, $errors) = @_;
 
@@ -620,7 +635,7 @@ sub req_order_detail {
         }
         return 0;
        },
-       order => [ \&tag_hash, $order ],
+       order => [ \&tag_object, $order ],
        extension =>
        sub {
         sprintf("%.2f", $lines[$line_index]{units} * $lines[$line_index]{$_[0]}/100.0)
@@ -643,6 +658,9 @@ sub req_order_detail {
        ),
        shipping_method_select =>
        [ tag_shipping_method_select => $class, $order ],
+       stage_select =>
+       [ tag_stage_select => $class, $req, $order ],
+       stage_description => escape_html($order->stage_description($req->language)),
       );
 
     return $req->dyn_response('admin/order_detail', \%acts);
@@ -752,7 +770,7 @@ sub req_paypal_refund {
 
 Make changes to an order, only a limited set of fields can be changed.
 
-Parameters:
+Parameters, all optional:
 
 =over
 
@@ -769,6 +787,11 @@ id of the dummy shipping method to set for the order.
 
 freight_tracking - the freight tracking code for the shipment.
 
+=item *
+
+stage - order stage, one of unprocessed, backorder, picked, shipped,
+cancelled.
+
 =back
 
 Requires csrfp token C<shop_order_save>.
@@ -819,13 +842,25 @@ sub req_order_save {
     }
   }
 
+  my $new_stage = 0;
+  my ($stage) = $cgi->param("stage");
+  my ($stage_note) = $cgi->param("stage_note");
+  if (defined $stage && $stage ne $order->stage
+     || defined $stage_note && $stage_note =~ /\S/) {
+    my @stages = BSE::TB::Orders->settable_stages;
+    if (grep $_ eq $stage, @stages) {
+      ++$new_stage;
+      ++$save;
+    }
+    else {
+      $errors{stage} = "msg:bse/admin/shop/saveorder/badstage:$stage";
+    }
+  }
+
   keys %errors
     and return $self->req_order_detail($req, \%errors);
 
   if ($save) {
-    $order->save;
-    $req->flash("msg:bse/admin/shop/saveorder/saved");
-
     if ($new_freight_tracking) {
       $req->audit
        (
@@ -844,6 +879,12 @@ sub req_order_save {
         level => "info",
        );
     }
+    if ($new_stage) {
+      $order->new_stage(scalar $req->user, $stage, $stage_note);
+    }
+
+    $order->save;
+    $req->flash("msg:bse/admin/shop/saveorder/saved");
   }
   else {
     $req->flash("msg:bse/admin/shop/saveorder/nochanges");
index 9ea005cfeb0d5773edc8b7a7b2603097d90f4aaa..82180f8bbef22ae34899301f00ad746585f4d2c7 100644 (file)
@@ -17,7 +17,7 @@ use BSE::Shipping;
 use BSE::Countries qw(bse_country_code);
 use BSE::Util::Secure qw(make_secret);
 
-our $VERSION = "1.010";
+our $VERSION = "1.012";
 
 use constant MSG_SHOP_CART_FULL => 'Your shopping cart is full, please remove an item and try adding an item again';
 
@@ -686,7 +686,9 @@ sub req_order {
   my %errors;
   my %values;
   for my $name (keys %fields) {
-    ($values{$name}) = $cgi->param($name);
+    my ($value) = $cgi->param($name);
+    defined $value or $value = "";
+    $values{$name} = $value;
   }
 
   my @required = 
@@ -840,6 +842,7 @@ my %nostore =
    cardNumber => 1,
    cardExpiry => 1,
    delivery_in => 1,
+   cardVerify => 1,
   );
 
 sub req_payment {
@@ -955,21 +958,21 @@ sub req_payment {
     
     my @items = $class->_build_items($req, \@products);
     
-    my @columns = BSE::TB::Order->columns;
-    my %columns; 
-    @columns{@columns} = @columns;
-    
-    for my $col (@columns) {
-      defined $order_values->{$col} or $order_values->{$col} = '';
-    }
-    
-    my @data = @{$order_values}{@columns};
-    shift @data;
-    
     if ($session->{order_work}) {
       $order = BSE::TB::Orders->getByPkey($session->{order_work});
     }
     if ($order && !$order->{complete}) {
+      my @columns = BSE::TB::Order->columns;
+      my %columns; 
+      @columns{@columns} = @columns;
+      
+      for my $col (@columns) {
+       defined $order_values->{$col} or $order_values->{$col} = '';
+      }
+      
+      my @data = @{$order_values}{@columns};
+      shift @data;
+    
       print STDERR "Recycling order $order->{id}\n";
       
       my @allbutid = @columns;
@@ -983,7 +986,7 @@ sub req_payment {
       };
     }
     else {
-      $order = BSE::TB::Orders->add(@data)
+      $order = BSE::TB::Orders->make(%$order_values)
        or die "Cannot add order";
     }
     
@@ -1112,7 +1115,8 @@ sub req_payment {
   }
 
   # order complete
-  $order->{complete} = 1;
+  $order->set_complete(1);
+  $order->set_stage("unprocessed");
   $order->save;
 
   $class->_finish_order($req, $order);
@@ -1678,7 +1682,7 @@ sub _fillout_order {
   }
   $values->{total} = $total;
   $values->{gst} = $total_gst;
-  $values->{wholesale} = $total_wholesale;
+  #$values->{wholesale} = $total_wholesale;
 
   my $prompt_ship = $cfg->entry("shop", "shipping", 0);
   if ($prompt_ship) {
@@ -1714,7 +1718,7 @@ sub _fillout_order {
       $values->{shipping_name} = $courier->name;
       $values->{shipping_cost} = $cost;
       $values->{shipping_trace} = $courier->trace;
-      $values->{delivery_in} = $courier->delivery_in();
+      #$values->{delivery_in} = $courier->delivery_in();
       $values->{total} += $values->{shipping_cost};
     }
     else {
index 2d07bd4a7205be112acdd71edb9f64629112833f..459e79900af7627ae29b5f898ba388f27c673bcb 100644 (file)
@@ -54,7 +54,10 @@ id: bse/admin/shop/saveorder/nochanges
 description: Displayed on a save that makes no changes
 
 id: bse/admin/shop/saveorder/badmethod
-description: Invalid shipping method
+description: Invalid shipping method, $1 is the supplied method id
+
+id: bse/admin/shop/saveorder/badstage
+description: Invalid order stage, $1 is the supplied stage id
 
 id: test/
 description: <<TEXT
@@ -128,6 +131,33 @@ description: Returned from PayPal for an order that was already paid.  The payme
 id: bse/shop/paypal/badtoken
 description: The transaction token supplied by PayPal to paypalret doesn't match the token value stored in the order
 
+id: bse/shop/orderstages/
+description: Descriptions of the order stages
+
+id: bse/shop/orderstages/incomplete
+description: The incomplete order stage
+
+id: bse/shop/orderstages/unprocessed
+description: The unprocessed order stage
+
+id: bse/shop/orderstages/backorder
+description: The backorderorder stage
+
+id: bse/shop/orderstages/picked
+description: The picked order stage
+
+id: bse/shop/orderstages/shipped
+description: The shipped order stage
+
+id: bse/shop/orderstages/cancelled
+description: The cancelled order stage
+
+id: bse/shop/orderstages/stalled
+description: The stalled order stage
+
+id: bse/shop/orderstages/returned
+description: The stalled returned stage
+
 id: bse/files/
 description: File validation messages
 
index e5b732fb0a8eda96f0b2d632648e82fdcab53431..5aa6e75e87a35ea8d3f0c49a1d93da418711bfa0 100644 (file)
@@ -1,5 +1,5 @@
 ---
-# VERSION=1.001
+# VERSION=1.002
 # defaults for the following
 language_code: en
 priority: 0
@@ -43,6 +43,9 @@ message: No changes to save
 id: bse/admin/shop/saveorder/badmethod
 message: Unknown shipping method '$1:s'
 
+id: bse/admin/shop/saveorder/badstage
+message: Unknown order stage '$1:s'
+
 id: bse/shop/cart/empty
 message: Cart emptied
 
@@ -70,6 +73,30 @@ message: PayPal payment cancelled
 id: bse/shop/paypal/badtoken
 message: The token returned by PayPal doesn't match the token generated for  order.
 
+id: bse/shop/orderstages/incomplete
+message: Not completed by customer
+
+id: bse/shop/orderstages/unprocessed
+message: Awaiting processing
+
+id: bse/shop/orderstages/backorder
+message: One or more items on backorder
+
+id: bse/shop/orderstages/picked
+message: Items picked and packed
+
+id: bse/shop/orderstages/shipped
+message: Shipped
+
+id: bse/shop/orderstages/cancelled
+message: Cancelled
+
+id: bse/shop/orderstages/stalled
+message: Stalled on customer response
+
+id: bse/shop/orderstages/returned
+message: Returned
+
 id: bse/files/image_file_required
 message: File must be an image file
 
index 0cb220bf9286cfe93f45c2db8d6d17c268fb1111..f1d6aaf0bd9ed5ec1a77643e3c49fbcf1d965a20 100644 (file)
@@ -135,6 +135,16 @@ Shipping via <:order shipping_method:>
 </tr>
 <:eif Cfg:>
 </table>
+<p>Order progress: <:stage_description:> <a href="#" onclick="document.getElementById('update_stage').style.display='block'; this.style.display='none'; return false;">update</a></p>
+
+<form id="update_stage" action="<:adminurl shopadmin:>" style="display: none" method="post">
+  <input type="hidden" name="id" value="<:order id:>" />
+  <:csrfp shop_order_save hidden:>
+  <:stage_select:>
+  <label>Log note: <input type="text" name="stage_note" size="40" /> (optional)</label>
+  <input type="submit" name="a_order_save" value="Save" />
+</form>
+
 <:switch:>
 <:case Eq [order paidFor] "0":>
 <p>This order hasn't been paid</p>
diff --git a/site/templates/email/ordershipped.tmpl b/site/templates/email/ordershipped.tmpl
new file mode 100644 (file)
index 0000000..29403d7
--- /dev/null
@@ -0,0 +1,14 @@
+Your order <:order id |%05d:> has been shipped.
+<:ifOrder freight_tracking
+:>It has tracking code <:order freight_tracking:>
+
+<:or:><:eif
+:>
+Item                                                      Units    Price
+<:iterator begin orderitems:>
+<:orderitem title |%-60s:>
+                                                         <:orderitem units |%6d:> <:money orderitem price |%8s:>
+<:iterator end orderitems:>
+<:ifOrder shipping_cost
+:>Shipping:                                                       <:money order shipping_cost |%8s:><:or:><:eif:>
+Total:                                                          <:money order total |%8s:>
index b8c9ced89249bb37954b771a0c6beaa714d5bd15..7022f85f86b5986bd38ec84d628ca4055f812ec8 100644 (file)
@@ -547,6 +547,7 @@ Column shipping_trace;text;YES;NULL;
 Column paypal_token;varchar(255);YES;NULL;
 Column paypal_tran_id;varchar(255);YES;NULL;
 Column freight_tracking;varchar(255);NO;;
+Column stage;varchar(20);NO;;
 Index PRIMARY;1;[id]
 Index order_cchash;0;[ccNumberHash]
 Index order_userId;0;[userId;orderDate]