350277c3ba77c87241ffeab2d22476cb87408826
[burette/analytic_point_of_sale.git] / point_of_sale.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 # analytic_point_of_sale module for OpenERP, Analytic Point of sale
5 # Copyright (C) 2017 L'Heureux Cyclage (<http://www.heureux-cyclage.org>)
6 # L'Heureux Cyclage
7 #
8 # This file is a part of analytic_point_of_sale
9 #
10 # analytic_point_of_sale is free software: you can redistribute it and/or
11 # modify it under the terms of the GNU General Public License as published
12 # by the Free Software Foundation, either version 3 of the License, or (at
13 # your option) any later version.
14 #
15 # analytic_point_of_sale is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
18 # Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along
21 # with this program. If not, see <http://www.gnu.org/licenses/>.
22 #
23 ##############################################################################
24
25 from openerp.osv import osv
26 from openerp.osv import orm
27 from openerp.osv import fields
28 from openerp.tools.translate import _
29
30
31 class pos_order(orm.Model):
32 _inherit = 'pos.order'
33
34 def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None):
35 # Tricky, via the workflow, we only have one id in the ids variable
36 """Create a account move line of order grouped by products or not."""
37 account_move_obj = self.pool.get('account.move')
38 account_period_obj = self.pool.get('account.period')
39 account_tax_obj = self.pool.get('account.tax')
40 property_obj = self.pool.get('ir.property')
41 cur_obj = self.pool.get('res.currency')
42 shop_obj = self.pool.get('sale.shop')
43
44 #session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context))
45
46 if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)):
47 raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!'))
48
49 grouped_data = {}
50 have_to_group_by = session and session.config_id.group_by or False
51
52 def compute_tax(amount, tax, line):
53 if amount > 0:
54 tax_code_id = tax['base_code_id']
55 tax_amount = line.price_subtotal * tax['base_sign']
56 else:
57 tax_code_id = tax['ref_base_code_id']
58 tax_amount = line.price_subtotal * tax['ref_base_sign']
59
60 return (tax_code_id, tax_amount,)
61
62 for order in self.browse(cr, uid, ids, context=context):
63 if order.account_move:
64 continue
65 if order.state != 'paid':
66 continue
67
68 current_company = order.sale_journal.company_id
69 analytic_account_id = order.shop_id.project_id.id
70 print("#DEBUG: analytic_account_id = %s" % analytic_account_id)
71
72 group_tax = {}
73 account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
74
75 order_account = order.partner_id and \
76 order.partner_id.property_account_receivable and \
77 order.partner_id.property_account_receivable.id or \
78 account_def and account_def.id or current_company.account_receivable.id
79
80 if move_id is None:
81 # Create an entry for the sale
82 move_id = account_move_obj.create(cr, uid, {
83 'ref' : order.name,
84 'journal_id': order.sale_journal.id,
85 }, context=context)
86
87 def insert_data(data_type, values):
88 # if have_to_group_by:
89
90 sale_journal_id = order.sale_journal.id
91 period = account_period_obj.find(cr, uid, context=dict(context or {}, company_id=current_company.id, account_period_prefer_normal=True))[0]
92
93 # 'quantity': line.qty,
94 # 'product_id': line.product_id.id,
95 values.update({
96 'date': order.date_order[:10],
97 'ref': order.name,
98 'journal_id' : sale_journal_id,
99 'period_id' : period,
100 'move_id' : move_id,
101 'company_id': current_company.id,
102 })
103
104 if data_type == 'product':
105 key = ('product', values['partner_id'], values['product_id'], values['debit'] > 0)
106 elif data_type == 'tax':
107 key = ('tax', values['partner_id'], values['tax_code_id'], values['debit'] > 0)
108 elif data_type == 'counter_part':
109 key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
110 else:
111 return
112
113 grouped_data.setdefault(key, [])
114
115 # if not have_to_group_by or (not grouped_data[key]):
116 # grouped_data[key].append(values)
117 # else:
118 # pass
119
120 if have_to_group_by:
121 if not grouped_data[key]:
122 grouped_data[key].append(values)
123 else:
124 current_value = grouped_data[key][0]
125 current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
126 current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
127 current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
128 current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
129 else:
130 grouped_data[key].append(values)
131
132 #because of the weird way the pos order is written, we need to make sure there is at least one line,
133 #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
134 #are set inside the for loop)
135 #TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
136 assert order.lines, _('The POS order must have lines when calling this method')
137 # Create an move for each order line
138
139 cur = order.pricelist_id.currency_id
140 for line in order.lines:
141 tax_amount = 0
142 taxes = []
143 for t in line.product_id.taxes_id:
144 if t.company_id.id == current_company.id:
145 taxes.append(t)
146 computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes']
147
148 for tax in computed_taxes:
149 tax_amount += cur_obj.round(cr, uid, cur, tax['amount'])
150 group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id'])
151
152 group_tax.setdefault(group_key, 0)
153 group_tax[group_key] += cur_obj.round(cr, uid, cur, tax['amount'])
154
155 amount = line.price_subtotal
156
157 # Search for the income account
158 if line.product_id.property_account_income.id:
159 income_account = line.product_id.property_account_income.id
160 elif line.product_id.categ_id.property_account_income_categ.id:
161 income_account = line.product_id.categ_id.property_account_income_categ.id
162 else:
163 raise osv.except_osv(_('Error!'), _('Please define income '\
164 'account for this product: "%s" (id:%d).') \
165 % (line.product_id.name, line.product_id.id, ))
166
167 # Empty the tax list as long as there is no tax code:
168 tax_code_id = False
169 tax_amount = 0
170 while computed_taxes:
171 tax = computed_taxes.pop(0)
172 tax_code_id, tax_amount = compute_tax(amount, tax, line)
173
174 # If there is one we stop
175 if tax_code_id:
176 break
177
178 # Create a move for the line
179 insert_data('product', {
180 'name': line.product_id.name,
181 'quantity': line.qty,
182 'product_id': line.product_id.id,
183 'account_id': income_account,
184 'analytic_account_id': analytic_account_id,
185 'credit': ((amount>0) and amount) or 0.0,
186 'debit': ((amount<0) and -amount) or 0.0,
187 'tax_code_id': tax_code_id,
188 'tax_amount': tax_amount,
189 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
190 })
191
192 # For each remaining tax with a code, whe create a move line
193 for tax in computed_taxes:
194 tax_code_id, tax_amount = compute_tax(amount, tax, line)
195 if not tax_code_id:
196 continue
197
198 insert_data('tax', {
199 'name': _('Tax'),
200 'product_id':line.product_id.id,
201 'quantity': line.qty,
202 'account_id': income_account,
203 'credit': 0.0,
204 'debit': 0.0,
205 'tax_code_id': tax_code_id,
206 'tax_amount': tax_amount,
207 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
208 })
209
210 # Create a move for each tax group
211 (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3)
212
213 for key, tax_amount in group_tax.items():
214 tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context)
215 insert_data('tax', {
216 'name': _('Tax') + ' ' + tax.name,
217 'quantity': line.qty,
218 'product_id': line.product_id.id,
219 'account_id': key[account_pos] or income_account,
220 'credit': ((tax_amount>0) and tax_amount) or 0.0,
221 'debit': ((tax_amount<0) and -tax_amount) or 0.0,
222 'tax_code_id': key[tax_code_pos],
223 'tax_amount': tax_amount,
224 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
225 })
226
227 # counterpart
228 insert_data('counter_part', {
229 'name': _("Trade Receivables"), #order.name,
230 'account_id': order_account,
231 'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
232 'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
233 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
234 })
235
236 order.write({'state':'done', 'account_move': move_id})
237
238 all_lines = []
239 for group_key, group_data in grouped_data.iteritems():
240 print("#DEBUG: group_key = %s - group_data = %s" % (group_key, group_data))
241 for value in group_data:
242 all_lines.append((0, 0, value),)
243 if move_id: #In case no order was changed
244 self.pool.get("account.move").write(cr, uid, [move_id], {'line_id':all_lines}, context=context)
245
246 return True
247
248 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: