Example #1
0
 protected function _execute()
 {
     if (!$this->_sale->loaded()) {
         throw new Exception("Sale could not be found.");
     }
     if ($this->_sale->date_cancelled || $this->_sale->cancel_transaction_id) {
         throw new Exception("Sale has already been cancelled.");
     }
     if ($this->_sale->refund_form_id and $this->_sale->refund_form_id > $this->_sale->id) {
         throw new Exception("Sale could not be cancelled - it has a refund attached to it.");
     }
     $date_cancelled = date("Y-m-d");
     if (isset($this->_data->date_cancelled)) {
         $date_cancelled = $this->_data->date_cancelled;
     }
     if ($date_cancelled != date("Y-m-d", strtotime($date_cancelled))) {
         throw new Exception("Invalid cancellation date: must be in YYYY-MM-DD format.");
     }
     if (strtotime($date_cancelled) < strtotime($this->_sale->date_created)) {
         throw new Exception("Invalid cancellation date: must be on or after the creation date of " . $this->_sale->date_created . ".");
     }
     if ($this->_sale->date_billed and strtotime($this->_sale->date_billed) > strtotime($date_cancelled)) {
         throw new Exception("Invalid cancellation date: must be after the invoice date of " . $this->_sale->date_billed . ".");
     }
     if ($this->_check_books_closed($date_cancelled)) {
         throw new Exception("Sale could not be cancelled.  The financial year has been closed already.");
     }
     $this->_sale->date_cancelled = $date_cancelled;
     $this->_sale->save();
     $sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('ids' => array($this->_sale->id))));
     $sale_calibrate_result = $sale_calibrate->execute();
     if (!$sale_calibrate_result->success) {
         $this->_sale->date_cancelled = NULL;
         $this->_sale->save();
         throw new Exception("Error trying to cancel sale: " . $sale_calibrate_result->error);
     }
     // Reload Sale
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     // If we're successful AND this has been billed- update taxes.
     if ($this->_sale->date_billed) {
         $tax_item_action = 'refund';
         $this->_update_form_tax_items($this->_sale->id, $tax_item_action);
     }
     // Remove the refund form from the corresponding form.
     if ($this->_sale->refund_form->loaded()) {
         $this->_sale->refund_form->refund_form_id = NULL;
         $this->_sale->refund_form->save();
     }
     // Recalibrate Payments
     $customer_payment_calibrate = new Beans_Customer_Payment_Calibrate($this->_beans_data_auth((object) array('form_ids' => array($this->_sale->id))));
     $customer_payment_calibrate_result = $customer_payment_calibrate->execute();
     if (!$customer_payment_calibrate_result->success) {
         throw new Exception("Error encountered when calibrating payments: " . $customer_payment_calibrate_result->error);
     }
     // Reload Sale per Payment Calibration.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     return (object) array("sale" => $this->_return_customer_sale_element($this->_sale));
 }
Example #2
0
 protected function _execute()
 {
     if (!$this->_sale->loaded()) {
         throw new Exception("That sale could not be found.");
     }
     if ($this->_sale->date_cancelled) {
         throw new Exception("A sale cannot be converted to an invoice after it has been cancelled.");
     }
     if ($this->_sale->date_billed) {
         throw new Exception("That sale has already been converted to an invoice.");
     }
     if ($this->_date_billed != date("Y-m-d", strtotime($this->_date_billed))) {
         throw new Exception("Invalid invoice date: must be in YYYY-MM-DD format.");
     }
     if (strtotime($this->_date_billed) < strtotime($this->_sale->date_created)) {
         throw new Exception("Invalid invoice date: must be on or after the creation date of " . $this->_sale->date_created . ".");
     }
     if ($this->_date_due and $this->_date_due != date("Y-m-d", strtotime($this->_date_due))) {
         throw new Exception("Invalid due date: must be in YYYY-MM-DD format.");
     }
     if ($this->_date_due and strtotime($this->_date_due) < strtotime($this->_date_billed)) {
         throw new Exception("Invalid due date: must be on or after the bill date.");
     }
     if ($this->_sale->total == 0.0) {
         throw new Exception("Cannot invoice a sale for \$0.00 - but it can be cancelled.");
     }
     $this->_sale->date_billed = $this->_date_billed;
     $this->_sale->date_due = $this->_date_due ? $this->_date_due : date("Y-m-d", strtotime($this->_sale->date_billed . ' +' . $this->_sale->account->terms . ' Days'));
     $this->_sale->save();
     $sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('ids' => array($this->_sale->id))));
     $sale_calibrate_result = $sale_calibrate->execute();
     if (!$sale_calibrate_result->success) {
         $this->_sale->date_billed = NULL;
         $this->_sale->date_due = NULL;
         $this->_sale->save();
         throw new Exception("Error trying to invoice sale: " . $sale_calibrate_result->error);
     }
     // Reload the sale.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     // Recalibrate Payments
     $customer_payment_calibrate = new Beans_Customer_Payment_Calibrate($this->_beans_data_auth((object) array('form_ids' => array($this->_sale->id))));
     $customer_payment_calibrate_result = $customer_payment_calibrate->execute();
     if (!$customer_payment_calibrate_result->success) {
         throw new Exception("Error encountered when calibrating payments: " . $customer_payment_calibrate_result->error);
     }
     // Update tax items only if we're successful.
     $tax_item_action = 'invoice';
     if ($this->_sale->total < 0 || $this->_sale->refund_form_id && $this->_sale->refund_form_id < $this->_sale->id) {
         $tax_item_action = 'refund';
     }
     $this->_update_form_tax_items($this->_sale->id, $tax_item_action);
     $this->_sale->save();
     // Reload Sale per Payment Calibration.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     return (object) array("sale" => $this->_return_customer_sale_element($this->_sale));
 }
