0012050: Merge Invoices
authorsstamer <s.stamer@metaways.de>
Wed, 25 Mar 2015 11:58:14 +0000 (12:58 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Aug 2016 08:10:21 +0000 (10:10 +0200)
added field ('merge') to contract to always merge all invoices to be
created into one

added option to merge all invoices to be created on request into one
single invoice

added button

https://forge.tine20.org/view.php?id=12050

Change-Id: I51b601852ae6e94fb4b0f0cab0e45442f64d4f87
Reviewed-on: http://gerrit.tine20.com/customers/3362
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Sales/InvoiceControllerTests.php
tine20/Sales/Controller/Invoice.php
tine20/Sales/Frontend/Json.php
tine20/Sales/Model/Contract.php
tine20/Sales/css/Sales.css
tine20/Sales/js/InvoiceGridPanel.js
tine20/Sales/translations/de.po

index 92dadf9..7402273 100644 (file)
@@ -775,6 +775,53 @@ class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
     }
     
     /**
+     * tests invoice merging
+     */
+    public function testMergingInvoices()
+    {
+        $startDate = clone $this->_referenceDate;
+        
+        $this->_createProducts();
+        
+        $this->_createCustomers(1);
+        $this->_createCostCenters();
+        
+        $monthBack = clone $this->_referenceDate;
+        $monthBack->subMonth(1);
+        $addressId = $this->_addressRecords->filter(
+                'customer_id', $this->_customerRecords->filter(
+                    'name', 'Customer1')->getFirstRecord()->getId())->filter(
+                        'type', 'billing')->getFirstRecord()->getId();
+        
+        $this->assertTrue($addressId !== NULL);
+        
+        // this contract begins 6 months before the first invoice will be created
+        $this->_createContracts(array(array(
+            'number'       => 100,
+            'title'        => 'MyContract',
+            'description'  => 'unittest',
+            'container_id' => $this->_sharedContractsContainerId,
+            'billing_point' => 'begin',
+            'billing_address_id' => $addressId,
+            
+            'interval' => 1,
+            'start_date' => $startDate->subMonth(6),
+            'last_autobill' => clone $this->_referenceDate,
+            'end_date' => NULL,
+            'products' => array(
+                array('product_id' => $this->_productRecords->getByIndex(0)->getId(), 'quantity' => 1, 'interval' => 1, 'last_autobill' => $monthBack),
+            )
+        )));
+        
+        $startDate = clone $this->_referenceDate;
+        $startDate->addDay(5);
+        $startDate->addMonth(24);
+        
+        $result = $this->_invoiceController->createAutoInvoices($startDate, null, true);
+        $this->assertEquals(1, $result['created_count']);
+    }
+    
+    /**
      * tests if a product aggregate gets billed in the correct periods
      */
     public function testOneProductContractInterval()
index 85257df..2fd9ee5 100644 (file)
@@ -145,8 +145,9 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
      * 
      * @param Tinebase_Record_RecordSet $contracts
      * @param Tinebase_DateTime $currentDate
+     * @param boolean $merge
      */
