← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~yannick-buron/sale-wkfl/sale-wkfl into lp:sale-wkfl

 

YannickB has proposed merging lp:~yannick-buron/sale-wkfl/sale-wkfl into lp:sale-wkfl.

Commit message:
Add sale_advance_invoice

Requested reviews:
  Sale Core Editors (sale-core-editors)

For more details, see:
https://code.launchpad.net/~yannick-buron/sale-wkfl/sale-wkfl/+merge/207468

Add the module sale_advance_invoice which allow to use some different behavior for the way advance invoice are managed in OpenERP.

Essentially, some of my customers were shocked by the negative amount in final invoice when OpenERP create the final invoice.

I created two new behavior :
-Progressive, the final invoice will only have the remaining amount, we don't touch the advance invoices.
-Complete final invoice, when we launch the final invoice we refund the advance invoice and reassign payment to the final invoice.

The complete final invoice feature was developed for the customer "Destock Meubles", a furniture distributor, one year ago. Due to the long time needed to deliver the furniture and the french law, they absolutely needed advance invoices and at the end complete invoice so I had to develop the feature. For the record, everything was automated and integrated with magentoerpconnect, which mean that the advance invoice and final invoice were created at the import order.

The code was still quite ugly at this time so I didn't merge it in community addons. But currently, another customer asked me to remove the deduction amount and so I saw the opportunity to introduce a simpler mode, progressive, clean the module and finally merge it with OCA.

I hope this will be useful to some people. I really think that negative amount is bad on the final invoice, and even illegal regarding some legislation like the french one.
-- 
https://code.launchpad.net/~yannick-buron/sale-wkfl/sale-wkfl/+merge/207468
Your team Sale Core Editors is requested to review the proposed merge of lp:~yannick-buron/sale-wkfl/sale-wkfl into lp:sale-wkfl.
=== added directory 'sale_advance_invoice'
=== added file 'sale_advance_invoice/__init__.py'
--- sale_advance_invoice/__init__.py	1970-01-01 00:00:00 +0000
+++ sale_advance_invoice/__init__.py	2014-02-20 15:23:38 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) Yannick Buron.
+#
+#    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/>.
+#
+##############################################################################
+
+import sale
+import invoice

=== added file 'sale_advance_invoice/__openerp__.py'
--- sale_advance_invoice/__openerp__.py	1970-01-01 00:00:00 +0000
+++ sale_advance_invoice/__openerp__.py	2014-02-20 15:23:38 +0000
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) Yannick Buron
+#
+#    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/>.
+#
+##############################################################################
+
+
+{
+    'name': 'Sale Advance Invoice',
+    'version': '1.0',
+    'category': 'Sales Management',
+    'complexity': "easy",
+    'description': """
+Sale Advance Invoice
+========================
+
+This module allow you, through a select field in sale.order, to change the OpenERP behavior when you create a percent advance invoice :
+
+-Deduce the advance amount in final invoice. This is the default behavior of OpenERP.
+-Create advance invoices, then in final invoice we create a complete invoice, refund the advance invoices and reassign previous payment to final invoice.
+-Create advance invoices, then the final invoice will only have the remaining amount to pay.
+
+Possible improvement: Make theses methods also work with fix amount.
+    """,
+    'author': 'Yannick Buron',
+    'website': '',
+    'depends': ['base', 'sale'],
+    'init_xml': [],
+    'update_xml': [
+        'sale_view.xml'
+    ],
+    'demo_xml': [],
+    'test': [],
+    'installable': True,
+    'application': False,
+    'auto_install': False,
+    'certificate': '',
+    'images': [],
+}