Example #3
0
 public function action_calibratedate()
 {
     $date = $this->request->post('date');
     $manual = $this->request->post('manual');
     if (!$date or $date != date("Y-m-d", strtotime($date))) {
         return $this->_return_error('Invalid date provided: ' . $date . ' expected YYYY-MM-DD');
     }
     // If manual, we don;t want them trying to calibrate pre-fye
     if ($manual == "1") {
         $account_closebooks_check = new Beans_Account_Closebooks_Check($this->_beans_data_auth());
         $account_closebooks_check_result = $account_closebooks_check->execute();
         if (!$account_closebooks_check_result->success) {
             return $this->_return_error("An unexpected error occurred when trying to validate the date: " . $account_closebooks_check_result->error);
         }
         if ($account_closebooks_check_result->data->previous_date && strtotime($date) <= strtotime($account_closebooks_check_result->data->previous_date)) {
             return $this->_return_error("The books were closed most recently on " . $account_closebooks_check_result->data->previous_date . ". " . "Please choose a date after that to begin manual calibration.");
         }
     }
     // This can take a while.
     set_time_limit(60 * 10);
     ini_set('memory_limit', '256M');
     // Recalibrate Customer Invoices / Cancellations
     $customer_sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('date_after' => $date, 'date_before' => $date)));
     $customer_sale_calibrate_result = $customer_sale_calibrate->execute();
     if (!$customer_sale_calibrate_result->success) {
         return $this->_return_error('Error updating customer sales: ' . $customer_sale_calibrate_result->error);
     }
     // Recalibrate any payments tied to these sales AFTER this transaction date.
     $customer_payment_calibrate = new Beans_Customer_Payment_Calibrate($this->_beans_data_auth((object) array('date_after' => $date, 'date_before' => $date)));
     $customer_payment_calibrate_result = $customer_payment_calibrate->execute();
     if (!$customer_payment_calibrate_result->success) {
         return $this->_return_error('Error updating customer payments: ' . $customer_payment_calibrate_result->error);
     }
     // Recalibrate Vendor Invoices / Cancellations
     $vendor_purchase_calibrate = new Beans_Vendor_Purchase_Calibrate($this->_beans_data_auth((object) array('date_after' => $date, 'date_before' => $date)));
     $vendor_purchase_calibrate_result = $vendor_purchase_calibrate->execute();
     if (!$vendor_purchase_calibrate_result->success) {
         return $this->_return_error('Error updating vendor purchases: ' . $vendor_purchase_calibrate_result->error);
     }
     // Recalibrate any payments tied to these purchases AFTER this transaction date.
     $vendor_payment_calibrate = new Beans_Vendor_Payment_Calibrate($this->_beans_data_auth((object) array('date_after' => $date, 'date_before' => $date)));
     $vendor_payment_calibrate_result = $vendor_payment_calibrate->execute();
     if (!$vendor_payment_calibrate_result->success) {
         return $this->_return_error('Error updating vendor payments: ' . $vendor_payment_calibrate_result->error);
     }
     $this->_return_object->data->date_next = date("Y-m-d", strtotime($date . " +1 Day"));
     $account_transaction_search = new Beans_Account_Transaction_Search($this->_beans_data_auth((object) array('sort_by' => 'newest', 'page_size' => 1)));
     $account_transaction_search_result = $account_transaction_search->execute();
     if (!$account_transaction_search_result->success || !count($account_transaction_search_result->data->transactions)) {
         return $this->_return_error('Error getting ending transaction: ' . $account_transaction_search_result->error);
     }
     $date_end = $account_transaction_search_result->data->transactions[0]->date;
     // This gets run on the very last iteration of calibration.
     if (strtotime($date_end) < strtotime($this->_return_object->data->date_next)) {
         $this->_return_object->data->date_next = FALSE;
         $account_calibrate = new Beans_Account_Calibrate($this->_beans_data_auth());
         $account_calibrate_result = $account_calibrate->execute();
         if (!$account_calibrate_result->success) {
             return $this->_return_error('Error calibrating individual account balances: ' . $account_calibrate_result->error);
         }
         $customer_sale_calibrate_check = new Beans_Customer_Sale_Calibrate_Check($this->_beans_data_auth());
         $customer_sale_calibrate_check_result = $customer_sale_calibrate_check->execute();
         if (!$customer_sale_calibrate_check_result->success) {
             return $this->_return_error('Error calibrating customer sales: ' . $customer_sale_calibrate_check_result->error);
         }
         if (count($customer_sale_calibrate_check_result->data->ids)) {
             $customer_sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('ids' => $customer_sale_calibrate_check_result->data->ids)));
             $customer_sale_calibrate_result = $customer_sale_calibrate->execute();
             if (!$customer_sale_calibrate_result->success) {
                 return $this->_return_error('Error calibrating customer sales: ' . $customer_sale_calibrate_result->error);
             }
         }
         $vendor_purchase_calibrate_check = new Beans_Vendor_Purchase_Calibrate_Check($this->_beans_data_auth());
         $vendor_purchase_calibrate_check_result = $vendor_purchase_calibrate_check->execute();
         if (!$vendor_purchase_calibrate_check_result->success) {
             return $this->_return_error('Error calibrating vendor purchases: ' . $vendor_purchase_calibrate_check_result->error);
         }
         if (count($vendor_purchase_calibrate_check_result->data->ids)) {
             $vendor_purchase_calibrate = new Beans_Vendor_Purchase_Calibrate($this->_beans_data_auth((object) array('ids' => $vendor_purchase_calibrate_check_result->data->ids)));
             $vendor_purchase_calibrate_result = $vendor_purchase_calibrate->execute();
             if (!$vendor_purchase_calibrate_result->success) {
                 return $this->_return_error('Error calibrating vendor purchases: ' . $vendor_purchase_calibrate_result->error);
             }
         }
     }
     // Update our latest date in case user pauses and comes back later.
     // We only do this if the user isn't manually recalibrating books.
     if ($manual !== "1") {
         $setup_company_update = new Beans_Setup_Company_Update($this->_beans_data_auth((object) array('settings' => array('calibrate_date_next' => $this->_return_object->data->date_next))));
         $setup_company_update_result = $setup_company_update->execute();
     }
 }
