← Back to team overview

savoirfairelinux-openerp team mailing list archive

[Merge] lp:~extra-addons-commiter/e-commerce-addons/trunk into lp:e-commerce-addons

 

Sébastien BEAU - http://www.akretion.com has proposed merging lp:~extra-addons-commiter/e-commerce-addons/trunk into lp:e-commerce-addons.

Requested reviews:
  extra-addons-commiter (extra-addons-commiter)

For more details, see:
https://code.launchpad.net/~extra-addons-commiter/e-commerce-addons/trunk/+merge/142952

this was the previous trunk branch and as some module have been ported we should merge it in stable
-- 
https://code.launchpad.net/~extra-addons-commiter/e-commerce-addons/trunk/+merge/142952
Your team extra-addons-commiter is requested to review the proposed merge of lp:~extra-addons-commiter/e-commerce-addons/trunk into lp:e-commerce-addons.
=== modified file 'base_sale_export_partner/__openerp__.py'
--- base_sale_export_partner/__openerp__.py	2012-11-21 13:48:17 +0000
+++ base_sale_export_partner/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -36,6 +36,6 @@
         'wizard/export_partner.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }

=== modified file 'base_sale_export_product/__openerp__.py'
--- base_sale_export_product/__openerp__.py	2012-11-21 13:48:17 +0000
+++ base_sale_export_product/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -36,6 +36,6 @@
         'wizard/export_product.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }

=== modified file 'base_sale_multichannels/__openerp__.py'
--- base_sale_multichannels/__openerp__.py	2012-11-30 12:02:42 +0000
+++ base_sale_multichannels/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -72,7 +72,7 @@
         'account_view.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'certificate': '',
 }
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'base_sale_multichannels/connector.py'
--- base_sale_multichannels/connector.py	1970-01-01 00:00:00 +0000
+++ base_sale_multichannels/connector.py	2013-01-11 17:08:20 +0000
@@ -0,0 +1,37 @@
+# -*- encoding: utf-8 -*-
+###############################################################################
+#                                                                             #
+#   product_custom_attributes for OpenERP                                     #
+#   Copyright (C) 2012 Camptocamp Alexandre Fayolle  <alexandre.fayolle@xxxxxxxxxxxxxx>  #
+#   Copyright (C) 2012 Akretion Sebastien Beau <sebastien.beau@xxxxxxxxxxxx>  #
+#                                                                             #
+#   This program is free software: you can redistribute it and/or modify      #
+#   it under the terms of the GNU Affero General Public License as            #
+#   published by the Free Software Foundation, either version 3 of the        #
+#   License, or (at your option) any later version.                           #
+#                                                                             #
+#   This program is distributed in the hope that it will be useful,           #
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of            #
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             #
+#   GNU Affero General Public License for more details.                       #
+#                                                                             #
+#   You should have received a copy of the GNU Affero General Public License  #
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.     #
+#                                                                             #
+###############################################################################
+
+from base_external_referentials.connector import AbstractConnector
+
+class BaseConnector(AbstractConnector):
+    def _get_import_defaults_res_partner(self, cr, uid, context=None):
+        pass
+    def _get_import_defaults_res_partner(self, cr, uid, context=None):
+        pass
+    def _get_import_defaults_external_shop_group(self, cr, uid, context=None):
+        pass
+
+    def _get_import_defaults_sale_order(self, cr, uid, context=None):
+        pass
+
+    def _record_one_sale_order(self, cr, uid, res_obj, resource, defaults, context):
+        pass

=== modified file 'base_sale_multichannels/partner.py'
--- base_sale_multichannels/partner.py	2012-10-24 20:17:59 +0000
+++ base_sale_multichannels/partner.py	2013-01-11 17:08:20 +0000
@@ -32,6 +32,7 @@
         'shop_ids': fields.many2many('sale.shop', 'sale_shop_res_partner_rel', 'shop_id', 'partner_id', 'Present in Shops', readonly=True, help="List of shops in which this customer exists."),
     }
 
+    # xxx move to BaseConnector _get_import_defaults_res_partner
     def _get_default_import_values(self, cr, uid, external_session, mapping_id=None, defaults=None, context=None):
         if external_session.sync_from_object._name == 'sale.shop':
             shop = external_session.sync_from_object

=== modified file 'base_sale_multichannels/sale.py'
--- base_sale_multichannels/sale.py	2013-01-03 10:08:13 +0000
+++ base_sale_multichannels/sale.py	2013-01-11 17:08:20 +0000
@@ -63,7 +63,7 @@
         'shop_ids': fields.one2many('sale.shop', 'shop_group_id', 'Sale Shops'),
     }
 
-
+    # xxx move to BaseConnector _get_import_defaults_external_shop_group
     def _get_default_import_values(self, cr, uid, external_session, **kwargs):
         return {'referential_id' : external_session.referential_id.id}
 
@@ -539,6 +539,7 @@
             vals['update_state_date'] = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
         return super(sale_order, self).write(cr, uid, ids, vals, context=context)
 
+    # xxx move to BaseConnector _get_import_defaults_sale_order
     def _get_default_import_values(self, cr, uid, external_session, mapping_id=None, defaults=None, context=None):
         shop = False
         if external_session.sync_from_object._name == 'sale.shop':
@@ -597,6 +598,7 @@
 
         return False
 
+    # xxx a deplacer dans BaseConnector  sale_order
     @catch_error_in_report
     def _record_one_external_resource(self, cr, uid, external_session, resource, defaults=None,
                                                         mapping=None, mapping_id=None, context=None):