=== added file 'sale_advance_invoice/invoice.py'
--- sale_advance_invoice/invoice.py	1970-01-01 00:00:00 +0000
+++ sale_advance_invoice/invoice.py	2014-02-20 15:23:38 +0000
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*- 
+##############################################################################
+#
+#    Copyright (C) SYNERPGY Vivek.
+#
+#    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 osv import osv, fields
+
+class account_invoice(osv.osv):
+
+    _inherit = 'account.invoice'
+
+    def action_move_create(self, cr, uid, ids, context=None):
+
+        res = super(account_invoice, self).action_move_create(cr, uid, ids)
+        # Pay the invoice
+        account_mov_line_obj = self.pool.get('account.move.line')
+        reconcile_obj = self.pool.get('account.move.reconcile')
+        voucher_obj = self.pool.get('account.voucher')
+        sale_obj = self.pool.get('sale.order')
+        for invoice in self.browse(cr, uid, ids):
+            # Search the sale order related with the invoices.
+            cr.execute("""
+                        SELECT order_id FROM sale_order_invoice_rel 
+                        WHERE invoice_id = %s""", (invoice.id,))
+            res = cr.fetchone()
+            if res:
+                sale_order_id = res[0]
+                sale_order = sale_obj.browse(cr, uid, sale_order_id)
+                # Find all the unreconciled payments.
+                payments = [payment for payment in sale_order.payment_ids] 
+
+                line_ids = account_mov_line_obj.search(cr, uid, [('move_id', '=', invoice.move_id.id)], order='date_maturity')
+ 
+                for payment in payments:
+                    if payment.reconcile_id:
+                        reconcile_obj.unlink(cr, uid, payment.reconcile_id.id)
+                    for line in  account_mov_line_obj.browse(cr, uid, line_ids):
+                        if (line.debit >= payment.credit) and (line.account_id == payment.account_id):
+                            try:
+                                account_mov_line_obj.reconcile_partial(cr, uid, [line.id,
+                                                                payment.id], type='auto')
+                                # Unlink the payment from the sale order.
+                                sale_obj.write(cr, uid, [sale_order_id],
+                                {'payment_ids': [(3, payment.id, False)]})
+                            except Exception, e:
+                                _logger.error(str(e))
+                            break

=== added file 'sale_advance_invoice/sale.py'
--- sale_advance_invoice/sale.py	1970-01-01 00:00:00 +0000
+++ sale_advance_invoice/sale.py	2014-02-20 15:23:38 +0000
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    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/>.
+#
+##############################################################################
+import time
+
+import netsvc
+from osv import osv, fields
+from openerp.tools.translate import _
+from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
+
+
+class sale_order(osv.osv):
+
+    _inherit = 'sale.order'
+    _columns = {
+        'advance_payment_method': fields.selection([
+            ('regular', 'Standard method - Deduce advance invoice in final invoice'),
+            ('progressive','Progressive - Simply invoice remaining amount in final invoice'),
+            ('final_complete','Final complete - Make complete final invoice, Refund and reassign payment of advance invoice')],
+            'How do you want to manage advance invoices?', required=True),
+        'percent_invoiced': fields.float('Percent already invoiced'),
+        'payment_ids': fields.many2many('account.move.line', 'sale_payment_line_rel', 'sale_id', 'payment_id', 'Payments'),
+    }
+
+    _defaults = {
+        'advance_payment_method': 'regular',
+        'percent_invoiced': 0.0,
+    }
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        if not default:
+            default = {}
+        default.update({
+            'payment_ids': [],
+            'percent_invoiced': 0.0,
+        })
+        return super(sale_order, self).copy(cr, uid, id, default, context=context)
+
+
+    def _prepare_invoice_lines(self, cr, uid, order, percent, context=None):
+
+        sale_obj = self.pool.get('sale.order')
+        ir_property_obj = self.pool.get('ir.property')
+        fiscal_obj = self.pool.get('account.fiscal.position')
+
+        prop = ir_property_obj.get(cr, uid,
+                    'property_account_income_categ', 'product.category', context=context)
+        prop_id = prop and prop.id or False
+        fallback_account_id = fiscal_obj.map_account(cr, uid, order.fiscal_position or False, prop_id)
+
+        inv_lines = []
+        for sale_line in order.order_line:
+            inv_lines.append((0,0, {
+                'name': sale_line.name,
+                'origin': order.name,
+                'account_id': sale_line.product_id and (sale_line.product_id.property_account_income and sale_line.product_id.property_account_income.id or sale_line.product_id.categ_id.property_account_income_categ.id) or fallback_account_id,
+                'price_unit': sale_line.price_unit * percent / 100,
+                'quantity': sale_line.product_uom_qty,
+                'discount': sale_line.discount,
+                'product_id': sale_line.product_id.id,
+                'invoice_line_tax_id': [(6,0,[x.id for x in sale_line.tax_id])],
+                'account_analytic_id': order.project_id.id or False,
+            }))
+        return inv_lines
+
+
+    def _prepare_invoice(self, cr, uid, order, lines, context=None):
+
+        inv_vals = super(sale_order, self)._prepare_invoice(cr, uid, order, lines, context=context)
+
+        sale_obj = self.pool.get('sale.order')
+        ir_property_obj = self.pool.get('ir.property')
+        fiscal_obj = self.pool.get('account.fiscal.position')
+
+
+        if order.advance_payment_method in ['final_complete','progressive']:
+            if order.advance_payment_method == 'final_complete':
+                percent = 100
+            else:
+                percent = 100 - order.percent_invoiced
+            inv_vals['invoice_line'] = self._prepare_invoice_lines(cr, uid, order, percent, context=context)
+
+        return inv_vals
+
+    def action_invoice_create(self, cr, uid, ids, grouped=False,
+                            states=['confirmed', 'done', 'exception'], 
+                            date_invoice = False, context=None):
+        if not context:
+            context = {}
+        active_ids = []
+        if 'active_ids' in context:
+            active_ids = context['active_ids']
+        move_line_obj = self.pool.get('account.move.line')
+        refund_obj = self.pool.get('account.invoice.refund')
+        # Refund and link the payments with the sale order.
+        for sale in self.browse(cr, uid, ids, context=context):
+            if sale.advance_payment_method == 'final_complete': 
+                invoices = sale.invoice_ids
+                paid_invoices = [invoice for invoice in invoices if invoice.state == 'paid']
+                payment_ids = [payment.id for invoice in paid_invoices 
+                            for payment in invoice.payment_ids]
+                move_line_obj._remove_move_reconcile(cr, uid, move_ids=payment_ids, context=context)
+                # Link the payment with the sale order.
+                res2 = self.write(cr, uid, [sale.id], {'payment_ids': [(6, 0, payment_ids)]})
+                # Search the journal
+                journal_ids = [paid_invoices[0].journal_id.id] if paid_invoices else False
+                if journal_ids:
+                    context['active_ids'] = [x.id for x in paid_invoices]
+                    refund_id = refund_obj.create(cr, uid, {'description':'Refund for final invoice','filter_refund':'cancel'}, context=context)
+                    refund_obj.compute_refund(cr, uid, [refund_id], mode='cancel', context=context)
+                    context['active_ids'] = active_ids
+        res = super(sale_order, self).action_invoice_create(
+                                        cr, uid, ids, grouped=grouped, 
+                                        states=states, date_invoice=date_invoice, 
+                                        context=context)
+        return res
+
+class sale_advance_payment_inv(osv.osv_memory):
+
+    _inherit = "sale.advance.payment.inv"
+
+    def _prepare_advance_invoice_vals(self, cr, uid, ids, context=None):
+        sale_obj = self.pool.get('sale.order')
+        ir_property_obj = self.pool.get('ir.property')
+        fiscal_obj = self.pool.get('account.fiscal.position')
+        wizard = self.browse(cr, uid, ids[0], context)
+
+        result = super(sale_advance_payment_inv,self)._prepare_advance_invoice_vals(cr, uid, ids, context=context)
+        res = []
+        for vals in result:
+            sale_id = vals[0]
+            inv_values = vals[1]
+            sale = sale_obj.browse(cr, uid, [sale_id], context=context)[0]
+            if wizard.advance_payment_method == 'percentage' and sale.advance_payment_method in ['progressive','final_complete']:
+                inv_values['invoice_line'] = sale_obj._prepare_invoice_lines(cr, uid, sale, wizard.amount, context=context)
+                sale_obj.write(cr, uid, [sale_id], {'percent_invoiced': sale.percent_invoiced + wizard.amount}, context=context)
+            res.append((sale_id, inv_values))
+        return res
+

=== added file 'sale_advance_invoice/sale_view.xml'
--- sale_advance_invoice/sale_view.xml	1970-01-01 00:00:00 +0000
+++ sale_advance_invoice/sale_view.xml	2014-02-20 15:23:38 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+    	
+        <record id="view_order_form_advance_invoice" model="ir.ui.view">
+            <field name="name">sale.order.form.advance.invoice</field>
+	    <field name="model">sale.order</field>
+	    <field name="inherit_id" ref="sale.view_order_form"/>
+	    <field name="type">form</field>
+	    <field name="arch" type="xml">
+                <field name="payment_term" position="before">
+                    <field name="advance_payment_method" attrs="{'readonly': [('state', 'not in', ['draft','sent'])]}"/>
+                </field>
+            </field>
+        </record>
+    </data>
+</openerp>


Follow ups