Example #4
0
 protected function _execute()
 {
     // Independently validate $this->_date_billed
     if ($this->_date_billed and $this->_date_billed != date("Y-m-d", strtotime($this->_date_billed))) {
         throw new Exception("Invalid invoice date: must be in YYYY-MM-DD format.");
     }
     // Three laws.
     $temp_date_created = isset($this->_data->date_created) ? $this->_data->date_created : date("Y-m-d");
     if ($temp_date_created != date("Y-m-d", strtotime($temp_date_created))) {
         throw new Exception("Invalid sale date: must be in YYYY-MM-DD format.");
     }
     if ($this->_date_billed and strtotime($this->_date_billed) < strtotime($temp_date_created)) {
         throw new Exception("Invalid invoice date: must on or after the date of the sale order.");
     }
     if ($this->_date_due and $this->_date_due != date("Y-m-d", strtotime($this->_date_due))) {
         throw new Exception("Invalid due date: must be in YYYY-MM-DD format.");
     }
     if ($this->_date_due and strtotime($this->_date_due) < strtotime($this->_date_billed)) {
         throw new Exception("Invalid due date: must be on or after the bill date.");
     }
     $this->_sale->entity_id = isset($this->_data->customer_id) ? (int) $this->_data->customer_id : NULL;
     $this->_sale->account_id = isset($this->_data->account_id) ? $this->_data->account_id : NULL;
     $this->_sale->refund_form_id = isset($this->_data->refund_sale_id) ? $this->_data->refund_sale_id : NULL;
     $this->_sale->sent = isset($this->_data->sent) ? $this->_data->sent : NULL;
     $this->_sale->date_created = isset($this->_data->date_created) ? $this->_data->date_created : NULL;
     $this->_sale->code = (isset($this->_data->sale_number) and $this->_data->sale_number) ? $this->_data->sale_number : "AUTOGENERATE";
     $this->_sale->reference = isset($this->_data->order_number) ? $this->_data->order_number : NULL;
     $this->_sale->alt_reference = isset($this->_data->po_number) ? $this->_data->po_number : NULL;
     $this->_sale->aux_reference = isset($this->_data->quote_number) ? $this->_data->quote_number : NULL;
     $this->_sale->tax_exempt = (isset($this->_data->tax_exempt) and $this->_data->tax_exempt) ? TRUE : FALSE;
     $this->_sale->tax_exempt_reason = ($this->_sale->tax_exempt and isset($this->_data->tax_exempt_reason) and $this->_data->tax_exempt_reason) ? $this->_data->tax_exempt_reason : NULL;
     $this->_sale->billing_address_id = isset($this->_data->billing_address_id) ? (int) $this->_data->billing_address_id : NULL;
     $this->_sale->shipping_address_id = isset($this->_data->shipping_address_id) ? (int) $this->_data->shipping_address_id : NULL;
     // Handle Default Account Receivable
     // Customer Default Account Receivable
     if ($this->_sale->account_id === NULL) {
         $customer = $this->_load_customer($this->_sale->entity_id);
         if ($customer->loaded() and $customer->default_account_id) {
             $this->_sale->account_id = $customer->default_account_id;
         }
     }
     // Default Account Receivable
     if ($this->_sale->account_id === NULL) {
         $this->_sale->account_id = $this->_beans_setting_get('account_default_receivable');
     }
     // Make sure we have good sale information before moving on.
     $this->_validate_customer_sale($this->_sale);
     $this->_sale->total = 0.0;
     $this->_sale->amount = 0.0;
     if (!isset($this->_data->lines) or !is_array($this->_data->lines) or !count($this->_data->lines)) {
         throw new Exception("Invalid sale line items: none provided.");
     }
     if (isset($this->_data->taxes)) {
         if (!is_array($this->_data->taxes)) {
             throw new Exception("Invalid sale taxes: must be an array.");
         }
         foreach ($this->_data->taxes as $sale_tax) {
             $new_sale_tax = $this->_default_form_tax();
             $new_sale_tax->tax_id = isset($sale_tax->tax_id) ? (int) $sale_tax->tax_id : NULL;
             if (!$new_sale_tax->tax_id) {
                 throw new Exception("Invalid sale tax ID: none provided.");
             }
             $tax = $this->_load_tax($new_sale_tax->tax_id);
             if (!$tax->loaded()) {
                 throw new Exception("Invalid sale tax ID: tax not found.");
             }
             $new_sale_tax->tax_percent = $tax->percent;
             $new_sale_tax->form_line_amount = 0.0;
             $new_sale_tax->form_line_taxable_amount = 0.0;
             $new_sale_tax->total = 0.0;
             if (!isset($this->_sale_taxes[$new_sale_tax->tax_id])) {
                 $this->_sale_taxes[$new_sale_tax->tax_id] = $new_sale_tax;
             }
         }
     }
     foreach ($this->_data->lines as $sale_line) {
         $new_sale_line = $this->_default_form_line();
         $new_sale_line->account_id = isset($sale_line->account_id) ? (int) $sale_line->account_id : NULL;
         $new_sale_line->tax_exempt = $this->_sale->tax_exempt || (isset($sale_line->tax_exempt) and $sale_line->tax_exempt) ? TRUE : FALSE;
         $new_sale_line->description = isset($sale_line->description) ? $sale_line->description : NULL;
         $new_sale_line->amount = isset($sale_line->amount) ? $this->_beans_round($sale_line->amount) : NULL;
         $new_sale_line->quantity = isset($sale_line->quantity) ? (double) $sale_line->quantity : NULL;
         // Handle Default Income Account
         if ($new_sale_line->account_id === NULL) {
             $new_sale_line->account_id = $this->_beans_setting_get('account_default_income');
         }
         $this->_validate_customer_sale_line($new_sale_line);
         $new_sale_line->total = $this->_beans_round($new_sale_line->amount * $new_sale_line->quantity);
         if (!$new_sale_line->tax_exempt) {
             foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
                 $this->_sale_taxes[$tax_id]->form_line_taxable_amount = $this->_beans_round($this->_sale_taxes[$tax_id]->form_line_taxable_amount + $new_sale_line->total);
             }
         }
         $this->_sale->amount = $this->_beans_round($this->_sale->amount + $new_sale_line->total);
         $this->_sale_lines[] = $new_sale_line;
     }
     $this->_sale->total = $this->_beans_round($this->_sale->total + $this->_sale->amount);
     foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
         $this->_sale_taxes[$tax_id]->total = $this->_beans_round($sale_tax->tax_percent * $sale_tax->form_line_taxable_amount);
         $this->_sale_taxes[$tax_id]->form_line_amount = $this->_sale->amount;
         $this->_sale->total = $this->_beans_round($this->_sale->total + $this->_sale_taxes[$tax_id]->total);
     }
     // Validate Totals
     if ($this->_sale->refund_form_id) {
         $refund_form = $this->_load_customer_sale($this->_sale->refund_form_id);
         if (!$refund_form->loaded()) {
             throw new Exception("That refund_sale_id was not found.");
         }
         $original_sale = $refund_form;
         $refund_sale = $this->_sale;
         if ($original_sale->total > 0.0 and $refund_sale->total > 0.0 or $original_sale->total < 0.0 and $refund_sale->total < 0.0) {
             throw new Exception("Refund and original sale totals must offset each other ( they cannot both be positive or negative ).");
         }
         if (abs($refund_sale->total) > abs($original_sale->total)) {
             throw new Exception("The refund total cannot be greater than the original sale total.");
         }
     }
     // Save Sale + Children
     $this->_sale->save();
     foreach ($this->_sale_lines as $j => $sale_line) {
         $sale_line->form_id = $this->_sale->id;
         $sale_line->save();
     }
     foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
         $this->_sale_taxes[$tax_id]->form_id = $this->_sale->id;
         $this->_sale_taxes[$tax_id]->save();
     }
     if ($this->_sale->code == "AUTOGENERATE") {
         $this->_sale->code = $this->_sale->id;
     }
     $this->_sale->save();
     $sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('ids' => array($this->_sale->id))));
     $sale_calibrate_result = $sale_calibrate->execute();
     if (!$sale_calibrate_result->success) {
         // We've had an account transaction failure and need to delete the sale we just created.
         $delete_sale = new Beans_Customer_Sale_Delete($this->_beans_data_auth((object) array('id' => $this->_sale->id)));
         $delete_sale_result = $delete_sale->execute();
         // TODO - 100PERCENTWORKING
         if (!$delete_sale_result->success) {
             throw new Exception("Error creating account transaction for sale. COULD NOT DELETE SALE! " . $delete_sale_result->error);
         }
         throw new Exception("Error trying to create sale: " . $sale_calibrate_result->error);
     }
     // Reload the sale.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     if ($this->_date_billed) {
         $customer_sale_invoice = new Beans_Customer_Sale_Invoice($this->_beans_data_auth((object) array('id' => $this->_sale->id, 'date_billed' => $this->_date_billed, 'date_due' => $this->_date_due ? $this->_date_due : FALSE)));
         $customer_sale_invoice_result = $customer_sale_invoice->execute();
         // If it fails - we undo everything ( this was a single request, after all ).
         if (!$customer_sale_invoice_result->success) {
             $delete_sale = new Beans_Customer_Sale_Delete($this->_beans_data_auth((object) array('id' => $this->_sale->id)));
             $delete_sale_result = $delete_sale->execute();
             // TODO - 100PERCENTWORKING
             if (!$delete_sale_result->success) {
                 throw new Exception("Error creating account transaction for sale. COULD NOT DELETE SALE! " . $delete_sale_result->error);
             }
             throw new Exception("Error creating sale invoice: " . $customer_sale_invoice_result->error);
         }
         return $this->_beans_return_internal_result($customer_sale_invoice_result);
     }
     // We need to reload the sale so that we can get the correct balance, etc.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     return (object) array("sale" => $this->_return_customer_sale_element($this->_sale));
 }
Example #5
0
 protected function _execute()
 {
     if (!$this->_sale->loaded()) {
         throw new Exception("Sale could not be found.");
     }
     // There's a unique use-case that's hard to replicate, but it produces a form that
     // has no create_transaction - closing the FYE with this form can be frustrating to deal with otherwise.
     if ($this->_check_books_closed($this->_sale->date_created) && $this->_sale->create_transaction_id && $this->_sale->invoice_transaction_id && $this->_sale->cancel_transaction_id) {
         throw new Exception("Sale could not be updated.  The financial year has been closed already.");
     }
     $sale_original_total = $this->_sale->total;
     if ($this->_sale->date_cancelled) {
         throw new Exception("A sale cannot be updated after it has been cancelled.");
     }
     if (isset($this->_data->account_id)) {
         $this->_sale->account_id = $this->_data->account_id;
     }
     if (isset($this->_data->sent)) {
         $this->_sale->sent = $this->_data->sent;
     }
     if (isset($this->_data->date_created)) {
         $this->_sale->date_created = $this->_data->date_created;
     }
     if (isset($this->_data->sale_number)) {
         $this->_sale->code = $this->_data->sale_number;
     }
     if (isset($this->_data->order_number)) {
         $this->_sale->reference = $this->_data->order_number;
     }
     if (isset($this->_data->quote_number)) {
         $this->_sale->aux_reference = $this->_data->quote_number;
     }
     if (isset($this->_data->po_number)) {
         $this->_sale->alt_reference = $this->_data->po_number;
     }
     if (isset($this->_data->tax_exempt)) {
         $this->_sale->tax_exempt = $this->_data->tax_exempt ? TRUE : FALSE;
     }
     if (isset($this->_data->tax_exempt_reason)) {
         $this->_sale->tax_exempt_reason = ($this->_sale->tax_exempt and strlen($this->_data->tax_exempt_reason)) ? $this->_data->tax_exempt_reason : NULL;
     }
     if (isset($this->_data->billing_address_id)) {
         $this->_sale->billing_address_id = $this->_data->billing_address_id;
     }
     if (isset($this->_data->shipping_address_id)) {
         $this->_sale->shipping_address_id = $this->_data->shipping_address_id;
     }
     // Field that can be updated ONLY after being invoiced.
     if ($this->_sale->date_billed) {
         /*
         if( isset($this->_data->date_billed) AND 
         	strtotime($this->_data->date_billed) < strtotime($this->_sale->date_created) )
         	throw new Exception("Invalid invoice date: must be on or after the creation date of ".$this->_sale->date_created.".");
         */
         if (isset($this->_data->date_billed)) {
             $this->_sale->date_billed = $this->_data->date_billed;
         }
         if (isset($this->_data->date_due)) {
             $this->_sale->date_due = $this->_data->date_due;
         }
         if (strtotime($this->_sale->date_created) > strtotime($this->_sale->date_billed)) {
             $this->_sale->date_created = $this->_sale->date_billed;
         }
     }
     // Make sure we have good sale information before moving on.
     $this->_validate_customer_sale($this->_sale);
     $this->_sale->total = 0.0;
     $this->_sale->amount = 0.0;
     if (!isset($this->_data->lines) or !is_array($this->_data->lines) or !count($this->_data->lines)) {
         throw new Exception("Invalid sale lines: none provided.");
     }
     if (isset($this->_data->taxes)) {
         if (!is_array($this->_data->taxes)) {
             throw new Exception("Invalid sale taxes: must be an array.");
         }
         foreach ($this->_data->taxes as $sale_tax) {
             $new_sale_tax = $this->_default_form_tax();
             $new_sale_tax->tax_id = isset($sale_tax->tax_id) ? (int) $sale_tax->tax_id : NULL;
             if (!$new_sale_tax->tax_id) {
                 throw new Exception("Invalid sale tax ID: none provided.");
             }
             $tax = $this->_load_tax($new_sale_tax->tax_id);
             if (!$tax->loaded()) {
                 throw new Exception("Invalid sale tax ID: tax not found.");
             }
             $new_sale_tax->tax_percent = $tax->percent;
             $new_sale_tax->form_line_amount = 0.0;
             $new_sale_tax->form_line_taxable_amount = 0.0;
             $new_sale_tax->total = 0.0;
             if (!isset($this->_sale_taxes[$new_sale_tax->tax_id])) {
                 $this->_sale_taxes[$new_sale_tax->tax_id] = $new_sale_tax;
             }
         }
     }
     foreach ($this->_data->lines as $sale_line) {
         $new_sale_line = $this->_default_form_line();
         $new_sale_line->account_id = isset($sale_line->account_id) ? (int) $sale_line->account_id : NULL;
         $new_sale_line->tax_exempt = $this->_sale->tax_exempt || (isset($sale_line->tax_exempt) and $sale_line->tax_exempt) ? TRUE : FALSE;
         $new_sale_line->description = isset($sale_line->description) ? $sale_line->description : NULL;
         $new_sale_line->amount = isset($sale_line->amount) ? $this->_beans_round($sale_line->amount) : NULL;
         $new_sale_line->quantity = isset($sale_line->quantity) ? (double) $sale_line->quantity : NULL;
         // Handle Default Income Account
         if ($new_sale_line->account_id === NULL) {
             $new_sale_line->account_id = $this->_beans_setting_get('account_default_income');
         }
         $this->_validate_customer_sale_line($new_sale_line);
         $new_sale_line->total = $this->_beans_round($new_sale_line->amount * $new_sale_line->quantity);
         if (!$new_sale_line->tax_exempt) {
             foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
                 $this->_sale_taxes[$tax_id]->form_line_taxable_amount = $this->_beans_round($this->_sale_taxes[$tax_id]->form_line_taxable_amount + $new_sale_line->total);
             }
         }
         $this->_sale->amount = $this->_beans_round($this->_sale->amount + $new_sale_line->total);
         $this->_sale_lines[] = $new_sale_line;
     }
     $this->_sale->total = $this->_beans_round($this->_sale->total + $this->_sale->amount);
     foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
         $this->_sale_taxes[$tax_id]->total = $this->_beans_round($sale_tax->tax_percent * $sale_tax->form_line_taxable_amount);
         $this->_sale_taxes[$tax_id]->form_line_amount = $this->_sale->amount;
         $this->_sale->total = $this->_beans_round($this->_sale->total + $this->_sale_taxes[$tax_id]->total);
     }
     // Validate Totals
     if ($this->_sale->refund_form_id) {
         $refund_form = $this->_load_customer_sale($this->_sale->refund_form_id);
         $original_sale = $this->_sale;
         $refund_sale = $refund_form;
         if ($this->_sale->refund_form_id < $this->_sale->id) {
             $refund_sale = $this->_sale;
             $original_sale = $refund_form;
         }
         if ($original_sale->total > 0.0 and $refund_sale->total > 0.0 or $original_sale->total < 0.0 and $refund_sale->total < 0.0) {
             throw new Exception("Refund and original sale totals must offset each other ( they cannot both be positive or negative ).");
         }
         if (abs($refund_sale->total) > abs($original_sale->total)) {
             throw new Exception("The refund total cannot be greater than the original sale total.");
         }
     }
     // Delete Account Transaction
     if ($this->_sale->create_transaction->loaded()) {
         $account_transaction_delete = new Beans_Account_Transaction_Delete($this->_beans_data_auth((object) array('id' => $this->_sale->create_transaction_id, 'form_type_handled' => 'sale')));
         $account_transaction_delete_result = $account_transaction_delete->execute();
         if (!$account_transaction_delete_result->success) {
             throw new Exception("Error cancelling account transaction: " . $account_transaction_delete_result->error);
         }
         $this->_sale->create_transaction_id = NULL;
     }
     // Delete all current sale children.
     foreach ($this->_sale->form_lines->find_all() as $sale_line) {
         $sale_line->delete();
     }
     foreach ($this->_sale->form_taxes->find_all() as $sale_tax) {
         $sale_tax->delete();
     }
     // Save Sale + Children
     $this->_sale->save();
     foreach ($this->_sale_lines as $j => $sale_line) {
         $sale_line->form_id = $this->_sale->id;
         $sale_line->save();
     }
     foreach ($this->_sale_taxes as $tax_id => $sale_tax) {
         $this->_sale_taxes[$tax_id]->form_id = $this->_sale->id;
         $this->_sale_taxes[$tax_id]->save();
     }
     $this->_sale->save();
     $sale_calibrate = new Beans_Customer_Sale_Calibrate($this->_beans_data_auth((object) array('ids' => array($this->_sale->id))));
     $sale_calibrate_result = $sale_calibrate->execute();
     if (!$sale_calibrate_result->success) {
         throw new Exception("Error trying to create sale transactions: " . $sale_calibrate_result->error);
     }
     // Reload the sale.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     // Upon updating a sale, if the total has changed we change the sent flag.
     if ($this->_sale->total != $sale_original_total and !isset($this->_data->sent)) {
         $this->_sale->sent = FALSE;
     }
     $customer_payment_calibrate = new Beans_Customer_Payment_Calibrate($this->_beans_data_auth((object) array('form_ids' => array($this->_sale->id))));
     $customer_payment_calibrate_result = $customer_payment_calibrate->execute();
     if (!$customer_payment_calibrate_result->success) {
         throw new Exception("Error encountered when calibrating payments: " . $customer_payment_calibrate_result->error);
     }
     // Update tax items only if we're successful
     // AND
     // Only if we've billed this sales order.
     if ($this->_sale->date_billed) {
         $tax_item_action = 'invoice';
         if ($this->_sale->refund_form_id && $this->_sale->refund_form_id < $this->_sale->id) {
             $tax_item_action = 'refund';
         }
         $this->_update_form_tax_items($this->_sale->id, $tax_item_action);
     }
     // We need to reload the sale so that we can get the correct balance, etc.
     $this->_sale = $this->_load_customer_sale($this->_sale->id);
     return (object) array("sale" => $this->_return_customer_sale_element($this->_sale));
 }