-    public function processAutoInvoiceIteration($contracts, $currentDate)
+    public function processAutoInvoiceIteration($contracts, $currentDate, $merge)
     {
         Timetracker_Controller_Timeaccount::getInstance()->resolveCustomfields(FALSE);
         Timetracker_Controller_Timesheet::getInstance()->resolveCustomfields(FALSE);
@@ -162,7 +163,7 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
             
             try {
-                $this->_createAutoInvoicesForContract($contract, clone $currentDate);
+                $this->_createAutoInvoicesForContract($contract, clone $currentDate, $merge);
                 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
             } catch (Exception $e) {
                 Tinebase_TransactionManager::getInstance()->rollBack();
@@ -933,8 +934,9 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
      * 
      * @param Sales_Model_Contract $contract
      * @param Tinebase_DateTime $currentDate
+     * @param boolean $merge
      */
-    protected function _createAutoInvoicesForContract(Sales_Model_Contract $contract, Tinebase_DateTime $currentDate)
+    protected function _createAutoInvoicesForContract(Sales_Model_Contract $contract, Tinebase_DateTime $currentDate, $merge = false)
     {
         // set this current billing date (user timezone)
         $this->_currentBillingDate = clone $currentDate;
@@ -1007,6 +1009,10 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
         
         $doSleep = false;
         
+        if ( ($merge || $contract->merge_invoices) && $this->_currentMonthToBill->isEarlier($this->_currentBillingDate) ) {
+            $this->_currentMonthToBill = clone $this->_currentBillingDate;
+        }
+        
         while ( $this->_currentMonthToBill->isEarlierOrEquals($this->_currentBillingDate) ) {
             
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
@@ -1125,8 +1131,9 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
      * 
      * @param Tinebase_DateTime $currentDate
      * @param Sales_Model_Contract $contract
+     * @param boolean $merge
      */
-    public function createAutoInvoices(Tinebase_DateTime $currentDate, Sales_Model_Contract $contract = NULL)
+    public function createAutoInvoices(Tinebase_DateTime $currentDate, Sales_Model_Contract $contract = NULL, $merge = false)
     {
         $this->_autoInvoiceIterationResults  = array();
         $this->_autoInvoiceIterationDetailResults = array();
@@ -1149,7 +1156,7 @@ class Sales_Controller_Invoice extends Sales_Controller_NumberableAbstract
             'function'   => 'processAutoInvoiceIteration',
         ));
         
-        $iterator->iterate($currentDate);
+        $iterator->iterate($currentDate, $merge);
 
         unset($this->_autoInvoiceIterationDetailResults);
 
index 9080ce2..a53b568 100644 (file)
@@ -657,6 +657,25 @@ class Sales_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     }
     
     /**
+     * merge an invoice
+     *
+     * @param string $id
+     */
+    public function mergeInvoice($id)
+    {
+        $invoice = Sales_Controller_Invoice::getInstance()->get($id);
+        $relation = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Invoice', 'Sql', $id, 'sibling', array('CONTRACT'), 'Sales_Model_Contract')->getFirstRecord();
+        $contract = Sales_Controller_Contract::getInstance()->get($relation->related_id);
+    
+        $date = clone $invoice->creation_time;
+        $date->setTimezone(Tinebase_Core::getUserTimezone());
+    
+        Sales_Controller_Invoice::getInstance()->delete(array($id));
+    
+        return Sales_Controller_Invoice::getInstance()->createAutoInvoices($date, $contractm, true);
+    }
+    
+    /**
      * Search for records matching given arguments
      *
      * @param  array $filter
index 159da85..deb4c67 100644 (file)
@@ -218,6 +218,12 @@ class Sales_Model_Contract extends Tinebase_Record_Abstract
             'fulltext' => array(
                 'type' => 'string',
             ),
+            
+            'merge_invoices' => array(
+                'type'    => 'boolean',
+                'label'   => 'Merge', // _('Merge')
+                'default' => false,
+            ),
         )
     );
 
index c816ac0..5be97fd 100644 (file)
     background-image:url("../../images/oxygen/22x22/actions/edit-redo.png") !important;
 }
 
+.x-btn-medium .action_merge {
+    background-image:url("../../images/oxygen/22x22/actions/go-down.png") !important;
+}
+
 .x-btn-medium .action_bill {
     background-image:url("../../images/oxygen/22x22/categories/applications-office.png") !important;
 }
     background-image:url("../../images/oxygen/16x16/actions/edit-redo.png") !important;
 }
 
+.action_merge {
+    background-image:url("../../images/oxygen/16x16/actions/go-down.png") !important;
+}
+
 .action_bill {
     background-image:url("../../images/oxygen/16x16/categories/applications-office.png") !important;
 }
index 11844f1..d621ef5 100644 (file)
@@ -131,9 +131,31 @@ Tine.Sales.InvoiceGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             iconAlign: 'top'
         });
         
-        var additionalActions = [this.actions_export, this.actions_reversal, this.actions_rebill];
+        this.actions_merge = new Ext.Action({
+            text: this.app.i18n._('Merge Invoices'),
+            iconCls: 'action_merge',
+            scope: this,
+            disabled: true,
+            allowMultiple: false,
+            handler: this.onMergeInvoice,
+            actionUpdater: function(action, grants, records) {
+                if (records.length == 1 && records[0].get('type') == 'INVOICE' && records[0].get('cleared') != 'CLEARED') {
+                    action.enable();
+                } else {
+                    action.disable();
+                }
+            }
+        });
+
+        var mergeButton = Ext.apply(new Ext.Button(this.actions_merge), {
+            scale: 'medium',
+            rowspan: 2,
+            iconAlign: 'top'
+        });
+        
+        var additionalActions = [this.actions_export, this.actions_reversal, this.actions_rebill, this.actions_merge];
         this.actionUpdater.addActions(additionalActions);
-        return [exportButton, reversalButton, rebillButton];
+        return [exportButton, reversalButton, rebillButton, mergeButton];
     },
     
     /**
@@ -188,6 +210,35 @@ Tine.Sales.InvoiceGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         });
     },
     
+    onMergeInvoice: function(action, event) {
+        var rows = this.getGrid().getSelectionModel().getSelections();
+        
+        if (rows.length != 1) {
+            return;
+        }
+        
+        this.billMask.show();
+        
+        var that = this;
+        
+        var req = Ext.Ajax.request({
+            url : 'index.php',
+            params : { 
+                method: 'Sales.mergeInvoice', 
+                id:     rows[0].id 
+            },
+            success : function(result, request) {
+                that.billMask.hide();
+                that.getGrid().store.reload();
+            },
+            failure : function(exception) {
+                that.billMask.hide();
+                Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
+            },
+            scope: that
+        });
+    },
+    
     /**
      * add custom items to context menu
      * 
@@ -198,7 +249,8 @@ Tine.Sales.InvoiceGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             '-',
             this.actions_export,
             this.actions_reversal,
-            this.actions_rebill
+            this.actions_rebill,
+            this.actions_merge
             ];
         
         return items;
index 85faad3..b34674c 100644 (file)
@@ -1352,3 +1352,6 @@ msgstr "Von mir erstellte Produkte"
 
 msgid "Inventory Change"
 msgstr "Best. Verdg. FE/UE"
+
+msgid "Merge Invoices"
+msgstr "Zusammenfassen"