diff --git a/.gitignore b/.gitignore index 9a0d287..591295a 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,5 @@ ENV/ # mypy .mypy_cache/ +# vscode +.vscode/ diff --git a/account_followup/README.rst b/account_followup/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/account_followup/__init__.py b/account_followup/__init__.py new file mode 100644 index 0000000..833f33a --- /dev/null +++ b/account_followup/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import models +from . import wizard +from . import report diff --git a/account_followup/__manifest__.py b/account_followup/__manifest__.py new file mode 100644 index 0000000..e64a629 --- /dev/null +++ b/account_followup/__manifest__.py @@ -0,0 +1,31 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Payment Follow-up Management", + "version": "11.0.1.0.0", + "category": "Accounting & Finance", + "website": "https://accessbookings.com", + "author": "Kinner Vachhani (Access Bookings), \ + Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + 'account', 'mail' + ], + "data": [ + "security/account_followup_security.xml", + "security/ir.model.access.csv", + "data/account_followup.xml", + "views/account_followup_customers.xml", + "wizard/account_followup_print.xml", + "report/account_followup_report.xml", + + ], + "demo": [ + "demo/account_followup.xml" + ], + "qweb": [ + ] +} diff --git a/account_followup/data/account_followup.xml b/account_followup/data/account_followup.xml new file mode 100644 index 0000000..5643ff3 --- /dev/null +++ b/account_followup/data/account_followup.xml @@ -0,0 +1,219 @@ + + + + + + + First polite payment follow-up reminder email + ${(user.email or '')|safe} + ${user.company_id.name} Payment Reminder + ${object.email|safe} + ${object.lang} + + + + +

Dear ${object.name},

+

+ 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. + +Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to +contact our accounting department. + +

+
+Best Regards, +
+
+${user.name} + +
+
+ + +
+ + + ]]>
+
+ + + + A bit urging second payment follow-up reminder email + ${(user.email or '')|safe} + ${user.company_id.name} Payment Reminder + ${object.email|safe} + ${object.lang} + + + + +

Dear ${object.name},

+

+ We are disappointed to see that despite sending a reminder, that your account is now seriously overdue. +It is essential that immediate payment is made, otherwise we will have to consider placing a stop on your account +which means that we will no longer be able to supply your company with (goods/services). +Please, take appropriate measures in order to carry out this payment in the next 8 days. +If there is a problem with paying invoice that we are not aware of, do not hesitate to contact our accounting +department. so that we can resolve the matter quickly. +Details of due payments is printed below. +

+
+Best Regards, + +
+
+${user.name} + +
+
+ +
+ + + ]]>
+
+ + + + Urging payment follow-up reminder email + ${(user.email or '')|safe} + ${user.company_id.name} Payment Reminder + ${object.email|safe} + ${object.lang} + + + + +

Dear ${object.name},

+

+ Despite several reminders, your account is still not settled. +Unless full payment is made in next 8 days, legal action for the recovery of the debt will be taken without +further notice. +I trust that this action will prove unnecessary and details of due payments is printed below. +In case of any queries concerning this matter, do not hesitate to contact our accounting department. +

+
+Best Regards, +
+
+${user.name} +
+
+ + +
+ + + ]]>
+
+ + + + Default payment follow-up reminder e-mail + ${(user.email or '')|safe} + ${user.company_id.name} Payment Reminder + ${object.email|safe} + ${object.lang} + + + + +

Dear ${object.name},

+

+ 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. +Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to +contact our accounting department. +

+
+Best Regards, +
+
+${user.name} +
+
+ + +
+ + ]]>
+
+ + + + + + + Send first reminder email + 0 + 15 + + True + +Dear %(partner_name)s, + +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. + +Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to contact our accounting department. + +Best Regards, + + + + + + Send reminder letter and email + 1 + 30 + + + True + True + +Dear %(partner_name)s, + +We are disappointed to see that despite sending a reminder, that your account is now seriously overdue. + +It is essential that immediate payment is made, otherwise we will have to consider placing a stop on your account which means that we will no longer be able to supply your company with (goods/services). +Please, take appropriate measures in order to carry out this payment in the next 8 days. + +If there is a problem with paying invoice that we are not aware of, do not hesitate to contact our accounting department, so that we can resolve the matter quickly. + +Details of due payments is printed below. + +Best Regards, + + + + + Call the customer on the phone + 3 + 40 + + + + True + Call the customer on the phone! + +Dear %(partner_name)s, + +Despite several reminders, your account is still not settled. + +Unless full payment is made in next 8 days, then legal action for the recovery of the debt will be taken without further notice. + +I trust that this action will prove unnecessary and details of due payments is printed below. + +In case of any queries concerning this matter, do not hesitate to contact our accounting department. + +Best Regards, + + +
+
diff --git a/account_followup/demo/account_followup.xml b/account_followup/demo/account_followup.xml new file mode 100644 index 0000000..6717ad9 --- /dev/null +++ b/account_followup/demo/account_followup.xml @@ -0,0 +1,46 @@ + + + + Urging reminder email + 4 + 50 + + True + + +Dear %(partner_name)s, + +Despite several reminders, your account is still not settled. + +Unless full payment is made in next 8 days, then legal action for the recovery of the debt will be taken without further notice. + +I trust that this action will prove unnecessary and details of due payments is printed below. + +In case of any queries concerning this matter, do not hesitate to contact our accounting department. + +Best Regards, + + + + Urging reminder letter + 5 + 60 + + + True + + +Dear %(partner_name)s, + +Despite several reminders, your account is still not settled. + +Unless full payment is made in next 8 days, then legal action for the recovery of the debt will be taken without further notice. + +I trust that this action will prove unnecessary and details of due payments is printed below. + +In case of any queries concerning this matter, do not hesitate to contact our accounting department. + +Best Regards, + + + diff --git a/account_followup/models/__init__.py b/account_followup/models/__init__.py new file mode 100644 index 0000000..7a322b6 --- /dev/null +++ b/account_followup/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import account_followup diff --git a/account_followup/models/account_followup.py b/account_followup/models/account_followup.py new file mode 100644 index 0000000..80ffc91 --- /dev/null +++ b/account_followup/models/account_followup.py @@ -0,0 +1,342 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.exceptions import Warning +from odoo import fields, models, api, _ + + +class Followup(models.Model): + """ Followup model base class """ + + _name = 'account_followup.followup' + _description = 'Account Follow-up' + + # Fields Declaration + followup_line = fields.One2many('account_followup.followup.line', + 'followup_id', + string='Followup', + copy=True) + company_id = fields.Many2one('res.company', + required=True, string='Company', + default=lambda self: + self.env['res.company']. + _company_default_get( + 'account_followup.followup')) + name = fields.Char(related='company_id.name', + store=True, readonly=True) + + _sql_constraints = [('company_uniq', 'unique(company_id)', + 'Only one follow-up per company is allowed')] + + +class FollowupLine(models.Model): + """ Followup lines model """ + + _name = 'account_followup.followup.line' + _description = 'Follow-up Criteria' + _order = 'delay' + _default_mail_description = _(""" + Dear %(partner_name)s, + +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. + +Would your payment have been carried out after this mail was sent, please +ignore this message. Do not hesitate to contact our accounting department. + +Best Regards, + """) + + # Fields Declaration + name = fields.Char('Follow-Up Action', required=True) + sequence = fields.Integer(help="Gives the sequence order when \ + displaying a list of follow-up lines.") + 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) + followup_id = fields.Many2one('account_followup.followup', + 'Follow Ups', + required=True, + ondelete="cascade") + description = fields.Text('Printed Message', translate=True, + default=_default_mail_description) + send_email = fields.Boolean('Send an Email', default=True, + help="When processing, it will send an email") + send_letter = fields.Boolean('Send a Letter', default=True, + help="When processing, \ + it will print a letter") + manual_action = fields.Boolean(default=False, + help="When processing, it will set the \ + manual action to be taken for that \ + customer. ") + manual_action_note = fields.Text('Action To Do', + placeholder="e.g. Give a phone call, \ + check with others , ...") + manual_action_responsible_id = fields.Many2one('res.users', + 'Assign a Responsible', + ondelete='set null') + email_template_id = fields.Many2one('mail.template', + 'Email Template', + ondelete='set null') + + _sql_constraints = [('days_uniq', 'unique(followup_id, delay)', + 'Days of the follow-up levels must be different')] + + # Constraints and onchanges + @api.constrains('description') + def _check_description(self): + """ Check followup line description """ + for line in self: + if line.description: + try: + line.description % {'partner_name': '', 'date': '', + 'user_signature': '', + 'company_name': ''} + except Exception as e: + raise Warning(_('Your description is invalid, \ + use the right legend or %% if you want to use \ + the percent character.')) + + +class AccountMoveLine(models.Model): + """ ADD followup line and date """ + + _inherit = 'account.move.line' + + # Fields Declaration + followup_line_id = fields.Many2one('account_followup.followup.line', + 'Follow-up Level', + # restrict deletion of the followup line + ondelete='restrict') + followup_date = fields.Date('Latest Follow-up', index=1) + # 'balance' field is not the same + result = fields.Float(compute="_compute_result", string="Balance") + + # compute fields + @api.multi + @api.depends('debit', 'credit') + def _compute_result(self): + """ Compute result field + result = debit - credit + """ + for aml in self: + aml.result = aml.debit - aml.credit + + +class ResPartner(models.Model): + """ Add followup fields on partner model """ + + _inherit = 'res.partner' + + # Fields Declaration + payment_responsible_id = fields.Many2one( + 'res.users', ondelete='set null', string='Follow-up Responsible', + help="Optionally you can assign a user to this field, which " + "will make him responsible for the action.", + track_visibility="onchange", copy=False) + payment_note = fields.Text( + 'Customer Payment Promise', + help="Payment Note", track_visibility="onchange", copy=False) + payment_next_action = fields.Text( + 'Next Action', copy=False, + help="This is the next action to be taken. It will automatically be " + "set when the partner gets a follow-up level that " + "requires a manual action. ", track_visibility="onchange") + payment_next_action_date = fields.Date( + 'Next Action Date', + copy=False, help="This is when the manual follow-up is needed. The \ + date will be set to the current date when the partner gets a \ + follow-up level that requires a manual action. Can be practical to \ + set manually e.g. to see if he keeps his promises.") + unreconciled_aml_ids = fields.One2many( + 'account.move.line', 'partner_id', + auto_join=True, + domain=['&', ('full_reconcile_id', '=', False), '&', + ('account_id.deprecated', '!=', True), '&', + ('account_id.internal_type', '=', 'receivable'), + ('invoice_id', '!=', False), + ('move_id.state', '!=', 'draft')]) + latest_followup_date = fields.Date( + compute="_compute_latest_followup_date", + string="Latest Follow-up Date", help="Latest date that the follow-up \ + level of the partner was changed") + latest_followup_level_id = fields.Many2one( + 'account_followup.followup.line', + compute="_compute_latest_followup_date", store=True, + string="Latest Follow-up Level", help="The maximum follow-up level") + latest_followup_level_id_without_lit = fields.Many2one( + 'account_followup.followup.line', + compute="_compute_latest_followup_date", store=True, + string="Latest Follow-up Level without litigation", + help="The maximum follow-up level without taking into account \ + the account move lines with litigation") + payment_amount_due = fields.Float( + compute="_compute_amounts_and_date", string="Amount Due", + search="_search_payment_amount_due" + ) + payment_amount_overdue = fields.Float( + compute="_compute_amounts_and_date", + string="Amount Overdue", + search="_search_payment_amount_overdue" + ) + payment_earliest_due_date = fields.Date( + compute="_compute_amounts_and_date", + string="Worst Due Date", + # TODO Implement search functionality + # search="_payment_earliest_date_search" + ) + + # Compute methods + @api.multi + @api.depends('unreconciled_aml_ids.followup_line_id', + 'unreconciled_aml_ids.partner_id') + def _compute_latest_followup_date(self): + """ Get latest followup date """ + company = self.env.user.company_id + for partner in self: + latest_date = False + latest_level = False + latest_days = False + latest_level_without_lit = False + latest_days_without_lit = False + for aml in partner.unreconciled_aml_ids: + if (aml.company_id == company) and \ + (aml.followup_line_id) and \ + (not latest_days or + latest_days < aml.followup_line_id.delay): + latest_days = aml.followup_line_id.delay + latest_level = aml.followup_line_id.id + if (aml.company_id == company) and \ + (not latest_date or latest_date < aml.followup_date): + latest_date = aml.followup_date + if (aml.company_id == company) and \ + (aml.blocked is False) and \ + (aml.followup_line_id and + (not latest_days_without_lit or + (latest_days_without_lit < aml.followup_line_id.delay) + )): + latest_days_without_lit = aml.followup_line_id.delay + latest_level_without_lit = aml.followup_line_id.id + partner.latest_followup_date = latest_date + partner.latest_followup_level_id = latest_level + partner.latest_followup_level_id_without_lit = \ + latest_level_without_lit + + # TODO depends field missing + @api.multi + @api.depends() + def _compute_amounts_and_date(self): + """ + Function that computes values for the followup functional fields. + Note that 'payment_amount_due' is similar to 'credit' field on + res.partner except it filters on user's company. + """ + company = self.env.user.company_id + current_date = fields.Date.context_today(self) + for partner in self: + worst_due_date = False + amount_due = amount_overdue = 0.0 + for aml in partner.unreconciled_aml_ids: + if (aml.company_id == company): + date_maturity = aml.date_maturity or aml.date + if not worst_due_date or date_maturity < worst_due_date: + worst_due_date = date_maturity + amount_due += aml.result + if (date_maturity <= current_date): + amount_overdue += aml.result + partner.payment_amount_due = amount_due + partner.payment_amount_overdue = amount_overdue + partner.payment_earliest_due_date = worst_due_date + + # Search methods + def _search_payment_amount_due(self, operator, value): + query, qargs = self._get_followup_overdue_query(operator, value) + self._cr.execute(query, qargs) + res = self._cr.fetchall() + if not res: + return [('id', '=', '0')] + return [('id', 'in', [x[0] for x in res])] + + def _search_payment_amount_overdue(self, operator, value): + return self.with_context(overdue=True).\ + _search_payment_amount_due(operator, value) + + # Actions methods + @api.multi + def action_button_print(self): + """ Print Overdue Payments button action """ + self.ensure_one() + company_id = self.env.user.company_id.id + aml = self.env['account.move.line'].search([ + ('partner_id', '=', self.id), + ('account_id.internal_type', '=', 'receivable'), + ('full_reconcile_id', '=', False), + ('company_id', '=', company_id), + '|', ('date_maturity', '=', False), + ('date_maturity', '<=', fields.Date.context_today(self))]) + if not aml: + raise Warning(_('Error!'), _("The partner does not have any " + "accounting entries to print in the " + "overdue report for the current " + "company.")) + followup = self.env['account_followup.followup'].\ + search([('company_id', '=', company_id)]) + if not followup: + raise Warning(_('Error!'), + _("There is no followup plan defined for " + "the current company.")) + self.message_post(body=_('Printed overdue payments report')) + return self.env.ref('account.action_report_print_overdue').\ + report_action(self) + + @api.multi + def action_partner_mail(self): + self.ensure_one() + for partner in self: + partners_to_email = partner.child_ids.filtered( + lambda r: r.type == 'invoice' and + r.email) + if not partners_to_email and partner.email: + partners_to_email = partner + if partners_to_email: + level = partner.latest_followup_level_id_without_lit + for partner_to_email in partners_to_email: + if level and level.send_email \ + and level.email_template_id \ + and level.email_template_id.id: + level.email_template_id.send_mail(partner_to_email.id) + else: + mail_template = self.env.ref( + "account_followup." + "email_template_account_followup_default") + mail_template.send_mail(partner_to_email.id) + + # Busniess methods + def _get_followup_overdue_query(self, operator, value): + """ + Get followup query + """ + overdue = self._context.get('overdue', False) + company_id = self.env.user.company_id.id + having_where_clause = ' (SUM(bal2) %s %s)' % (operator, value) + overdue_only_str = overdue and 'AND date_maturity <= NOW()' or '' + return ('''SELECT pid AS partner_id, SUM(bal2) FROM + (SELECT CASE WHEN bal IS NOT NULL THEN bal + ELSE 0.0 END AS bal2, p.id as pid FROM + (SELECT (debit-credit) AS bal, partner_id + FROM account_move_line l + WHERE account_id IN + (SELECT id FROM account_account + WHERE internal_type=\'receivable\' AND + not deprecated) + ''' + overdue_only_str + ''' + AND full_reconcile_id IS NULL + AND company_id = %s ) AS l + RIGHT JOIN res_partner p + ON p.id = partner_id ) AS pl + GROUP BY pid HAVING ''' + having_where_clause, + [company_id]) diff --git a/account_followup/report/__init__.py b/account_followup/report/__init__.py new file mode 100644 index 0000000..7034907 --- /dev/null +++ b/account_followup/report/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agp +from . import account_followup_report diff --git a/account_followup/report/account_followup_report.py b/account_followup/report/account_followup_report.py new file mode 100644 index 0000000..f8edbf9 --- /dev/null +++ b/account_followup/report/account_followup_report.py @@ -0,0 +1,55 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agp +from odoo import fields, models +from odoo import tools + + +class AccountFollowupStat(models.Model): + _name = "account_followup.stat" + _description = "Follow-up Statistics" + _rec_name = 'partner_id' + _auto = False + _order = 'date_move' + + partner_id = fields.Many2one('res.partner', readonly=True) + date_move = fields.Date('First move', readonly=True) + date_move_last = fields.Date('Last move', readonly=True) + date_followup = fields.Date('Latest followup', readonly=True) + followup_id = fields.Many2one('account_followup.followup.line', + 'Follow Ups', readonly=True, + ondelete="cascade") + balance = fields.Float(readonly=True) + debit = fields.Float(readonly=True) + credit = fields.Float(readonly=True) + company_id = fields.Many2one('res.company', readonly=True) + blocked = fields.Boolean(readonly=True) + + def init(self): + cr = self.env.cr + tools.drop_view_if_exists(cr, 'account_followup_stat') + cr.execute(""" + create or replace view account_followup_stat as ( + SELECT + l.id as id, + l.partner_id AS partner_id, + min(l.date) AS date_move, + max(l.date) AS date_move_last, + max(l.followup_date) AS date_followup, + max(l.followup_line_id) AS followup_id, + sum(l.debit) AS debit, + sum(l.credit) AS credit, + sum(l.debit - l.credit) AS balance, + l.company_id AS company_id, + l.blocked as blocked + FROM + account_move_line l + LEFT JOIN account_account a ON (l.account_id = a.id) + WHERE + a.deprecated IS NOT False AND + a.internal_type = 'receivable' AND + l.full_reconcile_id is NULL AND + l.partner_id IS NOT NULL + GROUP BY + l.id, l.partner_id, l.company_id, l.blocked + )""") diff --git a/account_followup/report/account_followup_report.xml b/account_followup/report/account_followup_report.xml new file mode 100644 index 0000000..b8d0315 --- /dev/null +++ b/account_followup/report/account_followup_report.xml @@ -0,0 +1,53 @@ + + + + + account_followup.stat.graph + account_followup.stat + + + + + + + + + + account_followup.stat.search + account_followup.stat + + + + + + + + + + + + + + + + + + + + + + Follow-ups Sent + account_followup.stat + form + pivot + {'search_default_followup_level':1} + + + + + + diff --git a/account_followup/security/account_followup_security.xml b/account_followup/security/account_followup_security.xml new file mode 100644 index 0000000..6978869 --- /dev/null +++ b/account_followup/security/account_followup_security.xml @@ -0,0 +1,16 @@ + + + + Account Follow-up multi company rule + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + Account Follow-up Statistics by Partner Rule + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + diff --git a/account_followup/security/ir.model.access.csv b/account_followup/security/ir.model.access.csv new file mode 100644 index 0000000..591cc34 --- /dev/null +++ b/account_followup/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_followup_followup_line,account_followup.followup.line,model_account_followup_followup_line,account.group_account_invoice,1,0,0,0 +access_account_followup_followup_line_manager,account_followup.followup.line.manager,model_account_followup_followup_line,account.group_account_manager,1,1,1,1 +access_account_followup_followup_accountant,account_followup.followup user,model_account_followup_followup,account.group_account_invoice,1,0,0,0 +access_account_followup_followup_manager,account_followup.followup.manager,model_account_followup_followup,account.group_account_manager,1,1,1,1 +access_account_followup_stat_invoice,account_followup.stat.invoice,model_account_followup_stat,account.group_account_invoice,1,1,0,0 +access_account_followup_stat_by_partner_manager,account_followup.stat.by.partner,model_account_followup_stat_by_partner,account.group_account_user,1,1,0,0 diff --git a/account_followup/test/account_followup.yml b/account_followup/test/account_followup.yml new file mode 100644 index 0000000..d64e546 --- /dev/null +++ b/account_followup/test/account_followup.yml @@ -0,0 +1,25 @@ +- + In order to test account follow-up module in odoo, I change the state of invoice to "open". +- + !record {model: account.invoice, id: account.demo_invoice_0}: + check_total: 14.0 + date_invoice: !eval "'%s-06-2' %(datetime.now().year)" + invoice_line: + - account_id : account.a_sale + name: 'Test PC' + quantity: 1.0 + journal_id: account.bank_journal + partner_id: base.res_partner_12 + reference_type: none +- + !workflow {model: account.invoice, action: invoice_open, ref: account.demo_invoice_0} +- + I create a follow-up. +- + !record {model: account_followup.print, id: account_followup_print_0}: + {} +- + I will process follow-ups +- + !python {model: account_followup.print}: | + self.do_process(cr, uid, [ref("account_followup_print_0")], {"active_ids": [ref("account_followup.account_followup_print_menu")], "active_id": ref("account_followup.account_followup_print_menu"),}) diff --git a/account_followup/tests/__init__.py b/account_followup/tests/__init__.py new file mode 100644 index 0000000..ba6f27d --- /dev/null +++ b/account_followup/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_account_followup diff --git a/account_followup/tests/test_account_followup.py b/account_followup/tests/test_account_followup.py new file mode 100644 index 0000000..c9fb772 --- /dev/null +++ b/account_followup/tests/test_account_followup.py @@ -0,0 +1,65 @@ +# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright 2018 Access Bookings Ltd (https://accessbookings.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +from odoo import fields + + +class TestAccountFollowup(TransactionCase): + def setUp(self): + """ setUp ***""" + super(TestAccountFollowup, self).setUp() + self.user = self.env['res.users'] + self.user_id = self.user + self.partner = self.env['res.partner'] + self.invoice = self.env['account.invoice'] + self.invoice_line = self.env['account.invoice.line'] + self.wizard = self.env['account_followup.print'] + self.followup_id = self.env['account_followup.followup'] + + self.partner_id = self.partner.create({'name': 'Test Company', + 'email': 'test@localhost', + 'is_company': True, + }) + self.followup_id = self.ref('account_followup.demo_followup1') + self.account_id = self.ref('account.data_account_type_receivable') + self.journal_id = self.env['account.journal'].search([('type', '=', + 'sale')], + limit=1) + self.pay_account_id = self.env['account.journal'].search([ + ('type', '=', 'bank')], limit=1) + # self.period_id = self.ref("account.period_10") + self.first_followup_line_id = self.ref( + "account_followup.demo_followup_line1") + self.last_followup_line_id = self.ref( + "account_followup.demo_followup_line3") + self.product_id = self.ref("product.product_product_6") + self.account_user_type = self.env.ref( + 'account.data_account_type_receivable') + invoice_line_data = [ + (0, 0, + { + 'name': "LCD Screen", + 'product_id': self.product_id, + 'quantity': 5, + 'price_unit': 200, + 'account_id': self.env['account.account'].search([ + ('user_type_id', '=', self.env.ref( + 'account.data_account_type_revenue').id)], + limit=1).id, + } + ) + ] + self.invoice_id = self.invoice.create(dict( + partner_id=self.partner_id.id, + account_id=self.account_id, + journal_id=self.journal_id.id, + invoice_line_ids=invoice_line_data + )) + + # Confirm the invoice + self.invoice_id.action_invoice_open() + + # self.voucher = self.env["account.voucher"] + self.current_date = fields.Date.context_today() diff --git a/account_followup/views/account_followup_customers.xml b/account_followup/views/account_followup_customers.xml new file mode 100644 index 0000000..57ebdd4 --- /dev/null +++ b/account_followup/views/account_followup_customers.xml @@ -0,0 +1,167 @@ + + + + + + res.partner.followup.inherit.tree + res.partner + + + + + + + + + + + + + + + + + + + + res.partner.followup.inherit.tree + res.partner + + + + + + + + + + Search + res.partner + + + + + + + + + + + + + + + + + + + + + + Manual Follow-Ups + + res.partner + form + tree,form + [('payment_amount_due', '>', 0.0)] + {'Followupfirst':True, 'search_default_todo': True} + + + + + + res.partner.followup.form.inherit + + res.partner + + + + +
+
+

+ The , the latest payment follow-up + was: +

+ + + +