protected function _execute() { if (!$this->_payment->loaded()) { throw new Exception("Payment could not be found."); } $form_ids_query = ' SELECT DISTINCT(account_transaction_forms.form_id) as form_id FROM account_transaction_forms ' . ' INNER JOIN account_transactions ON account_transaction_forms.account_transaction_id = account_transactions.id WHERE ' . ' account_transactions.transaction_id = ' . $this->_payment->id; $form_ids = DB::Query(Database::SELECT, $form_ids_query)->execute()->as_array(); $handled_sales_ids = array(); foreach ($form_ids as $form_id) { $handled_sales_ids[] = $form_id['form_id']; } $payment_date = $this->_payment->date; $payment_id = $this->_payment->id; // Try to cancel payment. $account_transaction_delete = new Beans_Account_Transaction_Delete($this->_beans_data_auth((object) array('id' => $this->_payment->id, 'payment_type_handled' => 'customer'))); $account_transaction_delete_result = $account_transaction_delete->execute(); if (!$account_transaction_delete_result->success) { throw new Exception("Error cancelling payment: " . $account_transaction_delete_result->error); } // Recalibrate Customer Invoices / Cancellations $customer_sale_calibrate_invoice = new Beans_Customer_Sale_Calibrate_Invoice($this->_beans_data_auth((object) array('ids' => $handled_sales_ids))); $customer_sale_calibrate_invoice_result = $customer_sale_calibrate_invoice->execute(); if (!$customer_sale_calibrate_invoice_result->success) { throw new Exception("UNEXPECTED ERROR: COULD NOT CALIBRATE CUSTOMER SALES: " . $customer_sale_calibrate_invoice_result->error); } // Recalibrate Customer Invoices / Cancellations $customer_sale_calibrate_cancel = new Beans_Customer_Sale_Calibrate_Cancel($this->_beans_data_auth((object) array('ids' => $handled_sales_ids))); $customer_sale_calibrate_cancel_result = $customer_sale_calibrate_cancel->execute(); if (!$customer_sale_calibrate_cancel_result->success) { throw new Exception("UNEXPECTED ERROR: COULD NOT CALIBRATE CUSTOMER SALES: " . $customer_sale_calibrate_cancel_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('form_ids' => $handled_sales_ids))); $customer_payment_calibrate_result = $customer_payment_calibrate->execute(); return (object) array("success" => TRUE, "auth_error" => "", "error" => "", "data" => (object) array()); }
protected function _execute() { if (!$this->_transaction_sale_account_id) { throw new Exception("INTERNAL ERROR: Could not find default SO receivable account."); } if (!$this->_transaction_sale_line_account_id) { throw new Exception("INTERNAL ERROR: Could not find default SO line account."); } if (!$this->_transaction_sale_tax_account_id) { throw new Exception("INTERNAL ERROR: Could not find default SO tax account."); } if (!$this->_transaction_sale_deferred_income_account_id) { throw new Exception("INTERNAL ERROR: Could not find default SO deferred income account."); } if (!$this->_transaction_sale_deferred_liability_account_id) { throw new Exception("INTERNAL ERROR: Could not find default SO deferred liability account."); } // Check for some basic data. if (!isset($this->_data->deposit_account_id)) { throw new Exception("Invalid payment deposit account ID: none provided."); } $deposit_account = $this->_load_account($this->_data->deposit_account_id); if (!$deposit_account->loaded()) { throw new Exception("Invalid payment deposit account ID: not found."); } if (!$deposit_account->deposit) { throw new Exception("Invalid payment deposit account ID: account must be marked as deposit."); } if (!$this->_data->amount) { throw new Exception("Invalid payment amount: none provided."); } // Formulate data request object for Beans_Account_Transaction_Create $create_transaction_data = new stdClass(); $create_transaction_data->code = isset($this->_data->number) ? $this->_data->number : NULL; $create_transaction_data->description = isset($this->_data->description) ? $this->_data->description : NULL; $create_transaction_data->description = "Customer Payment Recorded" . ($create_transaction_data->description ? ': ' . $create_transaction_data->description : ''); $create_transaction_data->date = isset($this->_data->date) ? $this->_data->date : NULL; $create_transaction_data->payment = "customer"; $sale_account_transfers = array(); $sale_account_transfers_forms = array(); $writeoff_account_transfer_total = 0.0; $writeoff_account_transfers_forms = array(); if (!$this->_data->sales or !count($this->_data->sales)) { throw new Exception("Please provide at least one sale for this payment."); } $handled_sales_ids = array(); foreach ($this->_data->sales as $sale_payment) { if (!isset($sale_payment->sale_id) or !$sale_payment->sale_id) { throw new Exception("Invalid payment sale ID: none provided."); } $sale = $this->_load_customer_sale($sale_payment->sale_id); if (!$sale->loaded()) { throw new Exception("Invalid payment sale: sale not found."); } if (!$sale_payment->amount) { throw new Exception("Invalid payment sale amount: none provided."); } if (in_array($sale->id, $handled_sales_ids)) { throw new Exception("Invalid payment sale: sale " . $sale->code . " cannot be in payment more than once."); } $handled_sales_ids[] = $sale->id; $sale_id = $sale->id; // Get the sale total, tax total, and balance as of the payment date. $sale_line_total = $sale->amount; $sale_tax_total = $this->_beans_round($sale->total - $sale->amount); $sale_balance = $this->_get_form_effective_balance($sale, $create_transaction_data->date, NULL); // This makes the math a bit easier to read. $sale_paid = $sale->total + $sale_balance; // Money Received / Paid = Bank $sale_transfer_amount = $sale_payment->amount; $sale_writeoff_amount = (isset($sale_payment->writeoff_balance) and $sale_payment->writeoff_balance) ? $this->_beans_round(0.0 - $sale_balance - $sale_transfer_amount) : FALSE; // AR Adjustment $sale_payment_amount = $sale_writeoff_amount ? $this->_beans_round($sale_transfer_amount + $sale_writeoff_amount) : $sale_transfer_amount; // Another variable to simplify code. $sale_transaction_account_id = FALSE; if ($sale->date_billed and $sale->invoice_transaction_id and strtotime($sale->date_billed) <= strtotime($create_transaction_data->date) or $sale->date_cancelled and $sale->cancel_transaction_id and strtotime($sale->date_cancelled) <= strtotime($create_transaction_data->date)) { // Sale AR $sale_transaction_account_id = $sale->account_id; if (!isset($sale_account_transfers[$sale_transaction_account_id])) { $sale_account_transfers[$sale_transaction_account_id] = 0.0; } $sale_account_transfers[$sale_transaction_account_id] = $this->_beans_round($sale_account_transfers[$sale_transaction_account_id] + $sale_payment_amount); if (!isset($sale_account_transfers_forms[$sale_transaction_account_id])) { $sale_account_transfers_forms[$sale_transaction_account_id] = array(); } $sale_account_transfers_forms[$sale_transaction_account_id][] = (object) array("form_id" => $sale_id, "amount" => $sale_payment_amount, "writeoff_amount" => $sale_writeoff_amount ? $sale_writeoff_amount : NULL); } else { $deferred_amounts = $this->_calculate_deferred_payment($sale_payment_amount, $sale_paid, $sale_line_total, $sale_tax_total); $income_transfer_amount = $deferred_amounts->income_transfer_amount; $tax_transfer_amount = $deferred_amounts->tax_transfer_amount; if ($income_transfer_amount) { if (!isset($sale_account_transfers[$this->_transaction_sale_deferred_income_account_id])) { $sale_account_transfers[$this->_transaction_sale_deferred_income_account_id] = 0.0; } if (!isset($sale_account_transfers[$this->_transaction_sale_line_account_id])) { $sale_account_transfers[$this->_transaction_sale_line_account_id] = 0.0; } $sale_account_transfers[$this->_transaction_sale_deferred_income_account_id] = $this->_beans_round($sale_account_transfers[$this->_transaction_sale_deferred_income_account_id] + $income_transfer_amount); $sale_account_transfers[$this->_transaction_sale_line_account_id] = $this->_beans_round($sale_account_transfers[$this->_transaction_sale_line_account_id] - $income_transfer_amount); } if ($tax_transfer_amount) { if (!isset($sale_account_transfers[$this->_transaction_sale_deferred_liability_account_id])) { $sale_account_transfers[$this->_transaction_sale_deferred_liability_account_id] = 0.0; } if (!isset($sale_account_transfers[$this->_transaction_sale_tax_account_id])) { $sale_account_transfers[$this->_transaction_sale_tax_account_id] = 0.0; } $sale_account_transfers[$this->_transaction_sale_deferred_liability_account_id] = $this->_beans_round($sale_account_transfers[$this->_transaction_sale_deferred_liability_account_id] + $tax_transfer_amount); $sale_account_transfers[$this->_transaction_sale_tax_account_id] = $this->_beans_round($sale_account_transfers[$this->_transaction_sale_tax_account_id] - $tax_transfer_amount); } if (!isset($sale_account_transfers[$this->_transaction_sale_account_id])) { $sale_account_transfers[$this->_transaction_sale_account_id] = 0.0; } $sale_account_transfers[$this->_transaction_sale_account_id] = $this->_beans_round($sale_account_transfers[$this->_transaction_sale_account_id] + $sale_payment_amount); if (!isset($sale_account_transfers_forms[$this->_transaction_sale_account_id])) { $sale_account_transfers_forms[$this->_transaction_sale_account_id] = array(); } $sale_account_transfers_forms[$this->_transaction_sale_account_id][] = (object) array("form_id" => $sale_id, "amount" => $sale_payment_amount, "writeoff_amount" => $sale_writeoff_amount ? $sale_writeoff_amount : NULL); } if ($sale_writeoff_amount) { $writeoff_account_transfer_total = $this->_beans_round($writeoff_account_transfer_total + $sale_writeoff_amount); } } $writeoff_account = FALSE; if ($writeoff_account_transfer_total != 0.0) { // We need a write-off account. if (!isset($this->_data->writeoff_account_id) or !$this->_data->writeoff_account_id) { throw new Exception("Invalid payment write-off account ID: none provided."); } $writeoff_account = $this->_load_account($this->_data->writeoff_account_id); if (!$writeoff_account->loaded()) { throw new Exception("Invalid payment write-off account ID: account not found."); } if (!$writeoff_account->writeoff) { throw new Exception("Invalid payment write-off account ID: account must be marked as write-off."); } if (isset($sale_account_transfers[$writeoff_account->id])) { throw new Exception("Invalid payment write-off account ID: account cannot be tied to any other transaction in the payment."); } $sale_account_transfers[$writeoff_account->id] = $writeoff_account_transfer_total; $sale_account_transfers_forms[$writeoff_account->id] = $writeoff_account_transfers_forms; } $adjustment_account = FALSE; if (isset($this->_data->adjustment_amount) and $this->_data->adjustment_amount != 0.0) { if (!isset($this->_data->adjustment_account_id) or !$this->_data->adjustment_account_id) { throw new Exception("Invalid adjustment account ID: none provided."); } $adjustment_account = $this->_load_account($this->_data->adjustment_account_id); if (!$adjustment_account->loaded()) { throw new Exception("Invalid adjustment account ID: account not found."); } if (isset($sale_account_transfers[$adjustment_account->id])) { throw new Exception("Invalid adjustment account ID: account cannot be tied to any other transaction in the payment."); } $sale_account_transfers[$adjustment_account->id] = $this->_data->adjustment_amount * -1; } // All of the accounts on sales are Accounts receivable and should be assets. // But to be on the safe side we're going to do table sign adjustments. foreach ($sale_account_transfers as $account_id => $transfer_amount) { $account = $this->_load_account($account_id); if (!$account->loaded()) { throw new Exception("System error: could not load account with ID " . $account_id); } $sale_account_transfers[$account_id] = ($writeoff_account and $writeoff_account->id == $account_id or $adjustment_account and $adjustment_account->id == $account_id) ? $transfer_amount * $deposit_account->account_type->table_sign : $transfer_amount * -1 * $deposit_account->account_type->table_sign; } // V2Item - Validate amount to avoid funky non-zero errors. $sale_account_transfers[$deposit_account->id] = $this->_data->amount * $deposit_account->account_type->table_sign; $create_transaction_data->account_transactions = array(); foreach ($sale_account_transfers as $account_id => $amount) { $account_transaction = new stdClass(); $account_transaction->account_id = $account_id; $account_transaction->amount = $amount; if ($account_transaction->account_id == $deposit_account->id) { $account_transaction->transfer = TRUE; } if ($writeoff_account and $account_transaction->account_id == $writeoff_account->id) { $account_transaction->writeoff = TRUE; } if ($adjustment_account and $account_transaction->account_id == $adjustment_account->id) { $account_transaction->adjustment = TRUE; } if (isset($sale_account_transfers_forms[$account_id])) { $account_transaction->forms = array(); foreach ($sale_account_transfers_forms[$account_id] as $form) { $account_transaction->forms[] = (object) array('form_id' => $form->form_id, 'amount' => $form->amount, 'writeoff_amount' => $form->writeoff_amount); } } $create_transaction_data->account_transactions[] = $account_transaction; } if ($this->_validate_only) { $create_transaction_data->validate_only = TRUE; } $create_transaction = new Beans_Account_Transaction_Create($this->_beans_data_auth($create_transaction_data)); $create_transaction_result = $create_transaction->execute(); if (!$create_transaction_result->success) { throw new Exception("An error occurred creating that payment: " . $create_transaction_result->error); } if ($this->_validate_only) { return (object) array(); } // Recalibrate Customer Invoices / Cancellations $customer_sale_calibrate_invoice = new Beans_Customer_Sale_Calibrate_Invoice($this->_beans_data_auth((object) array('ids' => $handled_sales_ids))); $customer_sale_calibrate_invoice_result = $customer_sale_calibrate_invoice->execute(); if (!$customer_sale_calibrate_invoice_result->success) { throw new Exception("UNEXPECTED ERROR: COULD NOT CALIBRATE CUSTOMER SALES: " . $customer_sale_calibrate_invoice_result->error); } // Recalibrate Customer Invoices / Cancellations $customer_sale_calibrate_cancel = new Beans_Customer_Sale_Calibrate_Cancel($this->_beans_data_auth((object) array('ids' => $handled_sales_ids))); $customer_sale_calibrate_cancel_result = $customer_sale_calibrate_cancel->execute(); if (!$customer_sale_calibrate_cancel_result->success) { throw new Exception("UNEXPECTED ERROR: COULD NOT CALIBRATE CUSTOMER SALES: " . $customer_sale_calibrate_cancel_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('form_ids' => $handled_sales_ids, 'after_payment_id' => $create_transaction_result->data->transaction->id))); $customer_payment_calibrate_result = $customer_payment_calibrate->execute(); if (!$customer_payment_calibrate_result->success) { throw new Exception("UNEXPECTED ERROR: COULD NOT CALIBRATE CUSTOMER PAYMENTS: " . $customer_sale_calibrate_result->error); } return (object) array("payment" => $this->_return_customer_payment_element($this->_load_customer_payment($create_transaction_result->data->transaction->id))); }
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(); } }
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)); }
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)); }
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)); }