~serpent-consulting-services/openerp-usa/shipping_api_6-1

« back to all changes in this revision

Viewing changes to account_cash_discount_us/account_cash_discount.py

  • Committer: npgllc
  • Date: 2012-08-02 17:13:27 UTC
  • Revision ID: npgllc-20120802171327-2xgyyjjb5d1kx26y
Removed all the 6.0 compatible modules

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
6
 
#    Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
7
 
#
8
 
#    This program is free software: you can redistribute it and/or modify
9
 
#    it under the terms of the GNU General Public License as published by
10
 
#    the Free Software Foundation, either version 3 of the License, or
11
 
#    (at your option) any later version.
12
 
#
13
 
#    This program is distributed in the hope that it will be useful,
14
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
#    GNU General Public License for more details.
17
 
#
18
 
#    You should have received a copy of the GNU General Public License
19
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>
20
 
#
21
 
##############################################################################
22
 
 
23
 
from collections import defaultdict
24
 
import mx.DateTime
25
 
from mx.DateTime import RelativeDateTime
26
 
 
27
 
from osv import fields, osv
28
 
import decimal_precision as dp
29
 
from tools.amount_to_text_en import amount_to_text
30
 
from amount_to_words import amount_to_words
31
 
 
32
 
def append_to_list(item, main_list):
33
 
    """
34
 
    @param item: the item to be added to the list. Can be an integer or list
35
 
    @param main_list: The list to which the item needs to be added.
36
 
    @return: The new list formed by appending the item to the list
37
 
    """
38
 
    if not isinstance(item, list):
39
 
        item = [item]
40
 
    return main_list + item
41
 
 
42
 
class account_payment_term(osv.osv):
43
 
    _inherit = "account.payment.term"
44
 
    _columns = {
45
 
        'cash_discount_ids': fields.one2many('account.cash.discount', 'payment_id', 'Cash Discounts'),
46
 
        }
47
 
 
48
 
    def get_discounts(self, cr, uid, id, base_date, context=None):
49
 
        """
50
 
        return the list of (date,percentage) ordered by date for the
51
 
        payment term with the corresponding id. return [] if no cash
52
 
        discount are defined. base_date is the date from where the
53
 
        discounts are computed.
54
 
        """
55
 
        res = []
56
 
        for pt in self.browse(cr, uid, id, context=context):
57
 
            for d in pt.cash_discount_ids:
58
 
                res.append(
59
 
                    ((mx.DateTime.strptime(base_date, '%Y-%m-%d') +\
60
 
                      RelativeDateTime(days=d.delay)).strftime("%Y-%m-%d"),
61
 
                     d.discount)
62
 
                    )
63
 
        res.sort(cmp=lambda x, y: cmp(x[0], y[0]))
64
 
        return res
65
 
 
66
 
account_payment_term()
67
 
 
68
 
 
69
 
class account_invoice(osv.osv):
70
 
    """
71
 
        Add discount calculation to invoice
72
 
    """
73
 
    _inherit = 'account.invoice'
74
 
 
75
 
    def _get_discount(self, cr, uid,  ids, field_name, args, context=None):
76
 
        """
77
 
        Calculate the value of variable date_discount (Discount Date) and amount_discounted (Discounted Total)
78
 
        """
79
 
        res = {}
80
 
        for invoice in self.browse(cr, uid, ids, context=context):
81
 
            res = defaultdict(list)
82
 
            res[invoice.id] = {
83
 
                'date_discount': invoice.date_due,
84
 
                'amount_discounted': invoice.amount_total
85
 
            }
86
 
            if not invoice.date_invoice:
87
 
                invoice_date = mx.DateTime.today().strftime("%Y-%m-%d")
88
 
                self.write(cr, uid, [invoice.id], {'date_invoice': invoice_date}, context=context)
89
 
            else:
90
 
                invoice_date = invoice.date_invoice
91
 
            discounts = invoice.payment_term and invoice.payment_term.get_discounts(invoice_date, context=context)
92
 
            if discounts:
93
 
                discount_total = 0.0
94
 
                non_discount_total = 0.0
95
 
                for line in invoice.invoice_line:
96
 
                    if line.cash_discount:
97
 
                        discount_total += line.price_subtotal
98
 
                        line_cash_discount =  round((1.0 - discounts[0][1]) * line.price_subtotal)
99
 
                    else:
100
 
                        non_discount_total += line.price_subtotal
101
 
                        line_cash_discount = 0.0
102
 
                    self.pool.get('account.invoice.line').write(cr, uid, line.id, {'cash_discount': line_cash_discount}, context=context)
103
 
                # assume taxes are never discountable
104
 
                non_discount_total += invoice.amount_tax
105
 
                # There may be more than one - return the earliest
106
 
                res[invoice.id] = {
107
 
                    'date_discount': discounts[0][0],
108
 
                    'amount_discounted': round(((1.0 - discounts[0][1]) * discount_total) + non_discount_total, 2)
109
 
                    }
110
 
            return res
111
 
 
112
 
    _columns = {
113
 
        'date_discount': fields.function(_get_discount, method=True, type='date', string='Discount Date', multi='all'),
114
 
        'amount_discounted': fields.function(_get_discount, method=True, type='float', digits_compute=dp.get_precision('Account'),
115
 
                                             string='Discounted Total', multi='all'),
116
 
        }
117
 
 
118
 
account_invoice()
119
 
 
120
 
class account_voucher(osv.osv):
121
 
    _inherit = 'account.voucher'
122
 
 
123
 
    def calc_supp_diff(self, cr, uid, ids, context=None):
124
 
        """
125
 
            Called by calculate/re-calculate action.
126
 
            This method will update the credit lines on voucher lines.
127
 
            If the field "auto_match" marked, this method will run a matching routine
128
 
        """
129
 
        for vch in self.browse(cr, uid, ids, context=context):
130
 
            for line in vch.line_dr_ids:
131
 
#                Update the credit lines and discount lines so that matching routine can use the latest available credits and discounts
132
 
                line._update_supp_discount_lines(context=context)
133
 
        for vch in self.browse(cr, uid, ids, context=context):
134
 
            amount = 0.00
135
 
            for line in vch.line_dr_ids:
136
 
                if line.pay:
137
 
                    amount += float(line.amount)
138
 
            if amount:
139
 
                self.write(cr, uid, [vch.id], {'amount': amount}, context=context)
140
 
        return {'nodestroy': True}
141
 
 
142
 
    def _update_discounts(self, lines, vch_date):
143
 
        date_discount = False
144
 
        amount_discount = False
145
 
        for line in lines:
146
 
            if 'date_discount' in line:
147
 
                date_discount = line['date_discount']
148
 
                amount_discounted = line['amount_discounted']
149
 
            else:
150
 
                return
151
 
            if line['amount'] >= line['amount_unreconciled']:
152
 
                amount_discount = 0.0
153
 
            elif vch_date <= date_discount and line['amount'] <= line['amount_unreconciled'] and line['amount'] >= amount_discounted:
154
 
                amount_discount = max(line['amount_unreconciled'] - amount_discounted, 0.0)
155
 
            elif vch_date <= date_discount and line['amount'] < amount_discounted:
156
 
                amount_discount = max(line['amount_unreconciled'] - amount_discounted, 0.0)
157
 
            line['cash_discount'] = amount_discount
158
 
            line['amount_difference'] = line['amount_unreconciled'] - line['amount'] - amount_discount
159
 
 
160
 
    def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
161
 
        """
162
 
        Function to update fields in customer payment form on changing customer
163
 
        """
164
 
        currency_pool = self.pool.get('res.currency')
165
 
        journal_pool = self.pool.get('account.journal')
166
 
        invoice_pool = self.pool.get('account.invoice')
167
 
        line_pool = self.pool.get('account.voucher.line')
168
 
        partner_pool = self.pool.get('res.partner')
169
 
        company_currency = False
170
 
        default = super(account_voucher, self).onchange_partner_id(cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=context)
171
 
        if not partner_id:
172
 
            return default
173
 
        # we have to clear out lines, because new lines will be created by the change
174
 
        if not journal_id:
175
 
            partner = partner_pool.browse(cr, uid, partner_id, context=context)
176
 
            # FIXME: Possibly unwanted code. Cannot find field payment_meth_id.
177
 
            if hasattr(partner, 'payment_meth_id') and partner.payment_meth_id:
178
 
                payment_mode_pool = self.pool.get('payment.mode')
179
 
                payment_meth = payment_mode_pool.browse(cr, uid, partner.payment_meth_id.id, context=context)
180
 
                if payment_meth:
181
 
                    default['value']['journal_id'] = payment_meth.journal.id
182
 
                    journal_id = payment_meth.journal.id
183
 
        line_ids = line_pool.search(cr, uid, [('voucher_id', 'in', ids)], context=context)
184
 
        if line_ids:
185
 
            line_pool.unlink(cr, uid, line_ids, context=context)
186
 
        if journal_id:
187
 
            journal = journal_pool.browse(cr, uid, journal_id, context=context)
188
 
            company_currency = journal and journal.company_id and journal.company_id.currency_id and journal.company_id.currency_id.id
189
 
        total_credit = 0.0
190
 
        total_debit = 0.0
191
 
        vch_date = False
192
 
        for vch in self.browse(cr, uid, ids, context=context):
193
 
            vch_date = vch.date
194
 
        if default and 'value' in default and 'line_cr_ids' in default['value']:
195
 
            for line in default['value']['line_cr_ids']:
196
 
                invoice_id = invoice_pool.search(cr, uid, [('number', '=', line['name'])], context=context)
197
 
                if invoice_id:
198
 
                    line['invoice_id'] = invoice_id[0]
199
 
                    invoice = invoice_pool.browse(cr, uid, invoice_id[0], context=context)
200
 
                    date_discount = invoice.date_discount
201
 
                    amount_discounted = invoice.amount_discounted
202
 
                    line['date_discount'] = date_discount
203
 
                    line['amount_discounted'] = amount_discounted
204
 
                else:
205
 
                    line['date_discount'] = False
206
 
                    line['amount_discounted'] = 0.0
207
 
                line['amount'] = 0.0
208
 
                total_credit += line['type'] == 'cr' and line['amount_unreconciled'] or 0.0
209
 
                total_debit += line['type'] == 'dr' and line['amount_unreconciled'] or 0.0
210
 
            # first, see if we can find an invoice matching the amount to be applied
211
 
            found = False
212
 
 
213
 
            def calc_amount(line, total):
214
 
                return min(line['amount_unreconciled'], total)
215
 
 
216
 
            lines = default['value']['line_cr_ids']
217
 
            if lines:
218
 
                return default
219
 
 
220
 
            # if only one, assign it
221
 
            if len(lines) == 1:
222
 
                amount = price
223
 
                lines[0]['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id, amount) or amount
224
 
                if lines[0]['type'] == 'cr':
225
 
                    total_credit -= amount
226
 
                else:
227
 
                    total_debit -= amount
228
 
                found = True
229
 
                self._update_discounts(lines, vch_date)
230
 
            if not found:
231
 
                for line in lines:
232
 
                    if line['amount_unreconciled'] == price:
233
 
                        if line['type'] == 'cr':
234
 
                            amount = calc_amount(line, total_credit)
235
 
                            line['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id, amount) or amount
236
 
                            total_credit -= amount
237
 
                            found = True
238
 
                            break
239
 
                        else:
240
 
                            amount = calc_amount(line, total_debit)
241
 
                            line['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id, amount) or amount
242
 
                            total_debit -= amount
243
 
                            found = True
244
 
                            break
245
 
            if not found:
246
 
                # see if we can find a combination that matches
247
 
                def search(lines, price):
248
 
                    for i in range(len(lines)):
249
 
                        if lines[i]['amount_unreconciled'] == price:
250
 
                            return [lines[i]]
251
 
                    for i in range(len(lines)):
252
 
                        for j in range(i + 1, len(lines)):
253
 
                            if lines[i]['amount_unreconciled'] + lines[j]['amount_unreconciled'] == price:
254
 
                                return [lines[i], lines[j]]
255
 
                    for i in range(len(lines)):
256
 
                        for j in range(i + 1, len(lines)):
257
 
                            for k in range(j + 1, len(lines)):
258
 
                                if lines[i]['amount_unreconciled'] + lines[j]['amount_unreconciled'] + lines[k]['amount_unreconciled'] == price:
259
 
                                    return [lines[i], lines[j], lines[k]]
260
 
 
261
 
                line_ids = search(lines, price)
262
 
 
263
 
                if line_ids:
264
 
                    for line in line_ids:
265
 
                        if line['type'] == 'cr':
266
 
                            amount = calc_amount(line, line['amount_unreconciled'])
267
 
                            line['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id, amount)
268
 
                            total_debit -= amount
269
 
                            found = True
270
 
                        else:
271
 
                            amount = calc_amount(line, line['amount_unreconciled'])
272
 
                            line['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id, amount)
273
 
                            total_credit -= amount
274
 
                            found = True
275
 
            if not found:
276
 
                # see if we can find a match using discounted amount
277
 
                def search2(lines, price):
278
 
                    for i in range(len(lines)):
279
 
                        if min(lines[i]['amount_unreconciled'], lines[i]['amount_discounted']) == price:
280
 
                            return [lines[i]]
281
 
                    for i in range(len(lines)):
282
 
                        for j in range(i + 1, len(lines)):
283
 
                            if min(lines[i]['amount_unreconciled'], lines[i]['amount_discounted']) + min(lines[j]['amount_unreconciled'],
284
 
                                   lines[j]['amount_discounted']) == price:
285
 
                                return [lines[i], lines[j]]
286
 
                    for i in range(len(lines)):
287
 
                        for j in range(i + 1, len(lines)):
288
 
                            for k in range(j + 1, len(lines)):
289
 
                                if min(lines[i]['amount_unreconciled'], lines[i]['amount_discounted']) + min(lines[j]['amount_unreconciled'],
290
 
                                       lines[j]['amount_discounted']) + min(lines[k]['amount_unreconciled'], lines[k]['amount_discounted']) == price:
291
 
                                    return [lines[i], lines[j], lines[k]]
292
 
                #FIXME: Is this call really necessary?
293
 
                line_ids = search(lines, price)
294
 
                lines = default['value']['line_cr_ids']
295
 
                line_ids = search2(lines, price)
296
 
                if line_ids:
297
 
                    found = True
298
 
                    for line in line_ids:
299
 
                        amount = calc_amount(line, min(line['amount_unreconciled'], line['amount_discounted']))
300
 
                        if line['type'] == 'cr':
301
 
                            total_debit -= amount
302
 
                        else:
303
 
                            total_credit -= amount
304
 
                        line['amount'] = currency_pool.compute(cr, uid, company_currency, currency_id,
305
 
                                                               min(line['amount_unreconciled'], line['amount_discounted']))
306
 
                lines = default['value']['line_cr_ids']
307
 
 
308
 
#            FIXME: removing amount from line_dr_ids (Credits on customer payment form) line.
309
 
#                    and amount from line_cr_ids (Invoice and outstanding transactions on customer payment form) line
310
 
#                    I do not think this this is a good solution. But it works.
311
 
#                    The whole "onchange_partner_id" function need a re-thinking (may have to rewrite it completely instead of calling super ).
312
 
 
313
 
        if default:
314
 
            for credit_line in default['value'].get('line_dr_ids', []):
315
 
                credit_line['amount'] = 0.0
316
 
            for invoce_line in default['value'].get('line_cr_ids', []):
317
 
                invoce_line['amount'] = 0.0
318
 
                invoce_line['amount_difference'] = invoce_line['amount_unreconciled']
319
 
        return default
320
 
 
321
 
    def calc_cash_discount(self, cr, uid, ids, vch, line, context=None):
322
 
        """
323
 
            Calculate discount per line
324
 
        """
325
 
        if context is None:
326
 
            context = {}
327
 
        total_allocated = 0.0
328
 
        for line in vch.line_ids:
329
 
            total_allocated += line.amount
330
 
        context.update({'total_allocated': total_allocated, 'total_amount': vch.date})
331
 
        amount_discount = 0.0
332
 
        if line.amount >= line.amount_unreconciled or line.amount < 0.01:
333
 
            amount_discount = 0.0
334
 
        elif line.amount >= line.amount_discounted and vch.date <= line.date_discount:
335
 
            amount_discount = line.amount_unreconciled - line.amount_discounted
336
 
        return amount_discount
337
 
 
338
 
    def onchange_amount(self, cr, uid, ids, amount, context=None):
339
 
        """
340
 
        Function to convert amount to words
341
 
        """
342
 
        result = {}
343
 
        currency_format =  self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_format
344
 
        if currency_format == 'us':
345
 
            amount_in_words = amount_to_words(amount)
346
 
        else:
347
 
            amount_in_words = amount_to_text(amount)
348
 
        result['amount_in_word'] = amount_in_words
349
 
        return {'value': result}
350
 
 
351
 
account_voucher()
352
 
 
353
 
class account_voucher_line(osv.osv):
354
 
    _inherit = 'account.voucher.line'
355
 
 
356
 
    def onchange_supp_pay(self, cr, uid, ids, line_amount, pay, amount_unreconciled, par_cr_ids, par_amount, credit_used, discount_used,
357
 
                          writeoff_amount=0, context=None):
358
 
        """
359
 
        Function to automatically fill the values when the pay checkbox is selected
360
 
        """
361
 
        ret = {}
362
 
#         FIXME: Please verify the following two implementation.
363
 
#         writeoff_amount = (not writeoff_amount and [0] or [writeoff_amount])[0]
364
 
#         discount_used = (not discount_used and [0] or [discount_used])[0]
365
 
#         credit_used = (not credit_used and [0] or [credit_used])[0]
366
 
        writeoff_amount = writeoff_amount or 0
367
 
        discount_used = discount_used or 0
368
 
        credit_used = credit_used or 0
369
 
 
370
 
        if pay:
371
 
            tot_amt = par_amount + line_amount
372
 
            for credit in par_cr_ids:
373
 
                if 'pay' in credit[2] and 'amount' in credit[2]:
374
 
                    tot_amt -= (credit[2]['amount'])
375
 
            if tot_amt < 0:
376
 
                ret['amount'] = 0.0
377
 
            else:
378
 
                amount_unreconciled -= (discount_used + writeoff_amount + credit_used)
379
 
#                FIXME: Please verify the following two operations
380
 
#                ret['amount'] = min(tot_amt, (amount_unreconciled < 0) and 0 or amount_unreconciled)
381
 
                ret['amount'] = min(tot_amt, amount_unreconciled)
382
 
        else:
383
 
            ret['amount'] = 0.0
384
 
        return {'value': ret}
385
 
 
386
 
    def recalculate_supp_values(self, cr, uid, ids, context=None):
387
 
        """
388
 
            Re-calculate button action
389
 
        """
390
 
        if isinstance(ids, list):
391
 
            ids = ids[0]
392
 
        voucher_line = self.browse(cr, uid, ids, context=context)
393
 
        if voucher_line.discount_used:
394
 
            self.write(cr, uid, ids, {'amount': voucher_line.amount_unreconciled - voucher_line.discount_used}, context=context)
395
 
        self.pool.get('account.voucher').calc_supp_diff(cr, uid, [voucher_line.voucher_id.id], context=context)
396
 
        return True
397
 
 
398
 
    def _update_credit_lines(self, cr, uid, ids, context=None):
399
 
        """
400
 
        Function to update the credit lines in payment lines
401
 
        """
402
 
        credits_used_pool = self.pool.get('account.voucher.line.credits_to_use')
403
 
        for line in self.browse(cr, uid, ids, context=context):
404
 
            credits_lines_used = [x.orginal_credit_line_id.id for x in line.available_credits]
405
 
            for credit_line in line.voucher_id.line_dr_ids:
406
 
                if credit_line.id not in credits_lines_used and line.invoice_id:
407
 
                    credits_used_pool.create(cr, uid, {
408
 
                                            'voucher_line_id': line.id,
409
 
                                            'orginal_credit_line_id': credit_line.id,
410
 
                                            'use_credit': False,
411
 
                                            'inv_credit': credit_line.move_line_id.id,
412
 
                                            'discount_window_date': credit_line.date_original,
413
 
                                            'orginal_amount': credit_line.amount_original,
414
 
                                            'available_amount': credit_line.amount_unreconciled - credit_line.pending_credits,
415
 
                                            'discount_amount': 0.0,
416
 
                                            'gl_account': credit_line.account_id.id,
417
 
                                            }, context=context)
418
 
                else:
419
 
                    to_update_credit_line_ids = credits_used_pool.search(cr, uid, [('voucher_line_id', '=', line.id),
420
 
                                                                        ('orginal_credit_line_id', '=', credit_line.id)], context=context)
421
 
                    credits_used_pool.write(cr, uid, to_update_credit_line_ids,
422
 
                                            {'available_amount': credit_line.amount_unreconciled - credit_line.pending_credits}, context=context)
423
 
 
424
 
    def _update_discount_lines(self, cr, uid, ids, context=None):
425
 
        """
426
 
        Function to update the discount lines in payment lines
427
 
        """
428
 
        discount_used_pool = self.pool.get('account.voucher.line.discount_to_use')
429
 
        user_pool = self.pool.get('res.users')
430
 
        user = user_pool.browse(cr, uid, uid, context=context)
431
 
        for line in self.browse(cr, uid, ids, context=context):
432
 
            if line.invoice_id:
433
 
                if not line.invoice_id.date_discount or not line.voucher_id.date or  line.voucher_id.date > line.invoice_id.date_discount:
434
 
                    #customer is not eligible for the discount
435
 
                    continue
436
 
 
437
 
                discount = line.invoice_id.amount_total - line.invoice_id.amount_discounted
438
 
                date_discount = line.invoice_id.date_discount
439
 
                discount_found = False
440
 
                for discount_line in line.available_discounts:
441
 
                    if line.invoice_id.payment_term.id == discount_line.inv_payment_terms.id:
442
 
                        discount_found = True
443
 
                        continue
444
 
                if not discount_found and user.company_id.sales_discount_account:
445
 
                    discount_used_pool.create(cr, uid, {
446
 
                                             'voucher_line_id': line.id,
447
 
                                             'use_discount': False,
448
 
                                             'inv_payment_terms': line.invoice_id.payment_term.id,
449
 
                                             'discount_window_date': date_discount,
450
 
                                             'proposed_discount': discount,
451
 
                                             'discount_amount': 0.0,
452
 
                                             'gl_account': user.company_id.sales_discount_account.id
453
 
                                             }, context=context)
454
 
 
455
 
    def _update_supp_discount_lines(self, cr, uid, ids, context=None):
456
 
        """
457
 
        Function to update the discount lines in payment lines
458
 
        """
459
 
        discount_used_pool = self.pool.get('account.voucher.line.discount_to_use')
460
 
        user_pool = self.pool.get('res.users')
461
 
        user = user_pool.browse(cr, uid, uid, context=context)
462
 
        for line in self.browse(cr, uid, ids, context=context):
463
 
            if line.invoice_id:
464
 
                if not line.invoice_id.date_discount or not line.voucher_id.date or  line.voucher_id.date > line.invoice_id.date_discount:
465
 
                    #customer is not eligible for the discount
466
 
                    continue
467
 
 
468
 
                discount = line.invoice_id.amount_total  - line.invoice_id.amount_discounted
469
 
                date_discount = line.invoice_id.date_discount
470
 
                discount_found = False
471
 
                for discount_line in line.available_discounts:
472
 
                    if line.invoice_id.payment_term.id == discount_line.inv_payment_terms.id:
473
 
                        discount_found = True
474
 
                        continue
475
 
                if not discount_found and user.company_id.purchase_discount_account:
476
 
                    discount_used_pool.create(cr, uid, {
477
 
                                              'voucher_line_id': line.id,
478
 
                                              'use_discount': False,
479
 
                                              'inv_payment_terms': line.invoice_id.payment_term.id,
480
 
                                              'discount_window_date': date_discount,
481
 
                                              'proposed_discount': discount,
482
 
                                              'discount_amount': 0.0,
483
 
                                              'gl_account':user.company_id.purchase_discount_account.id
484
 
                                        }, context=context)
485
 
        for line in self.browse(cr, uid, ids, context=context):
486
 
            if line.pay:
487
 
                for discount_line in line.available_discounts:
488
 
                    if not discount_line.credit_selected:
489
 
                        discount_used_pool.write(cr, uid, discount_line.id, {'use_discount': True, 'discount_amount': discount_line.proposed_discount},
490
 
                                                 context=context)
491
 
                self.write(cr, uid, line.id, {'amount': line.amount_original - line.discount_used}, context=context)
492
 
 
493
 
    def _compute_discount_used(self, cr, uid, ids, name, args, context=None):
494
 
        """
495
 
        Function to calculate the value of variable discount used
496
 
        """
497
 
        res = {}
498
 
        for line in self.browse(cr, uid, ids, context=context):
499
 
            res[line.id] = 0.00
500
 
            for discount_line in line.available_discounts:
501
 
                if discount_line.use_discount:
502
 
                    res[line.id] += discount_line.discount_amount
503
 
        return res
504
 
 
505
 
    def _get_disc(self, cr, uid, ids, mode='cust', context=None):
506
 
        """
507
 
        @param cr: current row of the database
508
 
        @param uid: id of the user currently logged in
509
 
        @param ids: ids of the selected records
510
 
        @param mode: cust or supp
511
 
        @param context: context
512
 
        @return: dictionary
513
 
        """
514
 
        invoice_obj = self.pool.get('account.invoice')
515
 
        voucher_line = self.browse(cr, uid, ids, context=context)
516
 
        line_type_map = {'cust': 'dr', 'supp': 'cr'}
517
 
        res = {}
518
 
        res = defaultdict(list)
519
 
        for line in voucher_line:
520
 
            if not line.move_line_id:
521
 
                continue
522
 
            date_discount = False
523
 
            if line.type == line_type_map[mode]:
524
 
                res[line.id] = {
525
 
                    'date_discount': '',
526
 
                    'amount_discounted': line.amount,
527
 
                    'cash_discount': 0.0,
528
 
                    }
529
 
                if line.type == 'cust':
530
 
                    res[line.id].update({
531
 
                        'amount_difference': line.amount_unreconciled - line.amount
532
 
                        })
533
 
                else:
534
 
                    res[line.id].update({
535
 
                        'supp_amount_difference': line.amount_unreconciled - line.amount,
536
 
                        'discount': False
537
 
                        })
538
 
                continue
539
 
            invoice_number = line.move_line_id.name or line.move_line_id.ref
540
 
            invoice_ids = False
541
 
            if invoice_number and invoice_number != '/':
542
 
                invoice_ids = invoice_obj.search(cr, uid, [('number', '=', str(invoice_number))], context=context)
543
 
            if not invoice_ids:
544
 
                invoice_number = line.move_line_id.move_id and line.move_line_id.move_id.name or line.move_line_id.move_id.ref
545
 
                if invoice_number and invoice_number != '/':
546
 
                    invoice_ids = invoice_obj.search(cr, uid, [('number', '=', str(invoice_number))], context=context)
547
 
            for invoice in invoice_obj.browse(cr, uid, invoice_ids, context=context):
548
 
                date_discount = invoice.date_due
549
 
                amount_discounted = invoice.amount_total
550
 
                if mode == 'cust':
551
 
                    res[line.id] = {
552
 
                        'date_discount': invoice.date_due,
553
 
                        'amount_discounted': invoice.amount_total
554
 
                        }
555
 
                else:
556
 
                    res[line.id] = {
557
 
                        'date_discount': invoice.date_due,
558
 
                        'supp_amount_difference': invoice.amount_total
559
 
                        }
560
 
                if not invoice.date_invoice:
561
 
                    invoice_date = mx.DateTime.today().strftime("%Y-%m-%d")
562
 
                    self.write(cr, uid, [invoice.id], {'date_invoice': invoice_date}, context=context)
563
 
                else:
564
 
                    invoice_date = invoice.date_invoice
565
 
                discounts = invoice.payment_term and invoice.payment_term.get_discounts(invoice_date, context=context)
566
 
                if discounts:
567
 
                    line_obj = self.pool.get('account.invoice.line')
568
 
                    discount_total = 0.0
569
 
                    non_discount_total = 0.0
570
 
                    for invline in invoice.invoice_line:
571
 
                        if invline.cash_discount:
572
 
                            discount_total += invline.price_subtotal
573
 
                            line_cash_discount =  round((1.0 - discounts[0][1]) * invline.price_subtotal)
574
 
                        else:
575
 
                            non_discount_total += invline.price_subtotal
576
 
                            line_cash_discount =  0.0
577
 
                        line_obj.write(cr, uid, invline.id, {'cash_discount': line_cash_discount}, context=context)
578
 
                    # assume taxes are never discountable
579
 
                    non_discount_total += invoice.amount_tax
580
 
                    # There may be more than one - return the earliest
581
 
                    date_discount = discounts[0][0]
582
 
                    amount_discounted = round(((1.0 - discounts[0][1]) * discount_total) + non_discount_total, 2)
583
 
            amount_discount = 0.0
584
 
            if line.amount >= line.amount_unreconciled or line.amount < 0.01:
585
 
                amount_discount = 0.0
586
 
            elif line.voucher_id.date <= date_discount and line.amount <= line.amount_unreconciled:
587
 
                amount_discount = max(line.amount_unreconciled - amount_discounted, 0.0)
588
 
            elif line.voucher_id.date <= date_discount and line.amount < amount_discounted:
589
 
                amount_discount = max(line.amount_unreconciled - amount_discounted, 0.0)
590
 
            else:
591
 
                amount_discount = 0.0
592
 
 
593
 
            res[line.id] = {
594
 
                'date_discount': date_discount,
595
 
                'amount_discounted': amount_discounted,
596
 
                'cash_discount': amount_discount,
597
 
                }
598
 
            if mode == 'cust':
599
 
                res[line.id].update({
600
 
                    'amount_difference': line.amount_unreconciled - line.amount - line.credit_used - line.discount_used - line.writeoff_amount
601
 
                    })
602
 
            else:
603
 
                discount = False
604
 
                if line.available_discounts:
605
 
                    discount = True
606
 
                res[line.id].update({
607
 
                    'supp_amount_difference': line.amount_unreconciled - line.amount - line.credit_used - line.discount_used - line.writeoff_amount,
608
 
                    'discount': discount
609
 
                    })
610
 
        return  res
611
 
 
612
 
    def _get_discount(self, cr, uid,  ids, field_name, args, context=None):
613
 
        """
614
 
        Function to calculate the value of variable date_discount,amount_discounted,cash_discount,amount_difference
615
 
        return the values as dictionary
616
 
        """
617
 
        return self._get_disc(cr, uid, ids, mode='cust', context=context)
618
 
 
619
 
    def _get_supp_discount(self, cr, uid, ids, field_name, args, context=None):
620
 
        """
621
 
        Function to calculate the value of variable date_discount,amount_discounted,cash_discount,amount_difference
622
 
        return the values as dictionary
623
 
        """
624
 
        return  self._get_disc(cr, uid, ids, mode='supp', context=context)
625
 
 
626
 
    _columns = {
627
 
        'date_discount': fields.function(_get_discount, method=True, type='date', string='Discount Date',
628
 
            multi='all'),
629
 
        'amount_discounted': fields.function(_get_discount, method=True, type='float', digits_compute=dp.get_precision('Account'),
630
 
                                              string='Discounted Total', multi='all'),
631
 
        'cash_discount': fields.function(_get_discount, method=True, type='float', multi="all", digits_compute=dp.get_precision('Account'),
632
 
                                          string='Cash Discount', sequence=20),
633
 
        'amount_difference': fields.function(_get_discount, method=True, multi='all', type='float', string='Unpaid Amt', digits=(16, 2)),
634
 
        'supp_amount_difference': fields.function(_get_supp_discount, method=True, multi='all1', type='float', string='Unpaid Amt', digits=(16, 2)),
635
 
        'interest': fields.float(string='Interest', digits=(16,2)),
636
 
 
637
 
        'discount_used': fields.function(_compute_discount_used, method=True, type='float', string='Discount Used', store=False, readonly=True,
638
 
                                         sequence=10),
639
 
        'available_discounts':fields.one2many('account.voucher.line.discount_to_use', 'voucher_line_id', 'Available Discounts'),
640
 
        'discount': fields.function(_get_supp_discount, method=True, multi='all1', type='boolean', string='Discount', readonly=True, sequence=20)
641
 
       }
642
 
 
643
 
    def clear_values(self, cr, uid, ids, context=None):
644
 
        """
645
 
        Clear the selected credits, discounts and writeoffs from voucher line
646
 
        """
647
 
        voucher_line = self.browse(cr, uid, ids[0], context=context)
648
 
        writeoff_ids = []
649
 
        available_credits_ids = []
650
 
        available_discounts_ids = []
651
 
        if hasattr(voucher_line, 'writeoff_ids'):
652
 
            for lines in self.read(cr, uid, ids, ['available_credits', 'writeoff_ids', 'available_discounts'], context=context):
653
 
                writeoff_ids = append_to_list(lines['writeoff_ids'], writeoff_ids)
654
 
                available_credits_ids = append_to_list(lines['available_credits'], available_credits_ids)
655
 
                available_discounts_ids = append_to_list(lines['available_discounts'], available_discounts_ids)
656
 
        else:
657
 
            for lines in self.read(cr, uid, ids, ['available_credits', 'available_discounts'], context=context):
658
 
                available_credits_ids = append_to_list(lines['available_credits'], available_credits_ids)
659
 
                available_discounts_ids = append_to_list(lines['available_discounts'], available_discounts_ids)
660
 
 
661
 
        self.pool.get('account.voucher.line.writeoff').unlink(cr, uid, writeoff_ids, context=context)
662
 
        self.pool.get('account.voucher.line.credits_to_use').write(cr, uid, available_credits_ids, {
663
 
            'use_credit':False,
664
 
            'discount_amount':0.0
665
 
            }, context=context)
666
 
        self.pool.get('account.voucher.line.discount_to_use').write(cr, uid, available_discounts_ids, {
667
 
            'use_discount':False,
668
 
            'discount_amount':0.0
669
 
            }, context=context)
670
 
        return True
671
 
 
672
 
account_voucher_line()
673
 
 
674
 
class product_product(osv.osv):
675
 
    """
676
 
        Add new account configuration fields to product
677
 
    """
678
 
    _inherit = 'product.product'
679
 
    _columns = {
680
 
        'cash_discount': fields.boolean('Cash Discount?'),
681
 
        'purchase_discount_account': fields.many2one('account.account', 'Purchase Discount Account'),
682
 
        'sales_discount_account': fields.many2one('account.account', 'Sales Discount Account', domain=[('type', '!=', 'view'),
683
 
                                                                                                       ('type', '!=', 'consolidation')]),
684
 
        'purchase_discount_journal': fields.many2one('account.journal', 'Purchase Discount Journal', domain=[('type', '!=', 'view'),
685
 
                                                                                                             ('type', '!=', 'consolidation'),
686
 
                                                                                                             ('type', '=', 'purchase')]),
687
 
        'sales_discount_journal': fields.many2one('account.journal', 'Sales Discount Journal', domain=[('type', '=', 'sale')]),
688
 
        }
689
 
 
690
 
    _defaults = {
691
 
        'cash_discount': True,
692
 
        }
693
 
 
694
 
product_product()
695
 
 
696
 
 
697
 
class account_invoice_line(osv.osv):
698
 
    """
699
 
        option to disable discount calculation per invoice line
700
 
    """
701
 
    _inherit = 'account.invoice.line'
702
 
    _columns = {
703
 
        'cash_discount': fields.boolean('Cash Discount?'),
704
 
        }
705
 
    _defaults = {
706
 
        'cash_discount': True,
707
 
        }
708
 
 
709
 
    def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False,
710
 
                          address_invoice_id=False, currency_id=False, context=None):
711
 
        """
712
 
            check if the discount is applicable to newly selected product
713
 
        """
714
 
        result = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom, qty=qty, name=name, type=type, partner_id=partner_id, fposition_id=fposition_id, price_unit=price_unit,
715
 
                                                                     address_invoice_id=address_invoice_id, currency_id=currency_id, context=context)
716
 
        if product:
717
 
            res = self.pool.get('product.product').read(cr, uid, product, ['cash_discount'], context=context)
718
 
            result['value']['cash_discount'] = res['cash_discount']
719
 
        else:
720
 
            result['value']['cash_discount'] = True
721
 
        return result
722
 
 
723
 
account_invoice_line()
724
 
 
725
 
 
726
 
class account_voucher_line_discount_to_use(osv.osv):
727
 
    """
728
 
        Dynamically generated discount lines that are applicable per voucher line
729
 
    """
730
 
    _name = "account.voucher.line.discount_to_use"
731
 
    _rec_name = 'discount_window_date'
732
 
    _columns = {
733
 
        'voucher_line_id': fields.many2one('account.voucher.line', 'Account Voucher Line', ondelete='cascade', readonly=True),
734
 
        'use_discount': fields.boolean('Use Discount', help='Used to indicate if the cash discount should be used/taken when calculating payment.',
735
 
                                       required=True),
736
 
        'inv_payment_terms': fields.many2one('account.payment.term', 'Invoice Payment Terms', help='Payments terms description'),
737
 
        'discount_window_date': fields.date('Discount Window Date',
738
 
                                            help='The last day of the discount window.\
739
 
                                            To receive discounts payments must be paid on or before this date.'),
740
 
        'proposed_discount': fields.float('Proposed Discount',
741
 
                                          help='This is the proposed full discount based on the Invoice Payment Terms and \
742
 
                                          the Original Amount.', readonly=True),
743
 
        'discount_amount': fields.float('Discount Amt', help='Enter the amount of discount to be given.', required=True),
744
 
        'gl_account': fields.many2one('account.account', 'G/L Account',
745
 
                                      help='Enter the General Ledger account number to record taking \
746
 
                                      the cash discount.', required=True),
747
 
        'credit_selected': fields.boolean('Credit Selected'),
748
 
        }
749
 
 
750
 
    _defaults = {
751
 
        'credit_selected': False,
752
 
        }
753
 
 
754
 
    def onchage_use_discount(self, cr, uid, ids, use_discount, proposed_discount, context=None):
755
 
        """
756
 
        Fill the value with proposed discount when use discount check box is clicked and remove the value when use discount is unchecked
757
 
        """
758
 
        res = {}
759
 
        if use_discount:
760
 
            res['value'] = {'discount_amount': proposed_discount}
761
 
        else:
762
 
            res['value'] = {'discount_amount': 0}
763
 
        res['value']['credit_selected'] = True
764
 
        return res
765
 
 
766
 
    def onchage_discount_amount(self, cr, uid, ids, proposed_discount, discount_amount, context=None):
767
 
        """
768
 
        Function to check discount amount entered in discount line of payment line
769
 
        """
770
 
        res = {}
771
 
        if discount_amount < 0:
772
 
            res['value'] = {'discount_amount': 0, 'use_discount': False }
773
 
            res['warning'] = {'title': 'Discount not in the limit', 'message': 'Discount should not be a negative value.'}
774
 
 
775
 
        elif discount_amount > proposed_discount:
776
 
            res['value'] = {'discount_amount': proposed_discount, 'use_discount': True }
777
 
            res['warning'] = {
778
 
                'title': 'Discount not in the limit',
779
 
                'message': 'Please adjust the Discount Amt value to be less than or equal to the Proposed Discount.'
780
 
                }
781
 
        elif discount_amount == 0.0:
782
 
            res['value'] = {'use_discount': False}
783
 
        else:
784
 
            res['value'] = {'use_discount': True}
785
 
        return res
786
 
 
787
 
account_voucher_line_discount_to_use()
788
 
 
789
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: