/** * Calculates the prices of deliverables * * and adds them up to the salesproject value */ function calculate_price() { $value = 0; $cost = 0; $deliverable_qb = org_openpsa_sales_salesproject_deliverable_dba::new_query_builder(); $deliverable_qb->add_constraint('salesproject', '=', $this->id); $deliverable_qb->add_constraint('up', '=', 0); $deliverable_qb->add_constraint('state', '<>', org_openpsa_sales_salesproject_deliverable_dba::STATUS_DECLINED); $deliverables = $deliverable_qb->execute(); foreach ($deliverables as $deliverable) { if ($deliverable->orgOpenpsaObtype == org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION) { $scheduler = new org_openpsa_invoices_scheduler($deliverable); if ($deliverable->end == 0) { // FIXME: Get this from config key 'subscription_profit_months' $cycles = $scheduler->calculate_cycles(12); } else { $cycles = $scheduler->calculate_cycles(); } $value = $value + $deliverable->price * $cycles; $cost = $cost + $deliverable->cost * $cycles; } else { $value = $value + $deliverable->price; $cost = $cost + $deliverable->cost; } } $profit = $value - $cost; if ($this->value != $value || $this->profit != $profit) { $this->value = $value; $this->profit = $value - $cost; $this->update(); } }
/** * Manually trigger a subscription cycle run. */ private function _run_cycle() { if (empty($_POST['at_entry'])) { throw new midcom_error('No AT entry specified'); } $entry = new midcom_services_at_entry_dba($_POST['at_entry']); $deliverable = new org_openpsa_sales_salesproject_deliverable_dba($entry->arguments['deliverable']); $scheduler = new org_openpsa_invoices_scheduler($deliverable); if (!$scheduler->run_cycle($entry->arguments['cycle'])) { throw new midcom_error('Failed to run cycle, see debug log for details'); } if (!$entry->delete()) { throw new midcom_error('Could not delete AT entry: ' . midcom_connection::get_error_string()); } }
private function _get_invoices_for_subscription($deliverable, $at_entry) { if ($deliverable->invoiceByActualUnits && $at_entry->arguments['cycle'] > 1) { $invoice_sum = $deliverable->invoiced / ($at_entry->arguments['cycle'] - 1); if ($invoice_sum == 0) { return array(); } $calculation_base = sprintf($this->_l10n->get('average of %s runs'), $at_entry->arguments['cycle'] - 1); } else { $invoice_sum = $deliverable->price; $calculation_base = $this->_l10n->get('fixed price'); } $salesproject = org_openpsa_sales_salesproject_dba::get_cached($deliverable->salesproject); $scheduler = new org_openpsa_invoices_scheduler($deliverable); $invoices = array(); $time = $at_entry->start; while ($time < $this->_request_data['end'] && ($time < $deliverable->end || $deliverable->continuous)) { $invoice = new org_openpsa_invoices_invoice_dba(); $invoice->customer = $salesproject->customer; $invoice->customerContact = $salesproject->customerContact; $invoice->owner = $salesproject->owner; $invoice->sum = $invoice_sum; $invoice->sent = $time; $invoice->due = $invoice->get_default('due') * 3600 * 24 + $time; $invoice->vat = $invoice->get_default('vat'); $invoice->description = $deliverable->title . ' (' . $calculation_base . ')'; if ($this->_sales_url) { $invoice->description = '<a href="' . $this->_sales_url . 'deliverable/' . $deliverable->guid . '/">' . $invoice->description . '</a>'; } $invoice->paid = $invoice->due; $invoices[] = $invoice; if (!($time = $scheduler->calculate_cycle_next($time))) { debug_add('Failed to calculate timestamp for next cycle, exiting', MIDCOM_LOG_WARN); break; } } return $invoices; }
/** * * @param mixed $handler_id The ID of the handler. * @param array &$data The local request data. */ public function _show_generator($handler_id, array &$data) { midcom_show_style('sales_report-deliverable-start'); // Quick workaround to Bergies lazy determination of whether this is user's or everyone's report... if ($this->_request_data['query_data']['resource'] == 'user:'******'auth')->user->guid) { // My report $data['handler_id'] = 'deliverable_report'; } else { // Generic report $data['handler_id'] = 'sales_report'; } /*** Copied from sales/handler/deliverable/report.php ***/ midcom_show_style('sales_report-deliverable-header'); $invoices_node = midcom_helper_misc::find_node_by_component('org.openpsa.invoices'); $sums_per_person = array(); $sums_all = array('price' => 0, 'cost' => 0, 'profit' => 0); $odd = true; foreach ($data['invoices'] as $deliverable_guid => $invoices) { if (count($invoices) == 0) { // No invoices sent in this project, skip continue; } try { $deliverable = org_openpsa_sales_salesproject_deliverable_dba::get_cached($deliverable_guid); $product = org_openpsa_products_product_dba::get_cached($deliverable->product); $salesproject = org_openpsa_sales_salesproject_dba::get_cached($deliverable->salesproject); $customer = midcom_db_group::get_cached($salesproject->customer); } catch (midcom_error $e) { continue; } if (!array_key_exists($salesproject->owner, $sums_per_person)) { $sums_per_person[$salesproject->owner] = array('price' => 0, 'cost' => 0, 'profit' => 0); } // Calculate the price and cost from invoices $invoice_price = 0; $data['invoice_string'] = ''; $invoice_cycle_numbers = array(); foreach ($invoices as $invoice) { $invoice_price += $invoice->sum; $invoice_class = $invoice->get_status(); if ($invoices_node) { $invoice_label = "<a class=\"{$invoice_class}\" href=\"{$invoices_node[MIDCOM_NAV_FULLURL]}invoice/{$invoice->guid}/\">" . $invoice->get_label() . "</a>"; } else { $invoice_label = $invoice->get_label(); } if ($product->delivery == org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION) { $invoice_cycle_numbers[] = (int) $invoice->parameter('org.openpsa.sales', 'cycle_number'); } $data['invoice_string'] .= "<li class=\"{$invoice_class}\">{$invoice_label}</li>\n"; } if ($product->delivery == org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION) { // This is a subscription, it should be shown only if it is the first invoice if (!in_array(1, $invoice_cycle_numbers)) { continue; // This will skip to next deliverable } $scheduler = new org_openpsa_invoices_scheduler($deliverable); if ($deliverable->end == 0) { // Subscription doesn't have an end date, use specified amount of months for calculation $cycles = $scheduler->calculate_cycles($this->_config->get('subscription_profit_months')); $data['calculation_basis'] = sprintf($data['l10n']->get('%s cycles in %s months'), $cycles, $this->_config->get('subscription_profit_months')); } else { $cycles = $scheduler->calculate_cycles(); $data['calculation_basis'] = sprintf($data['l10n']->get('%s cycles, %s - %s'), $cycles, strftime('%x', $deliverable->start), strftime('%x', $deliverable->end)); } $price = $deliverable->price * $cycles; $cost = $deliverable->cost * $cycles; } else { // This is a single delivery, calculate cost as percentage as it may be invoiced in pieces if ($deliverable->price) { $cost_percentage = 100 / $deliverable->price * $invoice_price; $cost = $deliverable->cost / 100 * $cost_percentage; } else { $cost_percentage = 100; $cost = $deliverable->cost; } $price = $invoice_price; $data['calculation_basis'] = sprintf($data['l10n']->get('%s%% of %s'), round($cost_percentage), $deliverable->price); } // And now just count the profit $profit = $price - $cost; $data['customer'] = $customer; $data['salesproject'] = $salesproject; $data['deliverable'] = $deliverable; $data['price'] = $price; $sums_per_person[$salesproject->owner]['price'] += $price; $sums_all['price'] += $price; $data['cost'] = $cost; $sums_per_person[$salesproject->owner]['cost'] += $cost; $sums_all['cost'] += $cost; $data['profit'] = $profit; $sums_per_person[$salesproject->owner]['profit'] += $profit; $sums_all['profit'] += $profit; if ($odd) { $data['row_class'] = ''; $odd = false; } else { $data['row_class'] = ' class="even"'; $odd = true; } midcom_show_style('sales_report-deliverable-item'); } $data['sums_per_person'] = $sums_per_person; $data['sums_all'] = $sums_all; midcom_show_style('sales_report-deliverable-footer'); /*** /Copied from sales/handler/deliverable/report.php ***/ midcom_show_style('sales_report-deliverable-end'); }
public function testCreate_task() { $organization = $this->create_object('org_openpsa_contacts_group_dba'); $manager = $this->create_object('midcom_db_person'); $member = $this->create_object('midcom_db_person'); $group = $this->create_object('org_openpsa_products_product_group_dba'); $product_attributes = array('productGroup' => $group->id, 'code' => 'TEST-' . __CLASS__ . time()); $product = $this->create_object('org_openpsa_products_product_dba', $product_attributes); $salesproject_attributes = array('owner' => $manager->id, 'customer' => $organization->id); $salesproject = $this->create_object('org_openpsa_sales_salesproject_dba', $salesproject_attributes); $member_attributes = array('person' => $member->id, 'objectGuid' => $salesproject->guid, 'role' => ORG_OPENPSA_OBTYPE_SALESPROJECT_MEMBER); $this->create_object('org_openpsa_contacts_role_dba', $member_attributes); $deliverable_attributes = array('salesproject' => $salesproject->id, 'product' => $product->id, 'description' => 'TEST DESCRIPTION', 'plannedUnits' => 15); $deliverable = $this->create_object('org_openpsa_sales_salesproject_deliverable_dba', $deliverable_attributes); $start = time(); $end = $start + 30 * 24 * 60 * 60; $title = 'TEST TITLE'; $start_cmp = mktime(0, 0, 0, date('n', $start), date('j', $start), date('Y', $start)); $end_cmp = mktime(23, 59, 59, date('n', $end), date('j', $end), date('Y', $end)); $scheduler = new org_openpsa_invoices_scheduler($deliverable); midcom::get('auth')->request_sudo('org.openpsa.invoices'); $task = $scheduler->create_task($start, $end, $title); $this->assertTrue(is_a($task, 'org_openpsa_projects_task_dba')); $this->register_object($task); $this->assertEquals($deliverable->id, $task->agreement); $this->assertEquals($salesproject->customer, $task->customer); $this->assertEquals($title, $task->title); $this->assertEquals($deliverable->description, $task->description); $this->assertEquals($start_cmp, $task->start); $this->assertEquals($end_cmp, $task->end); $this->assertEquals($deliverable->plannedUnits, $task->plannedHours); $this->assertEquals($salesproject->owner, $task->manager); $this->assertTrue($task->hoursInvoiceableDefault); $mc = org_openpsa_relatedto_dba::new_collector('fromGuid', $task->guid); $mc->add_value_property('toGuid'); $mc->execute(); $keys = $mc->list_keys(); $this->assertEquals(1, sizeof($keys)); $product_guid = $mc->get_subkey(key($keys), 'toGuid'); $this->assertEquals($product->guid, $product_guid); $salesproject->get_members(); $task->get_members(); $this->assertEquals($salesproject->contacts, $task->contacts); $project = new org_openpsa_projects_project($task->project); $this->assertTrue(!empty($project->guid)); $this->register_object($project); $project->get_members(); $this->assertEquals($salesproject->contacts, $project->contacts); $this->assertEquals($salesproject->owner, $project->manager); $task->priority = 4; $task->manager = $member->id; $task->update(); $task->add_members('resources', array($member->id)); $task->refresh(); $task2 = $scheduler->create_task($start, $end, $title, $task); $this->register_object($task2); $task2->get_members(); $task->get_members(); $this->assertEquals(4, $task2->priority); $this->assertEquals($member->id, $task2->manager); $this->assertEquals($task->resources, $task2->resources); midcom::get('auth')->drop_sudo(); }
function order() { if ($this->state >= org_openpsa_sales_salesproject_deliverable_dba::STATUS_ORDERED) { return false; } // Cache the original cost values intended and reset the fields $this->plannedUnits = $this->units; $this->plannedCost = $this->cost; if ($this->invoiceByActualUnits) { $this->cost = 0; $this->units = 0; } // Check what kind of order this is $product = org_openpsa_products_product_dba::get_cached($this->product); $scheduler = new org_openpsa_invoices_scheduler($this); if ($product->delivery == org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION) { // This is a new subscription, initiate the cycle but don't send invoice if (!$scheduler->run_cycle(1, false)) { return false; } } else { // Check if we need to create task or ship goods switch ($product->orgOpenpsaObtype) { case org_openpsa_products_product_dba::TYPE_SERVICE: $scheduler->create_task($this->start, $this->end, $this->title); break; case org_openpsa_products_product_dba::TYPE_GOODS: // TODO: Warehouse management: create new order // TODO: Warehouse management: create new order default: break; } } $this->state = org_openpsa_sales_salesproject_deliverable_dba::STATUS_ORDERED; if ($this->update()) { // Update sales project and mark as won $salesproject = new org_openpsa_sales_salesproject_dba($this->salesproject); if ($salesproject->status != org_openpsa_sales_salesproject_dba::STATUS_WON) { $salesproject->status = org_openpsa_sales_salesproject_dba::STATUS_WON; $salesproject->update(); } return true; } return false; }
/** * @depends testRun_cycle */ public function testRun_cycle_multiple() { midcom::get('auth')->request_sudo('org.openpsa.invoices'); $deliverable_attributes = array('salesproject' => $this->_salesproject->id, 'product' => $this->_product->id, 'description' => 'TEST DESCRIPTION 2', 'pricePerUnit' => 10, 'plannedUnits' => 15, 'units' => 10, 'invoiceByActualUnits' => true, 'state' => org_openpsa_sales_salesproject_deliverable_dba::STATUS_STARTED, 'start' => strtotime('2010-02-02 00:00:00')); $deliverable2 = $this->create_object('org_openpsa_sales_salesproject_deliverable_dba', $deliverable_attributes); $task_attributes = array('project' => $this->_project->id, 'agreement' => $deliverable2->id, 'title' => 'TEST TITLE 2', 'reportedHours' => 10); $task2 = $this->create_object('org_openpsa_projects_task_dba', $task_attributes); $this->_product->delivery = org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION; $this->_product->update(); $this->_deliverable->start = strtotime('2010-02-02 00:00:00'); $this->_deliverable->continuous = true; $this->_deliverable->invoiceByActualUnits = false; $this->_deliverable->pricePerUnit = 10; $this->_deliverable->plannedUnits = 10; $this->_deliverable->state = org_openpsa_sales_salesproject_deliverable_dba::STATUS_STARTED; $this->_deliverable->update(); $scheduler = new org_openpsa_invoices_scheduler($this->_deliverable); $stat = $scheduler->run_cycle(1, true); $this->assertTrue($stat); $scheduler = new org_openpsa_invoices_scheduler($deliverable2); $stat = $scheduler->run_cycle(1, true); $this->assertTrue($stat); $qb = org_openpsa_invoices_invoice_item_dba::new_query_builder(); $qb->add_constraint('deliverable', '=', $this->_deliverable->id); $results = $qb->execute(); $this->assertEquals(1, sizeof($results)); $item1 = $results[0]; $this->register_object($item1); $qb = org_openpsa_invoices_invoice_item_dba::new_query_builder(); $qb->add_constraint('deliverable', '=', $deliverable2->id); $results = $qb->execute(); $this->assertEquals(1, sizeof($results)); $item2 = $results[0]; $this->register_object($item2); $this->assertEquals($item1->invoice, $item2->invoice); $this->assertEquals($this->_deliverable->id, $item1->deliverable); $this->assertEquals($deliverable2->id, $item2->deliverable); $invoice = new org_openpsa_invoices_invoice_dba($item2->invoice); $this->register_object($invoice); $this->assertEquals(200, $invoice->sum); $this->assertEquals(100, $deliverable2->invoiced); midcom::get('auth')->drop_sudo(); }
/** * AT handler for handling subscription cycles. * * @param array $args handler arguments * @param object &$handler reference to the cron_handler object calling this method. * @return boolean indicating success/failure */ function new_subscription_cycle($args, &$handler) { if (!isset($args['deliverable']) || !isset($args['cycle'])) { $msg = 'deliverable GUID or cycle number not set, aborting'; $handler->print_error($msg); debug_add($msg, MIDCOM_LOG_ERROR); return false; } try { $deliverable = new org_openpsa_sales_salesproject_deliverable_dba($args['deliverable']); } catch (midcom_error $e) { $msg = "Deliverable {$args['deliverable']} not found, error " . midcom_connection::get_error_string(); $handler->print_error($msg); debug_add($msg, MIDCOM_LOG_ERROR); return false; } $scheduler = new org_openpsa_invoices_scheduler($deliverable); return $scheduler->run_cycle($args['cycle']); }