[I18N] Removed duplicated terms for inherited [_sql]_constraints -> 400 terms removed
[odoo/odoo.git] / addons / account_followup / account_followup.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from osv import fields, osv
23 from lxml import etree
24
25 from tools.translate import _
26
27
28 class followup(osv.osv):
29     _name = 'account_followup.followup'
30     _description = 'Account Follow-up'
31     _rec_name = 'name'
32     _columns = {
33         'followup_line': fields.one2many('account_followup.followup.line', 'followup_id', 'Follow-up'),
34         'company_id': fields.many2one('res.company', 'Company', required=True),
35         'name': fields.related('company_id', 'name', string = "Name"),
36     }
37     _defaults = {
38         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account_followup.followup', context=c),
39     }
40     _sql_constraints = [('company_uniq', 'unique(company_id)', 'Only one follow-up per company is allowed')] 
41
42
43 class followup_line(osv.osv):
44
45     def _get_default_template(self, cr, uid, ids, context=None):
46         try:
47             return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_followup', 'email_template_account_followup_default')[1]
48         except ValueError:
49             return False
50
51     _name = 'account_followup.followup.line'
52     _description = 'Follow-up Criteria'
53     _columns = {
54         'name': fields.char('Follow-Up Action', size=64, required=True),
55         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of follow-up lines."),
56         'delay': fields.integer('Due Days', help="The number of days after the due date of the invoice to wait before sending the reminder.  Could be negative if you want to send a polite alert beforehand.", required=True),
57         'followup_id': fields.many2one('account_followup.followup', 'Follow Ups', required=True, ondelete="cascade"),
58         'description': fields.text('Printed Message', translate=True),
59         'send_email':fields.boolean('Send an Email', help="When processing, it will send an email"),
60         'send_letter':fields.boolean('Send a Letter', help="When processing, it will print a letter"),
61         'manual_action':fields.boolean('Manual Action', help="When processing, it will set the manual action to be taken for that customer. "),
62         'manual_action_note':fields.text('Action To Do', placeholder="e.g. Give a phone call, check with others , ..."),
63         'manual_action_responsible_id':fields.many2one('res.users', 'Assign a Responsible', ondelete='set null'),
64         'email_template_id':fields.many2one('email.template', 'Email Template', ondelete='set null'),
65     }
66     _order = 'delay'
67     _sql_constraints = [('days_uniq', 'unique(followup_id, delay)', 'Days of the follow-up levels must be different')]
68     _defaults = {
69         'send_email': True,
70         'send_letter': True,
71         'manual_action':False,
72         'description': """
73         Dear %(partner_name)s,
74
75 Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take appropriate measures in order to carry out this payment in the next 8 days.
76
77 Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to contact our accounting department at (+32).10.68.94.39.
78
79 Best Regards,
80 """,
81     'email_template_id': _get_default_template,
82     }
83
84
85     def _check_description(self, cr, uid, ids, context=None):
86         for line in self.browse(cr, uid, ids, context=context):
87             if line.description:
88                 try:
89                     line.description % {'partner_name': '', 'date':'', 'user_signature': '', 'company_name': ''}
90                 except:
91                     return False
92         return True
93
94     _constraints = [
95         (_check_description, 'Your description is invalid, use the right legend or %% if you want to use the percent character.', ['description']),
96     ]
97
98
99 class account_move_line(osv.osv):
100
101     def _get_result(self, cr, uid, ids, name, arg, context=None):
102         res = {}
103         for aml in self.browse(cr, uid, ids, context=context):
104             res[aml.id] = aml.debit - aml.credit
105         return res
106
107     _inherit = 'account.move.line'
108     _columns = {
109         'followup_line_id': fields.many2one('account_followup.followup.line', 'Follow-up Level', 
110                                         ondelete='restrict'), #restrict deletion of the followup line
111         'followup_date': fields.date('Latest Follow-up', select=True),
112         'result':fields.function(_get_result, type='float', method=True, 
113                                 string="Balance") #'balance' field is not the same
114     }
115
116
117
118 class email_template(osv.osv):
119     _inherit = 'email.template'
120
121     # Adds current_date to the context.  That way it can be used to put
122     # the account move lines in bold that are overdue in the email
123     def render_template(self, cr, uid, template, model, res_id, context=None):
124         context['current_date'] = fields.date.context_today(cr, uid, context)
125         return super(email_template, self).render_template(cr, uid, template, model, res_id, context=context)
126
127
128 class res_partner(osv.osv):
129
130     def fields_view_get(self, cr, uid, view_id=None, view_type=None, context=None, toolbar=False, submenu=False):
131         res = super(res_partner, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context,
132                                                        toolbar=toolbar, submenu=submenu)
133         context = context or {}
134         if view_type == 'form' and context.get('Followupfirst'):
135             doc = etree.XML(res['arch'], parser=None, base_url=None)
136             first_node = doc.xpath("//page[@name='followup_tab']")
137             root = first_node[0].getparent()
138             root.insert(0, first_node[0])
139             res['arch'] = etree.tostring(doc, encoding="utf-8")
140         return res
141
142     def _get_latest(self, cr, uid, ids, names, arg, context=None, company_id=None):
143         res={}
144         if company_id == None:
145             company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
146         else:
147             company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
148         for partner in self.browse(cr, uid, ids, context=context):
149             amls = partner.unreconciled_aml_ids
150             latest_date = False
151             latest_level = False
152             latest_days = False
153             latest_level_without_lit = False
154             latest_days_without_lit = False
155             for aml in amls:
156                 if (aml.company_id == company) and (aml.followup_line_id != False) and (not latest_days or latest_days < aml.followup_line_id.delay):
157                     latest_days = aml.followup_line_id.delay
158                     latest_level = aml.followup_line_id.id
159                 if (aml.company_id == company) and (not latest_date or latest_date < aml.followup_date):
160                     latest_date = aml.followup_date
161                 if (aml.company_id == company) and (aml.blocked == False) and (aml.followup_line_id != False and 
162                             (not latest_days_without_lit or latest_days_without_lit < aml.followup_line_id.delay)):
163                     latest_days_without_lit =  aml.followup_line_id.delay
164                     latest_level_without_lit = aml.followup_line_id.id
165             res[partner.id] = {'latest_followup_date': latest_date,
166                                'latest_followup_level_id': latest_level,
167                                'latest_followup_level_id_without_lit': latest_level_without_lit}
168         return res
169
170     def do_partner_manual_action(self, cr, uid, partner_ids, context=None): 
171         #partner_ids -> res.partner
172         for partner in self.browse(cr, uid, partner_ids, context=context):
173             #Check action: check if the action was not empty, if not add
174             action_text= ""
175             if partner.payment_next_action:
176                 action_text = (partner.payment_next_action or '') + "\n" + (partner.latest_followup_level_id_without_lit.manual_action_note or '')
177             else:
178                 action_text = partner.latest_followup_level_id_without_lit.manual_action_note or ''
179
180             #Check date: put the minimum date if it existed already
181             action_date = (partner.payment_next_action_date and min(partner.payment_next_action_date, fields.date.context_today(cr, uid, context))
182                            ) or fields.date.context_today(cr, uid, context)
183
184             # Check responsible: if partner has not got a responsible already, take from follow-up
185             responsible_id = False
186             if partner.payment_responsible_id:
187                 responsible_id = partner.payment_responsible_id.id
188             else:
189                 p = partner.latest_followup_level_id_without_lit.manual_action_responsible_id
190                 responsible_id = p and p.id or False
191             self.write(cr, uid, [partner.id], {'payment_next_action_date': action_date,
192                                         'payment_next_action': action_text,
193                                         'payment_responsible_id': responsible_id})
194
195     def do_partner_print(self, cr, uid, wizard_partner_ids, data, context=None):
196         #wizard_partner_ids are ids from special view, not from res.partner
197         if not wizard_partner_ids:
198             return {}
199         data['partner_ids'] = wizard_partner_ids
200         datas = {
201              'ids': [],
202              'model': 'account_followup.followup',
203              'form': data
204         }
205         return {
206             'type': 'ir.actions.report.xml',
207             'report_name': 'account_followup.followup.print',
208             'datas': datas,
209             }
210
211     def do_partner_mail(self, cr, uid, partner_ids, context=None):
212         #partner_ids are res.partner ids
213         # If not defined by latest follow-up level, it will be the default template if it can find it
214         mtp = self.pool.get('email.template')
215         unknown_mails = 0
216         for partner in self.browse(cr, uid, partner_ids, context=context):
217             if partner.email and partner.email.strip():
218                 level = partner.latest_followup_level_id_without_lit
219                 if level and level.send_email and level.email_template_id and level.email_template_id.id:
220                     mtp.send_mail(cr, uid, level.email_template_id.id, partner.id, context=context)
221                 else:
222                     mail_template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 
223                                                     'account_followup', 'email_template_account_followup_default')
224                     mtp.send_mail(cr, uid, mail_template_id[1], partner.id, context=context)
225             else:
226                 unknown_mails = unknown_mails + 1
227                 action_text = _("Email not sent because of email address of partner not filled in")
228                 if partner.payment_next_action_date:
229                     payment_action_date = min(fields.date.context_today(cr, uid, context), partner.payment_next_action_date)
230                 else:
231                     payment_action_date = fields.date.context_today(cr, uid, context)
232                 if partner.payment_next_action:
233                     payment_next_action = partner.payment_next_action + " + " + action_text
234                 else:
235                     payment_next_action = action_text
236                 self.write(cr, uid, [partner.id], {'payment_next_action_date': payment_action_date,
237                                                    'payment_next_action': payment_next_action}, context=context)
238         return unknown_mails
239
240     def action_done(self, cr, uid, ids, context=None):
241         return self.write(cr, uid, ids, {'payment_next_action_date': False, 'payment_next_action':'', 'payment_responsible_id': False}, context=context)
242
243     def do_button_print(self, cr, uid, ids, context=None):
244         assert(len(ids) == 1)
245         self.message_post(cr, uid, [ids[0]], body=_('Printed overdue payments report'), context=context)
246         datas = {
247              'ids': ids,
248              'model': 'res.partner',
249              'form': self.read(cr, uid, ids[0], context=context)
250         }
251         return {
252             'type': 'ir.actions.report.xml',
253             'report_name': 'account.overdue',
254             'datas': datas,
255             'nodestroy' : True
256         }
257
258
259     _inherit = "res.partner"
260     _columns = {
261         'payment_responsible_id':fields.many2one('res.users', ondelete='set null', string='Follow-up Responsible', 
262                                                  help="Responsible for making sure the action happens."), 
263         'payment_note':fields.text('Customer Payment Promise', help="Payment Note"),
264         'payment_next_action':fields.text('Next Action',
265                                     help="This is the next action to be taken by the user.  It will automatically be set when the action fields are empty and the partner gets a follow-up level that requires a manual action. "), 
266         'payment_next_action_date':fields.date('Next Action Date',
267                                     help="This is when further follow-up is needed.  The date will have been set to the current date if the action fields are empty and the partner gets a follow-up level that requires a manual action. "), 
268         'unreconciled_aml_ids':fields.one2many('account.move.line', 'partner_id', domain=['&', ('reconcile_id', '=', False), '&', 
269                             ('account_id.active','=', True), '&', ('account_id.type', '=', 'receivable'), ('state', '!=', 'draft')]), 
270         'latest_followup_date':fields.function(_get_latest, method=True, type='date', string="Latest Follow-up Date", 
271                             help="Latest date that the follow-up level of the partner was changed", 
272                             store=False, 
273                             multi="latest"), 
274         'latest_followup_level_id':fields.function(_get_latest, method=True, 
275             type='many2one', relation='account_followup.followup.line', string="Latest Follow-up Level", 
276             help="The maximum follow-up level", 
277             store=False, 
278             multi="latest"), 
279         'latest_followup_level_id_without_lit':fields.function(_get_latest, method=True, 
280             type='many2one', relation='account_followup.followup.line', string="Latest Follow-up Level without litigation", 
281             help="The maximum follow-up level without taking into account the account move lines with litigation", 
282             store=False, 
283             multi="latest"),
284         'payment_amount_due':fields.related('credit', type='float', string="Total amount due", readonly=True),
285         }
286
287 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: