array(), 'module'=>array(), 'product'=>array(), 'service'=>array(), ); protected $_has_many = array( 'tax'=>array('model'=>'Invoice_Item_Tax','far_key'=>'id') ); protected $_display_filters = array( 'date_orig'=>array( array('Site::Date',array(':value')), ), 'date_start'=>array( array('Site::Date',array(':value')), ), 'date_stop'=>array( array('Site::Date',array(':value')), ), 'recurring_schedule'=>array( array('StaticList_RecurSchedule::get',array(':value')), ), ); // Items belonging to an invoice item protected $_sub_items_load = array( 'tax'=>array(), ); /** * Invoice Item Types * * @see isValid() */ private $_item_type = array( 0=>'Product/Service', // * Line Charge Topic on Invoice. 1=>'Hardware', // * 2=>'Service Relocation Fee', // * Must have corresponding SERVICE_ID 3=>'Service Change', // * Must have corresponding SERVICE_ID 4=>'Service Connection', // * Must have corresponding SERVICE_ID 5=>'Excess Usage', // * Excess Service Item, of item 0, must have corresponding SERVICE_ID 6=>'Service Cancellation', // * Must have corresponding SERVICE_ID 7=>'Extra Product/Service Charge', // * Service Billing in advance, Must have corresponding SERVICE_ID 8=>'Product Addition', // * Additional Product Customisation, Must have corresponding SERVICE_ID 120=>'Credit/Debit Transfer', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL : INVOICE_ID is NOT NULL 123=>'Shipping', 124=>'Late Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, 125=>'Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, MODULE_REF = CHECKOUT NAME 126=>'Other', // * MODEL_ID should be a module 127=>'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL ); /** REQUIRED ABSTRACT METHODS **/ /** LOCAL METHODS **/ /** * Module assocated with the invoice item */ private function _module() { if (! $this->module_id OR ! $this->module_ref) return NULL; return $this->module->instance($this->module_ref); } /** * Return the list of available item types */ public function _types() { return $this->_item_type; } /** * Add tax to our item * * @param Model_Country the country to get the tax rates * @param Boolean Is this price inc/ex tax */ public function add_sub_item(Model_Country $co,$taxed=FALSE) { $orig = $this->subtotal(); $tax = 0; foreach ($co->tax->find_all() as $to) { $iito = ORM::factory('Invoice_Item_Tax'); $iito->tax_id = $to->id; $iito->amount = Currency::round($taxed ? ($orig-$orig/(1+$to->rate)) : $orig*$to->rate); $tax += $iito->amount; $this->subitem_add($iito); } // If taxed, we need to reduce our base_rate to a pre-tax amount if ($taxed) { $this->price_base -= Currency::round($tax/$this->quantity,1); // If there is any rounding, we'll take it off the last IITO $iito->amount += $orig-$this->total(); } } // The total of all discounts public function discount() { return Currency::round($this->discount_amt); } /** * Validate that this record is correct */ private function isValid() { switch ($this->item_type) { case 0: // When a charge is charging normal service fees, this fails. if (is_object($this->module_id)) $this->module_id = $this->module_id->id; try{ // @todo Validate if module_id = charge and module_ref corresponds with the line item if (! $this->service_id OR ! $this->product_id OR ($this->module_id AND $this->module_id != 30 AND ! is_null($this->module_ref)) OR $this->product_name OR is_null($this->recurring_schedule)) return FALSE; } catch (Exception $e) { echo Debug::vars($this->dump()); } break; case 1: return TRUE; if (! $this->product_id OR $this->module_id OR $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) return FALSE; break; // These items come from the charge module, so we should have a charge module_id and ref case 2: try{ if (! $this->service_id OR ! $this->product_id OR ($this->module_id->id AND $this->module_id->id != 32 AND ! is_null($this->module_ref)) OR $this->product_name OR ! is_null($this->recurring_schedule)) return FALSE; } catch (Exception $e) { echo Debug::vars($this->dump()); die(); } break; case 5: case 4: case 7: if (! $this->service_id OR ! $this->product_id OR ! $this->module_id OR ! $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) return FALSE; break; // These items come from the charge module, so we should have a charge module_id and ref case 5: if (! $this->service_id OR ! $this->product_id OR ! $this->module_id OR ! $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) return FALSE; break; case 124: if ($this->service_id OR $this->product_id OR $this->module_id OR $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) return FALSE; break; } // If we get here, assume it is valid return TRUE; } /** * Return an instance of the Model of this charge */ public function module() { return $this->_module(); } public function name($variable=NULL) { if (! $this->isValid()) return sprintf('Record Error [%s-%s]',$this->item_type,$this->id); if (is_null($variable) OR ! $variable instanceof Model_Language) $variable = Site::language(); switch ($this->item_type) { case 0: case 2: case 3: case 4: case 5: return sprintf('%s: %s',$this->product->name($variable),$this->service->namesub($variable)); case 124: return StaticList_ItemType::get($this->item_type); default: return sprintf('Unknown [%s-%s]',$this->item_type,$this->id); } } public function namesub($variable=NULL) { if (! $this->isValid()) return sprintf('Record Error [%s-%s]',$this->item_type,$this->id); switch ($this->item_type) { case 0: return sprintf('%s: %s',StaticList_ItemType::get($this->item_type),$this->period()); case 1: return StaticList_ItemType::get($this->item_type); case 2: return sprintf('%s: %s',StaticList_ItemType::get($this->item_type),$this->_module() ? $this->_module()->display('date_charge') : $this->period()); case 3: case 4: case 6: return sprintf('%s: %s',StaticList_ItemType::get($this->item_type),($this->_module()->attributes ? $this->_module()->namesub($variable) : $this->_module()->display('date_charge'))); case 5: case 7: return $this->_module()->namesub($variable); case 123: return StaticList_ItemType::get($this->item_type); default: return sprintf('Unknown [%s-%s]',$this->item_type,$this->id); } } // Display the period that a transaction applies public function period() { return ($this->date_start == $this->date_stop) ? Site::Date($this->date_start) : sprintf('%s -> %s',Site::Date($this->date_start),Site::Date($this->date_stop)); } /** * Display the Invoice Item Reference Number */ public function refnum($short=FALSE) { return $short ? '' : sprintf('%03s-',$this->item_type).sprintf('%06s',$this->id); } public function save(Validation $validation = NULL) { // Our items will be clobbered once we save the object, so we need to save it here. $subitems = $this->subitems(); // Save the invoice item parent::save($validation); // Need to save the associated items and their taxes if ($this->loaded()) { foreach ($subitems as $iito) { $iito->invoice_item_id = $this->id; if (! $iito->changed()) continue; $iito->save(); if (! $iito->saved()) { $this->active = 0; $this->save(); break; } } } else throw new Kohana_Exception('Couldnt save invoice_item for some reason?'); return $this; } // This total of this item before discounts and taxes public function subtotal($format=FALSE) { $result = $this->price_base*$this->quantity; return $format ? Currency::display($result) : $result; } // Sum up the tax that applies to this invoice item public function tax($format=FALSE) { $result = 0; foreach ($this->subitems() as $iito) $result += $iito->amount; return $format ? Currency::display($result) : Currency::round($result); } public function tax_items() { $result = array(); foreach ($this->subitems() as $iito) { if (! isset($result[$iito->tax_id])) $result[$iito->tax_id] = 0; $result[$iito->tax_id] += $iito->amount; } return $result; } public function title(Model_Language $lo) { if (! $this->isValid()) return 'Record Error'; switch ($this->item_type) { case 0: case 2: case 3: case 4: return $this->product->name($lo); case 5: return $this->_module()->name($lo); case 124: return StaticList_ItemType::get($this->item_type); default: return 'Unknown'; } } public function total($format=FALSE) { $result = ! $this->active ? 0 : $this->subtotal()+$this->tax()-$this->discount(); return $format ? Currency::display($result) : Currency::round($result); } } ?>