=== modified file 'base_sale_report_synchronizer/__openerp__.py'
--- base_sale_report_synchronizer/__openerp__.py	2012-11-21 13:48:17 +0000
+++ base_sale_report_synchronizer/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -41,7 +41,7 @@
             'sale_view.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }
 

=== modified file 'product_custom_attributes_shop/__openerp__.py'
--- product_custom_attributes_shop/__openerp__.py	2012-12-07 12:44:57 +0000
+++ product_custom_attributes_shop/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -46,7 +46,7 @@
         'security/ir.model.access.csv',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }
 

=== modified file 'product_images_sync/__openerp__.py'
--- product_images_sync/__openerp__.py	2012-12-07 12:44:57 +0000
+++ product_images_sync/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -42,6 +42,6 @@
     'update_xml': [
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }

=== modified file 'product_links/__openerp__.py'
--- product_links/__openerp__.py	2012-11-21 13:48:17 +0000
+++ product_links/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -40,6 +40,6 @@
                    'product_links_view.xml',
                    ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }

=== modified file 'product_links_goodies/__openerp__.py'
--- product_links_goodies/__openerp__.py	2012-11-21 13:48:17 +0000
+++ product_links_goodies/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -38,7 +38,7 @@
             'product_goodies_data.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }
 

=== modified file 'product_links_sync/__openerp__.py'
--- product_links_sync/__openerp__.py	2012-11-21 13:48:17 +0000
+++ product_links_sync/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -38,7 +38,7 @@
            'sale_view.xml',
     ],
     'demo_xml': [],
-    'installable': True,
+    'installable': False,
     'active': False,
 }
 

=== modified file 'sale_automatic_workflow/__init__.py'
--- sale_automatic_workflow/__init__.py	2012-04-22 12:49:21 +0000
+++ sale_automatic_workflow/__init__.py	2013-01-11 17:08:20 +0000
@@ -24,7 +24,8 @@
 import sale_workflow_process
 import payment_method
 import automatic_workflow_job
-
+import invoice
+import stock
 
 
 

=== modified file 'sale_automatic_workflow/__openerp__.py'
--- sale_automatic_workflow/__openerp__.py	2012-11-21 13:48:17 +0000
+++ sale_automatic_workflow/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -22,13 +22,17 @@
 
 {
     'name': 'sale_automatic_workflow',
-    'version': '6.1.1',
+    'version': '0.1',
     'category': 'Generic Modules/Others',
     'license': 'AGPL-3',
     'description': """empty""",
     'author': 'Akretion',
     'website': 'http://www.akretion.com/',
-    'depends': ['sale_quick_payment', 'framework_helpers'], 
+    'depends': [
+        'sale_quick_payment',
+        'framework_helpers',
+        'stock',
+    ], 
     'init_xml': [],
     'update_xml': [ 
             'sale_view.xml',

=== modified file 'sale_automatic_workflow/automatic_workflow_job.py'
--- sale_automatic_workflow/automatic_workflow_job.py	2012-11-21 12:46:53 +0000
+++ sale_automatic_workflow/automatic_workflow_job.py	2013-01-11 17:08:20 +0000
@@ -72,7 +72,7 @@
                 with commit_now(cr, logger) as cr:
                     invoice_obj.reconcile_invoice(cr, uid, [invoice_id], context=context)
 
-            picking_obj = self.pool.get('stock.picking')
+            picking_obj = self.pool.get('stock.picking.out')
             picking_ids = picking_obj.search(cr, uid, [('state', 'in', ['draft', 'confirmed', 'assigned']), ('workflow_process_id.validate_picking', '=',True)], context=context)
             if picking_ids:
                 logger.debug(_('start to validate pickings : %s') %picking_ids)

=== added file 'sale_automatic_workflow/invoice.py'
--- sale_automatic_workflow/invoice.py	1970-01-01 00:00:00 +0000
+++ sale_automatic_workflow/invoice.py	2013-01-11 17:08:20 +0000
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+#   sale_automatic_workflow for OpenERP
+#   Copyright (C) 2011-TODAY Akretion <http://www.akretion.com>.
+#     All Rights Reserved
+#     @author Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License as
+#   published by the Free Software Foundation, either version 3 of the
+#   License, or (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+from openerp.osv.orm import Model
+from openerp.osv import fields
+
+class account_invoice(Model):
+    _inherit = "account.invoice"
+    _columns = {
+        'workflow_process_id':fields.many2one('sale.workflow.process', 'Sale Workflow Process'),
+        #TODO propose a merge to add this field by default in acount module
+        'sale_ids': fields.many2many('sale.order', 'sale_order_invoice_rel', 'invoice_id', 'order_id', 'Sale Orders')
+    }
+
+    def _can_be_reconciled(self, cr, uid, invoice, context=None):
+        if not (invoice.sale_ids and invoice.sale_ids[0].payment_ids and invoice.move_id):
+            return False
+        #Check currency
+        for payment in invoice.sale_ids[0].payment_ids:
+            for move in payment.move_ids:
+                if (move.currency_id.id or invoice.company_id.currency_id.id) != invoice.currency_id.id:
+                    return False
+        return True
+
+    def _get_sum_invoice_move_line(self, cr, uid, move_lines, context=None):
+        return self._get_sum_move_line(cr, uid, move_lines, 'debit', context=None)
+
+    def _get_sum_payment_move_line(self, cr, uid, move_lines, context=None):
+        return self._get_sum_move_line(cr, uid, move_lines, 'credit', context=None)
+
+    def _get_sum_move_line(self, cr, uid, move_lines, line_type, context=None):
+        res = {
+            'max_date': False,
+            'line_ids': [],
+            'total_amount': 0,
+            'total_amount_currency': 0,
+        }
+        for move_line in move_lines:
+            if move_line[line_type] > 0:
+                if move_line.date > res['max_date']:
+                    res['max_date'] = move_line.date
+                res['line_ids'].append(move_line.id)
+                res['total_amount'] += move_line[line_type]
+                res['total_amount_currency'] += move_line.amount_currency
+        return res
+
+    def _prepare_write_off(self, cr, uid, invoice, res_invoice, res_payment, context=None):
+        if not context:
+            context = {}
+        ctx = context.copy()
+        if res_invoice['total_amount'] - res_payment['total_amount'] > 0:
+            writeoff_type = 'expense'
+        else:
+            writeoff_type = 'income'
+        account_id, journal_id = invoice.company_id.\
+            get_write_off_information('exchange', writeoff_type, context=context)
+        max_date = max(res_invoice['max_date'], res_payment['max_date'])
+        ctx['p_date'] = max_date
+        period_id = self.pool.get('account.period').find(cr, uid, max_date, context=context)[0]
+        return {
+            'type': 'auto',
+            'writeoff_acc_id': account_id,
+            'writeoff_period_id': period_id,
+            'writeoff_journal_id': journal_id,
+            'context': ctx,
+        }
+
+    def reconcile_invoice(self, cr, uid, ids, context=None):
+        """
+        Simple method to reconcile the invoice with the payment generated on the sale order
+        """
+        if not context:
+            context={}
+        precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
+        obj_move_line = self.pool.get('account.move.line')
+        for invoice in self.browse(cr, uid, ids, context=context):
+            use_currency = invoice.currency_id.id != invoice.company_id.currency_id.id
+            if self._can_be_reconciled(cr, uid, invoice, context=context):
+                payment_move_line = []
+                for payment in invoice.sale_ids[0].payment_ids:
+                    payment_move_line += payment.move_ids
+                res_payment = self._get_sum_payment_move_line(cr, uid, payment_move_line, context=context)
+                res_invoice = self._get_sum_invoice_move_line(cr, uid, invoice.move_id.line_id, context=context)
+                line_ids = res_invoice['line_ids'] + res_payment['line_ids']
+                if not use_currency:
+                    balance = abs(res_invoice['total_amount']-res_payment['total_amount'])
+                    if line_ids and not round(balance, precision):
+                        obj_move_line.reconcile(cr, uid, line_ids, context=context)
+                else:
+                    balance = abs(res_invoice['total_amount_currency']-res_payment['total_amount_currency'])
+                    if line_ids and not round(balance, precision):
+                        kwargs = self._prepare_write_off(cr, uid, invoice, res_invoice, res_payment, context=context)
+                        obj_move_line.reconcile(cr, uid, line_ids, **kwargs)
+        return True

=== modified file 'sale_automatic_workflow/sale.py'
--- sale_automatic_workflow/sale.py	2012-09-19 13:24:46 +0000
+++ sale_automatic_workflow/sale.py	2013-01-11 17:08:20 +0000
@@ -1,4 +1,4 @@
-    # -*- encoding: utf-8 -*-
+# -*- encoding: utf-8 -*-
 #################################################################################
 #                                                                               #
 #    sale_automatic_workflow for OpenERP                                        #
@@ -39,77 +39,3 @@
         picking_vals = super(sale_order, self)._prepare_order_picking(cr, uid, order, context=context)
         picking_vals['workflow_process_id'] = order.workflow_process_id.id
         return picking_vals
-
-class stock_picking(Model):
-    _inherit = "stock.picking"
-    _columns = {
-        'workflow_process_id':fields.many2one('sale.workflow.process', 'Sale Workflow Process'),
-    }
-
-    def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
-        invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, \
-                                                            inv_type, journal_id, context=context)
-        invoice_vals['workflow_process_id'] = picking.workflow_process_id.id
-        if picking.workflow_process_id.invoice_date_is_order_date:
-            invoice_vals['date_invoice'] = picking.sale_id.date_order
-        return invoice_vals
-
-    def validate_picking(self, cr, uid, ids, context=None):
-        for picking in self.browse(cr, uid, ids, context=context):
-            self.force_assign(cr, uid, [picking.id])
-            partial_data = {}
-            for move in picking.move_lines:
-                partial_data["move" + str(move.id)] = {'product_qty': move.product_qty, 
-                                                       'product_uom': move.product_uom.id}
-            self.do_partial(cr, uid, [picking.id], partial_data)
-        return True
-
-#TODO reimplement me
-#    def validate_manufactoring_order(self, cr, uid, origin, context=None): #we do not create class mrp.production to avoid dependence with the module mrp
-#        if context is None:
-#            context = {}
-#        wf_service = netsvc.LocalService("workflow")
-#        mrp_prod_obj = self.pool.get('mrp.production')
-#        mrp_product_produce_obj = self.pool.get('mrp.product.produce')
-#        production_ids = mrp_prod_obj.search(cr, uid, [('origin', 'ilike', origin)])
-#        for production in mrp_prod_obj.browse(cr, uid, production_ids):
-#            mrp_prod_obj.force_production(cr, uid, [production.id])
-#            wf_service.trg_validate(uid, 'mrp.production', production.id, 'button_produce', cr)
-#            context.update({'active_model': 'mrp.production', 'active_ids': [production.id], 'search_default_ready': 1, 'active_id': production.id})
-#            produce = mrp_product_produce_obj.create(cr, uid, {'mode': 'consume_produce', 'product_qty': production.product_qty}, context)
-#            mrp_product_produce_obj.do_produce(cr, uid, [produce], context)
-#            self.validate_manufactoring_order(cr, uid, production.name, context)
-#        return True
-#        
-
-class account_invoice(Model):
-    _inherit = "account.invoice"
-    _columns = {
-        'workflow_process_id':fields.many2one('sale.workflow.process', 'Sale Workflow Process'),
-        #TODO propose a merge to add this field by default in acount module
-        'sale_ids': fields.many2many('sale.order', 'sale_order_invoice_rel', 'invoice_id', 'order_id', 'Sale Orders')
-    }
-
-    def reconcile_invoice(self, cr, uid, ids, context=None):
-        """
-        Simple method to reconcile the invoice with the payment generated on the sale order
-        """
-        obj_move_line = self.pool.get('account.move.line')
-        for invoice in self.browse(cr, uid, ids, context=context):
-            line_ids = []
-            payment_amount = 0
-            invoice_amount = 0
-            if invoice.sale_ids and invoice.sale_ids[0].payment_id and invoice.move_id:
-                for move in invoice.sale_ids[0].payment_id.move_ids:
-                    if move.credit > 0 and not move.reconcile_id:
-                        line_ids.append(move.id)
-                        payment_amount += move.credit
-                for move in invoice.move_id.line_id:
-                    if move.debit > 0 and not move.reconcile_id:
-                        line_ids.append(move.id)
-                        invoice_amount += move.debit
-            balance = abs(payment_amount-invoice_amount)
-            precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
-            if line_ids and not round(balance, precision):
-                obj_move_line.reconcile(cr, uid, line_ids, context=context)
-        return True

=== added file 'sale_automatic_workflow/stock.py'
--- sale_automatic_workflow/stock.py	1970-01-01 00:00:00 +0000
+++ sale_automatic_workflow/stock.py	2013-01-11 17:08:20 +0000
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+#   sale_automatic_workflow for OpenERP
+#   Copyright (C) 2011-TODAY Akretion <http://www.akretion.com>.
+#     All Rights Reserved
+#     @author Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License as
+#   published by the Free Software Foundation, either version 3 of the
+#   License, or (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+from openerp.osv.orm import Model
+from openerp.osv import fields
+
+class stock_picking_out(Model):
+    _inherit = "stock.picking.out"
+    _columns = {
+        'workflow_process_id':fields.many2one('sale.workflow.process', 'Sale Workflow Process'),
+    }
+
+    def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
+        invoice_vals = super(stock_picking_out, self)._prepare_invoice(cr, uid, picking, partner, \
+                                                            inv_type, journal_id, context=context)
+        invoice_vals['workflow_process_id'] = picking.workflow_process_id.id
+        if picking.workflow_process_id.invoice_date_is_order_date:
+            invoice_vals['date_invoice'] = picking.sale_id.date_order
+        return invoice_vals
+
+    def validate_picking(self, cr, uid, ids, context=None):
+        for picking in self.browse(cr, uid, ids, context=context):
+            self.force_assign(cr, uid, [picking.id])
+            partial_data = {}
+            for move in picking.move_lines:
+                partial_data["move" + str(move.id)] = {'product_qty': move.product_qty, 
+                                                       'product_uom': move.product_uom.id}
+            self.do_partial(cr, uid, [picking.id], partial_data)
+        return True
+
+#TODO reimplement me
+#    def validate_manufactoring_order(self, cr, uid, origin, context=None): #we do not create class mrp.production to avoid dependence with the module mrp
+#        if context is None:
+#            context = {}
+#        wf_service = netsvc.LocalService("workflow")
+#        mrp_prod_obj = self.pool.get('mrp.production')
+#        mrp_product_produce_obj = self.pool.get('mrp.product.produce')
+#        production_ids = mrp_prod_obj.search(cr, uid, [('origin', 'ilike', origin)])
+#        for production in mrp_prod_obj.browse(cr, uid, production_ids):
+#            mrp_prod_obj.force_production(cr, uid, [production.id])
+#            wf_service.trg_validate(uid, 'mrp.production', production.id, 'button_produce', cr)
+#            context.update({'active_model': 'mrp.production', 'active_ids': [production.id], 'search_default_ready': 1, 'active_id': production.id})
+#            produce = mrp_product_produce_obj.create(cr, uid, {'mode': 'consume_produce', 'product_qty': production.product_qty}, context)
+#            mrp_product_produce_obj.do_produce(cr, uid, [produce], context)
+#            self.validate_manufactoring_order(cr, uid, production.name, context)
+#        return True
+#        

=== modified file 'sale_exceptions/__openerp__.py'
--- sale_exceptions/__openerp__.py	2012-11-21 13:48:17 +0000
+++ sale_exceptions/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -25,7 +25,7 @@
 
 {
     'name': 'Sale Exceptions',
-    'version': '6.1.0',
+    'version': '0.1',
     'category': 'Generic Modules/Sale',
     'description': """
 This module allows you attach several customizable exceptions to your sale order in a way that you can filter orders by exceptions type and fix them.

=== modified file 'sale_exceptions/sale.py'
--- sale_exceptions/sale.py	2012-12-07 14:11:56 +0000
+++ sale_exceptions/sale.py	2013-01-11 17:08:20 +0000
@@ -33,6 +33,7 @@
 class sale_exception(Model):
     _name = "sale.exception"
     _description = "Sale Exceptions"
+    _order="active desc, sequence asc"
     _columns = {
         'name': fields.char('Exception Name', size=64, required=True, translate=True),
         'description': fields.text('Description', translate=True),
@@ -106,6 +107,7 @@
         view_id = model_data_obj.get_object_reference(
             cr, uid, 'sale_exceptions', 'view_sale_exception_confirm')[1]
         action = {
+            'name': _("Exceptions On Sale Order"),
             'type': 'ir.actions.act_window',
             'view_type': 'form',
             'view_mode': 'form',
@@ -117,14 +119,12 @@
         }
         return action
 
-    def button_order_confirm(self, cr, uid, ids, context=None):
+    def action_button_confirm(self, cr, uid, ids, context=None):
         exception_ids = self.detect_exceptions(cr, uid, ids, context=context)
         if exception_ids:
             return self._popup_exceptions(cr, uid, ids[0],  context=context)
         else:
-            wf_service = netsvc.LocalService("workflow")
-            wf_service.trg_validate(uid, 'sale.order', ids[0], 'order_confirm', cr)
-        return True
+            return super(sale_order, self).action_button_confirm(cr, uid, ids, context=context)
 
     def test_exceptions(self, cr, uid, ids, context=None):
         """

=== modified file 'sale_exceptions/sale_view.xml'
--- sale_exceptions/sale_view.xml	2012-10-22 14:39:18 +0000
+++ sale_exceptions/sale_view.xml	2013-01-11 17:08:20 +0000
@@ -2,29 +2,13 @@
 <openerp>
     <data>
 
-        <record id="view_sale_exception_search" model="ir.ui.view">
-            <field name="name">sale.exception.view.search</field>
-            <field name="model">sale.exception</field>
-            <field name="type">search</field>
-            <field name="arch" type="xml">
-                <search string="Search Sale Exceptions">
-                   <group>
-                        <filter name="all" icon="gtk-fullscreen" string="See All" domain="['|', ('active','=', False), ('active','=', True)]" help="See all rule active or unactive"/>
-                    </group>
-                    <newline/>
-                    <group>
-                        <field name="name"/>
-                    </group>
-               </search>
-            </field>
-        </record>
-
         <record id="view_sale_exception_tree" model="ir.ui.view">
             <field name="name">sale.exception.tree</field>
             <field name="model">sale.exception</field>
             <field name="type">tree</field>
             <field name="arch" type="xml">
                 <tree string="Sale Exception">
+                    <field name="active"/>
                     <field name="name"/>
                     <field name="description"/>
                     <field name="model"/>
@@ -66,7 +50,7 @@
                   <field name="view_type">form</field>
                   <field name="view_mode">tree,form</field>
                   <field name="view_id" ref="view_sale_exception_tree"/>
-                  <field name="search_view_id" ref="view_sale_exception_search"/>
+                  <field name="context">{'active_test': False}</field>
               </record>
 
         <menuitem action="action_sale_test_tree" id="menu_sale_test" parent="base.menu_sale_config_sales" />
@@ -79,28 +63,17 @@
             <field name="priority">100</field>
             <field name="inherit_id" ref="sale.view_order_form"/>
             <field name="arch" type="xml">
-                <xpath expr="/form/notebook/page[@string='Sales Order']/group/field[@name='state']"
-                       position="after">
+                <field name="name" position="after">
                     <field name="main_exception_id" nolabel="1"
                            attrs="{'invisible':[('main_exception_id','=', False)]}"/>
-                </xpath>
-                <xpath expr="/form/notebook/page[@string='Other Information']/separator[@string='Notes']"
-                        position="before">
+                </field>
+                <xpath expr="//page[@string='Other Information']/group"
+                        position="inside">
                     <group name="exception" colspan="2" col="2">
                         <separator string="Exception" colspan="2"/>
                         <field name="exceptions_ids" colspan="2" nolabel="1"/>
                     </group>
-                    <group name="notes" colspan="2" col="2">
-                        <separator colspan="2" string="Notes"/>
-                        <field colspan="2" name="note" nolabel="1"/>
-                    </group>
                 </xpath>
-                <xpath expr="/form/notebook/page[@string='Other Information']/separator[@string='Notes']" position="replace"/>
-                <xpath expr="/form/notebook/page[@string='Other Information']/field[@name='note']" position="replace"/>
-                <button name="order_confirm" position="attributes">
-                    <attribute name="name">button_order_confirm</attribute>
-                    <attribute name="type">object</attribute>
-                </button>
             </field>
         </record>
 

=== modified file 'sale_exceptions/wizard/sale_exception_confirm_view.xml'
--- sale_exceptions/wizard/sale_exception_confirm_view.xml	2012-03-23 14:41:24 +0000
+++ sale_exceptions/wizard/sale_exception_confirm_view.xml	2013-01-11 17:08:20 +0000
@@ -7,26 +7,25 @@
             <field name="model">sale.exception.confirm</field>
             <field name="type">form</field>
             <field name="arch" type="xml">
-                <form string="Sale Exceptions">
-                    <separator colspan="4" string="Exceptions on the sale order" />
-                    <field name="exception_ids" nolabel="1" colspan="4">
-                        <form string="Sale Exception">
-                           <field name="name" colspan="4"/>
-                           <field name="description" colspan="4"/>
-                        </form>
-                        <tree string="Sale Exceptions">
-                            <field name="name"/>
-                            <field name="description"/>
-                        </tree>
-                    </field>
-                    <newline/>
-                    <field name="ignore" groups='base.group_sale_manager'/>
-                    <newline/>
-                    <separator colspan="4"/>
-                    <group col="2" colspan="4">
+                <form string="Sale Exceptions On Sale Order" version="7.0">
+                    <group>
+                        <field name="exception_ids" nolabel="1" colspan="4">
+                            <form string="Sale Exception">
+                               <field name="name" colspan="4"/>
+                               <field name="description" colspan="4"/>
+                            </form>
+                            <tree string="Sale Exceptions">
+                                <field name="name"/>
+                                <field name="description"/>
+                            </tree>
+                        </field>
+                        <newline/>
+                        <field name="ignore" groups='base.group_sale_manager'/>
+                    </group>
+                    <footer>
                         <button name="action_confirm" string="_Ok"
                             colspan="1" type="object" icon="gtk-ok" />
-                    </group>
+                    </footer>
                 </form>
             </field>
         </record>

=== modified file 'sale_quick_payment/__init__.py'
--- sale_quick_payment/__init__.py	2012-05-18 09:00:34 +0000
+++ sale_quick_payment/__init__.py	2013-01-11 17:08:20 +0000
@@ -23,6 +23,4 @@
 import sale
 import payment_method
 import wizard
-import company
-
-
+import account_voucher

=== modified file 'sale_quick_payment/__openerp__.py'
--- sale_quick_payment/__openerp__.py	2012-12-07 12:44:57 +0000
+++ sale_quick_payment/__openerp__.py	2013-01-11 17:08:20 +0000
@@ -22,7 +22,7 @@
 
 {
     'name': 'sale_quick_payment',
-    'version': '6.1.0',
+    'version': '0.1',
     'category': 'Generic Modules/Others',
     'license': 'AGPL-3',
     'description': """
@@ -36,16 +36,16 @@
     'author': 'Akretion',
     'website': 'http://www.akretion.com/',
     'depends': [
-        'sale',
+        'sale_exceptions',
         'account_voucher',
         ], 
     'init_xml': [],
-    'update_xml': [ 
+    'update_xml': [
+            'wizard/pay_sale_order.xml',
             'sale_view.xml',
             'payment_method_view.xml',
-            'wizard/pay_sale_order.xml',
-            'company_view.xml',
             'security/ir.model.access.csv',
+            'settings/sale.exception.csv',
     ],
     'demo_xml': [],
     'installable': True,

=== added file 'sale_quick_payment/account_voucher.py'
--- sale_quick_payment/account_voucher.py	1970-01-01 00:00:00 +0000
+++ sale_quick_payment/account_voucher.py	2013-01-11 17:08:20 +0000
@@ -0,0 +1,33 @@
+# -*- encoding: utf-8 -*-
+###############################################################################
+#
+#   sale_quick_payment for OpenERP
+#   Copyright (C) 2012-TODAY Akretion <http://www.akretion.com>.
+#     All Rights Reserved
+#     @author Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License as
+#   published by the Free Software Foundation, either version 3 of the
+#   License, or (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+from openerp.osv.orm import Model
+from openerp.osv import fields
+
+class payment_method(Model):
+    _inherit = "account.voucher"
+
+    _columns = {
+        'order_ids': fields.many2many('sale.order', string='Sale Order'),
+    }
+
+

=== removed file 'sale_quick_payment/company.py'
--- sale_quick_payment/company.py	2012-08-21 13:57:44 +0000
+++ sale_quick_payment/company.py	1970-01-01 00:00:00 +0000
@@ -1,33 +0,0 @@
-# -*- encoding: utf-8 -*-
-#################################################################################
-#                                                                               #
-#    sale_quick_payment for OpenERP                                             #
-#    Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>   #
-#                                                                               #
-#    This program is free software: you can redistribute it and/or modify       #
-#    it under the terms of the GNU Affero General Public License as             #
-#    published by the Free Software Foundation, either version 3 of the         #
-#    License, or (at your option) any later version.                            #
-#                                                                               #
-#    This program is distributed in the hope that it will be useful,            #
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of             #
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              #
-#    GNU Affero General Public License for more details.                        #
-#                                                                               #
-#    You should have received a copy of the GNU Affero General Public License   #
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.      #
-#                                                                               #
-#################################################################################
-
-from openerp.osv.orm import Model
-from openerp.osv import fields
-from tools.translate import _
-
-class res_company(Model):
-    """Override company to add payment configuration"""
-    _inherit = "res.company"
-    _columns = {        
-        'sale_order_must_be_paid':fields.boolean('Sale Order Must Be Paid', 
-                        help='If this option is check an order can not be validaded without payment'
-                    ),
-    }

=== removed file 'sale_quick_payment/company_view.xml'
--- sale_quick_payment/company_view.xml	2012-05-18 09:00:34 +0000
+++ sale_quick_payment/company_view.xml	1970-01-01 00:00:00 +0000
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  sale_quick_payment for OpenERP
-  Copyright (C) 2012 Akretion Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
-  The licence is in the file __openerp__.py
--->
-
-
-<openerp>
-    <data>
-        <record model="ir.ui.view" id="view_company_form">
-            <field name="name">sale_quick_payment.view_company_form</field>
-            <field name="model">res.company</field>
-            <field name="inherit_id" ref="base.view_company_form"/>
-            <field name="type">form</field>
-            <field name="arch" type="xml">
-                <page string="Configuration" position="inside">
-                    <separator string="Sale Configuration" colspan="4"/>
-                    <field name="sale_order_must_be_paid"/>
-                </page>
-            </field>
-        </record>
-    </data>
-</openerp>
-

=== added directory 'sale_quick_payment/migrations'
=== added directory 'sale_quick_payment/migrations/0.1'
=== added file 'sale_quick_payment/migrations/0.1/post-migration.py'
--- sale_quick_payment/migrations/0.1/post-migration.py	1970-01-01 00:00:00 +0000
+++ sale_quick_payment/migrations/0.1/post-migration.py	2013-01-11 17:08:20 +0000
@@ -0,0 +1,35 @@
+# -*- encoding: utf-8 -*-
+###############################################################################
+#
+#   sale_quick_payment for OpenERP
+#   Copyright (C) 2012-TODAY Akretion <http://www.akretion.com>.
+#     All Rights Reserved
+#     @author Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License as
+#   published by the Free Software Foundation, either version 3 of the
+#   License, or (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+""" r0.1: Migration 6.1 => 7.0.0.1
+    migrate the field payment_id from one2many to payment_ids many2many
+"""
+__name__ = ("sale.order:: V7 change/rename the field payment_id into a"
+            "many2many with the name payment_ids")
+
+def migrate(cr, version):
+    cr.execute("INSERT INTO account_voucher_sale_order_rel"
+               "(sale_order_id, account_voucher_id) "
+               "(SELECT id, payment_id FROM "
+               " sale_order "
+               "WHERE payment_id IS NOT NULL )")
+

=== modified file 'sale_quick_payment/sale.py'
--- sale_quick_payment/sale.py	2012-12-07 12:44:57 +0000
+++ sale_quick_payment/sale.py	2013-01-11 17:08:20 +0000
@@ -25,20 +25,48 @@
 import netsvc
 from collections import Iterable
 from openerp.tools.translate import _
+import decimal_precision as dp
 
 class sale_order(Model):
     _inherit = "sale.order"
 
+    def _get_order_from_voucher(self, cr, uid, ids, context=None):
+        result = []
+        for voucher in self.pool.get('account.voucher').browse(cr, uid, ids, context=context):
+            for order in voucher.order_ids:
+                result.append(order.id)
+        return list(set(result))
+
+    def _get_order_from_line(self, cr, uid, ids, context=None):
+        return self.pool.get('sale.order')._get_order(cr, uid, ids, context=context)
+
+    def _amount_residual(self, cr, uid, ids, field_name, args, context=None):
+        res = {}
+        #TODO add here the support of multi-currency payment if need
+        for order in self.browse(cr, uid, ids, context=context):
+            res[order.id] = order.amount_total
+            for payment in order.payment_ids:
+                if payment.state == 'posted':
+                    res[order.id] -= payment.amount
+        return res
+
     _columns = {
-        'payment_id': fields.many2one('account.voucher', 'Payment'),
+        'payment_ids': fields.many2many('account.voucher', string='Payments'),
         'payment_method_id': fields.many2one('payment.method', 'Payment Method'),
+        'residual': fields.function(_amount_residual, digits_compute=dp.get_precision('Account'), string='Balance',
+            store = {
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line', 'payment_ids'], 10),
+                'sale.order.line': (_get_order_from_line, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 20),
+                'account.voucher': (_get_order_from_voucher, ['amount'], 30),
+            },
+            ),
     }
 
     def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
         default.update({
-            'payment_id': False,
+            'payment_ids': False,
         })
         return super(sale_order, self).copy(cr, uid, id, default, context=context)
 
@@ -132,14 +160,6 @@
         wf_service = netsvc.LocalService("workflow")
         wf_service.trg_validate(
             uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
-        sale.write({'payment_id': voucher_id}, context=context)
+        sale.write({'payment_ids': [(4,voucher_id)]}, context=context)
         return True
 
-    def button_order_confirm(self, cr, uid, ids, context=None):
-        for order in self.browse(cr, uid, ids, context=context):
-            if order.company_id.sale_order_must_be_paid and not order.payment_id:
-                raise except_osv(_('User Error!'),
-                    _('The sale Order %s Must be paid before validation') % (order.name))
-        return super(sale_order, self).button_order_confirm(cr, uid, ids, context=context)
-
-

=== modified file 'sale_quick_payment/sale_view.xml'
--- sale_quick_payment/sale_view.xml	2012-05-24 20:44:25 +0000
+++ sale_quick_payment/sale_view.xml	2013-01-11 17:08:20 +0000
@@ -17,15 +17,23 @@
             <field eval="16" name="priority"/>
             <field name="type">form</field>
             <field name="arch" type="xml">
+                <button name="print_quotation" position="after">
+                    <button name="%(open_pay_sale_order)d" string="Register Payments" type="action" states="draft,sent"/>
+                </button>
                 <page string="Other Information" position="after">
                     <page name="automation_information" string="Automation Information">
                         <group name="payment_information" colspan="4">
-                            <separator string="Payment Information" colspan="4"/>
                             <field name="payment_method_id" />
-                            <field name="payment_id" />
+                            <group name="payments" colspan="4">
+                                <separator string="Payments" colspan="4"/>
+                                <field name="payment_ids" nolabel="1" />
+                            </group>
                         </group>
                     </page>
                 </page>
+                <field name="amount_total" position="after">
+                    <field name="residual" invisible="1"/>
+                </field>
             </field>
         </record>
 

=== added directory 'sale_quick_payment/settings'
=== added file 'sale_quick_payment/settings/sale.exception.csv'
--- sale_quick_payment/settings/sale.exception.csv	1970-01-01 00:00:00 +0000
+++ sale_quick_payment/settings/sale.exception.csv	2013-01-11 17:08:20 +0000
@@ -0,0 +1,3 @@
+id,name,description,sequence,model,code
+must_be_paid,Sale order must be paid before validation,The sale order must be paid before the validation. Please fix it,50,sale.order,"if order.residual > 0:
+    failed = True"

=== modified file 'sale_quick_payment/wizard/pay_sale_order.py'
--- sale_quick_payment/wizard/pay_sale_order.py	2012-08-21 13:57:44 +0000
+++ sale_quick_payment/wizard/pay_sale_order.py	2013-01-11 17:08:20 +0000
@@ -25,6 +25,8 @@
 import decimal_precision as dp
 from datetime import datetime
 
+#TODO add button on sale_form header instead of more button
+
 class pay_sale_order(TransientModel):
     _name = 'pay.sale.order'
     _description = 'Wizard to generate a payment from the sale order'
@@ -35,15 +37,21 @@
         'date': fields.datetime('Payment Date'),
         }
 
-    def _get_journal_id(self, cr, uid, args):
-        if args.get('payment_method_id'):
-            payment_method = self.pool.get('payment.method').browse(cr, uid, args['payment_method_id'])
-            return payment_method.journal_id.id
+    def _get_journal_id(self, cr, uid, context=None):
+        if context is None:
+            context = {}
+        if context.get('active_id'):
+            order = self.pool.get('sale.order').browse(cr, uid, context['active_id'], context=context)
+            if order.payment_method_id:
+                return order.payment_method_id.journal_id.id
         return False
 
-    def _get_amount(self, cr, uid, args):
-        if args.get('amount_total'):
-            return args['amount_total']
+    def _get_amount(self, cr, uid, context=None):
+        if context is None:
+            context = {}
+        if context.get('active_id'):
+            order = self.pool.get('sale.order').browse(cr, uid, context['active_id'], context=context)
+            return order.residual
         return False
 
     _defaults = {
@@ -60,6 +68,18 @@
         @param ids: List of account chart’s IDs
         @return: dictionary of Product list window for a given attributs set
         """
-        for wizard in self.browse(cr, uid, ids, context=context):
-            self.pool.get('sale.order').pay_sale_order(cr, uid, context['active_id'], wizard.journal_id.id, wizard.amount, wizard.date, context=context)
+        wizard = self.browse(cr, uid, ids[0], context=context)
+        self.pool.get('sale.order').pay_sale_order(cr, uid, context['active_id'], wizard.journal_id.id, wizard.amount, wizard.date, context=context)
         return {'type': 'ir.actions.act_window_close'}
+
+    def pay_sale_order_and_confirm(self, cr, uid, ids, context=None):
+        """
+        Pay the sale order
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param ids: List of account chart’s IDs
+        @return: dictionary of Product list window for a given attributs set
+        """
+        self.pay_sale_order(cr, uid, ids, context=context)
+        return self.pool.get('sale.order').action_button_confirm(cr, uid, [context['active_id']], context=context)
+    

=== modified file 'sale_quick_payment/wizard/pay_sale_order.xml'
--- sale_quick_payment/wizard/pay_sale_order.xml	2012-04-22 12:48:34 +0000
+++ sale_quick_payment/wizard/pay_sale_order.xml	2013-01-11 17:08:20 +0000
@@ -6,14 +6,19 @@
             <field name="model">pay.sale.order</field>
             <field name="type">form</field>
             <field name="arch" type="xml">
-                <form string="Pay Sale Order">
-                    <field name="journal_id" colspan="4"/>
-                    <field name="amount" colspan="4"/>
-                    <field name="date" colspan="4"/>
-                    <group colspan="4">
-                        <button icon="gtk-cancel" special="cancel" string="Cancel"/>
-                        <button icon="gtk-ok" name="pay_sale_order" string="Pay" type="object"/>
+                <form string="Pay Sale Order" version="7.0">
+                    <group>
+                        <field name="journal_id" string="Journal"/>
+                        <field name="amount" string="Paid Amount"/>
+                        <field name="date" string="Date"/>
                     </group>
+                    <footer>
+                        <button string="Pay" name="pay_sale_order" type="object" class="oe_highlight"/>
+                        or
+                        <button string="Pay And Confirm Order" name="pay_sale_order_and_confirm" type="object" class="oe_highlight"/>
+                        or
+                        <button string="Cancel" class="oe_link" special="cancel"/>
+                    </footer>
                 </form>
             </field>
         </record>
@@ -26,13 +31,5 @@
             <field name="target">new</field>
             <field name="view_id" ref="pay_sale_order_view"/>
         </record>
-
-        <record id="ir_action_open_pay_sale_order" model="ir.values">
-            <field name="key2">client_action_multi</field>
-            <field name="model">sale.order</field>
-            <field name="name">Sale.Order</field>
-            <field eval="'ir.actions.act_window,%d'%open_pay_sale_order" name="value"/>
-            <field eval="True" name="object"/>
-        </record>
     </data>
 </openerp>


Follow ups