Sales - adds json attributes and callback
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 3 Dec 2015 11:19:45 +0000 (12:19 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Tue, 12 Jan 2016 14:16:26 +0000 (15:16 +0100)
added callback to Sales_Controller_Contract to notify listeners
when all creation or updating of all sub records is done
added Tinebase_Model_Converter that convert fields from
data to objects to data

Change-Id: I475359a3c33cbc6798f97a1db889207c7bb63069
Reviewed-on: http://gerrit.tine20.com/customers/2466
Tested-by: Jenkins CI (http://ci.tine20.com/)
Tested-by: sstamer <s.stamer@metaways.de>
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Sales/Controller/Contract.php
tine20/Sales/Controller/Invoice.php
tine20/Sales/Controller/ProductAggregate.php
tine20/Sales/Model/Accountable/Abstract.php
tine20/Sales/Model/Accountable/Interface.php
tine20/Tinebase/Backend/Sql/Abstract.php
tine20/Tinebase/Model/Converter/Interface.php [new file with mode: 0644]
tine20/Tinebase/Model/Converter/Json.php [new file with mode: 0644]
tine20/Tinebase/ModelConfiguration.php
tine20/Tinebase/Record/Abstract.php

index aa39896..a52e96a 100644 (file)
@@ -48,6 +48,12 @@ class Sales_Controller_Contract extends Sales_Controller_NumberableAbstract
      * @var boolean
      */
     protected $_handleDependentRecords = TRUE;
+
+    /**
+     * holds the callbacks to call after modifications are done
+     * @var array
+     */
+    protected $_afterModifyCallbacks = array();
     
     /**
      * holds the instance of the singleton
@@ -232,7 +238,21 @@ class Sales_Controller_Contract extends Sales_Controller_NumberableAbstract
     {
         $this->_handleDependentRecords = $toggle;
     }
-    
+
+    public function addAfterModifyCallback($key, $callable)
+    {
+        $this->_afterModifyCallbacks[$key] = $callable;
+    }
+
+    protected function _afterModifyCallbacks()
+    {
+        foreach($this->_afterModifyCallbacks as $callable)
+        {
+            call_user_func($callable[0], $callable[1]);
+        }
+        $this->_afterModifyCallbacks = array();
+    }
+
     /**
      * inspect creation of one record (after create)
      *
@@ -248,6 +268,8 @@ class Sales_Controller_Contract extends Sales_Controller_NumberableAbstract
                 $this->_createDependentRecords($_createdRecord, $_record, $property, $config[$property]['config']);
             }
         }
+
+        $this->_afterModifyCallbacks();
     }
     
     /**
@@ -257,14 +279,16 @@ class Sales_Controller_Contract extends Sales_Controller_NumberableAbstract
      * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
      * @return  void
      */
-    protected function _inspectBeforeUpdate($_record, $_oldRecord)
+    protected function _inspectAfterUpdate($_updatedRecord, $_record, $_oldRecord)
     {
         if ($this->_handleDependentRecords) {
-            $config = $_record::getConfiguration()->recordsFields;
+            $config = $_updatedRecord::getConfiguration()->recordsFields;
             foreach (array_keys($config) as $p) {
                 $this->_updateDependentRecords($_record, $_oldRecord, $p, $config[$p]['config']);
             }
         }
+
+        $this->_afterModifyCallbacks();
     }
     
     /**
index 2896e67..8fb0f22 100644 (file)
@@ -316,8 +316,13 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
     protected function _prepareInvoiceRelationsAndFindBillableAccountables($productAggregates)
     {
         $modelsToBill = array();
+        $billedRelations = array();
         $simpleProductsToBill = array();
         $modelsToSkip = array();
+        // this holds all relations for the invoice
+        $relations            = array();
+        $billableAccountables = array();
+
         
         // iterate product aggregates to get the billing definition for the models
         foreach ($productAggregates as $productAggregate) {
@@ -387,23 +392,50 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
                 if (($product->accountable == 'Sales_Model_Product') || ($product->accountable == '')) {
                     $simpleProductsToBill[] = array('pa' => $productAggregate, 'ac' => $productAggregate);
                 } else {
-                    $modelsToBill[$product->accountable] = $productAggregate;
+
+                    if ($productAggregate->json_attributes && isset($productAggregate->json_attributes['assignedAccountables']) &&
+                        is_array($productAggregate->json_attributes['assignedAccountables']) && count($productAggregate->json_attributes['assignedAccountables'])) {
+
+                        foreach ($productAggregate->json_attributes['assignedAccountables'] as $relationId) {
+                            $billedRelations[$relationId] = true;
+
+                            $relation = $this->_currentBillingContract->relations->getById($relationId);
+
+                            if (false === $relation || $product->accountable != $relation->related_model) {
+                                throw new Tinebase_Exception_UnexpectedValue('couldnt resolved assignedAccountables');
+                            }
+
+                            $relations[] = array_merge(array(
+                                'related_model'  => $relation->related_model,
+                                'related_id'     => $relation->related_id,
+                                'related_record' => $relation->related_record->toArray(),
+                            ), $this->_getRelationDefaults());
+
+                            $billableAccountables[] = array(
+                                'ac' => $relation->related_record,
+                                'pa' => $productAggregate
+                            );
+                        }
+                    } else {
+                        $modelsToBill[$product->accountable] = $productAggregate;
+                    }
                 }
             } else {
                 $modelsToSkip[] = $product->accountable;
             }
         }
         
-        // this holds all relations for the invoice
-        $relations            = array();
-        $billableAccountables = array();
+
         
         // iterate relations, look for accountables, prepare relations
         foreach ($this->_currentBillingContract->relations as $relation) {
+            if (isset($billedRelations[$relation->id])) {
+                continue;
+            }
             // use productaggregate definition, if it has been found
             if (isset($modelsToBill[$relation->related_model]) && (! in_array($relation->related_model, $modelsToSkip))) {
                 $relations[] = array_merge(array(
-                    'related_model'  => get_class($relation->related_record),
+                    'related_model'  => $relation->related_model,
                     'related_id'     => $relation->related_id,
                     'related_record' => $relation->related_record->toArray(),
                 ), $this->_getRelationDefaults());
@@ -416,7 +448,7 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
             } elseif ((! in_array($relation->related_model, $modelsToSkip)) && in_array('Sales_Model_Accountable_Interface', class_implements($relation->related_model))) {
                 // no product aggregate definition has been found -> use default values
                 $relations[] = array_merge(array(
-                    'related_model'  => get_class($relation->related_record),
+                    'related_model'  => $relation->related_model,
                     'related_id'     => $relation->related_id,
                     'related_record' => $relation->related_record->toArray(),
                 ), $this->_getRelationDefaults());
index e9b437d..407e872 100644 (file)
@@ -62,4 +62,63 @@ class Sales_Controller_ProductAggregate extends Tinebase_Controller_Record_Abstr
         
         return self::$_instance;
     }
+
+
+    /**
+     * inspect creation of one record (before create)
+     *
+     * @param   Tinebase_Record_Interface $_record
+     * @return  void
+     */
+    protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
+    {
+        $acs = $this->_getProductAccountables($_record);
+        if ($acs !== null && $acs->count())
+        {
+            foreach($acs as $ac) {
+                $ac->_inspectBeforeCreateProductAggregate($_record);
+            }
+        }
+    }
+
+    /**
+     * inspect update of one record (before update)
+     *
+     * @param   Tinebase_Record_Interface $_record      the update record
+     * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
+     * @return  void
+     */
+    protected function _inspectBeforeUpdate($_record, $_oldRecord)
+    {
+        if ($_record->product_id != $_oldRecord->product_id) {
+            // uhh, now what?!?
+        }
+        $acs = $this->_getProductAccountables($_record);
+        if ($acs !== null && $acs->count())
+        {
+            foreach($acs as $ac) {
+                $ac->_inspectBeforeUpdateProductAggregate($_record, $_oldRecord);
+            }
+        }
+    }
+
+    /**
+     * @param Sales_Model_ProductAggregate $productAggregate
+     * @return null|Tinebase_Record_RecordSet
+     * @throws Tinebase_Exception_AccessDenied
+     * @throws Tinebase_Exception_NotFound
+     */
+    protected function _getProductAccountables(Sales_Model_ProductAggregate $productAggregate) {
+        $json_attributes = $productAggregate->json_attributes;
+        if (!is_array($json_attributes) || !isset($json_attributes['assignedAccountables']))
+            return null;
+
+        $product = Sales_Controller_Product::getInstance()->get($productAggregate->product_id);
+        if ($product->accountable == '')
+            return null;
+
+
+        $app = Tinebase_Core::getApplicationInstance($product->accountable, '');
+        return $app->getMultiple($json_attributes['assignedAccountables']);
+    }
 }
index e2eaaf8..dfbaf5b 100644 (file)
@@ -263,4 +263,23 @@ abstract class Sales_Model_Accountable_Abstract extends Tinebase_Record_Abstract
      * @return boolean
      */
     abstract public function isBillable(Tinebase_DateTime $date, Sales_Model_Contract $contract = NULL);
+
+    /**
+     * called by the product aggregate controller in case accountables are assigned to the changed
+     * product aggregate
+     *
+     * @param Sales_Model_ProductAggregate $productAggregate
+     * @return void
+     */
+    public function _inspectBeforeCreateProductAggregate(Sales_Model_ProductAggregate $productAggregate) {}
+
+    /**
+     * called by the product aggregate controller in case accountables are assigned to the changed
+     * product aggregate
+     *
+     * @param Sales_Model_ProductAggregate $productAggregate
+     * @param Sales_Model_ProductAggregate $oldRecord
+     * @return void
+     */
+    public function _inspectBeforeUpdateProductAggregate(Sales_Model_ProductAggregate $productAggregate, Sales_Model_ProductAggregate $oldRecord) {}
 }
index faa60ca..d833450 100644 (file)
@@ -103,4 +103,23 @@ interface Sales_Model_Accountable_Interface
      * @return Sales_Model_ProductAggregate
      */
     public function getDefaultProductAggregate(Sales_Model_Contract $contract);
+
+    /**
+     * called by the product aggregate controller in case accountables are assigned to the changed
+     * product aggregate
+     *
+     * @param Sales_Model_ProductAggregate $productAggregate
+     * @return void
+     */
+    public function _inspectBeforeCreateProductAggregate(Sales_Model_ProductAggregate $productAggregate);
+
+    /**
+     * called by the product aggregate controller in case accountables are assigned to the changed
+     * product aggregate
+     *
+     * @param Sales_Model_ProductAggregate $productAggregate
+     * @param Sales_Model_ProductAggregate $oldRecord
+     * @return void
+     */
+    public function _inspectBeforeUpdateProductAggregate(Sales_Model_ProductAggregate $productAggregate, Sales_Model_ProductAggregate $oldRecord);
 }
index df08f18..aaa7b1b 100644 (file)
@@ -303,6 +303,8 @@ abstract class Tinebase_Backend_Sql_Abstract extends Tinebase_Backend_Abstract i
     protected function _rawDataToRecord(array $_rawData)
     {
         $result = new $this->_modelName($_rawData, true);
+
+        $result->runConvertToRecord();
         
         $this->_explodeForeignValues($result);
         
@@ -1078,6 +1080,7 @@ abstract class Tinebase_Backend_Sql_Abstract extends Tinebase_Backend_Abstract i
      */
     protected function _recordToRawData($_record)
     {
+        $_record->runConvertToData();
         $readOnlyFields = $_record->getReadOnlyFields();
         $raw = $_record->toArray(FALSE);
         foreach ($raw as $key => $value) {
diff --git a/tine20/Tinebase/Model/Converter/Interface.php b/tine20/Tinebase/Model/Converter/Interface.php
new file mode 100644 (file)
index 0000000..9ca341a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Converter
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ */
+
+/**
+ * Tinebase_Model_Converter_Interface
+ *
+ * Converter Interface
+ *
+ * @package     Tinebase
+ * @subpackage  Converter
+ */
+
+
+interface Tinebase_Model_Converter_Interface
+{
+    static function convertToRecord($blob);
+
+    static function convertToData($fieldValue);
+}
\ No newline at end of file
diff --git a/tine20/Tinebase/Model/Converter/Json.php b/tine20/Tinebase/Model/Converter/Json.php
new file mode 100644 (file)
index 0000000..c01f98e
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Converter
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ */
+
+/**
+ * Tinebase_Model_Converter_Json
+ *
+ * Json Converter
+ *
+ * @package     Tinebase
+ * @subpackage  Converter
+ */
+
+class Tinebase_Model_Converter_Json implements Tinebase_Model_Converter_Interface
+{
+
+    static public function convertToRecord($blob)
+    {
+        return Zend_Json::decode($blob);
+    }
+
+    static public function convertToData($fieldValue)
+    {
+        if (is_null($fieldValue)) {
+            return $fieldValue;
+        }
+        return Zend_Json::encode($fieldValue);
+    }
+}
\ No newline at end of file
index b06b9e8..d28bbff 100644 (file)
@@ -281,6 +281,7 @@ class Tinebase_ModelConfiguration {
      * integer     seconds       Seconds             integer  integer                       int               Tinebase_Model_Filter_Int
      * integer     minutes       Minutes             integer  integer                       int               Tinebase_Model_Filter_Int
      * float                     Float               float    float                         float             Tinebase_Model_Filter_Int
+     * json                      Json String         text     string                        array             Tinebase_Model_Filter_Text
      * container                 Container           string   Tine.Tinebase.Model.Container Tinebase_Model_Container                                    tine.widget.container.filtermodel
      * tag tinebase.tag
      * user                      User                string                                 Tinebase_Model_Filter_User
@@ -524,6 +525,13 @@ class Tinebase_ModelConfiguration {
      * @var array
      */
     protected $_filters;
+
+    /**
+     * converters (will be set by field configuration)
+     *
+     * @var array
+     */
+    protected $_converters = array();
     
     /**
      * Holds the default Data for the model (autoset from field config)
@@ -564,7 +572,7 @@ class Tinebase_ModelConfiguration {
         'defaultFilter', 'requiredRight', 'singularContainerMode', 'fields', 'defaultData', 'titleProperty',
         'useGroups', 'fieldGroupFeDefaults', 'fieldGroupRights', 'multipleEdit', 'multipleEditRequiredRight',
         'recordName', 'recordsName', 'appName', 'modelName', 'createModule', 'virtualFields', 'group', 'isDependent',
-        'hasCustomFields', 'modlogActive', 'hasAttachments', 'idProperty', 'splitButton'
+        'hasCustomFields', 'modlogActive', 'hasAttachments', 'idProperty', 'splitButton', 'attributeConfig'
     );
 
     /**
@@ -595,6 +603,12 @@ class Tinebase_ModelConfiguration {
      */
     protected $_filterConfiguration = NULL;
 
+    /**
+     *
+     * @var array
+     */
+    protected $_attributeConfig = NULL;
+
     /*
      * mappings
      */
@@ -609,6 +623,7 @@ class Tinebase_ModelConfiguration {
         'time'     => 'Tinebase_Model_Filter_Date',
         'string'   => 'Tinebase_Model_Filter_Text',
         'text'     => 'Tinebase_Model_Filter_Text',
+        'json'     => 'Tinebase_Model_Filter_Text',
         'boolean'  => 'Tinebase_Model_Filter_Bool',
         'integer'  => 'Tinebase_Model_Filter_Int',
         'float'    => 'Tinebase_Model_Filter_Float',
@@ -643,6 +658,15 @@ class Tinebase_ModelConfiguration {
     );
 
     /**
+     * This maps field types to their default converter
+     *
+     * @var array
+     */
+    protected $_converterDefaultMapping = array(
+        'json'      => array('Tinebase_Model_Converter_Json'),
+    );
+
+    /**
      * the constructor (must be called by the singleton pattern)
      *
      * @var array $modelClassConfiguration
@@ -849,6 +873,15 @@ class Tinebase_ModelConfiguration {
             if ($this->_modlogActive && (isset($fieldDef['modlogOmit']) || array_key_exists('modlogOmit', $fieldDef))) {
                 $this->_modlogOmitFields[] = $fieldKey;
             }
+
+            // set converters
+            if (isset($fieldDef['converters']) && is_array($fieldDef['converters'])) {
+                if (count($fieldDef['converters'])) {
+                    $this->_converters[$fieldKey] = $fieldDef['converters'];
+                }
+            } elseif(isset($this->_converterDefaultMapping[$fieldDef['type']])) {
+                $this->_converters[$fieldKey] = $this->_converterDefaultMapping[$fieldDef['type']];
+            }
             
             $this->_populateProperties($fieldKey, $fieldDef);
             
@@ -1054,6 +1087,14 @@ class Tinebase_ModelConfiguration {
     }
 
     /**
+     * returns the converters of the model
+     */
+    public function getConverters()
+    {
+        return $this->_converters;
+    }
+
+    /**
      * get protected property
      *
      * @param string name of property
index 58cf64e..4e98fc4 100644 (file)
@@ -1240,4 +1240,32 @@ abstract class Tinebase_Record_Abstract implements Tinebase_Record_Interface
         
         return $keys;
     }
+
+    public function runConvertToRecord()
+    {
+        $conf = self::getConfiguration();
+        if (null === $conf)
+            return;
+        foreach ($conf->getConverters() as $key => $converters) {
+            if (isset($this->_properties[$key])) {
+                foreach ($converters as $converter) {
+                    $this->_properties[$key] = $converter::convertToRecord($this->_properties[$key]);
+                }
+            }
+        }
+    }
+
+    public function runConvertToData()
+    {
+        $conf = self::getConfiguration();
+        if (null === $conf)
+            return;
+        foreach ($conf->getConverters() as $key => $converters) {
+            if (isset($this->_properties[$key])) {
+                foreach ($converters as $converter) {
+                    $this->_properties[$key] = $converter::convertToData($this->_properties[$key]);
+                }
+            }
+        }
+    }
 }