[PYTHON] +point of sale move lines analytic account based on shop default analytic...
authorLudovic CHEVALIER <ludovic.chevalier@heureux-cyclage.org>
Fri, 27 Jan 2017 07:46:06 +0000 (08:46 +0100)
committerLudovic CHEVALIER <ludovic.chevalier@heureux-cyclage.org>
Fri, 27 Jan 2017 07:46:06 +0000 (08:46 +0100)
__init__.py [new file with mode: 0644]
__openerp__.py [new file with mode: 0644]
point_of_sale.py [new file with mode: 0644]

diff --git a/__init__.py b/__init__.py
new file mode 100644 (file)
index 0000000..68ac9a6
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    analytic_point_of_sale module for OpenERP, Analytic Point of sale
+#    Copyright (C) 2017 L'Heureux Cyclage (<http://www.heureux-cyclage.org>)
+#    L'Heureux Cyclage
+#
+#    This file is a part of analytic_point_of_sale
+#
+#    analytic_point_of_sale is free software: you can redistribute it and/or
+#    modify it under the terms of the GNU General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or (at
+#    your option) any later version.
+#
+#    analytic_point_of_sale 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 General
+#    Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import point_of_sale
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/__openerp__.py b/__openerp__.py
new file mode 100644 (file)
index 0000000..55c9ac0
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    analytic_point_of_sale module for OpenERP, Analytic Point of sale
+#    Copyright (C) 2017 L'Heureux Cyclage (<http://www.heureux-cyclage.org>)
+#    L'Heureux Cyclage
+#
+#    This file is a part of analytic_point_of_sale
+#
+#    analytic_point_of_sale is free software: you can redistribute it and/or
+#    modify it under the terms of the GNU General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or (at
+#    your option) any later version.
+#
+#    analytic_point_of_sale 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 General
+#    Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    'name': 'Analytic Point of sale',
+    'version': '0.1',
+    'category': 'Accounting & Finance',
+    'complexity': "normal",
+    'description': """
+This module is add analycics features in point of sale
+======================================================
+
+Functionnalities :
+    * Use shops analytic account as default for move lines generate from the
+    point of sale;
+    * …
+    """,
+    'author': 'L\'Heureux Cyclage',
+    'website': 'http://www.heureux-cyclage.org/',
+    'depends': [
+        'sale',
+        'point_of_sale',
+               ],
+    'init_xml': [
+    ],
+    'update_xml': [
+    ],
+    'installable': True,
+    'auto_install': False,
+    'images': [],
+}
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/point_of_sale.py b/point_of_sale.py
new file mode 100644 (file)
index 0000000..350277c
--- /dev/null
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    analytic_point_of_sale module for OpenERP, Analytic Point of sale
+#    Copyright (C) 2017 L'Heureux Cyclage (<http://www.heureux-cyclage.org>)
+#    L'Heureux Cyclage
+#
+#    This file is a part of analytic_point_of_sale
+#
+#    analytic_point_of_sale is free software: you can redistribute it and/or
+#    modify it under the terms of the GNU General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or (at
+#    your option) any later version.
+#
+#    analytic_point_of_sale 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 General
+#    Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import osv
+from openerp.osv import orm
+from openerp.osv import fields
+from openerp.tools.translate import _
+
+
+class pos_order(orm.Model):
+    _inherit = 'pos.order'
+
+    def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None):
+        # Tricky, via the workflow, we only have one id in the ids variable
+        """Create a account move line of order grouped by products or not."""
+        account_move_obj = self.pool.get('account.move')
+        account_period_obj = self.pool.get('account.period')
+        account_tax_obj = self.pool.get('account.tax')
+        property_obj = self.pool.get('ir.property')
+        cur_obj = self.pool.get('res.currency')
+        shop_obj = self.pool.get('sale.shop')
+
+        #session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context))
+
+        if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)):
+            raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!'))
+
+        grouped_data = {}
+        have_to_group_by = session and session.config_id.group_by or False
+
+        def compute_tax(amount, tax, line):
+            if amount > 0:
+                tax_code_id = tax['base_code_id']
+                tax_amount = line.price_subtotal * tax['base_sign']
+            else:
+                tax_code_id = tax['ref_base_code_id']
+                tax_amount = line.price_subtotal * tax['ref_base_sign']
+
+            return (tax_code_id, tax_amount,)
+
+        for order in self.browse(cr, uid, ids, context=context):
+            if order.account_move:
+                continue
+            if order.state != 'paid':
+                continue
+
+            current_company = order.sale_journal.company_id
+            analytic_account_id = order.shop_id.project_id.id
+            print("#DEBUG: analytic_account_id = %s" % analytic_account_id)
+
+            group_tax = {}
+            account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
+
+            order_account = order.partner_id and \
+                            order.partner_id.property_account_receivable and \
+                            order.partner_id.property_account_receivable.id or \
+                            account_def and account_def.id or current_company.account_receivable.id
+
+            if move_id is None:
+                # Create an entry for the sale
+                move_id = account_move_obj.create(cr, uid, {
+                    'ref' : order.name,
+                    'journal_id': order.sale_journal.id,
+                }, context=context)
+
+            def insert_data(data_type, values):
+                # if have_to_group_by:
+
+                sale_journal_id = order.sale_journal.id
+                period = account_period_obj.find(cr, uid, context=dict(context or {}, company_id=current_company.id, account_period_prefer_normal=True))[0]
+
+                # 'quantity': line.qty,
+                # 'product_id': line.product_id.id,
+                values.update({
+                    'date': order.date_order[:10],
+                    'ref': order.name,
+                    'journal_id' : sale_journal_id,
+                    'period_id' : period,
+                    'move_id' : move_id,
+                    'company_id': current_company.id,
+                })
+
+                if data_type == 'product':
+                    key = ('product', values['partner_id'], values['product_id'], values['debit'] > 0)
+                elif data_type == 'tax':
+                    key = ('tax', values['partner_id'], values['tax_code_id'], values['debit'] > 0)
+                elif data_type == 'counter_part':
+                    key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
+                else:
+                    return
+
+                grouped_data.setdefault(key, [])
+
+                # if not have_to_group_by or (not grouped_data[key]):
+                #     grouped_data[key].append(values)
+                # else:
+                #     pass
+
+                if have_to_group_by:
+                    if not grouped_data[key]:
+                        grouped_data[key].append(values)
+                    else:
+                        current_value = grouped_data[key][0]
+                        current_value['quantity'] = current_value.get('quantity', 0.0) +  values.get('quantity', 0.0)
+                        current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
+                        current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
+                        current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
+                else:
+                    grouped_data[key].append(values)
+
+            #because of the weird way the pos order is written, we need to make sure there is at least one line,
+            #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
+            #are set inside the for loop)
+            #TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
+            assert order.lines, _('The POS order must have lines when calling this method')
+            # Create an move for each order line
+
+            cur = order.pricelist_id.currency_id
+            for line in order.lines:
+                tax_amount = 0
+                taxes = []
+                for t in line.product_id.taxes_id:
+                    if t.company_id.id == current_company.id:
+                        taxes.append(t)
+                computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes']
+
+                for tax in computed_taxes:
+                    tax_amount += cur_obj.round(cr, uid, cur, tax['amount'])
+                    group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id'])
+
+                    group_tax.setdefault(group_key, 0)
+                    group_tax[group_key] += cur_obj.round(cr, uid, cur, tax['amount'])
+
+                amount = line.price_subtotal
+
+                # Search for the income account
+                if  line.product_id.property_account_income.id:
+                    income_account = line.product_id.property_account_income.id
+                elif line.product_id.categ_id.property_account_income_categ.id:
+                    income_account = line.product_id.categ_id.property_account_income_categ.id
+                else:
+                    raise osv.except_osv(_('Error!'), _('Please define income '\
+                        'account for this product: "%s" (id:%d).') \
+                        % (line.product_id.name, line.product_id.id, ))
+
+                # Empty the tax list as long as there is no tax code:
+                tax_code_id = False
+                tax_amount = 0
+                while computed_taxes:
+                    tax = computed_taxes.pop(0)
+                    tax_code_id, tax_amount = compute_tax(amount, tax, line)
+
+                    # If there is one we stop
+                    if tax_code_id:
+                        break
+
+                # Create a move for the line
+                insert_data('product', {
+                    'name': line.product_id.name,
+                    'quantity': line.qty,
+                    'product_id': line.product_id.id,
+                    'account_id': income_account,
+                    'analytic_account_id': analytic_account_id,
+                    'credit': ((amount>0) and amount) or 0.0,
+                    'debit': ((amount<0) and -amount) or 0.0,
+                    'tax_code_id': tax_code_id,
+                    'tax_amount': tax_amount,
+                    'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
+                })
+
+                # For each remaining tax with a code, whe create a move line
+                for tax in computed_taxes:
+                    tax_code_id, tax_amount = compute_tax(amount, tax, line)
+                    if not tax_code_id:
+                        continue
+
+                    insert_data('tax', {
+                        'name': _('Tax'),
+                        'product_id':line.product_id.id,
+                        'quantity': line.qty,
+                        'account_id': income_account,
+                        'credit': 0.0,
+                        'debit': 0.0,
+                        'tax_code_id': tax_code_id,
+                        'tax_amount': tax_amount,
+                        'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
+                    })
+
+            # Create a move for each tax group
+            (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3)
+
+            for key, tax_amount in group_tax.items():
+                tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context)
+                insert_data('tax', {
+                    'name': _('Tax') + ' ' + tax.name,
+                    'quantity': line.qty,
+                    'product_id': line.product_id.id,
+                    'account_id': key[account_pos] or income_account,
+                    'credit': ((tax_amount>0) and tax_amount) or 0.0,
+                    'debit': ((tax_amount<0) and -tax_amount) or 0.0,
+                    'tax_code_id': key[tax_code_pos],
+                    'tax_amount': tax_amount,
+                    'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
+                })
+
+            # counterpart
+            insert_data('counter_part', {
+                'name': _("Trade Receivables"), #order.name,
+                'account_id': order_account,
+                'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
+                'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
+                'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
+            })
+
+            order.write({'state':'done', 'account_move': move_id})
+
+        all_lines = []
+        for group_key, group_data in grouped_data.iteritems():
+            print("#DEBUG: group_key = %s - group_data = %s" % (group_key, group_data))
+            for value in group_data:
+                all_lines.append((0, 0, value),)
+        if move_id: #In case no order was changed
+            self.pool.get("account.move").write(cr, uid, [move_id], {'line_id':all_lines}, context=context)
+
+        return True
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: